mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-05-31 04:05:40 -04:00
Update playlist API endpoints to load library items from DB
This commit is contained in:
parent
6056c14926
commit
fc44c801f2
@ -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
|
||||||
|
|
||||||
|
@ -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')
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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 => {
|
||||||
|
@ -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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user