mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Updates to LibraryController to use new Library model
- Additional validation on API endpoints - Removed success toast when reorder libraries
This commit is contained in:
		
							parent
							
								
									e0de59a4b6
								
							
						
					
					
						commit
						5d13faef33
					
				@ -76,8 +76,7 @@ export default {
 | 
			
		||||
      var newOrder = libraryOrderData.map((lib) => lib.id).join(',')
 | 
			
		||||
      if (currOrder !== newOrder) {
 | 
			
		||||
        this.$axios.$post('/api/libraries/order', libraryOrderData).then((response) => {
 | 
			
		||||
          if (response.libraries && response.libraries.length) {
 | 
			
		||||
            this.$toast.success('Library order saved', { timeout: 1500 })
 | 
			
		||||
          if (response.libraries?.length) {
 | 
			
		||||
            this.$store.commit('libraries/set', response.libraries)
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
@ -122,9 +122,8 @@ class FolderWatcher extends EventEmitter {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * TODO: Update to new library model
 | 
			
		||||
   *
 | 
			
		||||
   * @param {import('./objects/Library')} library
 | 
			
		||||
   * @param {import('./models/Library')} library
 | 
			
		||||
   */
 | 
			
		||||
  updateLibrary(library) {
 | 
			
		||||
    if (this.disabled) return
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,11 @@ const authorFilters = require('../utils/queries/authorFilters')
 | 
			
		||||
 * @property {import('../models/User')} user
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef {Request & RequestUserObject} RequestWithUser
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef RequestEntityObject
 | 
			
		||||
 * @property {import('../models/Library')} library
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef {RequestWithUser & RequestEntityObject} LibraryControllerRequest
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
class LibraryController {
 | 
			
		||||
@ -147,21 +152,25 @@ class LibraryController {
 | 
			
		||||
 | 
			
		||||
    library.libraryFolders = await library.getLibraryFolders()
 | 
			
		||||
 | 
			
		||||
    // TODO: Migrate to new library model
 | 
			
		||||
    const oldLibrary = Database.libraryModel.getOldLibrary(library)
 | 
			
		||||
 | 
			
		||||
    // Only emit to users with access to library
 | 
			
		||||
    const userFilter = (user) => {
 | 
			
		||||
      return user.checkCanAccessLibrary?.(oldLibrary.id)
 | 
			
		||||
      return user.checkCanAccessLibrary?.(library.id)
 | 
			
		||||
    }
 | 
			
		||||
    SocketAuthority.emitter('library_added', oldLibrary.toJSON(), userFilter)
 | 
			
		||||
    SocketAuthority.emitter('library_added', library.toOldJSON(), userFilter)
 | 
			
		||||
 | 
			
		||||
    // Add library watcher
 | 
			
		||||
    this.watcher.addLibrary(library)
 | 
			
		||||
 | 
			
		||||
    res.json(oldLibrary)
 | 
			
		||||
    res.json(library.toOldJSON())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * GET: /api/libraries
 | 
			
		||||
   * Get all libraries
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async findAll(req, res) {
 | 
			
		||||
    const libraries = await Database.libraryModel.getAllOldLibraries()
 | 
			
		||||
 | 
			
		||||
@ -180,7 +189,7 @@ class LibraryController {
 | 
			
		||||
  /**
 | 
			
		||||
   * GET: /api/libraries/:id
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async findOne(req, res) {
 | 
			
		||||
@ -204,7 +213,8 @@ class LibraryController {
 | 
			
		||||
  /**
 | 
			
		||||
   * GET: /api/libraries/:id/episode-downloads
 | 
			
		||||
   * Get podcast episodes in download queue
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   *
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getEpisodeDownloadQueue(req, res) {
 | 
			
		||||
@ -215,12 +225,28 @@ class LibraryController {
 | 
			
		||||
  /**
 | 
			
		||||
   * PATCH: /api/libraries/:id
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @this {import('../routers/ApiRouter')}
 | 
			
		||||
   *
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async update(req, res) {
 | 
			
		||||
    /** @type {import('../objects/Library')} */
 | 
			
		||||
    const oldLibrary = Database.libraryModel.getOldLibrary(req.library)
 | 
			
		||||
    // Validation
 | 
			
		||||
    const updatePayload = {}
 | 
			
		||||
    const keysToCheck = ['name', 'provider', 'mediaType', 'icon']
 | 
			
		||||
    for (const key of keysToCheck) {
 | 
			
		||||
      if (!req.body[key]) continue
 | 
			
		||||
      if (typeof req.body[key] !== 'string') {
 | 
			
		||||
        return res.status(400).send(`Invalid request. ${key} must be a string`)
 | 
			
		||||
      }
 | 
			
		||||
      updatePayload[key] = req.body[key]
 | 
			
		||||
    }
 | 
			
		||||
    if (req.body.displayOrder !== undefined) {
 | 
			
		||||
      if (isNaN(req.body.displayOrder)) {
 | 
			
		||||
        return res.status(400).send('Invalid request. displayOrder must be a number')
 | 
			
		||||
      }
 | 
			
		||||
      updatePayload.displayOrder = req.body.displayOrder
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Validate that the custom provider exists if given any
 | 
			
		||||
    if (req.body.provider?.startsWith('custom-')) {
 | 
			
		||||
@ -230,21 +256,72 @@ class LibraryController {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Validate settings
 | 
			
		||||
    const updatedSettings = {
 | 
			
		||||
      ...(req.library.settings || Database.libraryModel.getDefaultLibrarySettingsForMediaType(req.library.mediaType))
 | 
			
		||||
    }
 | 
			
		||||
    let hasUpdates = false
 | 
			
		||||
    let hasUpdatedDisableWatcher = false
 | 
			
		||||
    let hasUpdatedScanCron = false
 | 
			
		||||
    if (req.body.settings) {
 | 
			
		||||
      for (const key in req.body.settings) {
 | 
			
		||||
        if (updatedSettings[key] === undefined) continue
 | 
			
		||||
 | 
			
		||||
        if (key === 'metadataPrecedence') {
 | 
			
		||||
          if (!Array.isArray(req.body.settings[key])) {
 | 
			
		||||
            return res.status(400).send('Invalid request. Settings "metadataPrecedence" must be an array')
 | 
			
		||||
          }
 | 
			
		||||
          if (JSON.stringify(req.body.settings[key]) !== JSON.stringify(updatedSettings[key])) {
 | 
			
		||||
            hasUpdates = true
 | 
			
		||||
            updatedSettings[key] = [...req.body.settings[key]]
 | 
			
		||||
            Logger.debug(`[LibraryController] Library "${req.library.name}" updating setting "${key}" to "${updatedSettings[key]}"`)
 | 
			
		||||
          }
 | 
			
		||||
        } else if (key === 'autoScanCronExpression' || key === 'podcastSearchRegion') {
 | 
			
		||||
          if (req.body.settings[key] !== null && typeof req.body.settings[key] !== 'string') {
 | 
			
		||||
            return res.status(400).send(`Invalid request. Settings "${key}" must be a string`)
 | 
			
		||||
          }
 | 
			
		||||
          if (req.body.settings[key] !== updatedSettings[key]) {
 | 
			
		||||
            if (key === 'autoScanCronExpression') hasUpdatedScanCron = true
 | 
			
		||||
 | 
			
		||||
            hasUpdates = true
 | 
			
		||||
            updatedSettings[key] = req.body.settings[key]
 | 
			
		||||
            Logger.debug(`[LibraryController] Library "${req.library.name}" updating setting "${key}" to "${updatedSettings[key]}"`)
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          if (typeof req.body.settings[key] !== typeof updatedSettings[key]) {
 | 
			
		||||
            return res.status(400).send(`Invalid request. Setting "${key}" must be of type ${typeof updatedSettings[key]}`)
 | 
			
		||||
          }
 | 
			
		||||
          if (req.body.settings[key] !== updatedSettings[key]) {
 | 
			
		||||
            if (key === 'disableWatcher') hasUpdatedDisableWatcher = true
 | 
			
		||||
 | 
			
		||||
            hasUpdates = true
 | 
			
		||||
            updatedSettings[key] = req.body.settings[key]
 | 
			
		||||
            Logger.debug(`[LibraryController] Library "${req.library.name}" updating setting "${key}" to "${updatedSettings[key]}"`)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (hasUpdates) {
 | 
			
		||||
        updatePayload.settings = updatedSettings
 | 
			
		||||
        req.library.changed('settings', true)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let hasFolderUpdates = false
 | 
			
		||||
    // Validate new folder paths exist or can be created & resolve rel paths
 | 
			
		||||
    //   returns 400 if a new folder fails to access
 | 
			
		||||
    if (req.body.folders) {
 | 
			
		||||
    if (Array.isArray(req.body.folders)) {
 | 
			
		||||
      const newFolderPaths = []
 | 
			
		||||
      req.body.folders = req.body.folders.map((f) => {
 | 
			
		||||
        if (!f.id) {
 | 
			
		||||
          f.fullPath = fileUtils.filePathToPOSIX(Path.resolve(f.fullPath))
 | 
			
		||||
          newFolderPaths.push(f.fullPath)
 | 
			
		||||
          const path = f.fullPath || f.path
 | 
			
		||||
          f.path = fileUtils.filePathToPOSIX(Path.resolve(path))
 | 
			
		||||
          newFolderPaths.push(f.path)
 | 
			
		||||
        }
 | 
			
		||||
        return f
 | 
			
		||||
      })
 | 
			
		||||
      for (const path of newFolderPaths) {
 | 
			
		||||
        const pathExists = await fs.pathExists(path)
 | 
			
		||||
        if (!pathExists) {
 | 
			
		||||
          // Ensure dir will recursively create directories which might be preferred over mkdir
 | 
			
		||||
          const success = await fs
 | 
			
		||||
            .ensureDir(path)
 | 
			
		||||
            .then(() => true)
 | 
			
		||||
@ -256,10 +333,17 @@ class LibraryController {
 | 
			
		||||
            return res.status(400).send(`Invalid folder directory "${path}"`)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        // Create folder
 | 
			
		||||
        const libraryFolder = await Database.libraryFolderModel.create({
 | 
			
		||||
          path,
 | 
			
		||||
          libraryId: req.library.id
 | 
			
		||||
        })
 | 
			
		||||
        Logger.info(`[LibraryController] Created folder "${libraryFolder.path}" for library "${req.library.name}"`)
 | 
			
		||||
        hasFolderUpdates = true
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Handle removing folders
 | 
			
		||||
      for (const folder of oldLibrary.folders) {
 | 
			
		||||
      for (const folder of req.library.libraryFolders) {
 | 
			
		||||
        if (!req.body.folders.some((f) => f.id === folder.id)) {
 | 
			
		||||
          // Remove library items in folder
 | 
			
		||||
          const libraryItemsInFolder = await Database.libraryItemModel.findAll({
 | 
			
		||||
@ -278,67 +362,82 @@ class LibraryController {
 | 
			
		||||
              }
 | 
			
		||||
            ]
 | 
			
		||||
          })
 | 
			
		||||
          Logger.info(`[LibraryController] Removed folder "${folder.fullPath}" from library "${oldLibrary.name}" with ${libraryItemsInFolder.length} library items`)
 | 
			
		||||
          Logger.info(`[LibraryController] Removed folder "${folder.path}" from library "${req.library.name}" with ${libraryItemsInFolder.length} library items`)
 | 
			
		||||
          for (const libraryItem of libraryItemsInFolder) {
 | 
			
		||||
            let mediaItemIds = []
 | 
			
		||||
            if (oldLibrary.isPodcast) {
 | 
			
		||||
            if (req.library.isPodcast) {
 | 
			
		||||
              mediaItemIds = libraryItem.media.podcastEpisodes.map((pe) => pe.id)
 | 
			
		||||
            } else {
 | 
			
		||||
              mediaItemIds.push(libraryItem.mediaId)
 | 
			
		||||
            }
 | 
			
		||||
            Logger.info(`[LibraryController] Removing library item "${libraryItem.id}" from folder "${folder.fullPath}"`)
 | 
			
		||||
            Logger.info(`[LibraryController] Removing library item "${libraryItem.id}" from folder "${folder.path}"`)
 | 
			
		||||
            await this.handleDeleteLibraryItem(libraryItem.mediaType, libraryItem.id, mediaItemIds)
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // Remove folder
 | 
			
		||||
          await folder.destroy()
 | 
			
		||||
          hasFolderUpdates = true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const hasUpdates = oldLibrary.update(req.body)
 | 
			
		||||
    // TODO: Should check if this is an update to folder paths or name only
 | 
			
		||||
    if (hasUpdates) {
 | 
			
		||||
    if (Object.keys(updatePayload).length) {
 | 
			
		||||
      req.library.set(updatePayload)
 | 
			
		||||
      if (req.library.changed()) {
 | 
			
		||||
        Logger.debug(`[LibraryController] Updated library "${req.library.name}" with changed keys ${req.library.changed()}`)
 | 
			
		||||
        hasUpdates = true
 | 
			
		||||
        await req.library.save()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (hasUpdatedScanCron) {
 | 
			
		||||
      Logger.debug(`[LibraryController] Updated library "${req.library.name}" auto scan cron`)
 | 
			
		||||
      // Update auto scan cron
 | 
			
		||||
      this.cronManager.updateLibraryScanCron(oldLibrary)
 | 
			
		||||
      this.cronManager.updateLibraryScanCron(req.library)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
      const updatedLibrary = await Database.libraryModel.updateFromOld(oldLibrary)
 | 
			
		||||
      updatedLibrary.libraryFolders = await updatedLibrary.getLibraryFolders()
 | 
			
		||||
    if (hasFolderUpdates || hasUpdatedDisableWatcher) {
 | 
			
		||||
      req.library.libraryFolders = await req.library.getLibraryFolders()
 | 
			
		||||
 | 
			
		||||
      // Update watcher
 | 
			
		||||
      this.watcher.updateLibrary(updatedLibrary)
 | 
			
		||||
      this.watcher.updateLibrary(req.library)
 | 
			
		||||
 | 
			
		||||
      hasUpdates = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (hasUpdates) {
 | 
			
		||||
      // Only emit to users with access to library
 | 
			
		||||
      const userFilter = (user) => {
 | 
			
		||||
        return user.checkCanAccessLibrary?.(oldLibrary.id)
 | 
			
		||||
        return user.checkCanAccessLibrary?.(req.library.id)
 | 
			
		||||
      }
 | 
			
		||||
      SocketAuthority.emitter('library_updated', oldLibrary.toJSON(), userFilter)
 | 
			
		||||
      SocketAuthority.emitter('library_updated', req.library.toOldJSON(), userFilter)
 | 
			
		||||
 | 
			
		||||
      await Database.resetLibraryIssuesFilterData(oldLibrary.id)
 | 
			
		||||
      await Database.resetLibraryIssuesFilterData(req.library.id)
 | 
			
		||||
    }
 | 
			
		||||
    return res.json(oldLibrary.toJSON())
 | 
			
		||||
    return res.json(req.library.toOldJSON())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * DELETE: /api/libraries/:id
 | 
			
		||||
   * Delete a library
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async delete(req, res) {
 | 
			
		||||
    const library = Database.libraryModel.getOldLibrary(req.library)
 | 
			
		||||
 | 
			
		||||
    // Remove library watcher
 | 
			
		||||
    this.watcher.removeLibrary(req.library)
 | 
			
		||||
 | 
			
		||||
    // Remove collections for library
 | 
			
		||||
    const numCollectionsRemoved = await Database.collectionModel.removeAllForLibrary(library.id)
 | 
			
		||||
    const numCollectionsRemoved = await Database.collectionModel.removeAllForLibrary(req.library.id)
 | 
			
		||||
    if (numCollectionsRemoved) {
 | 
			
		||||
      Logger.info(`[Server] Removed ${numCollectionsRemoved} collections for library "${library.name}"`)
 | 
			
		||||
      Logger.info(`[Server] Removed ${numCollectionsRemoved} collections for library "${req.library.name}"`)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Remove items in this library
 | 
			
		||||
    const libraryItemsInLibrary = await Database.libraryItemModel.findAll({
 | 
			
		||||
      where: {
 | 
			
		||||
        libraryId: library.id
 | 
			
		||||
        libraryId: req.library.id
 | 
			
		||||
      },
 | 
			
		||||
      attributes: ['id', 'mediaId', 'mediaType'],
 | 
			
		||||
      include: [
 | 
			
		||||
@ -352,20 +451,20 @@ class LibraryController {
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    })
 | 
			
		||||
    Logger.info(`[LibraryController] Removing ${libraryItemsInLibrary.length} library items in library "${library.name}"`)
 | 
			
		||||
    Logger.info(`[LibraryController] Removing ${libraryItemsInLibrary.length} library items in library "${req.library.name}"`)
 | 
			
		||||
    for (const libraryItem of libraryItemsInLibrary) {
 | 
			
		||||
      let mediaItemIds = []
 | 
			
		||||
      if (library.isPodcast) {
 | 
			
		||||
      if (req.library.isPodcast) {
 | 
			
		||||
        mediaItemIds = libraryItem.media.podcastEpisodes.map((pe) => pe.id)
 | 
			
		||||
      } else {
 | 
			
		||||
        mediaItemIds.push(libraryItem.mediaId)
 | 
			
		||||
      }
 | 
			
		||||
      Logger.info(`[LibraryController] Removing library item "${libraryItem.id}" from library "${library.name}"`)
 | 
			
		||||
      Logger.info(`[LibraryController] Removing library item "${libraryItem.id}" from library "${req.library.name}"`)
 | 
			
		||||
      await this.handleDeleteLibraryItem(libraryItem.mediaType, libraryItem.id, mediaItemIds)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const libraryJson = library.toJSON()
 | 
			
		||||
    await Database.removeLibrary(library.id)
 | 
			
		||||
    const libraryJson = req.library.toOldJSON()
 | 
			
		||||
    await Database.removeLibrary(req.library.id)
 | 
			
		||||
 | 
			
		||||
    // Re-order libraries
 | 
			
		||||
    await Database.libraryModel.resetDisplayOrder()
 | 
			
		||||
@ -373,8 +472,8 @@ class LibraryController {
 | 
			
		||||
    SocketAuthority.emitter('library_removed', libraryJson)
 | 
			
		||||
 | 
			
		||||
    // Remove library filter data
 | 
			
		||||
    if (Database.libraryFilterData[library.id]) {
 | 
			
		||||
      delete Database.libraryFilterData[library.id]
 | 
			
		||||
    if (Database.libraryFilterData[req.library.id]) {
 | 
			
		||||
      delete Database.libraryFilterData[req.library.id]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return res.json(libraryJson)
 | 
			
		||||
@ -383,12 +482,10 @@ class LibraryController {
 | 
			
		||||
  /**
 | 
			
		||||
   * GET /api/libraries/:id/items
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getLibraryItems(req, res) {
 | 
			
		||||
    const oldLibrary = Database.libraryModel.getOldLibrary(req.library)
 | 
			
		||||
 | 
			
		||||
    const include = (req.query.include || '')
 | 
			
		||||
      .split(',')
 | 
			
		||||
      .map((v) => v.trim().toLowerCase())
 | 
			
		||||
@ -410,6 +507,8 @@ class LibraryController {
 | 
			
		||||
    payload.offset = payload.page * payload.limit
 | 
			
		||||
 | 
			
		||||
    // TODO: Temporary way of handling collapse sub-series. Either remove feature or handle through sql queries
 | 
			
		||||
    // TODO: Update to new library model
 | 
			
		||||
    const oldLibrary = Database.libraryModel.getOldLibrary(req.library)
 | 
			
		||||
    const filterByGroup = payload.filterBy?.split('.').shift()
 | 
			
		||||
    const filterByValue = filterByGroup ? libraryFilters.decode(payload.filterBy.replace(`${filterByGroup}.`, '')) : null
 | 
			
		||||
    if (filterByGroup === 'series' && filterByValue !== 'no-series' && payload.collapseseries) {
 | 
			
		||||
@ -425,9 +524,10 @@ class LibraryController {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * DELETE: /libraries/:id/issues
 | 
			
		||||
   * DELETE: /api/libraries/:id/issues
 | 
			
		||||
   * Remove all library items missing or invalid
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   *
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async removeLibraryItemsWithIssues(req, res) {
 | 
			
		||||
@ -464,7 +564,7 @@ class LibraryController {
 | 
			
		||||
    Logger.info(`[LibraryController] Removing ${libraryItemsWithIssues.length} items with issues`)
 | 
			
		||||
    for (const libraryItem of libraryItemsWithIssues) {
 | 
			
		||||
      let mediaItemIds = []
 | 
			
		||||
      if (req.library.mediaType === 'podcast') {
 | 
			
		||||
      if (req.library.isPodcast) {
 | 
			
		||||
        mediaItemIds = libraryItem.media.podcastEpisodes.map((pe) => pe.id)
 | 
			
		||||
      } else {
 | 
			
		||||
        mediaItemIds.push(libraryItem.mediaId)
 | 
			
		||||
@ -485,10 +585,11 @@ class LibraryController {
 | 
			
		||||
   * GET: /api/libraries/:id/series
 | 
			
		||||
   * Optional query string: `?include=rssfeed` that adds `rssFeed` to series if a feed is open
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getAllSeriesForLibrary(req, res) {
 | 
			
		||||
    // TODO: Update to new library model
 | 
			
		||||
    const oldLibrary = Database.libraryModel.getOldLibrary(req.library)
 | 
			
		||||
 | 
			
		||||
    const include = (req.query.include || '')
 | 
			
		||||
@ -523,7 +624,7 @@ class LibraryController {
 | 
			
		||||
   * rssfeed: adds `rssFeed` to series object if a feed is open
 | 
			
		||||
   * progress: adds `progress` to series object with { libraryItemIds:Array<llid>, libraryItemIdsFinished:Array<llid>, isFinished:boolean }
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res - Series
 | 
			
		||||
   */
 | 
			
		||||
  async getSeriesForLibrary(req, res) {
 | 
			
		||||
@ -560,7 +661,7 @@ class LibraryController {
 | 
			
		||||
   * GET: /api/libraries/:id/collections
 | 
			
		||||
   * Get all collections for library
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getCollectionsForLibrary(req, res) {
 | 
			
		||||
@ -599,7 +700,7 @@ class LibraryController {
 | 
			
		||||
   * GET: /api/libraries/:id/playlists
 | 
			
		||||
   * Get playlists for user in library
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getUserPlaylistsForLibrary(req, res) {
 | 
			
		||||
@ -624,7 +725,7 @@ class LibraryController {
 | 
			
		||||
  /**
 | 
			
		||||
   * GET: /api/libraries/:id/filterdata
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getLibraryFilterData(req, res) {
 | 
			
		||||
@ -636,10 +737,11 @@ class LibraryController {
 | 
			
		||||
   * GET: /api/libraries/:id/personalized
 | 
			
		||||
   * Home page shelves
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getUserPersonalizedShelves(req, res) {
 | 
			
		||||
    // TODO: Update to new library model
 | 
			
		||||
    const oldLibrary = Database.libraryModel.getOldLibrary(req.library)
 | 
			
		||||
    const limitPerShelf = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) || 10 : 10
 | 
			
		||||
    const include = (req.query.include || '')
 | 
			
		||||
@ -654,7 +756,13 @@ class LibraryController {
 | 
			
		||||
   * POST: /api/libraries/order
 | 
			
		||||
   * Change the display order of libraries
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @typedef LibraryReorderObj
 | 
			
		||||
   * @property {string} id
 | 
			
		||||
   * @property {number} newOrder
 | 
			
		||||
   *
 | 
			
		||||
   * @typedef {Request<{}, {}, LibraryReorderObj[], {}> & RequestUserObject} LibraryReorderRequest
 | 
			
		||||
   *
 | 
			
		||||
   * @param {LibraryReorderRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async reorder(req, res) {
 | 
			
		||||
@ -662,20 +770,25 @@ class LibraryController {
 | 
			
		||||
      Logger.error(`[LibraryController] Non-admin user "${req.user}" attempted to reorder libraries`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    const libraries = await Database.libraryModel.getAllOldLibraries()
 | 
			
		||||
 | 
			
		||||
    const libraries = await Database.libraryModel.getAllWithFolders()
 | 
			
		||||
 | 
			
		||||
    const orderdata = req.body
 | 
			
		||||
    if (!Array.isArray(orderdata) || orderdata.some((o) => typeof o?.id !== 'string' || typeof o?.newOrder !== 'number')) {
 | 
			
		||||
      return res.status(400).send('Invalid request. Request body must be an array of objects')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let hasUpdates = false
 | 
			
		||||
    for (let i = 0; i < orderdata.length; i++) {
 | 
			
		||||
      const library = libraries.find((lib) => lib.id === orderdata[i].id)
 | 
			
		||||
      if (!library) {
 | 
			
		||||
        Logger.error(`[LibraryController] Invalid library not found in reorder ${orderdata[i].id}`)
 | 
			
		||||
        return res.sendStatus(500)
 | 
			
		||||
        return res.status(400).send(`Library not found with id ${orderdata[i].id}`)
 | 
			
		||||
      }
 | 
			
		||||
      if (library.update({ displayOrder: orderdata[i].newOrder })) {
 | 
			
		||||
      if (library.displayOrder === orderdata[i].newOrder) continue
 | 
			
		||||
      library.displayOrder = orderdata[i].newOrder
 | 
			
		||||
      await library.save()
 | 
			
		||||
      hasUpdates = true
 | 
			
		||||
        await Database.libraryModel.updateFromOld(library)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (hasUpdates) {
 | 
			
		||||
@ -686,7 +799,7 @@ class LibraryController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    res.json({
 | 
			
		||||
      libraries: libraries.map((lib) => lib.toJSON())
 | 
			
		||||
      libraries: libraries.map((lib) => lib.toOldJSON())
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -695,18 +808,18 @@ class LibraryController {
 | 
			
		||||
   * Search library items with query
 | 
			
		||||
   *
 | 
			
		||||
   * ?q=search
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async search(req, res) {
 | 
			
		||||
    if (!req.query.q || typeof req.query.q !== 'string') {
 | 
			
		||||
      return res.status(400).send('Invalid request. Query param "q" must be a string')
 | 
			
		||||
    }
 | 
			
		||||
    const oldLibrary = Database.libraryModel.getOldLibrary(req.library)
 | 
			
		||||
 | 
			
		||||
    const limit = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 12
 | 
			
		||||
    const query = asciiOnlyToLowerCase(req.query.q.trim())
 | 
			
		||||
 | 
			
		||||
    const matches = await libraryItemFilters.search(req.user, oldLibrary, query, limit)
 | 
			
		||||
    const matches = await libraryItemFilters.search(req.user, req.library, query, limit)
 | 
			
		||||
    res.json(matches)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -714,7 +827,7 @@ class LibraryController {
 | 
			
		||||
   * GET: /api/libraries/:id/stats
 | 
			
		||||
   * Get stats for library
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async stats(req, res) {
 | 
			
		||||
@ -757,7 +870,7 @@ class LibraryController {
 | 
			
		||||
   * GET: /api/libraries/:id/authors
 | 
			
		||||
   * Get authors for library
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getAuthors(req, res) {
 | 
			
		||||
@ -796,7 +909,7 @@ class LibraryController {
 | 
			
		||||
  /**
 | 
			
		||||
   * GET: /api/libraries/:id/narrators
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getNarrators(req, res) {
 | 
			
		||||
@ -843,7 +956,7 @@ class LibraryController {
 | 
			
		||||
   * :narratorId is base64 encoded name
 | 
			
		||||
   * req.body { name }
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async updateNarrator(req, res) {
 | 
			
		||||
@ -894,7 +1007,7 @@ class LibraryController {
 | 
			
		||||
   * Remove narrator
 | 
			
		||||
   * :narratorId is base64 encoded name
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async removeNarrator(req, res) {
 | 
			
		||||
@ -937,7 +1050,7 @@ class LibraryController {
 | 
			
		||||
   * GET: /api/libraries/:id/matchall
 | 
			
		||||
   * Quick match all library items. Book libraries only.
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async matchAll(req, res) {
 | 
			
		||||
@ -945,6 +1058,7 @@ class LibraryController {
 | 
			
		||||
      Logger.error(`[LibraryController] Non-root user "${req.user.username}" attempted to match library items`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    // TODO: Update to new library model
 | 
			
		||||
    const oldLibrary = Database.libraryModel.getOldLibrary(req.library)
 | 
			
		||||
    Scanner.matchLibraryItems(oldLibrary)
 | 
			
		||||
    res.sendStatus(200)
 | 
			
		||||
@ -955,7 +1069,7 @@ class LibraryController {
 | 
			
		||||
   * Optional query:
 | 
			
		||||
   * ?force=1
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async scan(req, res) {
 | 
			
		||||
@ -964,6 +1078,7 @@ class LibraryController {
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    res.sendStatus(200)
 | 
			
		||||
    // TODO: Update to new library model
 | 
			
		||||
    const oldLibrary = Database.libraryModel.getOldLibrary(req.library)
 | 
			
		||||
    const forceRescan = req.query.force === '1'
 | 
			
		||||
    await LibraryScanner.scan(oldLibrary, forceRescan)
 | 
			
		||||
@ -976,13 +1091,14 @@ class LibraryController {
 | 
			
		||||
   * GET: /api/libraries/:id/recent-episodes
 | 
			
		||||
   * Used for latest page
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getRecentEpisodes(req, res) {
 | 
			
		||||
    if (req.library.mediaType !== 'podcast') {
 | 
			
		||||
      return res.sendStatus(404)
 | 
			
		||||
    }
 | 
			
		||||
    // TODO: Update to new library model
 | 
			
		||||
    const oldLibrary = Database.libraryModel.getOldLibrary(req.library)
 | 
			
		||||
    const payload = {
 | 
			
		||||
      episodes: [],
 | 
			
		||||
@ -999,7 +1115,7 @@ class LibraryController {
 | 
			
		||||
   * GET: /api/libraries/:id/opml
 | 
			
		||||
   * Get OPML file for a podcast library
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getOPMLFile(req, res) {
 | 
			
		||||
@ -1023,9 +1139,10 @@ class LibraryController {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * POST: /api/libraries/:id/remove-metadata
 | 
			
		||||
   * Remove all metadata.json or metadata.abs files in library item folders
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {LibraryControllerRequest} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async removeAllMetadataFiles(req, res) {
 | 
			
		||||
@ -1084,7 +1201,6 @@ class LibraryController {
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // const library = await Database.libraryModel.getOldById(req.params.id)
 | 
			
		||||
    const library = await Database.libraryModel.findByIdWithFolders(req.params.id)
 | 
			
		||||
    if (!library) {
 | 
			
		||||
      return res.status(404).send('Library not found')
 | 
			
		||||
 | 
			
		||||
@ -81,9 +81,8 @@ class CronManager {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * TODO: Update to new library model
 | 
			
		||||
   *
 | 
			
		||||
   * @param {*} library
 | 
			
		||||
   * @param {import('../models/Library')} library
 | 
			
		||||
   */
 | 
			
		||||
  removeCronForLibrary(library) {
 | 
			
		||||
    Logger.debug(`[CronManager] Removing library scan cron for ${library.name}`)
 | 
			
		||||
@ -91,9 +90,8 @@ class CronManager {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * TODO: Update to new library model
 | 
			
		||||
   *
 | 
			
		||||
   * @param {*} library
 | 
			
		||||
   * @param {import('../models/Library')} library
 | 
			
		||||
   */
 | 
			
		||||
  updateLibraryScanCron(library) {
 | 
			
		||||
    const expression = library.settings.autoScanCronExpression
 | 
			
		||||
 | 
			
		||||
@ -301,6 +301,35 @@ class Library extends Model {
 | 
			
		||||
      }
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get isPodcast() {
 | 
			
		||||
    return this.mediaType === 'podcast'
 | 
			
		||||
  }
 | 
			
		||||
  get isBook() {
 | 
			
		||||
    return this.mediaType === 'book'
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * TODO: Update to use new model
 | 
			
		||||
   */
 | 
			
		||||
  toOldJSON() {
 | 
			
		||||
    return {
 | 
			
		||||
      id: this.id,
 | 
			
		||||
      name: this.name,
 | 
			
		||||
      folders: (this.libraryFolders || []).map((f) => f.toOldJSON()),
 | 
			
		||||
      displayOrder: this.displayOrder,
 | 
			
		||||
      icon: this.icon,
 | 
			
		||||
      mediaType: this.mediaType,
 | 
			
		||||
      provider: this.provider,
 | 
			
		||||
      settings: {
 | 
			
		||||
        ...this.settings
 | 
			
		||||
      },
 | 
			
		||||
      lastScan: this.lastScan?.valueOf() || null,
 | 
			
		||||
      lastScanVersion: this.lastScanVersion,
 | 
			
		||||
      createdAt: this.createdAt.valueOf(),
 | 
			
		||||
      lastUpdate: this.updatedAt.valueOf()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = Library
 | 
			
		||||
 | 
			
		||||
@ -42,6 +42,18 @@ class LibraryFolder extends Model {
 | 
			
		||||
    })
 | 
			
		||||
    LibraryFolder.belongsTo(library)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * TODO: Update to use new model
 | 
			
		||||
   */
 | 
			
		||||
  toOldJSON() {
 | 
			
		||||
    return {
 | 
			
		||||
      id: this.id,
 | 
			
		||||
      fullPath: this.path,
 | 
			
		||||
      libraryId: this.libraryId,
 | 
			
		||||
      addedAt: this.createdAt.valueOf()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = LibraryFolder
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,3 @@
 | 
			
		||||
const uuidv4 = require('uuid').v4
 | 
			
		||||
const Folder = require('./Folder')
 | 
			
		||||
const LibrarySettings = require('./settings/LibrarySettings')
 | 
			
		||||
const { filePathToPOSIX } = require('../utils/fileUtils')
 | 
			
		||||
@ -28,15 +27,9 @@ class Library {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get folderPaths() {
 | 
			
		||||
    return this.folders.map((f) => f.fullPath)
 | 
			
		||||
  }
 | 
			
		||||
  get isPodcast() {
 | 
			
		||||
    return this.mediaType === 'podcast'
 | 
			
		||||
  }
 | 
			
		||||
  get isMusic() {
 | 
			
		||||
    return this.mediaType === 'music'
 | 
			
		||||
  }
 | 
			
		||||
  get isBook() {
 | 
			
		||||
    return this.mediaType === 'book'
 | 
			
		||||
  }
 | 
			
		||||
@ -98,61 +91,5 @@ class Library {
 | 
			
		||||
      lastUpdate: this.lastUpdate
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  update(payload) {
 | 
			
		||||
    let hasUpdates = false
 | 
			
		||||
 | 
			
		||||
    const keysToCheck = ['name', 'provider', 'mediaType', 'icon']
 | 
			
		||||
    keysToCheck.forEach((key) => {
 | 
			
		||||
      if (payload[key] && payload[key] !== this[key]) {
 | 
			
		||||
        this[key] = payload[key]
 | 
			
		||||
        hasUpdates = true
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    if (payload.settings && this.settings.update(payload.settings)) {
 | 
			
		||||
      hasUpdates = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!isNaN(payload.displayOrder) && payload.displayOrder !== this.displayOrder) {
 | 
			
		||||
      this.displayOrder = Number(payload.displayOrder)
 | 
			
		||||
      hasUpdates = true
 | 
			
		||||
    }
 | 
			
		||||
    if (payload.folders) {
 | 
			
		||||
      const newFolders = payload.folders.filter((f) => !f.id)
 | 
			
		||||
      const removedFolders = this.folders.filter((f) => !payload.folders.some((_f) => _f.id === f.id))
 | 
			
		||||
 | 
			
		||||
      if (removedFolders.length) {
 | 
			
		||||
        const removedFolderIds = removedFolders.map((f) => f.id)
 | 
			
		||||
        this.folders = this.folders.filter((f) => !removedFolderIds.includes(f.id))
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (newFolders.length) {
 | 
			
		||||
        newFolders.forEach((folderData) => {
 | 
			
		||||
          folderData.libraryId = this.id
 | 
			
		||||
          const newFolder = new Folder()
 | 
			
		||||
          newFolder.setData(folderData)
 | 
			
		||||
          this.folders.push(newFolder)
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (newFolders.length || removedFolders.length) {
 | 
			
		||||
        hasUpdates = true
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (hasUpdates) {
 | 
			
		||||
      this.lastUpdate = Date.now()
 | 
			
		||||
    }
 | 
			
		||||
    return hasUpdates
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  checkFullPathInLibrary(fullPath) {
 | 
			
		||||
    fullPath = filePathToPOSIX(fullPath)
 | 
			
		||||
    return this.folders.find((folder) => fullPath.startsWith(filePathToPOSIX(folder.fullPath)))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getFolderById(id) {
 | 
			
		||||
    return this.folders.find((folder) => folder.id === id)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
module.exports = Library
 | 
			
		||||
 | 
			
		||||
@ -53,6 +53,7 @@ class ApiRouter {
 | 
			
		||||
    this.audioMetadataManager = Server.audioMetadataManager
 | 
			
		||||
    /** @type {import('../managers/RssFeedManager')} */
 | 
			
		||||
    this.rssFeedManager = Server.rssFeedManager
 | 
			
		||||
    /** @type {import('../managers/CronManager')} */
 | 
			
		||||
    this.cronManager = Server.cronManager
 | 
			
		||||
    /** @type {import('../managers/NotificationManager')} */
 | 
			
		||||
    this.notificationManager = Server.notificationManager
 | 
			
		||||
 | 
			
		||||
@ -1268,7 +1268,7 @@ async function handleOldLibraries(ctx) {
 | 
			
		||||
        return false
 | 
			
		||||
      }
 | 
			
		||||
      const folderPaths = ol.folders?.map((f) => f.fullPath) || []
 | 
			
		||||
      return folderPaths.join(',') === library.folderPaths.join(',')
 | 
			
		||||
      return folderPaths.join(',') === library.folders.map((f) => f.fullPath).join(',')
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    if (matchingOldLibrary) {
 | 
			
		||||
 | 
			
		||||
@ -173,16 +173,16 @@ module.exports = {
 | 
			
		||||
  /**
 | 
			
		||||
   * Search library items
 | 
			
		||||
   * @param {import('../../models/User')} user
 | 
			
		||||
   * @param {import('../../objects/Library')} oldLibrary
 | 
			
		||||
   * @param {import('../../models/Library')} library
 | 
			
		||||
   * @param {string} query
 | 
			
		||||
   * @param {number} limit
 | 
			
		||||
   * @returns {{book:object[], narrators:object[], authors:object[], tags:object[], series:object[], podcast:object[]}}
 | 
			
		||||
   */
 | 
			
		||||
  search(user, oldLibrary, query, limit) {
 | 
			
		||||
    if (oldLibrary.isBook) {
 | 
			
		||||
      return libraryItemsBookFilters.search(user, oldLibrary, query, limit, 0)
 | 
			
		||||
  search(user, library, query, limit) {
 | 
			
		||||
    if (library.isBook) {
 | 
			
		||||
      return libraryItemsBookFilters.search(user, library, query, limit, 0)
 | 
			
		||||
    } else {
 | 
			
		||||
      return libraryItemsPodcastFilters.search(user, oldLibrary, query, limit, 0)
 | 
			
		||||
      return libraryItemsPodcastFilters.search(user, library, query, limit, 0)
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -966,13 +966,13 @@ module.exports = {
 | 
			
		||||
  /**
 | 
			
		||||
   * Search books, authors, series
 | 
			
		||||
   * @param {import('../../models/User')} user
 | 
			
		||||
   * @param {import('../../objects/Library')} oldLibrary
 | 
			
		||||
   * @param {import('../../models/Library')} library
 | 
			
		||||
   * @param {string} query
 | 
			
		||||
   * @param {number} limit
 | 
			
		||||
   * @param {number} offset
 | 
			
		||||
   * @returns {{book:object[], narrators:object[], authors:object[], tags:object[], series:object[]}}
 | 
			
		||||
   */
 | 
			
		||||
  async search(user, oldLibrary, query, limit, offset) {
 | 
			
		||||
  async search(user, library, query, limit, offset) {
 | 
			
		||||
    const userPermissionBookWhere = this.getUserPermissionBookWhereQuery(user)
 | 
			
		||||
 | 
			
		||||
    const normalizedQuery = query
 | 
			
		||||
@ -1006,7 +1006,7 @@ module.exports = {
 | 
			
		||||
        {
 | 
			
		||||
          model: Database.libraryItemModel,
 | 
			
		||||
          where: {
 | 
			
		||||
            libraryId: oldLibrary.id
 | 
			
		||||
            libraryId: library.id
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
@ -1047,7 +1047,7 @@ module.exports = {
 | 
			
		||||
    const narratorMatches = []
 | 
			
		||||
    const [narratorResults] = await Database.sequelize.query(`SELECT value, count(*) AS numBooks FROM books b, libraryItems li, json_each(b.narrators) WHERE json_valid(b.narrators) AND ${matchJsonValue} AND b.id = li.mediaId AND li.libraryId = :libraryId GROUP BY value LIMIT :limit OFFSET :offset;`, {
 | 
			
		||||
      replacements: {
 | 
			
		||||
        libraryId: oldLibrary.id,
 | 
			
		||||
        libraryId: library.id,
 | 
			
		||||
        limit,
 | 
			
		||||
        offset
 | 
			
		||||
      },
 | 
			
		||||
@ -1064,7 +1064,7 @@ module.exports = {
 | 
			
		||||
    const tagMatches = []
 | 
			
		||||
    const [tagResults] = await Database.sequelize.query(`SELECT value, count(*) AS numItems FROM books b, libraryItems li, json_each(b.tags) WHERE json_valid(b.tags) AND ${matchJsonValue} AND b.id = li.mediaId AND li.libraryId = :libraryId GROUP BY value ORDER BY numItems DESC LIMIT :limit OFFSET :offset;`, {
 | 
			
		||||
      replacements: {
 | 
			
		||||
        libraryId: oldLibrary.id,
 | 
			
		||||
        libraryId: library.id,
 | 
			
		||||
        limit,
 | 
			
		||||
        offset
 | 
			
		||||
      },
 | 
			
		||||
@ -1081,7 +1081,7 @@ module.exports = {
 | 
			
		||||
    const genreMatches = []
 | 
			
		||||
    const [genreResults] = await Database.sequelize.query(`SELECT value, count(*) AS numItems FROM books b, libraryItems li, json_each(b.genres) WHERE json_valid(b.genres) AND ${matchJsonValue} AND b.id = li.mediaId AND li.libraryId = :libraryId GROUP BY value ORDER BY numItems DESC LIMIT :limit OFFSET :offset;`, {
 | 
			
		||||
      replacements: {
 | 
			
		||||
        libraryId: oldLibrary.id,
 | 
			
		||||
        libraryId: library.id,
 | 
			
		||||
        limit,
 | 
			
		||||
        offset
 | 
			
		||||
      },
 | 
			
		||||
@ -1101,7 +1101,7 @@ module.exports = {
 | 
			
		||||
        [Sequelize.Op.and]: [
 | 
			
		||||
          Sequelize.literal(matchName),
 | 
			
		||||
          {
 | 
			
		||||
            libraryId: oldLibrary.id
 | 
			
		||||
            libraryId: library.id
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
      },
 | 
			
		||||
@ -1136,7 +1136,7 @@ module.exports = {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Search authors
 | 
			
		||||
    const authorMatches = await authorFilters.search(oldLibrary.id, normalizedQuery, limit, offset)
 | 
			
		||||
    const authorMatches = await authorFilters.search(library.id, normalizedQuery, limit, offset)
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      book: itemMatches,
 | 
			
		||||
 | 
			
		||||
@ -306,13 +306,13 @@ module.exports = {
 | 
			
		||||
  /**
 | 
			
		||||
   * Search podcasts
 | 
			
		||||
   * @param {import('../../models/User')} user
 | 
			
		||||
   * @param {import('../../objects/Library')} oldLibrary
 | 
			
		||||
   * @param {import('../../models/Library')} library
 | 
			
		||||
   * @param {string} query
 | 
			
		||||
   * @param {number} limit
 | 
			
		||||
   * @param {number} offset
 | 
			
		||||
   * @returns {{podcast:object[], tags:object[]}}
 | 
			
		||||
   */
 | 
			
		||||
  async search(user, oldLibrary, query, limit, offset) {
 | 
			
		||||
  async search(user, library, query, limit, offset) {
 | 
			
		||||
    const userPermissionPodcastWhere = this.getUserPermissionPodcastWhereQuery(user)
 | 
			
		||||
 | 
			
		||||
    const normalizedQuery = query
 | 
			
		||||
@ -345,7 +345,7 @@ module.exports = {
 | 
			
		||||
        {
 | 
			
		||||
          model: Database.libraryItemModel,
 | 
			
		||||
          where: {
 | 
			
		||||
            libraryId: oldLibrary.id
 | 
			
		||||
            libraryId: library.id
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
@ -372,7 +372,7 @@ module.exports = {
 | 
			
		||||
    const tagMatches = []
 | 
			
		||||
    const [tagResults] = await Database.sequelize.query(`SELECT value, count(*) AS numItems FROM podcasts p, libraryItems li, json_each(p.tags) WHERE json_valid(p.tags) AND ${matchJsonValue} AND p.id = li.mediaId AND li.libraryId = :libraryId GROUP BY value ORDER BY numItems DESC LIMIT :limit OFFSET :offset;`, {
 | 
			
		||||
      replacements: {
 | 
			
		||||
        libraryId: oldLibrary.id,
 | 
			
		||||
        libraryId: library.id,
 | 
			
		||||
        limit,
 | 
			
		||||
        offset
 | 
			
		||||
      },
 | 
			
		||||
@ -389,7 +389,7 @@ module.exports = {
 | 
			
		||||
    const genreMatches = []
 | 
			
		||||
    const [genreResults] = await Database.sequelize.query(`SELECT value, count(*) AS numItems FROM podcasts p, libraryItems li, json_each(p.genres) WHERE json_valid(p.genres) AND ${matchJsonValue} AND p.id = li.mediaId AND li.libraryId = :libraryId GROUP BY value ORDER BY numItems DESC LIMIT :limit OFFSET :offset;`, {
 | 
			
		||||
      replacements: {
 | 
			
		||||
        libraryId: oldLibrary.id,
 | 
			
		||||
        libraryId: library.id,
 | 
			
		||||
        limit,
 | 
			
		||||
        offset
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user