From 9d7d4c69020d6fc4b4da2948266c33ac56976217 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 18 Aug 2023 14:40:36 -0500 Subject: [PATCH] Update filterData for authors/series when added/removed --- client/layouts/default.vue | 7 ++ client/store/libraries.js | 4 ++ server/Database.js | 70 +++++++++++++++++++ server/controllers/AuthorController.js | 2 + server/controllers/LibraryController.js | 6 +- server/controllers/LibraryItemController.js | 2 +- server/models/Author.js | 9 +++ server/models/Series.js | 9 +++ server/routers/ApiRouter.js | 28 +++++++- server/scanner/Scanner.js | 14 +++- server/utils/queries/libraryFilters.js | 8 +-- .../utils/queries/libraryItemsBookFilters.js | 6 +- 12 files changed, 152 insertions(+), 13 deletions(-) diff --git a/client/layouts/default.vue b/client/layouts/default.vue index d27936e2..805fb8a1 100644 --- a/client/layouts/default.vue +++ b/client/layouts/default.vue @@ -343,6 +343,10 @@ export default { } this.$store.commit('libraries/removeCollection', collection) }, + seriesRemoved({ id, libraryId }) { + if (this.currentLibraryId !== libraryId) return + this.$store.commit('libraries/removeSeriesFromFilterData', id) + }, playlistAdded(playlist) { if (playlist.userId !== this.user.id || this.currentLibraryId !== playlist.libraryId) return this.$store.commit('libraries/addUpdateUserPlaylist', playlist) @@ -442,6 +446,9 @@ export default { this.socket.on('collection_updated', this.collectionUpdated) this.socket.on('collection_removed', this.collectionRemoved) + // Series Listeners + this.socket.on('series_removed', this.seriesRemoved) + // User Playlist Listeners this.socket.on('playlist_added', this.playlistAdded) this.socket.on('playlist_updated', this.playlistUpdated) diff --git a/client/store/libraries.js b/client/store/libraries.js index e0151626..fd8af4ae 100644 --- a/client/store/libraries.js +++ b/client/store/libraries.js @@ -234,6 +234,10 @@ export const mutations = { setNumUserPlaylists(state, numUserPlaylists) { state.numUserPlaylists = numUserPlaylists }, + removeSeriesFromFilterData(state, seriesId) { + if (!seriesId || !state.filterData) return + state.filterData.series = state.filterData.series.filter(se => se.id !== seriesId) + }, updateFilterDataWithItem(state, libraryItem) { if (!libraryItem || !state.filterData) return if (state.currentLibraryId !== libraryItem.libraryId) return diff --git a/server/Database.js b/server/Database.js index ebe6d4c7..303e2d4a 100644 --- a/server/Database.js +++ b/server/Database.js @@ -34,6 +34,16 @@ class Database { return this.sequelize?.models || {} } + /** @type {typeof import('./models/Author')} */ + get authorModel() { + return this.models.author + } + + /** @type {typeof import('./models/Series')} */ + get seriesModel() { + return this.models.series + } + async checkHasDb() { if (!await fs.pathExists(this.dbPath)) { Logger.info(`[Database] absdatabase.sqlite not found at ${this.dbPath}`) @@ -481,6 +491,66 @@ class Database { } } } + + removeSeriesFromFilterData(libraryId, seriesId) { + if (!this.libraryFilterData[libraryId]) return + this.libraryFilterData[libraryId].series = this.libraryFilterData[libraryId].series.filter(se => se.id !== seriesId) + } + + addSeriesToFilterData(libraryId, seriesName, seriesId) { + if (!this.libraryFilterData[libraryId]) return + // Check if series is already added + if (this.libraryFilterData[libraryId].series.some(se => se.id === seriesId)) return + this.libraryFilterData[libraryId].series.push({ + id: seriesId, + name: seriesName + }) + } + + removeAuthorFromFilterData(libraryId, authorId) { + if (!this.libraryFilterData[libraryId]) return + this.libraryFilterData[libraryId].authors = this.libraryFilterData[libraryId].authors.filter(au => au.id !== authorId) + } + + addAuthorToFilterData(libraryId, authorName, authorId) { + if (!this.libraryFilterData[libraryId]) return + // Check if author is already added + if (this.libraryFilterData[libraryId].authors.some(au => au.id === authorId)) return + this.libraryFilterData[libraryId].authors.push({ + id: authorId, + name: authorName + }) + } + + /** + * Used when updating items to make sure author id exists + * If library filter data is set then use that for check + * otherwise lookup in db + * @param {string} libraryId + * @param {string} authorId + * @returns {Promise} + */ + async checkAuthorExists(libraryId, authorId) { + if (!this.libraryFilterData[libraryId]) { + return this.authorModel.checkExistsById(authorId) + } + return this.libraryFilterData[libraryId].authors.some(au => au.id === authorId) + } + + /** + * Used when updating items to make sure series id exists + * If library filter data is set then use that for check + * otherwise lookup in db + * @param {string} libraryId + * @param {string} seriesId + * @returns {Promise} + */ + async checkSeriesExists(libraryId, seriesId) { + if (!this.libraryFilterData[libraryId]) { + return this.seriesModel.checkExistsById(seriesId) + } + return this.libraryFilterData[libraryId].series.some(se => se.id === seriesId) + } } module.exports = new Database() \ No newline at end of file diff --git a/server/controllers/AuthorController.js b/server/controllers/AuthorController.js index 5133d1cb..56cb59a9 100644 --- a/server/controllers/AuthorController.js +++ b/server/controllers/AuthorController.js @@ -113,6 +113,8 @@ class AuthorController { // Remove old author await Database.removeAuthor(req.author.id) SocketAuthority.emitter('author_removed', req.author.toJSON()) + // Update filter data + Database.removeAuthorFromFilterData(req.author.libraryId, req.author.id) // Send updated num books for merged author const numBooks = await Database.models.libraryItem.getForAuthor(existingAuthor).length diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js index 70c63708..97696243 100644 --- a/server/controllers/LibraryController.js +++ b/server/controllers/LibraryController.js @@ -859,12 +859,12 @@ class LibraryController { /** * GET: /api/libraries/:id/authors * Get authors for library - * @param {*} req - * @param {*} res + * @param {import('express').Request} req + * @param {import('express').Response} res */ async getAuthors(req, res) { const { bookWhere, replacements } = libraryItemsBookFilters.getUserPermissionBookWhereQuery(req.user) - const authors = await Database.models.author.findAll({ + const authors = await Database.authorModel.findAll({ where: { libraryId: req.library.id }, diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js index 2667128b..d5fa20bc 100644 --- a/server/controllers/LibraryItemController.js +++ b/server/controllers/LibraryItemController.js @@ -124,7 +124,7 @@ class LibraryItemController { // Book specific - Get all series being removed from this item let seriesRemoved = [] if (libraryItem.isBook && mediaPayload.metadata?.series) { - const seriesIdsInUpdate = (mediaPayload.metadata?.series || []).map(se => se.id) + const seriesIdsInUpdate = mediaPayload.metadata.series?.map(se => se.id) || [] seriesRemoved = libraryItem.media.metadata.series.filter(se => !seriesIdsInUpdate.includes(se.id)) } diff --git a/server/models/Author.js b/server/models/Author.js index 88dc3eea..9eeda5bf 100644 --- a/server/models/Author.js +++ b/server/models/Author.js @@ -83,6 +83,15 @@ class Author extends Model { }) } + /** + * Check if author exists + * @param {string} authorId + * @returns {Promise} + */ + static async checkExistsById(authorId) { + return (await this.count({ where: { id: authorId } })) > 0 + } + /** * Initialize model * @param {import('../Database').sequelize} sequelize diff --git a/server/models/Series.js b/server/models/Series.js index f4cdbffe..243be391 100644 --- a/server/models/Series.js +++ b/server/models/Series.js @@ -75,6 +75,15 @@ class Series extends Model { }) } + /** + * Check if series exists + * @param {string} seriesId + * @returns {Promise} + */ + static async checkExistsById(seriesId) { + return (await this.count({ where: { id: seriesId } })) > 0 + } + /** * Initialize model * @param {import('../Database').sequelize} sequelize diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index 69179006..e1287dcd 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -472,10 +472,20 @@ class ApiRouter { } } + /** + * Remove an empty series & close an open RSS feed + * @param {import('../models/Series')} series + */ async removeEmptySeries(series) { await this.rssFeedManager.closeFeedForEntityId(series.id) Logger.info(`[ApiRouter] Series "${series.name}" is now empty. Removing series`) await Database.removeSeries(series.id) + // Remove series from library filter data + Database.removeSeriesFromFilterData(series.libraryId, series.id) + SocketAuthority.emitter('series_removed', { + id: series.id, + libraryId: series.libraryId + }) } async getUserListeningSessionsHelper(userId) { @@ -546,7 +556,7 @@ class ApiRouter { const mediaMetadata = mediaPayload.metadata // Create new authors if in payload - if (mediaMetadata.authors && mediaMetadata.authors.length) { + if (mediaMetadata.authors?.length) { const newAuthors = [] for (let i = 0; i < mediaMetadata.authors.length; i++) { const authorName = (mediaMetadata.authors[i].name || '').trim() @@ -555,6 +565,12 @@ class ApiRouter { continue } + // Ensure the ID for the author exists + if (mediaMetadata.authors[i].id && !(await Database.checkAuthorExists(libraryId, mediaMetadata.authors[i].id))) { + Logger.warn(`[ApiRouter] Author id "${mediaMetadata.authors[i].id}" does not exist`) + mediaMetadata.authors[i].id = null + } + if (!mediaMetadata.authors[i].id || mediaMetadata.authors[i].id.startsWith('new')) { let author = Database.authors.find(au => au.libraryId === libraryId && au.checkNameEquals(authorName)) if (!author) { @@ -562,6 +578,8 @@ class ApiRouter { author.setData(mediaMetadata.authors[i], libraryId) Logger.debug(`[ApiRouter] Created new author "${author.name}"`) newAuthors.push(author) + // Update filter data + Database.addAuthorToFilterData(libraryId, author.name, author.id) } // Update ID in original payload @@ -584,6 +602,12 @@ class ApiRouter { continue } + // Ensure the ID for the series exists + if (mediaMetadata.series[i].id && !(await Database.checkSeriesExists(libraryId, mediaMetadata.series[i].id))) { + Logger.warn(`[ApiRouter] Series id "${mediaMetadata.series[i].id}" does not exist`) + mediaMetadata.series[i].id = null + } + if (!mediaMetadata.series[i].id || mediaMetadata.series[i].id.startsWith('new')) { let seriesItem = Database.series.find(se => se.libraryId === libraryId && se.checkNameEquals(seriesName)) if (!seriesItem) { @@ -591,6 +615,8 @@ class ApiRouter { seriesItem.setData(mediaMetadata.series[i], libraryId) Logger.debug(`[ApiRouter] Created new series "${seriesItem.name}"`) newSeries.push(seriesItem) + // Update filter data + Database.addSeriesToFilterData(libraryId, seriesItem.name, seriesItem.id) } // Update ID in original payload diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js index dfa26924..559cbcbd 100644 --- a/server/scanner/Scanner.js +++ b/server/scanner/Scanner.js @@ -486,6 +486,8 @@ class Scanner { _author = new Author() _author.setData(tempMinAuthor, libraryItem.libraryId) newAuthors.push(_author) + // Update filter data + Database.addAuthorToFilterData(libraryItem.libraryId, _author.name, _author.id) } return { @@ -502,11 +504,17 @@ class Scanner { const newSeries = [] libraryItem.media.metadata.series = libraryItem.media.metadata.series.map((tempMinSeries) => { let _series = Database.series.find(se => se.libraryId === libraryItem.libraryId && se.checkNameEquals(tempMinSeries.name)) - if (!_series) _series = newSeries.find(se => se.libraryId === libraryItem.libraryId && se.checkNameEquals(tempMinSeries.name)) // Check new unsaved series + if (!_series) { + // Check new unsaved series + _series = newSeries.find(se => se.libraryId === libraryItem.libraryId && se.checkNameEquals(tempMinSeries.name)) + } + if (!_series) { // Must create new series _series = new Series() _series.setData(tempMinSeries, libraryItem.libraryId) newSeries.push(_series) + // Update filter data + Database.addSeriesToFilterData(libraryItem.libraryId, _series.name, _series.id) } return { id: _series.id, @@ -924,6 +932,8 @@ class Scanner { author.setData({ name: authorName }, libraryItem.libraryId) await Database.createAuthor(author) SocketAuthority.emitter('author_added', author.toJSON()) + // Update filter data + Database.addAuthorToFilterData(libraryItem.libraryId, author.name, author.id) } authorPayload.push(author.toJSONMinimal()) } @@ -940,6 +950,8 @@ class Scanner { seriesItem = new Series() seriesItem.setData({ name: seriesMatchItem.series }, libraryItem.libraryId) await Database.createSeries(seriesItem) + // Update filter data + Database.addSeriesToFilterData(libraryItem.libraryId, seriesItem.name, seriesItem.id) SocketAuthority.emitter('series_added', seriesItem.toJSON()) } seriesPayload.push(seriesItem.toJSONMinimal(seriesMatchItem.sequence)) diff --git a/server/utils/queries/libraryFilters.js b/server/utils/queries/libraryFilters.js index 27e1b933..df6855a3 100644 --- a/server/utils/queries/libraryFilters.js +++ b/server/utils/queries/libraryFilters.js @@ -221,7 +221,7 @@ module.exports = { })) } - const { rows: series, count } = await Database.models.series.findAndCountAll({ + const { rows: series, count } = await Database.seriesModel.findAndCountAll({ where: seriesWhere, limit, offset: 0, @@ -291,7 +291,7 @@ module.exports = { async getNewestAuthors(library, user, limit) { if (library.mediaType !== 'book') return { authors: [], count: 0 } - const { rows: authors, count } = await Database.models.author.findAndCountAll({ + const { rows: authors, count } = await Database.authorModel.findAndCountAll({ where: { libraryId: library.id, createdAt: { @@ -461,7 +461,7 @@ module.exports = { if (book.language) data.languages.add(book.language) } - const series = await Database.models.series.findAll({ + const series = await Database.seriesModel.findAll({ where: { libraryId: oldLibrary.id }, @@ -469,7 +469,7 @@ module.exports = { }) series.forEach((s) => data.series.push({ id: s.id, name: s.name })) - const authors = await Database.models.author.findAll({ + const authors = await Database.authorModel.findAll({ where: { libraryId: oldLibrary.id }, diff --git a/server/utils/queries/libraryItemsBookFilters.js b/server/utils/queries/libraryItemsBookFilters.js index 7495dce6..e30b201d 100644 --- a/server/utils/queries/libraryItemsBookFilters.js +++ b/server/utils/queries/libraryItemsBookFilters.js @@ -278,7 +278,7 @@ module.exports = { * @returns {object} { booksToExclude, bookSeriesToInclude } */ async getCollapseSeriesBooksToExclude(bookFindOptions, seriesWhere) { - const allSeries = await Database.models.series.findAll({ + const allSeries = await Database.seriesModel.findAll({ attributes: [ 'id', 'name', @@ -642,7 +642,7 @@ module.exports = { const userPermissionBookWhere = this.getUserPermissionBookWhereQuery(user) bookWhere.push(...userPermissionBookWhere.bookWhere) - const { rows: series, count } = await Database.models.series.findAndCountAll({ + const { rows: series, count } = await Database.seriesModel.findAndCountAll({ where: [ { libraryId @@ -751,7 +751,7 @@ module.exports = { const userPermissionBookWhere = this.getUserPermissionBookWhereQuery(user) // Step 1: Get the first book of every series that hasnt been started yet - const seriesNotStarted = await Database.models.series.findAll({ + const seriesNotStarted = await Database.seriesModel.findAll({ where: [ { libraryId