@@ -21,6 +21,11 @@ const naturalSort = createNewSortInstance({
21
21
* @property {import('../models/User') } user
22
22
*
23
23
* @typedef {Request & RequestUserObject } RequestWithUser
24
+ *
25
+ * @typedef RequestEntityObject
26
+ * @property {import('../models/Author') } author
27
+ *
28
+ * @typedef {RequestWithUser & RequestEntityObject } AuthorControllerRequest
24
29
*/
25
30
26
31
class AuthorController {
@@ -29,13 +34,13 @@ class AuthorController {
29
34
/**
30
35
* GET: /api/authors/:id
31
36
*
32
- * @param {RequestWithUser } req
37
+ * @param {AuthorControllerRequest } req
33
38
* @param {Response } res
34
39
*/
35
40
async findOne ( req , res ) {
36
41
const include = ( req . query . include || '' ) . split ( ',' )
37
42
38
- const authorJson = req . author . toJSON ( )
43
+ const authorJson = req . author . toOldJSON ( )
39
44
40
45
// Used on author landing page to include library items and items grouped in series
41
46
if ( include . includes ( 'items' ) ) {
@@ -80,33 +85,37 @@ class AuthorController {
80
85
/**
81
86
* PATCH: /api/authors/:id
82
87
*
83
- * @param {RequestWithUser } req
88
+ * @param {AuthorControllerRequest } req
84
89
* @param {Response } res
85
90
*/
86
91
async update ( req , res ) {
87
- const payload = req . body
88
- let hasUpdated = false
89
-
90
- // author imagePath must be set through other endpoints as of v2.4.5
91
- if ( payload . imagePath !== undefined ) {
92
- Logger . warn ( `[AuthorController] Updating local author imagePath is not supported` )
93
- delete payload . imagePath
92
+ const keysToUpdate = [ 'name' , 'description' , 'asin' ]
93
+ const payload = { }
94
+ for ( const key in req . body ) {
95
+ if ( keysToUpdate . includes ( key ) && ( typeof req . body [ key ] === 'string' || req . body [ key ] === null ) ) {
96
+ payload [ key ] = req . body [ key ]
97
+ }
94
98
}
99
+ if ( ! Object . keys ( payload ) . length ) {
100
+ Logger . error ( `[AuthorController] Invalid request payload. No valid keys found` , req . body )
101
+ return res . status ( 400 ) . send ( 'Invalid request payload. No valid keys found' )
102
+ }
103
+
104
+ let hasUpdated = false
95
105
96
106
const authorNameUpdate = payload . name !== undefined && payload . name !== req . author . name
97
107
98
108
// Check if author name matches another author and merge the authors
99
109
let existingAuthor = null
100
110
if ( authorNameUpdate ) {
101
- const author = await Database . authorModel . findOne ( {
111
+ existingAuthor = await Database . authorModel . findOne ( {
102
112
where : {
103
113
id : {
104
114
[ sequelize . Op . not ] : req . author . id
105
115
} ,
106
116
name : payload . name
107
117
}
108
118
} )
109
- existingAuthor = author ?. getOldAuthor ( )
110
119
}
111
120
if ( existingAuthor ) {
112
121
Logger . info ( `[AuthorController] Merging author "${ req . author . name } " with "${ existingAuthor . name } "` )
@@ -143,86 +152,87 @@ class AuthorController {
143
152
}
144
153
145
154
// Remove old author
146
- await Database . removeAuthor ( req . author . id )
147
- SocketAuthority . emitter ( 'author_removed' , req . author . toJSON ( ) )
155
+ const oldAuthorJSON = req . author . toOldJSON ( )
156
+ await req . author . destroy ( )
157
+ SocketAuthority . emitter ( 'author_removed' , oldAuthorJSON )
148
158
// Update filter data
149
- Database . removeAuthorFromFilterData ( req . author . libraryId , req . author . id )
159
+ Database . removeAuthorFromFilterData ( oldAuthorJSON . libraryId , oldAuthorJSON . id )
150
160
151
161
// Send updated num books for merged author
152
162
const numBooks = await Database . bookAuthorModel . getCountForAuthor ( existingAuthor . id )
153
- SocketAuthority . emitter ( 'author_updated' , existingAuthor . toJSONExpanded ( numBooks ) )
163
+ SocketAuthority . emitter ( 'author_updated' , existingAuthor . toOldJSONExpanded ( numBooks ) )
154
164
155
165
res . json ( {
156
- author : existingAuthor . toJSON ( ) ,
166
+ author : existingAuthor . toOldJSON ( ) ,
157
167
merged : true
158
168
} )
159
- } else {
160
- // Regular author update
161
- if ( req . author . update ( payload ) ) {
162
- hasUpdated = true
163
- }
164
-
165
- if ( hasUpdated ) {
166
- req . author . updatedAt = Date . now ( )
169
+ return
170
+ }
167
171
168
- let numBooksForAuthor = 0
169
- if ( authorNameUpdate ) {
170
- const allItemsWithAuthor = await Database . authorModel . getAllLibraryItemsForAuthor ( req . author . id )
172
+ // Regular author update
173
+ req . author . set ( payload )
174
+ if ( req . author . changed ( ) ) {
175
+ await req . author . save ( )
176
+ hasUpdated = true
177
+ }
171
178
172
- numBooksForAuthor = allItemsWithAuthor . length
173
- const oldLibraryItems = [ ]
174
- // Update author name on all books
175
- for ( const libraryItem of allItemsWithAuthor ) {
176
- libraryItem . media . authors = libraryItem . media . authors . map ( ( au ) => {
177
- if ( au . id === req . author . id ) {
178
- au . name = req . author . name
179
- }
180
- return au
181
- } )
182
- const oldLibraryItem = Database . libraryItemModel . getOldLibraryItem ( libraryItem )
183
- oldLibraryItems . push ( oldLibraryItem )
179
+ if ( hasUpdated ) {
180
+ let numBooksForAuthor = 0
181
+ if ( authorNameUpdate ) {
182
+ const allItemsWithAuthor = await Database . authorModel . getAllLibraryItemsForAuthor ( req . author . id )
184
183
185
- await libraryItem . saveMetadataFile ( )
186
- }
184
+ numBooksForAuthor = allItemsWithAuthor . length
185
+ const oldLibraryItems = [ ]
186
+ // Update author name on all books
187
+ for ( const libraryItem of allItemsWithAuthor ) {
188
+ libraryItem . media . authors = libraryItem . media . authors . map ( ( au ) => {
189
+ if ( au . id === req . author . id ) {
190
+ au . name = req . author . name
191
+ }
192
+ return au
193
+ } )
194
+ const oldLibraryItem = Database . libraryItemModel . getOldLibraryItem ( libraryItem )
195
+ oldLibraryItems . push ( oldLibraryItem )
187
196
188
- if ( oldLibraryItems . length ) {
189
- SocketAuthority . emitter (
190
- 'items_updated' ,
191
- oldLibraryItems . map ( ( li ) => li . toJSONExpanded ( ) )
192
- )
193
- }
194
- } else {
195
- numBooksForAuthor = await Database . bookAuthorModel . getCountForAuthor ( req . author . id )
197
+ await libraryItem . saveMetadataFile ( )
196
198
}
197
199
198
- await Database . updateAuthor ( req . author )
199
- SocketAuthority . emitter ( 'author_updated' , req . author . toJSONExpanded ( numBooksForAuthor ) )
200
+ if ( oldLibraryItems . length ) {
201
+ SocketAuthority . emitter (
202
+ 'items_updated' ,
203
+ oldLibraryItems . map ( ( li ) => li . toJSONExpanded ( ) )
204
+ )
205
+ }
206
+ } else {
207
+ numBooksForAuthor = await Database . bookAuthorModel . getCountForAuthor ( req . author . id )
200
208
}
201
209
202
- res . json ( {
203
- author : req . author . toJSON ( ) ,
204
- updated : hasUpdated
205
- } )
210
+ SocketAuthority . emitter ( 'author_updated' , req . author . toOldJSONExpanded ( numBooksForAuthor ) )
206
211
}
212
+
213
+ res . json ( {
214
+ author : req . author . toOldJSON ( ) ,
215
+ updated : hasUpdated
216
+ } )
207
217
}
208
218
209
219
/**
210
220
* DELETE: /api/authors/:id
211
221
* Remove author from all books and delete
212
222
*
213
- * @param {RequestWithUser } req
223
+ * @param {AuthorControllerRequest } req
214
224
* @param {Response } res
215
225
*/
216
226
async delete ( req , res ) {
217
227
Logger . info ( `[AuthorController] Removing author "${ req . author . name } "` )
218
228
219
- await Database . authorModel . removeById ( req . author . id )
220
-
221
229
if ( req . author . imagePath ) {
222
230
await CacheManager . purgeImageCache ( req . author . id ) // Purge cache
223
231
}
224
232
225
- SocketAuthority . emitter ( 'author_removed' , req . author . toJSON ( ) )
233
+ await req . author . destroy ( )
234
+
235
+ SocketAuthority . emitter ( 'author_removed' , req . author . toOldJSON ( ) )
226
236
227
237
// Update filter data
228
238
Database . removeAuthorFromFilterData ( req . author . libraryId , req . author . id )
@@ -234,7 +244,7 @@ class AuthorController {
234
244
* POST: /api/authors/:id/image
235
245
* Upload author image from web URL
236
246
*
237
- * @param {RequestWithUser } req
247
+ * @param {AuthorControllerRequest } req
238
248
* @param {Response } res
239
249
*/
240
250
async uploadImage ( req , res ) {
@@ -265,21 +275,22 @@ class AuthorController {
265
275
}
266
276
267
277
req . author . imagePath = result . path
268
- req . author . updatedAt = Date . now ( )
269
- await Database . authorModel . updateFromOld ( req . author )
278
+ // imagePath may not have changed, but we still want to update the updatedAt field to bust image cache
279
+ req . author . changed ( 'imagePath' , true )
280
+ await req . author . save ( )
270
281
271
282
const numBooks = await Database . bookAuthorModel . getCountForAuthor ( req . author . id )
272
- SocketAuthority . emitter ( 'author_updated' , req . author . toJSONExpanded ( numBooks ) )
283
+ SocketAuthority . emitter ( 'author_updated' , req . author . toOldJSONExpanded ( numBooks ) )
273
284
res . json ( {
274
- author : req . author . toJSON ( )
285
+ author : req . author . toOldJSON ( )
275
286
} )
276
287
}
277
288
278
289
/**
279
290
* DELETE: /api/authors/:id/image
280
291
* Remove author image & delete image file
281
292
*
282
- * @param {RequestWithUser } req
293
+ * @param {AuthorControllerRequest } req
283
294
* @param {Response } res
284
295
*/
285
296
async deleteImage ( req , res ) {
@@ -291,19 +302,19 @@ class AuthorController {
291
302
await CacheManager . purgeImageCache ( req . author . id ) // Purge cache
292
303
await CoverManager . removeFile ( req . author . imagePath )
293
304
req . author . imagePath = null
294
- await Database . authorModel . updateFromOld ( req . author )
305
+ await req . author . save ( )
295
306
296
307
const numBooks = await Database . bookAuthorModel . getCountForAuthor ( req . author . id )
297
- SocketAuthority . emitter ( 'author_updated' , req . author . toJSONExpanded ( numBooks ) )
308
+ SocketAuthority . emitter ( 'author_updated' , req . author . toOldJSONExpanded ( numBooks ) )
298
309
res . json ( {
299
- author : req . author . toJSON ( )
310
+ author : req . author . toOldJSON ( )
300
311
} )
301
312
}
302
313
303
314
/**
304
315
* POST: /api/authors/:id/match
305
316
*
306
- * @param {RequestWithUser } req
317
+ * @param {AuthorControllerRequest } req
307
318
* @param {Response } res
308
319
*/
309
320
async match ( req , res ) {
@@ -342,24 +353,22 @@ class AuthorController {
342
353
}
343
354
344
355
if ( hasUpdates ) {
345
- req . author . updatedAt = Date . now ( )
346
-
347
- await Database . updateAuthor ( req . author )
356
+ await req . author . save ( )
348
357
349
358
const numBooks = await Database . bookAuthorModel . getCountForAuthor ( req . author . id )
350
- SocketAuthority . emitter ( 'author_updated' , req . author . toJSONExpanded ( numBooks ) )
359
+ SocketAuthority . emitter ( 'author_updated' , req . author . toOldJSONExpanded ( numBooks ) )
351
360
}
352
361
353
362
res . json ( {
354
363
updated : hasUpdates ,
355
- author : req . author
364
+ author : req . author . toOldJSON ( )
356
365
} )
357
366
}
358
367
359
368
/**
360
369
* GET: /api/authors/:id/image
361
370
*
362
- * @param {RequestWithUser } req
371
+ * @param {AuthorControllerRequest } req
363
372
* @param {Response } res
364
373
*/
365
374
async getImage ( req , res ) {
@@ -392,7 +401,7 @@ class AuthorController {
392
401
* @param {NextFunction } next
393
402
*/
394
403
async middleware ( req , res , next ) {
395
- const author = await Database . authorModel . getOldById ( req . params . id )
404
+ const author = await Database . authorModel . findByPk ( req . params . id )
396
405
if ( ! author ) return res . sendStatus ( 404 )
397
406
398
407
if ( req . method == 'DELETE' && ! req . user . canDelete ) {
0 commit comments