Update playlist API endpoints to load library items from DB

This commit is contained in:
advplyr 2023-08-13 11:22:38 -05:00
parent 6056c14926
commit fc44c801f2
5 changed files with 358 additions and 205 deletions

View File

@ -254,83 +254,11 @@ class Database {
await this.models.library.removeById(libraryId) await this.models.library.removeById(libraryId)
} }
async createCollection(oldCollection) {
if (!this.sequelize) return false
const newCollection = await this.models.collection.createFromOld(oldCollection)
// Create CollectionBooks
if (newCollection) {
const collectionBooks = []
oldCollection.books.forEach((libraryItemId) => {
const libraryItem = this.libraryItems.find(li => li.id === libraryItemId)
if (libraryItem) {
collectionBooks.push({
collectionId: newCollection.id,
bookId: libraryItem.media.id
})
}
})
if (collectionBooks.length) {
await this.createBulkCollectionBooks(collectionBooks)
}
}
}
createBulkCollectionBooks(collectionBooks) { createBulkCollectionBooks(collectionBooks) {
if (!this.sequelize) return false if (!this.sequelize) return false
return this.models.collectionBook.bulkCreate(collectionBooks) return this.models.collectionBook.bulkCreate(collectionBooks)
} }
async createPlaylist(oldPlaylist) {
if (!this.sequelize) return false
const newPlaylist = await this.models.playlist.createFromOld(oldPlaylist)
if (newPlaylist) {
const playlistMediaItems = []
let order = 1
for (const mediaItemObj of oldPlaylist.items) {
const libraryItem = this.libraryItems.find(li => li.id === mediaItemObj.libraryItemId)
if (!libraryItem) continue
let mediaItemId = libraryItem.media.id // bookId
let mediaItemType = 'book'
if (mediaItemObj.episodeId) {
mediaItemType = 'podcastEpisode'
mediaItemId = mediaItemObj.episodeId
}
playlistMediaItems.push({
playlistId: newPlaylist.id,
mediaItemId,
mediaItemType,
order: order++
})
}
if (playlistMediaItems.length) {
await this.createBulkPlaylistMediaItems(playlistMediaItems)
}
}
}
updatePlaylist(oldPlaylist) {
if (!this.sequelize) return false
const playlistMediaItems = []
let order = 1
oldPlaylist.items.forEach((item) => {
const libraryItem = this.getLibraryItem(item.libraryItemId)
if (!libraryItem) return
playlistMediaItems.push({
playlistId: oldPlaylist.id,
mediaItemId: item.episodeId || libraryItem.media.id,
mediaItemType: item.episodeId ? 'podcastEpisode' : 'book',
order: order++
})
})
return this.models.playlist.fullUpdateFromOld(oldPlaylist, playlistMediaItems)
}
async removePlaylist(playlistId) {
if (!this.sequelize) return false
await this.models.playlist.removeById(playlistId)
}
createPlaylistMediaItem(playlistMediaItem) { createPlaylistMediaItem(playlistMediaItem) {
if (!this.sequelize) return false if (!this.sequelize) return false
return this.models.playlistMediaItem.create(playlistMediaItem) return this.models.playlistMediaItem.create(playlistMediaItem)
@ -341,11 +269,6 @@ class Database {
return this.models.playlistMediaItem.bulkCreate(playlistMediaItems) return this.models.playlistMediaItem.bulkCreate(playlistMediaItems)
} }
removePlaylistMediaItem(playlistId, mediaItemId) {
if (!this.sequelize) return false
return this.models.playlistMediaItem.removeByIds(playlistId, mediaItemId)
}
getLibraryItem(libraryItemId) { getLibraryItem(libraryItemId) {
if (!this.sequelize || !libraryItemId) return false if (!this.sequelize || !libraryItemId) return false

View File

@ -7,71 +7,187 @@ const Playlist = require('../objects/Playlist')
class PlaylistController { class PlaylistController {
constructor() { } constructor() { }
// POST: api/playlists /**
* POST: /api/playlists
* Create playlist
* @param {*} req
* @param {*} res
*/
async create(req, res) { async create(req, res) {
const newPlaylist = new Playlist() const oldPlaylist = new Playlist()
req.body.userId = req.user.id req.body.userId = req.user.id
const success = newPlaylist.setData(req.body) const success = oldPlaylist.setData(req.body)
if (!success) { if (!success) {
return res.status(400).send('Invalid playlist request data') return res.status(400).send('Invalid playlist request data')
} }
const jsonExpanded = newPlaylist.toJSONExpanded(Database.libraryItems)
await Database.createPlaylist(newPlaylist) // Create Playlist record
const newPlaylist = await Database.models.playlist.createFromOld(oldPlaylist)
// Lookup all library items in playlist
const libraryItemIds = oldPlaylist.items.map(i => i.libraryItemId).filter(i => i)
const libraryItemsInPlaylist = await Database.models.libraryItem.findAll({
where: {
id: libraryItemIds
}
})
// Create playlistMediaItem records
const mediaItemsToAdd = []
let order = 1
for (const mediaItemObj of oldPlaylist.items) {
const libraryItem = libraryItemsInPlaylist.find(li => li.id === mediaItemObj.libraryItemId)
if (!libraryItem) continue
mediaItemsToAdd.push({
mediaItemId: mediaItemObj.episodeId || libraryItem.mediaId,
mediaItemType: mediaItemObj.episodeId ? 'podcastEpisode' : 'book',
playlistId: oldPlaylist.id,
order: order++
})
}
if (mediaItemsToAdd.length) {
await Database.createBulkPlaylistMediaItems(mediaItemsToAdd)
}
const jsonExpanded = await newPlaylist.getOldJsonExpanded()
SocketAuthority.clientEmitter(newPlaylist.userId, 'playlist_added', jsonExpanded) SocketAuthority.clientEmitter(newPlaylist.userId, 'playlist_added', jsonExpanded)
res.json(jsonExpanded) res.json(jsonExpanded)
} }
// GET: api/playlists /**
* GET: /api/playlists
* Get all playlists for user
* @param {*} req
* @param {*} res
*/
async findAllForUser(req, res) { async findAllForUser(req, res) {
const playlistsForUser = await Database.models.playlist.getPlaylistsForUserAndLibrary(req.user.id) const playlistsForUser = await Database.models.playlist.findAll({
where: {
userId: req.user.id
}
})
const playlists = []
for (const playlist of playlistsForUser) {
const jsonExpanded = await playlist.getOldJsonExpanded()
playlists.push(jsonExpanded)
}
res.json({ res.json({
playlists: playlistsForUser.map(p => p.toJSONExpanded(Database.libraryItems)) playlists
}) })
} }
// GET: api/playlists/:id /**
findOne(req, res) { * GET: /api/playlists/:id
res.json(req.playlist.toJSONExpanded(Database.libraryItems)) * @param {*} req
* @param {*} res
*/
async findOne(req, res) {
const jsonExpanded = await req.playlist.getOldJsonExpanded()
res.json(jsonExpanded)
} }
// PATCH: api/playlists/:id /**
* PATCH: /api/playlists/:id
* Update playlist
* @param {*} req
* @param {*} res
*/
async update(req, res) { async update(req, res) {
const playlist = req.playlist const updatedPlaylist = req.playlist.set(req.body)
let wasUpdated = playlist.update(req.body) let wasUpdated = false
const jsonExpanded = playlist.toJSONExpanded(Database.libraryItems) const changed = updatedPlaylist.changed()
if (changed?.length) {
await req.playlist.save()
Logger.debug(`[PlaylistController] Updated playlist ${req.playlist.id} keys [${changed.join(',')}]`)
wasUpdated = true
}
// If array of items is passed in then update order of playlist media items
const libraryItemIds = req.body.items?.map(i => i.libraryItemId).filter(i => i) || []
if (libraryItemIds.length) {
const libraryItems = await Database.models.libraryItem.findAll({
where: {
id: libraryItemIds
}
})
const existingPlaylistMediaItems = await updatedPlaylist.getPlaylistMediaItems({
order: [['order', 'ASC']]
})
// Set an array of mediaItemId
const newMediaItemIdOrder = []
for (const item of req.body.items) {
const libraryItem = libraryItems.find(li => li.id === item.libraryItemId)
if (!libraryItem) {
continue
}
const mediaItemId = item.episodeId || libraryItem.mediaId
newMediaItemIdOrder.push(mediaItemId)
}
// Sort existing playlist media items into new order
existingPlaylistMediaItems.sort((a, b) => {
const aIndex = newMediaItemIdOrder.findIndex(i => i === a.mediaItemId)
const bIndex = newMediaItemIdOrder.findIndex(i => i === b.mediaItemId)
return aIndex - bIndex
})
// Update order on playlistMediaItem records
let order = 1
for (const playlistMediaItem of existingPlaylistMediaItems) {
if (playlistMediaItem.order !== order) {
await playlistMediaItem.update({
order
})
wasUpdated = true
}
order++
}
}
const jsonExpanded = await updatedPlaylist.getOldJsonExpanded()
if (wasUpdated) { if (wasUpdated) {
await Database.updatePlaylist(playlist) SocketAuthority.clientEmitter(updatedPlaylist.userId, 'playlist_updated', jsonExpanded)
SocketAuthority.clientEmitter(playlist.userId, 'playlist_updated', jsonExpanded)
} }
res.json(jsonExpanded) res.json(jsonExpanded)
} }
// DELETE: api/playlists/:id /**
* DELETE: /api/playlists/:id
* Remove playlist
* @param {*} req
* @param {*} res
*/
async delete(req, res) { async delete(req, res) {
const playlist = req.playlist const jsonExpanded = await req.playlist.getOldJsonExpanded()
const jsonExpanded = playlist.toJSONExpanded(Database.libraryItems) await req.playlist.destroy()
await Database.removePlaylist(playlist.id) SocketAuthority.clientEmitter(jsonExpanded.userId, 'playlist_removed', jsonExpanded)
SocketAuthority.clientEmitter(playlist.userId, 'playlist_removed', jsonExpanded)
res.sendStatus(200) res.sendStatus(200)
} }
// POST: api/playlists/:id/item /**
* POST: /api/playlists/:id/item
* Add item to playlist
* @param {*} req
* @param {*} res
*/
async addItem(req, res) { async addItem(req, res) {
const playlist = req.playlist const oldPlaylist = await Database.models.playlist.getById(req.playlist.id)
const itemToAdd = req.body const itemToAdd = req.body
if (!itemToAdd.libraryItemId) { if (!itemToAdd.libraryItemId) {
return res.status(400).send('Request body has no libraryItemId') return res.status(400).send('Request body has no libraryItemId')
} }
const libraryItem = Database.libraryItems.find(li => li.id === itemToAdd.libraryItemId) const libraryItem = await Database.models.libraryItem.getOldById(itemToAdd.libraryItemId)
if (!libraryItem) { if (!libraryItem) {
return res.status(400).send('Library item not found') return res.status(400).send('Library item not found')
} }
if (libraryItem.libraryId !== playlist.libraryId) { if (libraryItem.libraryId !== oldPlaylist.libraryId) {
return res.status(400).send('Library item in different library') return res.status(400).send('Library item in different library')
} }
if (playlist.containsItem(itemToAdd)) { if (oldPlaylist.containsItem(itemToAdd)) {
return res.status(400).send('Item already in playlist') return res.status(400).send('Item already in playlist')
} }
if ((itemToAdd.episodeId && !libraryItem.isPodcast) || (libraryItem.isPodcast && !itemToAdd.episodeId)) { if ((itemToAdd.episodeId && !libraryItem.isPodcast) || (libraryItem.isPodcast && !itemToAdd.episodeId)) {
@ -81,160 +197,248 @@ class PlaylistController {
return res.status(400).send('Episode not found in library item') return res.status(400).send('Episode not found in library item')
} }
playlist.addItem(itemToAdd.libraryItemId, itemToAdd.episodeId)
const playlistMediaItem = { const playlistMediaItem = {
playlistId: playlist.id, playlistId: oldPlaylist.id,
mediaItemId: itemToAdd.episodeId || libraryItem.media.id, mediaItemId: itemToAdd.episodeId || libraryItem.media.id,
mediaItemType: itemToAdd.episodeId ? 'podcastEpisode' : 'book', mediaItemType: itemToAdd.episodeId ? 'podcastEpisode' : 'book',
order: playlist.items.length order: oldPlaylist.items.length + 1
} }
const jsonExpanded = playlist.toJSONExpanded(Database.libraryItems)
await Database.createPlaylistMediaItem(playlistMediaItem) await Database.createPlaylistMediaItem(playlistMediaItem)
const jsonExpanded = await req.playlist.getOldJsonExpanded()
SocketAuthority.clientEmitter(playlist.userId, 'playlist_updated', jsonExpanded) SocketAuthority.clientEmitter(playlist.userId, 'playlist_updated', jsonExpanded)
res.json(jsonExpanded) res.json(jsonExpanded)
} }
// DELETE: api/playlists/:id/item/:libraryItemId/:episodeId? /**
* DELETE: /api/playlists/:id/item/:libraryItemId/:episodeId?
* Remove item from playlist
* @param {*} req
* @param {*} res
*/
async removeItem(req, res) { async removeItem(req, res) {
const playlist = req.playlist const oldLibraryItem = await Database.models.libraryItem.getOldById(req.params.libraryItemId)
const itemToRemove = { if (!oldLibraryItem) {
libraryItemId: req.params.libraryItemId, return res.status(404).send('Library item not found')
episodeId: req.params.episodeId || null
}
if (!playlist.containsItem(itemToRemove)) {
return res.sendStatus(404)
} }
playlist.removeItem(itemToRemove.libraryItemId, itemToRemove.episodeId) // Get playlist media items
const mediaItemId = req.params.episodeId || oldLibraryItem.media.id
const playlistMediaItems = await req.playlist.getPlaylistMediaItems({
order: [['order', 'ASC']]
})
const jsonExpanded = playlist.toJSONExpanded(Database.libraryItems) // Check if media item to delete is in playlist
const mediaItemToRemove = playlistMediaItems.find(pmi => pmi.mediaItemId === mediaItemId)
if (!mediaItemToRemove) {
return res.status(404).send('Media item not found in playlist')
}
// Remove record
await mediaItemToRemove.destroy()
// Update playlist media items order
let order = 1
for (const mediaItem of playlistMediaItems) {
if (mediaItem.mediaItemId === mediaItemId) continue
if (mediaItem.order !== order) {
await mediaItem.update({
order
})
}
order++
}
const jsonExpanded = await req.playlist.getOldJsonExpanded()
// Playlist is removed when there are no items // Playlist is removed when there are no items
if (!playlist.items.length) { if (!jsonExpanded.items.length) {
Logger.info(`[PlaylistController] Playlist "${playlist.name}" has no more items - removing it`) Logger.info(`[PlaylistController] Playlist "${jsonExpanded.name}" has no more items - removing it`)
await Database.removePlaylist(playlist.id) await req.playlist.destroy()
SocketAuthority.clientEmitter(playlist.userId, 'playlist_removed', jsonExpanded) SocketAuthority.clientEmitter(jsonExpanded.userId, 'playlist_removed', jsonExpanded)
} else { } else {
await Database.updatePlaylist(playlist) SocketAuthority.clientEmitter(jsonExpanded.userId, 'playlist_updated', jsonExpanded)
SocketAuthority.clientEmitter(playlist.userId, 'playlist_updated', jsonExpanded)
} }
res.json(jsonExpanded) res.json(jsonExpanded)
} }
// POST: api/playlists/:id/batch/add /**
* POST: /api/playlists/:id/batch/add
* Batch add playlist items
* @param {*} req
* @param {*} res
*/
async addBatch(req, res) { async addBatch(req, res) {
const playlist = req.playlist if (!req.body.items?.length) {
if (!req.body.items || !req.body.items.length) { return res.status(400).send('Invalid request body')
return res.status(500).send('Invalid request body')
} }
const itemsToAdd = req.body.items const itemsToAdd = req.body.items
let hasUpdated = false
let order = playlist.items.length + 1 const libraryItemIds = itemsToAdd.map(i => i.libraryItemId).filter(i => i)
const playlistMediaItems = [] if (!libraryItemIds.length) {
return res.status(400).send('Invalid request body')
}
// Find all library items
const libraryItems = await Database.models.libraryItem.findAll({
where: {
id: libraryItemIds
}
})
// Get all existing playlist media items
const existingPlaylistMediaItems = await req.playlist.getPlaylistMediaItems({
order: [['order', 'ASC']]
})
const mediaItemsToAdd = []
// Setup array of playlistMediaItem records to add
let order = existingPlaylistMediaItems.length + 1
for (const item of itemsToAdd) { for (const item of itemsToAdd) {
if (!item.libraryItemId) { const libraryItem = libraryItems.find(li => li.id === item.libraryItemId)
return res.status(400).send('Item does not have libraryItemId')
}
const libraryItem = Database.getLibraryItem(item.libraryItemId)
if (!libraryItem) { if (!libraryItem) {
return res.status(400).send('Item not found with id ' + item.libraryItemId) return res.status(404).send('Item not found with id ' + item.libraryItemId)
} } else {
const mediaItemId = item.episodeId || libraryItem.mediaId
if (!playlist.containsItem(item)) { if (existingPlaylistMediaItems.some(pmi => pmi.mediaItemId === mediaItemId)) {
playlistMediaItems.push({ // Already exists in playlist
playlistId: playlist.id, continue
mediaItemId: item.episodeId || libraryItem.media.id, // podcastEpisodeId or bookId } else {
mediaItemType: item.episodeId ? 'podcastEpisode' : 'book', mediaItemsToAdd.push({
order: order++ playlistId: req.playlist.id,
}) mediaItemId,
playlist.addItem(item.libraryItemId, item.episodeId) mediaItemType: item.episodeId ? 'podcastEpisode' : 'book',
hasUpdated = true order: order++
})
}
} }
} }
const jsonExpanded = playlist.toJSONExpanded(Database.libraryItems) let jsonExpanded = null
if (hasUpdated) { if (mediaItemsToAdd.length) {
await Database.createBulkPlaylistMediaItems(playlistMediaItems) await Database.createBulkPlaylistMediaItems(mediaItemsToAdd)
SocketAuthority.clientEmitter(playlist.userId, 'playlist_updated', jsonExpanded) jsonExpanded = await req.playlist.getOldJsonExpanded()
SocketAuthority.clientEmitter(req.playlist.userId, 'playlist_updated', jsonExpanded)
} else {
jsonExpanded = await req.playlist.getOldJsonExpanded()
} }
res.json(jsonExpanded) res.json(jsonExpanded)
} }
// POST: api/playlists/:id/batch/remove /**
* POST: /api/playlists/:id/batch/remove
* Batch remove playlist items
* @param {*} req
* @param {*} res
*/
async removeBatch(req, res) { async removeBatch(req, res) {
const playlist = req.playlist if (!req.body.items?.length) {
if (!req.body.items || !req.body.items.length) { return res.status(400).send('Invalid request body')
return res.status(500).send('Invalid request body')
} }
const itemsToRemove = req.body.items const itemsToRemove = req.body.items
const libraryItemIds = itemsToRemove.map(i => i.libraryItemId).filter(i => i)
if (!libraryItemIds.length) {
return res.status(400).send('Invalid request body')
}
// Find all library items
const libraryItems = await Database.models.libraryItem.findAll({
where: {
id: libraryItemIds
}
})
// Get all existing playlist media items for playlist
const existingPlaylistMediaItems = await req.playlist.getPlaylistMediaItems({
order: [['order', 'ASC']]
})
let numMediaItems = existingPlaylistMediaItems.length
// Remove playlist media items
let hasUpdated = false let hasUpdated = false
for (const item of itemsToRemove) { for (const item of itemsToRemove) {
if (!item.libraryItemId) { const libraryItem = libraryItems.find(li => li.id === item.libraryItemId)
return res.status(400).send('Item does not have libraryItemId') if (!libraryItem) continue
} const mediaItemId = item.episodeId || libraryItem.mediaId
const existingMediaItem = existingPlaylistMediaItems.find(pmi => pmi.mediaItemId === mediaItemId)
if (playlist.containsItem(item)) { if (!existingMediaItem) continue
playlist.removeItem(item.libraryItemId, item.episodeId) await existingMediaItem.destroy()
hasUpdated = true hasUpdated = true
} numMediaItems--
} }
const jsonExpanded = playlist.toJSONExpanded(Database.libraryItems) const jsonExpanded = await req.playlist.getOldJsonExpanded()
if (hasUpdated) { if (hasUpdated) {
// Playlist is removed when there are no items // Playlist is removed when there are no items
if (!playlist.items.length) { if (!numMediaItems) {
Logger.info(`[PlaylistController] Playlist "${playlist.name}" has no more items - removing it`) Logger.info(`[PlaylistController] Playlist "${req.playlist.name}" has no more items - removing it`)
await Database.removePlaylist(playlist.id) await req.playlist.destroy()
SocketAuthority.clientEmitter(playlist.userId, 'playlist_removed', jsonExpanded) SocketAuthority.clientEmitter(playlist.userId, 'playlist_removed', jsonExpanded)
} else { } else {
await Database.updatePlaylist(playlist)
SocketAuthority.clientEmitter(playlist.userId, 'playlist_updated', jsonExpanded) SocketAuthority.clientEmitter(playlist.userId, 'playlist_updated', jsonExpanded)
} }
} }
res.json(jsonExpanded) res.json(jsonExpanded)
} }
// POST: api/playlists/collection/:collectionId /**
* POST: /api/playlists/collection/:collectionId
* Create a playlist from a collection
* @param {*} req
* @param {*} res
*/
async createFromCollection(req, res) { async createFromCollection(req, res) {
let collection = await Database.models.collection.getOldById(req.params.collectionId) const collection = await Database.models.collection.findByPk(req.params.collectionId)
if (!collection) { if (!collection) {
return res.status(404).send('Collection not found') return res.status(404).send('Collection not found')
} }
// Expand collection to get library items // Expand collection to get library items
collection = collection.toJSONExpanded(Database.libraryItems) const collectionExpanded = await collection.getOldJsonExpanded(req.user)
if (!collectionExpanded) {
// Filter out library items not accessible to user // This can happen if the user has no access to all items in collection
const libraryItems = collection.books.filter(item => req.user.checkCanAccessLibraryItem(item)) return res.status(404).send('Collection not found')
if (!libraryItems.length) {
return res.status(400).send('Collection has no books accessible to user')
} }
const newPlaylist = new Playlist() // Playlists cannot be empty
if (!collectionExpanded.books.length) {
return res.status(400).send('Collection has no books')
}
const newPlaylistData = { const oldPlaylist = new Playlist()
oldPlaylist.setData({
userId: req.user.id, userId: req.user.id,
libraryId: collection.libraryId, libraryId: collection.libraryId,
name: collection.name, name: collection.name,
description: collection.description || null, description: collection.description || null
items: libraryItems.map(li => ({ libraryItemId: li.id })) })
}
newPlaylist.setData(newPlaylistData)
const jsonExpanded = newPlaylist.toJSONExpanded(Database.libraryItems) // Create Playlist record
await Database.createPlaylist(newPlaylist) const newPlaylist = await Database.models.playlist.createFromOld(oldPlaylist)
// Create PlaylistMediaItem records
const mediaItemsToAdd = []
let order = 1
for (const libraryItem of collectionExpanded.books) {
mediaItemsToAdd.push({
playlistId: newPlaylist.id,
mediaItemId: libraryItem.media.id,
mediaItemType: 'book',
order: order++
})
}
await Database.createBulkPlaylistMediaItems(mediaItemsToAdd)
const jsonExpanded = await newPlaylist.getOldJsonExpanded()
SocketAuthority.clientEmitter(newPlaylist.userId, 'playlist_added', jsonExpanded) SocketAuthority.clientEmitter(newPlaylist.userId, 'playlist_added', jsonExpanded)
res.json(jsonExpanded) res.json(jsonExpanded)
} }
async middleware(req, res, next) { async middleware(req, res, next) {
if (req.params.id) { if (req.params.id) {
const playlist = await Database.models.playlist.getById(req.params.id) const playlist = await Database.models.playlist.findByPk(req.params.id)
if (!playlist) { if (!playlist) {
return res.status(404).send('Playlist not found') return res.status(404).send('Playlist not found')
} }

View File

@ -122,9 +122,13 @@ class UserController {
// Todo: check if user is logged in and cancel streams // Todo: check if user is logged in and cancel streams
// Remove user playlists // Remove user playlists
const userPlaylists = await Database.models.playlist.getPlaylistsForUserAndLibrary(user.id) const userPlaylists = await Database.models.playlist.findAll({
where: {
userId: user.id
}
})
for (const playlist of userPlaylists) { for (const playlist of userPlaylists) {
await Database.removePlaylist(playlist.id) await playlist.destroy()
} }
const userJson = user.toJSONForBrowser() const userJson = user.toJSONForBrowser()

View File

@ -84,7 +84,7 @@ module.exports = (sequelize) => {
id: libraryItemIds id: libraryItemIds
}) })
const playlistExpanded = oldCollection.toJSONExpanded(libraryItems) const playlistExpanded = oldPlaylist.toJSONExpanded(libraryItems)
if (include?.includes('rssfeed')) { if (include?.includes('rssfeed')) {
const feeds = await this.getFeeds() const feeds = await this.getFeeds()
@ -248,7 +248,7 @@ module.exports = (sequelize) => {
/** /**
* Get all playlists for mediaItemIds * Get all playlists for mediaItemIds
* @param {string[]} mediaItemIds * @param {string[]} mediaItemIds
* @returns {Promise<oldPlaylist[]>} * @returns {Promise<Playlist[]>}
*/ */
static async getPlaylistsForMediaItemIds(mediaItemIds) { static async getPlaylistsForMediaItemIds(mediaItemIds) {
if (!mediaItemIds?.length) return [] if (!mediaItemIds?.length) return []
@ -282,8 +282,13 @@ module.exports = (sequelize) => {
], ],
order: [['playlist', 'playlistMediaItems', 'order', 'ASC']] order: [['playlist', 'playlistMediaItems', 'order', 'ASC']]
}) })
return playlistMediaItemsExpanded.map(pmie => {
pmie.playlist.playlistMediaItems = pmie.playlist.playlistMediaItems.map(pmi => { const playlists = []
for (const playlistMediaItem of playlistMediaItemsExpanded) {
const playlist = playlistMediaItem.playlist
if (playlists.some(p => p.id === playlist.id)) continue
playlist.playlistMediaItems = playlist.playlistMediaItems.map(pmi => {
if (pmi.mediaItemType === 'book' && pmi.book !== undefined) { if (pmi.mediaItemType === 'book' && pmi.book !== undefined) {
pmi.mediaItem = pmi.book pmi.mediaItem = pmi.book
pmi.dataValues.mediaItem = pmi.dataValues.book pmi.dataValues.mediaItem = pmi.dataValues.book
@ -297,9 +302,9 @@ module.exports = (sequelize) => {
delete pmi.dataValues.podcastEpisode delete pmi.dataValues.podcastEpisode
return pmi return pmi
}) })
playlists.push(playlist)
return this.getOldPlaylist(pmie.playlist) }
}) return playlists
} }
} }
@ -320,7 +325,9 @@ module.exports = (sequelize) => {
library.hasMany(Playlist) library.hasMany(Playlist)
Playlist.belongsTo(library) Playlist.belongsTo(library)
user.hasMany(Playlist) user.hasMany(Playlist, {
onDelete: 'CASCADE'
})
Playlist.belongsTo(user) Playlist.belongsTo(user)
Playlist.addHook('afterFind', findResult => { Playlist.addHook('afterFind', findResult => {

View File

@ -403,17 +403,32 @@ class ApiRouter {
// remove item from playlists // remove item from playlists
const playlistsWithItem = await Database.models.playlist.getPlaylistsForMediaItemIds(mediaItemIds) const playlistsWithItem = await Database.models.playlist.getPlaylistsForMediaItemIds(mediaItemIds)
for (const playlist of playlistsWithItem) { for (const playlist of playlistsWithItem) {
playlist.removeItemsForLibraryItem(libraryItem.id) let numMediaItems = playlist.playlistMediaItems.length
let order = 1
// Remove items in playlist and re-order
for (const playlistMediaItem of playlist.playlistMediaItems) {
if (mediaItemIds.includes(playlistMediaItem.mediaItemId)) {
await playlistMediaItem.destroy()
numMediaItems--
} else {
if (playlistMediaItem.order !== order) {
playlistMediaItem.update({
order
})
}
order++
}
}
// If playlist is now empty then remove it // If playlist is now empty then remove it
if (!playlist.items.length) { const jsonExpanded = await playlist.getOldJsonExpanded()
if (!numMediaItems) {
Logger.info(`[ApiRouter] Playlist "${playlist.name}" has no more items - removing it`) Logger.info(`[ApiRouter] Playlist "${playlist.name}" has no more items - removing it`)
await Database.removePlaylist(playlist.id) await playlist.destroy()
SocketAuthority.clientEmitter(playlist.userId, 'playlist_removed', playlist.toJSONExpanded(Database.libraryItems)) SocketAuthority.clientEmitter(playlist.userId, 'playlist_removed', jsonExpanded)
} else { } else {
await Database.updatePlaylist(playlist) SocketAuthority.clientEmitter(playlist.userId, 'playlist_updated', jsonExpanded)
SocketAuthority.clientEmitter(playlist.userId, 'playlist_updated', playlist.toJSONExpanded(Database.libraryItems))
} }
} }