mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Fix:Book library collapse series with no-series filter #2976
This commit is contained in:
		
							parent
							
								
									ec07bfa940
								
							
						
					
					
						commit
						4cddc597c1
					
				| @ -23,7 +23,7 @@ const libraryItemsPodcastFilters = require('../utils/queries/libraryItemsPodcast | ||||
| const authorFilters = require('../utils/queries/authorFilters') | ||||
| 
 | ||||
| class LibraryController { | ||||
|   constructor() { } | ||||
|   constructor() {} | ||||
| 
 | ||||
|   async create(req, res) { | ||||
|     const newLibraryPayload = { | ||||
| @ -35,7 +35,7 @@ class LibraryController { | ||||
| 
 | ||||
|     // Validate that the custom provider exists if given any
 | ||||
|     if (newLibraryPayload.provider?.startsWith('custom-')) { | ||||
|       if (!await Database.customMetadataProviderModel.checkExistsBySlug(newLibraryPayload.provider)) { | ||||
|       if (!(await Database.customMetadataProviderModel.checkExistsBySlug(newLibraryPayload.provider))) { | ||||
|         Logger.error(`[LibraryController] Custom metadata provider "${newLibraryPayload.provider}" does not exist`) | ||||
|         return res.status(400).send('Custom metadata provider does not exist') | ||||
|       } | ||||
| @ -43,14 +43,15 @@ class LibraryController { | ||||
| 
 | ||||
|     // Validate folder paths exist or can be created & resolve rel paths
 | ||||
|     //   returns 400 if a folder fails to access
 | ||||
|     newLibraryPayload.folders = newLibraryPayload.folders.map(f => { | ||||
|     newLibraryPayload.folders = newLibraryPayload.folders.map((f) => { | ||||
|       f.fullPath = fileUtils.filePathToPOSIX(Path.resolve(f.fullPath)) | ||||
|       return f | ||||
|     }) | ||||
|     for (const folder of newLibraryPayload.folders) { | ||||
|       try { | ||||
|         const direxists = await fs.pathExists(folder.fullPath) | ||||
|         if (!direxists) { // If folder does not exist try to make it and set file permissions/owner
 | ||||
|         if (!direxists) { | ||||
|           // If folder does not exist try to make it and set file permissions/owner
 | ||||
|           await fs.mkdir(folder.fullPath) | ||||
|         } | ||||
|       } catch (error) { | ||||
| @ -85,12 +86,12 @@ class LibraryController { | ||||
|     const librariesAccessible = req.user.librariesAccessible || [] | ||||
|     if (librariesAccessible.length) { | ||||
|       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()) | ||||
|       }) | ||||
|     } | ||||
| 
 | ||||
|     res.json({ | ||||
|       libraries: libraries.map(lib => lib.toJSON()) | ||||
|       libraries: libraries.map((lib) => lib.toJSON()) | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
| @ -140,7 +141,7 @@ class LibraryController { | ||||
| 
 | ||||
|     // Validate that the custom provider exists if given any
 | ||||
|     if (req.body.provider?.startsWith('custom-')) { | ||||
|       if (!await Database.customMetadataProviderModel.checkExistsBySlug(req.body.provider)) { | ||||
|       if (!(await Database.customMetadataProviderModel.checkExistsBySlug(req.body.provider))) { | ||||
|         Logger.error(`[LibraryController] Custom metadata provider "${req.body.provider}" does not exist`) | ||||
|         return res.status(400).send('Custom metadata provider does not exist') | ||||
|       } | ||||
| @ -150,7 +151,7 @@ class LibraryController { | ||||
|     //   returns 400 if a new folder fails to access
 | ||||
|     if (req.body.folders) { | ||||
|       const newFolderPaths = [] | ||||
|       req.body.folders = req.body.folders.map(f => { | ||||
|       req.body.folders = req.body.folders.map((f) => { | ||||
|         if (!f.id) { | ||||
|           f.fullPath = fileUtils.filePathToPOSIX(Path.resolve(f.fullPath)) | ||||
|           newFolderPaths.push(f.fullPath) | ||||
| @ -161,10 +162,13 @@ class LibraryController { | ||||
|         const pathExists = await fs.pathExists(path) | ||||
|         if (!pathExists) { | ||||
|           // Ensure dir will recursively create directories which might be preferred over mkdir
 | ||||
|           const success = await fs.ensureDir(path).then(() => true).catch((error) => { | ||||
|             Logger.error(`[LibraryController] Failed to ensure folder dir "${path}"`, error) | ||||
|             return false | ||||
|           }) | ||||
|           const success = await fs | ||||
|             .ensureDir(path) | ||||
|             .then(() => true) | ||||
|             .catch((error) => { | ||||
|               Logger.error(`[LibraryController] Failed to ensure folder dir "${path}"`, error) | ||||
|               return false | ||||
|             }) | ||||
|           if (!success) { | ||||
|             return res.status(400).send(`Invalid folder directory "${path}"`) | ||||
|           } | ||||
| @ -173,7 +177,7 @@ class LibraryController { | ||||
| 
 | ||||
|       // Handle removing folders
 | ||||
|       for (const folder of library.folders) { | ||||
|         if (!req.body.folders.some(f => f.id === folder.id)) { | ||||
|         if (!req.body.folders.some((f) => f.id === folder.id)) { | ||||
|           // Remove library items in folder
 | ||||
|           const libraryItemsInFolder = await Database.libraryItemModel.findAll({ | ||||
|             where: { | ||||
| @ -195,7 +199,7 @@ class LibraryController { | ||||
|           for (const libraryItem of libraryItemsInFolder) { | ||||
|             let mediaItemIds = [] | ||||
|             if (library.isPodcast) { | ||||
|               mediaItemIds = libraryItem.media.podcastEpisodes.map(pe => pe.id) | ||||
|               mediaItemIds = libraryItem.media.podcastEpisodes.map((pe) => pe.id) | ||||
|             } else { | ||||
|               mediaItemIds.push(libraryItem.mediaId) | ||||
|             } | ||||
| @ -267,7 +271,7 @@ class LibraryController { | ||||
|     for (const libraryItem of libraryItemsInLibrary) { | ||||
|       let mediaItemIds = [] | ||||
|       if (library.isPodcast) { | ||||
|         mediaItemIds = libraryItem.media.podcastEpisodes.map(pe => pe.id) | ||||
|         mediaItemIds = libraryItem.media.podcastEpisodes.map((pe) => pe.id) | ||||
|       } else { | ||||
|         mediaItemIds.push(libraryItem.mediaId) | ||||
|       } | ||||
| @ -298,7 +302,10 @@ class LibraryController { | ||||
|    * @param {import('express').Response} res | ||||
|    */ | ||||
|   async getLibraryItems(req, res) { | ||||
|     const include = (req.query.include || '').split(',').map(v => v.trim().toLowerCase()).filter(v => !!v) | ||||
|     const include = (req.query.include || '') | ||||
|       .split(',') | ||||
|       .map((v) => v.trim().toLowerCase()) | ||||
|       .filter((v) => !!v) | ||||
| 
 | ||||
|     const payload = { | ||||
|       results: [], | ||||
| @ -316,7 +323,9 @@ class LibraryController { | ||||
|     payload.offset = payload.page * payload.limit | ||||
| 
 | ||||
|     // TODO: Temporary way of handling collapse sub-series. Either remove feature or handle through sql queries
 | ||||
|     if (payload.filterBy?.split('.')[0] === 'series' && payload.collapseseries) { | ||||
|     const filterByGroup = payload.filterBy?.split('.').shift() | ||||
|     const filterByValue = filterByGroup ? libraryFilters.decode(payload.filterBy.replace(`${filterByGroup}.`, '')) : null | ||||
|     if (filterByGroup === 'series' && filterByValue !== 'no-series' && payload.collapseseries) { | ||||
|       const seriesId = libraryFilters.decode(payload.filterBy.split('.')[1]) | ||||
|       payload.results = await libraryHelpers.handleCollapseSubseries(payload, seriesId, req.user, req.library) | ||||
|     } else { | ||||
| @ -369,7 +378,7 @@ class LibraryController { | ||||
|     for (const libraryItem of libraryItemsWithIssues) { | ||||
|       let mediaItemIds = [] | ||||
|       if (req.library.isPodcast) { | ||||
|         mediaItemIds = libraryItem.media.podcastEpisodes.map(pe => pe.id) | ||||
|         mediaItemIds = libraryItem.media.podcastEpisodes.map((pe) => pe.id) | ||||
|       } else { | ||||
|         mediaItemIds.push(libraryItem.mediaId) | ||||
|       } | ||||
| @ -386,14 +395,17 @@ class LibraryController { | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  * GET: /api/libraries/:id/series | ||||
|  * Optional query string: `?include=rssfeed` that adds `rssFeed` to series if a feed is open | ||||
|  *  | ||||
|  * @param {import('express').Request} req  | ||||
|  * @param {import('express').Response} res  | ||||
|  */ | ||||
|    * GET: /api/libraries/:id/series | ||||
|    * Optional query string: `?include=rssfeed` that adds `rssFeed` to series if a feed is open | ||||
|    * | ||||
|    * @param {import('express').Request} req | ||||
|    * @param {import('express').Response} res | ||||
|    */ | ||||
|   async getAllSeriesForLibrary(req, res) { | ||||
|     const include = (req.query.include || '').split(',').map(v => v.trim().toLowerCase()).filter(v => !!v) | ||||
|     const include = (req.query.include || '') | ||||
|       .split(',') | ||||
|       .map((v) => v.trim().toLowerCase()) | ||||
|       .filter((v) => !!v) | ||||
| 
 | ||||
|     const payload = { | ||||
|       results: [], | ||||
| @ -426,7 +438,10 @@ class LibraryController { | ||||
|    * @param {import('express').Response} res - Series | ||||
|    */ | ||||
|   async getSeriesForLibrary(req, res) { | ||||
|     const include = (req.query.include || '').split(',').map(v => v.trim().toLowerCase()).filter(v => !!v) | ||||
|     const include = (req.query.include || '') | ||||
|       .split(',') | ||||
|       .map((v) => v.trim().toLowerCase()) | ||||
|       .filter((v) => !!v) | ||||
| 
 | ||||
|     const series = await Database.seriesModel.findByPk(req.params.seriesId) | ||||
|     if (!series) return res.sendStatus(404) | ||||
| @ -436,10 +451,10 @@ class LibraryController { | ||||
| 
 | ||||
|     const seriesJson = oldSeries.toJSON() | ||||
|     if (include.includes('progress')) { | ||||
|       const libraryItemsFinished = libraryItemsInSeries.filter(li => !!req.user.getMediaProgress(li.id)?.isFinished) | ||||
|       const libraryItemsFinished = libraryItemsInSeries.filter((li) => !!req.user.getMediaProgress(li.id)?.isFinished) | ||||
|       seriesJson.progress = { | ||||
|         libraryItemIds: libraryItemsInSeries.map(li => li.id), | ||||
|         libraryItemIdsFinished: libraryItemsFinished.map(li => li.id), | ||||
|         libraryItemIds: libraryItemsInSeries.map((li) => li.id), | ||||
|         libraryItemIdsFinished: libraryItemsFinished.map((li) => li.id), | ||||
|         isFinished: libraryItemsFinished.length >= libraryItemsInSeries.length | ||||
|       } | ||||
|     } | ||||
| @ -459,7 +474,10 @@ class LibraryController { | ||||
|    * @param {*} res | ||||
|    */ | ||||
|   async getCollectionsForLibrary(req, res) { | ||||
|     const include = (req.query.include || '').split(',').map(v => v.trim().toLowerCase()).filter(v => !!v) | ||||
|     const include = (req.query.include || '') | ||||
|       .split(',') | ||||
|       .map((v) => v.trim().toLowerCase()) | ||||
|       .filter((v) => !!v) | ||||
| 
 | ||||
|     const payload = { | ||||
|       results: [], | ||||
| @ -495,7 +513,7 @@ class LibraryController { | ||||
|    */ | ||||
|   async getUserPlaylistsForLibrary(req, res) { | ||||
|     let playlistsForUser = await Database.playlistModel.getPlaylistsForUserAndLibrary(req.user.id, req.library.id) | ||||
|     playlistsForUser = await Promise.all(playlistsForUser.map(async p => p.getOldJsonExpanded())) | ||||
|     playlistsForUser = await Promise.all(playlistsForUser.map(async (p) => p.getOldJsonExpanded())) | ||||
| 
 | ||||
|     const payload = { | ||||
|       results: [], | ||||
| @ -531,7 +549,10 @@ class LibraryController { | ||||
|    */ | ||||
|   async getUserPersonalizedShelves(req, res) { | ||||
|     const limitPerShelf = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) || 10 : 10 | ||||
|     const include = (req.query.include || '').split(',').map(v => v.trim().toLowerCase()).filter(v => !!v) | ||||
|     const include = (req.query.include || '') | ||||
|       .split(',') | ||||
|       .map((v) => v.trim().toLowerCase()) | ||||
|       .filter((v) => !!v) | ||||
|     const shelves = await Database.libraryItemModel.getPersonalizedShelves(req.library, req.user, include, limitPerShelf) | ||||
|     res.json(shelves) | ||||
|   } | ||||
| @ -552,7 +573,7 @@ class LibraryController { | ||||
|     const orderdata = req.body | ||||
|     let hasUpdates = false | ||||
|     for (let i = 0; i < orderdata.length; i++) { | ||||
|       const library = libraries.find(lib => lib.id === orderdata[i].id) | ||||
|       const library = libraries.find((lib) => lib.id === orderdata[i].id) | ||||
|       if (!library) { | ||||
|         Logger.error(`[LibraryController] Invalid library not found in reorder ${orderdata[i].id}`) | ||||
|         return res.sendStatus(500) | ||||
| @ -571,7 +592,7 @@ class LibraryController { | ||||
|     } | ||||
| 
 | ||||
|     res.json({ | ||||
|       libraries: libraries.map(lib => lib.toJSON()) | ||||
|       libraries: libraries.map((lib) => lib.toJSON()) | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
| @ -657,9 +678,7 @@ class LibraryController { | ||||
|           attributes: [] | ||||
|         } | ||||
|       }, | ||||
|       order: [ | ||||
|         [Sequelize.literal('name COLLATE NOCASE'), 'ASC'] | ||||
|       ] | ||||
|       order: [[Sequelize.literal('name COLLATE NOCASE'), 'ASC']] | ||||
|     }) | ||||
| 
 | ||||
|     const oldAuthors = [] | ||||
| @ -699,7 +718,7 @@ class LibraryController { | ||||
| 
 | ||||
|     const narrators = {} | ||||
|     for (const book of booksWithNarrators) { | ||||
|       book.narrators.forEach(n => { | ||||
|       book.narrators.forEach((n) => { | ||||
|         if (typeof n !== 'string') { | ||||
|           Logger.error(`[LibraryController] getNarrators: Invalid narrator "${n}" on book "${book.title}"`) | ||||
|         } else if (!narrators[n]) { | ||||
| @ -715,7 +734,7 @@ class LibraryController { | ||||
|     } | ||||
| 
 | ||||
|     res.json({ | ||||
|       narrators: naturalSort(Object.values(narrators)).asc(n => n.name) | ||||
|       narrators: naturalSort(Object.values(narrators)).asc((n) => n.name) | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
| @ -747,7 +766,7 @@ class LibraryController { | ||||
|     const itemsWithNarrator = await libraryItemFilters.getAllLibraryItemsWithNarrators([narratorName]) | ||||
| 
 | ||||
|     for (const libraryItem of itemsWithNarrator) { | ||||
|       libraryItem.media.narrators = libraryItem.media.narrators.filter(n => n !== narratorName) | ||||
|       libraryItem.media.narrators = libraryItem.media.narrators.filter((n) => n !== narratorName) | ||||
|       if (!libraryItem.media.narrators.includes(updatedName)) { | ||||
|         libraryItem.media.narrators.push(updatedName) | ||||
|       } | ||||
| @ -759,7 +778,10 @@ class LibraryController { | ||||
|     } | ||||
| 
 | ||||
|     if (itemsUpdated.length) { | ||||
|       SocketAuthority.emitter('items_updated', itemsUpdated.map(li => li.toJSONExpanded())) | ||||
|       SocketAuthority.emitter( | ||||
|         'items_updated', | ||||
|         itemsUpdated.map((li) => li.toJSONExpanded()) | ||||
|       ) | ||||
|     } | ||||
| 
 | ||||
|     res.json({ | ||||
| @ -790,7 +812,7 @@ class LibraryController { | ||||
|     const itemsWithNarrator = await libraryItemFilters.getAllLibraryItemsWithNarrators([narratorName]) | ||||
| 
 | ||||
|     for (const libraryItem of itemsWithNarrator) { | ||||
|       libraryItem.media.narrators = libraryItem.media.narrators.filter(n => n !== narratorName) | ||||
|       libraryItem.media.narrators = libraryItem.media.narrators.filter((n) => n !== narratorName) | ||||
|       await libraryItem.media.update({ | ||||
|         narrators: libraryItem.media.narrators | ||||
|       }) | ||||
| @ -799,7 +821,10 @@ class LibraryController { | ||||
|     } | ||||
| 
 | ||||
|     if (itemsUpdated.length) { | ||||
|       SocketAuthority.emitter('items_updated', itemsUpdated.map(li => li.toJSONExpanded())) | ||||
|       SocketAuthority.emitter( | ||||
|         'items_updated', | ||||
|         itemsUpdated.map((li) => li.toJSONExpanded()) | ||||
|       ) | ||||
|     } | ||||
| 
 | ||||
|     res.json({ | ||||
| @ -859,7 +884,7 @@ class LibraryController { | ||||
|     const payload = { | ||||
|       episodes: [], | ||||
|       limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0, | ||||
|       page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0, | ||||
|       page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0 | ||||
|     } | ||||
| 
 | ||||
|     const offset = payload.page * payload.limit | ||||
| @ -929,10 +954,10 @@ class LibraryController { | ||||
| 
 | ||||
|     let numRemoved = 0 | ||||
|     for (const libraryItem of libraryItemsWithMetadata) { | ||||
|       const metadataFilepath = libraryItem.libraryFiles.find(lf => lf.metadata.filename === metadataFilename)?.metadata.path | ||||
|       const metadataFilepath = libraryItem.libraryFiles.find((lf) => lf.metadata.filename === metadataFilename)?.metadata.path | ||||
|       if (!metadataFilepath) continue | ||||
|       Logger.debug(`[LibraryController] Removing file "${metadataFilepath}"`) | ||||
|       if ((await fileUtils.removeFile(metadataFilepath))) { | ||||
|       if (await fileUtils.removeFile(metadataFilepath)) { | ||||
|         numRemoved++ | ||||
|       } | ||||
|     } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user