mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Update:Express middleware sets req.user to new data model, openid permissions functions moved to new data model
This commit is contained in:
		
							parent
							
								
									29a15858f4
								
							
						
					
					
						commit
						2472b86284
					
				@ -152,6 +152,8 @@ class Auth {
 | 
			
		||||
  /**
 | 
			
		||||
   * Finds an existing user by OpenID subject identifier, or by email/username based on server settings,
 | 
			
		||||
   * or creates a new user if configured to do so.
 | 
			
		||||
   *
 | 
			
		||||
   * @returns {import('./models/User')|null}
 | 
			
		||||
   */
 | 
			
		||||
  async findOrCreateUser(userinfo) {
 | 
			
		||||
    let user = await Database.userModel.getUserByOpenIDSub(userinfo.sub)
 | 
			
		||||
@ -307,9 +309,8 @@ class Auth {
 | 
			
		||||
    const absPermissions = userinfo[absPermissionsClaim]
 | 
			
		||||
    if (!absPermissions) throw new Error(`Advanced permissions claim ${absPermissionsClaim} not found in userinfo`)
 | 
			
		||||
 | 
			
		||||
    if (user.updatePermissionsFromExternalJSON(absPermissions)) {
 | 
			
		||||
    if (await user.updatePermissionsFromExternalJSON(absPermissions)) {
 | 
			
		||||
      Logger.info(`[Auth] openid callback: Updating advanced perms for user "${user.username}" using "${JSON.stringify(absPermissions)}"`)
 | 
			
		||||
      await Database.userModel.updateFromOld(user)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -921,7 +922,7 @@ class Auth {
 | 
			
		||||
  async userChangePassword(req, res) {
 | 
			
		||||
    let { password, newPassword } = req.body
 | 
			
		||||
    newPassword = newPassword || ''
 | 
			
		||||
    const matchingUser = req.userNew
 | 
			
		||||
    const matchingUser = req.user
 | 
			
		||||
 | 
			
		||||
    // Only root can have an empty password
 | 
			
		||||
    if (matchingUser.type !== 'root' && !newPassword) {
 | 
			
		||||
 | 
			
		||||
@ -91,8 +91,6 @@ class Server {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Middleware to check if the current request is authenticated
 | 
			
		||||
   * req.user is set if authenticated to the OLD user object
 | 
			
		||||
   * req.userNew is set if authenticated to the NEW user object
 | 
			
		||||
   *
 | 
			
		||||
   * @param {import('express').Request} req
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
@ -100,14 +98,7 @@ class Server {
 | 
			
		||||
   */
 | 
			
		||||
  authMiddleware(req, res, next) {
 | 
			
		||||
    // ask passportjs if the current request is authenticated
 | 
			
		||||
    this.auth.isAuthenticated(req, res, () => {
 | 
			
		||||
      if (req.user) {
 | 
			
		||||
        // TODO: req.userNew to become req.user
 | 
			
		||||
        req.userNew = req.user
 | 
			
		||||
        req.user = Database.userModel.getOldUser(req.user)
 | 
			
		||||
      }
 | 
			
		||||
      next()
 | 
			
		||||
    })
 | 
			
		||||
    this.auth.isAuthenticated(req, res, next)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  cancelLibraryScan(libraryId) {
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@ class AuthorController {
 | 
			
		||||
 | 
			
		||||
    // Used on author landing page to include library items and items grouped in series
 | 
			
		||||
    if (include.includes('items')) {
 | 
			
		||||
      authorJson.libraryItems = await Database.libraryItemModel.getForAuthor(req.author, req.userNew)
 | 
			
		||||
      authorJson.libraryItems = await Database.libraryItemModel.getForAuthor(req.author, req.user)
 | 
			
		||||
 | 
			
		||||
      if (include.includes('series')) {
 | 
			
		||||
        const seriesMap = {}
 | 
			
		||||
@ -222,8 +222,8 @@ class AuthorController {
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async uploadImage(req, res) {
 | 
			
		||||
    if (!req.userNew.canUpload) {
 | 
			
		||||
      Logger.warn(`User "${req.userNew.username}" attempted to upload an image without permission`)
 | 
			
		||||
    if (!req.user.canUpload) {
 | 
			
		||||
      Logger.warn(`User "${req.user.username}" attempted to upload an image without permission`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    if (!req.body.url) {
 | 
			
		||||
@ -362,11 +362,11 @@ class AuthorController {
 | 
			
		||||
    const author = await Database.authorModel.getOldById(req.params.id)
 | 
			
		||||
    if (!author) return res.sendStatus(404)
 | 
			
		||||
 | 
			
		||||
    if (req.method == 'DELETE' && !req.userNew.canDelete) {
 | 
			
		||||
      Logger.warn(`[AuthorController] User "${req.userNew.username}" attempted to delete without permission`)
 | 
			
		||||
    if (req.method == 'DELETE' && !req.user.canDelete) {
 | 
			
		||||
      Logger.warn(`[AuthorController] User "${req.user.username}" attempted to delete without permission`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    } else if ((req.method == 'PATCH' || req.method == 'POST') && !req.userNew.canUpdate) {
 | 
			
		||||
      Logger.warn(`[AuthorController] User "${req.userNew.username}" attempted to update without permission`)
 | 
			
		||||
    } else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) {
 | 
			
		||||
      Logger.warn(`[AuthorController] User "${req.user.username}" attempted to update without permission`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -113,8 +113,8 @@ class BackupController {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  middleware(req, res, next) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[BackupController] Non-admin user "${req.userNew.username}" attempting to access backups`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[BackupController] Non-admin user "${req.user.username}" attempting to access backups`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ class CacheController {
 | 
			
		||||
 | 
			
		||||
  // POST: api/cache/purge
 | 
			
		||||
  async purgeCache(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    await CacheManager.purgeAll()
 | 
			
		||||
@ -14,7 +14,7 @@ class CacheController {
 | 
			
		||||
 | 
			
		||||
  // POST: api/cache/items/purge
 | 
			
		||||
  async purgeItemsCache(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    await CacheManager.purgeItems()
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,7 @@ class CollectionController {
 | 
			
		||||
   */
 | 
			
		||||
  async create(req, res) {
 | 
			
		||||
    const newCollection = new Collection()
 | 
			
		||||
    req.body.userId = req.userNew.id
 | 
			
		||||
    req.body.userId = req.user.id
 | 
			
		||||
    if (!newCollection.setData(req.body)) {
 | 
			
		||||
      return res.status(400).send('Invalid collection data')
 | 
			
		||||
    }
 | 
			
		||||
@ -50,7 +50,7 @@ class CollectionController {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async findAll(req, res) {
 | 
			
		||||
    const collectionsExpanded = await Database.collectionModel.getOldCollectionsJsonExpanded(req.userNew)
 | 
			
		||||
    const collectionsExpanded = await Database.collectionModel.getOldCollectionsJsonExpanded(req.user)
 | 
			
		||||
    res.json({
 | 
			
		||||
      collections: collectionsExpanded
 | 
			
		||||
    })
 | 
			
		||||
@ -59,7 +59,7 @@ class CollectionController {
 | 
			
		||||
  async findOne(req, res) {
 | 
			
		||||
    const includeEntities = (req.query.include || '').split(',')
 | 
			
		||||
 | 
			
		||||
    const collectionExpanded = await req.collection.getOldJsonExpanded(req.userNew, includeEntities)
 | 
			
		||||
    const collectionExpanded = await req.collection.getOldJsonExpanded(req.user, includeEntities)
 | 
			
		||||
    if (!collectionExpanded) {
 | 
			
		||||
      // This may happen if the user is restricted from all books
 | 
			
		||||
      return res.sendStatus(404)
 | 
			
		||||
@ -334,11 +334,11 @@ class CollectionController {
 | 
			
		||||
      req.collection = collection
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (req.method == 'DELETE' && !req.userNew.canDelete) {
 | 
			
		||||
      Logger.warn(`[CollectionController] User "${req.userNew.username}" attempted to delete without permission`)
 | 
			
		||||
    if (req.method == 'DELETE' && !req.user.canDelete) {
 | 
			
		||||
      Logger.warn(`[CollectionController] User "${req.user.username}" attempted to delete without permission`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    } else if ((req.method == 'PATCH' || req.method == 'POST') && !req.userNew.canUpdate) {
 | 
			
		||||
      Logger.warn(`[CollectionController] User "${req.userNew.username}" attempted to update without permission`)
 | 
			
		||||
    } else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) {
 | 
			
		||||
      Logger.warn(`[CollectionController] User "${req.user.username}" attempted to update without permission`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -101,8 +101,8 @@ class CustomMetadataProviderController {
 | 
			
		||||
   * @param {import('express').NextFunction} next
 | 
			
		||||
   */
 | 
			
		||||
  async middleware(req, res, next) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.warn(`[CustomMetadataProviderController] Non-admin user "${req.userNew.username}" attempted access route "${req.path}"`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.warn(`[CustomMetadataProviderController] Non-admin user "${req.user.username}" attempted access route "${req.path}"`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -59,7 +59,7 @@ class EmailController {
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async sendEBookToDevice(req, res) {
 | 
			
		||||
    Logger.debug(`[EmailController] Send ebook to device requested by user "${req.userNew.username}" for libraryItemId=${req.body.libraryItemId}, deviceName=${req.body.deviceName}`)
 | 
			
		||||
    Logger.debug(`[EmailController] Send ebook to device requested by user "${req.user.username}" for libraryItemId=${req.body.libraryItemId}, deviceName=${req.body.deviceName}`)
 | 
			
		||||
 | 
			
		||||
    const device = Database.emailSettings.getEReaderDevice(req.body.deviceName)
 | 
			
		||||
    if (!device) {
 | 
			
		||||
@ -67,7 +67,7 @@ class EmailController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check user has access to device
 | 
			
		||||
    if (!Database.emailSettings.checkUserCanAccessDevice(device, req.userNew)) {
 | 
			
		||||
    if (!Database.emailSettings.checkUserCanAccessDevice(device, req.user)) {
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -77,7 +77,7 @@ class EmailController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check user has access to library item
 | 
			
		||||
    if (!req.userNew.checkCanAccessLibraryItem(libraryItem)) {
 | 
			
		||||
    if (!req.user.checkCanAccessLibraryItem(libraryItem)) {
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -90,7 +90,7 @@ class EmailController {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  adminMiddleware(req, res, next) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      return res.sendStatus(404)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -13,8 +13,8 @@ class FileSystemController {
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getPaths(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[FileSystemController] Non-admin user "${req.userNew.username}" attempting to get filesystem paths`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[FileSystemController] Non-admin user "${req.user.username}" attempting to get filesystem paths`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -69,8 +69,8 @@ class FileSystemController {
 | 
			
		||||
 | 
			
		||||
  // POST: api/filesystem/pathexists
 | 
			
		||||
  async checkPathExists(req, res) {
 | 
			
		||||
    if (!req.userNew.canUpload) {
 | 
			
		||||
      Logger.error(`[FileSystemController] Non-admin user "${req.userNew.username}" attempting to check path exists`)
 | 
			
		||||
    if (!req.user.canUpload) {
 | 
			
		||||
      Logger.error(`[FileSystemController] Non-admin user "${req.user.username}" attempting to check path exists`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -83,7 +83,7 @@ class LibraryController {
 | 
			
		||||
  async findAll(req, res) {
 | 
			
		||||
    const libraries = await Database.libraryModel.getAllOldLibraries()
 | 
			
		||||
 | 
			
		||||
    const librariesAccessible = req.userNew.permissions?.librariesAccessible || []
 | 
			
		||||
    const librariesAccessible = req.user.permissions?.librariesAccessible || []
 | 
			
		||||
    if (librariesAccessible.length) {
 | 
			
		||||
      return res.json({
 | 
			
		||||
        libraries: libraries.filter((lib) => librariesAccessible.includes(lib.id)).map((lib) => lib.toJSON())
 | 
			
		||||
@ -110,7 +110,7 @@ class LibraryController {
 | 
			
		||||
      return res.json({
 | 
			
		||||
        filterdata,
 | 
			
		||||
        issues: filterdata.numIssues,
 | 
			
		||||
        numUserPlaylists: await Database.playlistModel.getNumPlaylistsForUserAndLibrary(req.userNew.id, req.library.id),
 | 
			
		||||
        numUserPlaylists: await Database.playlistModel.getNumPlaylistsForUserAndLibrary(req.user.id, req.library.id),
 | 
			
		||||
        customMetadataProviders,
 | 
			
		||||
        library: req.library
 | 
			
		||||
      })
 | 
			
		||||
@ -327,9 +327,9 @@ class LibraryController {
 | 
			
		||||
    const filterByValue = filterByGroup ? libraryFilters.decode(payload.filterBy.replace(`${filterByGroup}.`, '')) : null
 | 
			
		||||
    if (filterByGroup === 'series' && filterByValue !== 'no-series' && payload.collapseseries) {
 | 
			
		||||
      const seriesId = libraryFilters.decode(payload.filterBy.split('.')[1])
 | 
			
		||||
      payload.results = await libraryHelpers.handleCollapseSubseries(payload, seriesId, req.userNew, req.library)
 | 
			
		||||
      payload.results = await libraryHelpers.handleCollapseSubseries(payload, seriesId, req.user, req.library)
 | 
			
		||||
    } else {
 | 
			
		||||
      const { libraryItems, count } = await Database.libraryItemModel.getByFilterAndSort(req.library, req.userNew, payload)
 | 
			
		||||
      const { libraryItems, count } = await Database.libraryItemModel.getByFilterAndSort(req.library, req.user, payload)
 | 
			
		||||
      payload.results = libraryItems
 | 
			
		||||
      payload.total = count
 | 
			
		||||
    }
 | 
			
		||||
@ -420,7 +420,7 @@ class LibraryController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const offset = payload.page * payload.limit
 | 
			
		||||
    const { series, count } = await seriesFilters.getFilteredSeries(req.library, req.userNew, payload.filterBy, payload.sortBy, payload.sortDesc, include, payload.limit, offset)
 | 
			
		||||
    const { series, count } = await seriesFilters.getFilteredSeries(req.library, req.user, payload.filterBy, payload.sortBy, payload.sortDesc, include, payload.limit, offset)
 | 
			
		||||
 | 
			
		||||
    payload.total = count
 | 
			
		||||
    payload.results = series
 | 
			
		||||
@ -447,11 +447,11 @@ class LibraryController {
 | 
			
		||||
    if (!series) return res.sendStatus(404)
 | 
			
		||||
    const oldSeries = series.getOldSeries()
 | 
			
		||||
 | 
			
		||||
    const libraryItemsInSeries = await libraryItemsBookFilters.getLibraryItemsForSeries(oldSeries, req.userNew)
 | 
			
		||||
    const libraryItemsInSeries = await libraryItemsBookFilters.getLibraryItemsForSeries(oldSeries, req.user)
 | 
			
		||||
 | 
			
		||||
    const seriesJson = oldSeries.toJSON()
 | 
			
		||||
    if (include.includes('progress')) {
 | 
			
		||||
      const libraryItemsFinished = libraryItemsInSeries.filter((li) => !!req.userNew.getMediaProgress(li.media.id)?.isFinished)
 | 
			
		||||
      const libraryItemsFinished = libraryItemsInSeries.filter((li) => !!req.user.getMediaProgress(li.media.id)?.isFinished)
 | 
			
		||||
      seriesJson.progress = {
 | 
			
		||||
        libraryItemIds: libraryItemsInSeries.map((li) => li.id),
 | 
			
		||||
        libraryItemIdsFinished: libraryItemsFinished.map((li) => li.id),
 | 
			
		||||
@ -492,7 +492,7 @@ class LibraryController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO: Create paginated queries
 | 
			
		||||
    let collections = await Database.collectionModel.getOldCollectionsJsonExpanded(req.userNew, req.library.id, include)
 | 
			
		||||
    let collections = await Database.collectionModel.getOldCollectionsJsonExpanded(req.user, req.library.id, include)
 | 
			
		||||
 | 
			
		||||
    payload.total = collections.length
 | 
			
		||||
 | 
			
		||||
@ -512,7 +512,7 @@ class LibraryController {
 | 
			
		||||
   * @param {*} res
 | 
			
		||||
   */
 | 
			
		||||
  async getUserPlaylistsForLibrary(req, res) {
 | 
			
		||||
    let playlistsForUser = await Database.playlistModel.getOldPlaylistsForUserAndLibrary(req.userNew.id, req.library.id)
 | 
			
		||||
    let playlistsForUser = await Database.playlistModel.getOldPlaylistsForUserAndLibrary(req.user.id, req.library.id)
 | 
			
		||||
 | 
			
		||||
    const payload = {
 | 
			
		||||
      results: [],
 | 
			
		||||
@ -552,7 +552,7 @@ class LibraryController {
 | 
			
		||||
      .split(',')
 | 
			
		||||
      .map((v) => v.trim().toLowerCase())
 | 
			
		||||
      .filter((v) => !!v)
 | 
			
		||||
    const shelves = await Database.libraryItemModel.getPersonalizedShelves(req.library, req.userNew, include, limitPerShelf)
 | 
			
		||||
    const shelves = await Database.libraryItemModel.getPersonalizedShelves(req.library, req.user, include, limitPerShelf)
 | 
			
		||||
    res.json(shelves)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -563,8 +563,8 @@ class LibraryController {
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async reorder(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[LibraryController] Non-admin user "${req.userNew}" attempted to reorder libraries`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[LibraryController] Non-admin user "${req.user}" attempted to reorder libraries`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    const libraries = await Database.libraryModel.getAllOldLibraries()
 | 
			
		||||
@ -609,7 +609,7 @@ class LibraryController {
 | 
			
		||||
    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.userNew, req.library, query, limit)
 | 
			
		||||
    const matches = await libraryItemFilters.search(req.user, req.library, query, limit)
 | 
			
		||||
    res.json(matches)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -662,7 +662,7 @@ class LibraryController {
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getAuthors(req, res) {
 | 
			
		||||
    const { bookWhere, replacements } = libraryItemsBookFilters.getUserPermissionBookWhereQuery(req.userNew)
 | 
			
		||||
    const { bookWhere, replacements } = libraryItemsBookFilters.getUserPermissionBookWhereQuery(req.user)
 | 
			
		||||
    const authors = await Database.authorModel.findAll({
 | 
			
		||||
      where: {
 | 
			
		||||
        libraryId: req.library.id
 | 
			
		||||
@ -672,7 +672,7 @@ class LibraryController {
 | 
			
		||||
        model: Database.bookModel,
 | 
			
		||||
        attributes: ['id', 'tags', 'explicit'],
 | 
			
		||||
        where: bookWhere,
 | 
			
		||||
        required: !req.userNew.isAdminOrUp, // Only show authors with 0 books for admin users or up
 | 
			
		||||
        required: !req.user.isAdminOrUp, // Only show authors with 0 books for admin users or up
 | 
			
		||||
        through: {
 | 
			
		||||
          attributes: []
 | 
			
		||||
        }
 | 
			
		||||
@ -746,8 +746,8 @@ class LibraryController {
 | 
			
		||||
   * @param {*} res
 | 
			
		||||
   */
 | 
			
		||||
  async updateNarrator(req, res) {
 | 
			
		||||
    if (!req.userNew.canUpdate) {
 | 
			
		||||
      Logger.error(`[LibraryController] Unauthorized user "${req.userNew.username}" attempted to update narrator`)
 | 
			
		||||
    if (!req.user.canUpdate) {
 | 
			
		||||
      Logger.error(`[LibraryController] Unauthorized user "${req.user.username}" attempted to update narrator`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -796,8 +796,8 @@ class LibraryController {
 | 
			
		||||
   * @param {*} res
 | 
			
		||||
   */
 | 
			
		||||
  async removeNarrator(req, res) {
 | 
			
		||||
    if (!req.userNew.canUpdate) {
 | 
			
		||||
      Logger.error(`[LibraryController] Unauthorized user "${req.userNew.username}" attempted to remove narrator`)
 | 
			
		||||
    if (!req.user.canUpdate) {
 | 
			
		||||
      Logger.error(`[LibraryController] Unauthorized user "${req.user.username}" attempted to remove narrator`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -839,8 +839,8 @@ class LibraryController {
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async matchAll(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[LibraryController] Non-root user "${req.userNew.username}" attempted to match library items`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[LibraryController] Non-root user "${req.user.username}" attempted to match library items`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    Scanner.matchLibraryItems(req.library)
 | 
			
		||||
@ -856,8 +856,8 @@ class LibraryController {
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async scan(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[LibraryController] Non-admin user "${req.userNew.username}" attempted to scan library`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[LibraryController] Non-admin user "${req.user.username}" attempted to scan library`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    res.sendStatus(200)
 | 
			
		||||
@ -887,7 +887,7 @@ class LibraryController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const offset = payload.page * payload.limit
 | 
			
		||||
    payload.episodes = await libraryItemsPodcastFilters.getRecentEpisodes(req.userNew, req.library, payload.limit, offset)
 | 
			
		||||
    payload.episodes = await libraryItemsPodcastFilters.getRecentEpisodes(req.user, req.library, payload.limit, offset)
 | 
			
		||||
    res.json(payload)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -898,7 +898,7 @@ class LibraryController {
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getOPMLFile(req, res) {
 | 
			
		||||
    const userPermissionPodcastWhere = libraryItemsPodcastFilters.getUserPermissionPodcastWhereQuery(req.userNew)
 | 
			
		||||
    const userPermissionPodcastWhere = libraryItemsPodcastFilters.getUserPermissionPodcastWhereQuery(req.user)
 | 
			
		||||
    const podcasts = await Database.podcastModel.findAll({
 | 
			
		||||
      attributes: ['id', 'feedURL', 'title', 'description', 'itunesPageURL', 'language'],
 | 
			
		||||
      where: userPermissionPodcastWhere.podcastWhere,
 | 
			
		||||
@ -924,8 +924,8 @@ class LibraryController {
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async removeAllMetadataFiles(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[LibraryController] Non-admin user "${req.userNew.username}" attempted to remove all metadata files`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[LibraryController] Non-admin user "${req.user.username}" attempted to remove all metadata files`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -974,8 +974,8 @@ class LibraryController {
 | 
			
		||||
   * @param {import('express').NextFunction} next
 | 
			
		||||
   */
 | 
			
		||||
  async middleware(req, res, next) {
 | 
			
		||||
    if (!req.userNew.checkCanAccessLibrary(req.params.id)) {
 | 
			
		||||
      Logger.warn(`[LibraryController] Library ${req.params.id} not accessible to user ${req.userNew.username}`)
 | 
			
		||||
    if (!req.user.checkCanAccessLibrary(req.params.id)) {
 | 
			
		||||
      Logger.warn(`[LibraryController] Library ${req.params.id} not accessible to user ${req.user.username}`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -18,8 +18,7 @@ const ShareManager = require('../managers/ShareManager')
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef RequestUserObjects
 | 
			
		||||
 * @property {import('../models/User')} userNew
 | 
			
		||||
 * @property {import('../objects/user/User')} user
 | 
			
		||||
 * @property {import('../models/User')} user
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef {Request & RequestUserObjects} RequestWithUser
 | 
			
		||||
 */
 | 
			
		||||
@ -33,8 +32,8 @@ class LibraryItemController {
 | 
			
		||||
   * ?include=progress,rssfeed,downloads,share
 | 
			
		||||
   * ?expanded=1
 | 
			
		||||
   *
 | 
			
		||||
   * @param {import('express').Request} req
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async findOne(req, res) {
 | 
			
		||||
    const includeEntities = (req.query.include || '').split(',')
 | 
			
		||||
@ -44,7 +43,7 @@ class LibraryItemController {
 | 
			
		||||
      // Include users media progress
 | 
			
		||||
      if (includeEntities.includes('progress')) {
 | 
			
		||||
        var episodeId = req.query.episode || null
 | 
			
		||||
        item.userMediaProgress = req.userNew.getOldMediaProgress(item.id, episodeId)
 | 
			
		||||
        item.userMediaProgress = req.user.getOldMediaProgress(item.id, episodeId)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (includeEntities.includes('rssfeed')) {
 | 
			
		||||
@ -52,7 +51,7 @@ class LibraryItemController {
 | 
			
		||||
        item.rssFeed = feedData?.toJSONMinified() || null
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (item.mediaType === 'book' && req.userNew.isAdminOrUp && includeEntities.includes('share')) {
 | 
			
		||||
      if (item.mediaType === 'book' && req.user.isAdminOrUp && includeEntities.includes('share')) {
 | 
			
		||||
        item.mediaItemShare = ShareManager.findByMediaItemId(item.media.id)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -69,6 +68,11 @@ class LibraryItemController {
 | 
			
		||||
    res.json(req.libraryItem)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async update(req, res) {
 | 
			
		||||
    var libraryItem = req.libraryItem
 | 
			
		||||
    // Item has cover and update is removing cover so purge it from cache
 | 
			
		||||
@ -91,8 +95,8 @@ class LibraryItemController {
 | 
			
		||||
   * Optional query params:
 | 
			
		||||
   * ?hard=1
 | 
			
		||||
   *
 | 
			
		||||
   * @param {import('express').Request} req
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async delete(req, res) {
 | 
			
		||||
    const hardDelete = req.query.hard == 1 // Delete from file system
 | 
			
		||||
@ -114,12 +118,12 @@ class LibraryItemController {
 | 
			
		||||
   * GET: /api/items/:id/download
 | 
			
		||||
   * Download library item. Zip file if multiple files.
 | 
			
		||||
   *
 | 
			
		||||
   * @param {import('express').Request} req
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  download(req, res) {
 | 
			
		||||
    if (!req.userNew.canDownload) {
 | 
			
		||||
      Logger.warn(`User "${req.userNew.username}" attempted to download without permission`)
 | 
			
		||||
    if (!req.user.canDownload) {
 | 
			
		||||
      Logger.warn(`User "${req.user.username}" attempted to download without permission`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    const libraryItemPath = req.libraryItem.path
 | 
			
		||||
@ -132,12 +136,12 @@ class LibraryItemController {
 | 
			
		||||
      if (audioMimeType) {
 | 
			
		||||
        res.setHeader('Content-Type', audioMimeType)
 | 
			
		||||
      }
 | 
			
		||||
      Logger.info(`[LibraryItemController] User "${req.userNew.username}" requested download for item "${itemTitle}" at "${libraryItemPath}"`)
 | 
			
		||||
      Logger.info(`[LibraryItemController] User "${req.user.username}" requested download for item "${itemTitle}" at "${libraryItemPath}"`)
 | 
			
		||||
      res.download(libraryItemPath, req.libraryItem.relPath)
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Logger.info(`[LibraryItemController] User "${req.userNew.username}" requested download for item "${itemTitle}" at "${libraryItemPath}"`)
 | 
			
		||||
    Logger.info(`[LibraryItemController] User "${req.user.username}" requested download for item "${itemTitle}" at "${libraryItemPath}"`)
 | 
			
		||||
    const filename = `${itemTitle}.zip`
 | 
			
		||||
    zipHelpers.zipDirectoryPipe(libraryItemPath, filename, res)
 | 
			
		||||
  }
 | 
			
		||||
@ -146,8 +150,8 @@ class LibraryItemController {
 | 
			
		||||
   * PATCH: /items/:id/media
 | 
			
		||||
   * Update media for a library item. Will create new authors & series when necessary
 | 
			
		||||
   *
 | 
			
		||||
   * @param {import('express').Request} req
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async updateMedia(req, res) {
 | 
			
		||||
    const libraryItem = req.libraryItem
 | 
			
		||||
@ -207,10 +211,16 @@ class LibraryItemController {
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // POST: api/items/:id/cover
 | 
			
		||||
  /**
 | 
			
		||||
   * POST: /api/items/:id/cover
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   * @param {boolean} [updateAndReturnJson=true]
 | 
			
		||||
   */
 | 
			
		||||
  async uploadCover(req, res, updateAndReturnJson = true) {
 | 
			
		||||
    if (!req.userNew.canUpload) {
 | 
			
		||||
      Logger.warn(`User "${req.userNew.username}" attempted to upload a cover without permission`)
 | 
			
		||||
    if (!req.user.canUpload) {
 | 
			
		||||
      Logger.warn(`User "${req.user.username}" attempted to upload a cover without permission`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -243,7 +253,12 @@ class LibraryItemController {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // PATCH: api/items/:id/cover
 | 
			
		||||
  /**
 | 
			
		||||
   * PATCH: /api/items/:id/cover
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async updateCover(req, res) {
 | 
			
		||||
    const libraryItem = req.libraryItem
 | 
			
		||||
    if (!req.body.cover) {
 | 
			
		||||
@ -264,7 +279,12 @@ class LibraryItemController {
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // DELETE: api/items/:id/cover
 | 
			
		||||
  /**
 | 
			
		||||
   * DELETE: /api/items/:id/cover
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async removeCover(req, res) {
 | 
			
		||||
    var libraryItem = req.libraryItem
 | 
			
		||||
 | 
			
		||||
@ -279,10 +299,10 @@ class LibraryItemController {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * GET: api/items/:id/cover
 | 
			
		||||
   * GET: /api/items/:id/cover
 | 
			
		||||
   *
 | 
			
		||||
   * @param {import('express').Request} req
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getCover(req, res) {
 | 
			
		||||
    const {
 | 
			
		||||
@ -308,7 +328,7 @@ class LibraryItemController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check if user can access this library item
 | 
			
		||||
    if (!req.userNew.checkCanAccessLibraryItem(libraryItem)) {
 | 
			
		||||
    if (!req.user.checkCanAccessLibraryItem(libraryItem)) {
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -377,7 +397,12 @@ class LibraryItemController {
 | 
			
		||||
    this.playbackSessionManager.startSessionRequest(req, res, episodeId)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // PATCH: api/items/:id/tracks
 | 
			
		||||
  /**
 | 
			
		||||
   * PATCH: /api/items/:id/tracks
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async updateTracks(req, res) {
 | 
			
		||||
    var libraryItem = req.libraryItem
 | 
			
		||||
    var orderedFileData = req.body.orderedFileData
 | 
			
		||||
@ -391,7 +416,12 @@ class LibraryItemController {
 | 
			
		||||
    res.json(libraryItem.toJSON())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // POST api/items/:id/match
 | 
			
		||||
  /**
 | 
			
		||||
   * POST /api/items/:id/match
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async match(req, res) {
 | 
			
		||||
    var libraryItem = req.libraryItem
 | 
			
		||||
 | 
			
		||||
@ -406,12 +436,12 @@ class LibraryItemController {
 | 
			
		||||
   * Optional query params:
 | 
			
		||||
   * ?hard=1
 | 
			
		||||
   *
 | 
			
		||||
   * @param {import('express').Request} req
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async batchDelete(req, res) {
 | 
			
		||||
    if (!req.userNew.canDelete) {
 | 
			
		||||
      Logger.warn(`[LibraryItemController] User "${req.userNew.username}" attempted to delete without permission`)
 | 
			
		||||
    if (!req.user.canDelete) {
 | 
			
		||||
      Logger.warn(`[LibraryItemController] User "${req.user.username}" attempted to delete without permission`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    const hardDelete = req.query.hard == 1 // Delete files from filesystem
 | 
			
		||||
@ -447,7 +477,12 @@ class LibraryItemController {
 | 
			
		||||
    res.sendStatus(200)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // POST: api/items/batch/update
 | 
			
		||||
  /**
 | 
			
		||||
   * POST: /api/items/batch/update
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async batchUpdate(req, res) {
 | 
			
		||||
    const updatePayloads = req.body
 | 
			
		||||
    if (!updatePayloads?.length) {
 | 
			
		||||
@ -493,7 +528,12 @@ class LibraryItemController {
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // POST: api/items/batch/get
 | 
			
		||||
  /**
 | 
			
		||||
   * POST: /api/items/batch/get
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async batchGet(req, res) {
 | 
			
		||||
    const libraryItemIds = req.body.libraryItemIds || []
 | 
			
		||||
    if (!libraryItemIds.length) {
 | 
			
		||||
@ -507,10 +547,15 @@ class LibraryItemController {
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // POST: api/items/batch/quickmatch
 | 
			
		||||
  /**
 | 
			
		||||
   * POST: /api/items/batch/quickmatch
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async batchQuickMatch(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.warn(`Non-admin user "${req.userNew.username}" other than admin attempted to batch quick match library items`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.warn(`Non-admin user "${req.user.username}" other than admin attempted to batch quick match library items`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -545,13 +590,18 @@ class LibraryItemController {
 | 
			
		||||
      updates: itemsUpdated,
 | 
			
		||||
      unmatched: itemsUnmatched
 | 
			
		||||
    }
 | 
			
		||||
    SocketAuthority.clientEmitter(req.userNew.id, 'batch_quickmatch_complete', result)
 | 
			
		||||
    SocketAuthority.clientEmitter(req.user.id, 'batch_quickmatch_complete', result)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // POST: api/items/batch/scan
 | 
			
		||||
  /**
 | 
			
		||||
   * POST: /api/items/batch/scan
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async batchScan(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.warn(`Non-admin user "${req.userNew.username}" other than admin attempted to batch scan library items`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.warn(`Non-admin user "${req.user.username}" other than admin attempted to batch scan library items`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -583,10 +633,15 @@ class LibraryItemController {
 | 
			
		||||
    await Database.resetLibraryIssuesFilterData(libraryId)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // POST: api/items/:id/scan
 | 
			
		||||
  /**
 | 
			
		||||
   * POST: /api/items/:id/scan
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async scan(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[LibraryItemController] Non-admin user "${req.userNew.username}" attempted to scan library item`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[LibraryItemController] Non-admin user "${req.user.username}" attempted to scan library item`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -602,9 +657,15 @@ class LibraryItemController {
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * GET: /api/items/:id/metadata-object
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  getMetadataObject(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[LibraryItemController] Non-admin user "${req.userNew.username}" attempted to get metadata object`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[LibraryItemController] Non-admin user "${req.user.username}" attempted to get metadata object`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -616,10 +677,15 @@ class LibraryItemController {
 | 
			
		||||
    res.json(this.audioMetadataManager.getMetadataObjectForApi(req.libraryItem))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // POST: api/items/:id/chapters
 | 
			
		||||
  /**
 | 
			
		||||
   * POST: /api/items/:id/chapters
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async updateMediaChapters(req, res) {
 | 
			
		||||
    if (!req.userNew.canUpdate) {
 | 
			
		||||
      Logger.error(`[LibraryItemController] User "${req.userNew.username}" attempted to update chapters with invalid permissions`)
 | 
			
		||||
    if (!req.user.canUpdate) {
 | 
			
		||||
      Logger.error(`[LibraryItemController] User "${req.user.username}" attempted to update chapters with invalid permissions`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -647,15 +713,15 @@ class LibraryItemController {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * GET api/items/:id/ffprobe/:fileid
 | 
			
		||||
   * GET: /api/items/:id/ffprobe/:fileid
 | 
			
		||||
   * FFProbe JSON result from audio file
 | 
			
		||||
   *
 | 
			
		||||
   * @param {express.Request} req
 | 
			
		||||
   * @param {express.Response} res
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getFFprobeData(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[LibraryItemController] Non-admin user "${req.userNew.username}" attempted to get ffprobe data`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[LibraryItemController] Non-admin user "${req.user.username}" attempted to get ffprobe data`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    if (req.libraryFile.fileType !== 'audio') {
 | 
			
		||||
@ -676,8 +742,8 @@ class LibraryItemController {
 | 
			
		||||
  /**
 | 
			
		||||
   * GET api/items/:id/file/:fileid
 | 
			
		||||
   *
 | 
			
		||||
   * @param {express.Request} req
 | 
			
		||||
   * @param {express.Response} res
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getLibraryFile(req, res) {
 | 
			
		||||
    const libraryFile = req.libraryFile
 | 
			
		||||
@ -699,13 +765,13 @@ class LibraryItemController {
 | 
			
		||||
  /**
 | 
			
		||||
   * DELETE api/items/:id/file/:fileid
 | 
			
		||||
   *
 | 
			
		||||
   * @param {express.Request} req
 | 
			
		||||
   * @param {express.Response} res
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async deleteLibraryFile(req, res) {
 | 
			
		||||
    const libraryFile = req.libraryFile
 | 
			
		||||
 | 
			
		||||
    Logger.info(`[LibraryItemController] User "${req.userNew.username}" requested file delete at "${libraryFile.metadata.path}"`)
 | 
			
		||||
    Logger.info(`[LibraryItemController] User "${req.user.username}" requested file delete at "${libraryFile.metadata.path}"`)
 | 
			
		||||
 | 
			
		||||
    await fs.remove(libraryFile.metadata.path).catch((error) => {
 | 
			
		||||
      Logger.error(`[LibraryItemController] Failed to delete library file at "${libraryFile.metadata.path}"`, error)
 | 
			
		||||
@ -727,18 +793,19 @@ class LibraryItemController {
 | 
			
		||||
  /**
 | 
			
		||||
   * GET api/items/:id/file/:fileid/download
 | 
			
		||||
   * Same as GET api/items/:id/file/:fileid but allows logging and restricting downloads
 | 
			
		||||
   * @param {express.Request} req
 | 
			
		||||
   * @param {express.Response} res
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async downloadLibraryFile(req, res) {
 | 
			
		||||
    const libraryFile = req.libraryFile
 | 
			
		||||
 | 
			
		||||
    if (!req.userNew.canDownload) {
 | 
			
		||||
      Logger.error(`[LibraryItemController] User "${req.userNew.username}" without download permission attempted to download file "${libraryFile.metadata.path}"`)
 | 
			
		||||
    if (!req.user.canDownload) {
 | 
			
		||||
      Logger.error(`[LibraryItemController] User "${req.user.username}" without download permission attempted to download file "${libraryFile.metadata.path}"`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Logger.info(`[LibraryItemController] User "${req.userNew.username}" requested download for item "${req.libraryItem.media.metadata.title}" file at "${libraryFile.metadata.path}"`)
 | 
			
		||||
    Logger.info(`[LibraryItemController] User "${req.user.username}" requested download for item "${req.libraryItem.media.metadata.title}" file at "${libraryFile.metadata.path}"`)
 | 
			
		||||
 | 
			
		||||
    if (global.XAccel) {
 | 
			
		||||
      const encodedURI = encodeUriPath(global.XAccel + libraryFile.metadata.path)
 | 
			
		||||
@ -761,8 +828,8 @@ class LibraryItemController {
 | 
			
		||||
   * fileid is only required when reading a supplementary ebook
 | 
			
		||||
   * when no fileid is passed in the primary ebook will be returned
 | 
			
		||||
   *
 | 
			
		||||
   * @param {express.Request} req
 | 
			
		||||
   * @param {express.Response} res
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getEBookFile(req, res) {
 | 
			
		||||
    let ebookFile = null
 | 
			
		||||
@ -782,7 +849,7 @@ class LibraryItemController {
 | 
			
		||||
    }
 | 
			
		||||
    const ebookFilePath = ebookFile.metadata.path
 | 
			
		||||
 | 
			
		||||
    Logger.info(`[LibraryItemController] User "${req.userNew.username}" requested download for item "${req.libraryItem.media.metadata.title}" ebook at "${ebookFilePath}"`)
 | 
			
		||||
    Logger.info(`[LibraryItemController] User "${req.user.username}" requested download for item "${req.libraryItem.media.metadata.title}" ebook at "${ebookFilePath}"`)
 | 
			
		||||
 | 
			
		||||
    if (global.XAccel) {
 | 
			
		||||
      const encodedURI = encodeUriPath(global.XAccel + ebookFilePath)
 | 
			
		||||
@ -799,8 +866,8 @@ class LibraryItemController {
 | 
			
		||||
   * if an ebook file is the primary ebook, then it will be changed to supplementary
 | 
			
		||||
   * if an ebook file is supplementary, then it will be changed to primary
 | 
			
		||||
   *
 | 
			
		||||
   * @param {express.Request} req
 | 
			
		||||
   * @param {express.Response} res
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async updateEbookFileStatus(req, res) {
 | 
			
		||||
    const ebookLibraryFile = req.libraryItem.libraryFiles.find((lf) => lf.ino === req.params.fileid)
 | 
			
		||||
@ -826,16 +893,16 @@ class LibraryItemController {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   *
 | 
			
		||||
   * @param {import('express').Request} req
 | 
			
		||||
   * @param {import('express').Response} res
 | 
			
		||||
   * @param {import('express').NextFunction} next
 | 
			
		||||
   * @param {RequestWithUser} req
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   * @param {NextFunction} next
 | 
			
		||||
   */
 | 
			
		||||
  async middleware(req, res, next) {
 | 
			
		||||
    req.libraryItem = await Database.libraryItemModel.getOldById(req.params.id)
 | 
			
		||||
    if (!req.libraryItem?.media) return res.sendStatus(404)
 | 
			
		||||
 | 
			
		||||
    // Check user can access this library item
 | 
			
		||||
    if (!req.userNew.checkCanAccessLibraryItem(req.libraryItem)) {
 | 
			
		||||
    if (!req.user.checkCanAccessLibraryItem(req.libraryItem)) {
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -850,11 +917,11 @@ class LibraryItemController {
 | 
			
		||||
 | 
			
		||||
    if (req.path.includes('/play')) {
 | 
			
		||||
      // allow POST requests using /play and /play/:episodeId
 | 
			
		||||
    } else if (req.method == 'DELETE' && !req.userNew.canDelete) {
 | 
			
		||||
      Logger.warn(`[LibraryItemController] User "${req.userNew.username}" attempted to delete without permission`)
 | 
			
		||||
    } else if (req.method == 'DELETE' && !req.user.canDelete) {
 | 
			
		||||
      Logger.warn(`[LibraryItemController] User "${req.user.username}" attempted to delete without permission`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    } else if ((req.method == 'PATCH' || req.method == 'POST') && !req.userNew.canUpdate) {
 | 
			
		||||
      Logger.warn(`[LibraryItemController] User "${req.userNew.username}" attempted to update without permission`)
 | 
			
		||||
    } else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) {
 | 
			
		||||
      Logger.warn(`[LibraryItemController] User "${req.user.username}" attempted to update without permission`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -8,8 +8,7 @@ const userStats = require('../utils/queries/userStats')
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef RequestUserObjects
 | 
			
		||||
 * @property {import('../models/User')} userNew
 | 
			
		||||
 * @property {import('../objects/user/User')} user
 | 
			
		||||
 * @property {import('../models/User')} user
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef {Request & RequestUserObjects} RequestWithUser
 | 
			
		||||
 */
 | 
			
		||||
@ -24,7 +23,7 @@ class MeController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  getCurrentUser(req, res) {
 | 
			
		||||
    res.json(req.userNew.toOldJSONForBrowser())
 | 
			
		||||
    res.json(req.user.toOldJSONForBrowser())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@ -36,7 +35,7 @@ class MeController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getListeningSessions(req, res) {
 | 
			
		||||
    const listeningSessions = await this.getUserListeningSessionsHelper(req.userNew.id)
 | 
			
		||||
    const listeningSessions = await this.getUserListeningSessionsHelper(req.user.id)
 | 
			
		||||
 | 
			
		||||
    const itemsPerPage = toNumber(req.query.itemsPerPage, 10) || 10
 | 
			
		||||
    const page = toNumber(req.query.page, 0)
 | 
			
		||||
@ -73,7 +72,7 @@ class MeController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const mediaItemId = episode?.id || libraryItem.mediaId
 | 
			
		||||
    let listeningSessions = await this.getUserItemListeningSessionsHelper(req.userNew.id, mediaItemId)
 | 
			
		||||
    let listeningSessions = await this.getUserItemListeningSessionsHelper(req.user.id, mediaItemId)
 | 
			
		||||
 | 
			
		||||
    const itemsPerPage = toNumber(req.query.itemsPerPage, 10) || 10
 | 
			
		||||
    const page = toNumber(req.query.page, 0)
 | 
			
		||||
@ -101,7 +100,7 @@ class MeController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getListeningStats(req, res) {
 | 
			
		||||
    const listeningStats = await this.getUserListeningStatsHelpers(req.userNew.id)
 | 
			
		||||
    const listeningStats = await this.getUserListeningStatsHelpers(req.user.id)
 | 
			
		||||
    res.json(listeningStats)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -112,7 +111,7 @@ class MeController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getMediaProgress(req, res) {
 | 
			
		||||
    const mediaProgress = req.userNew.getOldMediaProgress(req.params.id, req.params.episodeId || null)
 | 
			
		||||
    const mediaProgress = req.user.getOldMediaProgress(req.params.id, req.params.episodeId || null)
 | 
			
		||||
    if (!mediaProgress) {
 | 
			
		||||
      return res.sendStatus(404)
 | 
			
		||||
    }
 | 
			
		||||
@ -127,9 +126,9 @@ class MeController {
 | 
			
		||||
   */
 | 
			
		||||
  async removeMediaProgress(req, res) {
 | 
			
		||||
    await Database.mediaProgressModel.removeById(req.params.id)
 | 
			
		||||
    req.userNew.mediaProgresses = req.userNew.mediaProgresses.filter((mp) => mp.id !== req.params.id)
 | 
			
		||||
    req.user.mediaProgresses = req.user.mediaProgresses.filter((mp) => mp.id !== req.params.id)
 | 
			
		||||
 | 
			
		||||
    SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', req.userNew.toOldJSONForBrowser())
 | 
			
		||||
    SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toOldJSONForBrowser())
 | 
			
		||||
    res.sendStatus(200)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -146,12 +145,12 @@ class MeController {
 | 
			
		||||
      libraryItemId: req.params.libraryItemId,
 | 
			
		||||
      episodeId: req.params.episodeId
 | 
			
		||||
    }
 | 
			
		||||
    const mediaProgressResponse = await req.userNew.createUpdateMediaProgressFromPayload(progressUpdatePayload)
 | 
			
		||||
    const mediaProgressResponse = await req.user.createUpdateMediaProgressFromPayload(progressUpdatePayload)
 | 
			
		||||
    if (mediaProgressResponse.error) {
 | 
			
		||||
      return res.status(mediaProgressResponse.statusCode || 400).send(mediaProgressResponse.error)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', req.userNew.toOldJSONForBrowser())
 | 
			
		||||
    SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toOldJSONForBrowser())
 | 
			
		||||
    res.sendStatus(200)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -170,7 +169,7 @@ class MeController {
 | 
			
		||||
 | 
			
		||||
    let hasUpdated = false
 | 
			
		||||
    for (const itemProgress of itemProgressPayloads) {
 | 
			
		||||
      const mediaProgressResponse = await req.userNew.createUpdateMediaProgressFromPayload(itemProgress)
 | 
			
		||||
      const mediaProgressResponse = await req.user.createUpdateMediaProgressFromPayload(itemProgress)
 | 
			
		||||
      if (mediaProgressResponse.error) {
 | 
			
		||||
        Logger.error(`[MeController] batchUpdateMediaProgress: ${mediaProgressResponse.error}`)
 | 
			
		||||
        continue
 | 
			
		||||
@ -180,7 +179,7 @@ class MeController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (hasUpdated) {
 | 
			
		||||
      SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', req.userNew.toOldJSONForBrowser())
 | 
			
		||||
      SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toOldJSONForBrowser())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    res.sendStatus(200)
 | 
			
		||||
@ -205,8 +204,8 @@ class MeController {
 | 
			
		||||
      return res.status(400).send('Invalid title')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const bookmark = await req.userNew.createBookmark(req.params.id, time, title)
 | 
			
		||||
    SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', req.userNew.toOldJSONForBrowser())
 | 
			
		||||
    const bookmark = await req.user.createBookmark(req.params.id, time, title)
 | 
			
		||||
    SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toOldJSONForBrowser())
 | 
			
		||||
    res.json(bookmark)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -229,13 +228,13 @@ class MeController {
 | 
			
		||||
      return res.status(400).send('Invalid title')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const bookmark = await req.userNew.updateBookmark(req.params.id, time, title)
 | 
			
		||||
    const bookmark = await req.user.updateBookmark(req.params.id, time, title)
 | 
			
		||||
    if (!bookmark) {
 | 
			
		||||
      Logger.error(`[MeController] updateBookmark not found for library item id "${req.params.id}" and time "${time}"`)
 | 
			
		||||
      return res.sendStatus(404)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', req.userNew.toOldJSONForBrowser())
 | 
			
		||||
    SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toOldJSONForBrowser())
 | 
			
		||||
    res.json(bookmark)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -253,14 +252,14 @@ class MeController {
 | 
			
		||||
      return res.status(400).send('Invalid time')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!req.userNew.findBookmark(req.params.id, time)) {
 | 
			
		||||
    if (!req.user.findBookmark(req.params.id, time)) {
 | 
			
		||||
      Logger.error(`[MeController] removeBookmark not found`)
 | 
			
		||||
      return res.sendStatus(404)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await req.userNew.removeBookmark(req.params.id, time)
 | 
			
		||||
    await req.user.removeBookmark(req.params.id, time)
 | 
			
		||||
 | 
			
		||||
    SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', req.userNew.toOldJSONForBrowser())
 | 
			
		||||
    SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toOldJSONForBrowser())
 | 
			
		||||
    res.sendStatus(200)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -275,8 +274,8 @@ class MeController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  updatePassword(req, res) {
 | 
			
		||||
    if (req.userNew.isGuest) {
 | 
			
		||||
      Logger.error(`[MeController] Guest user "${req.userNew.username}" attempted to change password`)
 | 
			
		||||
    if (req.user.isGuest) {
 | 
			
		||||
      Logger.error(`[MeController] Guest user "${req.user.username}" attempted to change password`)
 | 
			
		||||
      return res.sendStatus(500)
 | 
			
		||||
    }
 | 
			
		||||
    this.auth.userChangePassword(req, res)
 | 
			
		||||
@ -294,7 +293,7 @@ class MeController {
 | 
			
		||||
  async getAllLibraryItemsInProgress(req, res) {
 | 
			
		||||
    const limit = !isNaN(req.query.limit) ? Number(req.query.limit) || 25 : 25
 | 
			
		||||
 | 
			
		||||
    const mediaProgressesInProgress = req.userNew.mediaProgresses.filter((mp) => !mp.isFinished && (mp.currentTime > 0 || mp.ebookProgress > 0))
 | 
			
		||||
    const mediaProgressesInProgress = req.user.mediaProgresses.filter((mp) => !mp.isFinished && (mp.currentTime > 0 || mp.ebookProgress > 0))
 | 
			
		||||
 | 
			
		||||
    const libraryItemsIds = [...new Set(mediaProgressesInProgress.map((mp) => mp.extraData?.libraryItemId).filter((id) => id))]
 | 
			
		||||
    const libraryItems = await Database.libraryItemModel.getAllOldLibraryItems({ id: libraryItemsIds })
 | 
			
		||||
@ -344,11 +343,11 @@ class MeController {
 | 
			
		||||
      return res.sendStatus(404)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const hasUpdated = await req.userNew.addSeriesToHideFromContinueListening(req.params.id)
 | 
			
		||||
    const hasUpdated = await req.user.addSeriesToHideFromContinueListening(req.params.id)
 | 
			
		||||
    if (hasUpdated) {
 | 
			
		||||
      SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', req.userNew.toOldJSONForBrowser())
 | 
			
		||||
      SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toOldJSONForBrowser())
 | 
			
		||||
    }
 | 
			
		||||
    res.json(req.userNew.toOldJSONForBrowser())
 | 
			
		||||
    res.json(req.user.toOldJSONForBrowser())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@ -363,11 +362,11 @@ class MeController {
 | 
			
		||||
      return res.sendStatus(404)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const hasUpdated = await req.userNew.removeSeriesFromHideFromContinueListening(req.params.id)
 | 
			
		||||
    const hasUpdated = await req.user.removeSeriesFromHideFromContinueListening(req.params.id)
 | 
			
		||||
    if (hasUpdated) {
 | 
			
		||||
      SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', req.userNew.toOldJSONForBrowser())
 | 
			
		||||
      SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toOldJSONForBrowser())
 | 
			
		||||
    }
 | 
			
		||||
    res.json(req.userNew.toOldJSONForBrowser())
 | 
			
		||||
    res.json(req.user.toOldJSONForBrowser())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@ -377,22 +376,22 @@ class MeController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async removeItemFromContinueListening(req, res) {
 | 
			
		||||
    const mediaProgress = req.userNew.mediaProgresses.find((mp) => mp.id === req.params.id)
 | 
			
		||||
    const mediaProgress = req.user.mediaProgresses.find((mp) => mp.id === req.params.id)
 | 
			
		||||
    if (!mediaProgress) {
 | 
			
		||||
      return res.sendStatus(404)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Already hidden
 | 
			
		||||
    if (mediaProgress.hideFromContinueListening) {
 | 
			
		||||
      return res.json(req.userNew.toOldJSONForBrowser())
 | 
			
		||||
      return res.json(req.user.toOldJSONForBrowser())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mediaProgress.hideFromContinueListening = true
 | 
			
		||||
    await mediaProgress.save()
 | 
			
		||||
 | 
			
		||||
    SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', req.userNew.toOldJSONForBrowser())
 | 
			
		||||
    SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toOldJSONForBrowser())
 | 
			
		||||
 | 
			
		||||
    res.json(req.userNew.toOldJSONForBrowser())
 | 
			
		||||
    res.json(req.user.toOldJSONForBrowser())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@ -407,7 +406,7 @@ class MeController {
 | 
			
		||||
      Logger.error(`[MeController] Invalid year "${year}"`)
 | 
			
		||||
      return res.status(400).send('Invalid year')
 | 
			
		||||
    }
 | 
			
		||||
    const data = await userStats.getStatsForYear(req.userNew.id, year)
 | 
			
		||||
    const data = await userStats.getStatsForYear(req.user.id, year)
 | 
			
		||||
    res.json(data)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -16,8 +16,7 @@ const adminStats = require('../utils/queries/adminStats')
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef RequestUserObjects
 | 
			
		||||
 * @property {import('../models/User')} userNew
 | 
			
		||||
 * @property {import('../objects/user/User')} user
 | 
			
		||||
 * @property {import('../models/User')} user
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef {Request & RequestUserObjects} RequestWithUser
 | 
			
		||||
 */
 | 
			
		||||
@ -33,8 +32,8 @@ class MiscController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async handleUpload(req, res) {
 | 
			
		||||
    if (!req.userNew.canUpload) {
 | 
			
		||||
      Logger.warn(`User "${req.userNew.username}" attempted to upload without permission`)
 | 
			
		||||
    if (!req.user.canUpload) {
 | 
			
		||||
      Logger.warn(`User "${req.user.username}" attempted to upload without permission`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    if (!req.files) {
 | 
			
		||||
@ -118,8 +117,8 @@ class MiscController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async updateServerSettings(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`User "${req.userNew.username}" other than admin attempting to update server settings`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`User "${req.user.username}" other than admin attempting to update server settings`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    const settingsUpdate = req.body
 | 
			
		||||
@ -149,8 +148,8 @@ class MiscController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async updateSortingPrefixes(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`User "${req.userNew.username}" other than admin attempting to update server sorting prefixes`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`User "${req.user.username}" other than admin attempting to update server sorting prefixes`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    let sortingPrefixes = req.body.sortingPrefixes
 | 
			
		||||
@ -249,7 +248,7 @@ class MiscController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async authorize(req, res) {
 | 
			
		||||
    const userResponse = await this.auth.getUserLoginResponsePayload(req.userNew)
 | 
			
		||||
    const userResponse = await this.auth.getUserLoginResponsePayload(req.user)
 | 
			
		||||
    res.json(userResponse)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -261,8 +260,8 @@ class MiscController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getAllTags(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to getAllTags`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.user.username}" attempted to getAllTags`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -305,8 +304,8 @@ class MiscController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async renameTag(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to renameTag`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.user.username}" attempted to renameTag`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -360,8 +359,8 @@ class MiscController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async deleteTag(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to deleteTag`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.user.username}" attempted to deleteTag`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -400,8 +399,8 @@ class MiscController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getAllGenres(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to getAllGenres`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.user.username}" attempted to getAllGenres`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    const genres = []
 | 
			
		||||
@ -443,8 +442,8 @@ class MiscController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async renameGenre(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to renameGenre`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.user.username}" attempted to renameGenre`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -498,8 +497,8 @@ class MiscController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async deleteGenre(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to deleteGenre`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.user.username}" attempted to deleteGenre`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -543,8 +542,8 @@ class MiscController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  updateWatchedPath(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to updateWatchedPath`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.user.username}" attempted to updateWatchedPath`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -601,8 +600,8 @@ class MiscController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  getAuthSettings(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to get auth settings`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.user.username}" attempted to get auth settings`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    return res.json(Database.serverSettings.authenticationSettings)
 | 
			
		||||
@ -616,8 +615,8 @@ class MiscController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async updateAuthSettings(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to update auth settings`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.user.username}" attempted to update auth settings`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -721,8 +720,8 @@ class MiscController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getAdminStatsForYear(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to get admin stats for year`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.user.username}" attempted to get admin stats for year`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    const year = Number(req.params.year)
 | 
			
		||||
@ -742,8 +741,8 @@ class MiscController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getLoggerData(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to get logger data`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[MiscController] Non-admin user "${req.user.username}" attempted to get logger data`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -4,8 +4,7 @@ const { version } = require('../../package.json')
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef RequestUserObjects
 | 
			
		||||
 * @property {import('../models/User')} userNew
 | 
			
		||||
 * @property {import('../objects/user/User')} user
 | 
			
		||||
 * @property {import('../models/User')} user
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef {Request & RequestUserObjects} RequestWithUser
 | 
			
		||||
 */
 | 
			
		||||
@ -135,7 +134,7 @@ class NotificationController {
 | 
			
		||||
   * @param {NextFunction} next
 | 
			
		||||
   */
 | 
			
		||||
  middleware(req, res, next) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7,8 +7,7 @@ const Playlist = require('../objects/Playlist')
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef RequestUserObjects
 | 
			
		||||
 * @property {import('../models/User')} userNew
 | 
			
		||||
 * @property {import('../objects/user/User')} user
 | 
			
		||||
 * @property {import('../models/User')} user
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef {Request & RequestUserObjects} RequestWithUser
 | 
			
		||||
 */
 | 
			
		||||
@ -25,7 +24,7 @@ class PlaylistController {
 | 
			
		||||
   */
 | 
			
		||||
  async create(req, res) {
 | 
			
		||||
    const oldPlaylist = new Playlist()
 | 
			
		||||
    req.body.userId = req.userNew.id
 | 
			
		||||
    req.body.userId = req.user.id
 | 
			
		||||
    const success = oldPlaylist.setData(req.body)
 | 
			
		||||
    if (!success) {
 | 
			
		||||
      return res.status(400).send('Invalid playlist request data')
 | 
			
		||||
@ -75,7 +74,7 @@ class PlaylistController {
 | 
			
		||||
  async findAllForUser(req, res) {
 | 
			
		||||
    const playlistsForUser = await Database.playlistModel.findAll({
 | 
			
		||||
      where: {
 | 
			
		||||
        userId: req.userNew.id
 | 
			
		||||
        userId: req.user.id
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    const playlists = []
 | 
			
		||||
@ -415,7 +414,7 @@ class PlaylistController {
 | 
			
		||||
      return res.status(404).send('Collection not found')
 | 
			
		||||
    }
 | 
			
		||||
    // Expand collection to get library items
 | 
			
		||||
    const collectionExpanded = await collection.getOldJsonExpanded(req.userNew)
 | 
			
		||||
    const collectionExpanded = await collection.getOldJsonExpanded(req.user)
 | 
			
		||||
    if (!collectionExpanded) {
 | 
			
		||||
      // This can happen if the user has no access to all items in collection
 | 
			
		||||
      return res.status(404).send('Collection not found')
 | 
			
		||||
@ -428,7 +427,7 @@ class PlaylistController {
 | 
			
		||||
 | 
			
		||||
    const oldPlaylist = new Playlist()
 | 
			
		||||
    oldPlaylist.setData({
 | 
			
		||||
      userId: req.userNew.id,
 | 
			
		||||
      userId: req.user.id,
 | 
			
		||||
      libraryId: collection.libraryId,
 | 
			
		||||
      name: collection.name,
 | 
			
		||||
      description: collection.description || null
 | 
			
		||||
@ -467,8 +466,8 @@ class PlaylistController {
 | 
			
		||||
      if (!playlist) {
 | 
			
		||||
        return res.status(404).send('Playlist not found')
 | 
			
		||||
      }
 | 
			
		||||
      if (playlist.userId !== req.userNew.id) {
 | 
			
		||||
        Logger.warn(`[PlaylistController] Playlist ${req.params.id} requested by user ${req.userNew.id} that is not the owner`)
 | 
			
		||||
      if (playlist.userId !== req.user.id) {
 | 
			
		||||
        Logger.warn(`[PlaylistController] Playlist ${req.params.id} requested by user ${req.user.id} that is not the owner`)
 | 
			
		||||
        return res.sendStatus(403)
 | 
			
		||||
      }
 | 
			
		||||
      req.playlist = playlist
 | 
			
		||||
 | 
			
		||||
@ -16,8 +16,7 @@ const LibraryItem = require('../objects/LibraryItem')
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef RequestUserObjects
 | 
			
		||||
 * @property {import('../models/User')} userNew
 | 
			
		||||
 * @property {import('../objects/user/User')} user
 | 
			
		||||
 * @property {import('../models/User')} user
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef {Request & RequestUserObjects} RequestWithUser
 | 
			
		||||
 */
 | 
			
		||||
@ -33,8 +32,8 @@ class PodcastController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async create(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[PodcastController] Non-admin user "${req.userNew.username}" attempted to create podcast`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[PodcastController] Non-admin user "${req.user.username}" attempted to create podcast`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    const payload = req.body
 | 
			
		||||
@ -134,8 +133,8 @@ class PodcastController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getPodcastFeed(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[PodcastController] Non-admin user "${req.userNew.username}" attempted to get podcast feed`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[PodcastController] Non-admin user "${req.user.username}" attempted to get podcast feed`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -160,8 +159,8 @@ class PodcastController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getFeedsFromOPMLText(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[PodcastController] Non-admin user "${req.userNew.username}" attempted to get feeds from opml`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[PodcastController] Non-admin user "${req.user.username}" attempted to get feeds from opml`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -183,8 +182,8 @@ class PodcastController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async bulkCreatePodcastsFromOpmlFeedUrls(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[PodcastController] Non-admin user "${req.userNew.username}" attempted to bulk create podcasts`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[PodcastController] Non-admin user "${req.user.username}" attempted to bulk create podcasts`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -218,8 +217,8 @@ class PodcastController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async checkNewEpisodes(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[PodcastController] Non-admin user "${req.userNew.username}" attempted to check/download episodes`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[PodcastController] Non-admin user "${req.user.username}" attempted to check/download episodes`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -246,8 +245,8 @@ class PodcastController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  clearEpisodeDownloadQueue(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[PodcastController] Non-admin user "${req.userNew.username}" attempting to clear download queue`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[PodcastController] Non-admin user "${req.user.username}" attempting to clear download queue`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    this.podcastManager.clearDownloadQueue(req.params.id)
 | 
			
		||||
@ -297,8 +296,8 @@ class PodcastController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async downloadEpisodes(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[PodcastController] Non-admin user "${req.userNew.username}" attempted to download episodes`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[PodcastController] Non-admin user "${req.user.username}" attempted to download episodes`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    const libraryItem = req.libraryItem
 | 
			
		||||
@ -320,8 +319,8 @@ class PodcastController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async quickMatchEpisodes(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[PodcastController] Non-admin user "${req.userNew.username}" attempted to download episodes`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[PodcastController] Non-admin user "${req.user.username}" attempted to download episodes`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -469,15 +468,15 @@ class PodcastController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check user can access this library item
 | 
			
		||||
    if (!req.userNew.checkCanAccessLibraryItem(item)) {
 | 
			
		||||
    if (!req.user.checkCanAccessLibraryItem(item)) {
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (req.method == 'DELETE' && !req.userNew.canDelete) {
 | 
			
		||||
      Logger.warn(`[PodcastController] User "${req.userNew.username}" attempted to delete without permission`)
 | 
			
		||||
    if (req.method == 'DELETE' && !req.user.canDelete) {
 | 
			
		||||
      Logger.warn(`[PodcastController] User "${req.user.username}" attempted to delete without permission`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    } else if ((req.method == 'PATCH' || req.method == 'POST') && !req.userNew.canUpdate) {
 | 
			
		||||
      Logger.warn(`[PodcastController] User "${req.userNew.username}" attempted to update without permission`)
 | 
			
		||||
    } else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) {
 | 
			
		||||
      Logger.warn(`[PodcastController] User "${req.user.username}" attempted to update without permission`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -5,8 +5,7 @@ const libraryItemsBookFilters = require('../utils/queries/libraryItemsBookFilter
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef RequestUserObjects
 | 
			
		||||
 * @property {import('../models/User')} userNew
 | 
			
		||||
 * @property {import('../objects/user/User')} user
 | 
			
		||||
 * @property {import('../models/User')} user
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef {Request & RequestUserObjects} RequestWithUser
 | 
			
		||||
 */
 | 
			
		||||
@ -45,8 +44,8 @@ class RSSFeedController {
 | 
			
		||||
    if (!item) return res.sendStatus(404)
 | 
			
		||||
 | 
			
		||||
    // Check user can access this library item
 | 
			
		||||
    if (!req.userNew.checkCanAccessLibraryItem(item)) {
 | 
			
		||||
      Logger.error(`[RSSFeedController] User "${req.userNew.username}" attempted to open an RSS feed for item "${item.media.metadata.title}" that they don\'t have access to`)
 | 
			
		||||
    if (!req.user.checkCanAccessLibraryItem(item)) {
 | 
			
		||||
      Logger.error(`[RSSFeedController] User "${req.user.username}" attempted to open an RSS feed for item "${item.media.metadata.title}" that they don\'t have access to`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -68,7 +67,7 @@ class RSSFeedController {
 | 
			
		||||
      return res.status(400).send('Slug already in use')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const feed = await this.rssFeedManager.openFeedForItem(req.userNew.id, item, req.body)
 | 
			
		||||
    const feed = await this.rssFeedManager.openFeedForItem(req.user.id, item, req.body)
 | 
			
		||||
    res.json({
 | 
			
		||||
      feed: feed.toJSONMinified()
 | 
			
		||||
    })
 | 
			
		||||
@ -109,7 +108,7 @@ class RSSFeedController {
 | 
			
		||||
      return res.status(400).send('Collection has no audio tracks')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const feed = await this.rssFeedManager.openFeedForCollection(req.userNew.id, collectionExpanded, req.body)
 | 
			
		||||
    const feed = await this.rssFeedManager.openFeedForCollection(req.user.id, collectionExpanded, req.body)
 | 
			
		||||
    res.json({
 | 
			
		||||
      feed: feed.toJSONMinified()
 | 
			
		||||
    })
 | 
			
		||||
@ -152,7 +151,7 @@ class RSSFeedController {
 | 
			
		||||
      return res.status(400).send('Series has no audio tracks')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const feed = await this.rssFeedManager.openFeedForSeries(req.userNew.id, seriesJson, req.body)
 | 
			
		||||
    const feed = await this.rssFeedManager.openFeedForSeries(req.user.id, seriesJson, req.body)
 | 
			
		||||
    res.json({
 | 
			
		||||
      feed: feed.toJSONMinified()
 | 
			
		||||
    })
 | 
			
		||||
@ -177,9 +176,9 @@ class RSSFeedController {
 | 
			
		||||
   * @param {NextFunction} next
 | 
			
		||||
   */
 | 
			
		||||
  middleware(req, res, next) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      // Only admins can manage rss feeds
 | 
			
		||||
      Logger.error(`[RSSFeedController] Non-admin user "${req.userNew.username}" attempted to make a request to an RSS feed route`)
 | 
			
		||||
      Logger.error(`[RSSFeedController] Non-admin user "${req.user.username}" attempted to make a request to an RSS feed route`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9,8 +9,7 @@ const { isValidASIN } = require('../utils')
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef RequestUserObjects
 | 
			
		||||
 * @property {import('../models/User')} userNew
 | 
			
		||||
 * @property {import('../objects/user/User')} user
 | 
			
		||||
 * @property {import('../models/User')} user
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef {Request & RequestUserObjects} RequestWithUser
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -6,8 +6,7 @@ const libraryItemsBookFilters = require('../utils/queries/libraryItemsBookFilter
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef RequestUserObjects
 | 
			
		||||
 * @property {import('../models/User')} userNew
 | 
			
		||||
 * @property {import('../objects/user/User')} user
 | 
			
		||||
 * @property {import('../models/User')} user
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef {Request & RequestUserObjects} RequestWithUser
 | 
			
		||||
 */
 | 
			
		||||
@ -37,7 +36,7 @@ class SeriesController {
 | 
			
		||||
    if (include.includes('progress')) {
 | 
			
		||||
      const libraryItemsInSeries = req.libraryItemsInSeries
 | 
			
		||||
      const libraryItemsFinished = libraryItemsInSeries.filter((li) => {
 | 
			
		||||
        return req.userNew.getMediaProgress(li.media.id)?.isFinished
 | 
			
		||||
        return req.user.getMediaProgress(li.media.id)?.isFinished
 | 
			
		||||
      })
 | 
			
		||||
      seriesJson.progress = {
 | 
			
		||||
        libraryItemIds: libraryItemsInSeries.map((li) => li.id),
 | 
			
		||||
@ -81,17 +80,17 @@ class SeriesController {
 | 
			
		||||
    /**
 | 
			
		||||
     * Filter out any library items not accessible to user
 | 
			
		||||
     */
 | 
			
		||||
    const libraryItems = await libraryItemsBookFilters.getLibraryItemsForSeries(series, req.userNew)
 | 
			
		||||
    const libraryItems = await libraryItemsBookFilters.getLibraryItemsForSeries(series, req.user)
 | 
			
		||||
    if (!libraryItems.length) {
 | 
			
		||||
      Logger.warn(`[SeriesController] User "${req.userNew.username}" attempted to access series "${series.id}" with no accessible books`)
 | 
			
		||||
      Logger.warn(`[SeriesController] User "${req.user.username}" attempted to access series "${series.id}" with no accessible books`)
 | 
			
		||||
      return res.sendStatus(404)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (req.method == 'DELETE' && !req.userNew.canDelete) {
 | 
			
		||||
      Logger.warn(`[SeriesController] User "${req.userNew.username}" attempted to delete without permission`)
 | 
			
		||||
    if (req.method == 'DELETE' && !req.user.canDelete) {
 | 
			
		||||
      Logger.warn(`[SeriesController] User "${req.user.username}" attempted to delete without permission`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    } else if ((req.method == 'PATCH' || req.method == 'POST') && !req.userNew.canUpdate) {
 | 
			
		||||
      Logger.warn(`[SeriesController] User "${req.userNew.username}" attempted to update without permission`)
 | 
			
		||||
    } else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) {
 | 
			
		||||
      Logger.warn(`[SeriesController] User "${req.user.username}" attempted to update without permission`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7,8 +7,7 @@ const ShareManager = require('../managers/ShareManager')
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef RequestUserObjects
 | 
			
		||||
 * @property {import('../models/User')} userNew
 | 
			
		||||
 * @property {import('../objects/user/User')} user
 | 
			
		||||
 * @property {import('../models/User')} user
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef {Request & RequestUserObjects} RequestWithUser
 | 
			
		||||
 */
 | 
			
		||||
@ -25,8 +24,8 @@ class SessionController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getAllWithUserData(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[SessionController] getAllWithUserData: Non-admin user "${req.userNew.username}" requested all session data`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[SessionController] getAllWithUserData: Non-admin user "${req.user.username}" requested all session data`)
 | 
			
		||||
      return res.sendStatus(404)
 | 
			
		||||
    }
 | 
			
		||||
    // Validate "user" query
 | 
			
		||||
@ -120,8 +119,8 @@ class SessionController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getOpenSessions(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[SessionController] getOpenSessions: Non-admin user "${req.userNew.username}" requested open session data`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[SessionController] getOpenSessions: Non-admin user "${req.user.username}" requested open session data`)
 | 
			
		||||
      return res.sendStatus(404)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -164,7 +163,7 @@ class SessionController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  sync(req, res) {
 | 
			
		||||
    this.playbackSessionManager.syncSessionRequest(req.userNew, req.playbackSession, req.body, res)
 | 
			
		||||
    this.playbackSessionManager.syncSessionRequest(req.user, req.playbackSession, req.body, res)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@ -178,7 +177,7 @@ class SessionController {
 | 
			
		||||
  close(req, res) {
 | 
			
		||||
    let syncData = req.body
 | 
			
		||||
    if (syncData && !Object.keys(syncData).length) syncData = null
 | 
			
		||||
    this.playbackSessionManager.closeSessionRequest(req.userNew, req.playbackSession, syncData, res)
 | 
			
		||||
    this.playbackSessionManager.closeSessionRequest(req.user, req.playbackSession, syncData, res)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@ -211,8 +210,8 @@ class SessionController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async batchDelete(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[SessionController] Non-admin user "${req.userNew.username}" attempted to batch delete sessions`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[SessionController] Non-admin user "${req.user.username}" attempted to batch delete sessions`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
    // Validate session ids
 | 
			
		||||
@ -235,7 +234,7 @@ class SessionController {
 | 
			
		||||
          id: req.body.sessions
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      Logger.info(`[SessionController] ${sessionsRemoved} playback sessions removed by "${req.userNew.username}"`)
 | 
			
		||||
      Logger.info(`[SessionController] ${sessionsRemoved} playback sessions removed by "${req.user.username}"`)
 | 
			
		||||
      res.sendStatus(200)
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      Logger.error(`[SessionController] Failed to remove playback sessions`, error)
 | 
			
		||||
@ -277,8 +276,8 @@ class SessionController {
 | 
			
		||||
    var playbackSession = this.playbackSessionManager.getSession(req.params.id)
 | 
			
		||||
    if (!playbackSession) return res.sendStatus(404)
 | 
			
		||||
 | 
			
		||||
    if (playbackSession.userId !== req.userNew.id) {
 | 
			
		||||
      Logger.error(`[SessionController] User "${req.userNew.username}" attempting to access session belonging to another user "${req.params.id}"`)
 | 
			
		||||
    if (playbackSession.userId !== req.user.id) {
 | 
			
		||||
      Logger.error(`[SessionController] User "${req.user.username}" attempting to access session belonging to another user "${req.params.id}"`)
 | 
			
		||||
      return res.sendStatus(404)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -299,11 +298,11 @@ class SessionController {
 | 
			
		||||
      return res.sendStatus(404)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (req.method == 'DELETE' && !req.userNew.canDelete) {
 | 
			
		||||
      Logger.warn(`[SessionController] User "${req.userNew.username}" attempted to delete without permission`)
 | 
			
		||||
    if (req.method == 'DELETE' && !req.user.canDelete) {
 | 
			
		||||
      Logger.warn(`[SessionController] User "${req.user.username}" attempted to delete without permission`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    } else if ((req.method == 'PATCH' || req.method == 'POST') && !req.userNew.canUpdate) {
 | 
			
		||||
      Logger.warn(`[SessionController] User "${req.userNew.username}" attempted to update without permission`)
 | 
			
		||||
    } else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) {
 | 
			
		||||
      Logger.warn(`[SessionController] User "${req.user.username}" attempted to update without permission`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -13,8 +13,7 @@ const ShareManager = require('../managers/ShareManager')
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef RequestUserObjects
 | 
			
		||||
 * @property {import('../models/User')} userNew
 | 
			
		||||
 * @property {import('../objects/user/User')} user
 | 
			
		||||
 * @property {import('../models/User')} user
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef {Request & RequestUserObjects} RequestWithUser
 | 
			
		||||
 */
 | 
			
		||||
@ -255,8 +254,8 @@ class ShareController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async createMediaItemShare(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[ShareController] Non-admin user "${req.userNew.username}" attempted to create item share`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[ShareController] Non-admin user "${req.user.username}" attempted to create item share`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -299,7 +298,7 @@ class ShareController {
 | 
			
		||||
        expiresAt: expiresAt || null,
 | 
			
		||||
        mediaItemId,
 | 
			
		||||
        mediaItemType,
 | 
			
		||||
        userId: req.userNew.id
 | 
			
		||||
        userId: req.user.id
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      ShareManager.openMediaItemShare(mediaItemShare)
 | 
			
		||||
@ -319,8 +318,8 @@ class ShareController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async deleteMediaItemShare(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[ShareController] Non-admin user "${req.userNew.username}" attempted to delete item share`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[ShareController] Non-admin user "${req.user.username}" attempted to delete item share`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -4,8 +4,7 @@ const Database = require('../Database')
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef RequestUserObjects
 | 
			
		||||
 * @property {import('../models/User')} userNew
 | 
			
		||||
 * @property {import('../objects/user/User')} user
 | 
			
		||||
 * @property {import('../models/User')} user
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef {Request & RequestUserObjects} RequestWithUser
 | 
			
		||||
 */
 | 
			
		||||
@ -39,7 +38,7 @@ class ToolsController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const options = req.query || {}
 | 
			
		||||
    this.abMergeManager.startAudiobookMerge(req.userNew.id, req.libraryItem, options)
 | 
			
		||||
    this.abMergeManager.startAudiobookMerge(req.user.id, req.libraryItem, options)
 | 
			
		||||
 | 
			
		||||
    res.sendStatus(200)
 | 
			
		||||
  }
 | 
			
		||||
@ -86,7 +85,7 @@ class ToolsController {
 | 
			
		||||
      forceEmbedChapters: req.query.forceEmbedChapters === '1',
 | 
			
		||||
      backup: req.query.backup === '1'
 | 
			
		||||
    }
 | 
			
		||||
    this.audioMetadataManager.updateMetadataForItem(req.userNew.id, req.libraryItem, options)
 | 
			
		||||
    this.audioMetadataManager.updateMetadataForItem(req.user.id, req.libraryItem, options)
 | 
			
		||||
    res.sendStatus(200)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -114,8 +113,8 @@ class ToolsController {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Check user can access this library item
 | 
			
		||||
      if (!req.userNew.checkCanAccessLibraryItem(libraryItem)) {
 | 
			
		||||
        Logger.error(`[ToolsController] Batch embed metadata library item (${libraryItemId}) not accessible to user "${req.userNew.username}"`)
 | 
			
		||||
      if (!req.user.checkCanAccessLibraryItem(libraryItem)) {
 | 
			
		||||
        Logger.error(`[ToolsController] Batch embed metadata library item (${libraryItemId}) not accessible to user "${req.user.username}"`)
 | 
			
		||||
        return res.sendStatus(403)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -136,7 +135,7 @@ class ToolsController {
 | 
			
		||||
      forceEmbedChapters: req.query.forceEmbedChapters === '1',
 | 
			
		||||
      backup: req.query.backup === '1'
 | 
			
		||||
    }
 | 
			
		||||
    this.audioMetadataManager.handleBatchEmbed(req.userNew.id, libraryItems, options)
 | 
			
		||||
    this.audioMetadataManager.handleBatchEmbed(req.user.id, libraryItems, options)
 | 
			
		||||
    res.sendStatus(200)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -147,8 +146,8 @@ class ToolsController {
 | 
			
		||||
   * @param {NextFunction} next
 | 
			
		||||
   */
 | 
			
		||||
  async middleware(req, res, next) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[LibraryItemController] Non-root user "${req.userNew.username}" attempted to access tools route`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`[LibraryItemController] Non-root user "${req.user.username}" attempted to access tools route`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -157,7 +156,7 @@ class ToolsController {
 | 
			
		||||
      if (!item?.media) return res.sendStatus(404)
 | 
			
		||||
 | 
			
		||||
      // Check user can access this library item
 | 
			
		||||
      if (!req.userNew.checkCanAccessLibraryItem(item)) {
 | 
			
		||||
      if (!req.user.checkCanAccessLibraryItem(item)) {
 | 
			
		||||
        return res.sendStatus(403)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -10,14 +10,12 @@ const { toNumber } = require('../utils/index')
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef RequestUserObjects
 | 
			
		||||
 * @property {import('../models/User')} userNew
 | 
			
		||||
 * @property {import('../objects/user/User')} user
 | 
			
		||||
 * @property {import('../models/User')} user
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef {Request & RequestUserObjects} RequestWithUser
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef UserControllerRequestProps
 | 
			
		||||
 * @property {import('../models/User')} userNew
 | 
			
		||||
 * @property {import('../objects/user/User')} user - User that made the request
 | 
			
		||||
 * @property {import('../models/User')} user - User that made the request
 | 
			
		||||
 * @property {import('../objects/user/User')} [reqUser] - User for req param id
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef {Request & UserControllerRequestProps} UserControllerRequest
 | 
			
		||||
@ -32,8 +30,8 @@ class UserController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async findAll(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) return res.sendStatus(403)
 | 
			
		||||
    const hideRootToken = !req.userNew.isRoot
 | 
			
		||||
    if (!req.user.isAdminOrUp) return res.sendStatus(403)
 | 
			
		||||
    const hideRootToken = !req.user.isRoot
 | 
			
		||||
 | 
			
		||||
    const includes = (req.query.include || '').split(',').map((i) => i.trim())
 | 
			
		||||
 | 
			
		||||
@ -62,8 +60,8 @@ class UserController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async findOne(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`Non-admin user "${req.userNew.username}" attempted to get user`)
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      Logger.error(`Non-admin user "${req.user.username}" attempted to get user`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -102,7 +100,7 @@ class UserController {
 | 
			
		||||
      return oldMediaProgress
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    const userJson = req.reqUser.toJSONForBrowser(!req.userNew.isRoot)
 | 
			
		||||
    const userJson = req.reqUser.toJSONForBrowser(!req.user.isRoot)
 | 
			
		||||
 | 
			
		||||
    userJson.mediaProgress = oldMediaProgresses
 | 
			
		||||
 | 
			
		||||
@ -155,8 +153,8 @@ class UserController {
 | 
			
		||||
  async update(req, res) {
 | 
			
		||||
    const user = req.reqUser
 | 
			
		||||
 | 
			
		||||
    if (user.type === 'root' && !req.userNew.isRoot) {
 | 
			
		||||
      Logger.error(`[UserController] Admin user "${req.userNew.username}" attempted to update root user`)
 | 
			
		||||
    if (user.type === 'root' && !req.user.isRoot) {
 | 
			
		||||
      Logger.error(`[UserController] Admin user "${req.user.username}" attempted to update root user`)
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -184,7 +182,7 @@ class UserController {
 | 
			
		||||
        Logger.info(`[UserController] User ${user.username} was generated a new api token`)
 | 
			
		||||
      }
 | 
			
		||||
      await Database.updateUser(user)
 | 
			
		||||
      SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', user.toJSONForBrowser())
 | 
			
		||||
      SocketAuthority.clientEmitter(req.user.id, 'user_updated', user.toJSONForBrowser())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    res.json({
 | 
			
		||||
@ -205,8 +203,8 @@ class UserController {
 | 
			
		||||
      Logger.error('[UserController] Attempt to delete root user. Root user cannot be deleted')
 | 
			
		||||
      return res.sendStatus(400)
 | 
			
		||||
    }
 | 
			
		||||
    if (req.userNew.id === req.params.id) {
 | 
			
		||||
      Logger.error(`[UserController] User ${req.userNew.username} is attempting to delete self`)
 | 
			
		||||
    if (req.user.id === req.params.id) {
 | 
			
		||||
      Logger.error(`[UserController] User ${req.user.username} is attempting to delete self`)
 | 
			
		||||
      return res.sendStatus(400)
 | 
			
		||||
    }
 | 
			
		||||
    const user = req.reqUser
 | 
			
		||||
@ -241,7 +239,7 @@ class UserController {
 | 
			
		||||
    Logger.debug(`[UserController] Unlinking user "${req.reqUser.username}" from OpenID with sub "${req.reqUser.authOpenIDSub}"`)
 | 
			
		||||
    req.reqUser.authOpenIDSub = null
 | 
			
		||||
    if (await Database.userModel.updateFromOld(req.reqUser)) {
 | 
			
		||||
      SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', req.reqUser.toJSONForBrowser())
 | 
			
		||||
      SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.reqUser.toJSONForBrowser())
 | 
			
		||||
      res.sendStatus(200)
 | 
			
		||||
    } else {
 | 
			
		||||
      res.sendStatus(500)
 | 
			
		||||
@ -296,7 +294,7 @@ class UserController {
 | 
			
		||||
   * @param {Response} res
 | 
			
		||||
   */
 | 
			
		||||
  async getOnlineUsers(req, res) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp) {
 | 
			
		||||
    if (!req.user.isAdminOrUp) {
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -313,9 +311,9 @@ class UserController {
 | 
			
		||||
   * @param {NextFunction} next
 | 
			
		||||
   */
 | 
			
		||||
  async middleware(req, res, next) {
 | 
			
		||||
    if (!req.userNew.isAdminOrUp && req.userNew.id !== req.params.id) {
 | 
			
		||||
    if (!req.user.isAdminOrUp && req.user.id !== req.params.id) {
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    } else if ((req.method == 'PATCH' || req.method == 'POST' || req.method == 'DELETE') && !req.userNew.isAdminOrUp) {
 | 
			
		||||
    } else if ((req.method == 'PATCH' || req.method == 'POST' || req.method == 'DELETE') && !req.user.isAdminOrUp) {
 | 
			
		||||
      return res.sendStatus(403)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -42,7 +42,7 @@ class ApiCacheManager {
 | 
			
		||||
        Logger.debug(`[ApiCacheManager] Skipping cache for random sort`)
 | 
			
		||||
        return next()
 | 
			
		||||
      }
 | 
			
		||||
      const key = { user: req.userNew.username, url: req.url }
 | 
			
		||||
      const key = { user: req.user.username, url: req.url }
 | 
			
		||||
      const stringifiedKey = JSON.stringify(key)
 | 
			
		||||
      Logger.debug(`[ApiCacheManager] count: ${this.cache.size} size: ${this.cache.calculatedSize}`)
 | 
			
		||||
      const cached = this.cache.get(stringifiedKey)
 | 
			
		||||
 | 
			
		||||
@ -48,7 +48,7 @@ class PlaybackSessionManager {
 | 
			
		||||
    const ip = requestIp.getClientIp(req)
 | 
			
		||||
 | 
			
		||||
    const deviceInfo = new DeviceInfo()
 | 
			
		||||
    deviceInfo.setData(ip, ua, clientDeviceInfo, serverVersion, req.userNew?.id)
 | 
			
		||||
    deviceInfo.setData(ip, ua, clientDeviceInfo, serverVersion, req.user?.id)
 | 
			
		||||
 | 
			
		||||
    if (clientDeviceInfo?.deviceId) {
 | 
			
		||||
      const existingDevice = await Database.getDeviceByDeviceId(clientDeviceInfo.deviceId)
 | 
			
		||||
@ -75,7 +75,7 @@ class PlaybackSessionManager {
 | 
			
		||||
    const deviceInfo = await this.getDeviceInfo(req, req.body?.deviceInfo)
 | 
			
		||||
    Logger.debug(`[PlaybackSessionManager] startSessionRequest for device ${deviceInfo.deviceDescription}`)
 | 
			
		||||
    const { libraryItem, body: options } = req
 | 
			
		||||
    const session = await this.startSession(req.userNew, deviceInfo, libraryItem, episodeId, options)
 | 
			
		||||
    const session = await this.startSession(req.user, deviceInfo, libraryItem, episodeId, options)
 | 
			
		||||
    res.json(session.toJSONForClient(libraryItem))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -96,7 +96,7 @@ class PlaybackSessionManager {
 | 
			
		||||
 | 
			
		||||
  async syncLocalSessionsRequest(req, res) {
 | 
			
		||||
    const deviceInfo = await this.getDeviceInfo(req, req.body?.deviceInfo)
 | 
			
		||||
    const user = req.userNew
 | 
			
		||||
    const user = req.user
 | 
			
		||||
    const sessions = req.body.sessions || []
 | 
			
		||||
 | 
			
		||||
    const syncResults = []
 | 
			
		||||
@ -239,7 +239,7 @@ class PlaybackSessionManager {
 | 
			
		||||
  async syncLocalSessionRequest(req, res) {
 | 
			
		||||
    const deviceInfo = await this.getDeviceInfo(req, req.body?.deviceInfo)
 | 
			
		||||
    const sessionJson = req.body
 | 
			
		||||
    const result = await this.syncLocalSession(req.userNew, sessionJson, deviceInfo)
 | 
			
		||||
    const result = await this.syncLocalSession(req.user, sessionJson, deviceInfo)
 | 
			
		||||
    if (result.error) {
 | 
			
		||||
      res.status(500).send(result.error)
 | 
			
		||||
    } else {
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,6 @@ const uuidv4 = require('uuid').v4
 | 
			
		||||
const sequelize = require('sequelize')
 | 
			
		||||
const Logger = require('../Logger')
 | 
			
		||||
const oldUser = require('../objects/user/User')
 | 
			
		||||
const AudioBookmark = require('../objects/user/AudioBookmark')
 | 
			
		||||
const SocketAuthority = require('../SocketAuthority')
 | 
			
		||||
const { isNullOrNaN } = require('../utils')
 | 
			
		||||
 | 
			
		||||
@ -52,6 +51,47 @@ class User extends Model {
 | 
			
		||||
    this.mediaProgresses
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * List of expected permission properties from the client
 | 
			
		||||
   * Only used for OpenID
 | 
			
		||||
   */
 | 
			
		||||
  static permissionMapping = {
 | 
			
		||||
    canDownload: 'download',
 | 
			
		||||
    canUpload: 'upload',
 | 
			
		||||
    canDelete: 'delete',
 | 
			
		||||
    canUpdate: 'update',
 | 
			
		||||
    canAccessExplicitContent: 'accessExplicitContent',
 | 
			
		||||
    canAccessAllLibraries: 'accessAllLibraries',
 | 
			
		||||
    canAccessAllTags: 'accessAllTags',
 | 
			
		||||
    tagsAreDenylist: 'selectedTagsNotAccessible',
 | 
			
		||||
    // Direct mapping for array-based permissions
 | 
			
		||||
    allowedLibraries: 'librariesAccessible',
 | 
			
		||||
    allowedTags: 'itemTagsSelected'
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get a sample to show how a JSON for updatePermissionsFromExternalJSON should look like
 | 
			
		||||
   * Only used for OpenID
 | 
			
		||||
   *
 | 
			
		||||
   * @returns {string} JSON string
 | 
			
		||||
   */
 | 
			
		||||
  static getSampleAbsPermissions() {
 | 
			
		||||
    // Start with a template object where all permissions are false for simplicity
 | 
			
		||||
    const samplePermissions = Object.keys(User.permissionMapping).reduce((acc, key) => {
 | 
			
		||||
      // For array-based permissions, provide a sample array
 | 
			
		||||
      if (key === 'allowedLibraries') {
 | 
			
		||||
        acc[key] = [`5406ba8a-16e1-451d-96d7-4931b0a0d966`, `918fd848-7c1d-4a02-818a-847435a879ca`]
 | 
			
		||||
      } else if (key === 'allowedTags') {
 | 
			
		||||
        acc[key] = [`ExampleTag`, `AnotherTag`, `ThirdTag`]
 | 
			
		||||
      } else {
 | 
			
		||||
        acc[key] = false
 | 
			
		||||
      }
 | 
			
		||||
      return acc
 | 
			
		||||
    }, {})
 | 
			
		||||
 | 
			
		||||
    return JSON.stringify(samplePermissions, null, 2) // Pretty print the JSON
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   *
 | 
			
		||||
   * @param {string} type
 | 
			
		||||
@ -818,6 +858,69 @@ class User extends Model {
 | 
			
		||||
    await this.save()
 | 
			
		||||
    return true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Update user permissions from external JSON
 | 
			
		||||
   *
 | 
			
		||||
   * @param {Object} absPermissions JSON containing user permissions
 | 
			
		||||
   * @returns {Promise<boolean>} true if updates were made
 | 
			
		||||
   */
 | 
			
		||||
  async updatePermissionsFromExternalJSON(absPermissions) {
 | 
			
		||||
    if (!this.permissions) this.permissions = {}
 | 
			
		||||
    let hasUpdates = false
 | 
			
		||||
 | 
			
		||||
    // Map the boolean permissions from absPermissions
 | 
			
		||||
    Object.keys(absPermissions).forEach((absKey) => {
 | 
			
		||||
      const userPermKey = User.permissionMapping[absKey]
 | 
			
		||||
      if (!userPermKey) {
 | 
			
		||||
        throw new Error(`Unexpected permission property: ${absKey}`)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!['librariesAccessible', 'itemTagsSelected'].includes(userPermKey)) {
 | 
			
		||||
        if (this.permissions[userPermKey] !== !!absPermissions[absKey]) {
 | 
			
		||||
          this.permissions[userPermKey] = !!absPermissions[absKey]
 | 
			
		||||
          hasUpdates = true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    // Handle allowedLibraries
 | 
			
		||||
    const librariesAccessible = this.permissions.librariesAccessible || []
 | 
			
		||||
    if (this.permissions.accessAllLibraries) {
 | 
			
		||||
      if (librariesAccessible.length) {
 | 
			
		||||
        this.permissions.librariesAccessible = []
 | 
			
		||||
        hasUpdates = true
 | 
			
		||||
      }
 | 
			
		||||
    } else if (absPermissions.allowedLibraries?.length && absPermissions.allowedLibraries.join(',') !== librariesAccessible.join(',')) {
 | 
			
		||||
      if (absPermissions.allowedLibraries.some((lid) => typeof lid !== 'string')) {
 | 
			
		||||
        throw new Error('Invalid permission property "allowedLibraries", expecting array of strings')
 | 
			
		||||
      }
 | 
			
		||||
      this.permissions.librariesAccessible = absPermissions.allowedLibraries
 | 
			
		||||
      hasUpdates = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Handle allowedTags
 | 
			
		||||
    const itemTagsSelected = this.permissions.itemTagsSelected || []
 | 
			
		||||
    if (this.permissions.accessAllTags) {
 | 
			
		||||
      if (itemTagsSelected.length) {
 | 
			
		||||
        this.permissions.itemTagsSelected = []
 | 
			
		||||
        hasUpdates = true
 | 
			
		||||
      }
 | 
			
		||||
    } else if (absPermissions.allowedTags?.length && absPermissions.allowedTags.join(',') !== itemTagsSelected.join(',')) {
 | 
			
		||||
      if (absPermissions.allowedTags.some((tag) => typeof tag !== 'string')) {
 | 
			
		||||
        throw new Error('Invalid permission property "allowedTags", expecting array of strings')
 | 
			
		||||
      }
 | 
			
		||||
      this.permissions.itemTagsSelected = absPermissions.allowedTags
 | 
			
		||||
      hasUpdates = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (hasUpdates) {
 | 
			
		||||
      this.changed('permissions', true)
 | 
			
		||||
      await this.save()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return hasUpdates
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = User
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ const Path = require('path')
 | 
			
		||||
const packageJson = require('../../../package.json')
 | 
			
		||||
const { BookshelfView } = require('../../utils/constants')
 | 
			
		||||
const Logger = require('../../Logger')
 | 
			
		||||
const User = require('../user/User')
 | 
			
		||||
const User = require('../../models/User')
 | 
			
		||||
 | 
			
		||||
class ServerSettings {
 | 
			
		||||
  constructor(settings) {
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,3 @@
 | 
			
		||||
const Logger = require('../../Logger')
 | 
			
		||||
const AudioBookmark = require('./AudioBookmark')
 | 
			
		||||
const MediaProgress = require('./MediaProgress')
 | 
			
		||||
 | 
			
		||||
@ -268,109 +267,5 @@ class User {
 | 
			
		||||
    }
 | 
			
		||||
    return hasUpdates
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // List of expected permission properties from the client
 | 
			
		||||
  static permissionMapping = {
 | 
			
		||||
    canDownload: 'download',
 | 
			
		||||
    canUpload: 'upload',
 | 
			
		||||
    canDelete: 'delete',
 | 
			
		||||
    canUpdate: 'update',
 | 
			
		||||
    canAccessExplicitContent: 'accessExplicitContent',
 | 
			
		||||
    canAccessAllLibraries: 'accessAllLibraries',
 | 
			
		||||
    canAccessAllTags: 'accessAllTags',
 | 
			
		||||
    tagsAreDenylist: 'selectedTagsNotAccessible',
 | 
			
		||||
    // Direct mapping for array-based permissions
 | 
			
		||||
    allowedLibraries: 'librariesAccessible',
 | 
			
		||||
    allowedTags: 'itemTagsSelected'
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Update user permissions from external JSON
 | 
			
		||||
   *
 | 
			
		||||
   * @param {Object} absPermissions JSON containing user permissions
 | 
			
		||||
   * @returns {boolean} true if updates were made
 | 
			
		||||
   */
 | 
			
		||||
  updatePermissionsFromExternalJSON(absPermissions) {
 | 
			
		||||
    let hasUpdates = false
 | 
			
		||||
    let updatedUserPermissions = {}
 | 
			
		||||
 | 
			
		||||
    // Initialize all permissions to false first
 | 
			
		||||
    Object.keys(User.permissionMapping).forEach((mappingKey) => {
 | 
			
		||||
      const userPermKey = User.permissionMapping[mappingKey]
 | 
			
		||||
      if (typeof this.permissions[userPermKey] === 'boolean') {
 | 
			
		||||
        updatedUserPermissions[userPermKey] = false // Default to false for boolean permissions
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    // Map the boolean permissions from absPermissions
 | 
			
		||||
    Object.keys(absPermissions).forEach((absKey) => {
 | 
			
		||||
      const userPermKey = User.permissionMapping[absKey]
 | 
			
		||||
      if (!userPermKey) {
 | 
			
		||||
        throw new Error(`Unexpected permission property: ${absKey}`)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (updatedUserPermissions[userPermKey] !== undefined) {
 | 
			
		||||
        updatedUserPermissions[userPermKey] = !!absPermissions[absKey]
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    // Update user permissions if changes were made
 | 
			
		||||
    if (JSON.stringify(this.permissions) !== JSON.stringify(updatedUserPermissions)) {
 | 
			
		||||
      this.permissions = updatedUserPermissions
 | 
			
		||||
      hasUpdates = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Handle allowedLibraries
 | 
			
		||||
    if (this.permissions.accessAllLibraries) {
 | 
			
		||||
      if (this.librariesAccessible.length) {
 | 
			
		||||
        this.librariesAccessible = []
 | 
			
		||||
        hasUpdates = true
 | 
			
		||||
      }
 | 
			
		||||
    } else if (absPermissions.allowedLibraries?.length && absPermissions.allowedLibraries.join(',') !== this.librariesAccessible.join(',')) {
 | 
			
		||||
      if (absPermissions.allowedLibraries.some((lid) => typeof lid !== 'string')) {
 | 
			
		||||
        throw new Error('Invalid permission property "allowedLibraries", expecting array of strings')
 | 
			
		||||
      }
 | 
			
		||||
      this.librariesAccessible = absPermissions.allowedLibraries
 | 
			
		||||
      hasUpdates = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Handle allowedTags
 | 
			
		||||
    if (this.permissions.accessAllTags) {
 | 
			
		||||
      if (this.itemTagsSelected.length) {
 | 
			
		||||
        this.itemTagsSelected = []
 | 
			
		||||
        hasUpdates = true
 | 
			
		||||
      }
 | 
			
		||||
    } else if (absPermissions.allowedTags?.length && absPermissions.allowedTags.join(',') !== this.itemTagsSelected.join(',')) {
 | 
			
		||||
      if (absPermissions.allowedTags.some((tag) => typeof tag !== 'string')) {
 | 
			
		||||
        throw new Error('Invalid permission property "allowedTags", expecting array of strings')
 | 
			
		||||
      }
 | 
			
		||||
      this.itemTagsSelected = absPermissions.allowedTags
 | 
			
		||||
      hasUpdates = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return hasUpdates
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get a sample to show how a JSON for updatePermissionsFromExternalJSON should look like
 | 
			
		||||
   *
 | 
			
		||||
   * @returns {string} JSON string
 | 
			
		||||
   */
 | 
			
		||||
  static getSampleAbsPermissions() {
 | 
			
		||||
    // Start with a template object where all permissions are false for simplicity
 | 
			
		||||
    const samplePermissions = Object.keys(User.permissionMapping).reduce((acc, key) => {
 | 
			
		||||
      // For array-based permissions, provide a sample array
 | 
			
		||||
      if (key === 'allowedLibraries') {
 | 
			
		||||
        acc[key] = [`5406ba8a-16e1-451d-96d7-4931b0a0d966`, `918fd848-7c1d-4a02-818a-847435a879ca`]
 | 
			
		||||
      } else if (key === 'allowedTags') {
 | 
			
		||||
        acc[key] = [`ExampleTag`, `AnotherTag`, `ThirdTag`]
 | 
			
		||||
      } else {
 | 
			
		||||
        acc[key] = false
 | 
			
		||||
      }
 | 
			
		||||
      return acc
 | 
			
		||||
    }, {})
 | 
			
		||||
 | 
			
		||||
    return JSON.stringify(samplePermissions, null, 2) // Pretty print the JSON
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
module.exports = User
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@ describe('ApiCacheManager', () => {
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    cache = { get: sinon.stub(), set: sinon.spy() }
 | 
			
		||||
    req = { user: { username: 'testUser' }, userNew: { username: 'testUser' }, url: '/test-url', query: {} }
 | 
			
		||||
    req = { user: { username: 'testUser' }, url: '/test-url', query: {} }
 | 
			
		||||
    res = { send: sinon.spy(), getHeaders: sinon.stub(), statusCode: 200, status: sinon.spy(), set: sinon.spy() }
 | 
			
		||||
    next = sinon.spy()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user