diff --git a/server/Database.js b/server/Database.js index faec70b8..1d9bbaa3 100644 --- a/server/Database.js +++ b/server/Database.js @@ -59,6 +59,16 @@ class Database { return this.models.libraryItem } + /** @type {typeof import('./models/PodcastEpisode')} */ + get podcastEpisodeModel() { + return this.models.podcastEpisode + } + + /** @type {typeof import('./models/MediaProgress')} */ + get mediaProgressModel() { + return this.models.mediaProgress + } + async checkHasDb() { if (!await fs.pathExists(this.dbPath)) { Logger.info(`[Database] absdatabase.sqlite not found at ${this.dbPath}`) diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js index ea5876b3..8d2cbc80 100644 --- a/server/controllers/LibraryController.js +++ b/server/controllers/LibraryController.js @@ -16,6 +16,7 @@ const naturalSort = createNewSortInstance({ const Database = require('../Database') const libraryFilters = require('../utils/queries/libraryFilters') +const libraryItemsPodcastFilters = require('../utils/queries/libraryItemsPodcastFilters') class LibraryController { constructor() { } @@ -1024,7 +1025,12 @@ class LibraryController { Logger.info('[LibraryController] Scan complete') } - // GET: api/libraries/:id/recent-episode + /** + * GET: /api/libraries/:id/recent-episodes + * Used for latest page + * @param {import('express').Request} req + * @param {import('express').Response} res + */ async getRecentEpisodes(req, res) { if (!req.library.isPodcast) { return res.sendStatus(404) @@ -1032,35 +1038,12 @@ class LibraryController { const payload = { episodes: [], - total: 0, limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0, page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0, } - var allUnfinishedEpisodes = [] - for (const libraryItem of req.libraryItems) { - const unfinishedEpisodes = libraryItem.media.episodes.filter(ep => { - const userProgress = req.user.getMediaProgress(libraryItem.id, ep.id) - return !userProgress || !userProgress.isFinished - }).map(_ep => { - const ep = _ep.toJSONExpanded() - ep.podcast = libraryItem.media.toJSONMinified() - ep.libraryItemId = libraryItem.id - ep.libraryId = libraryItem.libraryId - return ep - }) - allUnfinishedEpisodes.push(...unfinishedEpisodes) - } - - payload.total = allUnfinishedEpisodes.length - - allUnfinishedEpisodes = sort(allUnfinishedEpisodes).desc(ep => ep.publishedAt) - - if (payload.limit) { - var startIndex = payload.page * payload.limit - allUnfinishedEpisodes = allUnfinishedEpisodes.slice(startIndex, startIndex + payload.limit) - } - payload.episodes = allUnfinishedEpisodes + const offset = payload.page * payload.limit + payload.episodes = await libraryItemsPodcastFilters.getRecentEpisodes(req.user, req.library, payload.limit, offset) res.json(payload) } diff --git a/server/models/Podcast.js b/server/models/Podcast.js index 5f75ee3c..06b410cf 100644 --- a/server/models/Podcast.js +++ b/server/models/Podcast.js @@ -54,7 +54,7 @@ class Podcast extends Model { static getOldPodcast(libraryItemExpanded) { const podcastExpanded = libraryItemExpanded.media - const podcastEpisodes = podcastExpanded.podcastEpisodes?.map(ep => ep.getOldPodcastEpisode(libraryItemExpanded.id)).sort((a, b) => a.index - b.index) + const podcastEpisodes = podcastExpanded.podcastEpisodes?.map(ep => ep.getOldPodcastEpisode(libraryItemExpanded.id).toJSON()).sort((a, b) => a.index - b.index) return { id: podcastExpanded.id, libraryItemId: libraryItemExpanded.id, diff --git a/server/models/PodcastEpisode.js b/server/models/PodcastEpisode.js index 6aff7866..2626ee0f 100644 --- a/server/models/PodcastEpisode.js +++ b/server/models/PodcastEpisode.js @@ -1,4 +1,5 @@ const { DataTypes, Model } = require('sequelize') +const oldPodcastEpisode = require('../objects/entities/PodcastEpisode') class PodcastEpisode extends Model { constructor(values, options) { @@ -44,6 +45,10 @@ class PodcastEpisode extends Model { this.updatedAt } + /** + * @param {string} libraryItemId + * @returns {oldPodcastEpisode} + */ getOldPodcastEpisode(libraryItemId = null) { let enclosure = null if (this.enclosureURL) { @@ -53,7 +58,7 @@ class PodcastEpisode extends Model { length: this.enclosureSize !== null ? String(this.enclosureSize) : null } } - return { + return new oldPodcastEpisode({ libraryItemId: libraryItemId || null, podcastId: this.podcastId, id: this.id, @@ -72,7 +77,7 @@ class PodcastEpisode extends Model { publishedAt: this.publishedAt?.valueOf() || null, addedAt: this.createdAt.valueOf(), updatedAt: this.updatedAt.valueOf() - } + }) } static createFromOld(oldEpisode) { diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index c0ff10a0..e95cf1c3 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -94,7 +94,7 @@ class ApiRouter { this.router.delete('/libraries/:id/narrators/:narratorId', LibraryController.middlewareNew.bind(this), LibraryController.removeNarrator.bind(this)) this.router.get('/libraries/:id/matchall', LibraryController.middlewareNew.bind(this), LibraryController.matchAll.bind(this)) this.router.post('/libraries/:id/scan', LibraryController.middlewareNew.bind(this), LibraryController.scan.bind(this)) - this.router.get('/libraries/:id/recent-episodes', LibraryController.middleware.bind(this), LibraryController.getRecentEpisodes.bind(this)) + this.router.get('/libraries/:id/recent-episodes', LibraryController.middlewareNew.bind(this), LibraryController.getRecentEpisodes.bind(this)) this.router.get('/libraries/:id/opml', LibraryController.middleware.bind(this), LibraryController.getOPMLFile.bind(this)) this.router.post('/libraries/order', LibraryController.reorder.bind(this)) diff --git a/server/utils/queries/libraryItemsPodcastFilters.js b/server/utils/queries/libraryItemsPodcastFilters.js index b13125ba..e14718e5 100644 --- a/server/utils/queries/libraryItemsPodcastFilters.js +++ b/server/utils/queries/libraryItemsPodcastFilters.js @@ -283,7 +283,7 @@ module.exports = { const podcast = ep.podcast.toJSON() delete podcast.libraryItem libraryItem.media = podcast - libraryItem.recentEpisode = ep.getOldPodcastEpisode(libraryItem.id) + libraryItem.recentEpisode = ep.getOldPodcastEpisode(libraryItem.id).toJSON() return libraryItem }) @@ -396,5 +396,64 @@ module.exports = { podcast: itemMatches, tags: tagMatches } + }, + + /** + * Most recent podcast episodes not finished + * @param {import('../../objects/user/User')} oldUser + * @param {import('../../objects/Library')} oldLibrary + * @param {number} limit + * @param {number} offset + * @returns {Promise} + */ + async getRecentEpisodes(oldUser, oldLibrary, limit, offset) { + const userPermissionPodcastWhere = this.getUserPermissionPodcastWhereQuery(oldUser) + + const episodes = await Database.podcastEpisodeModel.findAll({ + where: { + '$mediaProgresses.isFinished$': { + [Sequelize.Op.or]: [null, false] + } + }, + replacements: userPermissionPodcastWhere.replacements, + include: [ + { + model: Database.podcastModel, + where: userPermissionPodcastWhere.podcastWhere, + required: true, + include: { + model: Database.libraryItemModel, + where: { + libraryId: oldLibrary.id + } + } + }, + { + model: Database.mediaProgressModel, + where: { + userId: oldUser.id + }, + required: false + } + ], + order: [ + ['publishedAt', 'DESC'] + ], + subQuery: false, + limit, + offset + }) + + const episodeResults = episodes.map((ep) => { + const libraryItem = ep.podcast.libraryItem + libraryItem.media = ep.podcast + const oldPodcast = Database.podcastModel.getOldPodcast(libraryItem) + const oldPodcastEpisode = ep.getOldPodcastEpisode(libraryItem.id).toJSONExpanded() + oldPodcastEpisode.podcast = oldPodcast + oldPodcastEpisode.libraryId = libraryItem.libraryId + return oldPodcastEpisode + }) + + return episodeResults } } \ No newline at end of file