mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-05-24 01:13:00 -04:00
Update more API endpoints to use new user model
This commit is contained in:
parent
9facf77ff1
commit
afc16358ca
@ -365,7 +365,7 @@ class AuthorController {
|
||||
if (req.method == 'DELETE' && !req.userNew.canDelete) {
|
||||
Logger.warn(`[AuthorController] User "${req.userNew.username}" attempted to delete without permission`)
|
||||
return res.sendStatus(403)
|
||||
} else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) {
|
||||
} else if ((req.method == 'PATCH' || req.method == 'POST') && !req.userNew.canUpdate) {
|
||||
Logger.warn(`[AuthorController] User "${req.userNew.username}" attempted to update without permission`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
@ -337,7 +337,7 @@ class CollectionController {
|
||||
if (req.method == 'DELETE' && !req.userNew.canDelete) {
|
||||
Logger.warn(`[CollectionController] User "${req.userNew.username}" attempted to delete without permission`)
|
||||
return res.sendStatus(403)
|
||||
} else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) {
|
||||
} else if ((req.method == 'PATCH' || req.method == 'POST') && !req.userNew.canUpdate) {
|
||||
Logger.warn(`[CollectionController] User "${req.userNew.username}" attempted to update without permission`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
const { Request, Response, NextFunction } = require('express')
|
||||
const Path = require('path')
|
||||
const fs = require('../libs/fsExtra')
|
||||
const Logger = require('../Logger')
|
||||
@ -15,6 +16,14 @@ const CacheManager = require('../managers/CacheManager')
|
||||
const CoverManager = require('../managers/CoverManager')
|
||||
const ShareManager = require('../managers/ShareManager')
|
||||
|
||||
/**
|
||||
* @typedef RequestUserObjects
|
||||
* @property {import('../models/User')} userNew
|
||||
* @property {import('../objects/user/User')} user
|
||||
*
|
||||
* @typedef {Request & RequestUserObjects} RequestWithUser
|
||||
*/
|
||||
|
||||
class LibraryItemController {
|
||||
constructor() {}
|
||||
|
||||
@ -328,7 +337,14 @@ class LibraryItemController {
|
||||
return CacheManager.handleCoverCache(res, libraryItem.id, libraryItem.media.coverPath, options)
|
||||
}
|
||||
|
||||
// POST: api/items/:id/play
|
||||
/**
|
||||
* POST: /api/items/:id/play
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
startPlaybackSession(req, res) {
|
||||
if (!req.libraryItem.media.numTracks && req.libraryItem.mediaType !== 'video') {
|
||||
Logger.error(`[LibraryItemController] startPlaybackSession cannot playback ${req.libraryItem.id}`)
|
||||
@ -338,7 +354,14 @@ class LibraryItemController {
|
||||
this.playbackSessionManager.startSessionRequest(req, res, null)
|
||||
}
|
||||
|
||||
// POST: api/items/:id/play/:episodeId
|
||||
/**
|
||||
* POST: /api/items/:id/play/:episodeId
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
startEpisodePlaybackSession(req, res) {
|
||||
var libraryItem = req.libraryItem
|
||||
if (!libraryItem.media.numTracks) {
|
||||
@ -830,7 +853,7 @@ class LibraryItemController {
|
||||
} else if (req.method == 'DELETE' && !req.userNew.canDelete) {
|
||||
Logger.warn(`[LibraryItemController] User "${req.userNew.username}" attempted to delete without permission`)
|
||||
return res.sendStatus(403)
|
||||
} else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) {
|
||||
} else if ((req.method == 'PATCH' || req.method == 'POST') && !req.userNew.canUpdate) {
|
||||
Logger.warn(`[LibraryItemController] User "${req.userNew.username}" attempted to update without permission`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ const userStats = require('../utils/queries/userStats')
|
||||
* @property {import('../objects/user/User')} user
|
||||
*
|
||||
* @typedef {Request & RequestUserObjects} RequestWithUser
|
||||
*
|
||||
*/
|
||||
|
||||
class MeController {
|
||||
|
@ -1,5 +1,6 @@
|
||||
const Sequelize = require('sequelize')
|
||||
const Path = require('path')
|
||||
const { Request, Response } = require('express')
|
||||
const fs = require('../libs/fsExtra')
|
||||
const Logger = require('../Logger')
|
||||
const SocketAuthority = require('../SocketAuthority')
|
||||
@ -13,21 +14,27 @@ const { sanitizeFilename } = require('../utils/fileUtils')
|
||||
const TaskManager = require('../managers/TaskManager')
|
||||
const adminStats = require('../utils/queries/adminStats')
|
||||
|
||||
//
|
||||
// This is a controller for routes that don't have a home yet :(
|
||||
//
|
||||
/**
|
||||
* @typedef RequestUserObjects
|
||||
* @property {import('../models/User')} userNew
|
||||
* @property {import('../objects/user/User')} user
|
||||
*
|
||||
* @typedef {Request & RequestUserObjects} RequestWithUser
|
||||
*/
|
||||
|
||||
class MiscController {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* POST: /api/upload
|
||||
* Update library item
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async handleUpload(req, res) {
|
||||
if (!req.user.canUpload) {
|
||||
Logger.warn('User attempted to upload without permission', req.user)
|
||||
if (!req.userNew.canUpload) {
|
||||
Logger.warn(`User "${req.userNew.username}" attempted to upload without permission`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
if (!req.files) {
|
||||
@ -83,8 +90,9 @@ class MiscController {
|
||||
/**
|
||||
* GET: /api/tasks
|
||||
* Get tasks for task manager
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
getTasks(req, res) {
|
||||
const includeArray = (req.query.include || '').split(',')
|
||||
@ -106,12 +114,12 @@ class MiscController {
|
||||
* PATCH: /api/settings
|
||||
* Update server settings
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async updateServerSettings(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error('User other than admin attempting to update server settings', req.user)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`User "${req.userNew.username}" other than admin attempting to update server settings`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
const settingsUpdate = req.body
|
||||
@ -137,12 +145,12 @@ class MiscController {
|
||||
/**
|
||||
* PATCH: /api/sorting-prefixes
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async updateSortingPrefixes(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error('User other than admin attempting to update server sorting prefixes', req.user)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`User "${req.userNew.username}" other than admin attempting to update server sorting prefixes`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
let sortingPrefixes = req.body.sortingPrefixes
|
||||
@ -237,14 +245,10 @@ class MiscController {
|
||||
*
|
||||
* @this import('../routers/ApiRouter')
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async authorize(req, res) {
|
||||
if (!req.user) {
|
||||
Logger.error('Invalid user in authorize')
|
||||
return res.sendStatus(401)
|
||||
}
|
||||
const userResponse = await this.auth.getUserLoginResponsePayload(req.userNew)
|
||||
res.json(userResponse)
|
||||
}
|
||||
@ -252,13 +256,14 @@ class MiscController {
|
||||
/**
|
||||
* GET: /api/tags
|
||||
* Get all tags
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getAllTags(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user attempted to getAllTags`)
|
||||
return res.sendStatus(404)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to getAllTags`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
const tags = []
|
||||
@ -295,13 +300,14 @@ class MiscController {
|
||||
* POST: /api/tags/rename
|
||||
* Rename tag
|
||||
* Req.body { tag, newTag }
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async renameTag(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user attempted to renameTag`)
|
||||
return res.sendStatus(404)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to renameTag`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
const tag = req.body.tag
|
||||
@ -349,13 +355,14 @@ class MiscController {
|
||||
* DELETE: /api/tags/:tag
|
||||
* Remove a tag
|
||||
* :tag param is base64 encoded
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async deleteTag(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user attempted to deleteTag`)
|
||||
return res.sendStatus(404)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to deleteTag`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
const tag = Buffer.from(decodeURIComponent(req.params.tag), 'base64').toString()
|
||||
@ -388,13 +395,14 @@ class MiscController {
|
||||
/**
|
||||
* GET: /api/genres
|
||||
* Get all genres
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getAllGenres(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user attempted to getAllGenres`)
|
||||
return res.sendStatus(404)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to getAllGenres`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
const genres = []
|
||||
const books = await Database.bookModel.findAll({
|
||||
@ -430,13 +438,14 @@ class MiscController {
|
||||
* POST: /api/genres/rename
|
||||
* Rename genres
|
||||
* Req.body { genre, newGenre }
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async renameGenre(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user attempted to renameGenre`)
|
||||
return res.sendStatus(404)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to renameGenre`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
const genre = req.body.genre
|
||||
@ -484,13 +493,14 @@ class MiscController {
|
||||
* DELETE: /api/genres/:genre
|
||||
* Remove a genre
|
||||
* :genre param is base64 encoded
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async deleteGenre(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user attempted to deleteGenre`)
|
||||
return res.sendStatus(404)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to deleteGenre`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
const genre = Buffer.from(decodeURIComponent(req.params.genre), 'base64').toString()
|
||||
@ -526,15 +536,16 @@ class MiscController {
|
||||
* Req.body { libraryId, path, type, [oldPath] }
|
||||
* type = add, unlink, rename
|
||||
* oldPath = required only for rename
|
||||
*
|
||||
* @this import('../routers/ApiRouter')
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
updateWatchedPath(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user attempted to updateWatchedPath`)
|
||||
return res.sendStatus(404)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to updateWatchedPath`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
const libraryId = req.body.libraryId
|
||||
@ -586,12 +597,12 @@ class MiscController {
|
||||
/**
|
||||
* GET: api/auth-settings (admin only)
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
getAuthSettings(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user "${req.user.username}" attempted to get auth settings`)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to get auth settings`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
return res.json(Database.serverSettings.authenticationSettings)
|
||||
@ -601,12 +612,12 @@ class MiscController {
|
||||
* PATCH: api/auth-settings
|
||||
* @this import('../routers/ApiRouter')
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async updateAuthSettings(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user "${req.user.username}" attempted to update auth settings`)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to update auth settings`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
@ -706,12 +717,12 @@ class MiscController {
|
||||
/**
|
||||
* GET: /api/stats/year/:year
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getAdminStatsForYear(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user "${req.user.username}" attempted to get admin stats for year`)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to get admin stats for year`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
const year = Number(req.params.year)
|
||||
@ -727,12 +738,12 @@ class MiscController {
|
||||
* GET: /api/logger-data
|
||||
* admin or up
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getLoggerData(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user "${req.user.username}" attempted to get logger data`)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[MiscController] Non-admin user "${req.userNew.username}" attempted to get logger data`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,27 @@
|
||||
const Logger = require('../Logger')
|
||||
const { Request, Response, NextFunction } = require('express')
|
||||
const Database = require('../Database')
|
||||
const { version } = require('../../package.json')
|
||||
|
||||
class NotificationController {
|
||||
constructor() { }
|
||||
/**
|
||||
* @typedef RequestUserObjects
|
||||
* @property {import('../models/User')} userNew
|
||||
* @property {import('../objects/user/User')} user
|
||||
*
|
||||
* @typedef {Request & RequestUserObjects} RequestWithUser
|
||||
*/
|
||||
|
||||
class NotificationController {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* GET: /api/notifications
|
||||
* Get notifications, settings and data
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
get(req, res) {
|
||||
res.json({
|
||||
data: this.notificationManager.getData(),
|
||||
@ -12,6 +29,12 @@ class NotificationController {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH: /api/notifications
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async update(req, res) {
|
||||
const updated = Database.notificationSettings.update(req.body)
|
||||
if (updated) {
|
||||
@ -20,15 +43,38 @@ class NotificationController {
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
/**
|
||||
* GET: /api/notificationdata
|
||||
* @deprecated Use /api/notifications
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
getData(req, res) {
|
||||
res.json(this.notificationManager.getData())
|
||||
}
|
||||
|
||||
/**
|
||||
* GET: /api/notifications/test
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async fireTestEvent(req, res) {
|
||||
await this.notificationManager.triggerNotification('onTest', { version: `v${version}` }, req.query.fail === '1')
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
/**
|
||||
* POST: /api/notifications
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async createNotification(req, res) {
|
||||
const success = Database.notificationSettings.createNotification(req.body)
|
||||
|
||||
@ -38,6 +84,12 @@ class NotificationController {
|
||||
res.json(Database.notificationSettings)
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE: /api/notifications/:id
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async deleteNotification(req, res) {
|
||||
if (Database.notificationSettings.removeNotification(req.notification.id)) {
|
||||
await Database.updateSetting(Database.notificationSettings)
|
||||
@ -45,6 +97,12 @@ class NotificationController {
|
||||
res.json(Database.notificationSettings)
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH: /api/notifications/:id
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async updateNotification(req, res) {
|
||||
const success = Database.notificationSettings.updateNotification(req.body)
|
||||
if (success) {
|
||||
@ -53,17 +111,32 @@ class NotificationController {
|
||||
res.json(Database.notificationSettings)
|
||||
}
|
||||
|
||||
/**
|
||||
* GET: /api/notifications/:id/test
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async sendNotificationTest(req, res) {
|
||||
if (!Database.notificationSettings.isUseable) return res.status(500).send('Apprise is not configured')
|
||||
if (!Database.notificationSettings.isUseable) return res.status(400).send('Apprise is not configured')
|
||||
|
||||
const success = await this.notificationManager.sendTestNotification(req.notification)
|
||||
if (success) res.sendStatus(200)
|
||||
else res.sendStatus(500)
|
||||
}
|
||||
|
||||
/**
|
||||
* Requires admin or up
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
middleware(req, res, next) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
return res.sendStatus(404)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
if (req.params.id) {
|
||||
@ -77,4 +150,4 @@ class NotificationController {
|
||||
next()
|
||||
}
|
||||
}
|
||||
module.exports = new NotificationController()
|
||||
module.exports = new NotificationController()
|
||||
|
@ -1,21 +1,31 @@
|
||||
const { Request, Response, NextFunction } = require('express')
|
||||
const Logger = require('../Logger')
|
||||
const SocketAuthority = require('../SocketAuthority')
|
||||
const Database = require('../Database')
|
||||
|
||||
const Playlist = require('../objects/Playlist')
|
||||
|
||||
/**
|
||||
* @typedef RequestUserObjects
|
||||
* @property {import('../models/User')} userNew
|
||||
* @property {import('../objects/user/User')} user
|
||||
*
|
||||
* @typedef {Request & RequestUserObjects} RequestWithUser
|
||||
*/
|
||||
|
||||
class PlaylistController {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* POST: /api/playlists
|
||||
* Create playlist
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async create(req, res) {
|
||||
const oldPlaylist = new Playlist()
|
||||
req.body.userId = req.user.id
|
||||
req.body.userId = req.userNew.id
|
||||
const success = oldPlaylist.setData(req.body)
|
||||
if (!success) {
|
||||
return res.status(400).send('Invalid playlist request data')
|
||||
@ -58,13 +68,14 @@ class PlaylistController {
|
||||
/**
|
||||
* GET: /api/playlists
|
||||
* Get all playlists for user
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async findAllForUser(req, res) {
|
||||
const playlistsForUser = await Database.playlistModel.findAll({
|
||||
where: {
|
||||
userId: req.user.id
|
||||
userId: req.userNew.id
|
||||
}
|
||||
})
|
||||
const playlists = []
|
||||
@ -79,8 +90,9 @@ class PlaylistController {
|
||||
|
||||
/**
|
||||
* GET: /api/playlists/:id
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async findOne(req, res) {
|
||||
const jsonExpanded = await req.playlist.getOldJsonExpanded()
|
||||
@ -90,8 +102,9 @@ class PlaylistController {
|
||||
/**
|
||||
* PATCH: /api/playlists/:id
|
||||
* Update playlist
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async update(req, res) {
|
||||
const updatedPlaylist = req.playlist.set(req.body)
|
||||
@ -156,8 +169,9 @@ class PlaylistController {
|
||||
/**
|
||||
* DELETE: /api/playlists/:id
|
||||
* Remove playlist
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async delete(req, res) {
|
||||
const jsonExpanded = await req.playlist.getOldJsonExpanded()
|
||||
@ -169,8 +183,9 @@ class PlaylistController {
|
||||
/**
|
||||
* POST: /api/playlists/:id/item
|
||||
* Add item to playlist
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async addItem(req, res) {
|
||||
const oldPlaylist = await Database.playlistModel.getById(req.playlist.id)
|
||||
@ -213,8 +228,9 @@ class PlaylistController {
|
||||
/**
|
||||
* DELETE: /api/playlists/:id/item/:libraryItemId/:episodeId?
|
||||
* Remove item from playlist
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async removeItem(req, res) {
|
||||
const oldLibraryItem = await Database.libraryItemModel.getOldById(req.params.libraryItemId)
|
||||
@ -266,8 +282,9 @@ class PlaylistController {
|
||||
/**
|
||||
* POST: /api/playlists/:id/batch/add
|
||||
* Batch add playlist items
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async addBatch(req, res) {
|
||||
if (!req.body.items?.length) {
|
||||
@ -330,8 +347,9 @@ class PlaylistController {
|
||||
/**
|
||||
* POST: /api/playlists/:id/batch/remove
|
||||
* Batch remove playlist items
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async removeBatch(req, res) {
|
||||
if (!req.body.items?.length) {
|
||||
@ -387,8 +405,9 @@ class PlaylistController {
|
||||
/**
|
||||
* POST: /api/playlists/collection/:collectionId
|
||||
* Create a playlist from a collection
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async createFromCollection(req, res) {
|
||||
const collection = await Database.collectionModel.findByPk(req.params.collectionId)
|
||||
@ -409,7 +428,7 @@ class PlaylistController {
|
||||
|
||||
const oldPlaylist = new Playlist()
|
||||
oldPlaylist.setData({
|
||||
userId: req.user.id,
|
||||
userId: req.userNew.id,
|
||||
libraryId: collection.libraryId,
|
||||
name: collection.name,
|
||||
description: collection.description || null
|
||||
@ -436,14 +455,20 @@ class PlaylistController {
|
||||
res.json(jsonExpanded)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async middleware(req, res, next) {
|
||||
if (req.params.id) {
|
||||
const playlist = await Database.playlistModel.findByPk(req.params.id)
|
||||
if (!playlist) {
|
||||
return res.status(404).send('Playlist not found')
|
||||
}
|
||||
if (playlist.userId !== req.user.id) {
|
||||
Logger.warn(`[PlaylistController] Playlist ${req.params.id} requested by user ${req.user.id} that is not the owner`)
|
||||
if (playlist.userId !== req.userNew.id) {
|
||||
Logger.warn(`[PlaylistController] Playlist ${req.params.id} requested by user ${req.userNew.id} that is not the owner`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
req.playlist = playlist
|
||||
|
@ -1,3 +1,4 @@
|
||||
const { Request, Response, NextFunction } = require('express')
|
||||
const Logger = require('../Logger')
|
||||
const SocketAuthority = require('../SocketAuthority')
|
||||
const Database = require('../Database')
|
||||
@ -13,6 +14,14 @@ const CoverManager = require('../managers/CoverManager')
|
||||
|
||||
const LibraryItem = require('../objects/LibraryItem')
|
||||
|
||||
/**
|
||||
* @typedef RequestUserObjects
|
||||
* @property {import('../models/User')} userNew
|
||||
* @property {import('../objects/user/User')} user
|
||||
*
|
||||
* @typedef {Request & RequestUserObjects} RequestWithUser
|
||||
*/
|
||||
|
||||
class PodcastController {
|
||||
/**
|
||||
* POST /api/podcasts
|
||||
@ -20,12 +29,12 @@ class PodcastController {
|
||||
*
|
||||
* @this import('../routers/ApiRouter')
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async create(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[PodcastController] Non-admin user "${req.user.username}" attempted to create podcast`)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[PodcastController] Non-admin user "${req.userNew.username}" attempted to create podcast`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
const payload = req.body
|
||||
@ -121,12 +130,12 @@ class PodcastController {
|
||||
* @typedef getPodcastFeedReqBody
|
||||
* @property {string} rssFeed
|
||||
*
|
||||
* @param {import('express').Request<{}, {}, getPodcastFeedReqBody, {}} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {Request<{}, {}, getPodcastFeedReqBody, {}> & RequestUserObjects} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getPodcastFeed(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[PodcastController] Non-admin user "${req.user.username}" attempted to get podcast feed`)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[PodcastController] Non-admin user "${req.userNew.username}" attempted to get podcast feed`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
@ -147,12 +156,12 @@ class PodcastController {
|
||||
*
|
||||
* @this import('../routers/ApiRouter')
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getFeedsFromOPMLText(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[PodcastController] Non-admin user "${req.user.username}" attempted to get feeds from opml`)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[PodcastController] Non-admin user "${req.userNew.username}" attempted to get feeds from opml`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
@ -170,12 +179,12 @@ class PodcastController {
|
||||
*
|
||||
* @this import('../routers/ApiRouter')
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async bulkCreatePodcastsFromOpmlFeedUrls(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[PodcastController] Non-admin user "${req.user.username}" attempted to bulk create podcasts`)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[PodcastController] Non-admin user "${req.userNew.username}" attempted to bulk create podcasts`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
@ -200,9 +209,17 @@ class PodcastController {
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
/**
|
||||
* GET: /api/podcasts/:id/checknew
|
||||
*
|
||||
* @this import('../routers/ApiRouter')
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async checkNewEpisodes(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[PodcastController] Non-admin user attempted to check/download episodes`, req.user)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[PodcastController] Non-admin user "${req.userNew.username}" attempted to check/download episodes`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
@ -220,15 +237,31 @@ class PodcastController {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* GET: /api/podcasts/:id/clear-queue
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
clearEpisodeDownloadQueue(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[PodcastController] Non-admin user attempting to clear download queue "${req.user.username}"`)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[PodcastController] Non-admin user "${req.userNew.username}" attempting to clear download queue`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
this.podcastManager.clearDownloadQueue(req.params.id)
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
/**
|
||||
* GET: /api/podcasts/:id/downloads
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
getEpisodeDownloads(req, res) {
|
||||
var libraryItem = req.libraryItem
|
||||
|
||||
@ -255,9 +288,17 @@ class PodcastController {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* POST: /api/podcasts/:id/download-episodes
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async downloadEpisodes(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[PodcastController] Non-admin user attempted to download episodes`, req.user)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[PodcastController] Non-admin user "${req.userNew.username}" attempted to download episodes`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
const libraryItem = req.libraryItem
|
||||
@ -270,10 +311,17 @@ class PodcastController {
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
// POST: api/podcasts/:id/match-episodes
|
||||
/**
|
||||
* POST: /api/podcasts/:id/match-episodes
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async quickMatchEpisodes(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[PodcastController] Non-admin user attempted to download episodes`, req.user)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[PodcastController] Non-admin user "${req.userNew.username}" attempted to download episodes`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
@ -289,6 +337,12 @@ class PodcastController {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH: /api/podcasts/:id/episode/:episodeId
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async updateEpisode(req, res) {
|
||||
const libraryItem = req.libraryItem
|
||||
|
||||
@ -305,7 +359,12 @@ class PodcastController {
|
||||
res.json(libraryItem.toJSONExpanded())
|
||||
}
|
||||
|
||||
// GET: api/podcasts/:id/episode/:episodeId
|
||||
/**
|
||||
* GET: /api/podcasts/:id/episode/:episodeId
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getEpisode(req, res) {
|
||||
const episodeId = req.params.episodeId
|
||||
const libraryItem = req.libraryItem
|
||||
@ -319,7 +378,12 @@ class PodcastController {
|
||||
res.json(episode)
|
||||
}
|
||||
|
||||
// DELETE: api/podcasts/:id/episode/:episodeId
|
||||
/**
|
||||
* DELETE: /api/podcasts/:id/episode/:episodeId
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async removeEpisode(req, res) {
|
||||
const episodeId = req.params.episodeId
|
||||
const libraryItem = req.libraryItem
|
||||
@ -390,6 +454,12 @@ class PodcastController {
|
||||
res.json(libraryItem.toJSON())
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async middleware(req, res, next) {
|
||||
const item = await Database.libraryItemModel.getOldById(req.params.id)
|
||||
if (!item?.media) return res.sendStatus(404)
|
||||
@ -399,15 +469,15 @@ class PodcastController {
|
||||
}
|
||||
|
||||
// Check user can access this library item
|
||||
if (!req.user.checkCanAccessLibraryItem(item)) {
|
||||
if (!req.userNew.checkCanAccessLibraryItem(item)) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
if (req.method == 'DELETE' && !req.user.canDelete) {
|
||||
Logger.warn(`[PodcastController] User attempted to delete without permission`, req.user.username)
|
||||
if (req.method == 'DELETE' && !req.userNew.canDelete) {
|
||||
Logger.warn(`[PodcastController] User "${req.userNew.username}" attempted to delete without permission`)
|
||||
return res.sendStatus(403)
|
||||
} else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) {
|
||||
Logger.warn('[PodcastController] User attempted to update without permission', req.user.username)
|
||||
} else if ((req.method == 'PATCH' || req.method == 'POST') && !req.userNew.canUpdate) {
|
||||
Logger.warn(`[PodcastController] User "${req.userNew.username}" attempted to update without permission`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,43 @@
|
||||
const { Request, Response, NextFunction } = require('express')
|
||||
const Logger = require('../Logger')
|
||||
const Database = require('../Database')
|
||||
const libraryItemsBookFilters = require('../utils/queries/libraryItemsBookFilters')
|
||||
|
||||
class RSSFeedController {
|
||||
constructor() { }
|
||||
/**
|
||||
* @typedef RequestUserObjects
|
||||
* @property {import('../models/User')} userNew
|
||||
* @property {import('../objects/user/User')} user
|
||||
*
|
||||
* @typedef {Request & RequestUserObjects} RequestWithUser
|
||||
*/
|
||||
|
||||
class RSSFeedController {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* GET: /api/feeds
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getAll(req, res) {
|
||||
const feeds = await this.rssFeedManager.getFeeds()
|
||||
res.json({
|
||||
feeds: feeds.map(f => f.toJSON()),
|
||||
minified: feeds.map(f => f.toJSONMinified())
|
||||
feeds: feeds.map((f) => f.toJSON()),
|
||||
minified: feeds.map((f) => f.toJSONMinified())
|
||||
})
|
||||
}
|
||||
|
||||
// POST: api/feeds/item/:itemId/open
|
||||
/**
|
||||
* POST: /api/feeds/item/:itemId/open
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async openRSSFeedForItem(req, res) {
|
||||
const options = req.body || {}
|
||||
|
||||
@ -21,8 +45,8 @@ class RSSFeedController {
|
||||
if (!item) return res.sendStatus(404)
|
||||
|
||||
// Check user can access this library item
|
||||
if (!req.user.checkCanAccessLibraryItem(item)) {
|
||||
Logger.error(`[RSSFeedController] User "${req.user.username}" attempted to open an RSS feed for item "${item.media.metadata.title}" that they don\'t have access to`)
|
||||
if (!req.userNew.checkCanAccessLibraryItem(item)) {
|
||||
Logger.error(`[RSSFeedController] User "${req.userNew.username}" attempted to open an RSS feed for item "${item.media.metadata.title}" that they don\'t have access to`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
@ -44,13 +68,20 @@ class RSSFeedController {
|
||||
return res.status(400).send('Slug already in use')
|
||||
}
|
||||
|
||||
const feed = await this.rssFeedManager.openFeedForItem(req.user, item, req.body)
|
||||
const feed = await this.rssFeedManager.openFeedForItem(req.userNew.id, item, req.body)
|
||||
res.json({
|
||||
feed: feed.toJSONMinified()
|
||||
})
|
||||
}
|
||||
|
||||
// POST: api/feeds/collection/:collectionId/open
|
||||
/**
|
||||
* POST: /api/feeds/collection/:collectionId/open
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async openRSSFeedForCollection(req, res) {
|
||||
const options = req.body || {}
|
||||
|
||||
@ -70,7 +101,7 @@ class RSSFeedController {
|
||||
}
|
||||
|
||||
const collectionExpanded = await collection.getOldJsonExpanded()
|
||||
const collectionItemsWithTracks = collectionExpanded.books.filter(li => li.media.tracks.length)
|
||||
const collectionItemsWithTracks = collectionExpanded.books.filter((li) => li.media.tracks.length)
|
||||
|
||||
// Check collection has audio tracks
|
||||
if (!collectionItemsWithTracks.length) {
|
||||
@ -78,13 +109,20 @@ class RSSFeedController {
|
||||
return res.status(400).send('Collection has no audio tracks')
|
||||
}
|
||||
|
||||
const feed = await this.rssFeedManager.openFeedForCollection(req.user, collectionExpanded, req.body)
|
||||
const feed = await this.rssFeedManager.openFeedForCollection(req.userNew.id, collectionExpanded, req.body)
|
||||
res.json({
|
||||
feed: feed.toJSONMinified()
|
||||
})
|
||||
}
|
||||
|
||||
// POST: api/feeds/series/:seriesId/open
|
||||
/**
|
||||
* POST: /api/feeds/series/:seriesId/open
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async openRSSFeedForSeries(req, res) {
|
||||
const options = req.body || {}
|
||||
|
||||
@ -106,7 +144,7 @@ class RSSFeedController {
|
||||
const seriesJson = series.toJSON()
|
||||
|
||||
// Get books in series that have audio tracks
|
||||
seriesJson.books = (await libraryItemsBookFilters.getLibraryItemsForSeries(series)).filter(li => li.media.numTracks)
|
||||
seriesJson.books = (await libraryItemsBookFilters.getLibraryItemsForSeries(series)).filter((li) => li.media.numTracks)
|
||||
|
||||
// Check series has audio tracks
|
||||
if (!seriesJson.books.length) {
|
||||
@ -114,20 +152,34 @@ class RSSFeedController {
|
||||
return res.status(400).send('Series has no audio tracks')
|
||||
}
|
||||
|
||||
const feed = await this.rssFeedManager.openFeedForSeries(req.user, seriesJson, req.body)
|
||||
const feed = await this.rssFeedManager.openFeedForSeries(req.userNew.id, seriesJson, req.body)
|
||||
res.json({
|
||||
feed: feed.toJSONMinified()
|
||||
})
|
||||
}
|
||||
|
||||
// POST: api/feeds/:id/close
|
||||
/**
|
||||
* POST: /api/feeds/:id/close
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
closeRSSFeed(req, res) {
|
||||
this.rssFeedManager.closeRssFeed(req, res)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
middleware(req, res, next) {
|
||||
if (!req.user.isAdminOrUp) { // Only admins can manage rss feeds
|
||||
Logger.error(`[RSSFeedController] Non-admin user attempted to make a request to an RSS feed route`, req.user.username)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
// Only admins can manage rss feeds
|
||||
Logger.error(`[RSSFeedController] Non-admin user "${req.userNew.username}" attempted to make a request to an RSS feed route`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
const { Request, Response } = require('express')
|
||||
const Logger = require('../Logger')
|
||||
const BookFinder = require('../finders/BookFinder')
|
||||
const PodcastFinder = require('../finders/PodcastFinder')
|
||||
@ -6,25 +7,51 @@ const MusicFinder = require('../finders/MusicFinder')
|
||||
const Database = require('../Database')
|
||||
const { isValidASIN } = require('../utils')
|
||||
|
||||
/**
|
||||
* @typedef RequestUserObjects
|
||||
* @property {import('../models/User')} userNew
|
||||
* @property {import('../objects/user/User')} user
|
||||
*
|
||||
* @typedef {Request & RequestUserObjects} RequestWithUser
|
||||
*/
|
||||
|
||||
class SearchController {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* GET: /api/search/books
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async findBooks(req, res) {
|
||||
const id = req.query.id
|
||||
const libraryItem = await Database.libraryItemModel.getOldById(id)
|
||||
const provider = req.query.provider || 'google'
|
||||
const title = req.query.title || ''
|
||||
const author = req.query.author || ''
|
||||
|
||||
if (typeof provider !== 'string' || typeof title !== 'string' || typeof author !== 'string') {
|
||||
Logger.error(`[SearchController] findBooks: Invalid request query params`)
|
||||
return res.status(400).send('Invalid request query params')
|
||||
}
|
||||
|
||||
const results = await BookFinder.search(libraryItem, provider, title, author)
|
||||
res.json(results)
|
||||
}
|
||||
|
||||
/**
|
||||
* GET: /api/search/covers
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async findCovers(req, res) {
|
||||
const query = req.query
|
||||
const podcast = query.podcast == 1
|
||||
|
||||
if (!query.title) {
|
||||
Logger.error(`[SearchController] findCovers: No title sent in query`)
|
||||
if (!query.title || typeof query.title !== 'string') {
|
||||
Logger.error(`[SearchController] findCovers: Invalid title sent in query`)
|
||||
return res.sendStatus(400)
|
||||
}
|
||||
|
||||
@ -37,10 +64,11 @@ class SearchController {
|
||||
}
|
||||
|
||||
/**
|
||||
* GET: /api/search/podcasts
|
||||
* Find podcast RSS feeds given a term
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async findPodcasts(req, res) {
|
||||
const term = req.query.term
|
||||
@ -56,12 +84,29 @@ class SearchController {
|
||||
res.json(results)
|
||||
}
|
||||
|
||||
/**
|
||||
* GET: /api/search/authors
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async findAuthor(req, res) {
|
||||
const query = req.query.q
|
||||
if (!query || typeof query !== 'string') {
|
||||
Logger.error(`[SearchController] findAuthor: Invalid query param`)
|
||||
return res.status(400).send('Invalid query param')
|
||||
}
|
||||
|
||||
const author = await AuthorFinder.findAuthorByName(query)
|
||||
res.json(author)
|
||||
}
|
||||
|
||||
/**
|
||||
* GET: /api/search/chapters
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async findChapters(req, res) {
|
||||
const asin = req.query.asin
|
||||
if (!isValidASIN(asin.toUpperCase())) {
|
||||
@ -74,12 +119,5 @@ class SearchController {
|
||||
}
|
||||
res.json(chapterData)
|
||||
}
|
||||
|
||||
async findMusicTrack(req, res) {
|
||||
const tracks = await MusicFinder.searchTrack(req.query || {})
|
||||
res.json({
|
||||
tracks
|
||||
})
|
||||
}
|
||||
}
|
||||
module.exports = new SearchController()
|
||||
|
@ -1,8 +1,17 @@
|
||||
const { Request, Response, NextFunction } = require('express')
|
||||
const Logger = require('../Logger')
|
||||
const SocketAuthority = require('../SocketAuthority')
|
||||
const Database = require('../Database')
|
||||
const libraryItemsBookFilters = require('../utils/queries/libraryItemsBookFilters')
|
||||
|
||||
/**
|
||||
* @typedef RequestUserObjects
|
||||
* @property {import('../models/User')} userNew
|
||||
* @property {import('../objects/user/User')} user
|
||||
*
|
||||
* @typedef {Request & RequestUserObjects} RequestWithUser
|
||||
*/
|
||||
|
||||
class SeriesController {
|
||||
constructor() {}
|
||||
|
||||
@ -13,8 +22,8 @@ class SeriesController {
|
||||
* TODO: Update mobile app to use /api/libraries/:id/series/:seriesId API route instead
|
||||
* Series are not library specific so we need to know what the library id is
|
||||
*
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async findOne(req, res) {
|
||||
const include = (req.query.include || '')
|
||||
@ -28,8 +37,7 @@ class SeriesController {
|
||||
if (include.includes('progress')) {
|
||||
const libraryItemsInSeries = req.libraryItemsInSeries
|
||||
const libraryItemsFinished = libraryItemsInSeries.filter((li) => {
|
||||
const mediaProgress = req.user.getMediaProgress(li.id)
|
||||
return mediaProgress?.isFinished
|
||||
return req.userNew.getMediaProgress(li.media.id)?.isFinished
|
||||
})
|
||||
seriesJson.progress = {
|
||||
libraryItemIds: libraryItemsInSeries.map((li) => li.id),
|
||||
@ -46,6 +54,11 @@ class SeriesController {
|
||||
res.json(seriesJson)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async update(req, res) {
|
||||
const hasUpdated = req.series.update(req.body)
|
||||
if (hasUpdated) {
|
||||
@ -55,6 +68,12 @@ class SeriesController {
|
||||
res.json(req.series.toJSON())
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async middleware(req, res, next) {
|
||||
const series = await Database.seriesModel.getOldById(req.params.id)
|
||||
if (!series) return res.sendStatus(404)
|
||||
@ -64,15 +83,15 @@ class SeriesController {
|
||||
*/
|
||||
const libraryItems = await libraryItemsBookFilters.getLibraryItemsForSeries(series, req.userNew)
|
||||
if (!libraryItems.length) {
|
||||
Logger.warn(`[SeriesController] User attempted to access series "${series.id}" with no accessible books`, req.user)
|
||||
Logger.warn(`[SeriesController] User "${req.userNew.username}" attempted to access series "${series.id}" with no accessible books`)
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
if (req.method == 'DELETE' && !req.user.canDelete) {
|
||||
Logger.warn(`[SeriesController] User attempted to delete without permission`, req.user)
|
||||
if (req.method == 'DELETE' && !req.userNew.canDelete) {
|
||||
Logger.warn(`[SeriesController] User "${req.userNew.username}" attempted to delete without permission`)
|
||||
return res.sendStatus(403)
|
||||
} else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) {
|
||||
Logger.warn('[SeriesController] User attempted to update without permission', req.user)
|
||||
} else if ((req.method == 'PATCH' || req.method == 'POST') && !req.userNew.canUpdate) {
|
||||
Logger.warn(`[SeriesController] User "${req.userNew.username}" attempted to update without permission`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
|
@ -1,26 +1,32 @@
|
||||
const { Request, Response, NextFunction } = require('express')
|
||||
const Logger = require('../Logger')
|
||||
const Database = require('../Database')
|
||||
const { toNumber, isUUID } = require('../utils/index')
|
||||
|
||||
const ShareManager = require('../managers/ShareManager')
|
||||
|
||||
/**
|
||||
* @typedef RequestUserObjects
|
||||
* @property {import('../models/User')} userNew
|
||||
* @property {import('../objects/user/User')} user
|
||||
*
|
||||
* @typedef {Request & RequestUserObjects} RequestWithUser
|
||||
*/
|
||||
|
||||
class SessionController {
|
||||
constructor() {}
|
||||
|
||||
async findOne(req, res) {
|
||||
return res.json(req.playbackSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* GET: /api/sessions
|
||||
*
|
||||
* @this import('../routers/ApiRouter')
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getAllWithUserData(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[SessionController] getAllWithUserData: Non-admin user requested all session data ${req.user.id}/"${req.user.username}"`)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[SessionController] getAllWithUserData: Non-admin user "${req.userNew.username}" requested all session data`)
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
// Validate "user" query
|
||||
@ -105,9 +111,17 @@ class SessionController {
|
||||
res.json(payload)
|
||||
}
|
||||
|
||||
/**
|
||||
* GET: /api/sessions/open
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getOpenSessions(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[SessionController] getOpenSessions: Non-admin user requested open session data ${req.user.id}/"${req.user.username}"`)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[SessionController] getOpenSessions: Non-admin user "${req.userNew.username}" requested open session data`)
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
@ -127,25 +141,54 @@ class SessionController {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* GET: /api/session/:id
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getOpenSession(req, res) {
|
||||
const libraryItem = await Database.libraryItemModel.getOldById(req.playbackSession.libraryItemId)
|
||||
const sessionForClient = req.playbackSession.toJSONForClient(libraryItem)
|
||||
res.json(sessionForClient)
|
||||
}
|
||||
|
||||
// POST: api/session/:id/sync
|
||||
/**
|
||||
* POST: /api/session/:id/sync
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
sync(req, res) {
|
||||
this.playbackSessionManager.syncSessionRequest(req.user, req.playbackSession, req.body, res)
|
||||
this.playbackSessionManager.syncSessionRequest(req.userNew, req.playbackSession, req.body, res)
|
||||
}
|
||||
|
||||
// POST: api/session/:id/close
|
||||
/**
|
||||
* POST: /api/session/:id/close
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
close(req, res) {
|
||||
let syncData = req.body
|
||||
if (syncData && !Object.keys(syncData).length) syncData = null
|
||||
this.playbackSessionManager.closeSessionRequest(req.user, req.playbackSession, syncData, res)
|
||||
this.playbackSessionManager.closeSessionRequest(req.userNew, req.playbackSession, syncData, res)
|
||||
}
|
||||
|
||||
// DELETE: api/session/:id
|
||||
/**
|
||||
* DELETE: /api/session/:id
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async delete(req, res) {
|
||||
// if session is open then remove it
|
||||
const openSession = this.playbackSessionManager.getSession(req.playbackSession.id)
|
||||
@ -164,12 +207,12 @@ class SessionController {
|
||||
* @typedef batchDeleteReqBody
|
||||
* @property {string[]} sessions
|
||||
*
|
||||
* @param {import('express').Request<{}, {}, batchDeleteReqBody, {}>} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {Request<{}, {}, batchDeleteReqBody, {}> & RequestUserObjects} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async batchDelete(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[SessionController] Non-admin user attempted to batch delete sessions "${req.user.username}"`)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[SessionController] Non-admin user "${req.userNew.username}" attempted to batch delete sessions`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
// Validate session ids
|
||||
@ -192,7 +235,7 @@ class SessionController {
|
||||
id: req.body.sessions
|
||||
}
|
||||
})
|
||||
Logger.info(`[SessionController] ${sessionsRemoved} playback sessions removed by "${req.user.username}"`)
|
||||
Logger.info(`[SessionController] ${sessionsRemoved} playback sessions removed by "${req.userNew.username}"`)
|
||||
res.sendStatus(200)
|
||||
} catch (error) {
|
||||
Logger.error(`[SessionController] Failed to remove playback sessions`, error)
|
||||
@ -200,22 +243,42 @@ class SessionController {
|
||||
}
|
||||
}
|
||||
|
||||
// POST: api/session/local
|
||||
/**
|
||||
* POST: /api/session/local
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
syncLocal(req, res) {
|
||||
this.playbackSessionManager.syncLocalSessionRequest(req, res)
|
||||
}
|
||||
|
||||
// POST: api/session/local-all
|
||||
/**
|
||||
* POST: /api/session/local-all
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
syncLocalSessions(req, res) {
|
||||
this.playbackSessionManager.syncLocalSessionsRequest(req, res)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
openSessionMiddleware(req, res, next) {
|
||||
var playbackSession = this.playbackSessionManager.getSession(req.params.id)
|
||||
if (!playbackSession) return res.sendStatus(404)
|
||||
|
||||
if (playbackSession.userId !== req.user.id) {
|
||||
Logger.error(`[SessionController] User "${req.user.username}" attempting to access session belonging to another user "${req.params.id}"`)
|
||||
if (playbackSession.userId !== req.userNew.id) {
|
||||
Logger.error(`[SessionController] User "${req.userNew.username}" attempting to access session belonging to another user "${req.params.id}"`)
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
@ -223,6 +286,12 @@ class SessionController {
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async middleware(req, res, next) {
|
||||
const playbackSession = await Database.getPlaybackSession(req.params.id)
|
||||
if (!playbackSession) {
|
||||
@ -230,11 +299,11 @@ class SessionController {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
if (req.method == 'DELETE' && !req.user.canDelete) {
|
||||
Logger.warn(`[SessionController] User attempted to delete without permission`, req.user)
|
||||
if (req.method == 'DELETE' && !req.userNew.canDelete) {
|
||||
Logger.warn(`[SessionController] User "${req.userNew.username}" attempted to delete without permission`)
|
||||
return res.sendStatus(403)
|
||||
} else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) {
|
||||
Logger.warn('[SessionController] User attempted to update without permission', req.user.username)
|
||||
} else if ((req.method == 'PATCH' || req.method == 'POST') && !req.userNew.canUpdate) {
|
||||
Logger.warn(`[SessionController] User "${req.userNew.username}" attempted to update without permission`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
const { Request, Response } = require('express')
|
||||
const uuid = require('uuid')
|
||||
const Path = require('path')
|
||||
const { Op } = require('sequelize')
|
||||
@ -10,6 +11,14 @@ const { getAudioMimeTypeFromExtname, encodeUriPath } = require('../utils/fileUti
|
||||
const PlaybackSession = require('../objects/PlaybackSession')
|
||||
const ShareManager = require('../managers/ShareManager')
|
||||
|
||||
/**
|
||||
* @typedef RequestUserObjects
|
||||
* @property {import('../models/User')} userNew
|
||||
* @property {import('../objects/user/User')} user
|
||||
*
|
||||
* @typedef {Request & RequestUserObjects} RequestWithUser
|
||||
*/
|
||||
|
||||
class ShareController {
|
||||
constructor() {}
|
||||
|
||||
@ -20,8 +29,8 @@ class ShareController {
|
||||
*
|
||||
* @this {import('../routers/PublicRouter')}
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getMediaItemShareBySlug(req, res) {
|
||||
const { slug } = req.params
|
||||
@ -122,8 +131,8 @@ class ShareController {
|
||||
* GET: /api/share/:slug/cover
|
||||
* Get media item share cover image
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getMediaItemShareCoverImage(req, res) {
|
||||
if (!req.cookies.share_session_id) {
|
||||
@ -162,8 +171,8 @@ class ShareController {
|
||||
* GET: /api/share/:slug/track/:index
|
||||
* Get media item share audio track
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getMediaItemShareAudioTrack(req, res) {
|
||||
if (!req.cookies.share_session_id) {
|
||||
@ -208,8 +217,8 @@ class ShareController {
|
||||
* PATCH: /api/share/:slug/progress
|
||||
* Update media item share progress
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async updateMediaItemShareProgress(req, res) {
|
||||
if (!req.cookies.share_session_id) {
|
||||
@ -242,12 +251,12 @@ class ShareController {
|
||||
* POST: /api/share/mediaitem
|
||||
* Create a new media item share
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async createMediaItemShare(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[ShareController] Non-admin user "${req.user.username}" attempted to create item share`)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[ShareController] Non-admin user "${req.userNew.username}" attempted to create item share`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
@ -290,7 +299,7 @@ class ShareController {
|
||||
expiresAt: expiresAt || null,
|
||||
mediaItemId,
|
||||
mediaItemType,
|
||||
userId: req.user.id
|
||||
userId: req.userNew.id
|
||||
})
|
||||
|
||||
ShareManager.openMediaItemShare(mediaItemShare)
|
||||
@ -306,12 +315,12 @@ class ShareController {
|
||||
* DELETE: /api/share/mediaitem/:id
|
||||
* Delete media item share
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async deleteMediaItemShare(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[ShareController] Non-admin user "${req.user.username}" attempted to delete item share`)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[ShareController] Non-admin user "${req.userNew.username}" attempted to delete item share`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,15 @@
|
||||
const { Request, Response, NextFunction } = require('express')
|
||||
const Logger = require('../Logger')
|
||||
const Database = require('../Database')
|
||||
|
||||
/**
|
||||
* @typedef RequestUserObjects
|
||||
* @property {import('../models/User')} userNew
|
||||
* @property {import('../objects/user/User')} user
|
||||
*
|
||||
* @typedef {Request & RequestUserObjects} RequestWithUser
|
||||
*/
|
||||
|
||||
class ToolsController {
|
||||
constructor() {}
|
||||
|
||||
@ -10,8 +19,8 @@ class ToolsController {
|
||||
*
|
||||
* @this import('../routers/ApiRouter')
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async encodeM4b(req, res) {
|
||||
if (req.libraryItem.isMissing || req.libraryItem.isInvalid) {
|
||||
@ -30,7 +39,7 @@ class ToolsController {
|
||||
}
|
||||
|
||||
const options = req.query || {}
|
||||
this.abMergeManager.startAudiobookMerge(req.user, req.libraryItem, options)
|
||||
this.abMergeManager.startAudiobookMerge(req.userNew.id, req.libraryItem, options)
|
||||
|
||||
res.sendStatus(200)
|
||||
}
|
||||
@ -41,8 +50,8 @@ class ToolsController {
|
||||
*
|
||||
* @this import('../routers/ApiRouter')
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async cancelM4bEncode(req, res) {
|
||||
const workerTask = this.abMergeManager.getPendingTaskByLibraryItemId(req.params.id)
|
||||
@ -59,8 +68,8 @@ class ToolsController {
|
||||
*
|
||||
* @this import('../routers/ApiRouter')
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async embedAudioFileMetadata(req, res) {
|
||||
if (req.libraryItem.isMissing || !req.libraryItem.hasAudioFiles || !req.libraryItem.isBook) {
|
||||
@ -77,7 +86,7 @@ class ToolsController {
|
||||
forceEmbedChapters: req.query.forceEmbedChapters === '1',
|
||||
backup: req.query.backup === '1'
|
||||
}
|
||||
this.audioMetadataManager.updateMetadataForItem(req.user, req.libraryItem, options)
|
||||
this.audioMetadataManager.updateMetadataForItem(req.userNew.id, req.libraryItem, options)
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
@ -87,8 +96,8 @@ class ToolsController {
|
||||
*
|
||||
* @this import('../routers/ApiRouter')
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async batchEmbedMetadata(req, res) {
|
||||
const libraryItemIds = req.body.libraryItemIds || []
|
||||
@ -105,8 +114,8 @@ class ToolsController {
|
||||
}
|
||||
|
||||
// Check user can access this library item
|
||||
if (!req.user.checkCanAccessLibraryItem(libraryItem)) {
|
||||
Logger.error(`[ToolsController] Batch embed metadata library item (${libraryItemId}) not accessible to user`, req.user)
|
||||
if (!req.userNew.checkCanAccessLibraryItem(libraryItem)) {
|
||||
Logger.error(`[ToolsController] Batch embed metadata library item (${libraryItemId}) not accessible to user "${req.userNew.username}"`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
@ -127,19 +136,19 @@ class ToolsController {
|
||||
forceEmbedChapters: req.query.forceEmbedChapters === '1',
|
||||
backup: req.query.backup === '1'
|
||||
}
|
||||
this.audioMetadataManager.handleBatchEmbed(req.user, libraryItems, options)
|
||||
this.audioMetadataManager.handleBatchEmbed(req.userNew.id, libraryItems, options)
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {import('express').NextFunction} next
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async middleware(req, res, next) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[LibraryItemController] Non-root user attempted to access tools route`, req.user)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`[LibraryItemController] Non-root user "${req.userNew.username}" attempted to access tools route`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
@ -148,7 +157,7 @@ class ToolsController {
|
||||
if (!item?.media) return res.sendStatus(404)
|
||||
|
||||
// Check user can access this library item
|
||||
if (!req.user.checkCanAccessLibraryItem(item)) {
|
||||
if (!req.userNew.checkCanAccessLibraryItem(item)) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
const { Request, Response, NextFunction } = require('express')
|
||||
const uuidv4 = require('uuid').v4
|
||||
const Logger = require('../Logger')
|
||||
const SocketAuthority = require('../SocketAuthority')
|
||||
@ -8,12 +9,18 @@ const User = require('../objects/user/User')
|
||||
const { toNumber } = require('../utils/index')
|
||||
|
||||
/**
|
||||
* @typedef RequestUserObjects
|
||||
* @property {import('../models/User')} userNew
|
||||
* @property {import('../objects/user/User')} user
|
||||
*
|
||||
* @typedef {Request & RequestUserObjects} RequestWithUser
|
||||
*
|
||||
* @typedef UserControllerRequestProps
|
||||
* @property {import('../models/User')} userNew
|
||||
* @property {import('../objects/user/User')} user - User that made the request
|
||||
* @property {import('../objects/user/User')} [reqUser] - User for req param id
|
||||
*
|
||||
* @typedef {import('express').Request & UserControllerRequestProps} UserControllerRequest
|
||||
* @typedef {import('express').Response} UserControllerResponse
|
||||
* @typedef {Request & UserControllerRequestProps} UserControllerRequest
|
||||
*/
|
||||
|
||||
class UserController {
|
||||
@ -22,11 +29,11 @@ class UserController {
|
||||
/**
|
||||
*
|
||||
* @param {UserControllerRequest} req
|
||||
* @param {UserControllerResponse} res
|
||||
* @param {Response} res
|
||||
*/
|
||||
async findAll(req, res) {
|
||||
if (!req.user.isAdminOrUp) return res.sendStatus(403)
|
||||
const hideRootToken = !req.user.isRoot
|
||||
if (!req.userNew.isAdminOrUp) return res.sendStatus(403)
|
||||
const hideRootToken = !req.userNew.isRoot
|
||||
|
||||
const includes = (req.query.include || '').split(',').map((i) => i.trim())
|
||||
|
||||
@ -52,11 +59,11 @@ class UserController {
|
||||
* Media progress items include: `displayTitle`, `displaySubtitle` (for podcasts), `coverPath` and `mediaUpdatedAt`
|
||||
*
|
||||
* @param {UserControllerRequest} req
|
||||
* @param {UserControllerResponse} res
|
||||
* @param {Response} res
|
||||
*/
|
||||
async findOne(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error('User other than admin attempting to get user', req.user)
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
Logger.error(`Non-admin user "${req.userNew.username}" attempted to get user`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
@ -95,13 +102,22 @@ class UserController {
|
||||
return oldMediaProgress
|
||||
})
|
||||
|
||||
const userJson = req.reqUser.toJSONForBrowser(!req.user.isRoot)
|
||||
const userJson = req.reqUser.toJSONForBrowser(!req.userNew.isRoot)
|
||||
|
||||
userJson.mediaProgress = oldMediaProgresses
|
||||
|
||||
res.json(userJson)
|
||||
}
|
||||
|
||||
/**
|
||||
* POST: /api/users
|
||||
* Create a new user
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async create(req, res) {
|
||||
const account = req.body
|
||||
const username = account.username
|
||||
@ -134,13 +150,13 @@ class UserController {
|
||||
* Update user
|
||||
*
|
||||
* @param {UserControllerRequest} req
|
||||
* @param {UserControllerResponse} res
|
||||
* @param {Response} res
|
||||
*/
|
||||
async update(req, res) {
|
||||
const user = req.reqUser
|
||||
|
||||
if (user.type === 'root' && !req.user.isRoot) {
|
||||
Logger.error(`[UserController] Admin user attempted to update root user`, req.user.username)
|
||||
if (user.type === 'root' && !req.userNew.isRoot) {
|
||||
Logger.error(`[UserController] Admin user "${req.userNew.username}" attempted to update root user`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
@ -168,7 +184,7 @@ class UserController {
|
||||
Logger.info(`[UserController] User ${user.username} was generated a new api token`)
|
||||
}
|
||||
await Database.updateUser(user)
|
||||
SocketAuthority.clientEmitter(req.user.id, 'user_updated', user.toJSONForBrowser())
|
||||
SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', user.toJSONForBrowser())
|
||||
}
|
||||
|
||||
res.json({
|
||||
@ -177,14 +193,21 @@ class UserController {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE: /api/users/:id
|
||||
* Delete a user
|
||||
*
|
||||
* @param {UserControllerRequest} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async delete(req, res) {
|
||||
if (req.params.id === 'root') {
|
||||
Logger.error('[UserController] Attempt to delete root user. Root user cannot be deleted')
|
||||
return res.sendStatus(500)
|
||||
return res.sendStatus(400)
|
||||
}
|
||||
if (req.user.id === req.params.id) {
|
||||
Logger.error(`[UserController] ${req.user.username} is attempting to delete themselves... why? WHY?`)
|
||||
return res.sendStatus(500)
|
||||
if (req.userNew.id === req.params.id) {
|
||||
Logger.error(`[UserController] User ${req.userNew.username} is attempting to delete self`)
|
||||
return res.sendStatus(400)
|
||||
}
|
||||
const user = req.reqUser
|
||||
|
||||
@ -212,20 +235,25 @@ class UserController {
|
||||
* PATCH: /api/users/:id/openid-unlink
|
||||
*
|
||||
* @param {UserControllerRequest} req
|
||||
* @param {UserControllerResponse} res
|
||||
* @param {Response} res
|
||||
*/
|
||||
async unlinkFromOpenID(req, res) {
|
||||
Logger.debug(`[UserController] Unlinking user "${req.reqUser.username}" from OpenID with sub "${req.reqUser.authOpenIDSub}"`)
|
||||
req.reqUser.authOpenIDSub = null
|
||||
if (await Database.userModel.updateFromOld(req.reqUser)) {
|
||||
SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.reqUser.toJSONForBrowser())
|
||||
SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', req.reqUser.toJSONForBrowser())
|
||||
res.sendStatus(200)
|
||||
} else {
|
||||
res.sendStatus(500)
|
||||
}
|
||||
}
|
||||
|
||||
// GET: api/users/:id/listening-sessions
|
||||
/**
|
||||
* GET: /api/users/:id/listening-sessions
|
||||
*
|
||||
* @param {UserControllerRequest} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getListeningSessions(req, res) {
|
||||
var listeningSessions = await this.getUserListeningSessionsHelper(req.params.id)
|
||||
|
||||
@ -246,15 +274,29 @@ class UserController {
|
||||
res.json(payload)
|
||||
}
|
||||
|
||||
// GET: api/users/:id/listening-stats
|
||||
/**
|
||||
* GET: /api/users/:id/listening-stats
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {UserControllerRequest} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getListeningStats(req, res) {
|
||||
var listeningStats = await this.getUserListeningStatsHelpers(req.params.id)
|
||||
res.json(listeningStats)
|
||||
}
|
||||
|
||||
// POST: api/users/online (admin)
|
||||
/**
|
||||
* GET: /api/users/online
|
||||
*
|
||||
* @this {import('../routers/ApiRouter')}
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getOnlineUsers(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
if (!req.userNew.isAdminOrUp) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
@ -264,10 +306,16 @@ class UserController {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async middleware(req, res, next) {
|
||||
if (!req.user.isAdminOrUp && req.user.id !== req.params.id) {
|
||||
if (!req.userNew.isAdminOrUp && req.userNew.id !== req.params.id) {
|
||||
return res.sendStatus(403)
|
||||
} else if ((req.method == 'PATCH' || req.method == 'POST' || req.method == 'DELETE') && !req.user.isAdminOrUp) {
|
||||
} else if ((req.method == 'PATCH' || req.method == 'POST' || req.method == 'DELETE') && !req.userNew.isAdminOrUp) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
|
@ -46,11 +46,11 @@ class AbMergeManager {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('../objects/user/User')} user
|
||||
* @param {string} userId
|
||||
* @param {import('../objects/LibraryItem')} libraryItem
|
||||
* @param {AbMergeEncodeOptions} [options={}]
|
||||
*/
|
||||
async startAudiobookMerge(user, libraryItem, options = {}) {
|
||||
async startAudiobookMerge(userId, libraryItem, options = {}) {
|
||||
const task = new Task()
|
||||
|
||||
const audiobookDirname = Path.basename(libraryItem.path)
|
||||
@ -61,7 +61,7 @@ class AbMergeManager {
|
||||
const taskData = {
|
||||
libraryItemId: libraryItem.id,
|
||||
libraryItemPath: libraryItem.path,
|
||||
userId: user.id,
|
||||
userId,
|
||||
originalTrackPaths: libraryItem.media.tracks.map((t) => t.metadata.path),
|
||||
inos: libraryItem.media.includedAudioFiles.map((f) => f.ino),
|
||||
tempFilepath,
|
||||
|
@ -42,7 +42,7 @@ class ApiCacheManager {
|
||||
Logger.debug(`[ApiCacheManager] Skipping cache for random sort`)
|
||||
return next()
|
||||
}
|
||||
const key = { user: req.user.username, url: req.url }
|
||||
const key = { user: req.userNew.username, url: req.url }
|
||||
const stringifiedKey = JSON.stringify(key)
|
||||
Logger.debug(`[ApiCacheManager] count: ${this.cache.size} size: ${this.cache.calculatedSize}`)
|
||||
const cached = this.cache.get(stringifiedKey)
|
||||
|
@ -32,13 +32,25 @@ class AudioMetadataMangaer {
|
||||
return ffmpegHelpers.getFFMetadataObject(libraryItem, libraryItem.media.includedAudioFiles.length)
|
||||
}
|
||||
|
||||
handleBatchEmbed(user, libraryItems, options = {}) {
|
||||
/**
|
||||
*
|
||||
* @param {string} userId
|
||||
* @param {*} libraryItems
|
||||
* @param {*} options
|
||||
*/
|
||||
handleBatchEmbed(userId, libraryItems, options = {}) {
|
||||
libraryItems.forEach((li) => {
|
||||
this.updateMetadataForItem(user, li, options)
|
||||
this.updateMetadataForItem(userId, li, options)
|
||||
})
|
||||
}
|
||||
|
||||
async updateMetadataForItem(user, libraryItem, options = {}) {
|
||||
/**
|
||||
*
|
||||
* @param {string} userId
|
||||
* @param {*} libraryItem
|
||||
* @param {*} options
|
||||
*/
|
||||
async updateMetadataForItem(userId, libraryItem, options = {}) {
|
||||
const forceEmbedChapters = !!options.forceEmbedChapters
|
||||
const backupFiles = !!options.backup
|
||||
|
||||
@ -58,7 +70,7 @@ class AudioMetadataMangaer {
|
||||
const taskData = {
|
||||
libraryItemId: libraryItem.id,
|
||||
libraryItemPath: libraryItem.path,
|
||||
userId: user.id,
|
||||
userId,
|
||||
audioFiles: audioFiles.map((af) => ({
|
||||
index: af.index,
|
||||
ino: af.ino,
|
||||
|
@ -39,7 +39,7 @@ class PlaybackSessionManager {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('../controllers/SessionController').RequestWithUser} req
|
||||
* @param {Object} [clientDeviceInfo]
|
||||
* @returns {Promise<DeviceInfo>}
|
||||
*/
|
||||
@ -48,7 +48,7 @@ class PlaybackSessionManager {
|
||||
const ip = requestIp.getClientIp(req)
|
||||
|
||||
const deviceInfo = new DeviceInfo()
|
||||
deviceInfo.setData(ip, ua, clientDeviceInfo, serverVersion, req.user?.id)
|
||||
deviceInfo.setData(ip, ua, clientDeviceInfo, serverVersion, req.userNew?.id)
|
||||
|
||||
if (clientDeviceInfo?.deviceId) {
|
||||
const existingDevice = await Database.getDeviceByDeviceId(clientDeviceInfo.deviceId)
|
||||
@ -67,18 +67,25 @@ class PlaybackSessionManager {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('../controllers/SessionController').RequestWithUser} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {string} [episodeId]
|
||||
*/
|
||||
async startSessionRequest(req, res, episodeId) {
|
||||
const deviceInfo = await this.getDeviceInfo(req, req.body?.deviceInfo)
|
||||
Logger.debug(`[PlaybackSessionManager] startSessionRequest for device ${deviceInfo.deviceDescription}`)
|
||||
const { user, libraryItem, body: options } = req
|
||||
const session = await this.startSession(user, deviceInfo, libraryItem, episodeId, options)
|
||||
const { libraryItem, body: options } = req
|
||||
const session = await this.startSession(req.userNew, deviceInfo, libraryItem, episodeId, options)
|
||||
res.json(session.toJSONForClient(libraryItem))
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('../models/User')} user
|
||||
* @param {*} session
|
||||
* @param {*} payload
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async syncSessionRequest(user, session, payload, res) {
|
||||
if (await this.syncSession(user, session, payload)) {
|
||||
res.sendStatus(200)
|
||||
@ -89,7 +96,7 @@ class PlaybackSessionManager {
|
||||
|
||||
async syncLocalSessionsRequest(req, res) {
|
||||
const deviceInfo = await this.getDeviceInfo(req, req.body?.deviceInfo)
|
||||
const user = req.user
|
||||
const user = req.userNew
|
||||
const sessions = req.body.sessions || []
|
||||
|
||||
const syncResults = []
|
||||
@ -104,6 +111,13 @@ class PlaybackSessionManager {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('../models/User')} user
|
||||
* @param {*} sessionJson
|
||||
* @param {*} deviceInfo
|
||||
* @returns
|
||||
*/
|
||||
async syncLocalSession(user, sessionJson, deviceInfo) {
|
||||
const libraryItem = await Database.libraryItemModel.getOldById(sessionJson.libraryItemId)
|
||||
const episode = sessionJson.episodeId && libraryItem && libraryItem.isPodcast ? libraryItem.media.getEpisode(sessionJson.episodeId) : null
|
||||
@ -174,41 +188,58 @@ class PlaybackSessionManager {
|
||||
progressSynced: false
|
||||
}
|
||||
|
||||
const userProgressForItem = user.getMediaProgress(session.libraryItemId, session.episodeId)
|
||||
const mediaItemId = session.episodeId || libraryItem.media.id
|
||||
let userProgressForItem = user.getMediaProgress(mediaItemId)
|
||||
if (userProgressForItem) {
|
||||
if (userProgressForItem.lastUpdate > session.updatedAt) {
|
||||
if (userProgressForItem.updatedAt.valueOf() > session.updatedAt) {
|
||||
Logger.debug(`[PlaybackSessionManager] Not updating progress for "${session.displayTitle}" because it has been updated more recently`)
|
||||
} else {
|
||||
Logger.debug(`[PlaybackSessionManager] Updating progress for "${session.displayTitle}" with current time ${session.currentTime} (previously ${userProgressForItem.currentTime})`)
|
||||
result.progressSynced = user.createUpdateMediaProgress(libraryItem, session.mediaProgressObject, session.episodeId)
|
||||
const updateResponse = await user.createUpdateMediaProgressFromPayload({
|
||||
libraryItemId: libraryItem.id,
|
||||
episodeId: session.episodeId,
|
||||
...session.mediaProgressObject
|
||||
})
|
||||
result.progressSynced = !!updateResponse.mediaProgress
|
||||
if (result.progressSynced) {
|
||||
userProgressForItem = updateResponse.mediaProgress
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Logger.debug(`[PlaybackSessionManager] Creating new media progress for media item "${session.displayTitle}"`)
|
||||
result.progressSynced = user.createUpdateMediaProgress(libraryItem, session.mediaProgressObject, session.episodeId)
|
||||
const updateResponse = await user.createUpdateMediaProgressFromPayload({
|
||||
libraryItemId: libraryItem.id,
|
||||
episodeId: session.episodeId,
|
||||
...session.mediaProgressObject
|
||||
})
|
||||
result.progressSynced = !!updateResponse.mediaProgress
|
||||
if (result.progressSynced) {
|
||||
userProgressForItem = updateResponse.mediaProgress
|
||||
}
|
||||
}
|
||||
|
||||
// Update user and emit socket event
|
||||
if (result.progressSynced) {
|
||||
const itemProgress = user.getMediaProgress(session.libraryItemId, session.episodeId)
|
||||
if (itemProgress) {
|
||||
await Database.upsertMediaProgress(itemProgress)
|
||||
SocketAuthority.clientEmitter(user.id, 'user_item_progress_updated', {
|
||||
id: itemProgress.id,
|
||||
sessionId: session.id,
|
||||
deviceDescription: session.deviceDescription,
|
||||
data: itemProgress.toJSON()
|
||||
})
|
||||
}
|
||||
SocketAuthority.clientEmitter(user.id, 'user_item_progress_updated', {
|
||||
id: userProgressForItem.id,
|
||||
sessionId: session.id,
|
||||
deviceDescription: session.deviceDescription,
|
||||
data: userProgressForItem.getOldMediaProgress()
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('../controllers/SessionController').RequestWithUser} req
|
||||
* @param {*} res
|
||||
*/
|
||||
async syncLocalSessionRequest(req, res) {
|
||||
const deviceInfo = await this.getDeviceInfo(req, req.body?.deviceInfo)
|
||||
const user = req.user
|
||||
const sessionJson = req.body
|
||||
const result = await this.syncLocalSession(user, sessionJson, deviceInfo)
|
||||
const result = await this.syncLocalSession(req.userNew, sessionJson, deviceInfo)
|
||||
if (result.error) {
|
||||
res.status(500).send(result.error)
|
||||
} else {
|
||||
@ -216,6 +247,13 @@ class PlaybackSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('../models/User')} user
|
||||
* @param {*} session
|
||||
* @param {*} syncData
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async closeSessionRequest(user, session, syncData, res) {
|
||||
await this.closeSession(user, session, syncData)
|
||||
res.sendStatus(200)
|
||||
@ -223,7 +261,7 @@ class PlaybackSessionManager {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('../objects/user/User')} user
|
||||
* @param {import('../models/User')} user
|
||||
* @param {DeviceInfo} deviceInfo
|
||||
* @param {import('../objects/LibraryItem')} libraryItem
|
||||
* @param {string|null} episodeId
|
||||
@ -241,7 +279,8 @@ class PlaybackSessionManager {
|
||||
const shouldDirectPlay = options.forceDirectPlay || (!options.forceTranscode && libraryItem.media.checkCanDirectPlay(options, episodeId))
|
||||
const mediaPlayer = options.mediaPlayer || 'unknown'
|
||||
|
||||
const userProgress = libraryItem.isMusic ? null : user.getMediaProgress(libraryItem.id, episodeId)
|
||||
const mediaItemId = episodeId || libraryItem.media.id
|
||||
const userProgress = user.getMediaProgress(mediaItemId)
|
||||
let userStartTime = 0
|
||||
if (userProgress) {
|
||||
if (userProgress.isFinished) {
|
||||
@ -292,6 +331,13 @@ class PlaybackSessionManager {
|
||||
return newPlaybackSession
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('../models/User')} user
|
||||
* @param {*} session
|
||||
* @param {*} syncData
|
||||
* @returns
|
||||
*/
|
||||
async syncSession(user, session, syncData) {
|
||||
const libraryItem = await Database.libraryItemModel.getOldById(session.libraryItemId)
|
||||
if (!libraryItem) {
|
||||
@ -303,20 +349,19 @@ class PlaybackSessionManager {
|
||||
session.addListeningTime(syncData.timeListened)
|
||||
Logger.debug(`[PlaybackSessionManager] syncSession "${session.id}" (Device: ${session.deviceDescription}) | Total Time Listened: ${session.timeListening}`)
|
||||
|
||||
const itemProgressUpdate = {
|
||||
const updateResponse = await user.createUpdateMediaProgressFromPayload({
|
||||
libraryItemId: libraryItem.id,
|
||||
episodeId: session.episodeId,
|
||||
duration: syncData.duration,
|
||||
currentTime: syncData.currentTime,
|
||||
progress: session.progress
|
||||
}
|
||||
const wasUpdated = user.createUpdateMediaProgress(libraryItem, itemProgressUpdate, session.episodeId)
|
||||
if (wasUpdated) {
|
||||
const itemProgress = user.getMediaProgress(session.libraryItemId, session.episodeId)
|
||||
if (itemProgress) await Database.upsertMediaProgress(itemProgress)
|
||||
})
|
||||
if (updateResponse.mediaProgress) {
|
||||
SocketAuthority.clientEmitter(user.id, 'user_item_progress_updated', {
|
||||
id: itemProgress.id,
|
||||
id: updateResponse.mediaProgress.id,
|
||||
sessionId: session.id,
|
||||
deviceDescription: session.deviceDescription,
|
||||
data: itemProgress.toJSON()
|
||||
data: updateResponse.mediaProgress.getOldMediaProgress()
|
||||
})
|
||||
}
|
||||
this.saveSession(session)
|
||||
@ -325,6 +370,13 @@ class PlaybackSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('../models/User')} user
|
||||
* @param {*} session
|
||||
* @param {*} syncData
|
||||
* @returns
|
||||
*/
|
||||
async closeSession(user, session, syncData = null) {
|
||||
if (syncData) {
|
||||
await this.syncSession(user, session, syncData)
|
||||
|
@ -9,7 +9,7 @@ const Feed = require('../objects/Feed')
|
||||
const libraryItemsBookFilters = require('../utils/queries/libraryItemsBookFilters')
|
||||
|
||||
class RssFeedManager {
|
||||
constructor() { }
|
||||
constructor() {}
|
||||
|
||||
async validateFeedEntity(feedObj) {
|
||||
if (feedObj.entityType === 'collection') {
|
||||
@ -44,7 +44,7 @@ class RssFeedManager {
|
||||
const feeds = await Database.feedModel.getOldFeeds()
|
||||
for (const feed of feeds) {
|
||||
// Remove invalid feeds
|
||||
if (!await this.validateFeedEntity(feed)) {
|
||||
if (!(await this.validateFeedEntity(feed))) {
|
||||
await Database.removeFeed(feed.id)
|
||||
}
|
||||
}
|
||||
@ -138,7 +138,7 @@ class RssFeedManager {
|
||||
const seriesJson = series.toJSON()
|
||||
|
||||
// Get books in series that have audio tracks
|
||||
seriesJson.books = (await libraryItemsBookFilters.getLibraryItemsForSeries(series)).filter(li => li.media.numTracks)
|
||||
seriesJson.books = (await libraryItemsBookFilters.getLibraryItemsForSeries(series)).filter((li) => li.media.numTracks)
|
||||
|
||||
// Find most recently updated item in series
|
||||
let mostRecentlyUpdatedAt = seriesJson.updatedAt
|
||||
@ -202,7 +202,14 @@ class RssFeedManager {
|
||||
readStream.pipe(res)
|
||||
}
|
||||
|
||||
async openFeedForItem(user, libraryItem, options) {
|
||||
/**
|
||||
*
|
||||
* @param {string} userId
|
||||
* @param {*} libraryItem
|
||||
* @param {*} options
|
||||
* @returns
|
||||
*/
|
||||
async openFeedForItem(userId, libraryItem, options) {
|
||||
const serverAddress = options.serverAddress
|
||||
const slug = options.slug
|
||||
const preventIndexing = options.metadataDetails?.preventIndexing ?? true
|
||||
@ -210,7 +217,7 @@ class RssFeedManager {
|
||||
const ownerEmail = options.metadataDetails?.ownerEmail
|
||||
|
||||
const feed = new Feed()
|
||||
feed.setFromItem(user.id, slug, libraryItem, serverAddress, preventIndexing, ownerName, ownerEmail)
|
||||
feed.setFromItem(userId, slug, libraryItem, serverAddress, preventIndexing, ownerName, ownerEmail)
|
||||
|
||||
Logger.info(`[RssFeedManager] Opened RSS feed "${feed.feedUrl}"`)
|
||||
await Database.createFeed(feed)
|
||||
@ -218,7 +225,14 @@ class RssFeedManager {
|
||||
return feed
|
||||
}
|
||||
|
||||
async openFeedForCollection(user, collectionExpanded, options) {
|
||||
/**
|
||||
*
|
||||
* @param {string} userId
|
||||
* @param {*} collectionExpanded
|
||||
* @param {*} options
|
||||
* @returns
|
||||
*/
|
||||
async openFeedForCollection(userId, collectionExpanded, options) {
|
||||
const serverAddress = options.serverAddress
|
||||
const slug = options.slug
|
||||
const preventIndexing = options.metadataDetails?.preventIndexing ?? true
|
||||
@ -226,7 +240,7 @@ class RssFeedManager {
|
||||
const ownerEmail = options.metadataDetails?.ownerEmail
|
||||
|
||||
const feed = new Feed()
|
||||
feed.setFromCollection(user.id, slug, collectionExpanded, serverAddress, preventIndexing, ownerName, ownerEmail)
|
||||
feed.setFromCollection(userId, slug, collectionExpanded, serverAddress, preventIndexing, ownerName, ownerEmail)
|
||||
|
||||
Logger.info(`[RssFeedManager] Opened RSS feed "${feed.feedUrl}"`)
|
||||
await Database.createFeed(feed)
|
||||
@ -234,7 +248,14 @@ class RssFeedManager {
|
||||
return feed
|
||||
}
|
||||
|
||||
async openFeedForSeries(user, seriesExpanded, options) {
|
||||
/**
|
||||
*
|
||||
* @param {string} userId
|
||||
* @param {*} seriesExpanded
|
||||
* @param {*} options
|
||||
* @returns
|
||||
*/
|
||||
async openFeedForSeries(userId, seriesExpanded, options) {
|
||||
const serverAddress = options.serverAddress
|
||||
const slug = options.slug
|
||||
const preventIndexing = options.metadataDetails?.preventIndexing ?? true
|
||||
@ -242,7 +263,7 @@ class RssFeedManager {
|
||||
const ownerEmail = options.metadataDetails?.ownerEmail
|
||||
|
||||
const feed = new Feed()
|
||||
feed.setFromSeries(user.id, slug, seriesExpanded, serverAddress, preventIndexing, ownerName, ownerEmail)
|
||||
feed.setFromSeries(userId, slug, seriesExpanded, serverAddress, preventIndexing, ownerName, ownerEmail)
|
||||
|
||||
Logger.info(`[RssFeedManager] Opened RSS feed "${feed.feedUrl}"`)
|
||||
await Database.createFeed(feed)
|
||||
|
@ -419,8 +419,11 @@ class User extends Model {
|
||||
)
|
||||
}
|
||||
|
||||
get isRoot() {
|
||||
return this.type === 'root'
|
||||
}
|
||||
get isAdminOrUp() {
|
||||
return this.type === 'root' || this.type === 'admin'
|
||||
return this.isRoot || this.type === 'admin'
|
||||
}
|
||||
get isUser() {
|
||||
return this.type === 'user'
|
||||
|
@ -372,88 +372,5 @@ class User {
|
||||
|
||||
return JSON.stringify(samplePermissions, null, 2) // Pretty print the JSON
|
||||
}
|
||||
|
||||
/**
|
||||
* Get first available library id for user
|
||||
*
|
||||
* @param {string[]} libraryIds
|
||||
* @returns {string|null}
|
||||
*/
|
||||
getDefaultLibraryId(libraryIds) {
|
||||
// Libraries should already be in ascending display order, find first accessible
|
||||
return libraryIds.find((lid) => this.checkCanAccessLibrary(lid)) || null
|
||||
}
|
||||
|
||||
getMediaProgress(libraryItemId, episodeId = null) {
|
||||
if (!this.mediaProgress) return null
|
||||
return this.mediaProgress.find((lip) => {
|
||||
if (episodeId && lip.episodeId !== episodeId) return false
|
||||
return lip.libraryItemId === libraryItemId
|
||||
})
|
||||
}
|
||||
|
||||
getAllMediaProgressForLibraryItem(libraryItemId) {
|
||||
if (!this.mediaProgress) return []
|
||||
return this.mediaProgress.filter((li) => li.libraryItemId === libraryItemId)
|
||||
}
|
||||
|
||||
createUpdateMediaProgress(libraryItem, updatePayload, episodeId = null) {
|
||||
const itemProgress = this.mediaProgress.find((li) => {
|
||||
if (episodeId && li.episodeId !== episodeId) return false
|
||||
return li.libraryItemId === libraryItem.id
|
||||
})
|
||||
if (!itemProgress) {
|
||||
const newItemProgress = new MediaProgress()
|
||||
|
||||
newItemProgress.setData(libraryItem, updatePayload, episodeId, this.id)
|
||||
this.mediaProgress.push(newItemProgress)
|
||||
return true
|
||||
}
|
||||
const wasUpdated = itemProgress.update(updatePayload)
|
||||
|
||||
if (updatePayload.lastUpdate) itemProgress.lastUpdate = updatePayload.lastUpdate // For local to keep update times in sync
|
||||
return wasUpdated
|
||||
}
|
||||
|
||||
checkCanAccessLibrary(libraryId) {
|
||||
if (this.permissions.accessAllLibraries) return true
|
||||
if (!this.librariesAccessible) return false
|
||||
return this.librariesAccessible.includes(libraryId)
|
||||
}
|
||||
|
||||
checkCanAccessLibraryItemWithTags(tags) {
|
||||
if (this.permissions.accessAllTags) return true
|
||||
if (this.permissions.selectedTagsNotAccessible) {
|
||||
if (!tags?.length) return true
|
||||
return tags.every((tag) => !this.itemTagsSelected.includes(tag))
|
||||
}
|
||||
if (!tags?.length) return false
|
||||
return this.itemTagsSelected.some((tag) => tags.includes(tag))
|
||||
}
|
||||
|
||||
checkCanAccessLibraryItem(libraryItem) {
|
||||
if (!this.checkCanAccessLibrary(libraryItem.libraryId)) return false
|
||||
|
||||
if (libraryItem.media.metadata.explicit && !this.canAccessExplicitContent) return false
|
||||
return this.checkCanAccessLibraryItemWithTags(libraryItem.media.tags)
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of podcast episodes not finished for library item
|
||||
* Note: libraryItem passed in from libraryHelpers is not a LibraryItem class instance
|
||||
* @param {LibraryItem|object} libraryItem
|
||||
* @returns {number}
|
||||
*/
|
||||
getNumEpisodesIncompleteForPodcast(libraryItem) {
|
||||
if (!libraryItem?.media.episodes) return 0
|
||||
let numEpisodesIncomplete = 0
|
||||
for (const episode of libraryItem.media.episodes) {
|
||||
const mediaProgress = this.getMediaProgress(libraryItem.id, episode.id)
|
||||
if (!mediaProgress?.isFinished) {
|
||||
numEpisodesIncomplete++
|
||||
}
|
||||
}
|
||||
return numEpisodesIncomplete
|
||||
}
|
||||
}
|
||||
module.exports = User
|
||||
|
@ -39,6 +39,7 @@ class ApiRouter {
|
||||
constructor(Server) {
|
||||
/** @type {import('../Auth')} */
|
||||
this.auth = Server.auth
|
||||
/** @type {import('../managers/PlaybackSessionManager')} */
|
||||
this.playbackSessionManager = Server.playbackSessionManager
|
||||
/** @type {import('../managers/AbMergeManager')} */
|
||||
this.abMergeManager = Server.abMergeManager
|
||||
@ -50,8 +51,10 @@ class ApiRouter {
|
||||
this.podcastManager = Server.podcastManager
|
||||
/** @type {import('../managers/AudioMetadataManager')} */
|
||||
this.audioMetadataManager = Server.audioMetadataManager
|
||||
/** @type {import('../managers/RssFeedManager')} */
|
||||
this.rssFeedManager = Server.rssFeedManager
|
||||
this.cronManager = Server.cronManager
|
||||
/** @type {import('../managers/NotificationManager')} */
|
||||
this.notificationManager = Server.notificationManager
|
||||
this.emailManager = Server.emailManager
|
||||
this.apiCacheManager = Server.apiCacheManager
|
||||
@ -281,7 +284,6 @@ class ApiRouter {
|
||||
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))
|
||||
this.router.get('/search/tracks', SearchController.findMusicTrack.bind(this))
|
||||
|
||||
//
|
||||
// Cache Routes (Admin and up)
|
||||
|
Loading…
x
Reference in New Issue
Block a user