2024-08-11 15:15:34 -05:00
const { Request , Response , NextFunction } = require ( 'express' )
2022-03-11 19:46:32 -06:00
const Logger = require ( '../Logger' )
2022-11-24 15:53:58 -06:00
const SocketAuthority = require ( '../SocketAuthority' )
2023-07-04 18:14:44 -05:00
const Database = require ( '../Database' )
2026-04-21 19:38:34 -07:00
const OpenAI = require ( '../providers/OpenAI' )
2024-12-15 12:37:01 -06:00
const RssFeedManager = require ( '../managers/RssFeedManager' )
2023-08-13 15:10:26 -05:00
const libraryItemsBookFilters = require ( '../utils/queries/libraryItemsBookFilters' )
2022-03-11 19:46:32 -06:00
2026-04-21 19:38:34 -07:00
const openAI = new OpenAI ( )
2024-08-11 15:15:34 -05:00
/ * *
2024-08-11 17:01:25 -05:00
* @ typedef RequestUserObject
2024-08-11 16:07:29 -05:00
* @ property { import ( '../models/User' ) } user
2024-08-11 15:15:34 -05:00
*
2024-08-11 17:01:25 -05:00
* @ typedef { Request & RequestUserObject } RequestWithUser
2024-09-01 15:08:56 -05:00
*
* @ typedef RequestEntityObject
* @ property { import ( '../models/Series' ) } series
*
* @ typedef { RequestWithUser & RequestEntityObject } SeriesControllerRequest
2024-08-11 15:15:34 -05:00
* /
2022-03-11 19:46:32 -06:00
class SeriesController {
2024-08-10 17:15:21 -05:00
constructor ( ) { }
2022-03-11 19:46:32 -06:00
2023-07-07 17:59:17 -05:00
/ * *
* @ deprecated
* / a p i / s e r i e s / : i d
2024-08-10 17:15:21 -05:00
*
2023-07-07 17:59:17 -05:00
* TODO : Update mobile app to use / api / libraries / : id / series / : seriesId API route instead
2023-07-08 09:57:32 -05:00
* Series are not library specific so we need to know what the library id is
2024-08-10 17:15:21 -05:00
*
2024-09-01 15:08:56 -05:00
* @ param { SeriesControllerRequest } req
2024-08-11 15:15:34 -05:00
* @ param { Response } res
2023-07-07 17:59:17 -05:00
* /
2022-03-12 18:50:31 -06:00
async findOne ( req , res ) {
2024-08-10 17:15:21 -05:00
const include = ( req . query . include || '' )
. split ( ',' )
. map ( ( v ) => v . trim ( ) )
. filter ( ( v ) => ! ! v )
2022-04-24 17:46:21 -05:00
2024-09-01 15:08:56 -05:00
const seriesJson = req . series . toOldJSON ( )
2022-04-24 17:46:21 -05:00
// Add progress map with isFinished flag
if ( include . includes ( 'progress' ) ) {
2023-05-28 08:51:34 -05:00
const libraryItemsInSeries = req . libraryItemsInSeries
2024-08-10 17:15:21 -05:00
const libraryItemsFinished = libraryItemsInSeries . filter ( ( li ) => {
2024-08-11 16:07:29 -05:00
return req . user . getMediaProgress ( li . media . id ) ? . isFinished
2022-04-24 17:46:21 -05:00
} )
seriesJson . progress = {
2024-08-10 17:15:21 -05:00
libraryItemIds : libraryItemsInSeries . map ( ( li ) => li . id ) ,
libraryItemIdsFinished : libraryItemsFinished . map ( ( li ) => li . id ) ,
2022-04-24 17:46:21 -05:00
isFinished : libraryItemsFinished . length === libraryItemsInSeries . length
}
}
2022-12-31 16:58:19 -06:00
if ( include . includes ( 'rssfeed' ) ) {
2024-12-15 12:37:01 -06:00
const feedObj = await RssFeedManager . findFeedForEntityId ( seriesJson . id )
2024-12-15 17:54:36 -06:00
seriesJson . rssFeed = feedObj ? . toOldJSONMinified ( ) || null
2022-12-31 16:58:19 -06:00
}
2023-07-07 17:59:17 -05:00
res . json ( seriesJson )
2022-03-12 18:50:31 -06:00
}
2024-08-11 15:15:34 -05:00
/ * *
2024-09-01 15:26:43 -05:00
* TODO : Currently unused in the client , should check for duplicate name
2024-08-11 15:15:34 -05:00
*
2024-09-01 15:08:56 -05:00
* @ param { SeriesControllerRequest } req
2024-08-11 15:15:34 -05:00
* @ param { Response } res
* /
2022-09-27 17:48:45 -05:00
async update ( req , res ) {
2024-09-01 15:26:43 -05:00
const keysToUpdate = [ 'name' , 'description' ]
const payload = { }
for ( const key of keysToUpdate ) {
if ( req . body [ key ] !== undefined && typeof req . body [ key ] === 'string' ) {
payload [ key ] = req . body [ key ]
}
}
if ( ! Object . keys ( payload ) . length ) {
return res . status ( 400 ) . send ( 'No valid fields to update' )
}
req . series . set ( payload )
if ( req . series . changed ( ) ) {
await req . series . save ( )
SocketAuthority . emitter ( 'series_updated' , req . series . toOldJSON ( ) )
2022-09-27 17:48:45 -05:00
}
2024-09-01 15:26:43 -05:00
res . json ( req . series . toOldJSON ( ) )
2022-09-27 17:48:45 -05:00
}
2026-04-21 19:38:34 -07:00
/ * *
* POST : / a p i / s e r i e s / : i d / o r g a n i z e - s t o r y - o r d e r
*
* @ param { SeriesControllerRequest } req
* @ param { Response } res
* /
async organizeStoryOrder ( req , res ) {
if ( ! openAI . isConfigured ) {
return res . status ( 400 ) . send ( 'OpenAI is not configured' )
}
if ( ! req . libraryItemsInSeries . length ) {
return res . status ( 400 ) . send ( 'No books found in this series' )
}
try {
const seriesOrder = await openAI . getSeriesOrder ( req . series , req . libraryItemsInSeries )
const sequenceByLibraryItemId = new Map ( seriesOrder . map ( ( book ) => [ book . id , book . sequence ] ) )
const updatedItems = [ ]
Logger . info ( ` [SeriesController] AI story-order evaluation returned ${ seriesOrder . length } books for series " ${ req . series . name } " ` )
for ( const libraryItem of req . libraryItemsInSeries ) {
const nextSequence = sequenceByLibraryItemId . get ( libraryItem . id )
if ( ! nextSequence ) continue
Logger . info ( ` [SeriesController] AI story-order applying " ${ libraryItem . media . title } " ( ${ libraryItem . id } ) -> sequence " ${ nextSequence } " in series " ${ req . series . name } " ` )
const seriesPayload = libraryItem . media . series . map ( ( series ) => ( {
id : series . id ,
name : series . name ,
sequence : series . id === req . series . id ? nextSequence : series . bookSeries ? . sequence || null
} ) )
const seriesUpdate = await libraryItem . media . updateSeriesFromRequest ( seriesPayload , libraryItem . libraryId )
if ( ! seriesUpdate ? . hasUpdates ) {
Logger . info ( ` [SeriesController] AI story-order found no change for " ${ libraryItem . media . title } " ( ${ libraryItem . id } ) ` )
continue
}
libraryItem . changed ( 'updatedAt' , true )
await libraryItem . save ( )
await libraryItem . saveMetadataFile ( )
updatedItems . push ( libraryItem )
SocketAuthority . libraryItemEmitter ( 'item_updated' , libraryItem )
}
if ( updatedItems . length ) {
SocketAuthority . emitter ( 'series_updated' , req . series . toOldJSON ( ) )
}
Logger . info ( ` [SeriesController] AI story-order completed for series " ${ req . series . name } " - updated= ${ updatedItems . length } , total= ${ req . libraryItemsInSeries . length } ` )
res . json ( {
updated : updatedItems . length ,
total : req . libraryItemsInSeries . length
} )
} catch ( error ) {
Logger . error ( ` [SeriesController] Failed to organize story order for " ${ req . series . name } " ` , error )
res . status ( 500 ) . send ( error . message || 'Failed to organize story order' )
}
}
2024-08-11 15:15:34 -05:00
/ * *
*
* @ param { RequestWithUser } req
* @ param { Response } res
* @ param { NextFunction } next
* /
2023-08-13 15:10:26 -05:00
async middleware ( req , res , next ) {
2024-09-01 15:08:56 -05:00
const series = await Database . seriesModel . findByPk ( req . params . id )
2022-03-12 18:50:31 -06:00
if ( ! series ) return res . sendStatus ( 404 )
2023-07-07 17:59:17 -05:00
/ * *
* Filter out any library items not accessible to user
* /
2024-08-11 16:07:29 -05:00
const libraryItems = await libraryItemsBookFilters . getLibraryItemsForSeries ( series , req . user )
2023-08-13 15:10:26 -05:00
if ( ! libraryItems . length ) {
2024-08-11 16:07:29 -05:00
Logger . warn ( ` [SeriesController] User " ${ req . user . username } " attempted to access series " ${ series . id } " with no accessible books ` )
2023-08-13 15:10:26 -05:00
return res . sendStatus ( 404 )
2023-05-28 08:51:34 -05:00
}
2024-08-11 16:07:29 -05:00
if ( req . method == 'DELETE' && ! req . user . canDelete ) {
Logger . warn ( ` [SeriesController] User " ${ req . user . username } " attempted to delete without permission ` )
2022-03-12 18:50:31 -06:00
return res . sendStatus ( 403 )
2024-08-11 16:07:29 -05:00
} else if ( ( req . method == 'PATCH' || req . method == 'POST' ) && ! req . user . canUpdate ) {
Logger . warn ( ` [SeriesController] User " ${ req . user . username } " attempted to update without permission ` )
2022-03-12 18:50:31 -06:00
return res . sendStatus ( 403 )
}
req . series = series
2023-08-13 15:10:26 -05:00
req . libraryItemsInSeries = libraryItems
2022-03-12 18:50:31 -06:00
next ( )
}
2022-03-11 19:46:32 -06:00
}
2024-08-10 17:15:21 -05:00
module . exports = new SeriesController ( )