diff --git a/server/managers/BackupManager.js b/server/managers/BackupManager.js index caedd567..cf90d0f8 100644 --- a/server/managers/BackupManager.js +++ b/server/managers/BackupManager.js @@ -11,7 +11,6 @@ const StreamZip = require('../libs/nodeStreamZip') // Utils const { getFileSize } = require('../utils/fileUtils') -const filePerms = require('../utils/filePerms') const Backup = require('../objects/Backup') diff --git a/server/models/LibraryItem.js b/server/models/LibraryItem.js index e68a9736..1b2bbfa5 100644 --- a/server/models/LibraryItem.js +++ b/server/models/LibraryItem.js @@ -464,6 +464,18 @@ module.exports = (sequelize) => { total: ebooksInProgressPayload.count }) } + + const continueSeriesPayload = await libraryFilters.getLibraryItemsContinueSeries(library, userId, include, limit) + if (continueSeriesPayload.libraryItems.length) { + shelves.push({ + id: 'continue-series', + label: 'Continue Series', + labelStringKey: 'LabelContinueSeries', + type: 'book', + entities: continueSeriesPayload.libraryItems, + total: continueSeriesPayload.count + }) + } } const mostRecentPayload = await libraryFilters.getLibraryItemsMostRecentlyAdded(library, userId, include, limit) @@ -478,9 +490,6 @@ module.exports = (sequelize) => { }) } - // TODO: Handle continue series library items - const continueSeriesPayload = await libraryFilters.getLibraryItemsContinueSeries(library, userId, include, limit) - return shelves } diff --git a/server/utils/queries/libraryFilters.js b/server/utils/queries/libraryFilters.js index 82ae64a1..55267f61 100644 --- a/server/utils/queries/libraryFilters.js +++ b/server/utils/queries/libraryFilters.js @@ -90,6 +90,19 @@ module.exports = { }, async getLibraryItemsContinueSeries(library, userId, include, limit) { - await libraryItemsBookFilters.getContinueSeriesLibraryItems(library.id, userId, limit, 0) + const { libraryItems, count } = await libraryItemsBookFilters.getContinueSeriesLibraryItems(library.id, userId, include, limit, 0) + 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() + } + if (li.series) { + oldLibraryItem.media.metadata.series = li.series + } + return oldLibraryItem + }), + count + } } } \ No newline at end of file diff --git a/server/utils/queries/libraryItemsBookFilters.js b/server/utils/queries/libraryItemsBookFilters.js index 2f6f7651..1c901428 100644 --- a/server/utils/queries/libraryItemsBookFilters.js +++ b/server/utils/queries/libraryItemsBookFilters.js @@ -549,20 +549,54 @@ module.exports = { } }, - async getContinueSeriesLibraryItems(libraryId, userId, limit, offset) { - const { rows: series, count } = await Database.models.series.findAndCountAll({ - where: [ - Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM books b, bookSeries bs, mediaProgresses mp WHERE mp.mediaItemId = b.id AND mp.userId = :userId AND bs.bookId = b.id AND bs.seriesId = series.id AND mp.isFinished = 1)`), { - [Sequelize.Op.gt]: 0 - }), - Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM books b, bookSeries bs, mediaProgresses mp WHERE mp.mediaItemId = b.id AND mp.userId = :userId AND bs.bookId = b.id AND bs.seriesId = series.id AND mp.isFinished = 0 AND mp.currentTime > 0)`), 0), - Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM bookSeries bs LEFT OUTER JOIN mediaProgresses mp ON mp.userId = :userId AND mp.mediaItemId = bs.bookId WHERE bs.seriesId = series.id AND (mp.currentTime = 0 OR mp.currentTime IS NULL) AND (mp.isFinished = 0 OR mp.isFinished IS NULL))`), { - [Sequelize.Op.gt]: 0 - }) - ], - replacements: { + async getContinueSeriesLibraryItems(libraryId, userId, include, limit, offset) { + const mediaProgressForUserForSeries = await Database.models.mediaProgress.findAll({ + where: { userId }, + include: [ + { + model: Database.models.book, + attributes: ['id', 'title'], + include: { + model: Database.models.series, + attributes: ['id'], + through: { + attributes: [] + }, + required: true + }, + required: true + } + ] + }) + + let seriesToIncludeMap = {} + let seriesToExclude = [] + for (const prog of mediaProgressForUserForSeries) { + const series = prog.mediaItem?.series || [] + for (const s of series) { + if (prog.currentTime > 0 && !prog.isFinished) { // in-progress + delete seriesToIncludeMap[s.id] + if (!seriesToExclude.includes(s.id)) seriesToExclude.push(s.id) + } else if (prog.isFinished && !seriesToExclude.includes(s.id)) { // finished + const lastUpdate = prog.updatedAt?.valueOf() || 0 + if (!seriesToIncludeMap[s.id] || lastUpdate > seriesToIncludeMap[s.id]) { + seriesToIncludeMap[s.id] = lastUpdate + } + } + } + } + + const { rows: series, count } = await Database.models.series.findAndCountAll({ + where: { + id: { + [Sequelize.Op.in]: Object.keys(seriesToIncludeMap) + }, + '$books.mediaProgresses.isFinished$': { + [Sequelize.Op.or]: [false, null] + } + }, distinct: true, include: [ { @@ -570,6 +604,7 @@ module.exports = { through: { attributes: ['sequence'] }, + required: true, include: [ { model: Database.models.libraryItem, @@ -578,21 +613,53 @@ module.exports = { } }, { - model: Database.models.author, - attributes: ['id', 'name'], - through: { - attributes: [] - } + model: Database.models.bookAuthor, + attributes: ['authorId'], + include: { + model: Database.models.author + }, + separate: true + }, + { + model: Database.models.mediaProgress, + where: { + userId + }, + required: false } ] } ], - order: [[Sequelize.literal(`\`books.bookSeries.sequence\` COLLATE NOCASE ASC NULLS LAST`)]], + order: [ + [Sequelize.literal(`CAST(\`books.bookSeries.sequence\` AS INTEGER) COLLATE NOCASE ASC NULLS LAST`)], + [Sequelize.literal(`\`books.mediaProgresses.updatedAt\` DESC`)] + ], subQuery: false, limit, offset }) Logger.debug('Found', series.length, 'series to continue', 'total=', count) + + const libraryItems = series.map(s => { + const book = s.books.find(book => { + return !book.mediaProgresses?.[0]?.isFinished + }) + const libraryItem = book.libraryItem.toJSON() + + libraryItem.series = { + id: s.id, + name: s.name, + sequence: book.bookSeries.sequence + } + delete book.bookSeries + + libraryItem.media = book + return libraryItem + }) + return { + libraryItems, + count + } } } \ No newline at end of file