mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-27 09:08:57 +01:00
Add remaining personalized shelf queries, update book libraries home page to use new API endpoint
This commit is contained in:
parent
80b3bfea51
commit
09eefae808
@ -68,6 +68,9 @@ export default {
|
||||
currentLibraryId() {
|
||||
return this.$store.state.libraries.currentLibraryId
|
||||
},
|
||||
currentLibraryMediaType() {
|
||||
return this.$store.getters['libraries/getCurrentLibraryMediaType']
|
||||
},
|
||||
libraryName() {
|
||||
return this.$store.getters['libraries/getCurrentLibraryName']
|
||||
},
|
||||
@ -167,8 +170,9 @@ export default {
|
||||
this.loaded = true
|
||||
},
|
||||
async fetchCategories() {
|
||||
const endpoint = this.currentLibraryMediaType === 'book' ? 'personalized2' : 'personalized'
|
||||
const categories = await this.$axios
|
||||
.$get(`/api/libraries/${this.currentLibraryId}/personalized?include=rssfeed,numEpisodesIncomplete`)
|
||||
.$get(`/api/libraries/${this.currentLibraryId}/${endpoint}?include=rssfeed,numEpisodesIncomplete`)
|
||||
.then((data) => {
|
||||
return data
|
||||
})
|
||||
@ -346,8 +350,6 @@ export default {
|
||||
})
|
||||
},
|
||||
episodeAdded(episodeWithLibraryItem) {
|
||||
console.log('Podcast episode added', episodeWithLibraryItem)
|
||||
|
||||
const isThisLibrary = episodeWithLibraryItem.libraryItem?.libraryId === this.currentLibraryId
|
||||
if (!this.search && isThisLibrary) {
|
||||
this.fetchCategories()
|
||||
|
@ -348,6 +348,10 @@ export default {
|
||||
},
|
||||
tracks() {
|
||||
return [
|
||||
{
|
||||
id: 'none',
|
||||
name: this.$strings.LabelTracksNone
|
||||
},
|
||||
{
|
||||
id: 'single',
|
||||
name: this.$strings.LabelTracksSingleTrack
|
||||
|
@ -222,6 +222,7 @@
|
||||
"LabelDirectory": "Verzeichnis",
|
||||
"LabelDiscFromFilename": "CD aus dem Dateinamen",
|
||||
"LabelDiscFromMetadata": "CD aus den Metadaten",
|
||||
"LabelDiscover": "Discover",
|
||||
"LabelDownload": "Herunterladen",
|
||||
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||
"LabelDuration": "Laufzeit",
|
||||
@ -474,6 +475,7 @@
|
||||
"LabelTrackFromMetadata": "Titel aus Metadaten",
|
||||
"LabelTracks": "Dateien",
|
||||
"LabelTracksMultiTrack": "Mehrfachdatei",
|
||||
"LabelTracksNone": "No tracks",
|
||||
"LabelTracksSingleTrack": "Einzeldatei",
|
||||
"LabelType": "Typ",
|
||||
"LabelUnabridged": "Ungekürzt",
|
||||
@ -702,4 +704,4 @@
|
||||
"ToastSocketFailedToConnect": "Verbindung zum WebSocket fehlgeschlagen",
|
||||
"ToastUserDeleteFailed": "Benutzer konnte nicht gelöscht werden",
|
||||
"ToastUserDeleteSuccess": "Benutzer gelöscht"
|
||||
}
|
||||
}
|
@ -222,6 +222,7 @@
|
||||
"LabelDirectory": "Directory",
|
||||
"LabelDiscFromFilename": "Disc from Filename",
|
||||
"LabelDiscFromMetadata": "Disc from Metadata",
|
||||
"LabelDiscover": "Discover",
|
||||
"LabelDownload": "Download",
|
||||
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||
"LabelDuration": "Duration",
|
||||
@ -474,6 +475,7 @@
|
||||
"LabelTrackFromMetadata": "Track from Metadata",
|
||||
"LabelTracks": "Tracks",
|
||||
"LabelTracksMultiTrack": "Multi-track",
|
||||
"LabelTracksNone": "No tracks",
|
||||
"LabelTracksSingleTrack": "Single-track",
|
||||
"LabelType": "Type",
|
||||
"LabelUnabridged": "Unabridged",
|
||||
|
@ -222,6 +222,7 @@
|
||||
"LabelDirectory": "Directorio",
|
||||
"LabelDiscFromFilename": "Disco a partir del Nombre del Archivo",
|
||||
"LabelDiscFromMetadata": "Disco a partir de Metadata",
|
||||
"LabelDiscover": "Discover",
|
||||
"LabelDownload": "Descargar",
|
||||
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||
"LabelDuration": "Duración",
|
||||
@ -474,6 +475,7 @@
|
||||
"LabelTrackFromMetadata": "Pista desde Metadata",
|
||||
"LabelTracks": "Pistas",
|
||||
"LabelTracksMultiTrack": "Multi-track",
|
||||
"LabelTracksNone": "No tracks",
|
||||
"LabelTracksSingleTrack": "Single-track",
|
||||
"LabelType": "Tipo",
|
||||
"LabelUnabridged": "Unabridged",
|
||||
|
@ -222,6 +222,7 @@
|
||||
"LabelDirectory": "Répertoire",
|
||||
"LabelDiscFromFilename": "Disque depuis le fichier",
|
||||
"LabelDiscFromMetadata": "Disque depuis les métadonnées",
|
||||
"LabelDiscover": "Discover",
|
||||
"LabelDownload": "Téléchargement",
|
||||
"LabelDownloadNEpisodes": "Télécharger {0} épisode(s)",
|
||||
"LabelDuration": "Durée",
|
||||
@ -474,6 +475,7 @@
|
||||
"LabelTrackFromMetadata": "Piste depuis les métadonnées",
|
||||
"LabelTracks": "Pistes",
|
||||
"LabelTracksMultiTrack": "Piste multiple",
|
||||
"LabelTracksNone": "No tracks",
|
||||
"LabelTracksSingleTrack": "Piste simple",
|
||||
"LabelType": "Type",
|
||||
"LabelUnabridged": "Version intégrale",
|
||||
|
@ -222,6 +222,7 @@
|
||||
"LabelDirectory": "Directory",
|
||||
"LabelDiscFromFilename": "Disc from Filename",
|
||||
"LabelDiscFromMetadata": "Disc from Metadata",
|
||||
"LabelDiscover": "Discover",
|
||||
"LabelDownload": "Download",
|
||||
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||
"LabelDuration": "Duration",
|
||||
@ -474,6 +475,7 @@
|
||||
"LabelTrackFromMetadata": "Track from Metadata",
|
||||
"LabelTracks": "Tracks",
|
||||
"LabelTracksMultiTrack": "Multi-track",
|
||||
"LabelTracksNone": "No tracks",
|
||||
"LabelTracksSingleTrack": "Single-track",
|
||||
"LabelType": "Type",
|
||||
"LabelUnabridged": "Unabridged",
|
||||
|
@ -222,6 +222,7 @@
|
||||
"LabelDirectory": "Directory",
|
||||
"LabelDiscFromFilename": "Disc from Filename",
|
||||
"LabelDiscFromMetadata": "Disc from Metadata",
|
||||
"LabelDiscover": "Discover",
|
||||
"LabelDownload": "Download",
|
||||
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||
"LabelDuration": "Duration",
|
||||
@ -474,6 +475,7 @@
|
||||
"LabelTrackFromMetadata": "Track from Metadata",
|
||||
"LabelTracks": "Tracks",
|
||||
"LabelTracksMultiTrack": "Multi-track",
|
||||
"LabelTracksNone": "No tracks",
|
||||
"LabelTracksSingleTrack": "Single-track",
|
||||
"LabelType": "Type",
|
||||
"LabelUnabridged": "Unabridged",
|
||||
|
@ -222,6 +222,7 @@
|
||||
"LabelDirectory": "Direktorij",
|
||||
"LabelDiscFromFilename": "CD iz imena datoteke",
|
||||
"LabelDiscFromMetadata": "CD iz metapodataka",
|
||||
"LabelDiscover": "Discover",
|
||||
"LabelDownload": "Preuzmi",
|
||||
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||
"LabelDuration": "Trajanje",
|
||||
@ -474,6 +475,7 @@
|
||||
"LabelTrackFromMetadata": "Track iz metapodataka",
|
||||
"LabelTracks": "Tracks",
|
||||
"LabelTracksMultiTrack": "Multi-track",
|
||||
"LabelTracksNone": "No tracks",
|
||||
"LabelTracksSingleTrack": "Single-track",
|
||||
"LabelType": "Tip",
|
||||
"LabelUnabridged": "Unabridged",
|
||||
|
@ -222,6 +222,7 @@
|
||||
"LabelDirectory": "Elenco",
|
||||
"LabelDiscFromFilename": "Disco dal nome file",
|
||||
"LabelDiscFromMetadata": "Disco dal Metadata",
|
||||
"LabelDiscover": "Discover",
|
||||
"LabelDownload": "Download",
|
||||
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||
"LabelDuration": "Durata",
|
||||
@ -474,6 +475,7 @@
|
||||
"LabelTrackFromMetadata": "Traccia da Metadata",
|
||||
"LabelTracks": "Traccia",
|
||||
"LabelTracksMultiTrack": "Multi-traccia",
|
||||
"LabelTracksNone": "No tracks",
|
||||
"LabelTracksSingleTrack": "Traccia-singola",
|
||||
"LabelType": "Tipo",
|
||||
"LabelUnabridged": "Integrale",
|
||||
@ -702,4 +704,4 @@
|
||||
"ToastSocketFailedToConnect": "Socket non riesce a connettersi",
|
||||
"ToastUserDeleteFailed": "Errore eliminazione utente",
|
||||
"ToastUserDeleteSuccess": "Utente eliminato"
|
||||
}
|
||||
}
|
@ -222,6 +222,7 @@
|
||||
"LabelDirectory": "Katalogas",
|
||||
"LabelDiscFromFilename": "Diskas pagal failo pavadinimą",
|
||||
"LabelDiscFromMetadata": "Diskas pagal metaduomenis",
|
||||
"LabelDiscover": "Discover",
|
||||
"LabelDownload": "Atsisiųsti",
|
||||
"LabelDownloadNEpisodes": "Atsisiųsti {0} epizodų",
|
||||
"LabelDuration": "Trukmė",
|
||||
@ -474,6 +475,7 @@
|
||||
"LabelTrackFromMetadata": "Takelis iš metaduomenų",
|
||||
"LabelTracks": "Takeliai",
|
||||
"LabelTracksMultiTrack": "Keli takeliai",
|
||||
"LabelTracksNone": "No tracks",
|
||||
"LabelTracksSingleTrack": "Vienas takelis",
|
||||
"LabelType": "Tipas",
|
||||
"LabelUnabridged": "Neprikurptas",
|
||||
@ -702,4 +704,4 @@
|
||||
"ToastSocketFailedToConnect": "Nepavyko prisijungti prie serverio",
|
||||
"ToastUserDeleteFailed": "Nepavyko ištrinti naudotojo",
|
||||
"ToastUserDeleteSuccess": "Naudotojas ištrintas"
|
||||
}
|
||||
}
|
@ -222,6 +222,7 @@
|
||||
"LabelDirectory": "Map",
|
||||
"LabelDiscFromFilename": "Schijf uit bestandsnaam",
|
||||
"LabelDiscFromMetadata": "Schijf uit metadata",
|
||||
"LabelDiscover": "Discover",
|
||||
"LabelDownload": "Download",
|
||||
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||
"LabelDuration": "Duur",
|
||||
@ -474,6 +475,7 @@
|
||||
"LabelTrackFromMetadata": "Track vanuit metadata",
|
||||
"LabelTracks": "Tracks",
|
||||
"LabelTracksMultiTrack": "Multi-track",
|
||||
"LabelTracksNone": "No tracks",
|
||||
"LabelTracksSingleTrack": "Single-track",
|
||||
"LabelType": "Type",
|
||||
"LabelUnabridged": "Onverkort",
|
||||
|
@ -222,6 +222,7 @@
|
||||
"LabelDirectory": "Katalog",
|
||||
"LabelDiscFromFilename": "Oznaczenie dysku z nazwy pliku",
|
||||
"LabelDiscFromMetadata": "Oznaczenie dysku z metadanych",
|
||||
"LabelDiscover": "Discover",
|
||||
"LabelDownload": "Pobierz",
|
||||
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||
"LabelDuration": "Czas trwania",
|
||||
@ -474,6 +475,7 @@
|
||||
"LabelTrackFromMetadata": "Ścieżka z metadanych",
|
||||
"LabelTracks": "Tracks",
|
||||
"LabelTracksMultiTrack": "Multi-track",
|
||||
"LabelTracksNone": "No tracks",
|
||||
"LabelTracksSingleTrack": "Single-track",
|
||||
"LabelType": "Typ",
|
||||
"LabelUnabridged": "Unabridged",
|
||||
|
@ -222,6 +222,7 @@
|
||||
"LabelDirectory": "Каталог",
|
||||
"LabelDiscFromFilename": "Диск из Имени файла",
|
||||
"LabelDiscFromMetadata": "Диск из Метаданных",
|
||||
"LabelDiscover": "Discover",
|
||||
"LabelDownload": "Скачать",
|
||||
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||
"LabelDuration": "Длина",
|
||||
@ -474,6 +475,7 @@
|
||||
"LabelTrackFromMetadata": "Трек из Метаданных",
|
||||
"LabelTracks": "Треков",
|
||||
"LabelTracksMultiTrack": "Мультитрек",
|
||||
"LabelTracksNone": "No tracks",
|
||||
"LabelTracksSingleTrack": "Один трек",
|
||||
"LabelType": "Тип",
|
||||
"LabelUnabridged": "Полное издание",
|
||||
|
@ -222,6 +222,7 @@
|
||||
"LabelDirectory": "目录",
|
||||
"LabelDiscFromFilename": "从文件名获取光盘",
|
||||
"LabelDiscFromMetadata": "从元数据获取光盘",
|
||||
"LabelDiscover": "Discover",
|
||||
"LabelDownload": "下载",
|
||||
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||
"LabelDuration": "持续时间",
|
||||
@ -474,6 +475,7 @@
|
||||
"LabelTrackFromMetadata": "从源数据获取音轨",
|
||||
"LabelTracks": "音轨",
|
||||
"LabelTracksMultiTrack": "多轨",
|
||||
"LabelTracksNone": "No tracks",
|
||||
"LabelTracksSingleTrack": "单轨",
|
||||
"LabelType": "类型",
|
||||
"LabelUnabridged": "未删节",
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { DataTypes, Model, literal } = require('sequelize')
|
||||
const { DataTypes, Model } = require('sequelize')
|
||||
const Logger = require('../Logger')
|
||||
const oldLibraryItem = require('../objects/LibraryItem')
|
||||
const libraryFilters = require('../utils/queries/libraryFilters')
|
||||
@ -447,32 +447,40 @@ module.exports = (sequelize) => {
|
||||
*/
|
||||
static async getPersonalizedShelves(library, userId, include, limit) {
|
||||
const isPodcastLibrary = library.mediaType === 'podcast'
|
||||
|
||||
const fullStart = Date.now() // Used for testing load times
|
||||
|
||||
const shelves = []
|
||||
const itemsInProgressPayload = await libraryFilters.getLibraryItemsInProgress(library, userId, include, limit, false)
|
||||
if (itemsInProgressPayload.libraryItems.length) {
|
||||
|
||||
const itemsInProgressPayload = await libraryFilters.getMediaItemsInProgress(library, userId, include, limit, false)
|
||||
if (itemsInProgressPayload.items.length) {
|
||||
shelves.push({
|
||||
id: 'continue-listening',
|
||||
label: 'Continue Listening',
|
||||
labelStringKey: 'LabelContinueListening',
|
||||
type: isPodcastLibrary ? 'episode' : 'book',
|
||||
entities: itemsInProgressPayload.libraryItems,
|
||||
entities: itemsInProgressPayload.items,
|
||||
total: itemsInProgressPayload.count
|
||||
})
|
||||
}
|
||||
Logger.debug(`Loaded ${itemsInProgressPayload.items.length} items for "Continue Listening" in ${((Date.now() - fullStart) / 1000).toFixed(2)}s`)
|
||||
|
||||
let start = Date.now()
|
||||
if (library.mediaType === 'book') {
|
||||
const ebooksInProgressPayload = await libraryFilters.getLibraryItemsInProgress(library, userId, include, limit, true)
|
||||
if (ebooksInProgressPayload.libraryItems.length) {
|
||||
const ebooksInProgressPayload = await libraryFilters.getMediaItemsInProgress(library, userId, include, limit, true)
|
||||
if (ebooksInProgressPayload.items.length) {
|
||||
shelves.push({
|
||||
id: 'continue-reading',
|
||||
label: 'Continue Reading',
|
||||
labelStringKey: 'LabelContinueReading',
|
||||
type: 'book',
|
||||
entities: ebooksInProgressPayload.libraryItems,
|
||||
entities: ebooksInProgressPayload.items,
|
||||
total: ebooksInProgressPayload.count
|
||||
})
|
||||
}
|
||||
Logger.debug(`Loaded ${ebooksInProgressPayload.items.length} items for "Continue Reading" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
|
||||
|
||||
start = Date.now()
|
||||
const continueSeriesPayload = await libraryFilters.getLibraryItemsContinueSeries(library, userId, include, limit)
|
||||
if (continueSeriesPayload.libraryItems.length) {
|
||||
shelves.push({
|
||||
@ -484,6 +492,8 @@ module.exports = (sequelize) => {
|
||||
total: continueSeriesPayload.count
|
||||
})
|
||||
}
|
||||
Logger.debug(`Loaded ${continueSeriesPayload.libraryItems.length} items for "Continue Series" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
|
||||
start = Date.now()
|
||||
}
|
||||
|
||||
const mostRecentPayload = await libraryFilters.getLibraryItemsMostRecentlyAdded(library, userId, include, limit)
|
||||
@ -497,7 +507,9 @@ module.exports = (sequelize) => {
|
||||
total: mostRecentPayload.count
|
||||
})
|
||||
}
|
||||
Logger.debug(`Loaded ${mostRecentPayload.libraryItems.length} items for "Recently Added" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
|
||||
|
||||
start = Date.now()
|
||||
const seriesMostRecentPayload = await libraryFilters.getSeriesMostRecentlyAdded(library, include, 5)
|
||||
if (seriesMostRecentPayload.series.length) {
|
||||
shelves.push({
|
||||
@ -509,6 +521,65 @@ module.exports = (sequelize) => {
|
||||
total: seriesMostRecentPayload.count
|
||||
})
|
||||
}
|
||||
Logger.debug(`Loaded ${seriesMostRecentPayload.series.length} items for "Recent Series" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
|
||||
|
||||
start = Date.now()
|
||||
const discoverLibraryItemsPayload = await libraryFilters.getLibraryItemsToDiscover(library, userId, include, limit)
|
||||
if (discoverLibraryItemsPayload.libraryItems.length) {
|
||||
shelves.push({
|
||||
id: 'discover',
|
||||
label: 'Discover',
|
||||
labelStringKey: 'LabelDiscover',
|
||||
type: library.mediaType,
|
||||
entities: discoverLibraryItemsPayload.libraryItems,
|
||||
total: discoverLibraryItemsPayload.count
|
||||
})
|
||||
}
|
||||
Logger.debug(`Loaded ${discoverLibraryItemsPayload.libraryItems.length} items for "Discover" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
|
||||
|
||||
start = Date.now()
|
||||
const listenAgainPayload = await libraryFilters.getMediaFinished(library, userId, include, limit, false)
|
||||
if (listenAgainPayload.items.length) {
|
||||
shelves.push({
|
||||
id: 'listen-again',
|
||||
label: 'Listen Again',
|
||||
labelStringKey: 'LabelListenAgain',
|
||||
type: isPodcastLibrary ? 'episode' : 'book',
|
||||
entities: listenAgainPayload.items,
|
||||
total: listenAgainPayload.count
|
||||
})
|
||||
}
|
||||
Logger.debug(`Loaded ${listenAgainPayload.items.length} items for "Listen Again" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
|
||||
|
||||
start = Date.now()
|
||||
const readAgainPayload = await libraryFilters.getMediaFinished(library, userId, include, limit, true)
|
||||
if (readAgainPayload.items.length) {
|
||||
shelves.push({
|
||||
id: 'read-again',
|
||||
label: 'Read Again',
|
||||
labelStringKey: 'LabelReadAgain',
|
||||
type: 'book',
|
||||
entities: readAgainPayload.items,
|
||||
total: readAgainPayload.count
|
||||
})
|
||||
}
|
||||
Logger.debug(`Loaded ${readAgainPayload.items.length} items for "Read Again" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
|
||||
|
||||
start = Date.now()
|
||||
const newestAuthorsPayload = await libraryFilters.getNewestAuthors(library, limit)
|
||||
if (newestAuthorsPayload.authors.length) {
|
||||
shelves.push({
|
||||
id: 'newest-authors',
|
||||
label: 'Newest Authors',
|
||||
labelStringKey: 'LabelNewestAuthors',
|
||||
type: 'authors',
|
||||
entities: newestAuthorsPayload.authors,
|
||||
total: newestAuthorsPayload.count
|
||||
})
|
||||
}
|
||||
Logger.debug(`Loaded ${newestAuthorsPayload.authors.length} authors for "Newest Authors" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
|
||||
|
||||
Logger.debug(`Loaded ${shelves.length} personalized shelves in ${((Date.now() - fullStart) / 1000).toFixed(2)}s`)
|
||||
|
||||
return shelves
|
||||
}
|
||||
|
@ -62,7 +62,8 @@ module.exports = {
|
||||
} else if (group === 'languages') {
|
||||
filtered = filtered.filter(li => li.media.metadata.language === filter)
|
||||
} else if (group === 'tracks') {
|
||||
if (filter === 'single') filtered = filtered.filter(li => li.isBook && li.media.numTracks === 1)
|
||||
if (filter === 'none') filtered = filtered.filter(li => li.isBook && !li.media.numTracks)
|
||||
else if (filter === 'single') filtered = filtered.filter(li => li.isBook && li.media.numTracks === 1)
|
||||
else if (filter === 'multi') filtered = filtered.filter(li => li.isBook && li.media.numTracks > 1)
|
||||
} else if (group === 'ebooks') {
|
||||
if (filter === 'ebook') filtered = filtered.filter(li => li.media.ebookFile)
|
||||
|
@ -1,3 +1,4 @@
|
||||
const Sequelize = require('sequelize')
|
||||
const Database = require('../../Database')
|
||||
const Logger = require('../../Logger')
|
||||
const libraryItemsBookFilters = require('./libraryItemsBookFilters')
|
||||
@ -41,14 +42,14 @@ module.exports = {
|
||||
* @param {string[]} include
|
||||
* @param {number} limit
|
||||
* @param {boolean} ebook true if continue reading shelf
|
||||
* @returns {object} { libraryItems:LibraryItem[], count:number }
|
||||
* @returns {object} { items:LibraryItem[], count:number }
|
||||
*/
|
||||
async getLibraryItemsInProgress(library, userId, include, limit, ebook = false) {
|
||||
async getMediaItemsInProgress(library, userId, include, limit, ebook = false) {
|
||||
if (library.mediaType === 'book') {
|
||||
const filterValue = ebook ? 'ebook-in-progress' : 'audio-in-progress'
|
||||
const { libraryItems, count } = await libraryItemsBookFilters.getFilteredLibraryItems(library.id, userId, 'progress', filterValue, 'progress', true, false, include, limit, 0)
|
||||
return {
|
||||
libraryItems: libraryItems.map(li => {
|
||||
items: libraryItems.map(li => {
|
||||
const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(li).toJSONMinified()
|
||||
if (li.rssFeed) {
|
||||
oldLibraryItem.rssFeed = Database.models.feed.getOldFeed(li.rssFeed).toJSONMinified()
|
||||
@ -58,9 +59,10 @@ module.exports = {
|
||||
count
|
||||
}
|
||||
} else {
|
||||
// TODO: Get episodes in progress
|
||||
return {
|
||||
count: 0,
|
||||
libraryItems: []
|
||||
items: []
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -132,6 +134,40 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get library items or podcast episodes for the "Listen Again" or "Read Again" shelf
|
||||
* @param {oldLibrary} library
|
||||
* @param {string} userId
|
||||
* @param {string[]} include
|
||||
* @param {number} limit
|
||||
* @param {boolean} ebook true if "Read Again" shelf
|
||||
* @returns {object} { items:object[], count:number }
|
||||
*/
|
||||
async getMediaFinished(library, userId, include, limit, ebook = false) {
|
||||
if (ebook && library.mediaType !== 'book') return { items: [], count: 0 }
|
||||
|
||||
if (library.mediaType === 'book') {
|
||||
const filterValue = ebook ? 'ebook-finished' : 'finished'
|
||||
const { libraryItems, count } = await libraryItemsBookFilters.getFilteredLibraryItems(library.id, userId, 'progress', filterValue, 'progress', true, false, include, limit, 0)
|
||||
return {
|
||||
items: libraryItems.map(li => {
|
||||
const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(li).toJSONMinified()
|
||||
if (li.rssFeed) {
|
||||
oldLibraryItem.rssFeed = Database.models.feed.getOldFeed(li.rssFeed).toJSONMinified()
|
||||
}
|
||||
return oldLibraryItem
|
||||
}),
|
||||
count
|
||||
}
|
||||
} else {
|
||||
// TODO: Get podcast episodes finished
|
||||
return {
|
||||
items: [],
|
||||
count: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get series for recent series shelf
|
||||
* @param {oldLibrary} library
|
||||
@ -140,6 +176,8 @@ module.exports = {
|
||||
* @returns {object} { series:oldSeries[], count:number}
|
||||
*/
|
||||
async getSeriesMostRecentlyAdded(library, include, limit) {
|
||||
if (library.mediaType !== 'book') return { series: [], count: 0 }
|
||||
|
||||
const seriesIncludes = []
|
||||
if (include.includes('rssfeed')) {
|
||||
seriesIncludes.push({
|
||||
@ -172,8 +210,6 @@ module.exports = {
|
||||
]
|
||||
})
|
||||
|
||||
Logger.debug(`Found ${series.length} series recently added (${count} total)`)
|
||||
|
||||
const allOldSeries = []
|
||||
for (const s of series) {
|
||||
const oldSeries = s.getOldSeries().toJSON()
|
||||
@ -205,5 +241,63 @@ module.exports = {
|
||||
series: allOldSeries,
|
||||
count
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get most recently created authors for "Newest Authors" shelf
|
||||
* Author must be linked to at least 1 book
|
||||
* @param {oldLibrary} library
|
||||
* @param {number} limit
|
||||
* @returns {object} { authors:oldAuthor[], count:number }
|
||||
*/
|
||||
async getNewestAuthors(library, limit) {
|
||||
if (library.mediaType !== 'book') return { authors: [], count: 0 }
|
||||
|
||||
const { rows: authors, count } = await Database.models.author.findAndCountAll({
|
||||
where: {
|
||||
libraryId: library.id
|
||||
},
|
||||
include: {
|
||||
model: Database.models.bookAuthor,
|
||||
required: true // Must belong to a book
|
||||
},
|
||||
limit,
|
||||
distinct: true,
|
||||
order: [
|
||||
['createdAt', 'DESC']
|
||||
]
|
||||
})
|
||||
|
||||
return {
|
||||
authors: authors.map((au) => {
|
||||
const numBooks = au.bookAuthors?.length || 0
|
||||
return au.getOldAuthor().toJSONExpanded(numBooks)
|
||||
}),
|
||||
count
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get book library items for the "Discover" shelf
|
||||
* @param {oldLibrary} library
|
||||
* @param {string} userId
|
||||
* @param {string[]} include
|
||||
* @param {number} limit
|
||||
* @returns {object} {libraryItems:oldLibraryItem[], count:number}
|
||||
*/
|
||||
async getLibraryItemsToDiscover(library, userId, include, limit) {
|
||||
if (library.mediaType !== 'book') return { libraryItems: [], count: 0 }
|
||||
|
||||
const { libraryItems, count } = await libraryItemsBookFilters.getDiscoverLibraryItems(library.id, userId, include, limit)
|
||||
return {
|
||||
libraryItems: libraryItems.map(li => {
|
||||
const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(li).toJSONMinified()
|
||||
if (li.rssFeed) {
|
||||
oldLibraryItem.rssFeed = Database.models.feed.getOldFeed(li.rssFeed).toJSONMinified()
|
||||
}
|
||||
return oldLibraryItem
|
||||
}),
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
@ -119,7 +119,9 @@ module.exports = {
|
||||
}
|
||||
]
|
||||
} else if (value === 'ebook-in-progress') {
|
||||
mediaWhere[Sequelize.Op.and] = [
|
||||
// Filters for ebook only
|
||||
mediaWhere = [
|
||||
Sequelize.where(Sequelize.fn('json_array_length', Sequelize.col('audioFiles')), 0),
|
||||
{
|
||||
'$mediaProgresses.ebookProgress$': {
|
||||
[Sequelize.Op.gt]: 0
|
||||
@ -129,6 +131,17 @@ module.exports = {
|
||||
'$mediaProgresses.isFinished$': false
|
||||
}
|
||||
]
|
||||
} else if (value === 'ebook-finished') {
|
||||
// Filters for ebook only
|
||||
mediaWhere = [
|
||||
Sequelize.where(Sequelize.fn('json_array_length', Sequelize.col('audioFiles')), 0),
|
||||
{
|
||||
'$mediaProgresses.isFinished$': true,
|
||||
'ebookFile': {
|
||||
[Sequelize.Op.not]: null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
} else if (group === 'series' && value === 'no-series') {
|
||||
mediaWhere['$series.id$'] = null
|
||||
@ -144,7 +157,9 @@ module.exports = {
|
||||
} else if (group === 'languages') {
|
||||
mediaWhere['language'] = value
|
||||
} else if (group === 'tracks') {
|
||||
if (value === 'multi') {
|
||||
if (value === 'none') {
|
||||
mediaWhere = Sequelize.where(Sequelize.fn('json_array_length', Sequelize.col('audioFiles')), 0)
|
||||
} else if (value === 'multi') {
|
||||
mediaWhere = Sequelize.where(Sequelize.fn('json_array_length', Sequelize.col('audioFiles')), {
|
||||
[Sequelize.Op.gt]: 1
|
||||
})
|
||||
@ -542,7 +557,7 @@ module.exports = {
|
||||
|
||||
return libraryItem
|
||||
})
|
||||
Logger.debug('Found', libraryItems.length, 'library items', 'total=', count)
|
||||
|
||||
return {
|
||||
libraryItems,
|
||||
count
|
||||
@ -663,8 +678,6 @@ module.exports = {
|
||||
offset
|
||||
})
|
||||
|
||||
Logger.debug('Found', series.length, 'series to continue', 'total=', count)
|
||||
|
||||
// Step 3: Map series to library items by selecting the first unfinished book in the series
|
||||
const libraryItems = series.map(s => {
|
||||
// Natural sort sequence, nulls last
|
||||
@ -695,6 +708,128 @@ module.exports = {
|
||||
libraryItem.media = bookSeries.book
|
||||
return libraryItem
|
||||
})
|
||||
return {
|
||||
libraryItems,
|
||||
count
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get book library items for the "Discover" shelf
|
||||
* Random selection of books that are not started
|
||||
* - only includes the first book of a not-started series
|
||||
* @param {string} libraryId
|
||||
* @param {string} userId
|
||||
* @param {string[]} include
|
||||
* @param {number} limit
|
||||
* @returns {object} {libraryItems:LibraryItem, count:number}
|
||||
*/
|
||||
async getDiscoverLibraryItems(libraryId, userId, include, limit) {
|
||||
// Step 1: Get the first book of every series that hasnt been started yet
|
||||
const seriesNotStarted = await Database.models.series.findAll({
|
||||
where: [
|
||||
{
|
||||
libraryId
|
||||
},
|
||||
Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM bookSeries bs LEFT OUTER JOIN mediaProgresses mp ON mp.mediaItemId = bs.bookId WHERE bs.seriesId = series.id AND mp.userId = :userId AND (mp.isFinished = 1 OR mp.currentTime > 0))`), 0)
|
||||
],
|
||||
replacements: {
|
||||
userId
|
||||
},
|
||||
attributes: ['id'],
|
||||
include: {
|
||||
model: Database.models.bookSeries,
|
||||
attributes: ['bookId', 'sequence'],
|
||||
separate: true,
|
||||
required: true,
|
||||
order: [
|
||||
[Sequelize.literal('CAST(sequence AS INTEGER) ASC NULLS LAST')]
|
||||
],
|
||||
limit: 1
|
||||
},
|
||||
subQuery: false
|
||||
})
|
||||
|
||||
const booksFromSeriesToInclude = seriesNotStarted.map(se => se.bookSeries?.[0]?.bookId).filter(bid => bid)
|
||||
|
||||
// optional include rssFeed
|
||||
const libraryItemIncludes = []
|
||||
if (include.includes('rssfeed')) {
|
||||
libraryItemIncludes.push({
|
||||
model: Database.models.feed
|
||||
})
|
||||
}
|
||||
|
||||
// Step 2: Get books not started and not in a series OR is the first book of a series not started (ordered randomly)
|
||||
const { rows: books, count } = await Database.models.book.findAndCountAll({
|
||||
where: {
|
||||
'$mediaProgresses.isFinished$': {
|
||||
[Sequelize.Op.or]: [null, 0]
|
||||
},
|
||||
'$mediaProgresses.currentTime$': {
|
||||
[Sequelize.Op.or]: [null, 0]
|
||||
},
|
||||
[Sequelize.Op.or]: [
|
||||
Sequelize.where(Sequelize.literal(`(SELECT COUNT(*) FROM bookSeries bs where bs.bookId = book.id)`), 0),
|
||||
{
|
||||
id: {
|
||||
[Sequelize.Op.in]: booksFromSeriesToInclude
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Database.models.libraryItem,
|
||||
where: {
|
||||
libraryId
|
||||
},
|
||||
include: libraryItemIncludes
|
||||
},
|
||||
{
|
||||
model: Database.models.mediaProgress,
|
||||
where: {
|
||||
userId
|
||||
},
|
||||
required: false
|
||||
},
|
||||
{
|
||||
model: Database.models.bookAuthor,
|
||||
attributes: ['authorId'],
|
||||
include: {
|
||||
model: Database.models.author
|
||||
},
|
||||
separate: true
|
||||
},
|
||||
{
|
||||
model: Database.models.bookSeries,
|
||||
attributes: ['seriesId', 'sequence'],
|
||||
include: {
|
||||
model: Database.models.series
|
||||
},
|
||||
separate: true
|
||||
}
|
||||
],
|
||||
subQuery: false,
|
||||
distinct: true,
|
||||
limit,
|
||||
order: Database.sequelize.random()
|
||||
})
|
||||
|
||||
// Step 3: Map books to library items
|
||||
const libraryItems = books.map((bookExpanded) => {
|
||||
const libraryItem = bookExpanded.libraryItem.toJSON()
|
||||
const book = bookExpanded.toJSON()
|
||||
delete book.libraryItem
|
||||
libraryItem.media = book
|
||||
|
||||
if (libraryItem.feeds?.length) {
|
||||
libraryItem.rssFeed = libraryItem.feeds[0]
|
||||
}
|
||||
|
||||
return libraryItem
|
||||
})
|
||||
|
||||
return {
|
||||
libraryItems,
|
||||
count
|
||||
|
@ -147,7 +147,7 @@ module.exports = {
|
||||
|
||||
return libraryItem
|
||||
})
|
||||
Logger.debug('Found', libraryItems.length, 'library items', 'total=', count)
|
||||
|
||||
return {
|
||||
libraryItems,
|
||||
count
|
||||
|
Loading…
Reference in New Issue
Block a user