mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			358 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			358 lines
		
	
	
		
			10 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)
 | 
						|
 | 
						|
    if (include?.includes('rssfeed')) {
 | 
						|
      const feeds = await this.getFeeds()
 | 
						|
      if (feeds?.length) {
 | 
						|
        playlistExpanded.rssFeed = this.sequelize.models.feed.getOldFeed(feeds[0])
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    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
 |