From 0ac63b267828ec6fe0aa774cd2ecc6bfd67e5b8d Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 8 Jul 2023 09:57:32 -0500 Subject: [PATCH] Update Series and Author model to be library specific --- .../controls/LibraryFilterSelect.vue | 12 ++- server/controllers/LibraryItemController.js | 4 +- server/controllers/SeriesController.js | 2 +- server/models/Author.js | 4 + server/models/Series.js | 4 + server/objects/entities/Author.js | 8 +- server/objects/entities/Series.js | 8 +- server/routers/ApiRouter.js | 10 +- server/scanner/Scanner.js | 30 +++--- server/utils/migrations/dbMigration.js | 98 +++++++++++++------ 10 files changed, 119 insertions(+), 61 deletions(-) diff --git a/client/components/controls/LibraryFilterSelect.vue b/client/components/controls/LibraryFilterSelect.vue index f94bef85..7c093c16 100644 --- a/client/components/controls/LibraryFilterSelect.vue +++ b/client/components/controls/LibraryFilterSelect.vue @@ -271,12 +271,16 @@ export default { let filterValue = null if (parts.length > 1) { const decoded = this.$decode(parts[1]) - if (decoded.startsWith('aut_')) { + if (parts[0] === 'authors') { const author = this.authors.find((au) => au.id == decoded) if (author) filterValue = author.name - } else if (decoded.startsWith('ser_')) { - const series = this.series.find((se) => se.id == decoded) - if (series) filterValue = series.name + } else if (parts[0] === 'series') { + if (decoded === 'no-series') { + filterValue = this.$strings.MessageNoSeries + } else { + const series = this.series.find((se) => se.id == decoded) + if (series) filterValue = series.name + } } else { filterValue = decoded } diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js index 67c19052..d86211bb 100644 --- a/server/controllers/LibraryItemController.js +++ b/server/controllers/LibraryItemController.js @@ -105,7 +105,7 @@ class LibraryItemController { // Book specific if (libraryItem.isBook) { - await this.createAuthorsAndSeriesForItemUpdate(mediaPayload) + await this.createAuthorsAndSeriesForItemUpdate(mediaPayload, libraryItem.libraryId) } // Podcast specific @@ -342,7 +342,7 @@ class LibraryItemController { var libraryItem = Database.libraryItems.find(_li => _li.id === updatePayloads[i].id) if (!libraryItem) return null - await this.createAuthorsAndSeriesForItemUpdate(mediaPayload) + await this.createAuthorsAndSeriesForItemUpdate(mediaPayload, libraryItem.libraryId) var hasUpdates = libraryItem.media.update(mediaPayload) if (hasUpdates) { diff --git a/server/controllers/SeriesController.js b/server/controllers/SeriesController.js index 41f44b4b..5db1f0e2 100644 --- a/server/controllers/SeriesController.js +++ b/server/controllers/SeriesController.js @@ -10,7 +10,7 @@ class SeriesController { * /api/series/:id * * TODO: Update mobile app to use /api/libraries/:id/series/:seriesId API route instead - * Series are not library specific so we need to know what the library id is + * Series are not library specific so we need to know what the library id is * * @param {*} req * @param {*} res diff --git a/server/models/Author.js b/server/models/Author.js index d0ae6d9f..9dd8705a 100644 --- a/server/models/Author.js +++ b/server/models/Author.js @@ -74,5 +74,9 @@ module.exports = (sequelize) => { modelName: 'author' }) + const { library } = sequelize.models + library.hasMany(Author) + Author.belongsTo(library) + return Author } \ No newline at end of file diff --git a/server/models/Series.js b/server/models/Series.js index ef0e5709..1baf96c8 100644 --- a/server/models/Series.js +++ b/server/models/Series.js @@ -68,5 +68,9 @@ module.exports = (sequelize) => { modelName: 'series' }) + const { library } = sequelize.models + library.hasMany(Series) + Series.belongsTo(library) + return Series } \ No newline at end of file diff --git a/server/objects/entities/Author.js b/server/objects/entities/Author.js index 7a274b92..2a0ffa8b 100644 --- a/server/objects/entities/Author.js +++ b/server/objects/entities/Author.js @@ -11,6 +11,7 @@ class Author { this.imagePath = null this.addedAt = null this.updatedAt = null + this.libraryId = null if (author) { this.construct(author) @@ -25,6 +26,7 @@ class Author { this.imagePath = author.imagePath this.addedAt = author.addedAt this.updatedAt = author.updatedAt + this.libraryId = author.libraryId } toJSON() { @@ -35,7 +37,8 @@ class Author { description: this.description, imagePath: this.imagePath, addedAt: this.addedAt, - updatedAt: this.updatedAt + updatedAt: this.updatedAt, + libraryId: this.libraryId } } @@ -52,7 +55,7 @@ class Author { } } - setData(data) { + setData(data, libraryId) { this.id = uuidv4() this.name = data.name this.description = data.description || null @@ -60,6 +63,7 @@ class Author { this.imagePath = data.imagePath || null this.addedAt = Date.now() this.updatedAt = Date.now() + this.libraryId = libraryId } update(payload) { diff --git a/server/objects/entities/Series.js b/server/objects/entities/Series.js index 4c7d8f31..736171f9 100644 --- a/server/objects/entities/Series.js +++ b/server/objects/entities/Series.js @@ -7,6 +7,7 @@ class Series { this.description = null this.addedAt = null this.updatedAt = null + this.libraryId = null if (series) { this.construct(series) @@ -19,6 +20,7 @@ class Series { this.description = series.description || null this.addedAt = series.addedAt this.updatedAt = series.updatedAt + this.libraryId = series.libraryId } toJSON() { @@ -27,7 +29,8 @@ class Series { name: this.name, description: this.description, addedAt: this.addedAt, - updatedAt: this.updatedAt + updatedAt: this.updatedAt, + libraryId: this.libraryId } } @@ -39,12 +42,13 @@ class Series { } } - setData(data) { + setData(data, libraryId) { this.id = uuidv4() this.name = data.name this.description = data.description || null this.addedAt = Date.now() this.updatedAt = Date.now() + this.libraryId = libraryId } update(series) { diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index 58237ce0..5acac48d 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -519,7 +519,7 @@ class ApiRouter { return listeningStats } - async createAuthorsAndSeriesForItemUpdate(mediaPayload) { + async createAuthorsAndSeriesForItemUpdate(mediaPayload, libraryId) { if (mediaPayload.metadata) { const mediaMetadata = mediaPayload.metadata @@ -534,10 +534,10 @@ class ApiRouter { } if (!mediaMetadata.authors[i].id || mediaMetadata.authors[i].id.startsWith('new')) { - let author = Database.authors.find(au => au.checkNameEquals(authorName)) + let author = Database.authors.find(au => au.libraryId === libraryId && au.checkNameEquals(authorName)) if (!author) { author = new Author() - author.setData(mediaMetadata.authors[i]) + author.setData(mediaMetadata.authors[i], libraryId) Logger.debug(`[ApiRouter] Created new author "${author.name}"`) newAuthors.push(author) } @@ -563,10 +563,10 @@ class ApiRouter { } if (!mediaMetadata.series[i].id || mediaMetadata.series[i].id.startsWith('new')) { - let seriesItem = Database.series.find(se => se.checkNameEquals(seriesName)) + let seriesItem = Database.series.find(se => se.libraryId === libraryId && se.checkNameEquals(seriesName)) if (!seriesItem) { seriesItem = new Series() - seriesItem.setData(mediaMetadata.series[i]) + seriesItem.setData(mediaMetadata.series[i], libraryId) Logger.debug(`[ApiRouter] Created new series "${seriesItem.name}"`) newSeries.push(seriesItem) } diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js index 1fb8016d..8d1a8ccf 100644 --- a/server/scanner/Scanner.js +++ b/server/scanner/Scanner.js @@ -477,13 +477,13 @@ class Scanner { // Create or match all new authors and series if (libraryItem.media.metadata.authors.some(au => au.id.startsWith('new'))) { - var newAuthors = [] + const newAuthors = [] libraryItem.media.metadata.authors = libraryItem.media.metadata.authors.map((tempMinAuthor) => { - var _author = Database.authors.find(au => au.checkNameEquals(tempMinAuthor.name)) - if (!_author) _author = newAuthors.find(au => au.checkNameEquals(tempMinAuthor.name)) // Check new unsaved authors + let _author = Database.authors.find(au => au.libraryId === libraryItem.libraryId && au.checkNameEquals(tempMinAuthor.name)) + if (!_author) _author = newAuthors.find(au => au.libraryId === libraryItem.libraryId && au.checkNameEquals(tempMinAuthor.name)) // Check new unsaved authors if (!_author) { // Must create new author _author = new Author() - _author.setData(tempMinAuthor) + _author.setData(tempMinAuthor, libraryItem.libraryId) newAuthors.push(_author) } @@ -498,13 +498,13 @@ class Scanner { } } if (libraryItem.media.metadata.series.some(se => se.id.startsWith('new'))) { - var newSeries = [] + const newSeries = [] libraryItem.media.metadata.series = libraryItem.media.metadata.series.map((tempMinSeries) => { - var _series = Database.series.find(se => se.checkNameEquals(tempMinSeries.name)) - if (!_series) _series = newSeries.find(se => se.checkNameEquals(tempMinSeries.name)) // Check new unsaved series + 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) { // Must create new series _series = new Series() - _series.setData(tempMinSeries) + _series.setData(tempMinSeries, libraryItem.libraryId) newSeries.push(_series) } return { @@ -877,12 +877,11 @@ class Scanner { matchData.author = matchData.author.split(',').map(au => au.trim()).filter(au => !!au) } const authorPayload = [] - for (let index = 0; index < matchData.author.length; index++) { - const authorName = matchData.author[index] - var author = Database.authors.find(au => au.checkNameEquals(authorName)) + for (const authorName of matchData.author) { + let author = Database.authors.find(au => au.libraryId === libraryItem.libraryId && au.checkNameEquals(authorName)) if (!author) { author = new Author() - author.setData({ name: authorName }) + author.setData({ name: authorName }, libraryItem.libraryId) await Database.createAuthor(author) SocketAuthority.emitter('author_added', author.toJSON()) } @@ -895,12 +894,11 @@ class Scanner { if (matchData.series && (!libraryItem.media.metadata.seriesName || options.overrideDetails)) { if (!Array.isArray(matchData.series)) matchData.series = [{ series: matchData.series, sequence: matchData.sequence }] const seriesPayload = [] - for (let index = 0; index < matchData.series.length; index++) { - const seriesMatchItem = matchData.series[index] - var seriesItem = Database.series.find(au => au.checkNameEquals(seriesMatchItem.series)) + for (const seriesMatchItem of matchData.series) { + let seriesItem = Database.series.find(se => se.libraryId === libraryItem.libraryId && se.checkNameEquals(seriesMatchItem.series)) if (!seriesItem) { seriesItem = new Series() - seriesItem.setData({ name: seriesMatchItem.series }) + seriesItem.setData({ name: seriesMatchItem.series }, libraryItem.libraryId) await Database.createSeries(seriesItem) SocketAuthority.emitter('series_added', seriesItem.toJSON()) } diff --git a/server/utils/migrations/dbMigration.js b/server/utils/migrations/dbMigration.js index 0b94dda6..7a018c73 100644 --- a/server/utils/migrations/dbMigration.js +++ b/server/utils/migrations/dbMigration.js @@ -9,8 +9,8 @@ const oldDbIdMap = { libraries: {}, libraryFolders: {}, libraryItems: {}, - authors: {}, - series: {}, + authors: {}, // key is (new) library id with another map of author ids + series: {}, // key is (new) library id with another map of series ids collections: {}, podcastEpisodes: {}, books: {}, // key is library item id @@ -98,10 +98,10 @@ function migrateBook(oldLibraryItem, LibraryItem) { // Migrate BookAuthors // for (const oldBookAuthor of oldBook.metadata.authors) { - if (oldDbIdMap.authors[oldBookAuthor.id]) { + if (oldDbIdMap.authors[LibraryItem.libraryId][oldBookAuthor.id]) { newRecords.bookAuthor.push({ id: uuidv4(), - authorId: oldDbIdMap.authors[oldBookAuthor.id], + authorId: oldDbIdMap.authors[LibraryItem.libraryId][oldBookAuthor.id], bookId: Book.id }) } else { @@ -113,11 +113,11 @@ function migrateBook(oldLibraryItem, LibraryItem) { // Migrate BookSeries // for (const oldBookSeries of oldBook.metadata.series) { - if (oldDbIdMap.series[oldBookSeries.id]) { + if (oldDbIdMap.series[LibraryItem.libraryId][oldBookSeries.id]) { const BookSeries = { id: uuidv4(), sequence: oldBookSeries.sequence, - seriesId: oldDbIdMap.series[oldBookSeries.id], + seriesId: oldDbIdMap.series[LibraryItem.libraryId][oldBookSeries.id], bookId: Book.id } newRecords.bookSeries.push(BookSeries) @@ -297,33 +297,66 @@ function migrateLibraries(oldLibraries) { } } -function migrateAuthors(oldAuthors) { +function migrateAuthors(oldAuthors, oldLibraryItems) { for (const oldAuthor of oldAuthors) { - const Author = { - id: uuidv4(), - name: oldAuthor.name, - asin: oldAuthor.asin || null, - description: oldAuthor.description, - imagePath: oldAuthor.imagePath, - createdAt: oldAuthor.addedAt || Date.now(), - updatedAt: oldAuthor.updatedAt || Date.now() + // Get an array of NEW library ids that have this author + const librariesWithThisAuthor = [...new Set(oldLibraryItems.map(li => { + if (!li.media.metadata.authors?.some(au => au.id === oldAuthor.id)) return null + if (!oldDbIdMap.libraries[li.libraryId]) { + Logger.warn(`[dbMigration] Authors library id ${li.libraryId} was not migrated`) + } + return oldDbIdMap.libraries[li.libraryId] + }).filter(lid => lid))] + + if (!librariesWithThisAuthor.length) { + Logger.error(`[dbMigration] Author ${oldAuthor.name} was not found in any libraries`) + } + + for (const libraryId of librariesWithThisAuthor) { + const Author = { + id: uuidv4(), + name: oldAuthor.name, + asin: oldAuthor.asin || null, + description: oldAuthor.description, + imagePath: oldAuthor.imagePath, + createdAt: oldAuthor.addedAt || Date.now(), + updatedAt: oldAuthor.updatedAt || Date.now(), + libraryId + } + if (!oldDbIdMap.authors[libraryId]) oldDbIdMap.authors[libraryId] = {} + oldDbIdMap.authors[libraryId][oldAuthor.id] = Author.id + newRecords.author.push(Author) } - oldDbIdMap.authors[oldAuthor.id] = Author.id - newRecords.author.push(Author) } } -function migrateSeries(oldSerieses) { +function migrateSeries(oldSerieses, oldLibraryItems) { + // Originaly series were shared between libraries if they had the same name + // Series will be separate between libraries for (const oldSeries of oldSerieses) { - const Series = { - id: uuidv4(), - name: oldSeries.name, - description: oldSeries.description || null, - createdAt: oldSeries.addedAt || Date.now(), - updatedAt: oldSeries.updatedAt || Date.now() + // Get an array of NEW library ids that have this series + const librariesWithThisSeries = [...new Set(oldLibraryItems.map(li => { + if (!li.media.metadata.series?.some(se => se.id === oldSeries.id)) return null + return oldDbIdMap.libraries[li.libraryId] + }).filter(lid => lid))] + + if (!librariesWithThisSeries.length) { + Logger.error(`[dbMigration] Series ${oldSeries.name} was not found in any libraries`) + } + + for (const libraryId of librariesWithThisSeries) { + const Series = { + id: uuidv4(), + name: oldSeries.name, + description: oldSeries.description || null, + createdAt: oldSeries.addedAt || Date.now(), + updatedAt: oldSeries.updatedAt || Date.now(), + libraryId + } + if (!oldDbIdMap.series[libraryId]) oldDbIdMap.series[libraryId] = {} + oldDbIdMap.series[libraryId][oldSeries.id] = Series.id + newRecords.series.push(Series) } - oldDbIdMap.series[oldSeries.id] = Series.id - newRecords.series.push(Series) } } @@ -615,7 +648,14 @@ function migrateFeeds(oldFeeds) { } else if (oldFeed.entityType === 'libraryItem') { entityId = oldDbIdMap.libraryItems[oldFeed.entityId] } else if (oldFeed.entityType === 'series') { - entityId = oldDbIdMap.series[oldFeed.entityId] + // Series were split to be per library + // This will use the first series it finds + for (const libraryId in oldDbIdMap.series) { + if (oldDbIdMap.series[libraryId][oldFeed.entityId]) { + entityId = oldDbIdMap.series[libraryId][oldFeed.entityId] + break + } + } } if (!entityId) { @@ -719,9 +759,9 @@ module.exports.migrate = async (DatabaseModels) => { const start = Date.now() migrateSettings(data.settings) - migrateAuthors(data.authors) - migrateSeries(data.series) migrateLibraries(data.libraries) + migrateAuthors(data.authors, data.libraryItems) + migrateSeries(data.series, data.libraryItems) migrateLibraryItems(data.libraryItems) migrateUsers(data.users) migrateSessions(data.sessions)