mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Update controllers to use new user model
This commit is contained in:
		
							parent
							
								
									202ceb02b5
								
							
						
					
					
						commit
						68ef3a07a7
					
				| @ -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.user) | ||||
|       authorJson.libraryItems = await Database.libraryItemModel.getForAuthor(req.author, req.userNew) | ||||
| 
 | ||||
|       if (include.includes('series')) { | ||||
|         const seriesMap = {} | ||||
| @ -222,8 +222,8 @@ class AuthorController { | ||||
|    * @param {import('express').Response} res | ||||
|    */ | ||||
|   async uploadImage(req, res) { | ||||
|     if (!req.user.canUpload) { | ||||
|       Logger.warn('User attempted to upload an image without permission', req.user) | ||||
|     if (!req.userNew.canUpload) { | ||||
|       Logger.warn(`User "${req.userNew.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.user.canDelete) { | ||||
|       Logger.warn(`[AuthorController] User attempted to delete without permission`, req.user) | ||||
|     if (req.method == 'DELETE' && !req.userNew.canDelete) { | ||||
|       Logger.warn(`[AuthorController] User "${req.userNew.username}" attempted to delete without permission`) | ||||
|       return res.sendStatus(403) | ||||
|     } else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) { | ||||
|       Logger.warn('[AuthorController] User attempted to update without permission', req.user) | ||||
|       Logger.warn(`[AuthorController] User "${req.userNew.username}" attempted to update without permission`) | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -113,8 +113,8 @@ class BackupController { | ||||
|   } | ||||
| 
 | ||||
|   middleware(req, res, next) { | ||||
|     if (!req.user.isAdminOrUp) { | ||||
|       Logger.error(`[BackupController] Non-admin user attempting to access backups`, req.user) | ||||
|     if (!req.userNew.isAdminOrUp) { | ||||
|       Logger.error(`[BackupController] Non-admin user "${req.userNew.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.user.isAdminOrUp) { | ||||
|     if (!req.userNew.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.user.isAdminOrUp) { | ||||
|     if (!req.userNew.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.user.id | ||||
|     req.body.userId = req.userNew.id | ||||
|     if (!newCollection.setData(req.body)) { | ||||
|       return res.status(400).send('Invalid collection data') | ||||
|     } | ||||
| @ -31,7 +31,7 @@ class CollectionController { | ||||
|     let order = 1 | ||||
|     const collectionBooksToAdd = [] | ||||
|     for (const libraryItemId of newCollection.books) { | ||||
|       const libraryItem = libraryItemsInCollection.find(li => li.id === libraryItemId) | ||||
|       const libraryItem = libraryItemsInCollection.find((li) => li.id === libraryItemId) | ||||
|       if (libraryItem) { | ||||
|         collectionBooksToAdd.push({ | ||||
|           collectionId: newCollection.id, | ||||
| @ -50,7 +50,7 @@ class CollectionController { | ||||
|   } | ||||
| 
 | ||||
|   async findAll(req, res) { | ||||
|     const collectionsExpanded = await Database.collectionModel.getOldCollectionsJsonExpanded(req.user) | ||||
|     const collectionsExpanded = await Database.collectionModel.getOldCollectionsJsonExpanded(req.userNew) | ||||
|     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.user, includeEntities) | ||||
|     const collectionExpanded = await req.collection.getOldJsonExpanded(req.userNew, includeEntities) | ||||
|     if (!collectionExpanded) { | ||||
|       // This may happen if the user is restricted from all books
 | ||||
|       return res.sendStatus(404) | ||||
| @ -102,8 +102,8 @@ class CollectionController { | ||||
|         order: [['order', 'ASC']] | ||||
|       }) | ||||
|       collectionBooks.sort((a, b) => { | ||||
|         const aIndex = req.body.books.findIndex(lid => lid === a.book.libraryItem.id) | ||||
|         const bIndex = req.body.books.findIndex(lid => lid === b.book.libraryItem.id) | ||||
|         const aIndex = req.body.books.findIndex((lid) => lid === a.book.libraryItem.id) | ||||
|         const bIndex = req.body.books.findIndex((lid) => lid === b.book.libraryItem.id) | ||||
|         return aIndex - bIndex | ||||
|       }) | ||||
|       for (let i = 0; i < collectionBooks.length; i++) { | ||||
| @ -153,7 +153,7 @@ class CollectionController { | ||||
| 
 | ||||
|     // Check if book is already in collection
 | ||||
|     const collectionBooks = await req.collection.getCollectionBooks() | ||||
|     if (collectionBooks.some(cb => cb.bookId === libraryItem.media.id)) { | ||||
|     if (collectionBooks.some((cb) => cb.bookId === libraryItem.media.id)) { | ||||
|       return res.status(400).send('Book already in collection') | ||||
|     } | ||||
| 
 | ||||
| @ -187,7 +187,7 @@ class CollectionController { | ||||
|     }) | ||||
| 
 | ||||
|     let jsonExpanded = null | ||||
|     const collectionBookToRemove = collectionBooks.find(cb => cb.bookId === libraryItem.media.id) | ||||
|     const collectionBookToRemove = collectionBooks.find((cb) => cb.bookId === libraryItem.media.id) | ||||
|     if (collectionBookToRemove) { | ||||
|       // Remove collection book record
 | ||||
|       await collectionBookToRemove.destroy() | ||||
| @ -221,7 +221,7 @@ class CollectionController { | ||||
|    */ | ||||
|   async addBatch(req, res) { | ||||
|     // filter out invalid libraryItemIds
 | ||||
|     const bookIdsToAdd = (req.body.books || []).filter(b => !!b && typeof b == 'string') | ||||
|     const bookIdsToAdd = (req.body.books || []).filter((b) => !!b && typeof b == 'string') | ||||
|     if (!bookIdsToAdd.length) { | ||||
|       return res.status(500).send('Invalid request body') | ||||
|     } | ||||
| @ -247,7 +247,7 @@ class CollectionController { | ||||
| 
 | ||||
|     // Check and set new collection books to add
 | ||||
|     for (const libraryItem of libraryItems) { | ||||
|       if (!collectionBooks.some(cb => cb.bookId === libraryItem.media.id)) { | ||||
|       if (!collectionBooks.some((cb) => cb.bookId === libraryItem.media.id)) { | ||||
|         collectionBooksToAdd.push({ | ||||
|           collectionId: req.collection.id, | ||||
|           bookId: libraryItem.media.id, | ||||
| @ -279,7 +279,7 @@ class CollectionController { | ||||
|    */ | ||||
|   async removeBatch(req, res) { | ||||
|     // filter out invalid libraryItemIds
 | ||||
|     const bookIdsToRemove = (req.body.books || []).filter(b => !!b && typeof b == 'string') | ||||
|     const bookIdsToRemove = (req.body.books || []).filter((b) => !!b && typeof b == 'string') | ||||
|     if (!bookIdsToRemove.length) { | ||||
|       return res.status(500).send('Invalid request body') | ||||
|     } | ||||
| @ -305,7 +305,7 @@ class CollectionController { | ||||
|     let order = 1 | ||||
|     let hasUpdated = false | ||||
|     for (const collectionBook of collectionBooks) { | ||||
|       if (libraryItems.some(li => li.media.id === collectionBook.bookId)) { | ||||
|       if (libraryItems.some((li) => li.media.id === collectionBook.bookId)) { | ||||
|         await collectionBook.destroy() | ||||
|         hasUpdated = true | ||||
|         continue | ||||
| @ -334,11 +334,11 @@ class CollectionController { | ||||
|       req.collection = collection | ||||
|     } | ||||
| 
 | ||||
|     if (req.method == 'DELETE' && !req.user.canDelete) { | ||||
|       Logger.warn(`[CollectionController] User attempted to delete without permission`, req.user.username) | ||||
|     if (req.method == 'DELETE' && !req.userNew.canDelete) { | ||||
|       Logger.warn(`[CollectionController] User "${req.userNew.username}" attempted to delete without permission`) | ||||
|       return res.sendStatus(403) | ||||
|     } else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) { | ||||
|       Logger.warn('[CollectionController] User attempted to update without permission', req.user.username) | ||||
|       Logger.warn(`[CollectionController] User "${req.userNew.username}" attempted to update without permission`) | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -47,7 +47,7 @@ class CustomMetadataProviderController { | ||||
|       name, | ||||
|       mediaType, | ||||
|       url, | ||||
|       authHeaderValue: !authHeaderValue ? null : authHeaderValue, | ||||
|       authHeaderValue: !authHeaderValue ? null : authHeaderValue | ||||
|     }) | ||||
| 
 | ||||
|     // TODO: Necessary to emit to all clients?
 | ||||
| @ -76,13 +76,16 @@ class CustomMetadataProviderController { | ||||
|     await provider.destroy() | ||||
| 
 | ||||
|     // Libraries using this provider fallback to default provider
 | ||||
|     await Database.libraryModel.update({ | ||||
|     await Database.libraryModel.update( | ||||
|       { | ||||
|         provider: fallbackProvider | ||||
|     }, { | ||||
|       }, | ||||
|       { | ||||
|         where: { | ||||
|           provider: slug | ||||
|         } | ||||
|     }) | ||||
|       } | ||||
|     ) | ||||
| 
 | ||||
|     // TODO: Necessary to emit to all clients?
 | ||||
|     SocketAuthority.emitter('custom_metadata_provider_removed', providerClientJson) | ||||
| @ -98,8 +101,8 @@ class CustomMetadataProviderController { | ||||
|    * @param {import('express').NextFunction} next | ||||
|    */ | ||||
|   async middleware(req, res, next) { | ||||
|     if (!req.user.isAdminOrUp) { | ||||
|       Logger.warn(`[CustomMetadataProviderController] Non-admin user "${req.user.username}" attempted access route "${req.path}"`) | ||||
|     if (!req.userNew.isAdminOrUp) { | ||||
|       Logger.warn(`[CustomMetadataProviderController] Non-admin user "${req.userNew.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.user.username}" for libraryItemId=${req.body.libraryItemId}, deviceName=${req.body.deviceName}`) | ||||
|     Logger.debug(`[EmailController] Send ebook to device requested by user "${req.userNew.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.user)) { | ||||
|     if (!Database.emailSettings.checkUserCanAccessDevice(device, req.userNew)) { | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
| 
 | ||||
| @ -77,7 +77,7 @@ class EmailController { | ||||
|     } | ||||
| 
 | ||||
|     // Check user has access to library item
 | ||||
|     if (!req.user.checkCanAccessLibraryItem(libraryItem)) { | ||||
|     if (!req.userNew.checkCanAccessLibraryItem(libraryItem)) { | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
| 
 | ||||
| @ -90,7 +90,7 @@ class EmailController { | ||||
|   } | ||||
| 
 | ||||
|   adminMiddleware(req, res, next) { | ||||
|     if (!req.user.isAdminOrUp) { | ||||
|     if (!req.userNew.isAdminOrUp) { | ||||
|       return res.sendStatus(404) | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -13,8 +13,8 @@ class FileSystemController { | ||||
|    * @param {import('express').Response} res | ||||
|    */ | ||||
|   async getPaths(req, res) { | ||||
|     if (!req.user.isAdminOrUp) { | ||||
|       Logger.error(`[FileSystemController] Non-admin user attempting to get filesystem paths`, req.user) | ||||
|     if (!req.userNew.isAdminOrUp) { | ||||
|       Logger.error(`[FileSystemController] Non-admin user "${req.userNew.username}" attempting to get filesystem paths`) | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
| 
 | ||||
| @ -22,7 +22,7 @@ class FileSystemController { | ||||
|     const level = toNumber(req.query.level, 0) | ||||
| 
 | ||||
|     // Validate path. Must be absolute
 | ||||
|     if (relpath && (!Path.isAbsolute(relpath) || !await fs.pathExists(relpath))) { | ||||
|     if (relpath && (!Path.isAbsolute(relpath) || !(await fs.pathExists(relpath)))) { | ||||
|       Logger.error(`[FileSystemController] Invalid path in query string "${relpath}"`) | ||||
|       return res.status(400).send('Invalid "path" query string') | ||||
|     } | ||||
| @ -40,7 +40,7 @@ class FileSystemController { | ||||
|           return [] | ||||
|         }) | ||||
|         if (drives.length) { | ||||
|           directories = drives.map(d => { | ||||
|           directories = drives.map((d) => { | ||||
|             return { | ||||
|               path: d, | ||||
|               dirname: d, | ||||
| @ -54,10 +54,10 @@ class FileSystemController { | ||||
|     } | ||||
| 
 | ||||
|     // Exclude some dirs from this project to be cleaner in Docker
 | ||||
|     const excludedDirs = ['node_modules', 'client', 'server', '.git', 'static', 'build', 'dist', 'metadata', 'config', 'sys', 'proc', '.devcontainer', '.nyc_output', '.github', '.vscode'].map(dirname => { | ||||
|     const excludedDirs = ['node_modules', 'client', 'server', '.git', 'static', 'build', 'dist', 'metadata', 'config', 'sys', 'proc', '.devcontainer', '.nyc_output', '.github', '.vscode'].map((dirname) => { | ||||
|       return fileUtils.filePathToPOSIX(Path.join(global.appRoot, dirname)) | ||||
|     }) | ||||
|     directories = directories.filter(dir => { | ||||
|     directories = directories.filter((dir) => { | ||||
|       return !excludedDirs.includes(dir.path) | ||||
|     }) | ||||
| 
 | ||||
| @ -69,8 +69,8 @@ class FileSystemController { | ||||
| 
 | ||||
|   // POST: api/filesystem/pathexists
 | ||||
|   async checkPathExists(req, res) { | ||||
|     if (!req.user.canUpload) { | ||||
|       Logger.error(`[FileSystemController] Non-admin user attempting to check path exists`, req.user) | ||||
|     if (!req.userNew.canUpload) { | ||||
|       Logger.error(`[FileSystemController] Non-admin user "${req.userNew.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.user.librariesAccessible || [] | ||||
|     const librariesAccessible = req.userNew.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.user.id, req.library.id), | ||||
|         numUserPlaylists: await Database.playlistModel.getNumPlaylistsForUserAndLibrary(req.userNew.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.user, req.library) | ||||
|       payload.results = await libraryHelpers.handleCollapseSubseries(payload, seriesId, req.userNew, req.library) | ||||
|     } else { | ||||
|       const { libraryItems, count } = await Database.libraryItemModel.getByFilterAndSort(req.library, req.user, payload) | ||||
|       const { libraryItems, count } = await Database.libraryItemModel.getByFilterAndSort(req.library, req.userNew, 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.user, payload.filterBy, payload.sortBy, payload.sortDesc, include, payload.limit, offset) | ||||
|     const { series, count } = await seriesFilters.getFilteredSeries(req.library, req.userNew, 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.user) | ||||
|     const libraryItemsInSeries = await libraryItemsBookFilters.getLibraryItemsForSeries(oldSeries, req.userNew) | ||||
| 
 | ||||
|     const seriesJson = oldSeries.toJSON() | ||||
|     if (include.includes('progress')) { | ||||
|       const libraryItemsFinished = libraryItemsInSeries.filter((li) => !!req.user.getMediaProgress(li.id)?.isFinished) | ||||
|       const libraryItemsFinished = libraryItemsInSeries.filter((li) => !!req.userNew.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.user, req.library.id, include) | ||||
|     let collections = await Database.collectionModel.getOldCollectionsJsonExpanded(req.userNew, 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.user.id, req.library.id) | ||||
|     let playlistsForUser = await Database.playlistModel.getOldPlaylistsForUserAndLibrary(req.userNew.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.user, include, limitPerShelf) | ||||
|     const shelves = await Database.libraryItemModel.getPersonalizedShelves(req.library, req.userNew, include, limitPerShelf) | ||||
|     res.json(shelves) | ||||
|   } | ||||
| 
 | ||||
| @ -563,8 +563,8 @@ class LibraryController { | ||||
|    * @param {import('express').Response} res | ||||
|    */ | ||||
|   async reorder(req, res) { | ||||
|     if (!req.user.isAdminOrUp) { | ||||
|       Logger.error('[LibraryController] ReorderLibraries invalid user', req.user) | ||||
|     if (!req.userNew.isAdminOrUp) { | ||||
|       Logger.error(`[LibraryController] Non-admin user "${req.userNew}" 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.user, req.library, query, limit) | ||||
|     const matches = await libraryItemFilters.search(req.userNew, 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.user) | ||||
|     const { bookWhere, replacements } = libraryItemsBookFilters.getUserPermissionBookWhereQuery(req.userNew) | ||||
|     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.user.isAdminOrUp, // Only show authors with 0 books for admin users or up
 | ||||
|         required: !req.userNew.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.user.canUpdate) { | ||||
|       Logger.error(`[LibraryController] Unauthorized user "${req.user.username}" attempted to update narrator`) | ||||
|     if (!req.userNew.canUpdate) { | ||||
|       Logger.error(`[LibraryController] Unauthorized user "${req.userNew.username}" attempted to update narrator`) | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
| 
 | ||||
| @ -796,8 +796,8 @@ class LibraryController { | ||||
|    * @param {*} res | ||||
|    */ | ||||
|   async removeNarrator(req, res) { | ||||
|     if (!req.user.canUpdate) { | ||||
|       Logger.error(`[LibraryController] Unauthorized user "${req.user.username}" attempted to remove narrator`) | ||||
|     if (!req.userNew.canUpdate) { | ||||
|       Logger.error(`[LibraryController] Unauthorized user "${req.userNew.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.user.isAdminOrUp) { | ||||
|       Logger.error(`[LibraryController] Non-root user attempted to match library items`, req.user) | ||||
|     if (!req.userNew.isAdminOrUp) { | ||||
|       Logger.error(`[LibraryController] Non-root user "${req.userNew.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.user.isAdminOrUp) { | ||||
|       Logger.error(`[LibraryController] Non-root user attempted to scan library`, req.user) | ||||
|     if (!req.userNew.isAdminOrUp) { | ||||
|       Logger.error(`[LibraryController] Non-admin user "${req.userNew.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.user, req.library, payload.limit, offset) | ||||
|     payload.episodes = await libraryItemsPodcastFilters.getRecentEpisodes(req.userNew, 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.user) | ||||
|     const userPermissionPodcastWhere = libraryItemsPodcastFilters.getUserPermissionPodcastWhereQuery(req.userNew) | ||||
|     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.user.isAdminOrUp) { | ||||
|       Logger.error(`[LibraryController] Non-admin user attempted to remove all metadata files`, req.user) | ||||
|     if (!req.userNew.isAdminOrUp) { | ||||
|       Logger.error(`[LibraryController] Non-admin user "${req.userNew.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.user.checkCanAccessLibrary(req.params.id)) { | ||||
|       Logger.warn(`[LibraryController] Library ${req.params.id} not accessible to user ${req.user.username}`) | ||||
|     if (!req.userNew.checkCanAccessLibrary(req.params.id)) { | ||||
|       Logger.warn(`[LibraryController] Library ${req.params.id} not accessible to user ${req.userNew.username}`) | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -35,7 +35,7 @@ class LibraryItemController { | ||||
|       // Include users media progress
 | ||||
|       if (includeEntities.includes('progress')) { | ||||
|         var episodeId = req.query.episode || null | ||||
|         item.userMediaProgress = req.user.getMediaProgress(item.id, episodeId) | ||||
|         item.userMediaProgress = req.userNew.getOldMediaProgress(item.id, episodeId) | ||||
|       } | ||||
| 
 | ||||
|       if (includeEntities.includes('rssfeed')) { | ||||
| @ -43,7 +43,7 @@ class LibraryItemController { | ||||
|         item.rssFeed = feedData?.toJSONMinified() || null | ||||
|       } | ||||
| 
 | ||||
|       if (item.mediaType === 'book' && req.user.isAdminOrUp && includeEntities.includes('share')) { | ||||
|       if (item.mediaType === 'book' && req.userNew.isAdminOrUp && includeEntities.includes('share')) { | ||||
|         item.mediaItemShare = ShareManager.findByMediaItemId(item.media.id) | ||||
|       } | ||||
| 
 | ||||
| @ -109,8 +109,8 @@ class LibraryItemController { | ||||
|    * @param {import('express').Response} res | ||||
|    */ | ||||
|   download(req, res) { | ||||
|     if (!req.user.canDownload) { | ||||
|       Logger.warn('User attempted to download without permission', req.user) | ||||
|     if (!req.userNew.canDownload) { | ||||
|       Logger.warn(`User "${req.userNew.username}" attempted to download without permission`) | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
|     const libraryItemPath = req.libraryItem.path | ||||
| @ -123,12 +123,12 @@ class LibraryItemController { | ||||
|       if (audioMimeType) { | ||||
|         res.setHeader('Content-Type', audioMimeType) | ||||
|       } | ||||
|       Logger.info(`[LibraryItemController] User "${req.user.username}" requested download for item "${itemTitle}" at "${libraryItemPath}"`) | ||||
|       Logger.info(`[LibraryItemController] User "${req.userNew.username}" requested download for item "${itemTitle}" at "${libraryItemPath}"`) | ||||
|       res.download(libraryItemPath, req.libraryItem.relPath) | ||||
|       return | ||||
|     } | ||||
| 
 | ||||
|     Logger.info(`[LibraryItemController] User "${req.user.username}" requested download for item "${itemTitle}" at "${libraryItemPath}"`) | ||||
|     Logger.info(`[LibraryItemController] User "${req.userNew.username}" requested download for item "${itemTitle}" at "${libraryItemPath}"`) | ||||
|     const filename = `${itemTitle}.zip` | ||||
|     zipHelpers.zipDirectoryPipe(libraryItemPath, filename, res) | ||||
|   } | ||||
| @ -200,8 +200,8 @@ class LibraryItemController { | ||||
| 
 | ||||
|   // POST: api/items/:id/cover
 | ||||
|   async uploadCover(req, res, updateAndReturnJson = true) { | ||||
|     if (!req.user.canUpload) { | ||||
|       Logger.warn('User attempted to upload a cover without permission', req.user) | ||||
|     if (!req.userNew.canUpload) { | ||||
|       Logger.warn(`User "${req.userNew.username}" attempted to upload a cover without permission`) | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
| 
 | ||||
| @ -299,7 +299,7 @@ class LibraryItemController { | ||||
|     } | ||||
| 
 | ||||
|     // Check if user can access this library item
 | ||||
|     if (!req.user.checkCanAccessLibraryItemWithData(libraryItem.libraryId, libraryItem.media.explicit, libraryItem.media.tags)) { | ||||
|     if (!req.userNew.checkCanAccessLibraryItem(libraryItem)) { | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
| 
 | ||||
| @ -387,8 +387,8 @@ class LibraryItemController { | ||||
|    * @param {import('express').Response} res | ||||
|    */ | ||||
|   async batchDelete(req, res) { | ||||
|     if (!req.user.canDelete) { | ||||
|       Logger.warn(`[LibraryItemController] User attempted to delete without permission`, req.user) | ||||
|     if (!req.userNew.canDelete) { | ||||
|       Logger.warn(`[LibraryItemController] User "${req.userNew.username}" attempted to delete without permission`) | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
|     const hardDelete = req.query.hard == 1 // Delete files from filesystem
 | ||||
| @ -486,8 +486,8 @@ class LibraryItemController { | ||||
| 
 | ||||
|   // POST: api/items/batch/quickmatch
 | ||||
|   async batchQuickMatch(req, res) { | ||||
|     if (!req.user.isAdminOrUp) { | ||||
|       Logger.warn('User other than admin attempted to batch quick match library items', req.user) | ||||
|     if (!req.userNew.isAdminOrUp) { | ||||
|       Logger.warn(`Non-admin user "${req.userNew.username}" other than admin attempted to batch quick match library items`) | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
| 
 | ||||
| @ -522,13 +522,13 @@ class LibraryItemController { | ||||
|       updates: itemsUpdated, | ||||
|       unmatched: itemsUnmatched | ||||
|     } | ||||
|     SocketAuthority.clientEmitter(req.user.id, 'batch_quickmatch_complete', result) | ||||
|     SocketAuthority.clientEmitter(req.userNew.id, 'batch_quickmatch_complete', result) | ||||
|   } | ||||
| 
 | ||||
|   // POST: api/items/batch/scan
 | ||||
|   async batchScan(req, res) { | ||||
|     if (!req.user.isAdminOrUp) { | ||||
|       Logger.warn('User other than admin attempted to batch scan library items', req.user) | ||||
|     if (!req.userNew.isAdminOrUp) { | ||||
|       Logger.warn(`Non-admin user "${req.userNew.username}" other than admin attempted to batch scan library items`) | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
| 
 | ||||
| @ -562,8 +562,8 @@ class LibraryItemController { | ||||
| 
 | ||||
|   // POST: api/items/:id/scan
 | ||||
|   async scan(req, res) { | ||||
|     if (!req.user.isAdminOrUp) { | ||||
|       Logger.error(`[LibraryItemController] Non-admin user attempted to scan library item`, req.user) | ||||
|     if (!req.userNew.isAdminOrUp) { | ||||
|       Logger.error(`[LibraryItemController] Non-admin user "${req.userNew.username}" attempted to scan library item`) | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
| 
 | ||||
| @ -580,8 +580,8 @@ class LibraryItemController { | ||||
|   } | ||||
| 
 | ||||
|   getMetadataObject(req, res) { | ||||
|     if (!req.user.isAdminOrUp) { | ||||
|       Logger.error(`[LibraryItemController] Non-admin user attempted to get metadata object`, req.user) | ||||
|     if (!req.userNew.isAdminOrUp) { | ||||
|       Logger.error(`[LibraryItemController] Non-admin user "${req.userNew.username}" attempted to get metadata object`) | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
| 
 | ||||
| @ -595,8 +595,8 @@ class LibraryItemController { | ||||
| 
 | ||||
|   // POST: api/items/:id/chapters
 | ||||
|   async updateMediaChapters(req, res) { | ||||
|     if (!req.user.canUpdate) { | ||||
|       Logger.error(`[LibraryItemController] User attempted to update chapters with invalid permissions`, req.user.username) | ||||
|     if (!req.userNew.canUpdate) { | ||||
|       Logger.error(`[LibraryItemController] User "${req.userNew.username}" attempted to update chapters with invalid permissions`) | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
| 
 | ||||
| @ -631,8 +631,8 @@ class LibraryItemController { | ||||
|    * @param {express.Response} res | ||||
|    */ | ||||
|   async getFFprobeData(req, res) { | ||||
|     if (!req.user.isAdminOrUp) { | ||||
|       Logger.error(`[LibraryItemController] Non-admin user attempted to get ffprobe data`, req.user) | ||||
|     if (!req.userNew.isAdminOrUp) { | ||||
|       Logger.error(`[LibraryItemController] Non-admin user "${req.userNew.username}" attempted to get ffprobe data`) | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
|     if (req.libraryFile.fileType !== 'audio') { | ||||
| @ -682,7 +682,7 @@ class LibraryItemController { | ||||
|   async deleteLibraryFile(req, res) { | ||||
|     const libraryFile = req.libraryFile | ||||
| 
 | ||||
|     Logger.info(`[LibraryItemController] User "${req.user.username}" requested file delete at "${libraryFile.metadata.path}"`) | ||||
|     Logger.info(`[LibraryItemController] User "${req.userNew.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) | ||||
| @ -710,12 +710,12 @@ class LibraryItemController { | ||||
|   async downloadLibraryFile(req, res) { | ||||
|     const libraryFile = req.libraryFile | ||||
| 
 | ||||
|     if (!req.user.canDownload) { | ||||
|       Logger.error(`[LibraryItemController] User without download permission attempted to download file "${libraryFile.metadata.path}"`, req.user) | ||||
|     if (!req.userNew.canDownload) { | ||||
|       Logger.error(`[LibraryItemController] User "${req.userNew.username}" without download permission attempted to download file "${libraryFile.metadata.path}"`) | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
| 
 | ||||
|     Logger.info(`[LibraryItemController] User "${req.user.username}" requested download for item "${req.libraryItem.media.metadata.title}" file at "${libraryFile.metadata.path}"`) | ||||
|     Logger.info(`[LibraryItemController] User "${req.userNew.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) | ||||
| @ -759,7 +759,7 @@ class LibraryItemController { | ||||
|     } | ||||
|     const ebookFilePath = ebookFile.metadata.path | ||||
| 
 | ||||
|     Logger.info(`[LibraryItemController] User "${req.user.username}" requested download for item "${req.libraryItem.media.metadata.title}" ebook at "${ebookFilePath}"`) | ||||
|     Logger.info(`[LibraryItemController] User "${req.userNew.username}" requested download for item "${req.libraryItem.media.metadata.title}" ebook at "${ebookFilePath}"`) | ||||
| 
 | ||||
|     if (global.XAccel) { | ||||
|       const encodedURI = encodeUriPath(global.XAccel + ebookFilePath) | ||||
| @ -812,7 +812,7 @@ class LibraryItemController { | ||||
|     if (!req.libraryItem?.media) return res.sendStatus(404) | ||||
| 
 | ||||
|     // Check user can access this library item
 | ||||
|     if (!req.user.checkCanAccessLibraryItem(req.libraryItem)) { | ||||
|     if (!req.userNew.checkCanAccessLibraryItem(req.libraryItem)) { | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
| 
 | ||||
| @ -827,11 +827,11 @@ class LibraryItemController { | ||||
| 
 | ||||
|     if (req.path.includes('/play')) { | ||||
|       // allow POST requests using /play and /play/:episodeId
 | ||||
|     } else if (req.method == 'DELETE' && !req.user.canDelete) { | ||||
|       Logger.warn(`[LibraryItemController] User attempted to delete without permission`, req.user) | ||||
|     } else if (req.method == 'DELETE' && !req.userNew.canDelete) { | ||||
|       Logger.warn(`[LibraryItemController] User "${req.userNew.username}" attempted to delete without permission`) | ||||
|       return res.sendStatus(403) | ||||
|     } else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) { | ||||
|       Logger.warn('[LibraryItemController] User attempted to update without permission', req.user.username) | ||||
|       Logger.warn(`[LibraryItemController] User "${req.userNew.username}" attempted to update without permission`) | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -25,7 +25,7 @@ class PlaylistController { | ||||
|     const newPlaylist = await Database.playlistModel.createFromOld(oldPlaylist) | ||||
| 
 | ||||
|     // Lookup all library items in playlist
 | ||||
|     const libraryItemIds = oldPlaylist.items.map(i => i.libraryItemId).filter(i => i) | ||||
|     const libraryItemIds = oldPlaylist.items.map((i) => i.libraryItemId).filter((i) => i) | ||||
|     const libraryItemsInPlaylist = await Database.libraryItemModel.findAll({ | ||||
|       where: { | ||||
|         id: libraryItemIds | ||||
| @ -36,7 +36,7 @@ class PlaylistController { | ||||
|     const mediaItemsToAdd = [] | ||||
|     let order = 1 | ||||
|     for (const mediaItemObj of oldPlaylist.items) { | ||||
|       const libraryItem = libraryItemsInPlaylist.find(li => li.id === mediaItemObj.libraryItemId) | ||||
|       const libraryItem = libraryItemsInPlaylist.find((li) => li.id === mediaItemObj.libraryItemId) | ||||
|       if (!libraryItem) continue | ||||
| 
 | ||||
|       mediaItemsToAdd.push({ | ||||
| @ -104,7 +104,7 @@ class PlaylistController { | ||||
|     } | ||||
| 
 | ||||
|     // If array of items is passed in then update order of playlist media items
 | ||||
|     const libraryItemIds = req.body.items?.map(i => i.libraryItemId).filter(i => i) || [] | ||||
|     const libraryItemIds = req.body.items?.map((i) => i.libraryItemId).filter((i) => i) || [] | ||||
|     if (libraryItemIds.length) { | ||||
|       const libraryItems = await Database.libraryItemModel.findAll({ | ||||
|         where: { | ||||
| @ -118,7 +118,7 @@ class PlaylistController { | ||||
|       // Set an array of mediaItemId
 | ||||
|       const newMediaItemIdOrder = [] | ||||
|       for (const item of req.body.items) { | ||||
|         const libraryItem = libraryItems.find(li => li.id === item.libraryItemId) | ||||
|         const libraryItem = libraryItems.find((li) => li.id === item.libraryItemId) | ||||
|         if (!libraryItem) { | ||||
|           continue | ||||
|         } | ||||
| @ -128,8 +128,8 @@ class PlaylistController { | ||||
| 
 | ||||
|       // Sort existing playlist media items into new order
 | ||||
|       existingPlaylistMediaItems.sort((a, b) => { | ||||
|         const aIndex = newMediaItemIdOrder.findIndex(i => i === a.mediaItemId) | ||||
|         const bIndex = newMediaItemIdOrder.findIndex(i => i === b.mediaItemId) | ||||
|         const aIndex = newMediaItemIdOrder.findIndex((i) => i === a.mediaItemId) | ||||
|         const bIndex = newMediaItemIdOrder.findIndex((i) => i === b.mediaItemId) | ||||
|         return aIndex - bIndex | ||||
|       }) | ||||
| 
 | ||||
| @ -229,7 +229,7 @@ class PlaylistController { | ||||
|     }) | ||||
| 
 | ||||
|     // Check if media item to delete is in playlist
 | ||||
|     const mediaItemToRemove = playlistMediaItems.find(pmi => pmi.mediaItemId === mediaItemId) | ||||
|     const mediaItemToRemove = playlistMediaItems.find((pmi) => pmi.mediaItemId === mediaItemId) | ||||
|     if (!mediaItemToRemove) { | ||||
|       return res.status(404).send('Media item not found in playlist') | ||||
|     } | ||||
| @ -275,7 +275,7 @@ class PlaylistController { | ||||
|     } | ||||
|     const itemsToAdd = req.body.items | ||||
| 
 | ||||
|     const libraryItemIds = itemsToAdd.map(i => i.libraryItemId).filter(i => i) | ||||
|     const libraryItemIds = itemsToAdd.map((i) => i.libraryItemId).filter((i) => i) | ||||
|     if (!libraryItemIds.length) { | ||||
|       return res.status(400).send('Invalid request body') | ||||
|     } | ||||
| @ -297,12 +297,12 @@ class PlaylistController { | ||||
|     // Setup array of playlistMediaItem records to add
 | ||||
|     let order = existingPlaylistMediaItems.length + 1 | ||||
|     for (const item of itemsToAdd) { | ||||
|       const libraryItem = libraryItems.find(li => li.id === item.libraryItemId) | ||||
|       const libraryItem = libraryItems.find((li) => li.id === item.libraryItemId) | ||||
|       if (!libraryItem) { | ||||
|         return res.status(404).send('Item not found with id ' + item.libraryItemId) | ||||
|       } else { | ||||
|         const mediaItemId = item.episodeId || libraryItem.mediaId | ||||
|         if (existingPlaylistMediaItems.some(pmi => pmi.mediaItemId === mediaItemId)) { | ||||
|         if (existingPlaylistMediaItems.some((pmi) => pmi.mediaItemId === mediaItemId)) { | ||||
|           // Already exists in playlist
 | ||||
|           continue | ||||
|         } else { | ||||
| @ -339,7 +339,7 @@ class PlaylistController { | ||||
|     } | ||||
| 
 | ||||
|     const itemsToRemove = req.body.items | ||||
|     const libraryItemIds = itemsToRemove.map(i => i.libraryItemId).filter(i => i) | ||||
|     const libraryItemIds = itemsToRemove.map((i) => i.libraryItemId).filter((i) => i) | ||||
|     if (!libraryItemIds.length) { | ||||
|       return res.status(400).send('Invalid request body') | ||||
|     } | ||||
| @ -360,10 +360,10 @@ class PlaylistController { | ||||
|     // Remove playlist media items
 | ||||
|     let hasUpdated = false | ||||
|     for (const item of itemsToRemove) { | ||||
|       const libraryItem = libraryItems.find(li => li.id === item.libraryItemId) | ||||
|       const libraryItem = libraryItems.find((li) => li.id === item.libraryItemId) | ||||
|       if (!libraryItem) continue | ||||
|       const mediaItemId = item.episodeId || libraryItem.mediaId | ||||
|       const existingMediaItem = existingPlaylistMediaItems.find(pmi => pmi.mediaItemId === mediaItemId) | ||||
|       const existingMediaItem = existingPlaylistMediaItems.find((pmi) => pmi.mediaItemId === mediaItemId) | ||||
|       if (!existingMediaItem) continue | ||||
|       await existingMediaItem.destroy() | ||||
|       hasUpdated = true | ||||
| @ -396,7 +396,7 @@ class PlaylistController { | ||||
|       return res.status(404).send('Collection not found') | ||||
|     } | ||||
|     // Expand collection to get library items
 | ||||
|     const collectionExpanded = await collection.getOldJsonExpanded(req.user) | ||||
|     const collectionExpanded = await collection.getOldJsonExpanded(req.userNew) | ||||
|     if (!collectionExpanded) { | ||||
|       // This can happen if the user has no access to all items in collection
 | ||||
|       return res.status(404).send('Collection not found') | ||||
|  | ||||
| @ -17,20 +17,23 @@ class SeriesController { | ||||
|    * @param {*} res | ||||
|    */ | ||||
|   async findOne(req, res) { | ||||
|     const include = (req.query.include || '').split(',').map(v => v.trim()).filter(v => !!v) | ||||
|     const include = (req.query.include || '') | ||||
|       .split(',') | ||||
|       .map((v) => v.trim()) | ||||
|       .filter((v) => !!v) | ||||
| 
 | ||||
|     const seriesJson = req.series.toJSON() | ||||
| 
 | ||||
|     // Add progress map with isFinished flag
 | ||||
|     if (include.includes('progress')) { | ||||
|       const libraryItemsInSeries = req.libraryItemsInSeries | ||||
|       const libraryItemsFinished = libraryItemsInSeries.filter(li => { | ||||
|       const libraryItemsFinished = libraryItemsInSeries.filter((li) => { | ||||
|         const mediaProgress = req.user.getMediaProgress(li.id) | ||||
|         return mediaProgress?.isFinished | ||||
|       }) | ||||
|       seriesJson.progress = { | ||||
|         libraryItemIds: libraryItemsInSeries.map(li => li.id), | ||||
|         libraryItemIdsFinished: libraryItemsFinished.map(li => li.id), | ||||
|         libraryItemIds: libraryItemsInSeries.map((li) => li.id), | ||||
|         libraryItemIdsFinished: libraryItemsFinished.map((li) => li.id), | ||||
|         isFinished: libraryItemsFinished.length === libraryItemsInSeries.length | ||||
|       } | ||||
|     } | ||||
| @ -59,7 +62,7 @@ class SeriesController { | ||||
|     /** | ||||
|      * Filter out any library items not accessible to user | ||||
|      */ | ||||
|     const libraryItems = await libraryItemsBookFilters.getLibraryItemsForSeries(series, req.user) | ||||
|     const libraryItems = await libraryItemsBookFilters.getLibraryItemsForSeries(series, req.userNew) | ||||
|     if (!libraryItems.length) { | ||||
|       Logger.warn(`[SeriesController] User attempted to access series "${series.id}" with no accessible books`, req.user) | ||||
|       return res.sendStatus(404) | ||||
|  | ||||
| @ -22,7 +22,8 @@ class Collection extends Model { | ||||
| 
 | ||||
|   /** | ||||
|    * Get all old collections toJSONExpanded, items filtered for user permissions | ||||
|    * @param {oldUser} [user] | ||||
|    * | ||||
|    * @param {import('./User')} user | ||||
|    * @param {string} [libraryId] | ||||
|    * @param {string[]} [include] | ||||
|    * @returns {Promise<oldCollection[]>} oldCollection.toJSONExpanded | ||||
| @ -116,7 +117,8 @@ class Collection extends Model { | ||||
| 
 | ||||
|   /** | ||||
|    * Get old collection toJSONExpanded, items filtered for user permissions | ||||
|    * @param {oldUser} [user] | ||||
|    * | ||||
|    * @param {import('./User')|null} user | ||||
|    * @param {string[]} [include] | ||||
|    * @returns {Promise<oldCollection>} oldCollection.toJSONExpanded | ||||
|    */ | ||||
|  | ||||
| @ -543,7 +543,7 @@ class LibraryItem extends Model { | ||||
|   /** | ||||
|    * Get library items using filter and sort | ||||
|    * @param {oldLibrary} library | ||||
|    * @param {oldUser} user | ||||
|    * @param {import('./User')} user | ||||
|    * @param {object} options | ||||
|    * @returns {{ libraryItems:oldLibraryItem[], count:number }} | ||||
|    */ | ||||
| @ -586,7 +586,7 @@ class LibraryItem extends Model { | ||||
|   /** | ||||
|    * Get home page data personalized shelves | ||||
|    * @param {oldLibrary} library | ||||
|    * @param {oldUser} user | ||||
|    * @param {import('./User')} user | ||||
|    * @param {string[]} include | ||||
|    * @param {number} limit | ||||
|    * @returns {object[]} array of shelf objects | ||||
| @ -759,7 +759,7 @@ class LibraryItem extends Model { | ||||
|   /** | ||||
|    * Get book library items for author, optional use user permissions | ||||
|    * @param {oldAuthor} author | ||||
|    * @param {[oldUser]} user | ||||
|    * @param {import('./User')} user | ||||
|    * @returns {Promise<oldLibraryItem[]>} | ||||
|    */ | ||||
|   static async getForAuthor(author, user = null) { | ||||
|  | ||||
| @ -414,6 +414,21 @@ class User extends Model { | ||||
|   get isUser() { | ||||
|     return this.type === 'user' | ||||
|   } | ||||
|   get canAccessExplicitContent() { | ||||
|     return !!this.permissions?.accessExplicitContent && this.isActive | ||||
|   } | ||||
|   get canDelete() { | ||||
|     return !!this.permissions?.delete && this.isActive | ||||
|   } | ||||
|   get canUpdate() { | ||||
|     return !!this.permissions?.update && this.isActive | ||||
|   } | ||||
|   get canDownload() { | ||||
|     return !!this.permissions?.download && this.isActive | ||||
|   } | ||||
|   get canUpload() { | ||||
|     return !!this.permissions?.upload && this.isActive | ||||
|   } | ||||
|   /** @type {string|null} */ | ||||
|   get authOpenIDSub() { | ||||
|     return this.extraData?.authOpenIDSub || null | ||||
| @ -490,6 +505,40 @@ class User extends Model { | ||||
|     return this.permissions.librariesAccessible.includes(libraryId) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Check user has access to library item with tags | ||||
|    * | ||||
|    * @param {string[]} tags | ||||
|    * @returns {boolean} | ||||
|    */ | ||||
|   checkCanAccessLibraryItemWithTags(tags) { | ||||
|     if (this.permissions.accessAllTags) return true | ||||
|     const itemTagsSelected = this.permissions?.itemTagsSelected || [] | ||||
|     if (this.permissions.selectedTagsNotAccessible) { | ||||
|       if (!tags?.length) return true | ||||
|       return tags.every((tag) => !itemTagsSelected?.includes(tag)) | ||||
|     } | ||||
|     if (!tags?.length) return false | ||||
|     return itemTagsSelected.some((tag) => tags.includes(tag)) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Check user can access library item | ||||
|    * TODO: Currently supports both old and new library item models | ||||
|    * | ||||
|    * @param {import('../objects/LibraryItem')|import('./LibraryItem')} libraryItem | ||||
|    * @returns {boolean} | ||||
|    */ | ||||
|   checkCanAccessLibraryItem(libraryItem) { | ||||
|     if (!this.checkCanAccessLibrary(libraryItem.libraryId)) return false | ||||
| 
 | ||||
|     const libraryItemExplicit = !!libraryItem.media.explicit || !!libraryItem.media.metadata?.explicit | ||||
| 
 | ||||
|     if (libraryItemExplicit && !this.canAccessExplicitContent) return false | ||||
| 
 | ||||
|     return this.checkCanAccessLibraryItemWithTags(libraryItem.media.tags) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Get first available library id for user | ||||
|    * | ||||
| @ -500,6 +549,34 @@ class User extends Model { | ||||
|     // Libraries should already be in ascending display order, find first accessible
 | ||||
|     return libraryIds.find((lid) => this.checkCanAccessLibrary(lid)) || null | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Get media progress by media item id | ||||
|    * | ||||
|    * @param {string} libraryItemId | ||||
|    * @param {string|null} [episodeId] | ||||
|    * @returns {import('./MediaProgress')|null} | ||||
|    */ | ||||
|   getMediaProgress(mediaItemId) { | ||||
|     if (!this.mediaProgresses?.length) return null | ||||
|     return this.mediaProgresses.find((mp) => mp.mediaItemId === mediaItemId) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Get old media progress | ||||
|    * TODO: Update to new model | ||||
|    * | ||||
|    * @param {string} libraryItemId | ||||
|    * @param {string} [episodeId] | ||||
|    * @returns | ||||
|    */ | ||||
|   getOldMediaProgress(libraryItemId, episodeId = null) { | ||||
|     const mediaProgress = this.mediaProgresses?.find((mp) => { | ||||
|       if (episodeId && mp.mediaItemId === episodeId) return true | ||||
|       return mp.extraData?.libraryItemId === libraryItemId | ||||
|     }) | ||||
|     return mediaProgress?.getOldMediaProgress() || null | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| module.exports = User | ||||
|  | ||||
| @ -11,7 +11,7 @@ module.exports = { | ||||
|     const seriesToFilterOut = {} | ||||
|     books.forEach((libraryItem) => { | ||||
|       // get all book series for item that is not already filtered out
 | ||||
|       const bookSeries = (libraryItem.media.metadata.series || []).filter(se => !seriesToFilterOut[se.id]) | ||||
|       const bookSeries = (libraryItem.media.metadata.series || []).filter((se) => !seriesToFilterOut[se.id]) | ||||
|       if (!bookSeries.length) return | ||||
| 
 | ||||
|       bookSeries.forEach((bookSeriesObj) => { | ||||
| @ -43,11 +43,11 @@ module.exports = { | ||||
| 
 | ||||
|     // Library setting to hide series with only 1 book
 | ||||
|     if (hideSingleBookSeries) { | ||||
|       seriesItems = seriesItems.filter(se => se.books.length > 1) | ||||
|       seriesItems = seriesItems.filter((se) => se.books.length > 1) | ||||
|     } | ||||
| 
 | ||||
|     return seriesItems.map((series) => { | ||||
|       series.books = naturalSort(series.books).asc(li => li.sequence) | ||||
|       series.books = naturalSort(series.books).asc((li) => li.sequence) | ||||
|       return series | ||||
|     }) | ||||
|   }, | ||||
| @ -55,9 +55,7 @@ module.exports = { | ||||
|   collapseBookSeries(libraryItems, filterSeries, hideSingleBookSeries) { | ||||
|     // Get series from the library items. If this list is being collapsed after filtering for a series,
 | ||||
|     // don't collapse that series, only books that are in other series.
 | ||||
|     const seriesObjects = this | ||||
|       .getSeriesFromBooks(libraryItems, filterSeries, hideSingleBookSeries) | ||||
|       .filter(s => s.id != filterSeries) | ||||
|     const seriesObjects = this.getSeriesFromBooks(libraryItems, filterSeries, hideSingleBookSeries).filter((s) => s.id != filterSeries) | ||||
| 
 | ||||
|     const filteredLibraryItems = [] | ||||
| 
 | ||||
| @ -65,22 +63,29 @@ module.exports = { | ||||
|       if (li.mediaType != 'book') return | ||||
| 
 | ||||
|       // Handle when this is the first book in a series
 | ||||
|       seriesObjects.filter(s => s.books[0].id == li.id).forEach(series => { | ||||
|       seriesObjects | ||||
|         .filter((s) => s.books[0].id == li.id) | ||||
|         .forEach((series) => { | ||||
|           // Clone the library item as we need to attach data to it, but don't
 | ||||
|           // want to change the global copy of the library item
 | ||||
|         filteredLibraryItems.push(Object.assign( | ||||
|           Object.create(Object.getPrototypeOf(li)), | ||||
|           li, { collapsedSeries: series })) | ||||
|           filteredLibraryItems.push(Object.assign(Object.create(Object.getPrototypeOf(li)), li, { collapsedSeries: series })) | ||||
|         }) | ||||
| 
 | ||||
|       // Only included books not contained in series
 | ||||
|       if (!seriesObjects.some(s => s.books.some(b => b.id == li.id))) | ||||
|         filteredLibraryItems.push(li) | ||||
|       if (!seriesObjects.some((s) => s.books.some((b) => b.id == li.id))) filteredLibraryItems.push(li) | ||||
|     }) | ||||
| 
 | ||||
|     return filteredLibraryItems | ||||
|   }, | ||||
| 
 | ||||
|   /** | ||||
|    * | ||||
|    * @param {*} payload | ||||
|    * @param {string} seriesId | ||||
|    * @param {import('../models/User')} user | ||||
|    * @param {import('../objects/Library')} library | ||||
|    * @returns {Object[]} | ||||
|    */ | ||||
|   async handleCollapseSubseries(payload, seriesId, user, library) { | ||||
|     const seriesWithBooks = await Database.seriesModel.findByPk(seriesId, { | ||||
|       include: { | ||||
| @ -112,15 +117,16 @@ module.exports = { | ||||
|       return [] | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     const books = seriesWithBooks.books | ||||
|     payload.total = books.length | ||||
| 
 | ||||
|     let libraryItems = books.map((book) => { | ||||
|     let libraryItems = books | ||||
|       .map((book) => { | ||||
|         const libraryItem = book.libraryItem | ||||
|         libraryItem.media = book | ||||
|         return Database.libraryItemModel.getOldLibraryItem(libraryItem) | ||||
|     }).filter(li => { | ||||
|       }) | ||||
|       .filter((li) => { | ||||
|         return user.checkCanAccessLibraryItem(li) | ||||
|       }) | ||||
| 
 | ||||
| @ -139,7 +145,8 @@ module.exports = { | ||||
|         { | ||||
|           [direction]: (li) => li.media.metadata.getSeries(seriesId).sequence | ||||
|         }, | ||||
|         { // If no series sequence then fallback to sorting by title (or collapsed series name for sub-series)
 | ||||
|         { | ||||
|           // If no series sequence then fallback to sorting by title (or collapsed series name for sub-series)
 | ||||
|           [direction]: (li) => { | ||||
|             if (sortingIgnorePrefix) { | ||||
|               return li.collapsedSeries?.nameIgnorePrefix || li.media.metadata.titleIgnorePrefix | ||||
| @ -185,7 +192,8 @@ module.exports = { | ||||
|       libraryItems = libraryItems.slice(startIndex, startIndex + payload.limit) | ||||
|     } | ||||
| 
 | ||||
|     return Promise.all(libraryItems.map(async li => { | ||||
|     return Promise.all( | ||||
|       libraryItems.map(async (li) => { | ||||
|         const filteredSeries = li.media.metadata.getSeries(seriesId) | ||||
|         const json = li.toJSONMinified() | ||||
|         json.media.metadata.series = { | ||||
| @ -199,33 +207,33 @@ module.exports = { | ||||
|             id: li.collapsedSeries.id, | ||||
|             name: li.collapsedSeries.name, | ||||
|             nameIgnorePrefix: li.collapsedSeries.nameIgnorePrefix, | ||||
|           libraryItemIds: li.collapsedSeries.books.map(b => b.id), | ||||
|             libraryItemIds: li.collapsedSeries.books.map((b) => b.id), | ||||
|             numBooks: li.collapsedSeries.books.length | ||||
|           } | ||||
| 
 | ||||
|           // If collapsing by series and filtering by a series, generate the list of sequences the collapsed
 | ||||
|           // series represents in the filtered series
 | ||||
|         json.collapsedSeries.seriesSequenceList = | ||||
|           naturalSort(li.collapsedSeries.books.filter(b => b.filterSeriesSequence).map(b => b.filterSeriesSequence)).asc() | ||||
|           json.collapsedSeries.seriesSequenceList = naturalSort(li.collapsedSeries.books.filter((b) => b.filterSeriesSequence).map((b) => b.filterSeriesSequence)) | ||||
|             .asc() | ||||
|             .reduce((ranges, currentSequence) => { | ||||
|               let lastRange = ranges.at(-1) | ||||
|               let isNumber = /^(\d+|\d+\.\d*|\d*\.\d+)$/.test(currentSequence) | ||||
|               if (isNumber) currentSequence = parseFloat(currentSequence) | ||||
| 
 | ||||
|               if (lastRange && isNumber && lastRange.isNumber && ((lastRange.end + 1) == currentSequence)) { | ||||
|               if (lastRange && isNumber && lastRange.isNumber && lastRange.end + 1 == currentSequence) { | ||||
|                 lastRange.end = currentSequence | ||||
|               } | ||||
|               else { | ||||
|               } else { | ||||
|                 ranges.push({ start: currentSequence, end: currentSequence, isNumber: isNumber }) | ||||
|               } | ||||
| 
 | ||||
|               return ranges | ||||
|             }, []) | ||||
|             .map(r => r.start == r.end ? r.start : `${r.start}-${r.end}`) | ||||
|             .map((r) => (r.start == r.end ? r.start : `${r.start}-${r.end}`)) | ||||
|             .join(', ') | ||||
|         } | ||||
| 
 | ||||
|         return json | ||||
|     })) | ||||
|       }) | ||||
|     ) | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -16,7 +16,7 @@ module.exports = { | ||||
|   /** | ||||
|    * Get library items using filter and sort | ||||
|    * @param {import('../../objects/Library')} library | ||||
|    * @param {import('../../objects/user/User')} user | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {object} options | ||||
|    * @returns {object} { libraryItems:LibraryItem[], count:number } | ||||
|    */ | ||||
| @ -42,7 +42,7 @@ module.exports = { | ||||
|   /** | ||||
|    * Get library items for continue listening & continue reading shelves | ||||
|    * @param {import('../../objects/Library')} library | ||||
|    * @param {import('../../objects/user/User')} user | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {string[]} include | ||||
|    * @param {number} limit | ||||
|    * @returns {Promise<{ items:import('../../models/LibraryItem')[], count:number }>} | ||||
| @ -79,7 +79,7 @@ module.exports = { | ||||
|   /** | ||||
|    * Get library items for most recently added shelf | ||||
|    * @param {import('../../objects/Library')} library | ||||
|    * @param {oldUser} user | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {string[]} include | ||||
|    * @param {number} limit | ||||
|    * @returns {object} { libraryItems:LibraryItem[], count:number } | ||||
| @ -127,7 +127,7 @@ module.exports = { | ||||
|   /** | ||||
|    * Get library items for continue series shelf | ||||
|    * @param {import('../../objects/Library')} library | ||||
|    * @param {oldUser} user | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {string[]} include | ||||
|    * @param {number} limit | ||||
|    * @returns {object} { libraryItems:LibraryItem[], count:number } | ||||
| @ -155,7 +155,7 @@ module.exports = { | ||||
|   /** | ||||
|    * Get library items or podcast episodes for the "Listen Again" and "Read Again" shelf | ||||
|    * @param {import('../../objects/Library')} library | ||||
|    * @param {oldUser} user | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {string[]} include | ||||
|    * @param {number} limit | ||||
|    * @returns {object} { items:object[], count:number } | ||||
| @ -192,7 +192,7 @@ module.exports = { | ||||
|   /** | ||||
|    * Get series for recent series shelf | ||||
|    * @param {import('../../objects/Library')} library | ||||
|    * @param {import('../../objects/user/User')} user | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {string[]} include | ||||
|    * @param {number} limit | ||||
|    * @returns {{ series:import('../../objects/entities/Series')[], count:number}} | ||||
| @ -235,7 +235,7 @@ module.exports = { | ||||
|       if (!user.canAccessExplicitContent) { | ||||
|         attrQuery += ' AND b.explicit = 0' | ||||
|       } | ||||
|       if (!user.permissions.accessAllTags && user.itemTagsSelected.length) { | ||||
|       if (!user.permissions?.accessAllTags && user.permissions?.itemTagsSelected?.length) { | ||||
|         if (user.permissions.selectedTagsNotAccessible) { | ||||
|           attrQuery += ' AND (SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected)) = 0' | ||||
|         } else { | ||||
| @ -317,7 +317,7 @@ module.exports = { | ||||
|    * Get most recently created authors for "Newest Authors" shelf | ||||
|    * Author must be linked to at least 1 book | ||||
|    * @param {oldLibrary} library | ||||
|    * @param {oldUser} user | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {number} limit | ||||
|    * @returns {object} { authors:oldAuthor[], count:number } | ||||
|    */ | ||||
| @ -360,7 +360,7 @@ module.exports = { | ||||
|   /** | ||||
|    * Get book library items for the "Discover" shelf | ||||
|    * @param {oldLibrary} library | ||||
|    * @param {oldUser} user | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {string[]} include | ||||
|    * @param {number} limit | ||||
|    * @returns {object} {libraryItems:oldLibraryItem[], count:number} | ||||
| @ -387,7 +387,7 @@ module.exports = { | ||||
|   /** | ||||
|    * Get podcast episodes most recently added | ||||
|    * @param {oldLibrary} library | ||||
|    * @param {oldUser} user | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {number} limit | ||||
|    * @returns {object} {libraryItems:oldLibraryItem[], count:number} | ||||
|    */ | ||||
| @ -408,7 +408,7 @@ module.exports = { | ||||
|   /** | ||||
|    * Get library items for an author, optional use user permissions | ||||
|    * @param {oldAuthor} author | ||||
|    * @param {[oldUser]} user | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {number} limit | ||||
|    * @param {number} offset | ||||
|    * @returns {Promise<object>} { libraryItems:LibraryItem[], count:number } | ||||
|  | ||||
| @ -172,17 +172,17 @@ module.exports = { | ||||
| 
 | ||||
|   /** | ||||
|    * Search library items | ||||
|    * @param {import('../../objects/user/User')} oldUser  | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {import('../../objects/Library')} oldLibrary | ||||
|    * @param {string} query | ||||
|    * @param {number} limit | ||||
|    * @returns {{book:object[], narrators:object[], authors:object[], tags:object[], series:object[], podcast:object[]}} | ||||
|    */ | ||||
|   search(oldUser, oldLibrary, query, limit) { | ||||
|   search(user, oldLibrary, query, limit) { | ||||
|     if (oldLibrary.isBook) { | ||||
|       return libraryItemsBookFilters.search(oldUser, oldLibrary, query, limit, 0) | ||||
|       return libraryItemsBookFilters.search(user, oldLibrary, query, limit, 0) | ||||
|     } else { | ||||
|       return libraryItemsPodcastFilters.search(oldUser, oldLibrary, query, limit, 0) | ||||
|       return libraryItemsPodcastFilters.search(user, oldLibrary, query, limit, 0) | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
| @ -208,12 +208,10 @@ module.exports = { | ||||
|           attributes: ['id', 'title'] | ||||
|         } | ||||
|       ], | ||||
|       order: [ | ||||
|         ['size', 'DESC'] | ||||
|       ], | ||||
|       order: [['size', 'DESC']], | ||||
|       limit | ||||
|     }) | ||||
|     return libraryItems.map(libraryItem => { | ||||
|     return libraryItems.map((libraryItem) => { | ||||
|       return { | ||||
|         id: libraryItem.id, | ||||
|         title: libraryItem.media.title, | ||||
|  | ||||
| @ -8,7 +8,7 @@ const ShareManager = require('../../managers/ShareManager') | ||||
| module.exports = { | ||||
|   /** | ||||
|    * User permissions to restrict books for explicit content & tags | ||||
|    * @param {import('../../objects/user/User')} user | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @returns {{ bookWhere:Sequelize.WhereOptions, replacements:object }} | ||||
|    */ | ||||
|   getUserPermissionBookWhereQuery(user) { | ||||
| @ -21,8 +21,8 @@ module.exports = { | ||||
|         explicit: false | ||||
|       }) | ||||
|     } | ||||
|     if (!user.permissions.accessAllTags && user.itemTagsSelected.length) { | ||||
|       replacements['userTagsSelected'] = user.itemTagsSelected | ||||
|     if (!user.permissions?.accessAllTags && user.permissions?.itemTagsSelected?.length) { | ||||
|       replacements['userTagsSelected'] = user.permissions.itemTagsSelected | ||||
|       if (user.permissions.selectedTagsNotAccessible) { | ||||
|         bookWhere.push(Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), 0)) | ||||
|       } else { | ||||
| @ -333,7 +333,7 @@ module.exports = { | ||||
|   /** | ||||
|    * Get library items for book media type using filter and sort | ||||
|    * @param {string} libraryId | ||||
|    * @param {import('../../objects/user/User')} user | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {string|null} filterGroup | ||||
|    * @param {string|null} filterValue | ||||
|    * @param {string} sortBy | ||||
| @ -637,7 +637,7 @@ module.exports = { | ||||
|    * 3. Has at least 1 unfinished book | ||||
|    * TODO: Reduce queries | ||||
|    * @param {import('../../objects/Library')} library | ||||
|    * @param {import('../../objects/user/User')} user | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {string[]} include | ||||
|    * @param {number} limit | ||||
|    * @param {number} offset | ||||
| @ -672,7 +672,7 @@ module.exports = { | ||||
|       where: [ | ||||
|         { | ||||
|           id: { | ||||
|             [Sequelize.Op.notIn]: user.seriesHideFromContinueListening | ||||
|             [Sequelize.Op.notIn]: user.extraData?.seriesHideFromContinueListening || [] | ||||
|           }, | ||||
|           libraryId | ||||
|         }, | ||||
| @ -780,7 +780,7 @@ module.exports = { | ||||
|    * Random selection of books that are not started | ||||
|    *  - only includes the first book of a not-started series | ||||
|    * @param {string} libraryId | ||||
|    * @param {oldUser} user | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {string[]} include | ||||
|    * @param {number} limit | ||||
|    * @returns {object} {libraryItems:LibraryItem, count:number} | ||||
| @ -955,25 +955,25 @@ module.exports = { | ||||
|   /** | ||||
|    * Get library items for series | ||||
|    * @param {import('../../objects/entities/Series')} oldSeries | ||||
|    * @param {import('../../objects/user/User')} [oldUser] | ||||
|    * @param {import('../../models/User')} [user] | ||||
|    * @returns {Promise<import('../../objects/LibraryItem')[]>} | ||||
|    */ | ||||
|   async getLibraryItemsForSeries(oldSeries, oldUser) { | ||||
|     const { libraryItems } = await this.getFilteredLibraryItems(oldSeries.libraryId, oldUser, 'series', oldSeries.id, null, null, false, [], null, null) | ||||
|   async getLibraryItemsForSeries(oldSeries, user) { | ||||
|     const { libraryItems } = await this.getFilteredLibraryItems(oldSeries.libraryId, user, 'series', oldSeries.id, null, null, false, [], null, null) | ||||
|     return libraryItems.map((li) => Database.libraryItemModel.getOldLibraryItem(li)) | ||||
|   }, | ||||
| 
 | ||||
|   /** | ||||
|    * Search books, authors, series | ||||
|    * @param {import('../../objects/user/User')} oldUser | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {import('../../objects/Library')} oldLibrary | ||||
|    * @param {string} query | ||||
|    * @param {number} limit | ||||
|    * @param {number} offset | ||||
|    * @returns {{book:object[], narrators:object[], authors:object[], tags:object[], series:object[]}} | ||||
|    */ | ||||
|   async search(oldUser, oldLibrary, query, limit, offset) { | ||||
|     const userPermissionBookWhere = this.getUserPermissionBookWhereQuery(oldUser) | ||||
|   async search(user, oldLibrary, query, limit, offset) { | ||||
|     const userPermissionBookWhere = this.getUserPermissionBookWhereQuery(user) | ||||
| 
 | ||||
|     const normalizedQuery = query | ||||
| 
 | ||||
|  | ||||
| @ -1,12 +1,11 @@ | ||||
| const Sequelize = require('sequelize') | ||||
| const Database = require('../../Database') | ||||
| const Logger = require('../../Logger') | ||||
| const { asciiOnlyToLowerCase } = require('../index') | ||||
| 
 | ||||
| module.exports = { | ||||
|   /** | ||||
|    * User permissions to restrict podcasts for explicit content & tags | ||||
|    * @param {import('../../objects/user/User')} user | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @returns {{ podcastWhere:Sequelize.WhereOptions, replacements:object }} | ||||
|    */ | ||||
|   getUserPermissionPodcastWhereQuery(user) { | ||||
| @ -17,18 +16,20 @@ module.exports = { | ||||
|         explicit: false | ||||
|       }) | ||||
|     } | ||||
|     if (!user.permissions.accessAllTags && user.itemTagsSelected.length) { | ||||
|       replacements['userTagsSelected'] = user.itemTagsSelected | ||||
| 
 | ||||
|     if (!user.permissions?.accessAllTags && user.permissions?.itemTagsSelected?.length) { | ||||
|       replacements['userTagsSelected'] = user.permissions.itemTagsSelected | ||||
|       if (user.permissions.selectedTagsNotAccessible) { | ||||
|         podcastWhere.push(Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), 0)) | ||||
|         bookWhere.push(Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), 0)) | ||||
|       } else { | ||||
|         podcastWhere.push( | ||||
|         bookWhere.push( | ||||
|           Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), { | ||||
|             [Sequelize.Op.gte]: 1 | ||||
|           }) | ||||
|         ) | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
|       podcastWhere, | ||||
|       replacements | ||||
| @ -98,7 +99,7 @@ module.exports = { | ||||
|   /** | ||||
|    * Get library items for podcast media type using filter and sort | ||||
|    * @param {string} libraryId | ||||
|    * @param {oldUser} user | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {[string]} filterGroup | ||||
|    * @param {[string]} filterValue | ||||
|    * @param {string} sortBy | ||||
| @ -200,7 +201,7 @@ module.exports = { | ||||
|   /** | ||||
|    * Get podcast episodes filtered and sorted | ||||
|    * @param {string} libraryId | ||||
|    * @param {oldUser} user | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {[string]} filterGroup | ||||
|    * @param {[string]} filterValue | ||||
|    * @param {string} sortBy | ||||
| @ -304,15 +305,15 @@ module.exports = { | ||||
| 
 | ||||
|   /** | ||||
|    * Search podcasts | ||||
|    * @param {import('../../objects/user/User')} oldUser | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {import('../../objects/Library')} oldLibrary | ||||
|    * @param {string} query | ||||
|    * @param {number} limit | ||||
|    * @param {number} offset | ||||
|    * @returns {{podcast:object[], tags:object[]}} | ||||
|    */ | ||||
|   async search(oldUser, oldLibrary, query, limit, offset) { | ||||
|     const userPermissionPodcastWhere = this.getUserPermissionPodcastWhereQuery(oldUser) | ||||
|   async search(user, oldLibrary, query, limit, offset) { | ||||
|     const userPermissionPodcastWhere = this.getUserPermissionPodcastWhereQuery(user) | ||||
| 
 | ||||
|     const normalizedQuery = query | ||||
|     const matchTitle = Database.matchExpression('title', normalizedQuery) | ||||
| @ -410,14 +411,14 @@ module.exports = { | ||||
| 
 | ||||
|   /** | ||||
|    * Most recent podcast episodes not finished | ||||
|    * @param {import('../../objects/user/User')} oldUser | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {import('../../objects/Library')} oldLibrary | ||||
|    * @param {number} limit | ||||
|    * @param {number} offset | ||||
|    * @returns {Promise<object[]>} | ||||
|    */ | ||||
|   async getRecentEpisodes(oldUser, oldLibrary, limit, offset) { | ||||
|     const userPermissionPodcastWhere = this.getUserPermissionPodcastWhereQuery(oldUser) | ||||
|   async getRecentEpisodes(user, oldLibrary, limit, offset) { | ||||
|     const userPermissionPodcastWhere = this.getUserPermissionPodcastWhereQuery(user) | ||||
| 
 | ||||
|     const episodes = await Database.podcastEpisodeModel.findAll({ | ||||
|       where: { | ||||
| @ -441,7 +442,7 @@ module.exports = { | ||||
|         { | ||||
|           model: Database.mediaProgressModel, | ||||
|           where: { | ||||
|             userId: oldUser.id | ||||
|             userId: user.id | ||||
|           }, | ||||
|           required: false | ||||
|         } | ||||
|  | ||||
| @ -12,7 +12,7 @@ module.exports = { | ||||
|    * Get series filtered and sorted | ||||
|    * | ||||
|    * @param {import('../../objects/Library')} library | ||||
|    * @param {import('../../objects/user/User')} user | ||||
|    * @param {import('../../models/User')} user | ||||
|    * @param {string} filterBy | ||||
|    * @param {string} sortBy | ||||
|    * @param {boolean} sortDesc | ||||
| @ -93,7 +93,7 @@ module.exports = { | ||||
|       if (!user.canAccessExplicitContent) { | ||||
|         attrQuery += ' AND b.explicit = 0' | ||||
|       } | ||||
|       if (!user.permissions.accessAllTags && user.itemTagsSelected.length) { | ||||
|       if (!user.permissions?.accessAllTags && user.permissions?.itemTagsSelected?.length) { | ||||
|         if (user.permissions.selectedTagsNotAccessible) { | ||||
|           attrQuery += ' AND (SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected)) = 0' | ||||
|         } else { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user