From 00f9940712911c926076f53e95fb967181d59593 Mon Sep 17 00:00:00 2001 From: Varun Bajaj Date: Mon, 16 Feb 2026 00:52:01 -0500 Subject: [PATCH 1/2] feat: add total duration and progress to series --- client/components/app/BookShelfToolbar.vue | 8 ++++++ server/controllers/LibraryController.js | 10 ++++++- server/models/Series.js | 31 ++++++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/client/components/app/BookShelfToolbar.vue b/client/components/app/BookShelfToolbar.vue index b7ecff624..f79346fe9 100644 --- a/client/components/app/BookShelfToolbar.vue +++ b/client/components/app/BookShelfToolbar.vue @@ -44,6 +44,14 @@
{{ $formatNumber(numShowing) }}
+
diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js index 55ef45690..4a1f873cc 100644 --- a/server/controllers/LibraryController.js +++ b/server/controllers/LibraryController.js @@ -789,10 +789,15 @@ class LibraryController { const seriesJson = series.toOldJSON() if (include.includes('progress')) { const libraryItemsFinished = libraryItemsInSeries.filter((li) => !!req.user.getMediaProgress(li.media.id)?.isFinished) + const totalListened = libraryItemsInSeries.reduce((acc, li) => { + const p = req.user.getMediaProgress(li.media.id) + return acc + (p?.isFinished ? (li.media.duration || 0) : (p?.currentTime || 0)) + }, 0) seriesJson.progress = { libraryItemIds: libraryItemsInSeries.map((li) => li.id), libraryItemIdsFinished: libraryItemsFinished.map((li) => li.id), - isFinished: libraryItemsFinished.length >= libraryItemsInSeries.length + isFinished: libraryItemsFinished.length >= libraryItemsInSeries.length, + totalDurationListened: totalListened } } @@ -801,6 +806,9 @@ class LibraryController { seriesJson.rssFeed = feedObj?.toOldJSONMinified() || null } + // populate total duration (same unit as libraryItem.duration) + seriesJson.totalDuration = await Database.seriesModel.getTotalDurationById(series.id) + res.json(seriesJson) } diff --git a/server/models/Series.js b/server/models/Series.js index 6ca288464..c6b92ad13 100644 --- a/server/models/Series.js +++ b/server/models/Series.js @@ -65,6 +65,37 @@ class Series extends Model { series.books = await series.getBooksExpandedWithLibraryItem() return series } + + /** + * + * @param {string} seriesId + * @returns {Promise} total duration (same unit as libraryItem.duration) + */ + static async getTotalDurationById(seriesId) { + const { book: bookModel, libraryItem: libraryItemModel, bookSeries: bookSeriesModel } = this.sequelize.models + + // Sum durations on libraryItems for books that belong to the series. + // This relies on Sequelize associations existing between: + // libraryItem -> book, book -> bookSeries (or bookSeries as a through model). + const total = await libraryItemModel.sum('duration', { + where: { mediaType: 'book' }, + include: [ + { + model: bookModel, + attributes: [], + include: [ + { + model: bookSeriesModel, + attributes: [], + where: { seriesId } + } + ] + } + ] + }) + + return Number(total) || 0 + } /** * From 1204936bf165ed7645393e707fcce39b5bff5077 Mon Sep 17 00:00:00 2001 From: Varun Bajaj Date: Mon, 16 Feb 2026 01:37:28 -0500 Subject: [PATCH 2/2] Updated JSDoc comment for getSeriesForLibrary --- server/controllers/LibraryController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js index 4a1f873cc..33c2639f0 100644 --- a/server/controllers/LibraryController.js +++ b/server/controllers/LibraryController.js @@ -770,7 +770,7 @@ class LibraryController { * * Optional includes (e.g. `?include=rssfeed,progress`) * rssfeed: adds `rssFeed` to series object if a feed is open - * progress: adds `progress` to series object with { libraryItemIds:Array, libraryItemIdsFinished:Array, isFinished:boolean } + * progress: adds `progress` to series object with { libraryItemIds:Array, libraryItemIdsFinished:Array, isFinished:boolean, totalDurationListened: number } * * @param {LibraryControllerRequest} req * @param {Response} res - Series