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