Remove old Author object & fix issue deleting empty authors

This commit is contained in:
advplyr 2024-08-31 13:27:48 -05:00
parent acc4bdbc5e
commit ba742563c2
13 changed files with 227 additions and 314 deletions

View file

@ -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) {