From d9f828c717b4c65b59579baa7e6bdcd6dc2d53f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rasmus=20Kr=C3=A4mer?= Date: Sun, 5 May 2024 13:14:30 +0200 Subject: [PATCH 1/4] Added item sessions endpoint --- server/controllers/MeController.js | 41 +++++++++++++++++++++++++++--- server/routers/ApiRouter.js | 13 +++++++--- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/server/controllers/MeController.js b/server/controllers/MeController.js index b5147fb7d5..d14c07e354 100644 --- a/server/controllers/MeController.js +++ b/server/controllers/MeController.js @@ -33,6 +33,39 @@ class MeController { res.json(payload) } + // GET: api/me/item/listening-sessions/:libraryItemId/:episodeId + async getListeningSessions(req, res) { + const libraryItem = await Database.libraryItemModel.getOldById(req.params.libraryItemId) + const episode = (req.params.episodeId && libraryItem && libraryItem.isPodcast) ? libraryItem.media.getEpisode(req.params.episodeId) : null + + if (!libraryItem || (libraryItem.isPodcast && !episode)) { + Logger.error(`[PlaybackSessionManager] listening-sessions: Media item not found for library item id "${req.params.id}"`) + return { + success: false, + error: 'Media item not found' + } + } + + const mediaItemId = episode ? episode.id : libraryItem.media.id + let listeningSessions = await this.getUserItemListeningSessionsHelper(req.user.id, mediaItemId) + + const itemsPerPage = toNumber(req.query.itemsPerPage, 10) || 10 + const page = toNumber(req.query.page, 0) + + const start = page * itemsPerPage + const sessions = listeningSessions.slice(start, start + itemsPerPage) + + const payload = { + total: listeningSessions.length, + numPages: Math.ceil(listeningSessions.length / itemsPerPage), + page, + itemsPerPage, + sessions + } + + res.json(payload) + } + // GET: api/me/listening-stats async getListeningStats(req, res) { const listeningStats = await this.getUserListeningStatsHelpers(req.user.id) @@ -337,9 +370,9 @@ class MeController { /** * GET: /api/me/stats/year/:year - * - * @param {import('express').Request} req - * @param {import('express').Response} res + * + * @param {import('express').Request} req + * @param {import('express').Response} res */ async getStatsForYear(req, res) { const year = Number(req.params.year) @@ -351,4 +384,4 @@ class MeController { res.json(data) } } -module.exports = new MeController() \ No newline at end of file +module.exports = new MeController() diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index 43e7c907e4..fc5d6a294c 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -166,6 +166,8 @@ class ApiRouter { // this.router.get('/me', MeController.getCurrentUser.bind(this)) this.router.get('/me/listening-sessions', MeController.getListeningSessions.bind(this)) + this.router.get('/me/item/listening-sessions/:libraryItemId', MeController.getListeningSessions.bind(this)) + this.router.get('/me/item/listening-sessions/:libraryItemId/:episodeId', MeController.getListeningSessions.bind(this)) this.router.get('/me/listening-stats', MeController.getListeningStats.bind(this)) this.router.get('/me/progress/:id/remove-from-continue-listening', MeController.removeItemFromContinueListening.bind(this)) this.router.get('/me/progress/:id/:episodeId?', MeController.getMediaProgress.bind(this)) @@ -425,9 +427,9 @@ class ApiRouter { /** * Used when a series is removed from a book * Series is removed if it only has 1 book - * + * * @param {string} bookId - * @param {string[]} seriesIds + * @param {string[]} seriesIds */ async checkRemoveEmptySeries(bookId, seriesIds) { if (!seriesIds?.length) return @@ -455,7 +457,7 @@ class ApiRouter { /** * Remove an empty series & close an open RSS feed - * @param {import('../models/Series')} series + * @param {import('../models/Series')} series */ async removeEmptySeries(series) { await this.rssFeedManager.closeFeedForEntityId(series.id) @@ -474,6 +476,11 @@ class ApiRouter { return userSessions.sort((a, b) => b.updatedAt - a.updatedAt) } + async getUserItemListeningSessionsHelper(userId, mediaItemId) { + const userSessions = await Database.getPlaybackSessions({ userId, mediaItemId }) + return userSessions.sort((a, b) => b.updatedAt - a.updatedAt) + } + async getUserListeningStatsHelpers(userId) { const today = date.format(new Date(), 'YYYY-MM-DD') From 09e26a9e564e4540bff44633a31a82d048937c4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rasmus=20Kr=C3=A4mer?= Date: Tue, 14 May 2024 10:51:50 +0200 Subject: [PATCH 2/4] Use new database models, fix function name and use optional path parameter --- server/controllers/MeController.js | 8 ++++---- server/routers/ApiRouter.js | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/server/controllers/MeController.js b/server/controllers/MeController.js index d14c07e354..735728cab5 100644 --- a/server/controllers/MeController.js +++ b/server/controllers/MeController.js @@ -34,11 +34,11 @@ class MeController { } // GET: api/me/item/listening-sessions/:libraryItemId/:episodeId - async getListeningSessions(req, res) { - const libraryItem = await Database.libraryItemModel.getOldById(req.params.libraryItemId) - const episode = (req.params.episodeId && libraryItem && libraryItem.isPodcast) ? libraryItem.media.getEpisode(req.params.episodeId) : null + async getItemListeningSessions(req, res) { + const libraryItem = await Database.libraryItemModel.findByPk(req.params.libraryItemId) + const episode = await Database.podcastEpisodeModel.findByPk(req.params.episodeId) - if (!libraryItem || (libraryItem.isPodcast && !episode)) { + if (!libraryItem || (libraryItem.mediaType === "podcast" && !episode)) { Logger.error(`[PlaybackSessionManager] listening-sessions: Media item not found for library item id "${req.params.id}"`) return { success: false, diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index fc5d6a294c..2b7adacc4d 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -166,8 +166,7 @@ class ApiRouter { // this.router.get('/me', MeController.getCurrentUser.bind(this)) this.router.get('/me/listening-sessions', MeController.getListeningSessions.bind(this)) - this.router.get('/me/item/listening-sessions/:libraryItemId', MeController.getListeningSessions.bind(this)) - this.router.get('/me/item/listening-sessions/:libraryItemId/:episodeId', MeController.getListeningSessions.bind(this)) + this.router.get('/me/item/listening-sessions/:libraryItemId/:episodeId?', MeController.getItemListeningSessions.bind(this)) this.router.get('/me/listening-stats', MeController.getListeningStats.bind(this)) this.router.get('/me/progress/:id/remove-from-continue-listening', MeController.removeItemFromContinueListening.bind(this)) this.router.get('/me/progress/:id/:episodeId?', MeController.getMediaProgress.bind(this)) From 9c2ed279df5ae181670a0c2f9e35106238adb786 Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 23 May 2024 16:32:34 -0500 Subject: [PATCH 3/4] Fix mediaId reference, add JS docs, autoformatting --- server/controllers/MeController.js | 49 ++++++++++++++++++------------ 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/server/controllers/MeController.js b/server/controllers/MeController.js index 735728cab5..efc4357f84 100644 --- a/server/controllers/MeController.js +++ b/server/controllers/MeController.js @@ -6,7 +6,7 @@ const { toNumber } = require('../utils/index') const userStats = require('../utils/queries/userStats') class MeController { - constructor() { } + constructor() {} getCurrentUser(req, res) { res.json(req.user.toJSONForBrowser()) @@ -33,20 +33,27 @@ class MeController { res.json(payload) } - // GET: api/me/item/listening-sessions/:libraryItemId/:episodeId + /** + * GET: /api/me/item/listening-sessions/:libraryItemId/:episodeId + * + * @this import('../routers/ApiRouter') + * + * @param {import('express').Request} req + * @param {import('express').Response} res + */ async getItemListeningSessions(req, res) { const libraryItem = await Database.libraryItemModel.findByPk(req.params.libraryItemId) const episode = await Database.podcastEpisodeModel.findByPk(req.params.episodeId) - if (!libraryItem || (libraryItem.mediaType === "podcast" && !episode)) { - Logger.error(`[PlaybackSessionManager] listening-sessions: Media item not found for library item id "${req.params.id}"`) + if (!libraryItem || (libraryItem.mediaType === 'podcast' && !episode)) { + Logger.error(`[PlaybackSessionManager] listening-sessions: Media item not found for library item id "${req.params.libraryItemId}"`) return { success: false, error: 'Media item not found' } } - const mediaItemId = episode ? episode.id : libraryItem.media.id + const mediaItemId = episode ? episode.id : libraryItem.mediaId let listeningSessions = await this.getUserItemListeningSessionsHelper(req.user.id, mediaItemId) const itemsPerPage = toNumber(req.query.itemsPerPage, 10) || 10 @@ -113,7 +120,7 @@ class MeController { if (!libraryItem) { return res.status(404).send('Item not found') } - if (!libraryItem.media.episodes.find(ep => ep.id === episodeId)) { + if (!libraryItem.media.episodes.find((ep) => ep.id === episodeId)) { Logger.error(`[MeController] removeEpisode episode ${episodeId} not found for item ${libraryItem.id}`) return res.status(404).send('Episode not found') } @@ -156,7 +163,7 @@ class MeController { // POST: api/me/item/:id/bookmark async createBookmark(req, res) { - if (!await Database.libraryItemModel.checkExistsById(req.params.id)) return res.sendStatus(404) + if (!(await Database.libraryItemModel.checkExistsById(req.params.id))) return res.sendStatus(404) const { time, title } = req.body const bookmark = req.user.createBookmark(req.params.id, time, title) @@ -167,7 +174,7 @@ class MeController { // PATCH: api/me/item/:id/bookmark async updateBookmark(req, res) { - if (!await Database.libraryItemModel.checkExistsById(req.params.id)) return res.sendStatus(404) + if (!(await Database.libraryItemModel.checkExistsById(req.params.id))) return res.sendStatus(404) const { time, title } = req.body if (!req.user.findBookmark(req.params.id, time)) { @@ -185,7 +192,7 @@ class MeController { // DELETE: api/me/item/:id/bookmark/:time async removeBookmark(req, res) { - if (!await Database.libraryItemModel.checkExistsById(req.params.id)) return res.sendStatus(404) + if (!(await Database.libraryItemModel.checkExistsById(req.params.id))) return res.sendStatus(404) const time = Number(req.params.time) if (isNaN(time)) return res.sendStatus(500) @@ -287,11 +294,10 @@ class MeController { // TODO: More efficient to do this in a single query for (const mediaProgress of req.user.mediaProgress) { if (!mediaProgress.isFinished && (mediaProgress.progress > 0 || mediaProgress.ebookProgress > 0)) { - const libraryItem = await Database.libraryItemModel.getOldById(mediaProgress.libraryItemId) if (libraryItem) { if (mediaProgress.episodeId && libraryItem.mediaType === 'podcast') { - const episode = libraryItem.media.episodes.find(ep => ep.id === mediaProgress.episodeId) + const episode = libraryItem.media.episodes.find((ep) => ep.id === mediaProgress.episodeId) if (episode) { const libraryItemWithEpisode = { ...libraryItem.toJSONMinified(), @@ -310,7 +316,9 @@ class MeController { } } - itemsInProgress = sort(itemsInProgress).desc(li => li.progressLastUpdate).slice(0, limit) + itemsInProgress = sort(itemsInProgress) + .desc((li) => li.progressLastUpdate) + .slice(0, limit) res.json({ libraryItems: itemsInProgress }) @@ -350,19 +358,22 @@ class MeController { // GET: api/me/progress/:id/remove-from-continue-listening async removeItemFromContinueListening(req, res) { - const mediaProgress = req.user.mediaProgress.find(mp => mp.id === req.params.id) + const mediaProgress = req.user.mediaProgress.find((mp) => mp.id === req.params.id) if (!mediaProgress) { return res.sendStatus(404) } const hasUpdated = req.user.removeProgressFromContinueListening(req.params.id) if (hasUpdated) { - await Database.mediaProgressModel.update({ - hideFromContinueListening: true - }, { - where: { - id: mediaProgress.id + await Database.mediaProgressModel.update( + { + hideFromContinueListening: true + }, + { + where: { + id: mediaProgress.id + } } - }) + ) SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) } res.json(req.user.toJSONForBrowser()) From 23dcf684d9e1753975175c463c5080435e1a9006 Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 23 May 2024 16:35:36 -0500 Subject: [PATCH 4/4] Item listening sessions endpoint returns 404 on not found media item --- server/controllers/MeController.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/server/controllers/MeController.js b/server/controllers/MeController.js index efc4357f84..7126d45b29 100644 --- a/server/controllers/MeController.js +++ b/server/controllers/MeController.js @@ -46,14 +46,11 @@ class MeController { const episode = await Database.podcastEpisodeModel.findByPk(req.params.episodeId) if (!libraryItem || (libraryItem.mediaType === 'podcast' && !episode)) { - Logger.error(`[PlaybackSessionManager] listening-sessions: Media item not found for library item id "${req.params.libraryItemId}"`) - return { - success: false, - error: 'Media item not found' - } + Logger.error(`[MeController] Media item not found for library item id "${req.params.libraryItemId}"`) + return res.sendStatus(404) } - const mediaItemId = episode ? episode.id : libraryItem.mediaId + const mediaItemId = episode?.id || libraryItem.mediaId let listeningSessions = await this.getUserItemListeningSessionsHelper(req.user.id, mediaItemId) const itemsPerPage = toNumber(req.query.itemsPerPage, 10) || 10