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