mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +01:00
351 lines
9.9 KiB
JavaScript
351 lines
9.9 KiB
JavaScript
const { DataTypes, Model, Op, literal } = require('sequelize')
|
|
const Logger = require('../Logger')
|
|
|
|
const oldPlaylist = require('../objects/Playlist')
|
|
|
|
class Playlist extends Model {
|
|
constructor(values, options) {
|
|
super(values, options)
|
|
|
|
/** @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
|
|
}
|
|
|
|
static getOldPlaylist(playlistExpanded) {
|
|
const items = playlistExpanded.playlistMediaItems
|
|
.map((pmi) => {
|
|
const mediaItem = pmi.mediaItem || pmi.dataValues?.mediaItem
|
|
const libraryItemId = mediaItem?.podcast?.libraryItem?.id || 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)
|
|
|
|
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()
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Get old playlist toJSONExpanded
|
|
* @param {string[]} [include]
|
|
* @returns {Promise<oldPlaylist>} 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
|
|
}
|
|
}
|
|
],
|
|
order: [['order', 'ASC']]
|
|
})) || []
|
|
|
|
const oldPlaylist = this.sequelize.models.playlist.getOldPlaylist(this)
|
|
const libraryItemIds = oldPlaylist.items.map((i) => i.libraryItemId)
|
|
|
|
let libraryItems = await this.sequelize.models.libraryItem.getAllOldLibraryItems({
|
|
id: libraryItemIds
|
|
})
|
|
|
|
const playlistExpanded = oldPlaylist.toJSONExpanded(libraryItems)
|
|
|
|
return playlistExpanded
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
static removeById(playlistId) {
|
|
return this.destroy({
|
|
where: {
|
|
id: playlistId
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
}
|
|
}
|
|
]
|
|
},
|
|
order: [['playlistMediaItems', 'order', 'ASC']]
|
|
})
|
|
if (!playlist) return null
|
|
return this.getOldPlaylist(playlist)
|
|
}
|
|
|
|
/**
|
|
* Get old playlists for user and optionally for library
|
|
*
|
|
* @param {string} userId
|
|
* @param {string} [libraryId]
|
|
* @returns {Promise<oldPlaylist[]>}
|
|
*/
|
|
static async getOldPlaylistsForUserAndLibrary(userId, libraryId = null) {
|
|
if (!userId && !libraryId) return []
|
|
const whereQuery = {}
|
|
if (userId) {
|
|
whereQuery.userId = userId
|
|
}
|
|
if (libraryId) {
|
|
whereQuery.libraryId = libraryId
|
|
}
|
|
const playlistsExpanded = await this.findAll({
|
|
where: whereQuery,
|
|
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
|
|
}
|
|
}
|
|
]
|
|
},
|
|
order: [
|
|
[literal('name COLLATE NOCASE'), 'ASC'],
|
|
['playlistMediaItems', 'order', 'ASC']
|
|
]
|
|
})
|
|
|
|
const oldPlaylists = []
|
|
for (const playlistExpanded of playlistsExpanded) {
|
|
const oldPlaylist = this.getOldPlaylist(playlistExpanded)
|
|
const libraryItems = []
|
|
for (const pmi of playlistExpanded.playlistMediaItems) {
|
|
let mediaItem = pmi.mediaItem || pmi.dataValues.mediaItem
|
|
|
|
if (!mediaItem) {
|
|
Logger.error(`[Playlist] Invalid playlist media item - No media item found`, JSON.stringify(mediaItem, null, 2))
|
|
continue
|
|
}
|
|
let libraryItem = mediaItem.libraryItem || mediaItem.podcast?.libraryItem
|
|
|
|
if (mediaItem.podcast) {
|
|
libraryItem.media = mediaItem.podcast
|
|
libraryItem.media.podcastEpisodes = [mediaItem]
|
|
delete mediaItem.podcast.libraryItem
|
|
} else {
|
|
libraryItem.media = mediaItem
|
|
delete mediaItem.libraryItem
|
|
}
|
|
|
|
const oldLibraryItem = this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
|
|
libraryItems.push(oldLibraryItem)
|
|
}
|
|
const oldPlaylistJson = oldPlaylist.toJSONExpanded(libraryItems)
|
|
oldPlaylists.push(oldPlaylistJson)
|
|
}
|
|
|
|
return oldPlaylists
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
],
|
|
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)
|
|
}
|
|
return playlists
|
|
}
|
|
|
|
/**
|
|
* 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'
|
|
}
|
|
)
|
|
|
|
const { library, user } = sequelize.models
|
|
library.hasMany(Playlist)
|
|
Playlist.belongsTo(library)
|
|
|
|
user.hasMany(Playlist, {
|
|
onDelete: 'CASCADE'
|
|
})
|
|
Playlist.belongsTo(user)
|
|
|
|
Playlist.addHook('afterFind', (findResult) => {
|
|
if (!findResult) return
|
|
|
|
if (!Array.isArray(findResult)) findResult = [findResult]
|
|
|
|
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
|
|
})
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
module.exports = Playlist
|