2021-11-22 03:00:40 +01:00
const Logger = require ( '../Logger' )
2022-11-24 22:53:58 +01:00
const SocketAuthority = require ( '../SocketAuthority' )
2023-07-05 01:14:44 +02:00
const Database = require ( '../Database' )
2022-08-14 17:24:41 +02:00
const { sort } = require ( '../libs/fastSort' )
2023-07-05 01:14:44 +02:00
const { toNumber } = require ( '../utils/index' )
2023-12-20 00:19:33 +01:00
const userStats = require ( '../utils/queries/userStats' )
2021-11-22 03:00:40 +01:00
class MeController {
constructor ( ) { }
2023-02-05 23:52:17 +01:00
getCurrentUser ( req , res ) {
res . json ( req . user . toJSONForBrowser ( ) )
}
2021-11-22 03:00:40 +01:00
// GET: api/me/listening-sessions
async getListeningSessions ( req , res ) {
var listeningSessions = await this . getUserListeningSessionsHelper ( req . user . id )
2022-06-04 17:52:37 +02:00
const itemsPerPage = toNumber ( req . query . itemsPerPage , 10 ) || 10
const page = toNumber ( req . query . page , 0 )
const start = page * itemsPerPage
const sessions = listeningSessions . slice ( start , start + itemsPerPage )
const payload = {
total : listeningSessions . length ,
numPages : Math . ceil ( listeningSessions . length / itemsPerPage ) ,
page ,
itemsPerPage ,
sessions
}
res . json ( payload )
2021-11-22 03:00:40 +01:00
}
// GET: api/me/listening-stats
async getListeningStats ( req , res ) {
2023-07-05 01:14:44 +02:00
const listeningStats = await this . getUserListeningStatsHelpers ( req . user . id )
2021-11-22 03:00:40 +01:00
res . json ( listeningStats )
}
2022-06-04 01:59:42 +02:00
// GET: api/me/progress/:id/:episodeId?
async getMediaProgress ( req , res ) {
2022-07-03 19:15:40 +02:00
const mediaProgress = req . user . getMediaProgress ( req . params . id , req . params . episodeId || null )
2022-06-04 01:59:42 +02:00
if ( ! mediaProgress ) {
return res . sendStatus ( 404 )
}
res . json ( mediaProgress )
}
2022-03-17 19:33:22 +01:00
// DELETE: api/me/progress/:id
2022-03-26 17:59:34 +01:00
async removeMediaProgress ( req , res ) {
2023-05-20 22:19:09 +02:00
if ( ! req . user . removeMediaProgress ( req . params . id ) ) {
2022-03-17 19:33:22 +01:00
return res . sendStatus ( 200 )
2021-11-22 03:00:40 +01:00
}
2023-07-05 01:14:44 +02:00
await Database . removeMediaProgress ( req . params . id )
2022-11-24 22:53:58 +01:00
SocketAuthority . clientEmitter ( req . user . id , 'user_updated' , req . user . toJSONForBrowser ( ) )
2021-11-22 03:00:40 +01:00
res . sendStatus ( 200 )
}
2022-03-17 19:33:22 +01:00
// PATCH: api/me/progress/:id
2022-03-26 17:59:34 +01:00
async createUpdateMediaProgress ( req , res ) {
2023-08-20 20:34:03 +02:00
const libraryItem = await Database . libraryItemModel . getOldById ( req . params . id )
2022-03-14 01:34:31 +01:00
if ( ! libraryItem ) {
return res . status ( 404 ) . send ( 'Item not found' )
2021-11-22 03:00:40 +01:00
}
2022-06-25 18:01:01 +02:00
2023-07-05 01:14:44 +02:00
if ( req . user . createUpdateMediaProgress ( libraryItem , req . body ) ) {
const mediaProgress = req . user . getMediaProgress ( libraryItem . id )
if ( mediaProgress ) await Database . upsertMediaProgress ( mediaProgress )
2022-11-24 22:53:58 +01:00
SocketAuthority . clientEmitter ( req . user . id , 'user_updated' , req . user . toJSONForBrowser ( ) )
2021-11-22 03:00:40 +01:00
}
res . sendStatus ( 200 )
}
2022-03-26 23:41:26 +01:00
// PATCH: api/me/progress/:id/:episodeId
async createUpdateEpisodeMediaProgress ( req , res ) {
2023-07-05 01:14:44 +02:00
const episodeId = req . params . episodeId
2023-08-20 20:34:03 +02:00
const libraryItem = await Database . libraryItemModel . getOldById ( req . params . id )
2022-03-26 23:41:26 +01:00
if ( ! libraryItem ) {
return res . status ( 404 ) . send ( 'Item not found' )
}
if ( ! libraryItem . media . episodes . find ( ep => ep . id === episodeId ) ) {
Logger . error ( ` [MeController] removeEpisode episode ${ episodeId } not found for item ${ libraryItem . id } ` )
return res . status ( 404 ) . send ( 'Episode not found' )
}
2023-07-05 01:14:44 +02:00
if ( req . user . createUpdateMediaProgress ( libraryItem , req . body , episodeId ) ) {
const mediaProgress = req . user . getMediaProgress ( libraryItem . id , episodeId )
if ( mediaProgress ) await Database . upsertMediaProgress ( mediaProgress )
2022-11-24 22:53:58 +01:00
SocketAuthority . clientEmitter ( req . user . id , 'user_updated' , req . user . toJSONForBrowser ( ) )
2022-03-26 23:41:26 +01:00
}
res . sendStatus ( 200 )
}
2022-03-17 19:33:22 +01:00
// PATCH: api/me/progress/batch/update
2022-03-26 17:59:34 +01:00
async batchUpdateMediaProgress ( req , res ) {
2023-07-05 01:14:44 +02:00
const itemProgressPayloads = req . body
if ( ! itemProgressPayloads ? . length ) {
2022-11-23 02:15:36 +01:00
return res . status ( 400 ) . send ( 'Missing request payload' )
2021-11-22 03:00:40 +01:00
}
2023-07-05 01:14:44 +02:00
let shouldUpdate = false
for ( const itemProgress of itemProgressPayloads ) {
2023-08-20 20:34:03 +02:00
const libraryItem = await Database . libraryItemModel . getOldById ( itemProgress . libraryItemId )
2022-03-14 01:34:31 +01:00
if ( libraryItem ) {
2023-07-05 01:14:44 +02:00
if ( req . user . createUpdateMediaProgress ( libraryItem , itemProgress , itemProgress . episodeId ) ) {
const mediaProgress = req . user . getMediaProgress ( libraryItem . id , itemProgress . episodeId )
if ( mediaProgress ) await Database . upsertMediaProgress ( mediaProgress )
shouldUpdate = true
}
2022-03-17 19:33:22 +01:00
} else {
2022-03-26 17:59:34 +01:00
Logger . error ( ` [MeController] batchUpdateMediaProgress: Library Item does not exist ${ itemProgress . id } ` )
2021-11-22 03:00:40 +01:00
}
2023-07-05 01:14:44 +02:00
}
2021-11-22 03:00:40 +01:00
if ( shouldUpdate ) {
2022-11-24 22:53:58 +01:00
SocketAuthority . clientEmitter ( req . user . id , 'user_updated' , req . user . toJSONForBrowser ( ) )
2021-11-22 03:00:40 +01:00
}
res . sendStatus ( 200 )
}
2022-03-18 02:28:04 +01:00
// POST: api/me/item/:id/bookmark
async createBookmark ( req , res ) {
2023-08-20 20:34:03 +02:00
if ( ! await Database . libraryItemModel . checkExistsById ( req . params . id ) ) return res . sendStatus ( 404 )
2023-08-12 22:52:09 +02:00
2022-03-18 02:28:04 +01:00
const { time , title } = req . body
2023-08-12 22:52:09 +02:00
const bookmark = req . user . createBookmark ( req . params . id , time , title )
2023-07-05 01:14:44 +02:00
await Database . updateUser ( req . user )
2022-11-24 22:53:58 +01:00
SocketAuthority . clientEmitter ( req . user . id , 'user_updated' , req . user . toJSONForBrowser ( ) )
2022-03-18 02:28:04 +01:00
res . json ( bookmark )
}
// PATCH: api/me/item/:id/bookmark
async updateBookmark ( req , res ) {
2023-08-20 20:34:03 +02:00
if ( ! await Database . libraryItemModel . checkExistsById ( req . params . id ) ) return res . sendStatus ( 404 )
2023-08-12 22:52:09 +02:00
2022-03-18 02:28:04 +01:00
const { time , title } = req . body
2023-08-12 22:52:09 +02:00
if ( ! req . user . findBookmark ( req . params . id , time ) ) {
2022-03-18 02:28:04 +01:00
Logger . error ( ` [MeController] updateBookmark not found ` )
return res . sendStatus ( 404 )
}
2023-08-12 22:52:09 +02:00
const bookmark = req . user . updateBookmark ( req . params . id , time , title )
2022-03-18 02:28:04 +01:00
if ( ! bookmark ) return res . sendStatus ( 500 )
2023-08-12 22:52:09 +02:00
2023-07-05 01:14:44 +02:00
await Database . updateUser ( req . user )
2022-11-24 22:53:58 +01:00
SocketAuthority . clientEmitter ( req . user . id , 'user_updated' , req . user . toJSONForBrowser ( ) )
2022-03-18 02:28:04 +01:00
res . json ( bookmark )
}
// DELETE: api/me/item/:id/bookmark/:time
async removeBookmark ( req , res ) {
2023-08-20 20:34:03 +02:00
if ( ! await Database . libraryItemModel . checkExistsById ( req . params . id ) ) return res . sendStatus ( 404 )
2023-08-12 22:52:09 +02:00
const time = Number ( req . params . time )
2022-03-18 02:28:04 +01:00
if ( isNaN ( time ) ) return res . sendStatus ( 500 )
2023-08-12 22:52:09 +02:00
if ( ! req . user . findBookmark ( req . params . id , time ) ) {
2022-03-18 02:28:04 +01:00
Logger . error ( ` [MeController] removeBookmark not found ` )
return res . sendStatus ( 404 )
}
2023-08-12 22:52:09 +02:00
req . user . removeBookmark ( req . params . id , time )
2023-07-05 01:14:44 +02:00
await Database . updateUser ( req . user )
2022-11-24 22:53:58 +01:00
SocketAuthority . clientEmitter ( req . user . id , 'user_updated' , req . user . toJSONForBrowser ( ) )
2022-03-18 02:28:04 +01:00
res . sendStatus ( 200 )
}
2021-11-22 03:00:40 +01:00
// PATCH: api/me/password
updatePassword ( req , res ) {
2022-04-30 01:38:13 +02:00
if ( req . user . isGuest ) {
Logger . error ( ` [MeController] Guest user attempted to change password ` , req . user . username )
return res . sendStatus ( 500 )
}
2021-11-22 03:00:40 +01:00
this . auth . userChangePassword ( req , res )
}
2023-02-05 23:52:17 +01:00
// TODO: Deprecated. Removed from Android. Only used in iOS app now.
2022-04-10 00:56:51 +02:00
// POST: api/me/sync-local-progress
async syncLocalMediaProgress ( req , res ) {
if ( ! req . body . localMediaProgress ) {
Logger . error ( ` [MeController] syncLocalMediaProgress invalid post body ` )
return res . sendStatus ( 500 )
}
const updatedLocalMediaProgress = [ ]
2023-07-05 01:14:44 +02:00
let numServerProgressUpdates = 0
2023-01-28 21:46:01 +01:00
const updatedServerMediaProgress = [ ]
const localMediaProgress = req . body . localMediaProgress || [ ]
2022-07-14 02:18:49 +02:00
2023-07-05 01:14:44 +02:00
for ( const localProgress of localMediaProgress ) {
2022-04-10 00:56:51 +02:00
if ( ! localProgress . libraryItemId ) {
Logger . error ( ` [MeController] syncLocalMediaProgress invalid local media progress object ` , localProgress )
2023-07-27 01:08:55 +02:00
continue
2022-04-10 00:56:51 +02:00
}
2023-09-04 23:33:55 +02:00
const libraryItem = await Database . libraryItemModel . getOldById ( localProgress . libraryItemId )
2022-04-10 00:56:51 +02:00
if ( ! libraryItem ) {
2023-09-30 23:01:10 +02:00
Logger . error ( ` [MeController] syncLocalMediaProgress invalid local media progress object no library item with id " ${ localProgress . libraryItemId } " ` , localProgress )
2023-07-27 01:08:55 +02:00
continue
2022-04-10 00:56:51 +02:00
}
2023-01-28 21:46:01 +01:00
let mediaProgress = req . user . getMediaProgress ( localProgress . libraryItemId , localProgress . episodeId )
2022-04-10 00:56:51 +02:00
if ( ! mediaProgress ) {
// New media progress from mobile
Logger . debug ( ` [MeController] syncLocalMediaProgress local progress is new - creating ${ localProgress . id } ` )
req . user . createUpdateMediaProgress ( libraryItem , localProgress , localProgress . episodeId )
2023-01-28 21:46:01 +01:00
mediaProgress = req . user . getMediaProgress ( localProgress . libraryItemId , localProgress . episodeId )
2023-07-05 01:14:44 +02:00
if ( mediaProgress ) await Database . upsertMediaProgress ( mediaProgress )
2023-01-28 21:46:01 +01:00
updatedServerMediaProgress . push ( mediaProgress )
2022-04-10 00:56:51 +02:00
numServerProgressUpdates ++
} else if ( mediaProgress . lastUpdate < localProgress . lastUpdate ) {
Logger . debug ( ` [MeController] syncLocalMediaProgress local progress is more recent - updating ${ mediaProgress . id } ` )
req . user . createUpdateMediaProgress ( libraryItem , localProgress , localProgress . episodeId )
2023-01-28 21:46:01 +01:00
mediaProgress = req . user . getMediaProgress ( localProgress . libraryItemId , localProgress . episodeId )
2023-07-05 01:14:44 +02:00
if ( mediaProgress ) await Database . upsertMediaProgress ( mediaProgress )
2023-01-28 21:46:01 +01:00
updatedServerMediaProgress . push ( mediaProgress )
2022-04-10 00:56:51 +02:00
numServerProgressUpdates ++
} else if ( mediaProgress . lastUpdate > localProgress . lastUpdate ) {
2023-01-28 21:46:01 +01:00
const updateTimeDifference = mediaProgress . lastUpdate - localProgress . lastUpdate
2022-04-10 00:56:51 +02:00
Logger . debug ( ` [MeController] syncLocalMediaProgress server progress is more recent by ${ updateTimeDifference } ms - ${ mediaProgress . id } ` )
for ( const key in localProgress ) {
2022-07-14 02:18:49 +02:00
// Local media progress ID uses the local library item id and server media progress uses the library item id
if ( key !== 'id' && mediaProgress [ key ] != undefined && localProgress [ key ] !== mediaProgress [ key ] ) {
2022-04-10 00:56:51 +02:00
// Logger.debug(`[MeController] syncLocalMediaProgress key ${key} changed from ${localProgress[key]} to ${mediaProgress[key]} - ${mediaProgress.id}`)
localProgress [ key ] = mediaProgress [ key ]
}
}
updatedLocalMediaProgress . push ( localProgress )
} else {
Logger . debug ( ` [MeController] syncLocalMediaProgress server and local are in sync - ${ mediaProgress . id } ` )
}
2023-07-05 01:14:44 +02:00
}
2022-04-10 00:56:51 +02:00
Logger . debug ( ` [MeController] syncLocalMediaProgress server updates = ${ numServerProgressUpdates } , local updates = ${ updatedLocalMediaProgress . length } ` )
if ( numServerProgressUpdates > 0 ) {
2022-11-24 22:53:58 +01:00
SocketAuthority . clientEmitter ( req . user . id , 'user_updated' , req . user . toJSONForBrowser ( ) )
2022-04-10 00:56:51 +02:00
}
res . json ( {
numServerProgressUpdates ,
2023-01-28 21:46:01 +01:00
localProgressUpdates : updatedLocalMediaProgress , // Array of LocalMediaProgress that were updated from server (server more recent)
serverProgressUpdates : updatedServerMediaProgress // Array of MediaProgress that made updates to server (local more recent)
2022-04-10 00:56:51 +02:00
} )
}
2022-08-14 17:24:41 +02:00
// GET: api/me/items-in-progress
2023-09-04 23:33:55 +02:00
async getAllLibraryItemsInProgress ( req , res ) {
2022-08-14 17:24:41 +02:00
const limit = ! isNaN ( req . query . limit ) ? Number ( req . query . limit ) || 25 : 25
2023-03-25 20:07:35 +01:00
let itemsInProgress = [ ]
2023-09-04 23:33:55 +02:00
// TODO: More efficient to do this in a single query
2022-08-14 17:24:41 +02:00
for ( const mediaProgress of req . user . mediaProgress ) {
2023-03-25 20:07:35 +01:00
if ( ! mediaProgress . isFinished && ( mediaProgress . progress > 0 || mediaProgress . ebookProgress > 0 ) ) {
2023-09-04 23:33:55 +02:00
const libraryItem = await Database . libraryItemModel . getOldById ( mediaProgress . libraryItemId )
2022-08-14 17:24:41 +02:00
if ( libraryItem ) {
if ( mediaProgress . episodeId && libraryItem . mediaType === 'podcast' ) {
const episode = libraryItem . media . episodes . find ( ep => ep . id === mediaProgress . episodeId )
if ( episode ) {
const libraryItemWithEpisode = {
... libraryItem . toJSONMinified ( ) ,
recentEpisode : episode . toJSON ( ) ,
progressLastUpdate : mediaProgress . lastUpdate
}
itemsInProgress . push ( libraryItemWithEpisode )
}
} else if ( ! mediaProgress . episodeId ) {
itemsInProgress . push ( {
... libraryItem . toJSONMinified ( ) ,
progressLastUpdate : mediaProgress . lastUpdate
} )
}
}
}
}
itemsInProgress = sort ( itemsInProgress ) . desc ( li => li . progressLastUpdate ) . slice ( 0 , limit )
res . json ( {
libraryItems : itemsInProgress
} )
}
2022-09-29 00:12:27 +02:00
2022-09-29 00:45:39 +02:00
// GET: api/me/series/:id/remove-from-continue-listening
async removeSeriesFromContinueListening ( req , res ) {
2023-09-03 17:49:02 +02:00
const series = await Database . seriesModel . getOldById ( req . params . id )
2022-09-29 00:12:27 +02:00
if ( ! series ) {
2022-09-29 00:45:39 +02:00
Logger . error ( ` [MeController] removeSeriesFromContinueListening: Series ${ req . params . id } not found ` )
2022-09-29 00:12:27 +02:00
return res . sendStatus ( 404 )
}
const hasUpdated = req . user . addSeriesToHideFromContinueListening ( req . params . id )
if ( hasUpdated ) {
2023-07-05 01:14:44 +02:00
await Database . updateUser ( req . user )
2022-11-24 22:53:58 +01:00
SocketAuthority . clientEmitter ( req . user . id , 'user_updated' , req . user . toJSONForBrowser ( ) )
2022-09-29 00:12:27 +02:00
}
res . json ( req . user . toJSONForBrowser ( ) )
}
2022-09-29 00:45:39 +02:00
2022-11-16 00:20:57 +01:00
// GET: api/me/series/:id/readd-to-continue-listening
async readdSeriesFromContinueListening ( req , res ) {
2023-09-03 17:49:02 +02:00
const series = await Database . seriesModel . getOldById ( req . params . id )
2022-11-16 00:20:57 +01:00
if ( ! series ) {
Logger . error ( ` [MeController] readdSeriesFromContinueListening: Series ${ req . params . id } not found ` )
return res . sendStatus ( 404 )
}
const hasUpdated = req . user . removeSeriesFromHideFromContinueListening ( req . params . id )
if ( hasUpdated ) {
2023-07-05 01:14:44 +02:00
await Database . updateUser ( req . user )
2022-11-24 22:53:58 +01:00
SocketAuthority . clientEmitter ( req . user . id , 'user_updated' , req . user . toJSONForBrowser ( ) )
2022-11-16 00:20:57 +01:00
}
res . json ( req . user . toJSONForBrowser ( ) )
}
2022-09-29 00:45:39 +02:00
// GET: api/me/progress/:id/remove-from-continue-listening
async removeItemFromContinueListening ( req , res ) {
2023-09-08 00:49:35 +02:00
const mediaProgress = req . user . mediaProgress . find ( mp => mp . id === req . params . id )
if ( ! mediaProgress ) {
return res . sendStatus ( 404 )
}
2022-09-29 00:45:39 +02:00
const hasUpdated = req . user . removeProgressFromContinueListening ( req . params . id )
if ( hasUpdated ) {
2023-09-08 00:49:35 +02:00
await Database . mediaProgressModel . update ( {
hideFromContinueListening : true
} , {
where : {
id : mediaProgress . id
}
} )
2022-11-24 22:53:58 +01:00
SocketAuthority . clientEmitter ( req . user . id , 'user_updated' , req . user . toJSONForBrowser ( ) )
2022-09-29 00:45:39 +02:00
}
res . json ( req . user . toJSONForBrowser ( ) )
}
2023-12-20 00:19:33 +01:00
/ * *
2023-12-23 00:01:07 +01:00
* GET : / a p i / s t a t s / y e a r / : y e a r
2023-12-20 00:19:33 +01:00
*
* @ param { import ( 'express' ) . Request } req
* @ param { import ( 'express' ) . Response } res
* /
async getStatsForYear ( req , res ) {
const year = Number ( req . params . year )
if ( isNaN ( year ) || year < 2000 || year > 9999 ) {
Logger . error ( ` [MeController] Invalid year " ${ year } " ` )
return res . status ( 400 ) . send ( 'Invalid year' )
}
2023-12-23 00:01:07 +01:00
const data = await userStats . getStatsForYear ( req . user , year )
2023-12-20 00:19:33 +01:00
res . json ( data )
}
2021-11-22 03:00:40 +01:00
}
module . exports = new MeController ( )