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')
|
|
|
|
|
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')
|
2022-03-17 19:10:47 -05:00
|
|
|
|
2024-09-01 15:08:56 -05:00
|
|
|
const { getTitleIgnorePrefix } = require('../utils/index')
|
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
|
2023-10-26 16:41:54 -05:00
|
|
|
/** @type {import('../Watcher')} */
|
2022-11-24 15:53:58 -06:00
|
|
|
this.watcher = Server.watcher
|
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-11 15:15:34 -05:00
|
|
|
/** @type {import('../managers/RssFeedManager')} */
|
2022-11-24 15:53:58 -06:00
|
|
|
this.rssFeedManager = Server.rssFeedManager
|
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
|
|
|
|
//
|
2023-11-23 09:55:55 +02: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))
|
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-11 19:46:32 -06:00
|
|
|
this.router.patch('/items/:id', LibraryItemController.middleware.bind(this), LibraryItemController.update.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))
|
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))
|
2021-11-21 20:00:40 -06:00
|
|
|
this.router.patch('/me/password', 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))
|
2022-03-13 10:35:35 -05:00
|
|
|
this.router.get('/authors/:id/image', AuthorController.middleware.bind(this), 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))
|
|
|
|
|
|
|
|
//
|
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))
|
|
|
|
|
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))
|
2023-12-22 17:01:07 -06:00
|
|
|
this.router.get('/stats/year/:year', MiscController.getAdminStatsForYear.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} mediaType
|
|
|
|
* @param {string} libraryItemId
|
2023-08-13 17:45:53 -05:00
|
|
|
* @param {string[]} mediaItemIds array of bookId or podcastEpisodeId
|
|
|
|
*/
|
|
|
|
async handleDeleteLibraryItem(mediaType, libraryItemId, mediaItemIds) {
|
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-24 16:35:26 -06:00
|
|
|
// TODO: Remove open sessions for library item
|
2023-08-13 17:45:53 -05:00
|
|
|
|
|
|
|
// Remove series if empty
|
|
|
|
if (mediaType === 'book') {
|
2023-08-17 17:58:57 -05:00
|
|
|
// TODO: update filter data
|
2023-08-20 13:34:03 -05:00
|
|
|
const bookSeries = await Database.bookSeriesModel.findAll({
|
2023-08-13 17:45:53 -05:00
|
|
|
where: {
|
|
|
|
bookId: mediaItemIds[0]
|
|
|
|
},
|
|
|
|
include: {
|
2023-08-20 13:34:03 -05:00
|
|
|
model: Database.seriesModel,
|
2023-08-13 17:45:53 -05:00
|
|
|
include: {
|
2023-08-20 13:34:03 -05:00
|
|
|
model: Database.bookModel
|
2023-08-13 17:45:53 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
for (const bs of bookSeries) {
|
|
|
|
if (bs.series.books.length === 1) {
|
|
|
|
await this.removeEmptySeries(bs.series)
|
|
|
|
}
|
|
|
|
}
|
2022-11-27 14:49:21 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// remove item from playlists
|
2023-08-20 13:34:03 -05:00
|
|
|
const playlistsWithItem = await Database.playlistModel.getPlaylistsForMediaItemIds(mediaItemIds)
|
2023-07-23 09:42:57 -05:00
|
|
|
for (const playlist of playlistsWithItem) {
|
2023-08-13 11:22:38 -05:00
|
|
|
let numMediaItems = playlist.playlistMediaItems.length
|
|
|
|
|
|
|
|
let order = 1
|
|
|
|
// Remove items in playlist and re-order
|
|
|
|
for (const playlistMediaItem of playlist.playlistMediaItems) {
|
|
|
|
if (mediaItemIds.includes(playlistMediaItem.mediaItemId)) {
|
|
|
|
await playlistMediaItem.destroy()
|
|
|
|
numMediaItems--
|
|
|
|
} else {
|
|
|
|
if (playlistMediaItem.order !== order) {
|
|
|
|
playlistMediaItem.update({
|
|
|
|
order
|
|
|
|
})
|
|
|
|
}
|
|
|
|
order++
|
|
|
|
}
|
|
|
|
}
|
2022-11-27 14:49:21 -06:00
|
|
|
|
|
|
|
// If playlist is now empty then remove it
|
2023-08-13 11:22:38 -05:00
|
|
|
const jsonExpanded = await playlist.getOldJsonExpanded()
|
|
|
|
if (!numMediaItems) {
|
2022-11-27 14:49:21 -06:00
|
|
|
Logger.info(`[ApiRouter] Playlist "${playlist.name}" has no more items - removing it`)
|
2023-08-13 11:22:38 -05:00
|
|
|
await playlist.destroy()
|
|
|
|
SocketAuthority.clientEmitter(playlist.userId, 'playlist_removed', jsonExpanded)
|
2022-11-27 14:49:21 -06:00
|
|
|
} else {
|
2023-08-13 11:22:38 -05:00
|
|
|
SocketAuthority.clientEmitter(playlist.userId, 'playlist_updated', jsonExpanded)
|
2022-11-27 14:49:21 -06:00
|
|
|
}
|
2021-11-12 19:43:16 -06:00
|
|
|
}
|
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
|
2023-08-13 17:45:53 -05:00
|
|
|
await this.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
|
|
|
|
2023-08-13 17:45:53 -05:00
|
|
|
const itemMetadataPath = Path.join(global.MetadataPath, 'items', libraryItemId)
|
2023-04-16 16:23:13 -05:00
|
|
|
if (await fs.pathExists(itemMetadataPath)) {
|
2024-08-03 17:09:17 -05:00
|
|
|
Logger.info(`[ApiRouter] Removing item metadata at "${itemMetadataPath}"`)
|
2023-04-16 16:23:13 -05:00
|
|
|
await fs.remove(itemMetadataPath)
|
|
|
|
}
|
|
|
|
|
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', {
|
2023-08-13 17:45:53 -05:00
|
|
|
id: libraryItemId
|
2023-08-13 15:10:26 -05:00
|
|
|
})
|
2021-11-21 20:00:40 -06:00
|
|
|
}
|
|
|
|
|
2023-08-17 17:58:57 -05:00
|
|
|
/**
|
|
|
|
* Used when a series is removed from a book
|
|
|
|
* Series is removed if it only has 1 book
|
2024-05-05 13:14:30 +02:00
|
|
|
*
|
2023-08-19 17:12:24 -05:00
|
|
|
* @param {string} bookId
|
2024-05-05 13:14:30 +02:00
|
|
|
* @param {string[]} seriesIds
|
2023-08-17 17:58:57 -05:00
|
|
|
*/
|
|
|
|
async checkRemoveEmptySeries(bookId, seriesIds) {
|
|
|
|
if (!seriesIds?.length) return
|
|
|
|
|
2023-08-20 13:34:03 -05:00
|
|
|
const bookSeries = await Database.bookSeriesModel.findAll({
|
2023-08-17 17:58:57 -05:00
|
|
|
where: {
|
|
|
|
bookId,
|
|
|
|
seriesId: seriesIds
|
|
|
|
},
|
|
|
|
include: [
|
|
|
|
{
|
2023-08-20 13:34:03 -05:00
|
|
|
model: Database.seriesModel,
|
2023-08-17 17:58:57 -05:00
|
|
|
include: {
|
2023-08-20 13:34:03 -05:00
|
|
|
model: Database.bookModel
|
2023-08-17 17:58:57 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
})
|
|
|
|
for (const bs of bookSeries) {
|
|
|
|
if (bs.series.books.length === 1) {
|
|
|
|
await this.removeEmptySeries(bs.series)
|
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} libraryId
|
|
|
|
* @param {string[]} authorIds
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
|
|
|
async checkRemoveAuthorsWithNoBooks(libraryId, authorIds) {
|
|
|
|
if (!authorIds?.length) return
|
|
|
|
|
|
|
|
const bookAuthorsToRemove = (
|
|
|
|
await Database.authorModel.findAll({
|
|
|
|
where: [
|
|
|
|
{
|
|
|
|
id: authorIds,
|
|
|
|
asin: {
|
|
|
|
[sequelize.Op.or]: [null, '']
|
|
|
|
},
|
|
|
|
description: {
|
|
|
|
[sequelize.Op.or]: [null, '']
|
|
|
|
},
|
|
|
|
imagePath: {
|
|
|
|
[sequelize.Op.or]: [null, '']
|
|
|
|
}
|
|
|
|
},
|
|
|
|
sequelize.where(sequelize.literal('(SELECT count(*) FROM bookAuthors ba WHERE ba.authorId = author.id)'), 0)
|
|
|
|
],
|
|
|
|
attributes: ['id', 'name'],
|
|
|
|
raw: true
|
|
|
|
})
|
|
|
|
).map((au) => ({ id: au.id, name: au.name }))
|
|
|
|
|
|
|
|
if (bookAuthorsToRemove.length) {
|
|
|
|
await Database.authorModel.destroy({
|
|
|
|
where: {
|
|
|
|
id: bookAuthorsToRemove.map((au) => au.id)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
bookAuthorsToRemove.forEach(({ id, name }) => {
|
|
|
|
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`)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-18 14:40:36 -05:00
|
|
|
/**
|
|
|
|
* Remove an empty series & close an open RSS feed
|
2024-05-05 13:14:30 +02:00
|
|
|
* @param {import('../models/Series')} series
|
2023-08-18 14:40:36 -05:00
|
|
|
*/
|
2023-08-13 17:45:53 -05:00
|
|
|
async removeEmptySeries(series) {
|
|
|
|
await this.rssFeedManager.closeFeedForEntityId(series.id)
|
|
|
|
Logger.info(`[ApiRouter] Series "${series.name}" is now empty. Removing series`)
|
2024-09-01 15:08:56 -05:00
|
|
|
|
2023-08-18 14:40:36 -05:00
|
|
|
// Remove series from library filter data
|
|
|
|
Database.removeSeriesFromFilterData(series.libraryId, series.id)
|
|
|
|
SocketAuthority.emitter('series_removed', {
|
|
|
|
id: series.id,
|
|
|
|
libraryId: series.libraryId
|
|
|
|
})
|
2024-09-01 15:08:56 -05:00
|
|
|
|
|
|
|
await series.destroy()
|
2023-08-13 17:45:53 -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
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2021-11-12 19:43:16 -06:00
|
|
|
async getUserListeningStatsHelpers(userId) {
|
|
|
|
const today = date.format(new Date(), 'YYYY-MM-DD')
|
|
|
|
|
2022-11-24 16:35:26 -06:00
|
|
|
const listeningSessions = await this.getUserListeningSessionsHelper(userId)
|
|
|
|
const listeningStats = {
|
2021-11-12 19:43:16 -06:00
|
|
|
totalTime: 0,
|
2022-03-17 19:10:47 -05:00
|
|
|
items: {},
|
2021-11-12 19:43:16 -06:00
|
|
|
days: {},
|
|
|
|
dayOfWeek: {},
|
2021-12-29 15:53:19 -06:00
|
|
|
today: 0,
|
|
|
|
recentSessions: listeningSessions.slice(0, 10)
|
2021-11-12 19:43:16 -06:00
|
|
|
}
|
|
|
|
listeningSessions.forEach((s) => {
|
2022-11-24 16:35:26 -06:00
|
|
|
let sessionTimeListening = s.timeListening
|
2022-04-20 18:16:27 -05:00
|
|
|
if (typeof sessionTimeListening == 'string') {
|
|
|
|
sessionTimeListening = Number(sessionTimeListening)
|
|
|
|
}
|
|
|
|
|
2021-11-12 19:43:16 -06:00
|
|
|
if (s.dayOfWeek) {
|
|
|
|
if (!listeningStats.dayOfWeek[s.dayOfWeek]) listeningStats.dayOfWeek[s.dayOfWeek] = 0
|
2022-04-20 18:16:27 -05:00
|
|
|
listeningStats.dayOfWeek[s.dayOfWeek] += sessionTimeListening
|
2021-11-12 19:43:16 -06:00
|
|
|
}
|
2022-04-20 18:16:27 -05:00
|
|
|
if (s.date && sessionTimeListening > 0) {
|
2021-11-12 19:43:16 -06:00
|
|
|
if (!listeningStats.days[s.date]) listeningStats.days[s.date] = 0
|
2022-04-20 18:16:27 -05:00
|
|
|
listeningStats.days[s.date] += sessionTimeListening
|
2021-11-12 19:43:16 -06:00
|
|
|
|
|
|
|
if (s.date === today) {
|
2022-04-20 18:16:27 -05:00
|
|
|
listeningStats.today += sessionTimeListening
|
2021-11-12 19:43:16 -06:00
|
|
|
}
|
|
|
|
}
|
2022-03-17 19:10:47 -05:00
|
|
|
if (!listeningStats.items[s.libraryItemId]) {
|
|
|
|
listeningStats.items[s.libraryItemId] = {
|
|
|
|
id: s.libraryItemId,
|
2022-04-20 18:16:27 -05:00
|
|
|
timeListening: sessionTimeListening,
|
2022-03-17 19:10:47 -05:00
|
|
|
mediaMetadata: s.mediaMetadata,
|
2021-12-29 15:53:19 -06:00
|
|
|
lastUpdate: s.lastUpdate
|
|
|
|
}
|
|
|
|
} else {
|
2022-04-20 18:16:27 -05:00
|
|
|
listeningStats.items[s.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
|
|
|
})
|
|
|
|
return listeningStats
|
|
|
|
}
|
2021-12-12 17:15:37 -06:00
|
|
|
|
2023-07-08 09:57:32 -05:00
|
|
|
async createAuthorsAndSeriesForItemUpdate(mediaPayload, libraryId) {
|
2022-03-13 17:10:48 -05:00
|
|
|
if (mediaPayload.metadata) {
|
2022-11-24 16:35:26 -06:00
|
|
|
const mediaMetadata = mediaPayload.metadata
|
2022-03-13 17:10:48 -05:00
|
|
|
|
|
|
|
// Create new authors if in payload
|
2023-08-18 14:40:36 -05:00
|
|
|
if (mediaMetadata.authors?.length) {
|
2022-11-24 16:35:26 -06:00
|
|
|
const newAuthors = []
|
2022-03-13 17:10:48 -05:00
|
|
|
for (let i = 0; i < mediaMetadata.authors.length; i++) {
|
2023-03-31 17:04:26 -05:00
|
|
|
const authorName = (mediaMetadata.authors[i].name || '').trim()
|
|
|
|
if (!authorName) {
|
|
|
|
Logger.error(`[ApiRouter] Invalid author object, no name`, mediaMetadata.authors[i])
|
2024-03-28 17:00:07 -05:00
|
|
|
mediaMetadata.authors[i].id = null
|
2023-03-31 17:04:26 -05:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-09-17 15:29:39 -05:00
|
|
|
if (mediaMetadata.authors[i].id?.startsWith('new')) {
|
|
|
|
mediaMetadata.authors[i].id = null
|
|
|
|
}
|
|
|
|
|
2023-08-18 14:40:36 -05:00
|
|
|
// Ensure the ID for the author exists
|
|
|
|
if (mediaMetadata.authors[i].id && !(await Database.checkAuthorExists(libraryId, mediaMetadata.authors[i].id))) {
|
|
|
|
Logger.warn(`[ApiRouter] Author id "${mediaMetadata.authors[i].id}" does not exist`)
|
|
|
|
mediaMetadata.authors[i].id = null
|
|
|
|
}
|
|
|
|
|
2023-09-17 15:29:39 -05:00
|
|
|
if (!mediaMetadata.authors[i].id) {
|
2024-08-31 13:27:48 -05:00
|
|
|
let author = await Database.authorModel.getByNameAndLibrary(authorName, libraryId)
|
2022-03-13 17:10:48 -05:00
|
|
|
if (!author) {
|
2024-08-31 13:27:48 -05:00
|
|
|
author = await Database.authorModel.create({
|
|
|
|
name: authorName,
|
2024-09-01 15:08:56 -05:00
|
|
|
lastFirst: Database.authorModel.getLastFirst(authorName),
|
2024-08-31 13:27:48 -05:00
|
|
|
libraryId
|
|
|
|
})
|
|
|
|
Logger.debug(`[ApiRouter] Creating new author "${author.name}"`)
|
2022-03-13 17:10:48 -05:00
|
|
|
newAuthors.push(author)
|
2023-08-18 14:40:36 -05:00
|
|
|
// Update filter data
|
|
|
|
Database.addAuthorToFilterData(libraryId, author.name, author.id)
|
2022-03-13 17:10:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Update ID in original payload
|
|
|
|
mediaMetadata.authors[i].id = author.id
|
|
|
|
}
|
|
|
|
}
|
2024-03-28 17:00:07 -05:00
|
|
|
// Remove authors without an id
|
2024-06-19 17:14:37 -05:00
|
|
|
mediaMetadata.authors = mediaMetadata.authors.filter((au) => !!au.id)
|
2022-03-13 17:10:48 -05:00
|
|
|
if (newAuthors.length) {
|
2024-06-19 17:14:37 -05:00
|
|
|
SocketAuthority.emitter(
|
|
|
|
'authors_added',
|
2024-08-31 13:27:48 -05:00
|
|
|
newAuthors.map((au) => au.toOldJSON())
|
2024-06-19 17:14:37 -05:00
|
|
|
)
|
2022-03-13 17:10:48 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create new series if in payload
|
|
|
|
if (mediaMetadata.series && mediaMetadata.series.length) {
|
2022-11-24 16:35:26 -06:00
|
|
|
const newSeries = []
|
2022-03-13 17:10:48 -05:00
|
|
|
for (let i = 0; i < mediaMetadata.series.length; i++) {
|
2023-03-31 17:04:26 -05:00
|
|
|
const seriesName = (mediaMetadata.series[i].name || '').trim()
|
|
|
|
if (!seriesName) {
|
|
|
|
Logger.error(`[ApiRouter] Invalid series object, no name`, mediaMetadata.series[i])
|
2024-03-28 17:00:07 -05:00
|
|
|
mediaMetadata.series[i].id = null
|
2023-03-31 17:04:26 -05:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-09-17 15:29:39 -05:00
|
|
|
if (mediaMetadata.series[i].id?.startsWith('new')) {
|
|
|
|
mediaMetadata.series[i].id = null
|
|
|
|
}
|
|
|
|
|
2023-08-18 14:40:36 -05:00
|
|
|
// Ensure the ID for the series exists
|
|
|
|
if (mediaMetadata.series[i].id && !(await Database.checkSeriesExists(libraryId, mediaMetadata.series[i].id))) {
|
|
|
|
Logger.warn(`[ApiRouter] Series id "${mediaMetadata.series[i].id}" does not exist`)
|
|
|
|
mediaMetadata.series[i].id = null
|
|
|
|
}
|
|
|
|
|
2023-09-17 15:29:39 -05:00
|
|
|
if (!mediaMetadata.series[i].id) {
|
2024-09-01 15:08:56 -05:00
|
|
|
let seriesItem = await Database.seriesModel.getByNameAndLibrary(seriesName, libraryId)
|
2022-03-13 17:10:48 -05:00
|
|
|
if (!seriesItem) {
|
2024-09-01 15:08:56 -05:00
|
|
|
seriesItem = await Database.seriesModel.create({
|
|
|
|
name: seriesName,
|
|
|
|
nameIgnorePrefix: getTitleIgnorePrefix(seriesName),
|
|
|
|
libraryId
|
|
|
|
})
|
|
|
|
Logger.debug(`[ApiRouter] Creating new series "${seriesItem.name}"`)
|
2022-03-13 17:10:48 -05:00
|
|
|
newSeries.push(seriesItem)
|
2023-08-18 14:40:36 -05:00
|
|
|
// Update filter data
|
|
|
|
Database.addSeriesToFilterData(libraryId, seriesItem.name, seriesItem.id)
|
2022-03-13 17:10:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Update ID in original payload
|
|
|
|
mediaMetadata.series[i].id = seriesItem.id
|
|
|
|
}
|
|
|
|
}
|
2024-03-28 17:00:07 -05:00
|
|
|
// Remove series without an id
|
2024-06-19 17:14:37 -05:00
|
|
|
mediaMetadata.series = mediaMetadata.series.filter((se) => se.id)
|
2022-03-13 17:10:48 -05:00
|
|
|
if (newSeries.length) {
|
2024-06-19 17:14:37 -05:00
|
|
|
SocketAuthority.emitter(
|
|
|
|
'multiple_series_added',
|
2024-09-01 15:08:56 -05:00
|
|
|
newSeries.map((se) => se.toOldJSON())
|
2024-06-19 17:14:37 -05:00
|
|
|
)
|
2022-03-13 17:10:48 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-08-17 17:01:11 -05:00
|
|
|
}
|
2023-02-27 02:56:07 +00:00
|
|
|
module.exports = ApiRouter
|