From 0426bf90e08b71a9479d65b33d44f9a8a202f7a6 Mon Sep 17 00:00:00 2001 From: Mitch Downey Date: Fri, 17 Nov 2023 21:12:10 -0600 Subject: [PATCH 1/2] Add getSecondaryQueueEpisodesForPlaylist handling and playlist.itemsOrder fix --- src/controllers/playlist.ts | 80 ++++++++++++++++++------------- src/controllers/secondaryQueue.ts | 49 ++++++++++++++++++- src/routes/secondaryQueue.ts | 32 ++++++++++++- 3 files changed, 125 insertions(+), 36 deletions(-) diff --git a/src/controllers/playlist.ts b/src/controllers/playlist.ts index 07605159..ac9d2f26 100644 --- a/src/controllers/playlist.ts +++ b/src/controllers/playlist.ts @@ -4,6 +4,7 @@ import { validateClassOrThrow } from '~/lib/errors' import { getUserSubscribedPlaylistIds } from './user' import { getMediaRef } from './mediaRef' import { getEpisode } from './episode' +import { combineAndSortPlaylistItems } from 'podverse-shared' const createError = require('http-errors') // medium = podcast are always be put in the general "mixed" category for playlists @@ -250,57 +251,72 @@ const addOrRemovePlaylistItem = async (playlistId, mediaRefId, episodeId, logged throw new createError.Unauthorized('Log in to delete this playlist') } - const itemsOrder = playlist.itemsOrder - let actionTaken = 'removed' + const { episodes, itemsOrder: previousItemsOrder, mediaRefs } = playlist + + /* + Prior to 4.15.6, the itemsOrder property was not getting set properly. + As a result the itemsOrder may have fallen out-of-sync with the saved + episodes and mediaRefs. Whenever the itemsOrder.length does not match + the combinedItems length, then fully update the itemsOrder. + */ + + let newItemsOrder = previousItemsOrder + const combinedItemsTotal = episodes.length + mediaRefs.length + if (combinedItemsTotal !== previousItemsOrder.length) { + const combinedAndSortedItems = combineAndSortPlaylistItems( + episodes as any, + mediaRefs as any, + previousItemsOrder as any + ) + newItemsOrder = combinedAndSortedItems.map((item: any) => item.id) + } - if (mediaRefId) { - // If no mediaRefs match filter, add the playlist item. - // Else, remove the playlist item. - const filteredMediaRefs = playlist.mediaRefs.filter((x) => x.id !== mediaRefId) + let actionTaken = '' - if (filteredMediaRefs.length === playlist.mediaRefs.length) { + if (mediaRefId) { + // If no mediaRefs match filter, add the playlist item. Else, remove the playlist item. + const filteredMediaRefs = mediaRefs.filter((x) => x.id !== mediaRefId) + const mediaRefWasRemoved = filteredMediaRefs.length === mediaRefs.length + if (mediaRefWasRemoved) { + actionTaken = 'removed' + playlist.mediaRefs = filteredMediaRefs + playlist.itemsOrder = newItemsOrder.filter((x) => x !== mediaRefId) + } else { const mediaRef = await getMediaRef(mediaRefId) - - if (mediaRef) { + if (!mediaRef) { + throw new createError.NotFound('MediaRef not found') + } else { + actionTaken = 'added' const filteredMedium = getPlaylistMedium(mediaRef.episode.podcast.medium) if (playlist.medium !== 'mixed' && playlist.medium !== filteredMedium) { throw new createError.NotFound('Item can not be added to this type of playlist') } - playlist.mediaRefs.push(mediaRef) - actionTaken = 'added' - } else { - throw new createError.NotFound('MediaRef not found') + playlist.itemsOrder.push(mediaRef.id) } - } else { - playlist.mediaRefs = filteredMediaRefs } - - playlist.itemsOrder = itemsOrder.filter((x) => x !== mediaRefId) } else if (episodeId) { - // If no episodes match filter, add the playlist item. - // Else, remove the playlist item. - const filteredEpisodes = playlist.episodes.filter((x) => x.id !== episodeId) - - if (filteredEpisodes.length === playlist.episodes.length) { + // If no episodes match filter, add the playlist item. Else, remove the playlist item. + const filteredEpisodes = episodes.filter((x) => x.id !== episodeId) + const episodeWasRemoved = filteredEpisodes.length === episodes.length + if (episodeWasRemoved) { + actionTaken = 'removed' + playlist.episodes = filteredEpisodes + playlist.itemsOrder = newItemsOrder.filter((x) => x !== episodeId) + } else { const episode = await getEpisode(episodeId) - - if (episode) { + if (!episode) { + throw new createError.NotFound('Episode not found') + } else { + actionTaken = 'added' const filteredMedium = getPlaylistMedium(episode.podcast.medium) if (playlist.medium !== 'mixed' && playlist.medium !== filteredMedium) { throw new createError.NotFound('Item can not be added to this type of playlist') } - playlist.episodes.push(episode) - actionTaken = 'added' - } else { - throw new createError.NotFound('Episode not found') + playlist.itemsOrder.push(episode.id) } - } else { - playlist.episodes = filteredEpisodes } - - playlist.itemsOrder = itemsOrder.filter((x) => x !== episodeId) } else { throw new createError.NotFound('Must provide a MediaRef or Episode id') } diff --git a/src/controllers/secondaryQueue.ts b/src/controllers/secondaryQueue.ts index 9f1865ff..0bc450cf 100644 --- a/src/controllers/secondaryQueue.ts +++ b/src/controllers/secondaryQueue.ts @@ -1,10 +1,12 @@ import { LessThan, MoreThan, getRepository } from 'typeorm' import { Episode, Podcast } from '~/entities' +import { getPlaylist } from './playlist' +import { combineAndSortPlaylistItems } from 'podverse-shared' const createError = require('http-errors') const relations = ['liveItem', 'podcast'] -type SecondaryQueueResponseData = { +type SecondaryQueueEpisodesForPodcastIdResponseData = { previousEpisodes: Episode[] nextEpisodes: Episode[] inheritedPodcast: Podcast @@ -13,7 +15,7 @@ type SecondaryQueueResponseData = { export const getSecondaryQueueEpisodesForPodcastId = async ( episodeId: string, podcastId: string -): Promise => { +): Promise => { const repository = getRepository(Episode) const currentEpisode = await repository.findOne( { @@ -72,3 +74,46 @@ export const getSecondaryQueueEpisodesForPodcastId = async ( return { previousEpisodes, nextEpisodes, inheritedPodcast } } + +type SecondaryQueueEpisodesForPlaylistIdResponseData = { + previousEpisodesAndMediaRefs: Episode[] + nextEpisodesAndMediaRefs: Episode[] +} + +export const getSecondaryQueueEpisodesForPlaylist = async ( + playlistId: string, + episodeOrMediaRefId: string, + audioOnly: boolean +): Promise => { + const currentPlaylist = await getPlaylist(playlistId) + + if (!currentPlaylist) { + throw new createError.NotFound('Playlist not found') + } + + const { episodes, itemsOrder, mediaRefs } = currentPlaylist + + const combinedPlaylistItems = combineAndSortPlaylistItems(episodes as any, mediaRefs as any, itemsOrder as any) + let filteredPlaylistItems = combinedPlaylistItems + if (audioOnly) { + filteredPlaylistItems = filteredPlaylistItems.filter((item: any) => { + if (item?.startTime) { + const mediaRef = item + return !mediaRef?.episode?.podcast?.hasVideo + } else { + const episode = item + return !episode?.podcast?.hasVideo + } + }) + } + + const currentItemIndex = filteredPlaylistItems.findIndex((item: any) => { + return item.id === episodeOrMediaRefId + }) + + // limit to the nearest 50 ids + const previousEpisodesAndMediaRefs = filteredPlaylistItems.slice(currentItemIndex - 50, currentItemIndex) + const nextEpisodesAndMediaRefs = filteredPlaylistItems.slice(currentItemIndex + 1, currentItemIndex + 1 + 50) + + return { previousEpisodesAndMediaRefs, nextEpisodesAndMediaRefs } +} diff --git a/src/routes/secondaryQueue.ts b/src/routes/secondaryQueue.ts index 41715c9d..c374d46b 100644 --- a/src/routes/secondaryQueue.ts +++ b/src/routes/secondaryQueue.ts @@ -1,12 +1,16 @@ import * as Router from 'koa-router' import { config } from '~/config' -import { getSecondaryQueueEpisodesForPodcastId } from '~/controllers/secondaryQueue' +import { + getSecondaryQueueEpisodesForPodcastId, + getSecondaryQueueEpisodesForPlaylist +} from '~/controllers/secondaryQueue' import { emitRouterError } from '~/lib/errors' import { parseNSFWHeader } from '~/middleware/parseNSFWHeader' const router = new Router({ prefix: `${config.apiPrefix}${config.apiVersion}/secondary-queue` }) -// Get episodes that are adjacent to a podcast +/* TODO: REMOVE THIS AFTER NEXT BETA RELEASE */ +// Get episodes that are adjacent within a podcast router.get('/episode/:episodeId/podcast/:podcastId', parseNSFWHeader, async (ctx) => { try { const data = await getSecondaryQueueEpisodesForPodcastId(ctx.params.episodeId, ctx.params.podcastId) @@ -16,4 +20,28 @@ router.get('/episode/:episodeId/podcast/:podcastId', parseNSFWHeader, async (ctx } }) +// Get episodes that are adjacent within a podcast +router.get('/podcast/:podcastId/episode/:episodeId', parseNSFWHeader, async (ctx) => { + try { + const data = await getSecondaryQueueEpisodesForPodcastId(ctx.params.episodeId, ctx.params.podcastId) + ctx.body = data + } catch (error) { + emitRouterError(error, ctx) + } +}) + +// Get episodes that are adjacent within a playlist +router.get('/playlist/:playlistId/episode-or-media-ref/:episodeOrMediaRef', parseNSFWHeader, async (ctx) => { + try { + const data = await getSecondaryQueueEpisodesForPlaylist( + ctx.params.playlistId, + ctx.params.episodeOrMediaRef, + !!ctx.query.audioOnly + ) + ctx.body = data + } catch (error) { + emitRouterError(error, ctx) + } +}) + export const secondaryQueueRouter = router From 6e45e6ec7e7a42a56e9c08151c9fa1f99bb182f2 Mon Sep 17 00:00:00 2001 From: Mitch Downey Date: Fri, 17 Nov 2023 21:12:31 -0600 Subject: [PATCH 2/2] Bump to version 4.15.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ae808dae..cb181f7d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "podverse-api", - "version": "4.15.5", + "version": "4.15.6", "description": "Data API, database migration scripts, and backend services for all Podverse models.", "contributors": [ "Mitch Downey"