mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-03-28 00:21:47 +01:00
Merge pull request #3185 from mikiher/genre-search
Adds genres to gloabl search
This commit is contained in:
commit
604ae080ac
@ -5,6 +5,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-2 authorSearchCardContent h-full">
|
<div class="flex-grow px-2 authorSearchCardContent h-full">
|
||||||
<p class="truncate text-sm">{{ name }}</p>
|
<p class="truncate text-sm">{{ name }}</p>
|
||||||
|
<p class="text-xs text-gray-400">{{ $getString('LabelXBooks', [numBooks]) }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -23,6 +24,9 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
name() {
|
name() {
|
||||||
return this.author.name
|
return this.author.name
|
||||||
|
},
|
||||||
|
numBooks() {
|
||||||
|
return this.author.numBooks
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {},
|
methods: {},
|
||||||
@ -33,7 +37,7 @@ export default {
|
|||||||
<style>
|
<style>
|
||||||
.authorSearchCardContent {
|
.authorSearchCardContent {
|
||||||
width: calc(100% - 80px);
|
width: calc(100% - 80px);
|
||||||
height: 40px;
|
height: 44px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
36
client/components/cards/GenreSearchCard.vue
Normal file
36
client/components/cards/GenreSearchCard.vue
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex h-full px-1 overflow-hidden">
|
||||||
|
<div class="w-10 h-10 flex items-center justify-center">
|
||||||
|
<span class="material-symbols text-2xl text-gray-200">category</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow px-2 tagSearchCardContent h-full">
|
||||||
|
<p class="truncate text-sm">{{ genre }}</p>
|
||||||
|
<p class="text-xs text-gray-400">{{ $getString('LabelXItems', [numItems]) }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
genre: String,
|
||||||
|
numItems: Number
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
methods: {},
|
||||||
|
mounted() {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tagSearchCardContent {
|
||||||
|
width: calc(100% - 40px);
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
@ -5,6 +5,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-2 narratorSearchCardContent h-full">
|
<div class="flex-grow px-2 narratorSearchCardContent h-full">
|
||||||
<p class="truncate text-sm">{{ narrator }}</p>
|
<p class="truncate text-sm">{{ narrator }}</p>
|
||||||
|
<p class="text-xs text-gray-400">{{ $getString('LabelXBooks', [numBooks]) }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -12,7 +13,8 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
narrator: String
|
narrator: String,
|
||||||
|
numBooks: Number
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {}
|
||||||
@ -26,7 +28,7 @@ export default {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.narratorSearchCardContent {
|
.narratorSearchCardContent {
|
||||||
width: calc(100% - 40px);
|
width: calc(100% - 40px);
|
||||||
height: 40px;
|
height: 44px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-2 tagSearchCardContent h-full">
|
<div class="flex-grow px-2 tagSearchCardContent h-full">
|
||||||
<p class="truncate text-sm">{{ tag }}</p>
|
<p class="truncate text-sm">{{ tag }}</p>
|
||||||
|
<p class="text-xs text-gray-400">{{ $getString('LabelXItems', [numItems]) }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -12,7 +13,8 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
tag: String
|
tag: String,
|
||||||
|
numItems: Number
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {}
|
||||||
@ -26,7 +28,7 @@ export default {
|
|||||||
<style>
|
<style>
|
||||||
.tagSearchCardContent {
|
.tagSearchCardContent {
|
||||||
width: calc(100% - 40px);
|
width: calc(100% - 40px);
|
||||||
height: 40px;
|
height: 44px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -59,9 +59,18 @@
|
|||||||
|
|
||||||
<p v-if="tagResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelTags }}</p>
|
<p v-if="tagResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelTags }}</p>
|
||||||
<template v-for="item in tagResults">
|
<template v-for="item in tagResults">
|
||||||
<li :key="item.name" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
|
<li :key="`tag.${item.name}`" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
|
||||||
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=tags.${$encode(item.name)}`">
|
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=tags.${$encode(item.name)}`">
|
||||||
<cards-tag-search-card :tag="item.name" />
|
<cards-tag-search-card :tag="item.name" :num-items="item.numItems" />
|
||||||
|
</nuxt-link>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<p v-if="genreResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelGenres }}</p>
|
||||||
|
<template v-for="item in genreResults">
|
||||||
|
<li :key="`genre.${item.name}`" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
|
||||||
|
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=genres.${$encode(item.name)}`">
|
||||||
|
<cards-genre-search-card :genre="item.name" :num-items="item.numItems" />
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
@ -70,7 +79,7 @@
|
|||||||
<template v-for="narrator in narratorResults">
|
<template v-for="narrator in narratorResults">
|
||||||
<li :key="narrator.name" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
|
<li :key="narrator.name" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
|
||||||
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=narrators.${$encode(narrator.name)}`">
|
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=narrators.${$encode(narrator.name)}`">
|
||||||
<cards-narrator-search-card :narrator="narrator.name" />
|
<cards-narrator-search-card :narrator="narrator.name" :num-books="narrator.numBooks" />
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
@ -95,6 +104,7 @@ export default {
|
|||||||
authorResults: [],
|
authorResults: [],
|
||||||
seriesResults: [],
|
seriesResults: [],
|
||||||
tagResults: [],
|
tagResults: [],
|
||||||
|
genreResults: [],
|
||||||
narratorResults: [],
|
narratorResults: [],
|
||||||
searchTimeout: null,
|
searchTimeout: null,
|
||||||
lastSearch: null
|
lastSearch: null
|
||||||
@ -105,7 +115,7 @@ export default {
|
|||||||
return this.$store.state.libraries.currentLibraryId
|
return this.$store.state.libraries.currentLibraryId
|
||||||
},
|
},
|
||||||
totalResults() {
|
totalResults() {
|
||||||
return this.bookResults.length + this.seriesResults.length + this.authorResults.length + this.tagResults.length + this.podcastResults.length + this.narratorResults.length
|
return this.bookResults.length + this.seriesResults.length + this.authorResults.length + this.tagResults.length + this.genreResults.length + this.podcastResults.length + this.narratorResults.length
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -116,7 +126,7 @@ export default {
|
|||||||
if (!this.search) return
|
if (!this.search) return
|
||||||
var search = this.search
|
var search = this.search
|
||||||
this.clearResults()
|
this.clearResults()
|
||||||
this.$router.push(`/library/${this.currentLibraryId}/search?q=${search}`)
|
this.$router.push(`/library/${this.currentLibraryId}/search?q=${encodeURIComponent(search)}`)
|
||||||
},
|
},
|
||||||
clearResults() {
|
clearResults() {
|
||||||
this.search = null
|
this.search = null
|
||||||
@ -126,6 +136,7 @@ export default {
|
|||||||
this.authorResults = []
|
this.authorResults = []
|
||||||
this.seriesResults = []
|
this.seriesResults = []
|
||||||
this.tagResults = []
|
this.tagResults = []
|
||||||
|
this.genreResults = []
|
||||||
this.narratorResults = []
|
this.narratorResults = []
|
||||||
this.showMenu = false
|
this.showMenu = false
|
||||||
this.isFetching = false
|
this.isFetching = false
|
||||||
@ -155,7 +166,7 @@ export default {
|
|||||||
}
|
}
|
||||||
this.isFetching = true
|
this.isFetching = true
|
||||||
|
|
||||||
const searchResults = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/search?q=${value}&limit=3`).catch((error) => {
|
const searchResults = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/search?q=${encodeURIComponent(value)}&limit=3`).catch((error) => {
|
||||||
console.error('Search error', error)
|
console.error('Search error', error)
|
||||||
return []
|
return []
|
||||||
})
|
})
|
||||||
@ -168,6 +179,7 @@ export default {
|
|||||||
this.authorResults = searchResults.authors || []
|
this.authorResults = searchResults.authors || []
|
||||||
this.seriesResults = searchResults.series || []
|
this.seriesResults = searchResults.series || []
|
||||||
this.tagResults = searchResults.tags || []
|
this.tagResults = searchResults.tags || []
|
||||||
|
this.genreResults = searchResults.genres || []
|
||||||
this.narratorResults = searchResults.narrators || []
|
this.narratorResults = searchResults.narrators || []
|
||||||
|
|
||||||
this.isFetching = false
|
this.isFetching = false
|
||||||
|
@ -92,12 +92,13 @@ export default {
|
|||||||
|
|
||||||
if (this.$route.name.startsWith('config')) {
|
if (this.$route.name.startsWith('config')) {
|
||||||
// No need to refresh
|
// No need to refresh
|
||||||
} else if (this.$route.name.startsWith('library') && this.$route.name !== 'library-library-series-id') {
|
|
||||||
const newRoute = this.$route.path.replace(currLibraryId, library.id)
|
|
||||||
this.$router.push(newRoute)
|
|
||||||
} else if (this.$route.name === 'library-library-series-id' && library.mediaType === 'book') {
|
} else if (this.$route.name === 'library-library-series-id' && library.mediaType === 'book') {
|
||||||
// For series item page redirect to root series page
|
// For series item page redirect to root series page
|
||||||
this.$router.push(`/library/${library.id}/bookshelf/series`)
|
this.$router.push(`/library/${library.id}/bookshelf/series`)
|
||||||
|
} else if (this.$route.name === 'library-library-search') {
|
||||||
|
this.$router.push(this.$route.fullPath.replace(currLibraryId, library.id))
|
||||||
|
} else if (this.$route.name.startsWith('library')) {
|
||||||
|
this.$router.push(this.$route.path.replace(currLibraryId, library.id))
|
||||||
} else {
|
} else {
|
||||||
this.$router.push(`/library/${library.id}`)
|
this.$router.push(`/library/${library.id}`)
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ export default {
|
|||||||
if (!library) {
|
if (!library) {
|
||||||
return redirect('/oops?message=Library not found')
|
return redirect('/oops?message=Library not found')
|
||||||
}
|
}
|
||||||
let results = await app.$axios.$get(`/api/libraries/${libraryId}/search?q=${query.q}`).catch((error) => {
|
let results = await app.$axios.$get(`/api/libraries/${libraryId}/search?q=${encodeURIComponent(query.q)}`).catch((error) => {
|
||||||
console.error('Failed to search library', error)
|
console.error('Failed to search library', error)
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
@ -55,7 +55,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async search() {
|
async search() {
|
||||||
const results = await this.$axios.$get(`/api/libraries/${this.libraryId}/search?q=${this.query}`).catch((error) => {
|
const results = await this.$axios.$get(`/api/libraries/${this.libraryId}/search?q=${encodeURIComponent(this.query)}`).catch((error) => {
|
||||||
console.error('Failed to search library', error)
|
console.error('Failed to search library', error)
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
@ -611,6 +611,8 @@
|
|||||||
"LabelViewQueue": "View player queue",
|
"LabelViewQueue": "View player queue",
|
||||||
"LabelVolume": "Volume",
|
"LabelVolume": "Volume",
|
||||||
"LabelWeekdaysToRun": "Weekdays to run",
|
"LabelWeekdaysToRun": "Weekdays to run",
|
||||||
|
"LabelXBooks": "{0} books",
|
||||||
|
"LabelXItems": "{0} items",
|
||||||
"LabelYearReviewHide": "Hide Year in Review",
|
"LabelYearReviewHide": "Hide Year in Review",
|
||||||
"LabelYearReviewShow": "See Year in Review",
|
"LabelYearReviewShow": "See Year in Review",
|
||||||
"LabelYourAudiobookDuration": "Your audiobook duration",
|
"LabelYourAudiobookDuration": "Your audiobook duration",
|
||||||
|
@ -1079,7 +1079,7 @@ module.exports = {
|
|||||||
|
|
||||||
// Search tags
|
// Search tags
|
||||||
const tagMatches = []
|
const tagMatches = []
|
||||||
const [tagResults] = await Database.sequelize.query(`SELECT value, count(*) AS numItems FROM books b, libraryItems li, json_each(b.tags) WHERE json_valid(b.tags) AND json_each.value LIKE :query AND b.id = li.mediaId AND li.libraryId = :libraryId GROUP BY value LIMIT :limit OFFSET :offset;`, {
|
const [tagResults] = await Database.sequelize.query(`SELECT value, count(*) AS numItems FROM books b, libraryItems li, json_each(b.tags) WHERE json_valid(b.tags) AND json_each.value LIKE :query AND b.id = li.mediaId AND li.libraryId = :libraryId GROUP BY value ORDER BY numItems DESC LIMIT :limit OFFSET :offset;`, {
|
||||||
replacements: {
|
replacements: {
|
||||||
query: `%${query}%`,
|
query: `%${query}%`,
|
||||||
libraryId: oldLibrary.id,
|
libraryId: oldLibrary.id,
|
||||||
@ -1095,6 +1095,24 @@ module.exports = {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search genres
|
||||||
|
const genreMatches = []
|
||||||
|
const [genreResults] = await Database.sequelize.query(`SELECT value, count(*) AS numItems FROM books b, libraryItems li, json_each(b.genres) WHERE json_valid(b.genres) AND json_each.value LIKE :query AND b.id = li.mediaId AND li.libraryId = :libraryId GROUP BY value ORDER BY numItems DESC LIMIT :limit OFFSET :offset;`, {
|
||||||
|
replacements: {
|
||||||
|
query: `%${query}%`,
|
||||||
|
libraryId: oldLibrary.id,
|
||||||
|
limit,
|
||||||
|
offset
|
||||||
|
},
|
||||||
|
raw: true
|
||||||
|
})
|
||||||
|
for (const row of genreResults) {
|
||||||
|
genreMatches.push({
|
||||||
|
name: row.value,
|
||||||
|
numItems: row.numItems
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Search series
|
// Search series
|
||||||
const allSeries = await Database.seriesModel.findAll({
|
const allSeries = await Database.seriesModel.findAll({
|
||||||
where: {
|
where: {
|
||||||
@ -1140,6 +1158,7 @@ module.exports = {
|
|||||||
book: itemMatches,
|
book: itemMatches,
|
||||||
narrators: narratorMatches,
|
narrators: narratorMatches,
|
||||||
tags: tagMatches,
|
tags: tagMatches,
|
||||||
|
genres: genreMatches,
|
||||||
series: seriesMatches,
|
series: seriesMatches,
|
||||||
authors: authorMatches
|
authors: authorMatches
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
const Sequelize = require('sequelize')
|
const Sequelize = require('sequelize')
|
||||||
const Database = require('../../Database')
|
const Database = require('../../Database')
|
||||||
const Logger = require('../../Logger')
|
const Logger = require('../../Logger')
|
||||||
@ -23,9 +22,11 @@ module.exports = {
|
|||||||
if (user.permissions.selectedTagsNotAccessible) {
|
if (user.permissions.selectedTagsNotAccessible) {
|
||||||
podcastWhere.push(Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), 0))
|
podcastWhere.push(Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), 0))
|
||||||
} else {
|
} else {
|
||||||
podcastWhere.push(Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), {
|
podcastWhere.push(
|
||||||
|
Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), {
|
||||||
[Sequelize.Op.gte]: 1
|
[Sequelize.Op.gte]: 1
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@ -130,7 +131,7 @@ module.exports = {
|
|||||||
]
|
]
|
||||||
} else if (filterGroup === 'recent') {
|
} else if (filterGroup === 'recent') {
|
||||||
libraryItemWhere['createdAt'] = {
|
libraryItemWhere['createdAt'] = {
|
||||||
[Sequelize.Op.gte]: new Date(new Date() - (60 * 24 * 60 * 60 * 1000)) // 60 days ago
|
[Sequelize.Op.gte]: new Date(new Date() - 60 * 24 * 60 * 60 * 1000) // 60 days ago
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,10 +155,7 @@ module.exports = {
|
|||||||
replacements,
|
replacements,
|
||||||
distinct: true,
|
distinct: true,
|
||||||
attributes: {
|
attributes: {
|
||||||
include: [
|
include: [[Sequelize.literal(`(SELECT count(*) FROM podcastEpisodes pe WHERE pe.podcastId = podcast.id)`), 'numEpisodes'], ...podcastIncludes]
|
||||||
[Sequelize.literal(`(SELECT count(*) FROM podcastEpisodes pe WHERE pe.podcastId = podcast.id)`), 'numEpisodes'],
|
|
||||||
...podcastIncludes
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
@ -251,7 +249,7 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
} else if (filterGroup === 'recent') {
|
} else if (filterGroup === 'recent') {
|
||||||
podcastEpisodeWhere['createdAt'] = {
|
podcastEpisodeWhere['createdAt'] = {
|
||||||
[Sequelize.Op.gte]: new Date(new Date() - (60 * 24 * 60 * 60 * 1000)) // 60 days ago
|
[Sequelize.Op.gte]: new Date(new Date() - 60 * 24 * 60 * 60 * 1000) // 60 days ago
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,7 +384,7 @@ module.exports = {
|
|||||||
|
|
||||||
// Search tags
|
// Search tags
|
||||||
const tagMatches = []
|
const tagMatches = []
|
||||||
const [tagResults] = await Database.sequelize.query(`SELECT value, count(*) AS numItems FROM podcasts p, libraryItems li, json_each(p.tags) WHERE json_valid(p.tags) AND json_each.value LIKE :query AND p.id = li.mediaId AND li.libraryId = :libraryId GROUP BY value LIMIT :limit OFFSET :offset;`, {
|
const [tagResults] = await Database.sequelize.query(`SELECT value, count(*) AS numItems FROM podcasts p, libraryItems li, json_each(p.tags) WHERE json_valid(p.tags) AND json_each.value LIKE :query AND p.id = li.mediaId AND li.libraryId = :libraryId GROUP BY value ORDER BY numItems DESC LIMIT :limit OFFSET :offset;`, {
|
||||||
replacements: {
|
replacements: {
|
||||||
query: `%${query}%`,
|
query: `%${query}%`,
|
||||||
libraryId: oldLibrary.id,
|
libraryId: oldLibrary.id,
|
||||||
@ -402,9 +400,28 @@ module.exports = {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search genres
|
||||||
|
const genreMatches = []
|
||||||
|
const [genreResults] = await Database.sequelize.query(`SELECT value, count(*) AS numItems FROM podcasts p, libraryItems li, json_each(p.genres) WHERE json_valid(p.genres) AND json_each.value LIKE :query AND p.id = li.mediaId AND li.libraryId = :libraryId GROUP BY value ORDER BY numItems DESC LIMIT :limit OFFSET :offset;`, {
|
||||||
|
replacements: {
|
||||||
|
query: `%${query}%`,
|
||||||
|
libraryId: oldLibrary.id,
|
||||||
|
limit,
|
||||||
|
offset
|
||||||
|
},
|
||||||
|
raw: true
|
||||||
|
})
|
||||||
|
for (const row of genreResults) {
|
||||||
|
genreMatches.push({
|
||||||
|
name: row.value,
|
||||||
|
numItems: row.numItems
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
podcast: itemMatches,
|
podcast: itemMatches,
|
||||||
tags: tagMatches
|
tags: tagMatches,
|
||||||
|
genres: genreMatches
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -446,9 +463,7 @@ module.exports = {
|
|||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
order: [
|
order: [['publishedAt', 'DESC']],
|
||||||
['publishedAt', 'DESC']
|
|
||||||
],
|
|
||||||
subQuery: false,
|
subQuery: false,
|
||||||
limit,
|
limit,
|
||||||
offset
|
offset
|
||||||
@ -519,11 +534,7 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
async getLongestPodcasts(libraryId, limit) {
|
async getLongestPodcasts(libraryId, limit) {
|
||||||
const podcasts = await Database.podcastModel.findAll({
|
const podcasts = await Database.podcastModel.findAll({
|
||||||
attributes: [
|
attributes: ['id', 'title', [Sequelize.literal(`(SELECT SUM(json_extract(pe.audioFile, '$.duration')) FROM podcastEpisodes pe WHERE pe.podcastId = podcast.id)`), 'duration']],
|
||||||
'id',
|
|
||||||
'title',
|
|
||||||
[Sequelize.literal(`(SELECT SUM(json_extract(pe.audioFile, '$.duration')) FROM podcastEpisodes pe WHERE pe.podcastId = podcast.id)`), 'duration']
|
|
||||||
],
|
|
||||||
include: {
|
include: {
|
||||||
model: Database.libraryItemModel,
|
model: Database.libraryItemModel,
|
||||||
attributes: ['id', 'libraryId'],
|
attributes: ['id', 'libraryId'],
|
||||||
@ -531,12 +542,10 @@ module.exports = {
|
|||||||
libraryId
|
libraryId
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
order: [
|
order: [['duration', 'DESC']],
|
||||||
['duration', 'DESC']
|
|
||||||
],
|
|
||||||
limit
|
limit
|
||||||
})
|
})
|
||||||
return podcasts.map(podcast => {
|
return podcasts.map((podcast) => {
|
||||||
return {
|
return {
|
||||||
id: podcast.libraryItem.id,
|
id: podcast.libraryItem.id,
|
||||||
title: podcast.title,
|
title: podcast.title,
|
||||||
|
Loading…
Reference in New Issue
Block a user