2021-08-18 00:01:11 +02:00
|
|
|
const express = require('express')
|
2021-09-22 03:57:33 +02:00
|
|
|
const Path = require('path')
|
|
|
|
const fs = require('fs-extra')
|
2021-11-13 02:43:16 +01:00
|
|
|
const date = require('date-and-time')
|
2022-03-06 01:54:24 +01:00
|
|
|
const axios = require('axios')
|
2021-10-16 14:49:12 +02:00
|
|
|
|
2021-08-18 00:01:11 +02:00
|
|
|
const Logger = require('./Logger')
|
2021-11-22 03:00:40 +01:00
|
|
|
const { isObject } = require('./utils/index')
|
2022-03-06 01:54:24 +01:00
|
|
|
const { parsePodcastRssFeedXml } = require('./utils/podcastUtils')
|
2021-10-16 14:49:12 +02:00
|
|
|
|
2021-11-22 03:00:40 +01:00
|
|
|
const LibraryController = require('./controllers/LibraryController')
|
|
|
|
const UserController = require('./controllers/UserController')
|
|
|
|
const CollectionController = require('./controllers/CollectionController')
|
|
|
|
const MeController = require('./controllers/MeController')
|
|
|
|
const BackupController = require('./controllers/BackupController')
|
2022-03-11 01:45:02 +01:00
|
|
|
const LibraryItemController = require('./controllers/LibraryItemController')
|
2022-03-12 02:46:32 +01:00
|
|
|
const SeriesController = require('./controllers/SeriesController')
|
2022-03-13 12:42:43 +01:00
|
|
|
const AuthorController = require('./controllers/AuthorController')
|
2021-10-28 21:41:42 +02:00
|
|
|
|
2022-03-06 23:32:04 +01:00
|
|
|
const BookFinder = require('./finders/BookFinder')
|
|
|
|
const AuthorFinder = require('./finders/AuthorFinder')
|
|
|
|
const PodcastFinder = require('./finders/PodcastFinder')
|
2022-03-13 23:10:48 +01:00
|
|
|
|
|
|
|
const Author = require('./objects/entities/Author')
|
|
|
|
const Series = require('./objects/entities/Series')
|
2021-12-26 18:25:07 +01:00
|
|
|
const FileSystemController = require('./controllers/FileSystemController')
|
2021-08-18 00:01:11 +02:00
|
|
|
|
|
|
|
class ApiController {
|
2022-03-11 02:13:19 +01:00
|
|
|
constructor(db, auth, scanner, streamManager, downloadManager, coverController, backupManager, watcher, cacheManager, emitter, clientEmitter) {
|
2021-08-18 00:01:11 +02:00
|
|
|
this.db = db
|
|
|
|
this.auth = auth
|
2022-02-16 01:33:33 +01:00
|
|
|
this.scanner = scanner
|
2021-08-18 00:01:11 +02:00
|
|
|
this.streamManager = streamManager
|
2021-09-04 21:17:26 +02:00
|
|
|
this.downloadManager = downloadManager
|
2021-10-09 00:30:20 +02:00
|
|
|
this.backupManager = backupManager
|
2021-11-22 03:00:40 +01:00
|
|
|
this.coverController = coverController
|
2021-10-05 05:11:42 +02:00
|
|
|
this.watcher = watcher
|
2021-12-13 00:15:37 +01:00
|
|
|
this.cacheManager = cacheManager
|
2021-08-18 00:01:11 +02:00
|
|
|
this.emitter = emitter
|
2021-09-06 01:20:29 +02:00
|
|
|
this.clientEmitter = clientEmitter
|
2021-08-18 00:01:11 +02:00
|
|
|
|
2021-10-28 21:41:42 +02:00
|
|
|
this.bookFinder = new BookFinder()
|
2022-02-27 20:47:52 +01:00
|
|
|
this.authorFinder = new AuthorFinder()
|
2022-03-06 23:32:04 +01:00
|
|
|
this.podcastFinder = new PodcastFinder()
|
2021-10-28 21:41:42 +02:00
|
|
|
|
2021-08-18 00:01:11 +02:00
|
|
|
this.router = express()
|
|
|
|
this.init()
|
|
|
|
}
|
|
|
|
|
|
|
|
init() {
|
2021-11-22 03:00:40 +01:00
|
|
|
//
|
|
|
|
// Library Routes
|
|
|
|
//
|
|
|
|
this.router.post('/libraries', LibraryController.create.bind(this))
|
|
|
|
this.router.get('/libraries', LibraryController.findAll.bind(this))
|
2021-12-01 03:02:40 +01:00
|
|
|
this.router.get('/libraries/:id', LibraryController.middleware.bind(this), LibraryController.findOne.bind(this))
|
|
|
|
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))
|
|
|
|
|
2022-03-11 01:45:02 +01:00
|
|
|
this.router.get('/libraries/:id/items', LibraryController.middleware.bind(this), LibraryController.getLibraryItems.bind(this))
|
2021-12-02 02:07:03 +01:00
|
|
|
this.router.get('/libraries/:id/series', LibraryController.middleware.bind(this), LibraryController.getAllSeriesForLibrary.bind(this))
|
|
|
|
this.router.get('/libraries/:id/series/:series', LibraryController.middleware.bind(this), LibraryController.getSeriesForLibrary.bind(this))
|
2021-12-01 03:02:40 +01:00
|
|
|
this.router.get('/libraries/:id/collections', LibraryController.middleware.bind(this), LibraryController.getCollectionsForLibrary.bind(this))
|
2022-03-11 01:45:02 +01:00
|
|
|
this.router.get('/libraries/:id/personalized', LibraryController.middleware.bind(this), LibraryController.getLibraryUserPersonalized.bind(this))
|
|
|
|
this.router.get('/libraries/:id/filterdata', LibraryController.middleware.bind(this), LibraryController.getLibraryFilterData.bind(this))
|
2021-12-01 03:02:40 +01:00
|
|
|
this.router.get('/libraries/:id/search', LibraryController.middleware.bind(this), LibraryController.search.bind(this))
|
2021-12-02 02:07:03 +01:00
|
|
|
this.router.get('/libraries/:id/stats', LibraryController.middleware.bind(this), LibraryController.stats.bind(this))
|
2021-12-03 02:02:38 +01:00
|
|
|
this.router.get('/libraries/:id/authors', LibraryController.middleware.bind(this), LibraryController.getAuthors.bind(this))
|
2022-02-15 23:36:22 +01:00
|
|
|
this.router.post('/libraries/:id/matchbooks', LibraryController.middleware.bind(this), LibraryController.matchBooks.bind(this))
|
2021-12-05 16:52:23 +01:00
|
|
|
|
2022-03-13 23:10:48 +01:00
|
|
|
this.router.post('/libraries/order', LibraryController.reorder.bind(this))
|
2022-03-11 01:45:02 +01:00
|
|
|
|
|
|
|
//
|
|
|
|
// Item Routes
|
|
|
|
//
|
2022-03-14 01:34:31 +01:00
|
|
|
this.router.delete('/items/all', LibraryItemController.deleteAll.bind(this))
|
|
|
|
|
2022-03-11 01:45:02 +01:00
|
|
|
this.router.get('/items/:id', LibraryItemController.middleware.bind(this), LibraryItemController.findOne.bind(this))
|
2022-03-12 02:46:32 +01:00
|
|
|
this.router.patch('/items/:id', LibraryItemController.middleware.bind(this), LibraryItemController.update.bind(this))
|
2022-03-13 00:45:32 +01:00
|
|
|
this.router.delete('/items/:id', LibraryItemController.middleware.bind(this), LibraryItemController.delete.bind(this))
|
2022-03-12 02:46:32 +01:00
|
|
|
this.router.patch('/items/:id/media', LibraryItemController.middleware.bind(this), LibraryItemController.updateMedia.bind(this))
|
2022-03-11 01:45:02 +01:00
|
|
|
this.router.get('/items/:id/cover', LibraryItemController.middleware.bind(this), LibraryItemController.getCover.bind(this))
|
2022-03-13 00:45:32 +01: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 02:59:35 +01:00
|
|
|
this.router.get('/items/:id/stream', LibraryItemController.middleware.bind(this), LibraryItemController.openStream.bind(this))
|
2022-03-14 01:34:31 +01:00
|
|
|
this.router.post('/items/:id/match', LibraryItemController.middleware.bind(this), LibraryItemController.match.bind(this))
|
|
|
|
this.router.patch('/items/:id/tracks', LibraryItemController.middleware.bind(this), LibraryItemController.updateTracks.bind(this))
|
2022-03-11 01:45:02 +01:00
|
|
|
|
2022-03-13 23:10:48 +01: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))
|
|
|
|
|
2021-11-22 03:00:40 +01:00
|
|
|
//
|
|
|
|
// User Routes
|
|
|
|
//
|
|
|
|
this.router.post('/users', UserController.create.bind(this))
|
|
|
|
this.router.get('/users', UserController.findAll.bind(this))
|
|
|
|
this.router.get('/users/:id', UserController.findOne.bind(this))
|
|
|
|
this.router.patch('/users/:id', UserController.update.bind(this))
|
|
|
|
this.router.delete('/users/:id', UserController.delete.bind(this))
|
|
|
|
|
|
|
|
this.router.get('/users/:id/listening-sessions', UserController.getListeningStats.bind(this))
|
|
|
|
this.router.get('/users/:id/listening-stats', UserController.getListeningStats.bind(this))
|
|
|
|
|
|
|
|
//
|
|
|
|
// Collection Routes
|
|
|
|
//
|
|
|
|
this.router.post('/collections', CollectionController.create.bind(this))
|
|
|
|
this.router.get('/collections', CollectionController.findAll.bind(this))
|
|
|
|
this.router.get('/collections/:id', CollectionController.findOne.bind(this))
|
|
|
|
this.router.patch('/collections/:id', CollectionController.update.bind(this))
|
|
|
|
this.router.delete('/collections/:id', CollectionController.delete.bind(this))
|
|
|
|
|
|
|
|
this.router.post('/collections/:id/book', CollectionController.addBook.bind(this))
|
|
|
|
this.router.delete('/collections/:id/book/:bookId', CollectionController.removeBook.bind(this))
|
2021-11-27 23:01:53 +01:00
|
|
|
this.router.post('/collections/:id/batch/add', CollectionController.addBatch.bind(this))
|
|
|
|
this.router.post('/collections/:id/batch/remove', CollectionController.removeBatch.bind(this))
|
2021-11-22 03:00:40 +01:00
|
|
|
|
|
|
|
//
|
|
|
|
// Current User Routes (Me)
|
|
|
|
//
|
|
|
|
this.router.get('/me/listening-sessions', MeController.getListeningSessions.bind(this))
|
|
|
|
this.router.get('/me/listening-stats', MeController.getListeningStats.bind(this))
|
|
|
|
this.router.patch('/me/audiobook/:id/reset-progress', MeController.resetAudiobookProgress.bind(this))
|
|
|
|
this.router.patch('/me/audiobook/:id', MeController.updateAudiobookData.bind(this))
|
|
|
|
this.router.patch('/me/audiobook/batch/update', MeController.batchUpdateAudiobookData.bind(this))
|
|
|
|
this.router.patch('/me/password', MeController.updatePassword.bind(this))
|
|
|
|
this.router.patch('/me/settings', MeController.updateSettings.bind(this))
|
|
|
|
|
|
|
|
//
|
|
|
|
// Backup Routes
|
|
|
|
//
|
|
|
|
this.router.delete('/backup/:id', BackupController.delete.bind(this))
|
|
|
|
this.router.post('/backup/upload', BackupController.upload.bind(this))
|
|
|
|
|
2021-12-08 01:42:56 +01:00
|
|
|
//
|
|
|
|
// Search Routes
|
|
|
|
//
|
|
|
|
this.router.get('/search/covers', this.findCovers.bind(this))
|
|
|
|
this.router.get('/search/books', this.findBooks.bind(this))
|
2022-03-06 23:32:04 +01:00
|
|
|
this.router.get('/search/podcast', this.findPodcasts.bind(this))
|
2022-03-11 02:13:19 +01:00
|
|
|
this.router.get('/search/authors', this.findAuthor.bind(this))
|
2021-12-08 01:42:56 +01:00
|
|
|
|
2021-12-26 18:25:07 +01:00
|
|
|
//
|
|
|
|
// File System Routes
|
|
|
|
//
|
|
|
|
this.router.get('/filesystem', FileSystemController.getPaths.bind(this))
|
|
|
|
|
2021-11-22 03:00:40 +01:00
|
|
|
//
|
2022-03-12 02:46:32 +01:00
|
|
|
// Author Routes
|
2021-11-22 03:00:40 +01:00
|
|
|
//
|
2022-03-13 23:10:48 +01:00
|
|
|
this.router.get('/authors/search', AuthorController.search.bind(this))
|
2022-03-13 12:42:43 +01:00
|
|
|
this.router.get('/authors/:id', AuthorController.middleware.bind(this), AuthorController.findOne.bind(this))
|
2022-03-15 00:53:49 +01:00
|
|
|
this.router.patch('/authors/:id', AuthorController.middleware.bind(this), AuthorController.update.bind(this))
|
2022-03-13 12:42:43 +01:00
|
|
|
this.router.post('/authors/:id/match', AuthorController.middleware.bind(this), AuthorController.match.bind(this))
|
2022-03-13 16:35:35 +01:00
|
|
|
this.router.get('/authors/:id/image', AuthorController.middleware.bind(this), AuthorController.getImage.bind(this))
|
2021-11-18 02:19:24 +01:00
|
|
|
|
2022-03-12 02:46:32 +01:00
|
|
|
//
|
|
|
|
// Series Routes
|
|
|
|
//
|
|
|
|
this.router.get('/series/search', SeriesController.search.bind(this))
|
2022-03-13 23:10:48 +01:00
|
|
|
this.router.get('/series/:id', SeriesController.middleware.bind(this), SeriesController.findOne.bind(this))
|
2022-03-12 02:46:32 +01:00
|
|
|
|
|
|
|
//
|
|
|
|
// Misc Routes
|
|
|
|
//
|
2021-09-05 02:58:39 +02:00
|
|
|
this.router.patch('/serverSettings', this.updateServerSettings.bind(this))
|
|
|
|
|
2021-08-18 00:01:11 +02:00
|
|
|
this.router.post('/authorize', this.authorize.bind(this))
|
2021-08-20 00:29:36 +02:00
|
|
|
|
2021-09-04 21:17:26 +02:00
|
|
|
this.router.get('/download/:id', this.download.bind(this))
|
2021-10-05 05:11:42 +02:00
|
|
|
|
2021-11-20 03:08:03 +01:00
|
|
|
this.router.post('/syncUserAudiobookData', this.syncUserAudiobookData.bind(this))
|
2021-08-18 00:01:11 +02:00
|
|
|
|
2021-12-13 00:15:37 +01:00
|
|
|
this.router.post('/purgecache', this.purgeCache.bind(this))
|
2021-12-20 01:45:56 +01:00
|
|
|
|
|
|
|
this.router.post('/syncStream', this.syncStream.bind(this))
|
2022-01-27 01:00:40 +01:00
|
|
|
this.router.post('/syncLocal', this.syncLocal.bind(this))
|
2022-02-23 00:33:55 +01:00
|
|
|
|
|
|
|
this.router.post('/streams/:id/close', this.closeStream.bind(this))
|
2022-03-06 01:54:24 +01:00
|
|
|
|
|
|
|
this.router.post('/getPodcastFeed', this.getPodcastFeed.bind(this))
|
2021-12-12 17:52:27 +01:00
|
|
|
}
|
|
|
|
|
2021-12-08 01:42:56 +01:00
|
|
|
async findBooks(req, res) {
|
2021-12-08 21:29:10 +01:00
|
|
|
var provider = req.query.provider || 'google'
|
2021-10-28 21:41:42 +02:00
|
|
|
var title = req.query.title || ''
|
|
|
|
var author = req.query.author || ''
|
|
|
|
var results = await this.bookFinder.search(provider, title, author)
|
|
|
|
res.json(results)
|
2021-08-18 00:01:11 +02:00
|
|
|
}
|
|
|
|
|
2021-12-08 01:42:56 +01:00
|
|
|
async findCovers(req, res) {
|
|
|
|
var query = req.query
|
|
|
|
var result = await this.bookFinder.findCovers(query.provider, query.title, query.author || null)
|
|
|
|
res.json(result)
|
2021-08-21 16:15:44 +02:00
|
|
|
}
|
|
|
|
|
2022-03-06 23:32:04 +01:00
|
|
|
async findPodcasts(req, res) {
|
|
|
|
var term = req.query.term
|
|
|
|
var results = await this.podcastFinder.search(term)
|
|
|
|
res.json(results)
|
|
|
|
}
|
|
|
|
|
2022-03-11 02:13:19 +01:00
|
|
|
async findAuthor(req, res) {
|
|
|
|
var query = req.query.q
|
|
|
|
var author = await this.authorFinder.findAuthorByName(query)
|
|
|
|
res.json(author)
|
|
|
|
}
|
|
|
|
|
2021-08-18 00:01:11 +02:00
|
|
|
authorize(req, res) {
|
|
|
|
if (!req.user) {
|
|
|
|
Logger.error('Invalid user in authorize')
|
|
|
|
return res.sendStatus(401)
|
|
|
|
}
|
|
|
|
res.json({ user: req.user })
|
|
|
|
}
|
|
|
|
|
2021-09-05 02:58:39 +02:00
|
|
|
async updateServerSettings(req, res) {
|
2021-09-07 00:42:15 +02:00
|
|
|
if (!req.user.isRoot) {
|
|
|
|
Logger.error('User other than root attempting to update server settings', req.user)
|
|
|
|
return res.sendStatus(403)
|
|
|
|
}
|
2021-09-05 02:58:39 +02:00
|
|
|
var settingsUpdate = req.body
|
|
|
|
if (!settingsUpdate || !isObject(settingsUpdate)) {
|
2021-10-06 04:10:49 +02:00
|
|
|
return res.status(500).send('Invalid settings update object')
|
2021-09-05 02:58:39 +02:00
|
|
|
}
|
2021-10-09 18:09:06 +02:00
|
|
|
|
2021-09-05 02:58:39 +02:00
|
|
|
var madeUpdates = this.db.serverSettings.update(settingsUpdate)
|
|
|
|
if (madeUpdates) {
|
2021-10-09 18:09:06 +02:00
|
|
|
// If backup schedule is updated - update backup manager
|
|
|
|
if (settingsUpdate.backupSchedule !== undefined) {
|
|
|
|
this.backupManager.updateCronSchedule()
|
|
|
|
}
|
|
|
|
|
2022-02-27 20:47:52 +01:00
|
|
|
await this.db.updateServerSettings()
|
2021-09-05 02:58:39 +02:00
|
|
|
}
|
|
|
|
return res.json({
|
|
|
|
success: true,
|
|
|
|
serverSettings: this.db.serverSettings
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-09-04 21:17:26 +02:00
|
|
|
async download(req, res) {
|
2021-09-07 00:42:15 +02:00
|
|
|
if (!req.user.canDownload) {
|
|
|
|
Logger.error('User attempting to download without permission', req.user)
|
|
|
|
return res.sendStatus(403)
|
|
|
|
}
|
2021-09-04 21:17:26 +02:00
|
|
|
var downloadId = req.params.id
|
|
|
|
Logger.info('Download Request', downloadId)
|
|
|
|
var download = this.downloadManager.getDownload(downloadId)
|
|
|
|
if (!download) {
|
|
|
|
Logger.error('Download request not found', downloadId)
|
|
|
|
return res.sendStatus(404)
|
|
|
|
}
|
|
|
|
|
|
|
|
var options = {
|
|
|
|
headers: {
|
|
|
|
'Content-Type': download.mimeType
|
|
|
|
}
|
|
|
|
}
|
|
|
|
res.download(download.fullPath, download.filename, options, (err) => {
|
|
|
|
if (err) {
|
|
|
|
Logger.error('Download Error', err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-10-05 05:11:42 +02:00
|
|
|
async getDirectories(dir, relpath, excludedDirs, level = 0) {
|
|
|
|
try {
|
|
|
|
var paths = await fs.readdir(dir)
|
|
|
|
|
|
|
|
var dirs = await Promise.all(paths.map(async dirname => {
|
|
|
|
var fullPath = Path.join(dir, dirname)
|
|
|
|
var path = Path.join(relpath, dirname)
|
|
|
|
|
|
|
|
var isDir = (await fs.lstat(fullPath)).isDirectory()
|
2021-10-11 02:29:22 +02:00
|
|
|
if (isDir && !excludedDirs.includes(path) && dirname !== 'node_modules') {
|
2021-10-05 05:11:42 +02:00
|
|
|
return {
|
|
|
|
path,
|
|
|
|
dirname,
|
|
|
|
fullPath,
|
|
|
|
level,
|
|
|
|
dirs: level < 4 ? (await this.getDirectories(fullPath, path, excludedDirs, level + 1)) : []
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
dirs = dirs.filter(d => d)
|
|
|
|
return dirs
|
|
|
|
} catch (error) {
|
|
|
|
Logger.error('Failed to readdir', dir, error)
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-22 03:00:40 +01:00
|
|
|
async syncUserAudiobookData(req, res) {
|
|
|
|
if (!req.body.data) {
|
|
|
|
return res.status(403).send('Invalid local user audiobook data')
|
|
|
|
}
|
|
|
|
|
|
|
|
var hasUpdates = false
|
|
|
|
|
|
|
|
// Local user audiobook data use the latest update
|
|
|
|
req.body.data.forEach((uab) => {
|
|
|
|
if (!uab || !uab.audiobookId) {
|
|
|
|
Logger.error('[ApiController] Invalid user audiobook data', uab)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var audiobook = this.db.audiobooks.find(ab => ab.id === uab.audiobookId)
|
|
|
|
if (!audiobook) {
|
|
|
|
Logger.info('[ApiController] syncUserAudiobookData local audiobook data audiobook no longer exists', uab.audiobookId)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (req.user.syncLocalUserAudiobookData(uab, audiobook)) {
|
|
|
|
this.clientEmitter(req.user.id, 'current_user_audiobook_update', { id: uab.audiobookId, data: uab })
|
|
|
|
hasUpdates = true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
if (hasUpdates) {
|
|
|
|
await this.db.updateEntity('user', req.user)
|
|
|
|
}
|
|
|
|
|
|
|
|
var allUserAudiobookData = Object.values(req.user.audiobooksToJSON())
|
|
|
|
res.json(allUserAudiobookData)
|
2021-11-13 02:43:16 +01:00
|
|
|
}
|
|
|
|
|
2022-01-27 01:00:40 +01:00
|
|
|
// Sync audiobook stream progress
|
2021-12-20 01:45:56 +01:00
|
|
|
async syncStream(req, res) {
|
|
|
|
Logger.debug(`[ApiController] syncStream for ${req.user.username} - ${req.body.streamId}`)
|
|
|
|
this.streamManager.streamSyncFromApi(req, res)
|
|
|
|
}
|
|
|
|
|
2022-01-27 01:00:40 +01:00
|
|
|
// Sync local downloaded audiobook progress
|
|
|
|
async syncLocal(req, res) {
|
|
|
|
Logger.debug(`[ApiController] syncLocal for ${req.user.username}`)
|
|
|
|
var progressPayload = req.body
|
|
|
|
var audiobookProgress = req.user.updateAudiobookData(progressPayload.audiobookId, progressPayload)
|
|
|
|
if (audiobookProgress) {
|
|
|
|
await this.db.updateEntity('user', req.user)
|
|
|
|
this.clientEmitter(req.user.id, 'current_user_audiobook_update', {
|
|
|
|
id: progressPayload.audiobookId,
|
|
|
|
data: audiobookProgress || null
|
|
|
|
})
|
|
|
|
}
|
2022-02-23 00:33:55 +01:00
|
|
|
res.sendStatus(200)
|
2022-01-27 01:00:40 +01:00
|
|
|
}
|
|
|
|
|
2021-11-22 03:00:40 +01:00
|
|
|
//
|
|
|
|
// Helper Methods
|
|
|
|
//
|
|
|
|
userJsonWithBookProgressDetails(user) {
|
|
|
|
var json = user.toJSONForBrowser()
|
|
|
|
|
|
|
|
// User audiobook progress attach book details
|
|
|
|
if (json.audiobooks && Object.keys(json.audiobooks).length) {
|
|
|
|
for (const audiobookId in json.audiobooks) {
|
2022-03-13 23:33:50 +01:00
|
|
|
var libraryItem = this.db.libraryItems.find(li => li.id === audiobookId)
|
|
|
|
if (!libraryItem) {
|
|
|
|
Logger.error('[ApiController] Library item not found for users progress ' + audiobookId)
|
2021-11-22 03:00:40 +01:00
|
|
|
} else {
|
2022-03-13 23:33:50 +01:00
|
|
|
json.audiobooks[audiobookId].media = libraryItem.media.toJSONExpanded()
|
2021-11-22 03:00:40 +01:00
|
|
|
}
|
|
|
|
}
|
2021-11-13 02:43:16 +01:00
|
|
|
}
|
2021-11-22 03:00:40 +01:00
|
|
|
|
|
|
|
return json
|
2021-11-13 02:43:16 +01:00
|
|
|
}
|
|
|
|
|
2022-03-13 00:45:32 +01:00
|
|
|
async handleDeleteLibraryItem(libraryItem) {
|
|
|
|
// Remove libraryItem from users
|
2021-11-22 03:00:40 +01:00
|
|
|
for (let i = 0; i < this.db.users.length; i++) {
|
|
|
|
var user = this.db.users[i]
|
2022-03-13 00:45:32 +01:00
|
|
|
var madeUpdates = user.deleteAudiobookData(libraryItem.id)
|
2021-11-22 03:00:40 +01:00
|
|
|
if (madeUpdates) {
|
|
|
|
await this.db.updateEntity('user', user)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove any streams open for this audiobook
|
2022-03-13 00:45:32 +01:00
|
|
|
var streams = this.streamManager.streams.filter(stream => stream.audiobookId === libraryItem.id)
|
2021-11-22 03:00:40 +01:00
|
|
|
for (let i = 0; i < streams.length; i++) {
|
|
|
|
var stream = streams[i]
|
|
|
|
var client = stream.client
|
|
|
|
await stream.close()
|
|
|
|
if (client && client.user) {
|
|
|
|
client.user.stream = null
|
|
|
|
client.stream = null
|
|
|
|
this.db.updateUserStream(client.user.id, null)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove book from collections
|
2022-03-13 00:45:32 +01:00
|
|
|
var collectionsWithBook = this.db.collections.filter(c => c.books.includes(libraryItem.id))
|
2021-11-22 03:00:40 +01:00
|
|
|
for (let i = 0; i < collectionsWithBook.length; i++) {
|
|
|
|
var collection = collectionsWithBook[i]
|
2022-03-13 00:45:32 +01:00
|
|
|
collection.removeBook(libraryItem.id)
|
2021-11-22 03:00:40 +01:00
|
|
|
await this.db.updateEntity('collection', collection)
|
2022-03-13 00:45:32 +01:00
|
|
|
this.clientEmitter(collection.userId, 'collection_updated', collection.toJSONExpanded(this.db.libraryItems))
|
2021-11-13 02:43:16 +01:00
|
|
|
}
|
2021-11-22 03:00:40 +01:00
|
|
|
|
2021-12-13 00:15:37 +01:00
|
|
|
// purge cover cache
|
2022-03-13 00:45:32 +01:00
|
|
|
if (libraryItem.media.coverPath) {
|
|
|
|
await this.cacheManager.purgeCoverCache(libraryItem.id)
|
2021-12-13 00:15:37 +01:00
|
|
|
}
|
|
|
|
|
2022-03-13 00:45:32 +01:00
|
|
|
var json = libraryItem.toJSONExpanded()
|
|
|
|
await this.db.removeLibraryItem(libraryItem.id)
|
|
|
|
this.emitter('item_removed', json)
|
2021-11-22 03:00:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async getUserListeningSessionsHelper(userId) {
|
|
|
|
var userSessions = await this.db.selectUserSessions(userId)
|
|
|
|
var listeningSessions = userSessions.filter(us => us.sessionType === 'listeningSession')
|
|
|
|
return listeningSessions.sort((a, b) => b.lastUpdate - a.lastUpdate)
|
2021-11-13 02:43:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async getUserListeningStatsHelpers(userId) {
|
|
|
|
const today = date.format(new Date(), 'YYYY-MM-DD')
|
|
|
|
|
|
|
|
var listeningSessions = await this.getUserListeningSessionsHelper(userId)
|
|
|
|
var listeningStats = {
|
|
|
|
totalTime: 0,
|
|
|
|
books: {},
|
|
|
|
days: {},
|
|
|
|
dayOfWeek: {},
|
2021-12-29 22:53:19 +01:00
|
|
|
today: 0,
|
|
|
|
recentSessions: listeningSessions.slice(0, 10)
|
2021-11-13 02:43:16 +01:00
|
|
|
}
|
|
|
|
listeningSessions.forEach((s) => {
|
|
|
|
if (s.dayOfWeek) {
|
|
|
|
if (!listeningStats.dayOfWeek[s.dayOfWeek]) listeningStats.dayOfWeek[s.dayOfWeek] = 0
|
|
|
|
listeningStats.dayOfWeek[s.dayOfWeek] += s.timeListening
|
|
|
|
}
|
2021-12-29 22:53:19 +01:00
|
|
|
if (s.date && s.timeListening > 0) {
|
2021-11-13 02:43:16 +01:00
|
|
|
if (!listeningStats.days[s.date]) listeningStats.days[s.date] = 0
|
|
|
|
listeningStats.days[s.date] += s.timeListening
|
|
|
|
|
|
|
|
if (s.date === today) {
|
|
|
|
listeningStats.today += s.timeListening
|
|
|
|
}
|
|
|
|
}
|
2021-12-29 22:53:19 +01:00
|
|
|
if (!listeningStats.books[s.audiobookId]) {
|
|
|
|
listeningStats.books[s.audiobookId] = {
|
|
|
|
id: s.audiobookId,
|
|
|
|
timeListening: s.timeListening,
|
|
|
|
title: s.audiobookTitle,
|
|
|
|
author: s.audiobookAuthor,
|
|
|
|
lastUpdate: s.lastUpdate
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
listeningStats.books[s.audiobookId].timeListening += s.timeListening
|
|
|
|
}
|
2021-11-13 02:43:16 +01:00
|
|
|
|
|
|
|
listeningStats.totalTime += s.timeListening
|
|
|
|
})
|
|
|
|
return listeningStats
|
|
|
|
}
|
2021-12-13 00:15:37 +01:00
|
|
|
|
|
|
|
async purgeCache(req, res) {
|
|
|
|
if (!req.user.isRoot) {
|
|
|
|
return res.sendStatus(403)
|
|
|
|
}
|
|
|
|
Logger.info(`[ApiController] Purging all cache`)
|
|
|
|
await this.cacheManager.purgeAll()
|
|
|
|
res.sendStatus(200)
|
|
|
|
}
|
2022-02-23 00:33:55 +01:00
|
|
|
|
|
|
|
async closeStream(req, res) {
|
|
|
|
const streamId = req.params.id
|
|
|
|
const userId = req.user.id
|
|
|
|
this.streamManager.closeStreamApiRequest(userId, streamId)
|
|
|
|
res.sendStatus(200)
|
|
|
|
}
|
2022-03-06 01:54:24 +01:00
|
|
|
|
2022-03-13 23:10:48 +01:00
|
|
|
async createAuthorsAndSeriesForItemUpdate(mediaPayload) {
|
|
|
|
if (mediaPayload.metadata) {
|
|
|
|
var mediaMetadata = mediaPayload.metadata
|
|
|
|
|
|
|
|
// Create new authors if in payload
|
|
|
|
if (mediaMetadata.authors && mediaMetadata.authors.length) {
|
|
|
|
// TODO: validate authors
|
|
|
|
var newAuthors = []
|
|
|
|
for (let i = 0; i < mediaMetadata.authors.length; i++) {
|
|
|
|
if (mediaMetadata.authors[i].id.startsWith('new')) {
|
|
|
|
var author = this.db.authors.find(au => au.checkNameEquals(mediaMetadata.authors[i].name))
|
|
|
|
if (!author) {
|
|
|
|
author = new Author()
|
|
|
|
author.setData(mediaMetadata.authors[i])
|
|
|
|
Logger.debug(`[ApiController] Created new author "${author.name}"`)
|
|
|
|
newAuthors.push(author)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update ID in original payload
|
|
|
|
mediaMetadata.authors[i].id = author.id
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (newAuthors.length) {
|
|
|
|
await this.db.insertEntities('author', newAuthors)
|
|
|
|
this.emitter('authors_added', newAuthors)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create new series if in payload
|
|
|
|
if (mediaMetadata.series && mediaMetadata.series.length) {
|
|
|
|
// TODO: validate series
|
|
|
|
var newSeries = []
|
|
|
|
for (let i = 0; i < mediaMetadata.series.length; i++) {
|
|
|
|
if (mediaMetadata.series[i].id.startsWith('new')) {
|
|
|
|
var seriesItem = this.db.series.find(se => se.checkNameEquals(mediaMetadata.series[i].name))
|
|
|
|
if (!seriesItem) {
|
|
|
|
seriesItem = new Series()
|
|
|
|
seriesItem.setData(mediaMetadata.series[i])
|
|
|
|
Logger.debug(`[ApiController] Created new series "${seriesItem.name}"`)
|
|
|
|
newSeries.push(seriesItem)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update ID in original payload
|
|
|
|
mediaMetadata.series[i].id = seriesItem.id
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (newSeries.length) {
|
|
|
|
await this.db.insertEntities('series', newSeries)
|
|
|
|
this.emitter('authors_added', newSeries)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-06 01:54:24 +01:00
|
|
|
getPodcastFeed(req, res) {
|
|
|
|
var url = req.body.rssFeed
|
|
|
|
if (!url) {
|
|
|
|
return res.status(400).send('Bad request')
|
|
|
|
}
|
|
|
|
|
|
|
|
axios.get(url).then(async (data) => {
|
|
|
|
if (!data || !data.data) {
|
|
|
|
Logger.error('Invalid podcast feed request response')
|
|
|
|
return res.status(500).send('Bad response from feed request')
|
|
|
|
}
|
|
|
|
var podcast = await parsePodcastRssFeedXml(data.data)
|
|
|
|
if (!podcast) {
|
|
|
|
return res.status(500).send('Invalid podcast RSS feed')
|
|
|
|
}
|
|
|
|
res.json(podcast)
|
|
|
|
}).catch((error) => {
|
|
|
|
console.error('Failed', error)
|
|
|
|
res.status(500).send(error)
|
|
|
|
})
|
|
|
|
}
|
2021-08-18 00:01:11 +02:00
|
|
|
}
|
|
|
|
module.exports = ApiController
|