diff --git a/server/Database.js b/server/Database.js index a3959ccd..6628eb05 100644 --- a/server/Database.js +++ b/server/Database.js @@ -462,26 +462,6 @@ class Database { await this.models.series.removeById(seriesId) } - async createAuthor(oldAuthor) { - if (!this.sequelize) return false - await this.models.author.createFromOld(oldAuthor) - } - - async createBulkAuthors(oldAuthors) { - if (!this.sequelize) return false - await this.models.author.createBulkFromOld(oldAuthors) - } - - updateAuthor(oldAuthor) { - if (!this.sequelize) return false - return this.models.author.updateFromOld(oldAuthor) - } - - async removeAuthor(authorId) { - if (!this.sequelize) return false - await this.models.author.removeById(authorId) - } - async createBulkBookAuthors(bookAuthors) { if (!this.sequelize) return false await this.models.bookAuthor.bulkCreate(bookAuthors) @@ -684,7 +664,7 @@ class Database { */ async getAuthorIdByName(libraryId, authorName) { if (!this.libraryFilterData[libraryId]) { - return (await this.authorModel.getOldByNameAndLibrary(authorName, libraryId))?.id || null + return (await this.authorModel.getByNameAndLibrary(authorName, libraryId))?.id || null } return this.libraryFilterData[libraryId].authors.find((au) => au.name === authorName)?.id || null } diff --git a/server/controllers/AuthorController.js b/server/controllers/AuthorController.js index 99b97763..7a46b9b8 100644 --- a/server/controllers/AuthorController.js +++ b/server/controllers/AuthorController.js @@ -21,6 +21,11 @@ const naturalSort = createNewSortInstance({ * @property {import('../models/User')} user * * @typedef {Request & RequestUserObject} RequestWithUser + * + * @typedef RequestEntityObject + * @property {import('../models/Author')} author + * + * @typedef {RequestWithUser & RequestEntityObject} AuthorControllerRequest */ class AuthorController { @@ -29,13 +34,13 @@ class AuthorController { /** * GET: /api/authors/:id * - * @param {RequestWithUser} req + * @param {AuthorControllerRequest} req * @param {Response} res */ async findOne(req, res) { const include = (req.query.include || '').split(',') - const authorJson = req.author.toJSON() + const authorJson = req.author.toOldJSON() // Used on author landing page to include library items and items grouped in series if (include.includes('items')) { @@ -80,25 +85,30 @@ class AuthorController { /** * PATCH: /api/authors/:id * - * @param {RequestWithUser} req + * @param {AuthorControllerRequest} req * @param {Response} res */ async update(req, res) { - const payload = req.body - let hasUpdated = false - - // author imagePath must be set through other endpoints as of v2.4.5 - if (payload.imagePath !== undefined) { - Logger.warn(`[AuthorController] Updating local author imagePath is not supported`) - delete payload.imagePath + const keysToUpdate = ['name', 'description', 'asin'] + const payload = {} + for (const key in req.body) { + if (keysToUpdate.includes(key) && (typeof req.body[key] === 'string' || req.body[key] === null)) { + payload[key] = req.body[key] + } } + if (!Object.keys(payload).length) { + Logger.error(`[AuthorController] Invalid request payload. No valid keys found`, req.body) + return res.status(400).send('Invalid request payload. No valid keys found') + } + + let hasUpdated = false const authorNameUpdate = payload.name !== undefined && payload.name !== req.author.name // Check if author name matches another author and merge the authors let existingAuthor = null if (authorNameUpdate) { - const author = await Database.authorModel.findOne({ + existingAuthor = await Database.authorModel.findOne({ where: { id: { [sequelize.Op.not]: req.author.id @@ -106,7 +116,6 @@ class AuthorController { name: payload.name } }) - existingAuthor = author?.getOldAuthor() } if (existingAuthor) { Logger.info(`[AuthorController] Merging author "${req.author.name}" with "${existingAuthor.name}"`) @@ -143,86 +152,87 @@ class AuthorController { } // Remove old author - await Database.removeAuthor(req.author.id) - SocketAuthority.emitter('author_removed', req.author.toJSON()) + const oldAuthorJSON = req.author.toOldJSON() + await req.author.destroy() + SocketAuthority.emitter('author_removed', oldAuthorJSON) // Update filter data - Database.removeAuthorFromFilterData(req.author.libraryId, req.author.id) + Database.removeAuthorFromFilterData(oldAuthorJSON.libraryId, oldAuthorJSON.id) // Send updated num books for merged author const numBooks = await Database.bookAuthorModel.getCountForAuthor(existingAuthor.id) - SocketAuthority.emitter('author_updated', existingAuthor.toJSONExpanded(numBooks)) + SocketAuthority.emitter('author_updated', existingAuthor.toOldJSONExpanded(numBooks)) res.json({ - author: existingAuthor.toJSON(), + author: existingAuthor.toOldJSON(), merged: true }) - } else { - // Regular author update - if (req.author.update(payload)) { - hasUpdated = true - } + return + } - if (hasUpdated) { - req.author.updatedAt = Date.now() + // Regular author update + req.author.set(payload) + if (req.author.changed()) { + await req.author.save() + hasUpdated = true + } - let numBooksForAuthor = 0 - if (authorNameUpdate) { - const allItemsWithAuthor = await Database.authorModel.getAllLibraryItemsForAuthor(req.author.id) + if (hasUpdated) { + let numBooksForAuthor = 0 + if (authorNameUpdate) { + const allItemsWithAuthor = await Database.authorModel.getAllLibraryItemsForAuthor(req.author.id) - numBooksForAuthor = allItemsWithAuthor.length - const oldLibraryItems = [] - // Update author name on all books - for (const libraryItem of allItemsWithAuthor) { - libraryItem.media.authors = libraryItem.media.authors.map((au) => { - if (au.id === req.author.id) { - au.name = req.author.name - } - return au - }) - const oldLibraryItem = Database.libraryItemModel.getOldLibraryItem(libraryItem) - oldLibraryItems.push(oldLibraryItem) + numBooksForAuthor = allItemsWithAuthor.length + const oldLibraryItems = [] + // Update author name on all books + for (const libraryItem of allItemsWithAuthor) { + libraryItem.media.authors = libraryItem.media.authors.map((au) => { + if (au.id === req.author.id) { + au.name = req.author.name + } + return au + }) + const oldLibraryItem = Database.libraryItemModel.getOldLibraryItem(libraryItem) + oldLibraryItems.push(oldLibraryItem) - await libraryItem.saveMetadataFile() - } - - if (oldLibraryItems.length) { - SocketAuthority.emitter( - 'items_updated', - oldLibraryItems.map((li) => li.toJSONExpanded()) - ) - } - } else { - numBooksForAuthor = await Database.bookAuthorModel.getCountForAuthor(req.author.id) + await libraryItem.saveMetadataFile() } - await Database.updateAuthor(req.author) - SocketAuthority.emitter('author_updated', req.author.toJSONExpanded(numBooksForAuthor)) + if (oldLibraryItems.length) { + SocketAuthority.emitter( + 'items_updated', + oldLibraryItems.map((li) => li.toJSONExpanded()) + ) + } + } else { + numBooksForAuthor = await Database.bookAuthorModel.getCountForAuthor(req.author.id) } - res.json({ - author: req.author.toJSON(), - updated: hasUpdated - }) + SocketAuthority.emitter('author_updated', req.author.toOldJSONExpanded(numBooksForAuthor)) } + + res.json({ + author: req.author.toOldJSON(), + updated: hasUpdated + }) } /** * DELETE: /api/authors/:id * Remove author from all books and delete * - * @param {RequestWithUser} req + * @param {AuthorControllerRequest} req * @param {Response} res */ async delete(req, res) { Logger.info(`[AuthorController] Removing author "${req.author.name}"`) - await Database.authorModel.removeById(req.author.id) - if (req.author.imagePath) { await CacheManager.purgeImageCache(req.author.id) // Purge cache } - SocketAuthority.emitter('author_removed', req.author.toJSON()) + await req.author.destroy() + + SocketAuthority.emitter('author_removed', req.author.toOldJSON()) // Update filter data Database.removeAuthorFromFilterData(req.author.libraryId, req.author.id) @@ -234,7 +244,7 @@ class AuthorController { * POST: /api/authors/:id/image * Upload author image from web URL * - * @param {RequestWithUser} req + * @param {AuthorControllerRequest} req * @param {Response} res */ async uploadImage(req, res) { @@ -265,13 +275,14 @@ class AuthorController { } req.author.imagePath = result.path - req.author.updatedAt = Date.now() - await Database.authorModel.updateFromOld(req.author) + // imagePath may not have changed, but we still want to update the updatedAt field to bust image cache + req.author.changed('imagePath', true) + await req.author.save() const numBooks = await Database.bookAuthorModel.getCountForAuthor(req.author.id) - SocketAuthority.emitter('author_updated', req.author.toJSONExpanded(numBooks)) + SocketAuthority.emitter('author_updated', req.author.toOldJSONExpanded(numBooks)) res.json({ - author: req.author.toJSON() + author: req.author.toOldJSON() }) } @@ -279,7 +290,7 @@ class AuthorController { * DELETE: /api/authors/:id/image * Remove author image & delete image file * - * @param {RequestWithUser} req + * @param {AuthorControllerRequest} req * @param {Response} res */ async deleteImage(req, res) { @@ -291,19 +302,19 @@ class AuthorController { await CacheManager.purgeImageCache(req.author.id) // Purge cache await CoverManager.removeFile(req.author.imagePath) req.author.imagePath = null - await Database.authorModel.updateFromOld(req.author) + await req.author.save() const numBooks = await Database.bookAuthorModel.getCountForAuthor(req.author.id) - SocketAuthority.emitter('author_updated', req.author.toJSONExpanded(numBooks)) + SocketAuthority.emitter('author_updated', req.author.toOldJSONExpanded(numBooks)) res.json({ - author: req.author.toJSON() + author: req.author.toOldJSON() }) } /** * POST: /api/authors/:id/match * - * @param {RequestWithUser} req + * @param {AuthorControllerRequest} req * @param {Response} res */ async match(req, res) { @@ -342,24 +353,22 @@ class AuthorController { } if (hasUpdates) { - req.author.updatedAt = Date.now() - - await Database.updateAuthor(req.author) + await req.author.save() const numBooks = await Database.bookAuthorModel.getCountForAuthor(req.author.id) - SocketAuthority.emitter('author_updated', req.author.toJSONExpanded(numBooks)) + SocketAuthority.emitter('author_updated', req.author.toOldJSONExpanded(numBooks)) } res.json({ updated: hasUpdates, - author: req.author + author: req.author.toOldJSON() }) } /** * GET: /api/authors/:id/image * - * @param {RequestWithUser} req + * @param {AuthorControllerRequest} req * @param {Response} res */ async getImage(req, res) { @@ -392,7 +401,7 @@ class AuthorController { * @param {NextFunction} next */ async middleware(req, res, next) { - const author = await Database.authorModel.getOldById(req.params.id) + const author = await Database.authorModel.findByPk(req.params.id) if (!author) return res.sendStatus(404) if (req.method == 'DELETE' && !req.user.canDelete) { diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js index 59e8c181..9d6be80e 100644 --- a/server/controllers/LibraryController.js +++ b/server/controllers/LibraryController.js @@ -887,8 +887,7 @@ class LibraryController { const oldAuthors = [] for (const author of authors) { - const oldAuthor = author.getOldAuthor().toJSON() - oldAuthor.numBooks = author.books.length + const oldAuthor = author.toOldJSONExpanded(author.books.length) oldAuthor.lastFirst = author.lastFirst oldAuthors.push(oldAuthor) } diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js index 9a87f7a7..472f5678 100644 --- a/server/controllers/LibraryItemController.js +++ b/server/controllers/LibraryItemController.js @@ -151,6 +151,8 @@ class LibraryItemController { * PATCH: /items/:id/media * Update media for a library item. Will create new authors & series when necessary * + * @this {import('../routers/ApiRouter')} + * * @param {RequestWithUser} req * @param {Response} res */ @@ -185,6 +187,12 @@ class LibraryItemController { seriesRemoved = libraryItem.media.metadata.series.filter((se) => !seriesIdsInUpdate.includes(se.id)) } + let authorsRemoved = [] + if (libraryItem.isBook && mediaPayload.metadata?.authors) { + const authorIdsInUpdate = mediaPayload.metadata.authors.map((au) => au.id) + authorsRemoved = libraryItem.media.metadata.authors.filter((au) => !authorIdsInUpdate.includes(au.id)) + } + const hasUpdates = libraryItem.media.update(mediaPayload) || mediaPayload.url if (hasUpdates) { libraryItem.updatedAt = Date.now() @@ -205,6 +213,15 @@ class LibraryItemController { Logger.debug(`[LibraryItemController] Updated library item media ${libraryItem.media.metadata.title}`) await Database.updateLibraryItem(libraryItem) SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded()) + + if (authorsRemoved.length) { + // Check remove empty authors + Logger.debug(`[LibraryItemController] Authors were removed from book. Check if authors are now empty.`) + await this.checkRemoveAuthorsWithNoBooks( + libraryItem.libraryId, + authorsRemoved.map((au) => au.id) + ) + } } res.json({ updated: hasUpdates, @@ -823,7 +840,7 @@ class LibraryItemController { // We actually need to check for Webkit on Apple mobile devices because this issue impacts all browsers on iOS/iPadOS/etc, not just Safari. const isAppleMobileBrowser = ua.device.vendor === 'Apple' && ua.device.type === 'mobile' && ua.engine.name === 'WebKit' if (isAppleMobileBrowser && audioMimeType === AudioMimeType.M4B) { - audioMimeType = 'audio/m4b' + audioMimeType = 'audio/m4b' } res.setHeader('Content-Type', audioMimeType) } diff --git a/server/managers/CacheManager.js b/server/managers/CacheManager.js index 8f810a33..b4d2f270 100644 --- a/server/managers/CacheManager.js +++ b/server/managers/CacheManager.js @@ -124,6 +124,13 @@ class CacheManager { await this.ensureCachePaths() } + /** + * + * @param {import('express').Response} res + * @param {import('../models/Author')} author + * @param {{ format?: string, width?: number, height?: number }} options + * @returns + */ async handleAuthorCache(res, author, options = {}) { const format = options.format || 'webp' const width = options.width || 400 diff --git a/server/models/Author.js b/server/models/Author.js index a49141d7..1668d9e7 100644 --- a/server/models/Author.js +++ b/server/models/Author.js @@ -1,7 +1,5 @@ const { DataTypes, Model, where, fn, col } = require('sequelize') -const oldAuthor = require('../objects/entities/Author') - class Author extends Model { constructor(values, options) { super(values, options) @@ -26,69 +24,6 @@ class Author extends Model { this.createdAt } - getOldAuthor() { - return new oldAuthor({ - id: this.id, - asin: this.asin, - name: this.name, - description: this.description, - imagePath: this.imagePath, - libraryId: this.libraryId, - addedAt: this.createdAt.valueOf(), - updatedAt: this.updatedAt.valueOf() - }) - } - - static updateFromOld(oldAuthor) { - const author = this.getFromOld(oldAuthor) - return this.update(author, { - where: { - id: author.id - } - }) - } - - static createFromOld(oldAuthor) { - const author = this.getFromOld(oldAuthor) - return this.create(author) - } - - static createBulkFromOld(oldAuthors) { - const authors = oldAuthors.map(this.getFromOld) - return this.bulkCreate(authors) - } - - static getFromOld(oldAuthor) { - return { - id: oldAuthor.id, - name: oldAuthor.name, - lastFirst: oldAuthor.lastFirst, - asin: oldAuthor.asin, - description: oldAuthor.description, - imagePath: oldAuthor.imagePath, - libraryId: oldAuthor.libraryId - } - } - - static removeById(authorId) { - return this.destroy({ - where: { - id: authorId - } - }) - } - - /** - * Get oldAuthor by id - * @param {string} authorId - * @returns {Promise} - */ - static async getOldById(authorId) { - const author = await this.findByPk(authorId) - if (!author) return null - return author.getOldAuthor() - } - /** * Check if author exists * @param {string} authorId @@ -99,25 +34,22 @@ class Author extends Model { } /** - * Get old author by name and libraryId. name case insensitive + * Get author by name and libraryId. name case insensitive * TODO: Look for authors ignoring punctuation * * @param {string} authorName * @param {string} libraryId - * @returns {Promise} + * @returns {Promise} */ - static async getOldByNameAndLibrary(authorName, libraryId) { - const author = ( - await this.findOne({ - where: [ - where(fn('lower', col('name')), authorName.toLowerCase()), - { - libraryId - } - ] - }) - )?.getOldAuthor() - return author + static async getByNameAndLibrary(authorName, libraryId) { + return this.findOne({ + where: [ + where(fn('lower', col('name')), authorName.toLowerCase()), + { + libraryId + } + ] + }) } /** @@ -213,5 +145,36 @@ class Author extends Model { }) Author.belongsTo(library) } + + toOldJSON() { + return { + id: this.id, + asin: this.asin, + name: this.name, + description: this.description, + imagePath: this.imagePath, + libraryId: this.libraryId, + addedAt: this.createdAt.valueOf(), + updatedAt: this.updatedAt.valueOf() + } + } + + /** + * + * @param {number} numBooks + * @returns + */ + toOldJSONExpanded(numBooks = 0) { + const oldJson = this.toOldJSON() + oldJson.numBooks = numBooks + return oldJson + } + + toJSONMinimal() { + return { + id: this.id, + name: this.name + } + } } module.exports = Author diff --git a/server/models/LibraryItem.js b/server/models/LibraryItem.js index 54f85ade..3f585ee0 100644 --- a/server/models/LibraryItem.js +++ b/server/models/LibraryItem.js @@ -773,7 +773,7 @@ class LibraryItem extends Model { /** * Get book library items for author, optional use user permissions - * @param {oldAuthor} author + * @param {import('./Author')} author * @param {import('./User')} user * @returns {Promise} */ diff --git a/server/objects/entities/Author.js b/server/objects/entities/Author.js deleted file mode 100644 index 3d7c0e3c..00000000 --- a/server/objects/entities/Author.js +++ /dev/null @@ -1,101 +0,0 @@ -const Logger = require('../../Logger') -const uuidv4 = require("uuid").v4 -const { checkNamesAreEqual, nameToLastFirst } = require('../../utils/parsers/parseNameString') - -class Author { - constructor(author) { - this.id = null - this.asin = null - this.name = null - this.description = null - this.imagePath = null - this.addedAt = null - this.updatedAt = null - this.libraryId = null - - if (author) { - this.construct(author) - } - } - - construct(author) { - this.id = author.id - this.asin = author.asin - this.name = author.name || '' - this.description = author.description || null - this.imagePath = author.imagePath - this.addedAt = author.addedAt - this.updatedAt = author.updatedAt - this.libraryId = author.libraryId - } - - get lastFirst() { - if (!this.name) return '' - return nameToLastFirst(this.name) - } - - toJSON() { - return { - id: this.id, - asin: this.asin, - name: this.name, - description: this.description, - imagePath: this.imagePath, - addedAt: this.addedAt, - updatedAt: this.updatedAt, - libraryId: this.libraryId - } - } - - toJSONExpanded(numBooks = 0) { - const json = this.toJSON() - json.numBooks = numBooks - return json - } - - toJSONMinimal() { - return { - id: this.id, - name: this.name - } - } - - setData(data, libraryId) { - this.id = uuidv4() - if (!data.name) { - Logger.error(`[Author] setData: Setting author data without a name`, data) - } - this.name = data.name || '' - this.description = data.description || null - this.asin = data.asin || null - this.imagePath = data.imagePath || null - this.addedAt = Date.now() - this.updatedAt = Date.now() - this.libraryId = libraryId - } - - update(payload) { - const json = this.toJSON() - delete json.id - delete json.addedAt - delete json.updatedAt - let hasUpdates = false - for (const key in json) { - if (payload[key] !== undefined && json[key] != payload[key]) { - this[key] = payload[key] - hasUpdates = true - } - } - return hasUpdates - } - - checkNameEquals(name) { - if (!name) return false - if (this.name === null) { - Logger.error(`[Author] Author name is null (${this.id})`) - return false - } - return checkNamesAreEqual(this.name, name) - } -} -module.exports = Author \ No newline at end of file diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index e6e5a694..484377e0 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -1,5 +1,6 @@ const express = require('express') const Path = require('path') +const sequelize = require('sequelize') const Logger = require('../Logger') const Database = require('../Database') @@ -32,7 +33,6 @@ const CustomMetadataProviderController = require('../controllers/CustomMetadataP const MiscController = require('../controllers/MiscController') const ShareController = require('../controllers/ShareController') -const Author = require('../objects/entities/Author') const Series = require('../objects/entities/Series') class ApiRouter { @@ -469,6 +469,54 @@ class ApiRouter { } } + /** + * Remove authors with no books and unset asin, description and imagePath + * Note: Other implementation is in BookScanner.checkAuthorsRemovedFromBooks (can be merged) + * + * @param {string} libraryId + * @param {string[]} authorIds + * @returns {Promise} + */ + async checkRemoveAuthorsWithNoBooks(libraryId, authorIds) { + if (!authorIds?.length) return + + const bookAuthorsToRemove = ( + await Database.authorModel.findAll({ + where: [ + { + id: authorIds, + asin: { + [sequelize.Op.or]: [null, ''] + }, + description: { + [sequelize.Op.or]: [null, ''] + }, + imagePath: { + [sequelize.Op.or]: [null, ''] + } + }, + sequelize.where(sequelize.literal('(SELECT count(*) FROM bookAuthors ba WHERE ba.authorId = author.id)'), 0) + ], + attributes: ['id', 'name'], + raw: true + }) + ).map((au) => ({ id: au.id, name: au.name })) + + if (bookAuthorsToRemove.length) { + await Database.authorModel.destroy({ + where: { + id: bookAuthorsToRemove.map((au) => au.id) + } + }) + bookAuthorsToRemove.forEach(({ id, name }) => { + Database.removeAuthorFromFilterData(libraryId, id) + // TODO: Clients were expecting full author in payload but its unnecessary + SocketAuthority.emitter('author_removed', { id, libraryId }) + Logger.info(`[ApiRouter] Removed author "${name}" with no books`) + }) + } + } + /** * Remove an empty series & close an open RSS feed * @param {import('../models/Series')} series @@ -567,11 +615,13 @@ class ApiRouter { } if (!mediaMetadata.authors[i].id) { - let author = await Database.authorModel.getOldByNameAndLibrary(authorName, libraryId) + let author = await Database.authorModel.getByNameAndLibrary(authorName, libraryId) if (!author) { - author = new Author() - author.setData(mediaMetadata.authors[i], libraryId) - Logger.debug(`[ApiRouter] Created new author "${author.name}"`) + author = await Database.authorModel.create({ + name: authorName, + libraryId + }) + Logger.debug(`[ApiRouter] Creating new author "${author.name}"`) newAuthors.push(author) // Update filter data Database.addAuthorToFilterData(libraryId, author.name, author.id) @@ -584,10 +634,9 @@ class ApiRouter { // Remove authors without an id mediaMetadata.authors = mediaMetadata.authors.filter((au) => !!au.id) if (newAuthors.length) { - await Database.createBulkAuthors(newAuthors) SocketAuthority.emitter( 'authors_added', - newAuthors.map((au) => au.toJSON()) + newAuthors.map((au) => au.toOldJSON()) ) } } diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js index 4d67248c..5508ff18 100644 --- a/server/scanner/Scanner.js +++ b/server/scanner/Scanner.js @@ -8,7 +8,6 @@ const { findMatchingEpisodesInFeed, getPodcastFeed } = require('../utils/podcast const BookFinder = require('../finders/BookFinder') const PodcastFinder = require('../finders/PodcastFinder') const LibraryScan = require('./LibraryScan') -const Author = require('../objects/entities/Author') const Series = require('../objects/entities/Series') const LibraryScanner = require('./LibraryScanner') const CoverManager = require('../managers/CoverManager') @@ -206,12 +205,13 @@ class Scanner { } const authorPayload = [] for (const authorName of matchData.author) { - let author = await Database.authorModel.getOldByNameAndLibrary(authorName, libraryItem.libraryId) + let author = await Database.authorModel.getByNameAndLibrary(authorName, libraryItem.libraryId) if (!author) { - author = new Author() - author.setData({ name: authorName }, libraryItem.libraryId) - await Database.createAuthor(author) - SocketAuthority.emitter('author_added', author.toJSON()) + author = await Database.authorModel.create({ + name: authorName, + libraryId: libraryItem.libraryId + }) + SocketAuthority.emitter('author_added', author.toOldJSON()) // Update filter data Database.addAuthorToFilterData(libraryItem.libraryId, author.name, author.id) } diff --git a/server/utils/parsers/parseNameString.js b/server/utils/parsers/parseNameString.js index c1f8ab3f..741beb09 100644 --- a/server/utils/parsers/parseNameString.js +++ b/server/utils/parsers/parseNameString.js @@ -42,15 +42,15 @@ module.exports.parse = (nameString) => { var splitNames = [] // Example &LF: Friedman, Milton & Friedman, Rose if (nameString.includes('&')) { - nameString.split('&').forEach((asa) => splitNames = splitNames.concat(asa.split(','))) + nameString.split('&').forEach((asa) => (splitNames = splitNames.concat(asa.split(',')))) } else if (nameString.includes(' and ')) { - nameString.split(' and ').forEach((asa) => splitNames = splitNames.concat(asa.split(','))) + nameString.split(' and ').forEach((asa) => (splitNames = splitNames.concat(asa.split(',')))) } else if (nameString.includes(';')) { - nameString.split(';').forEach((asa) => splitNames = splitNames.concat(asa.split(','))) + nameString.split(';').forEach((asa) => (splitNames = splitNames.concat(asa.split(',')))) } else { splitNames = nameString.split(',') } - if (splitNames.length) splitNames = splitNames.map(a => a.trim()) + if (splitNames.length) splitNames = splitNames.map((a) => a.trim()) var names = [] @@ -84,21 +84,12 @@ module.exports.parse = (nameString) => { } // Filter out names that have no first and last - names = names.filter(n => n.first_name || n.last_name) + names = names.filter((n) => n.first_name || n.last_name) // Set name strings and remove duplicates - const namesArray = [...new Set(names.map(a => a.first_name ? `${a.first_name} ${a.last_name}` : a.last_name))] + const namesArray = [...new Set(names.map((a) => (a.first_name ? `${a.first_name} ${a.last_name}` : a.last_name)))] return { names: namesArray // Array of first last } } - -module.exports.checkNamesAreEqual = (name1, name2) => { - if (!name1 || !name2) return false - - // e.g. John H. Smith will be equal to John H Smith - name1 = String(name1).toLowerCase().trim().replace(/\./g, '') - name2 = String(name2).toLowerCase().trim().replace(/\./g, '') - return name1 === name2 -} \ No newline at end of file diff --git a/server/utils/queries/authorFilters.js b/server/utils/queries/authorFilters.js index bd4d0892..67591535 100644 --- a/server/utils/queries/authorFilters.js +++ b/server/utils/queries/authorFilters.js @@ -73,8 +73,7 @@ module.exports = { }) const authorMatches = [] for (const author of authors) { - const oldAuthor = author.getOldAuthor().toJSON() - oldAuthor.numBooks = author.dataValues.numBooks + const oldAuthor = author.toOldJSONExpanded(author.dataValues.numBooks) authorMatches.push(oldAuthor) } return authorMatches diff --git a/server/utils/queries/libraryFilters.js b/server/utils/queries/libraryFilters.js index 2268ef21..39c50856 100644 --- a/server/utils/queries/libraryFilters.js +++ b/server/utils/queries/libraryFilters.js @@ -353,7 +353,7 @@ module.exports = { return { authors: authors.map((au) => { const numBooks = au.books.length || 0 - return au.getOldAuthor().toJSONExpanded(numBooks) + return au.toOldJSONExpanded(numBooks) }), count } @@ -409,7 +409,7 @@ module.exports = { /** * Get library items for an author, optional use user permissions - * @param {oldAuthor} author + * @param {import('../../models/Author')} author * @param {import('../../models/User')} user * @param {number} limit * @param {number} offset