audiobookshelf/server/models/Playlist.js

343 lines
9.5 KiB
JavaScript
Raw Normal View History

const { DataTypes, Model, Op, literal } = require('sequelize')
const Logger = require('../Logger')
2023-07-05 01:14:44 +02:00
const oldPlaylist = require('../objects/Playlist')
2023-08-16 23:38:48 +02:00
class Playlist extends Model {
constructor(values, options) {
super(values, options)
2023-07-05 01:14:44 +02:00
2023-08-16 23:38:48 +02:00
/** @type {UUIDV4} */
this.id
/** @type {string} */
this.name
/** @type {string} */
this.description
/** @type {UUIDV4} */
this.libraryId
/** @type {UUIDV4} */
this.userId
/** @type {Date} */
this.createdAt
/** @type {Date} */
this.updatedAt
}
2023-07-05 01:14:44 +02:00
2023-08-16 23:38:48 +02:00
static async getOldPlaylists() {
const playlists = await this.findAll({
include: {
model: this.sequelize.models.playlistMediaItem,
include: [
{
2023-08-16 23:38:48 +02:00
model: this.sequelize.models.book,
include: this.sequelize.models.libraryItem
},
{
2023-08-16 23:38:48 +02:00
model: this.sequelize.models.podcastEpisode,
include: {
2023-08-16 23:38:48 +02:00
model: this.sequelize.models.podcast,
include: this.sequelize.models.libraryItem
}
}
2023-08-16 23:38:48 +02:00
]
},
order: [['playlistMediaItems', 'order', 'ASC']]
})
return playlists.map(p => this.getOldPlaylist(p))
}
2023-08-16 23:38:48 +02:00
static getOldPlaylist(playlistExpanded) {
const items = playlistExpanded.playlistMediaItems.map(pmi => {
const libraryItemId = pmi.mediaItem?.podcast?.libraryItem?.id || pmi.mediaItem?.libraryItem?.id || null
if (!libraryItemId) {
Logger.error(`[Playlist] Invalid playlist media item - No library item id found`, JSON.stringify(pmi, null, 2))
return null
}
return {
episodeId: pmi.mediaItemType === 'podcastEpisode' ? pmi.mediaItemId : '',
libraryItemId
}
}).filter(pmi => pmi)
2023-08-16 23:38:48 +02:00
return new oldPlaylist({
id: playlistExpanded.id,
libraryId: playlistExpanded.libraryId,
userId: playlistExpanded.userId,
name: playlistExpanded.name,
description: playlistExpanded.description,
items,
lastUpdate: playlistExpanded.updatedAt.valueOf(),
createdAt: playlistExpanded.createdAt.valueOf()
})
}
2023-08-16 23:38:48 +02:00
/**
* Get old playlist toJSONExpanded
* @param {[string[]]} include
* @returns {Promise<object>} oldPlaylist.toJSONExpanded
*/
async getOldJsonExpanded(include) {
this.playlistMediaItems = await this.getPlaylistMediaItems({
include: [
{
model: this.sequelize.models.book,
include: this.sequelize.models.libraryItem
},
{
model: this.sequelize.models.podcastEpisode,
include: {
model: this.sequelize.models.podcast,
include: this.sequelize.models.libraryItem
}
}
2023-08-16 23:38:48 +02:00
],
order: [['order', 'ASC']]
}) || []
2023-08-16 23:38:48 +02:00
const oldPlaylist = this.sequelize.models.playlist.getOldPlaylist(this)
const libraryItemIds = oldPlaylist.items.map(i => i.libraryItemId)
2023-08-16 23:38:48 +02:00
let libraryItems = await this.sequelize.models.libraryItem.getAllOldLibraryItems({
id: libraryItemIds
})
2023-07-05 01:14:44 +02:00
2023-08-16 23:38:48 +02:00
const playlistExpanded = oldPlaylist.toJSONExpanded(libraryItems)
if (include?.includes('rssfeed')) {
const feeds = await this.getFeeds()
if (feeds?.length) {
playlistExpanded.rssFeed = this.sequelize.models.feed.getOldFeed(feeds[0])
2023-07-05 01:14:44 +02:00
}
}
2023-08-16 23:38:48 +02:00
return playlistExpanded
}
2023-08-16 23:38:48 +02:00
static createFromOld(oldPlaylist) {
const playlist = this.getFromOld(oldPlaylist)
return this.create(playlist)
}
static getFromOld(oldPlaylist) {
return {
id: oldPlaylist.id,
name: oldPlaylist.name,
description: oldPlaylist.description,
userId: oldPlaylist.userId,
libraryId: oldPlaylist.libraryId
}
2023-08-16 23:38:48 +02:00
}
2023-08-16 23:38:48 +02:00
static removeById(playlistId) {
return this.destroy({
where: {
id: playlistId
}
2023-08-16 23:38:48 +02:00
})
}
/**
* Get playlist by id
* @param {string} playlistId
* @returns {Promise<oldPlaylist|null>} returns null if not found
*/
static async getById(playlistId) {
if (!playlistId) return null
const playlist = await this.findByPk(playlistId, {
include: {
model: this.sequelize.models.playlistMediaItem,
include: [
{
model: this.sequelize.models.book,
include: this.sequelize.models.libraryItem
},
{
model: this.sequelize.models.podcastEpisode,
include: {
model: this.sequelize.models.podcast,
include: this.sequelize.models.libraryItem
}
2023-08-16 23:38:48 +02:00
}
]
2023-08-16 23:38:48 +02:00
},
order: [['playlistMediaItems', 'order', 'ASC']]
})
if (!playlist) return null
return this.getOldPlaylist(playlist)
}
2023-08-16 23:38:48 +02:00
/**
* Get playlists for user and optionally for library
* @param {string} userId
* @param {[string]} libraryId optional
* @returns {Promise<Playlist[]>}
*/
static async getPlaylistsForUserAndLibrary(userId, libraryId = null) {
if (!userId && !libraryId) return []
const whereQuery = {}
if (userId) {
whereQuery.userId = userId
}
2023-08-16 23:38:48 +02:00
if (libraryId) {
whereQuery.libraryId = libraryId
}
const playlists = await this.findAll({
where: whereQuery,
include: {
model: this.sequelize.models.playlistMediaItem,
include: [
{
2023-08-16 23:38:48 +02:00
model: this.sequelize.models.book,
include: this.sequelize.models.libraryItem
},
{
model: this.sequelize.models.podcastEpisode,
include: {
2023-08-16 23:38:48 +02:00
model: this.sequelize.models.podcast,
include: this.sequelize.models.libraryItem
}
}
2023-08-16 23:38:48 +02:00
]
},
order: [
[literal('name COLLATE NOCASE'), 'ASC'],
['playlistMediaItems', 'order', 'ASC']
]
})
return playlists
}
2023-08-16 23:38:48 +02:00
/**
* Get number of playlists for a user and library
* @param {string} userId
* @param {string} libraryId
* @returns
*/
static async getNumPlaylistsForUserAndLibrary(userId, libraryId) {
return this.count({
where: {
userId,
libraryId
}
})
}
2023-08-16 23:38:48 +02:00
/**
* Get all playlists for mediaItemIds
* @param {string[]} mediaItemIds
* @returns {Promise<Playlist[]>}
*/
static async getPlaylistsForMediaItemIds(mediaItemIds) {
if (!mediaItemIds?.length) return []
const playlistMediaItemsExpanded = await this.sequelize.models.playlistMediaItem.findAll({
where: {
mediaItemId: {
[Op.in]: mediaItemIds
}
},
include: [
{
model: this.sequelize.models.playlist,
include: {
model: this.sequelize.models.playlistMediaItem,
include: [
{
model: this.sequelize.models.book,
include: this.sequelize.models.libraryItem
},
{
model: this.sequelize.models.podcastEpisode,
include: {
model: this.sequelize.models.podcast,
include: this.sequelize.models.libraryItem
}
}
]
}
2023-08-16 23:38:48 +02:00
}
],
order: [['playlist', 'playlistMediaItems', 'order', 'ASC']]
})
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) {
pmi.mediaItem = pmi.book
pmi.dataValues.mediaItem = pmi.dataValues.book
} else if (pmi.mediaItemType === 'podcastEpisode' && pmi.podcastEpisode !== undefined) {
pmi.mediaItem = pmi.podcastEpisode
pmi.dataValues.mediaItem = pmi.dataValues.podcastEpisode
}
delete pmi.book
delete pmi.dataValues.book
delete pmi.podcastEpisode
delete pmi.dataValues.podcastEpisode
return pmi
})
playlists.push(playlist)
}
2023-08-16 23:38:48 +02:00
return playlists
2023-07-05 01:14:44 +02:00
}
2023-08-16 23:38:48 +02:00
/**
* Initialize model
* @param {import('../Database').sequelize} sequelize
*/
static init(sequelize) {
super.init({
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
name: DataTypes.STRING,
description: DataTypes.TEXT
}, {
sequelize,
modelName: 'playlist'
})
2023-07-05 01:14:44 +02:00
2023-08-16 23:38:48 +02:00
const { library, user } = sequelize.models
library.hasMany(Playlist)
Playlist.belongsTo(library)
2023-07-05 01:14:44 +02:00
2023-08-16 23:38:48 +02:00
user.hasMany(Playlist, {
onDelete: 'CASCADE'
})
Playlist.belongsTo(user)
2023-07-05 01:14:44 +02:00
2023-08-16 23:38:48 +02:00
Playlist.addHook('afterFind', findResult => {
if (!findResult) return
2023-07-05 01:14:44 +02:00
2023-08-16 23:38:48 +02:00
if (!Array.isArray(findResult)) findResult = [findResult]
2023-07-05 01:14:44 +02:00
2023-08-16 23:38:48 +02:00
for (const instance of findResult) {
if (instance.playlistMediaItems?.length) {
instance.playlistMediaItems = instance.playlistMediaItems.map(pmi => {
if (pmi.mediaItemType === 'book' && pmi.book !== undefined) {
pmi.mediaItem = pmi.book
pmi.dataValues.mediaItem = pmi.dataValues.book
} else if (pmi.mediaItemType === 'podcastEpisode' && pmi.podcastEpisode !== undefined) {
pmi.mediaItem = pmi.podcastEpisode
pmi.dataValues.mediaItem = pmi.dataValues.podcastEpisode
}
// To prevent mistakes:
delete pmi.book
delete pmi.dataValues.book
delete pmi.podcastEpisode
delete pmi.dataValues.podcastEpisode
return pmi
})
}
2023-07-05 01:14:44 +02:00
2023-08-16 23:38:48 +02:00
}
})
}
}
2023-07-05 01:14:44 +02:00
2023-08-16 23:38:48 +02:00
module.exports = Playlist