2021-08-17 17:01:11 -05:00
const express = require ( 'express' )
2021-09-21 20:57:33 -05:00
const Path = require ( 'path' )
2024-08-31 13:27:48 -05:00
const sequelize = require ( 'sequelize' )
2022-11-24 15:53:58 -06:00
const Logger = require ( '../Logger' )
2023-07-04 18:14:44 -05:00
const Database = require ( '../Database' )
2022-11-24 15:53:58 -06:00
const SocketAuthority = require ( '../SocketAuthority' )
2022-07-05 19:53:01 -05:00
const fs = require ( '../libs/fsExtra' )
2022-07-06 19:18:27 -05:00
const date = require ( '../libs/dateAndTime' )
2022-03-17 19:10:47 -05:00
2023-09-06 17:48:50 -05:00
const CacheManager = require ( '../managers/CacheManager' )
2024-12-15 12:37:01 -06:00
const RssFeedManager = require ( '../managers/RssFeedManager' )
2023-09-06 17:48:50 -05:00
2022-03-17 19:10:47 -05:00
const LibraryController = require ( '../controllers/LibraryController' )
const UserController = require ( '../controllers/UserController' )
const CollectionController = require ( '../controllers/CollectionController' )
2022-11-26 15:14:45 -06:00
const PlaylistController = require ( '../controllers/PlaylistController' )
2022-03-17 19:10:47 -05:00
const MeController = require ( '../controllers/MeController' )
const BackupController = require ( '../controllers/BackupController' )
const LibraryItemController = require ( '../controllers/LibraryItemController' )
const SeriesController = require ( '../controllers/SeriesController' )
2022-11-19 13:28:06 -06:00
const FileSystemController = require ( '../controllers/FileSystemController' )
2022-03-17 19:10:47 -05:00
const AuthorController = require ( '../controllers/AuthorController' )
const SessionController = require ( '../controllers/SessionController' )
2022-03-19 10:13:10 -05:00
const PodcastController = require ( '../controllers/PodcastController' )
2022-09-21 18:01:10 -05:00
const NotificationController = require ( '../controllers/NotificationController' )
2023-05-29 17:38:38 -05:00
const EmailController = require ( '../controllers/EmailController' )
2022-11-19 13:28:06 -06:00
const SearchController = require ( '../controllers/SearchController' )
const CacheController = require ( '../controllers/CacheController' )
const ToolsController = require ( '../controllers/ToolsController' )
2022-12-26 16:58:36 -06:00
const RSSFeedController = require ( '../controllers/RSSFeedController' )
2024-02-11 16:48:16 -06:00
const CustomMetadataProviderController = require ( '../controllers/CustomMetadataProviderController' )
2022-03-18 11:51:55 -05:00
const MiscController = require ( '../controllers/MiscController' )
2024-06-22 16:42:13 -05:00
const ShareController = require ( '../controllers/ShareController' )
2025-03-29 17:34:17 -05:00
const StatsController = require ( '../controllers/StatsController' )
2025-06-30 10:12:39 -05:00
const ApiKeyController = require ( '../controllers/ApiKeyController' )
2022-03-17 19:10:47 -05:00
class ApiRouter {
2022-11-24 15:53:58 -06:00
constructor ( Server ) {
2023-11-22 19:00:11 +02:00
/** @type {import('../Auth')} */
2022-11-24 15:53:58 -06:00
this . auth = Server . auth
2024-08-11 15:15:34 -05:00
/** @type {import('../managers/PlaybackSessionManager')} */
2022-11-24 15:53:58 -06:00
this . playbackSessionManager = Server . playbackSessionManager
2024-07-31 17:32:51 -05:00
/** @type {import('../managers/AbMergeManager')} */
2022-11-24 15:53:58 -06:00
this . abMergeManager = Server . abMergeManager
2024-06-19 17:14:37 -05:00
/** @type {import('../managers/BackupManager')} */
2022-11-24 15:53:58 -06:00
this . backupManager = Server . backupManager
2024-07-16 17:05:52 -05:00
/** @type {import('../managers/PodcastManager')} */
2022-11-24 15:53:58 -06:00
this . podcastManager = Server . podcastManager
2024-07-31 17:32:51 -05:00
/** @type {import('../managers/AudioMetadataManager')} */
2022-11-24 15:53:58 -06:00
this . audioMetadataManager = Server . audioMetadataManager
2024-08-24 15:38:15 -05:00
/** @type {import('../managers/CronManager')} */
2022-11-24 15:53:58 -06:00
this . cronManager = Server . cronManager
2024-08-11 17:01:25 -05:00
/** @type {import('../managers/EmailManager')} */
2023-05-29 17:38:38 -05:00
this . emailManager = Server . emailManager
2023-11-17 08:49:40 +02:00
this . apiCacheManager = Server . apiCacheManager
2021-08-17 17:01:11 -05:00
this . router = express ( )
2023-02-01 14:34:01 -06:00
this . router . disable ( 'x-powered-by' )
2021-08-17 17:01:11 -05:00
this . init ( )
}
init ( ) {
2021-11-21 20:00:40 -06:00
//
// Library Routes
//
2025-01-20 08:59:45 -06:00
this . router . get ( /^\/libraries/ , this . apiCacheManager . middleware )
2021-11-21 20:00:40 -06:00
this . router . post ( '/libraries' , LibraryController . create . bind ( this ) )
this . router . get ( '/libraries' , LibraryController . findAll . bind ( this ) )
2023-11-23 09:55:55 +02:00
this . router . get ( '/libraries/:id' , LibraryController . middleware . bind ( this ) , LibraryController . findOne . bind ( this ) )
2023-09-04 15:26:07 -05:00
this . router . patch ( '/libraries/:id' , LibraryController . middleware . bind ( this ) , LibraryController . update . bind ( this ) )
this . router . delete ( '/libraries/:id' , LibraryController . middleware . bind ( this ) , LibraryController . delete . bind ( this ) )
2021-11-30 20:02:40 -06:00
2023-11-23 09:55:55 +02:00
this . router . get ( '/libraries/:id/items' , LibraryController . middleware . bind ( this ) , LibraryController . getLibraryItems . bind ( this ) )
2023-09-04 15:26:07 -05:00
this . router . delete ( '/libraries/:id/issues' , LibraryController . middleware . bind ( this ) , LibraryController . removeLibraryItemsWithIssues . bind ( this ) )
2023-11-23 09:55:55 +02:00
this . router . get ( '/libraries/:id/episode-downloads' , LibraryController . middleware . bind ( this ) , LibraryController . getEpisodeDownloadQueue . bind ( this ) )
this . router . get ( '/libraries/:id/series' , LibraryController . middleware . bind ( this ) , LibraryController . getAllSeriesForLibrary . bind ( this ) )
2023-09-04 15:26:07 -05:00
this . router . get ( '/libraries/:id/series/:seriesId' , LibraryController . middleware . bind ( this ) , LibraryController . getSeriesForLibrary . bind ( this ) )
2023-11-23 09:55:55 +02:00
this . router . get ( '/libraries/:id/collections' , LibraryController . middleware . bind ( this ) , LibraryController . getCollectionsForLibrary . bind ( this ) )
this . router . get ( '/libraries/:id/playlists' , LibraryController . middleware . bind ( this ) , LibraryController . getUserPlaylistsForLibrary . bind ( this ) )
this . router . get ( '/libraries/:id/personalized' , LibraryController . middleware . bind ( this ) , LibraryController . getUserPersonalizedShelves . bind ( this ) )
this . router . get ( '/libraries/:id/filterdata' , LibraryController . middleware . bind ( this ) , LibraryController . getLibraryFilterData . bind ( this ) )
this . router . get ( '/libraries/:id/search' , LibraryController . middleware . bind ( this ) , LibraryController . search . bind ( this ) )
2023-09-04 15:26:07 -05:00
this . router . get ( '/libraries/:id/stats' , LibraryController . middleware . bind ( this ) , LibraryController . stats . bind ( this ) )
2023-11-23 09:55:55 +02:00
this . router . get ( '/libraries/:id/authors' , LibraryController . middleware . bind ( this ) , LibraryController . getAuthors . bind ( this ) )
this . router . get ( '/libraries/:id/narrators' , LibraryController . middleware . bind ( this ) , LibraryController . getNarrators . bind ( this ) )
2023-09-04 15:26:07 -05:00
this . router . patch ( '/libraries/:id/narrators/:narratorId' , LibraryController . middleware . bind ( this ) , LibraryController . updateNarrator . bind ( this ) )
this . router . delete ( '/libraries/:id/narrators/:narratorId' , LibraryController . middleware . bind ( this ) , LibraryController . removeNarrator . bind ( this ) )
2023-11-23 09:55:55 +02:00
this . router . get ( '/libraries/:id/matchall' , LibraryController . middleware . bind ( this ) , LibraryController . matchAll . bind ( this ) )
2023-09-04 15:26:07 -05:00
this . router . post ( '/libraries/:id/scan' , LibraryController . middleware . bind ( this ) , LibraryController . scan . bind ( this ) )
this . router . get ( '/libraries/:id/recent-episodes' , LibraryController . middleware . bind ( this ) , LibraryController . getRecentEpisodes . bind ( this ) )
this . router . get ( '/libraries/:id/opml' , LibraryController . middleware . bind ( this ) , LibraryController . getOPMLFile . bind ( this ) )
2022-03-13 17:10:48 -05:00
this . router . post ( '/libraries/order' , LibraryController . reorder . bind ( this ) )
2023-10-17 17:46:43 -05:00
this . router . post ( '/libraries/:id/remove-metadata' , LibraryController . middleware . bind ( this ) , LibraryController . removeAllMetadataFiles . bind ( this ) )
2024-10-11 16:55:09 -05:00
this . router . get ( '/libraries/:id/podcast-titles' , LibraryController . middleware . bind ( this ) , LibraryController . getPodcastTitles . bind ( this ) )
2025-03-17 19:35:59 +01:00
this . router . get ( '/libraries/:id/download' , LibraryController . middleware . bind ( this ) , LibraryController . downloadMultiple . bind ( this ) )
2022-03-10 18:45:02 -06:00
//
// Item Routes
//
2023-05-27 14:51:03 -05:00
this . router . post ( '/items/batch/delete' , LibraryItemController . batchDelete . bind ( this ) )
this . router . post ( '/items/batch/update' , LibraryItemController . batchUpdate . bind ( this ) )
this . router . post ( '/items/batch/get' , LibraryItemController . batchGet . bind ( this ) )
this . router . post ( '/items/batch/quickmatch' , LibraryItemController . batchQuickMatch . bind ( this ) )
this . router . post ( '/items/batch/scan' , LibraryItemController . batchScan . bind ( this ) )
2022-03-13 19:34:31 -05:00
2022-03-10 18:45:02 -06:00
this . router . get ( '/items/:id' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . findOne . bind ( this ) )
2022-03-12 17:45:32 -06:00
this . router . delete ( '/items/:id' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . delete . bind ( this ) )
2023-04-09 17:05:35 -05:00
this . router . get ( '/items/:id/download' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . download . bind ( this ) )
2022-03-11 19:46:32 -06:00
this . router . patch ( '/items/:id/media' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . updateMedia . bind ( this ) )
2023-09-21 16:57:48 -05:00
this . router . get ( '/items/:id/cover' , LibraryItemController . getCover . bind ( this ) )
2022-03-12 17:45:32 -06:00
this . router . post ( '/items/:id/cover' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . uploadCover . bind ( this ) )
this . router . patch ( '/items/:id/cover' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . updateCover . bind ( this ) )
this . router . delete ( '/items/:id/cover' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . removeCover . bind ( this ) )
2022-03-13 19:34:31 -05:00
this . router . post ( '/items/:id/match' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . match . bind ( this ) )
2022-03-17 19:10:47 -05:00
this . router . post ( '/items/:id/play' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . startPlaybackSession . bind ( this ) )
2022-03-26 17:41:26 -05:00
this . router . post ( '/items/:id/play/:episodeId' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . startEpisodePlaybackSession . bind ( this ) )
2022-03-26 11:59:34 -05:00
this . router . patch ( '/items/:id/tracks' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . updateTracks . bind ( this ) )
2023-02-03 17:50:42 -06:00
this . router . post ( '/items/:id/scan' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . scan . bind ( this ) )
2024-07-06 16:00:48 -05:00
this . router . get ( '/items/:id/metadata-object' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . getMetadataObject . bind ( this ) )
2022-05-10 17:03:41 -05:00
this . router . post ( '/items/:id/chapters' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . updateMediaChapters . bind ( this ) )
2023-06-25 16:16:11 -05:00
this . router . get ( '/items/:id/ffprobe/:fileid' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . getFFprobeData . bind ( this ) )
2023-05-28 12:34:22 -05:00
this . router . get ( '/items/:id/file/:fileid' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . getLibraryFile . bind ( this ) )
this . router . delete ( '/items/:id/file/:fileid' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . deleteLibraryFile . bind ( this ) )
this . router . get ( '/items/:id/file/:fileid/download' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . downloadLibraryFile . bind ( this ) )
2023-06-10 12:46:57 -05:00
this . router . get ( '/items/:id/ebook/:fileid?' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . getEBookFile . bind ( this ) )
this . router . patch ( '/items/:id/ebook/:fileid/status' , LibraryItemController . middleware . bind ( this ) , LibraryItemController . updateEbookFileStatus . bind ( this ) )
2022-03-10 18:45:02 -06:00
2021-11-21 20:00:40 -06:00
//
// User Routes
//
2022-09-25 17:11:39 -05:00
this . router . post ( '/users' , UserController . middleware . bind ( this ) , UserController . create . bind ( this ) )
this . router . get ( '/users' , UserController . middleware . bind ( this ) , UserController . findAll . bind ( this ) )
2022-11-10 17:42:20 -06:00
this . router . get ( '/users/online' , UserController . getOnlineUsers . bind ( this ) )
2022-09-25 17:11:39 -05:00
this . router . get ( '/users/:id' , UserController . middleware . bind ( this ) , UserController . findOne . bind ( this ) )
this . router . patch ( '/users/:id' , UserController . middleware . bind ( this ) , UserController . update . bind ( this ) )
this . router . delete ( '/users/:id' , UserController . middleware . bind ( this ) , UserController . delete . bind ( this ) )
2024-02-18 15:38:45 -06:00
this . router . patch ( '/users/:id/openid-unlink' , UserController . middleware . bind ( this ) , UserController . unlinkFromOpenID . bind ( this ) )
2022-09-25 17:11:39 -05:00
this . router . get ( '/users/:id/listening-sessions' , UserController . middleware . bind ( this ) , UserController . getListeningSessions . bind ( this ) )
this . router . get ( '/users/:id/listening-stats' , UserController . middleware . bind ( this ) , UserController . getListeningStats . bind ( this ) )
2021-11-21 20:00:40 -06:00
//
// Collection Routes
//
2022-08-31 15:46:10 -05:00
this . router . post ( '/collections' , CollectionController . middleware . bind ( this ) , CollectionController . create . bind ( this ) )
2021-11-21 20:00:40 -06:00
this . router . get ( '/collections' , CollectionController . findAll . bind ( this ) )
2022-08-31 15:46:10 -05:00
this . router . get ( '/collections/:id' , CollectionController . middleware . bind ( this ) , CollectionController . findOne . bind ( this ) )
this . router . patch ( '/collections/:id' , CollectionController . middleware . bind ( this ) , CollectionController . update . bind ( this ) )
this . router . delete ( '/collections/:id' , CollectionController . middleware . bind ( this ) , CollectionController . delete . bind ( this ) )
this . router . post ( '/collections/:id/book' , CollectionController . middleware . bind ( this ) , CollectionController . addBook . bind ( this ) )
this . router . delete ( '/collections/:id/book/:bookId' , CollectionController . middleware . bind ( this ) , CollectionController . removeBook . bind ( this ) )
this . router . post ( '/collections/:id/batch/add' , CollectionController . middleware . bind ( this ) , CollectionController . addBatch . bind ( this ) )
this . router . post ( '/collections/:id/batch/remove' , CollectionController . middleware . bind ( this ) , CollectionController . removeBatch . bind ( this ) )
2021-11-21 20:00:40 -06:00
2022-11-26 15:14:45 -06:00
//
// Playlist Routes
//
2022-12-17 17:31:19 -06:00
this . router . post ( '/playlists' , PlaylistController . create . bind ( this ) )
2022-11-26 15:14:45 -06:00
this . router . get ( '/playlists' , PlaylistController . findAllForUser . bind ( this ) )
this . router . get ( '/playlists/:id' , PlaylistController . middleware . bind ( this ) , PlaylistController . findOne . bind ( this ) )
this . router . patch ( '/playlists/:id' , PlaylistController . middleware . bind ( this ) , PlaylistController . update . bind ( this ) )
this . router . delete ( '/playlists/:id' , PlaylistController . middleware . bind ( this ) , PlaylistController . delete . bind ( this ) )
this . router . post ( '/playlists/:id/item' , PlaylistController . middleware . bind ( this ) , PlaylistController . addItem . bind ( this ) )
this . router . delete ( '/playlists/:id/item/:libraryItemId/:episodeId?' , PlaylistController . middleware . bind ( this ) , PlaylistController . removeItem . bind ( this ) )
this . router . post ( '/playlists/:id/batch/add' , PlaylistController . middleware . bind ( this ) , PlaylistController . addBatch . bind ( this ) )
this . router . post ( '/playlists/:id/batch/remove' , PlaylistController . middleware . bind ( this ) , PlaylistController . removeBatch . bind ( this ) )
2022-12-17 17:31:19 -06:00
this . router . post ( '/playlists/collection/:collectionId' , PlaylistController . createFromCollection . bind ( this ) )
2022-11-26 15:14:45 -06:00
2021-11-21 20:00:40 -06:00
//
// Current User Routes (Me)
//
2023-02-05 16:52:17 -06:00
this . router . get ( '/me' , MeController . getCurrentUser . bind ( this ) )
2026-07-25 17:18:38 -05:00
this . router . get ( '/me/sessions' , MeController . getSessions . bind ( this ) )
2026-07-26 17:36:36 -05:00
this . router . delete ( '/me/sessions/:id' , MeController . deleteSession . bind ( this ) )
2026-07-13 11:41:49 +02:00
this . router . get ( '/me/progress' , MeController . getAllMediaProgress . bind ( this ) )
this . router . get ( '/me/bookmarks' , MeController . getAllBookmarks . bind ( this ) )
this . router . get ( '/me/bookmarks/:libraryItemId' , MeController . getBookmarksForLibraryItem . bind ( this ) )
2021-11-21 20:00:40 -06:00
this . router . get ( '/me/listening-sessions' , MeController . getListeningSessions . bind ( this ) )
2024-05-14 10:51:50 +02:00
this . router . get ( '/me/item/listening-sessions/:libraryItemId/:episodeId?' , MeController . getItemListeningSessions . bind ( this ) )
2021-11-21 20:00:40 -06:00
this . router . get ( '/me/listening-stats' , MeController . getListeningStats . bind ( this ) )
2022-09-28 17:45:39 -05:00
this . router . get ( '/me/progress/:id/remove-from-continue-listening' , MeController . removeItemFromContinueListening . bind ( this ) )
2022-06-03 18:59:42 -05:00
this . router . get ( '/me/progress/:id/:episodeId?' , MeController . getMediaProgress . bind ( this ) )
2022-04-23 17:17:05 -05:00
this . router . patch ( '/me/progress/batch/update' , MeController . batchUpdateMediaProgress . bind ( this ) )
2024-08-11 11:53:30 -05:00
this . router . patch ( '/me/progress/:libraryItemId/:episodeId?' , MeController . createUpdateMediaProgress . bind ( this ) )
2022-03-26 11:59:34 -05:00
this . router . delete ( '/me/progress/:id' , MeController . removeMediaProgress . bind ( this ) )
2022-03-17 20:28:04 -05:00
this . router . post ( '/me/item/:id/bookmark' , MeController . createBookmark . bind ( this ) )
this . router . patch ( '/me/item/:id/bookmark' , MeController . updateBookmark . bind ( this ) )
this . router . delete ( '/me/item/:id/bookmark/:time' , MeController . removeBookmark . bind ( this ) )
2025-07-07 16:23:15 -05:00
this . router . patch ( '/me/password' , this . auth . authRateLimiter , MeController . updatePassword . bind ( this ) )
2022-08-14 10:24:41 -05:00
this . router . get ( '/me/items-in-progress' , MeController . getAllLibraryItemsInProgress . bind ( this ) )
2022-09-28 17:45:39 -05:00
this . router . get ( '/me/series/:id/remove-from-continue-listening' , MeController . removeSeriesFromContinueListening . bind ( this ) )
2022-11-15 17:20:57 -06:00
this . router . get ( '/me/series/:id/readd-to-continue-listening' , MeController . readdSeriesFromContinueListening . bind ( this ) )
2023-12-22 17:01:07 -06:00
this . router . get ( '/me/stats/year/:year' , MeController . getStatsForYear . bind ( this ) )
2024-10-26 16:34:34 -04:00
this . router . post ( '/me/ereader-devices' , MeController . updateUserEReaderDevices . bind ( this ) )
2021-11-21 20:00:40 -06:00
//
// Backup Routes
//
2022-11-24 13:14:29 -06:00
this . router . get ( '/backups' , BackupController . middleware . bind ( this ) , BackupController . getAll . bind ( this ) )
this . router . post ( '/backups' , BackupController . middleware . bind ( this ) , BackupController . create . bind ( this ) )
this . router . delete ( '/backups/:id' , BackupController . middleware . bind ( this ) , BackupController . delete . bind ( this ) )
2023-06-27 16:41:32 -05:00
this . router . get ( '/backups/:id/download' , BackupController . middleware . bind ( this ) , BackupController . download . bind ( this ) )
2022-11-24 13:14:29 -06:00
this . router . get ( '/backups/:id/apply' , BackupController . middleware . bind ( this ) , BackupController . apply . bind ( this ) )
this . router . post ( '/backups/upload' , BackupController . middleware . bind ( this ) , BackupController . upload . bind ( this ) )
2024-06-19 17:14:37 -05:00
this . router . patch ( '/backups/path' , BackupController . middleware . bind ( this ) , BackupController . updatePath . bind ( this ) )
2021-11-21 20:00:40 -06:00
2021-12-26 11:25:07 -06:00
//
// File System Routes
//
this . router . get ( '/filesystem' , FileSystemController . getPaths . bind ( this ) )
2023-05-27 16:00:34 -05:00
this . router . post ( '/filesystem/pathexists' , FileSystemController . checkPathExists . bind ( this ) )
2021-12-26 11:25:07 -06:00
2021-11-21 20:00:40 -06:00
//
2022-03-11 19:46:32 -06:00
// Author Routes
2021-11-21 20:00:40 -06:00
//
2022-03-13 06:42:43 -05:00
this . router . get ( '/authors/:id' , AuthorController . middleware . bind ( this ) , AuthorController . findOne . bind ( this ) )
2022-03-14 18:53:49 -05:00
this . router . patch ( '/authors/:id' , AuthorController . middleware . bind ( this ) , AuthorController . update . bind ( this ) )
2023-09-24 17:06:32 -05:00
this . router . delete ( '/authors/:id' , AuthorController . middleware . bind ( this ) , AuthorController . delete . bind ( this ) )
2022-03-13 06:42:43 -05:00
this . router . post ( '/authors/:id/match' , AuthorController . middleware . bind ( this ) , AuthorController . match . bind ( this ) )
2024-11-03 08:44:57 +02:00
this . router . get ( '/authors/:id/image' , AuthorController . getImage . bind ( this ) )
2023-10-13 17:37:37 -05:00
this . router . post ( '/authors/:id/image' , AuthorController . middleware . bind ( this ) , AuthorController . uploadImage . bind ( this ) )
this . router . delete ( '/authors/:id/image' , AuthorController . middleware . bind ( this ) , AuthorController . deleteImage . bind ( this ) )
2021-11-17 19:19:24 -06:00
2022-03-11 19:46:32 -06:00
//
// Series Routes
//
2022-03-13 17:10:48 -05:00
this . router . get ( '/series/:id' , SeriesController . middleware . bind ( this ) , SeriesController . findOne . bind ( this ) )
2022-09-27 17:48:45 -05:00
this . router . patch ( '/series/:id' , SeriesController . middleware . bind ( this ) , SeriesController . update . bind ( this ) )
2022-03-11 19:46:32 -06:00
2022-03-17 19:10:47 -05:00
//
// Playback Session Routes
//
2022-06-04 12:44:42 -05:00
this . router . get ( '/sessions' , SessionController . getAllWithUserData . bind ( this ) )
2022-08-13 12:24:19 -05:00
this . router . delete ( '/sessions/:id' , SessionController . middleware . bind ( this ) , SessionController . delete . bind ( this ) )
2023-04-08 18:01:24 -05:00
this . router . get ( '/sessions/open' , SessionController . getOpenSessions . bind ( this ) )
2023-12-21 13:52:42 -06:00
this . router . post ( '/sessions/batch/delete' , SessionController . batchDelete . bind ( this ) )
2023-02-05 16:52:17 -06:00
this . router . post ( '/session/local' , SessionController . syncLocal . bind ( this ) )
this . router . post ( '/session/local-all' , SessionController . syncLocalSessions . bind ( this ) )
2022-08-13 12:24:19 -05:00
// TODO: Update these endpoints because they are only for open playback sessions
this . router . get ( '/session/:id' , SessionController . openSessionMiddleware . bind ( this ) , SessionController . getOpenSession . bind ( this ) )
this . router . post ( '/session/:id/sync' , SessionController . openSessionMiddleware . bind ( this ) , SessionController . sync . bind ( this ) )
this . router . post ( '/session/:id/close' , SessionController . openSessionMiddleware . bind ( this ) , SessionController . close . bind ( this ) )
2022-03-17 19:10:47 -05:00
2022-03-19 10:13:10 -05:00
//
// Podcast Routes
//
this . router . post ( '/podcasts' , PodcastController . create . bind ( this ) )
this . router . post ( '/podcasts/feed' , PodcastController . getPodcastFeed . bind ( this ) )
2024-07-16 17:05:52 -05:00
this . router . post ( '/podcasts/opml/parse' , PodcastController . getFeedsFromOPMLText . bind ( this ) )
this . router . post ( '/podcasts/opml/create' , PodcastController . bulkCreatePodcastsFromOpmlFeedUrls . bind ( this ) )
2022-05-02 14:41:59 -05:00
this . router . get ( '/podcasts/:id/checknew' , PodcastController . middleware . bind ( this ) , PodcastController . checkNewEpisodes . bind ( this ) )
this . router . get ( '/podcasts/:id/downloads' , PodcastController . middleware . bind ( this ) , PodcastController . getEpisodeDownloads . bind ( this ) )
this . router . get ( '/podcasts/:id/clear-queue' , PodcastController . middleware . bind ( this ) , PodcastController . clearEpisodeDownloadQueue . bind ( this ) )
2022-07-31 13:12:37 -05:00
this . router . get ( '/podcasts/:id/search-episode' , PodcastController . middleware . bind ( this ) , PodcastController . findEpisode . bind ( this ) )
2022-05-02 14:41:59 -05:00
this . router . post ( '/podcasts/:id/download-episodes' , PodcastController . middleware . bind ( this ) , PodcastController . downloadEpisodes . bind ( this ) )
2023-01-04 18:13:46 -06:00
this . router . post ( '/podcasts/:id/match-episodes' , PodcastController . middleware . bind ( this ) , PodcastController . quickMatchEpisodes . bind ( this ) )
2023-03-04 19:04:55 +00:00
this . router . get ( '/podcasts/:id/episode/:episodeId' , PodcastController . middleware . bind ( this ) , PodcastController . getEpisode . bind ( this ) )
2022-05-02 14:41:59 -05:00
this . router . patch ( '/podcasts/:id/episode/:episodeId' , PodcastController . middleware . bind ( this ) , PodcastController . updateEpisode . bind ( this ) )
2022-05-24 18:38:25 -05:00
this . router . delete ( '/podcasts/:id/episode/:episodeId' , PodcastController . middleware . bind ( this ) , PodcastController . removeEpisode . bind ( this ) )
2022-03-19 10:13:10 -05:00
2022-09-21 18:01:10 -05:00
//
2022-12-26 16:58:36 -06:00
// Notification Routes (Admin and up)
2022-09-21 18:01:10 -05:00
//
this . router . get ( '/notifications' , NotificationController . middleware . bind ( this ) , NotificationController . get . bind ( this ) )
this . router . patch ( '/notifications' , NotificationController . middleware . bind ( this ) , NotificationController . update . bind ( this ) )
2022-09-22 18:12:48 -05:00
this . router . get ( '/notificationdata' , NotificationController . middleware . bind ( this ) , NotificationController . getData . bind ( this ) )
2022-09-24 16:15:16 -05:00
this . router . get ( '/notifications/test' , NotificationController . middleware . bind ( this ) , NotificationController . fireTestEvent . bind ( this ) )
2022-09-23 18:10:03 -05:00
this . router . post ( '/notifications' , NotificationController . middleware . bind ( this ) , NotificationController . createNotification . bind ( this ) )
this . router . delete ( '/notifications/:id' , NotificationController . middleware . bind ( this ) , NotificationController . deleteNotification . bind ( this ) )
this . router . patch ( '/notifications/:id' , NotificationController . middleware . bind ( this ) , NotificationController . updateNotification . bind ( this ) )
this . router . get ( '/notifications/:id/test' , NotificationController . middleware . bind ( this ) , NotificationController . sendNotificationTest . bind ( this ) )
2022-09-21 18:01:10 -05:00
2023-05-29 17:38:38 -05:00
//
// Email Routes (Admin and up)
//
2023-10-29 11:28:34 -05:00
this . router . get ( '/emails/settings' , EmailController . adminMiddleware . bind ( this ) , EmailController . getSettings . bind ( this ) )
this . router . patch ( '/emails/settings' , EmailController . adminMiddleware . bind ( this ) , EmailController . updateSettings . bind ( this ) )
this . router . post ( '/emails/test' , EmailController . adminMiddleware . bind ( this ) , EmailController . sendTest . bind ( this ) )
this . router . post ( '/emails/ereader-devices' , EmailController . adminMiddleware . bind ( this ) , EmailController . updateEReaderDevices . bind ( this ) )
this . router . post ( '/emails/send-ebook-to-device' , EmailController . sendEBookToDevice . bind ( this ) )
2023-05-29 17:38:38 -05:00
2022-11-19 13:28:06 -06:00
//
// Search Routes
//
this . router . get ( '/search/covers' , SearchController . findCovers . bind ( this ) )
this . router . get ( '/search/books' , SearchController . findBooks . bind ( this ) )
this . router . get ( '/search/podcast' , SearchController . findPodcasts . bind ( this ) )
this . router . get ( '/search/authors' , SearchController . findAuthor . bind ( this ) )
this . router . get ( '/search/chapters' , SearchController . findChapters . bind ( this ) )
2025-10-19 10:53:27 +03:00
this . router . get ( '/search/providers' , SearchController . getAllProviders . bind ( this ) )
2022-11-19 13:28:06 -06:00
//
2022-12-26 16:58:36 -06:00
// Cache Routes (Admin and up)
2022-11-19 13:28:06 -06:00
//
this . router . post ( '/cache/purge' , CacheController . purgeCache . bind ( this ) )
this . router . post ( '/cache/items/purge' , CacheController . purgeItemsCache . bind ( this ) )
//
2022-12-26 16:58:36 -06:00
// Tools Routes (Admin and up)
2022-11-19 13:28:06 -06:00
//
2023-04-02 16:13:18 -05:00
this . router . post ( '/tools/item/:id/encode-m4b' , ToolsController . middleware . bind ( this ) , ToolsController . encodeM4b . bind ( this ) )
this . router . delete ( '/tools/item/:id/encode-m4b' , ToolsController . middleware . bind ( this ) , ToolsController . cancelM4bEncode . bind ( this ) )
this . router . post ( '/tools/item/:id/embed-metadata' , ToolsController . middleware . bind ( this ) , ToolsController . embedAudioFileMetadata . bind ( this ) )
this . router . post ( '/tools/batch/embed-metadata' , ToolsController . middleware . bind ( this ) , ToolsController . batchEmbedMetadata . bind ( this ) )
2022-11-19 13:28:06 -06:00
2023-08-22 09:42:55 -07:00
//
2022-12-26 16:58:36 -06:00
// RSS Feed Routes (Admin and up)
//
2023-08-22 16:37:22 -05:00
this . router . get ( '/feeds' , RSSFeedController . middleware . bind ( this ) , RSSFeedController . getAll . bind ( this ) )
2022-12-26 16:58:36 -06:00
this . router . post ( '/feeds/item/:itemId/open' , RSSFeedController . middleware . bind ( this ) , RSSFeedController . openRSSFeedForItem . bind ( this ) )
2022-12-26 17:48:39 -06:00
this . router . post ( '/feeds/collection/:collectionId/open' , RSSFeedController . middleware . bind ( this ) , RSSFeedController . openRSSFeedForCollection . bind ( this ) )
2022-12-31 16:58:19 -06:00
this . router . post ( '/feeds/series/:seriesId/open' , RSSFeedController . middleware . bind ( this ) , RSSFeedController . openRSSFeedForSeries . bind ( this ) )
2022-12-26 16:58:36 -06:00
this . router . post ( '/feeds/:id/close' , RSSFeedController . middleware . bind ( this ) , RSSFeedController . closeRSSFeed . bind ( this ) )
2024-02-11 16:48:16 -06:00
//
// Custom Metadata Provider routes
//
this . router . get ( '/custom-metadata-providers' , CustomMetadataProviderController . middleware . bind ( this ) , CustomMetadataProviderController . getAll . bind ( this ) )
this . router . post ( '/custom-metadata-providers' , CustomMetadataProviderController . middleware . bind ( this ) , CustomMetadataProviderController . create . bind ( this ) )
this . router . delete ( '/custom-metadata-providers/:id' , CustomMetadataProviderController . middleware . bind ( this ) , CustomMetadataProviderController . delete . bind ( this ) )
2024-06-22 16:42:13 -05:00
//
// Share routes
//
this . router . post ( '/share/mediaitem' , ShareController . createMediaItemShare . bind ( this ) )
this . router . delete ( '/share/mediaitem/:id' , ShareController . deleteMediaItemShare . bind ( this ) )
2025-03-29 17:34:17 -05:00
//
// Stats Routes
//
2025-03-29 17:37:13 -05:00
this . router . get ( '/stats/year/:year' , StatsController . middleware . bind ( this ) , StatsController . getAdminStatsForYear . bind ( this ) )
this . router . get ( '/stats/server' , StatsController . middleware . bind ( this ) , StatsController . getServerStats . bind ( this ) )
2025-03-29 17:34:17 -05:00
2025-06-30 10:12:39 -05:00
//
// API Key Routes
//
2025-06-30 11:32:02 -05:00
this . router . get ( '/api-keys' , ApiKeyController . middleware . bind ( this ) , ApiKeyController . getAll . bind ( this ) )
2025-06-30 10:12:39 -05:00
this . router . post ( '/api-keys' , ApiKeyController . middleware . bind ( this ) , ApiKeyController . create . bind ( this ) )
2025-06-30 11:32:02 -05:00
this . router . patch ( '/api-keys/:id' , ApiKeyController . middleware . bind ( this ) , ApiKeyController . update . bind ( this ) )
this . router . delete ( '/api-keys/:id' , ApiKeyController . middleware . bind ( this ) , ApiKeyController . delete . bind ( this ) )
2025-06-30 10:12:39 -05:00
2022-03-11 19:46:32 -06:00
//
// Misc Routes
//
2022-03-18 11:51:55 -05:00
this . router . post ( '/upload' , MiscController . handleUpload . bind ( this ) )
2022-10-02 14:16:17 -05:00
this . router . get ( '/tasks' , MiscController . getTasks . bind ( this ) )
2022-10-02 14:46:48 -05:00
this . router . patch ( '/settings' , MiscController . updateServerSettings . bind ( this ) )
2023-09-08 12:32:30 -05:00
this . router . patch ( '/sorting-prefixes' , MiscController . updateSortingPrefixes . bind ( this ) )
2022-03-18 11:51:55 -05:00
this . router . post ( '/authorize' , MiscController . authorize . bind ( this ) )
2022-03-20 06:29:08 -05:00
this . router . get ( '/tags' , MiscController . getAllTags . bind ( this ) )
2022-12-18 14:17:52 -06:00
this . router . post ( '/tags/rename' , MiscController . renameTag . bind ( this ) )
this . router . delete ( '/tags/:tag' , MiscController . deleteTag . bind ( this ) )
2022-12-18 14:52:53 -06:00
this . router . get ( '/genres' , MiscController . getAllGenres . bind ( this ) )
this . router . post ( '/genres/rename' , MiscController . renameGenre . bind ( this ) )
this . router . delete ( '/genres/:genre' , MiscController . deleteGenre . bind ( this ) )
2022-08-01 18:06:22 -05:00
this . router . post ( '/validate-cron' , MiscController . validateCronExpression . bind ( this ) )
2023-11-22 19:00:11 +02:00
this . router . get ( '/auth-settings' , MiscController . getAuthSettings . bind ( this ) )
this . router . patch ( '/auth-settings' , MiscController . updateAuthSettings . bind ( this ) )
2023-10-24 13:35:43 +00:00
this . router . post ( '/watcher/update' , MiscController . updateWatchedPath . bind ( this ) )
2024-02-15 16:46:19 -06:00
this . router . get ( '/logger-data' , MiscController . getLoggerData . bind ( this ) )
2021-09-04 14:17:26 -05:00
}
2021-11-21 20:00:40 -06:00
//
// Helper Methods
//
2023-08-13 17:45:53 -05:00
/ * *
* Remove library item and associated entities
2023-08-22 09:42:55 -07:00
* @ param { string } libraryItemId
2023-08-13 17:45:53 -05:00
* @ param { string [ ] } mediaItemIds array of bookId or podcastEpisodeId
2026-03-31 22:02:52 +03:00
* @ param { string } libraryId
2023-08-13 17:45:53 -05:00
* /
2026-03-31 22:02:52 +03:00
async handleDeleteLibraryItem ( libraryItemId , mediaItemIds , libraryId ) {
2024-08-03 17:09:17 -05:00
const numProgressRemoved = await Database . mediaProgressModel . destroy ( {
where : {
mediaItemId : mediaItemIds
2021-11-21 20:00:40 -06:00
}
2024-08-03 17:09:17 -05:00
} )
if ( numProgressRemoved > 0 ) {
Logger . info ( ` [ApiRouter] Removed ${ numProgressRemoved } media progress entries for library item " ${ libraryItemId } " ` )
2021-11-21 20:00:40 -06:00
}
2022-11-27 14:49:21 -06:00
// remove item from playlists
2025-01-03 12:06:20 -06:00
await Database . playlistModel . removeMediaItemsFromPlaylists ( mediaItemIds )
2021-11-21 20:00:40 -06:00
2022-12-31 14:08:34 -06:00
// Close rss feed - remove from db and emit socket event
2024-12-15 12:37:01 -06:00
await RssFeedManager . closeFeedForEntityId ( libraryItemId )
2022-12-31 14:08:34 -06:00
2021-12-12 17:15:37 -06:00
// purge cover cache
2023-09-06 17:48:50 -05:00
await CacheManager . purgeCoverCache ( libraryItemId )
2021-12-12 17:15:37 -06:00
2024-12-01 09:51:26 -06:00
// Remove metadata file if in /metadata/items dir
2024-12-01 12:44:21 -06:00
if ( global . MetadataPath ) {
const itemMetadataPath = Path . join ( global . MetadataPath , 'items' , libraryItemId )
if ( await fs . pathExists ( itemMetadataPath ) ) {
Logger . info ( ` [ApiRouter] Removing item metadata at " ${ itemMetadataPath } " ` )
await fs . remove ( itemMetadataPath )
}
2023-04-16 16:23:13 -05:00
}
2024-08-03 17:09:17 -05:00
await Database . libraryItemModel . removeById ( libraryItemId )
2023-08-13 15:10:26 -05:00
SocketAuthority . emitter ( 'item_removed' , {
2026-03-31 22:02:52 +03:00
id : libraryItemId ,
libraryId
2023-08-13 15:10:26 -05:00
} )
2021-11-21 20:00:40 -06:00
}
2023-08-17 17:58:57 -05:00
/ * *
2024-12-01 09:51:26 -06:00
* After deleting book ( s ) , remove empty series
2024-05-05 13:14:30 +02:00
*
* @ param { string [ ] } seriesIds
2023-08-17 17:58:57 -05:00
* /
2024-12-01 09:51:26 -06:00
async checkRemoveEmptySeries ( seriesIds ) {
2023-08-17 17:58:57 -05:00
if ( ! seriesIds ? . length ) return
2025-03-08 11:16:34 -07:00
const transaction = await Database . sequelize . transaction ( )
try {
2025-03-08 17:47:47 -06:00
const seriesToRemove = (
2025-03-08 11:16:34 -07:00
await Database . seriesModel . findAll ( {
where : [
{
id : seriesIds
} ,
sequelize . where ( sequelize . literal ( '(SELECT count(*) FROM bookSeries bs WHERE bs.seriesId = series.id)' ) , 0 )
] ,
attributes : [ 'id' , 'name' , 'libraryId' ] ,
include : {
model : Database . bookModel ,
attributes : [ 'id' ] ,
required : false // Ensure it includes series even if no books exist
} ,
transaction
} )
) . map ( ( s ) => ( { id : s . id , name : s . name , libraryId : s . libraryId } ) )
2024-12-01 09:51:26 -06:00
2025-03-08 17:47:47 -06:00
if ( seriesToRemove . length ) {
2025-03-08 11:16:34 -07:00
await Database . seriesModel . destroy ( {
where : {
2025-03-08 17:47:47 -06:00
id : seriesToRemove . map ( ( s ) => s . id )
2025-03-08 11:16:34 -07:00
} ,
transaction
} )
2022-12-31 16:58:19 -06:00
}
2025-03-08 11:16:34 -07:00
await transaction . commit ( )
2025-03-08 17:47:47 -06:00
seriesToRemove . forEach ( ( { id , name , libraryId } ) => {
2025-03-08 11:16:34 -07:00
Logger . info ( ` [ApiRouter] Series " ${ name } " is now empty. Removing series ` )
// Remove series from library filter data
Database . removeSeriesFromFilterData ( libraryId , id )
SocketAuthority . emitter ( 'series_removed' , { id : id , libraryId : libraryId } )
} )
2025-03-08 17:47:47 -06:00
// Close rss feeds - remove from db and emit socket event
if ( seriesToRemove . length ) {
await RssFeedManager . closeFeedsForEntityIds ( seriesToRemove . map ( ( s ) => s . id ) )
}
2025-03-08 11:16:34 -07:00
} catch ( error ) {
await transaction . rollback ( )
Logger . error ( ` [ApiRouter] Error removing empty series: ${ error . message } ` )
2022-12-31 16:58:19 -06:00
}
}
2024-08-31 13:27:48 -05:00
/ * *
* Remove authors with no books and unset asin , description and imagePath
* Note : Other implementation is in BookScanner . checkAuthorsRemovedFromBooks ( can be merged )
*
* @ param { string [ ] } authorIds
* @ returns { Promise < void > }
* /
2024-12-01 09:51:26 -06:00
async checkRemoveAuthorsWithNoBooks ( authorIds ) {
2024-08-31 13:27:48 -05:00
if ( ! authorIds ? . length ) return
2025-03-08 10:43:27 -07:00
const transaction = await Database . sequelize . transaction ( )
try {
// Select authors with locking to prevent concurrent updates
const bookAuthorsToRemove = (
await Database . authorModel . findAll ( {
where : [
{
id : authorIds ,
asin : {
[ sequelize . Op . or ] : [ null , '' ]
} ,
description : {
[ sequelize . Op . or ] : [ null , '' ]
} ,
imagePath : {
[ sequelize . Op . or ] : [ null , '' ]
}
2024-08-31 13:27:48 -05:00
} ,
2025-03-08 10:43:27 -07:00
sequelize . where ( sequelize . literal ( '(SELECT count(*) FROM bookAuthors ba WHERE ba.authorId = author.id)' ) , 0 )
] ,
attributes : [ 'id' , 'name' , 'libraryId' ] ,
raw : true ,
transaction
} )
) . map ( ( au ) => ( { id : au . id , name : au . name , libraryId : au . libraryId } ) )
if ( bookAuthorsToRemove . length ) {
await Database . authorModel . destroy ( {
where : {
id : bookAuthorsToRemove . map ( ( au ) => au . id )
2024-08-31 13:27:48 -05:00
} ,
2025-03-08 10:43:27 -07:00
transaction
} )
}
2024-08-31 13:27:48 -05:00
2025-03-08 10:43:27 -07:00
await transaction . commit ( )
// Remove all book authors after completing remove from database
2024-12-01 09:51:26 -06:00
bookAuthorsToRemove . forEach ( ( { id , name , libraryId } ) => {
2024-08-31 13:27:48 -05:00
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 ` )
} )
2025-03-08 10:43:27 -07:00
} catch ( error ) {
await transaction . rollback ( )
Logger . error ( ` [ApiRouter] Error removing authors: ${ error . message } ` )
2024-08-31 13:27:48 -05:00
}
}
2021-11-21 20:00:40 -06:00
async getUserListeningSessionsHelper ( userId ) {
2023-07-04 18:14:44 -05:00
const userSessions = await Database . getPlaybackSessions ( { userId } )
2022-03-17 19:10:47 -05:00
return userSessions . sort ( ( a , b ) => b . updatedAt - a . updatedAt )
2021-11-12 19:43:16 -06:00
}
2026-03-07 18:35:02 -05:00
async getUserListeningSessionsPageHelper ( userId , page = 0 , itemsPerPage = 10 , mediaItemId = null ) {
const where = { userId }
if ( mediaItemId ) where . mediaItemId = mediaItemId
const start = page * itemsPerPage
const [ total , sessions ] = await Promise . all ( [
Database . countPlaybackSessions ( where ) ,
Database . getPlaybackSessions ( where , {
limit : itemsPerPage ,
offset : start ,
order : [ [ 'updatedAt' , 'DESC' ] ]
} )
] )
return {
total ,
numPages : Math . ceil ( total / itemsPerPage ) ,
page ,
itemsPerPage ,
sessions
}
}
2024-05-05 13:14:30 +02:00
async getUserItemListeningSessionsHelper ( userId , mediaItemId ) {
const userSessions = await Database . getPlaybackSessions ( { userId , mediaItemId } )
return userSessions . sort ( ( a , b ) => b . updatedAt - a . updatedAt )
}
2026-03-08 18:28:01 -04:00
async getUserListeningStatsHelpers ( userId , options = { } ) {
2026-03-07 18:35:02 -05:00
const startedAt = Date . now ( )
2021-11-12 19:43:16 -06:00
const today = date . format ( new Date ( ) , 'YYYY-MM-DD' )
2026-03-08 18:28:01 -04:00
const includeItems = options . includeItems !== false
const includeRecentSessions = options . includeRecentSessions !== false
2026-03-08 23:08:33 -04:00
const getSessionField = ( session , key ) => {
if ( ! session || typeof session !== 'object' ) return undefined
if ( session [ key ] !== undefined ) return session [ key ]
const lowerKey = key . toLowerCase ( )
if ( session [ lowerKey ] !== undefined ) return session [ lowerKey ]
return undefined
}
const getSessionObjectField = ( session , key ) => {
const value = getSessionField ( session , key )
if ( ! value ) return null
if ( typeof value === 'string' ) {
try {
return JSON . parse ( value )
} catch ( error ) {
return null
}
}
return value
}
2026-03-08 18:28:01 -04:00
const tasks = [ Database . getPlaybackSessionsForStats ( { userId } ) ]
if ( includeRecentSessions ) {
tasks . push (
Database . getPlaybackSessions ( { userId } , {
limit : 10 ,
offset : 0 ,
order : [ [ 'updatedAt' , 'DESC' ] ]
} )
)
}
const [ listeningSessions , recentSessions = [ ] ] = await Promise . all ( tasks )
2026-03-07 18:35:02 -05:00
2022-11-24 16:35:26 -06:00
const listeningStats = {
2021-11-12 19:43:16 -06:00
totalTime : 0 ,
days : { } ,
dayOfWeek : { } ,
2026-03-08 18:28:01 -04:00
today : 0
2021-11-12 19:43:16 -06:00
}
2026-03-08 18:28:01 -04:00
if ( includeItems ) listeningStats . items = { }
if ( includeRecentSessions ) listeningStats . recentSessions = recentSessions
2021-11-12 19:43:16 -06:00
listeningSessions . forEach ( ( s ) => {
2026-03-08 23:08:33 -04:00
const extraData = getSessionObjectField ( s , 'extraData' )
const libraryItemId = extraData ? . libraryItemId || extraData ? . libraryitemid || null
const sessionDate = getSessionField ( s , 'date' )
const sessionDayOfWeek = getSessionField ( s , 'dayOfWeek' )
const sessionUpdatedAt = getSessionField ( s , 'updatedAt' )
const sessionMediaMetadata = getSessionObjectField ( s , 'mediaMetadata' )
const numericListening = Number ( getSessionField ( s , 'timeListening' ) )
2026-03-08 22:30:29 -04:00
const sessionTimeListening = Number . isFinite ( numericListening )
? numericListening
: 0
2022-04-20 18:16:27 -05:00
2026-03-08 23:08:33 -04:00
if ( sessionDayOfWeek ) {
if ( ! listeningStats . dayOfWeek [ sessionDayOfWeek ] ) listeningStats . dayOfWeek [ sessionDayOfWeek ] = 0
listeningStats . dayOfWeek [ sessionDayOfWeek ] += sessionTimeListening
2021-11-12 19:43:16 -06:00
}
2026-03-08 23:08:33 -04:00
if ( sessionDate && sessionTimeListening > 0 ) {
if ( ! listeningStats . days [ sessionDate ] ) listeningStats . days [ sessionDate ] = 0
listeningStats . days [ sessionDate ] += sessionTimeListening
2021-11-12 19:43:16 -06:00
2026-03-08 23:08:33 -04:00
if ( sessionDate === today ) {
2022-04-20 18:16:27 -05:00
listeningStats . today += sessionTimeListening
2021-11-12 19:43:16 -06:00
}
}
2026-03-07 18:35:02 -05:00
if ( ! libraryItemId ) {
listeningStats . totalTime += sessionTimeListening
return
}
2026-03-08 18:28:01 -04:00
if ( includeItems ) {
if ( ! listeningStats . items [ libraryItemId ] ) {
listeningStats . items [ libraryItemId ] = {
id : libraryItemId ,
timeListening : sessionTimeListening ,
2026-03-08 23:08:33 -04:00
mediaMetadata : sessionMediaMetadata ,
lastUpdate : sessionUpdatedAt
2026-03-08 18:28:01 -04:00
}
} else {
listeningStats . items [ libraryItemId ] . timeListening += sessionTimeListening
2021-12-29 15:53:19 -06:00
}
}
2021-11-12 19:43:16 -06:00
2022-04-20 18:16:27 -05:00
listeningStats . totalTime += sessionTimeListening
2021-11-12 19:43:16 -06:00
} )
2026-03-07 18:35:02 -05:00
Logger . debug (
2026-03-08 18:28:01 -04:00
` [ApiRouter] Listening stats for user " ${ userId } " aggregated ${ listeningSessions . length } sessions in ${ Date . now ( ) - startedAt } ms includeItems= ${ includeItems } includeRecentSessions= ${ includeRecentSessions } `
2026-03-07 18:35:02 -05:00
)
2021-11-12 19:43:16 -06:00
return listeningStats
}
2021-08-17 17:01:11 -05:00
}
2023-02-27 02:56:07 +00:00
module . exports = ApiRouter