Merge branch 'master' into mf/rssInboundManager

# Conflicts:
#	client/pages/config/rss-feeds.vue
#	client/strings/en-us.json
#	server/managers/PodcastManager.js
#	server/models/Podcast.js
This commit is contained in:
mfcar 2023-12-08 23:41:33 -03:00
commit eb8e49e4fc
No known key found for this signature in database
180 changed files with 14188 additions and 5517 deletions

View file

@ -67,30 +67,10 @@ class AuthorController {
const payload = req.body
let hasUpdated = false
// Updating/removing cover image
if (payload.imagePath !== undefined && payload.imagePath !== req.author.imagePath) {
if (!payload.imagePath && req.author.imagePath) { // If removing image then remove file
await CacheManager.purgeImageCache(req.author.id) // Purge cache
await CoverManager.removeFile(req.author.imagePath)
} else if (payload.imagePath.startsWith('http')) { // Check if image path is a url
const imageData = await AuthorFinder.saveAuthorImage(req.author.id, payload.imagePath)
if (imageData) {
if (req.author.imagePath) {
await CacheManager.purgeImageCache(req.author.id) // Purge cache
}
payload.imagePath = imageData.path
hasUpdated = true
}
} else if (payload.imagePath && payload.imagePath !== req.author.imagePath) { // Changing image path locally
if (!await fs.pathExists(payload.imagePath)) { // Make sure image path exists
Logger.error(`[AuthorController] Image path does not exist: "${payload.imagePath}"`)
return res.status(400).send('Author image path does not exist')
}
if (req.author.imagePath) {
await CacheManager.purgeImageCache(req.author.id) // Purge cache
}
}
// author imagePath must be set through other endpoints as of v2.4.5
if (payload.imagePath !== undefined) {
Logger.warn(`[AuthorController] Updating local author imagePath is not supported`)
delete payload.imagePath
}
const authorNameUpdate = payload.name !== undefined && payload.name !== req.author.name
@ -131,7 +111,7 @@ class AuthorController {
Database.removeAuthorFromFilterData(req.author.libraryId, req.author.id)
// Send updated num books for merged author
const numBooks = await Database.libraryItemModel.getForAuthor(existingAuthor).length
const numBooks = (await Database.libraryItemModel.getForAuthor(existingAuthor)).length
SocketAuthority.emitter('author_updated', existingAuthor.toJSONExpanded(numBooks))
res.json({
@ -191,6 +171,75 @@ class AuthorController {
res.sendStatus(200)
}
/**
* POST: /api/authors/:id/image
* Upload author image from web URL
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async uploadImage(req, res) {
if (!req.user.canUpload) {
Logger.warn('User attempted to upload an image without permission', req.user)
return res.sendStatus(403)
}
if (!req.body.url) {
Logger.error(`[AuthorController] Invalid request payload. 'url' not in request body`)
return res.status(400).send(`Invalid request payload. 'url' not in request body`)
}
if (!req.body.url.startsWith?.('http:') && !req.body.url.startsWith?.('https:')) {
Logger.error(`[AuthorController] Invalid request payload. Invalid url "${req.body.url}"`)
return res.status(400).send(`Invalid request payload. Invalid url "${req.body.url}"`)
}
Logger.debug(`[AuthorController] Requesting download author image from url "${req.body.url}"`)
const result = await AuthorFinder.saveAuthorImage(req.author.id, req.body.url)
if (result?.error) {
return res.status(400).send(result.error)
} else if (!result?.path) {
return res.status(500).send('Unknown error occurred')
}
if (req.author.imagePath) {
await CacheManager.purgeImageCache(req.author.id) // Purge cache
}
req.author.imagePath = result.path
await Database.authorModel.updateFromOld(req.author)
const numBooks = (await Database.libraryItemModel.getForAuthor(req.author)).length
SocketAuthority.emitter('author_updated', req.author.toJSONExpanded(numBooks))
res.json({
author: req.author.toJSON()
})
}
/**
* DELETE: /api/authors/:id/image
* Remove author image & delete image file
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async deleteImage(req, res) {
if (!req.author.imagePath) {
Logger.error(`[AuthorController] Author "${req.author.imagePath}" has no imagePath set`)
return res.status(400).send('Author has no image path set')
}
Logger.info(`[AuthorController] Removing image for author "${req.author.name}" at "${req.author.imagePath}"`)
await CacheManager.purgeImageCache(req.author.id) // Purge cache
await CoverManager.removeFile(req.author.imagePath)
req.author.imagePath = null
await Database.authorModel.updateFromOld(req.author)
const numBooks = (await Database.libraryItemModel.getForAuthor(req.author)).length
SocketAuthority.emitter('author_updated', req.author.toJSONExpanded(numBooks))
res.json({
author: req.author.toJSON()
})
}
async match(req, res) {
let authorData = null
const region = req.body.region || 'us'
@ -215,7 +264,7 @@ class AuthorController {
await CacheManager.purgeImageCache(req.author.id)
const imageData = await AuthorFinder.saveAuthorImage(req.author.id, authorData.image)
if (imageData) {
if (imageData?.path) {
req.author.imagePath = imageData.path
hasUpdates = true
}
@ -231,7 +280,7 @@ class AuthorController {
await Database.updateAuthor(req.author)
const numBooks = await Database.libraryItemModel.getForAuthor(req.author).length
const numBooks = (await Database.libraryItemModel.getForAuthor(req.author)).length
SocketAuthority.emitter('author_updated', req.author.toJSONExpanded(numBooks))
}

View file

@ -51,32 +51,45 @@ class EmailController {
})
}
/**
* Send ebook to device
* User must have access to device and library item
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async sendEBookToDevice(req, res) {
Logger.debug(`[EmailController] Send ebook to device request for libraryItemId=${req.body.libraryItemId}, deviceName=${req.body.deviceName}`)
Logger.debug(`[EmailController] Send ebook to device requested by user "${req.user.username}" for libraryItemId=${req.body.libraryItemId}, deviceName=${req.body.deviceName}`)
const device = Database.emailSettings.getEReaderDevice(req.body.deviceName)
if (!device) {
return res.status(404).send('Ereader device not found')
}
// Check user has access to device
if (!Database.emailSettings.checkUserCanAccessDevice(device, req.user)) {
return res.sendStatus(403)
}
const libraryItem = await Database.libraryItemModel.getOldById(req.body.libraryItemId)
if (!libraryItem) {
return res.status(404).send('Library item not found')
}
// Check user has access to library item
if (!req.user.checkCanAccessLibraryItem(libraryItem)) {
return res.sendStatus(403)
}
const ebookFile = libraryItem.media.ebookFile
if (!ebookFile) {
return res.status(404).send('EBook file not found')
}
const device = Database.emailSettings.getEReaderDevice(req.body.deviceName)
if (!device) {
return res.status(404).send('E-reader device not found')
return res.status(404).send('Ebook file not found')
}
this.emailManager.sendEBookToDevice(ebookFile, device, res)
}
middleware(req, res, next) {
adminMiddleware(req, res, next) {
if (!req.user.isAdminOrUp) {
return res.sendStatus(404)
}

View file

@ -9,7 +9,8 @@ const libraryItemsBookFilters = require('../utils/queries/libraryItemsBookFilter
const libraryItemFilters = require('../utils/queries/libraryItemFilters')
const seriesFilters = require('../utils/queries/seriesFilters')
const fileUtils = require('../utils/fileUtils')
const { sort, createNewSortInstance } = require('../libs/fastSort')
const { asciiOnlyToLowerCase } = require('../utils/index')
const { createNewSortInstance } = require('../libs/fastSort')
const naturalSort = createNewSortInstance({
comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare
})
@ -555,7 +556,7 @@ class LibraryController {
return res.status(400).send('No query string')
}
const limit = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 12
const query = req.query.q.trim().toLowerCase()
const query = asciiOnlyToLowerCase(req.query.q.trim())
const matches = await libraryItemFilters.search(req.user, req.library, query, limit)
res.json(matches)
@ -620,7 +621,7 @@ class LibraryController {
model: Database.bookModel,
attributes: ['id', 'tags', 'explicit'],
where: bookWhere,
required: false,
required: !req.user.isAdminOrUp, // Only show authors with 0 books for admin users or up
through: {
attributes: []
}
@ -774,6 +775,13 @@ class LibraryController {
})
}
/**
* GET: /api/libraries/:id/matchall
* Quick match all library items. Book libraries only.
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async matchAll(req, res) {
if (!req.user.isAdminOrUp) {
Logger.error(`[LibraryController] Non-root user attempted to match library items`, req.user)
@ -783,7 +791,14 @@ class LibraryController {
res.sendStatus(200)
}
// POST: api/libraries/:id/scan
/**
* POST: /api/libraries/:id/scan
* Optional query:
* ?force=1
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async scan(req, res) {
if (!req.user.isAdminOrUp) {
Logger.error(`[LibraryController] Non-root user attempted to scan library`, req.user)
@ -791,7 +806,8 @@ class LibraryController {
}
res.sendStatus(200)
await LibraryScanner.scan(req.library)
const forceRescan = req.query.force === '1'
await LibraryScanner.scan(req.library, forceRescan)
await Database.resetLibraryIssuesFilterData(req.library.id)
Logger.info('[LibraryController] Scan complete')
@ -845,6 +861,56 @@ class LibraryController {
res.send(opmlText)
}
/**
* Remove all metadata.json or metadata.abs files in library item folders
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async removeAllMetadataFiles(req, res) {
if (!req.user.isAdminOrUp) {
Logger.error(`[LibraryController] Non-admin user attempted to remove all metadata files`, req.user)
return res.sendStatus(403)
}
const fileExt = req.query.ext === 'abs' ? 'abs' : 'json'
const metadataFilename = `metadata.${fileExt}`
const libraryItemsWithMetadata = await Database.libraryItemModel.findAll({
attributes: ['id', 'libraryFiles'],
where: [
{
libraryId: req.library.id
},
Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(libraryFiles) WHERE json_valid(libraryFiles) AND json_extract(json_each.value, "$.metadata.filename") = "${metadataFilename}")`), {
[Sequelize.Op.gte]: 1
})
]
})
if (!libraryItemsWithMetadata.length) {
Logger.info(`[LibraryController] No ${metadataFilename} files found to remove`)
return res.json({
found: 0
})
}
Logger.info(`[LibraryController] Found ${libraryItemsWithMetadata.length} ${metadataFilename} files to remove`)
let numRemoved = 0
for (const libraryItem of libraryItemsWithMetadata) {
const metadataFilepath = libraryItem.libraryFiles.find(lf => lf.metadata.filename === metadataFilename)?.metadata.path
if (!metadataFilepath) continue
Logger.debug(`[LibraryController] Removing file "${metadataFilepath}"`)
if ((await fileUtils.removeFile(metadataFilepath))) {
numRemoved++
}
}
res.json({
found: libraryItemsWithMetadata.length,
removed: numRemoved
})
}
/**
* Middleware that is not using libraryItems from memory
* @param {import('express').Request} req

View file

@ -85,12 +85,31 @@ class LibraryItemController {
res.sendStatus(200)
}
/**
* GET: /api/items/:id/download
* Download library item. Zip file if multiple files.
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
download(req, res) {
if (!req.user.canDownload) {
Logger.warn('User attempted to download without permission', req.user)
return res.sendStatus(403)
}
// If library item is a single file in root dir then no need to zip
if (req.libraryItem.isFile) {
// Express does not set the correct mimetype for m4b files so use our defined mimetypes if available
const audioMimeType = getAudioMimeTypeFromExtname(Path.extname(req.libraryItem.path))
if (audioMimeType) {
res.setHeader('Content-Type', audioMimeType)
}
res.download(req.libraryItem.path, req.libraryItem.relPath)
return
}
const libraryItemPath = req.libraryItem.path
const itemTitle = req.libraryItem.media.metadata.title
Logger.info(`[LibraryItemController] User "${req.user.username}" requested download for item "${itemTitle}" at "${libraryItemPath}"`)
@ -163,22 +182,22 @@ class LibraryItemController {
return res.sendStatus(403)
}
var libraryItem = req.libraryItem
let libraryItem = req.libraryItem
var result = null
if (req.body && req.body.url) {
let result = null
if (req.body?.url) {
Logger.debug(`[LibraryItemController] Requesting download cover from url "${req.body.url}"`)
result = await CoverManager.downloadCoverFromUrl(libraryItem, req.body.url)
} else if (req.files && req.files.cover) {
} else if (req.files?.cover) {
Logger.debug(`[LibraryItemController] Handling uploaded cover`)
result = await CoverManager.uploadCover(libraryItem, req.files.cover)
} else {
return res.status(400).send('Invalid request no file or url')
}
if (result && result.error) {
if (result?.error) {
return res.status(400).send(result.error)
} else if (!result || !result.cover) {
} else if (!result?.cover) {
return res.status(500).send('Unknown error occurred')
}

View file

@ -8,6 +8,9 @@ const Database = require('../Database')
const libraryItemFilters = require('../utils/queries/libraryItemFilters')
const patternValidation = require('../libs/nodeCron/pattern-validation')
const { isObject, getTitleIgnorePrefix } = require('../utils/index')
const { sanitizeFilename } = require('../utils/fileUtils')
const TaskManager = require('../managers/TaskManager')
//
// This is a controller for routes that don't have a home yet :(
@ -30,12 +33,9 @@ class MiscController {
Logger.error('Invalid request, no files')
return res.sendStatus(400)
}
const files = Object.values(req.files)
const title = req.body.title
const author = req.body.author
const series = req.body.series
const libraryId = req.body.library
const folderId = req.body.folder
const { title, author, series, folder: folderId, library: libraryId } = req.body
const library = await Database.libraryModel.getOldById(libraryId)
if (!library) {
@ -50,43 +50,29 @@ class MiscController {
return res.status(500).send(`Invalid post data`)
}
// For setting permissions recursively
let outputDirectory = ''
let firstDirPath = ''
if (library.isPodcast) { // Podcasts only in 1 folder
outputDirectory = Path.join(folder.fullPath, title)
firstDirPath = outputDirectory
} else {
firstDirPath = Path.join(folder.fullPath, author)
if (series && author) {
outputDirectory = Path.join(folder.fullPath, author, series, title)
} else if (author) {
outputDirectory = Path.join(folder.fullPath, author, title)
} else {
outputDirectory = Path.join(folder.fullPath, title)
}
}
if (await fs.pathExists(outputDirectory)) {
Logger.error(`[Server] Upload directory "${outputDirectory}" already exists`)
return res.status(500).send(`Directory "${outputDirectory}" already exists`)
}
// Podcasts should only be one folder deep
const outputDirectoryParts = library.isPodcast ? [title] : [author, series, title]
// `.filter(Boolean)` to strip out all the potentially missing details (eg: `author`)
// before sanitizing all the directory parts to remove illegal chars and finally prepending
// the base folder path
const cleanedOutputDirectoryParts = outputDirectoryParts.filter(Boolean).map(part => sanitizeFilename(part))
const outputDirectory = Path.join(...[folder.fullPath, ...cleanedOutputDirectoryParts])
await fs.ensureDir(outputDirectory)
Logger.info(`Uploading ${files.length} files to`, outputDirectory)
for (let i = 0; i < files.length; i++) {
var file = files[i]
for (const file of files) {
const path = Path.join(outputDirectory, sanitizeFilename(file.name))
var path = Path.join(outputDirectory, file.name)
await file.mv(path).then(() => {
return true
}).catch((error) => {
Logger.error('Failed to move file', path, error)
return false
})
await file.mv(path)
.then(() => {
return true
})
.catch((error) => {
Logger.error('Failed to move file', path, error)
return false
})
}
res.sendStatus(200)
@ -102,7 +88,7 @@ class MiscController {
const includeArray = (req.query.include || '').split(',')
const data = {
tasks: this.taskManager.tasks.map(t => t.toJSON())
tasks: TaskManager.tasks.map(t => t.toJSON())
}
if (includeArray.includes('queue')) {
@ -117,8 +103,9 @@ class MiscController {
/**
* PATCH: /api/settings
* Update server settings
* @param {*} req
* @param {*} res
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async updateServerSettings(req, res) {
if (!req.user.isAdminOrUp) {
@ -126,7 +113,7 @@ class MiscController {
return res.sendStatus(403)
}
const settingsUpdate = req.body
if (!settingsUpdate || !isObject(settingsUpdate)) {
if (!isObject(settingsUpdate)) {
return res.status(400).send('Invalid settings update object')
}
@ -246,8 +233,8 @@ class MiscController {
* POST: /api/authorize
* Used to authorize an API token
*
* @param {*} req
* @param {*} res
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async authorize(req, res) {
if (!req.user) {
@ -525,6 +512,54 @@ class MiscController {
})
}
/**
* POST: /api/watcher/update
* Update a watch path
* Req.body { libraryId, path, type, [oldPath] }
* type = add, unlink, rename
* oldPath = required only for rename
* @this import('../routers/ApiRouter')
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
updateWatchedPath(req, res) {
if (!req.user.isAdminOrUp) {
Logger.error(`[MiscController] Non-admin user attempted to updateWatchedPath`)
return res.sendStatus(404)
}
const libraryId = req.body.libraryId
const path = req.body.path
const type = req.body.type
if (!libraryId || !path || !type) {
Logger.error(`[MiscController] Invalid request body for updateWatchedPath. libraryId: "${libraryId}", path: "${path}", type: "${type}"`)
return res.sendStatus(400)
}
switch (type) {
case 'add':
this.watcher.onFileAdded(libraryId, path)
break
case 'unlink':
this.watcher.onFileRemoved(libraryId, path)
break
case 'rename':
const oldPath = req.body.oldPath
if (!oldPath) {
Logger.error(`[MiscController] Invalid request body for updateWatchedPath. oldPath is required for rename.`)
return res.sendStatus(400)
}
this.watcher.onFileRename(libraryId, oldPath, path)
break
default:
Logger.error(`[MiscController] Invalid type for updateWatchedPath. type: "${type}"`)
return res.sendStatus(400)
}
res.sendStatus(200)
}
validateCronExpression(req, res) {
const expression = req.body.expression
if (!expression) {
@ -539,5 +574,127 @@ class MiscController {
res.status(400).send(error.message)
}
}
/**
* GET: api/auth-settings (admin only)
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
getAuthSettings(req, res) {
if (!req.user.isAdminOrUp) {
Logger.error(`[MiscController] Non-admin user "${req.user.username}" attempted to get auth settings`)
return res.sendStatus(403)
}
return res.json(Database.serverSettings.authenticationSettings)
}
/**
* PATCH: api/auth-settings
* @this import('../routers/ApiRouter')
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async updateAuthSettings(req, res) {
if (!req.user.isAdminOrUp) {
Logger.error(`[MiscController] Non-admin user "${req.user.username}" attempted to update auth settings`)
return res.sendStatus(403)
}
const settingsUpdate = req.body
if (!isObject(settingsUpdate)) {
return res.status(400).send('Invalid auth settings update object')
}
let hasUpdates = false
const currentAuthenticationSettings = Database.serverSettings.authenticationSettings
const originalAuthMethods = [...currentAuthenticationSettings.authActiveAuthMethods]
// TODO: Better validation of auth settings once auth settings are separated from server settings
for (const key in currentAuthenticationSettings) {
if (settingsUpdate[key] === undefined) continue
if (key === 'authActiveAuthMethods') {
let updatedAuthMethods = settingsUpdate[key]?.filter?.((authMeth) => Database.serverSettings.supportedAuthMethods.includes(authMeth))
if (Array.isArray(updatedAuthMethods) && updatedAuthMethods.length) {
updatedAuthMethods.sort()
currentAuthenticationSettings[key].sort()
if (updatedAuthMethods.join() !== currentAuthenticationSettings[key].join()) {
Logger.debug(`[MiscController] Updating auth settings key "authActiveAuthMethods" from "${currentAuthenticationSettings[key].join()}" to "${updatedAuthMethods.join()}"`)
Database.serverSettings[key] = updatedAuthMethods
hasUpdates = true
}
} else {
Logger.warn(`[MiscController] Invalid value for authActiveAuthMethods`)
}
} else if (key === 'authOpenIDMobileRedirectURIs') {
function isValidRedirectURI(uri) {
if (typeof uri !== 'string') return false
const pattern = new RegExp('^\\w+://[\\w.-]+$', 'i')
return pattern.test(uri)
}
const uris = settingsUpdate[key]
if (!Array.isArray(uris) ||
(uris.includes('*') && uris.length > 1) ||
uris.some(uri => uri !== '*' && !isValidRedirectURI(uri))) {
Logger.warn(`[MiscController] Invalid value for authOpenIDMobileRedirectURIs`)
continue
}
// Update the URIs
if (Database.serverSettings[key].some(uri => !uris.includes(uri)) || uris.some(uri => !Database.serverSettings[key].includes(uri))) {
Logger.debug(`[MiscController] Updating auth settings key "${key}" from "${Database.serverSettings[key]}" to "${uris}"`)
Database.serverSettings[key] = uris
hasUpdates = true
}
} else {
const updatedValueType = typeof settingsUpdate[key]
if (['authOpenIDAutoLaunch', 'authOpenIDAutoRegister'].includes(key)) {
if (updatedValueType !== 'boolean') {
Logger.warn(`[MiscController] Invalid value for ${key}. Expected boolean`)
continue
}
} else if (settingsUpdate[key] !== null && updatedValueType !== 'string') {
Logger.warn(`[MiscController] Invalid value for ${key}. Expected string or null`)
continue
}
let updatedValue = settingsUpdate[key]
if (updatedValue === '') updatedValue = null
let currentValue = currentAuthenticationSettings[key]
if (currentValue === '') currentValue = null
if (updatedValue !== currentValue) {
Logger.debug(`[MiscController] Updating auth settings key "${key}" from "${currentValue}" to "${updatedValue}"`)
Database.serverSettings[key] = updatedValue
hasUpdates = true
}
}
}
if (hasUpdates) {
await Database.updateServerSettings()
// Use/unuse auth methods
Database.serverSettings.supportedAuthMethods.forEach((authMethod) => {
if (originalAuthMethods.includes(authMethod) && !Database.serverSettings.authActiveAuthMethods.includes(authMethod)) {
// Auth method has been removed
Logger.info(`[MiscController] Disabling active auth method "${authMethod}"`)
this.auth.unuseAuthStrategy(authMethod)
} else if (!originalAuthMethods.includes(authMethod) && Database.serverSettings.authActiveAuthMethods.includes(authMethod)) {
// Auth method has been added
Logger.info(`[MiscController] Enabling active auth method "${authMethod}"`)
this.auth.useAuthStrategy(authMethod)
}
})
}
res.json({
updated: hasUpdates,
serverSettings: Database.serverSettings.toJSONForBrowser()
})
}
}
module.exports = new MiscController()
module.exports = new MiscController()

View file

@ -222,10 +222,9 @@ class PodcastController {
Logger.error(`[PodcastController] Non-admin user attempted to download episodes`, req.user)
return res.sendStatus(403)
}
var libraryItem = req.libraryItem
var episodes = req.body
if (!episodes || !episodes.length) {
const libraryItem = req.libraryItem
const episodes = req.body
if (!episodes?.length) {
return res.sendStatus(400)
}
@ -324,7 +323,7 @@ class PodcastController {
const numItems = pmi.playlist.playlistMediaItems.length - 1
if (!numItems) {
Logger.info(`[PodcastController] Playlist "${playlist.name}" has no more items - removing it`)
Logger.info(`[PodcastController] Playlist "${pmi.playlist.name}" has no more items - removing it`)
const jsonExpanded = await pmi.playlist.getOldJsonExpanded()
SocketAuthority.clientEmitter(pmi.playlist.userId, 'playlist_removed', jsonExpanded)
await pmi.playlist.destroy()

View file

@ -6,7 +6,7 @@ class SessionController {
constructor() { }
async findOne(req, res) {
return res.json(req.session)
return res.json(req.playbackSession)
}
async getAllWithUserData(req, res) {
@ -63,32 +63,32 @@ class SessionController {
}
async getOpenSession(req, res) {
const libraryItem = await Database.libraryItemModel.getOldById(req.session.libraryItemId)
const sessionForClient = req.session.toJSONForClient(libraryItem)
const libraryItem = await Database.libraryItemModel.getOldById(req.playbackSession.libraryItemId)
const sessionForClient = req.playbackSession.toJSONForClient(libraryItem)
res.json(sessionForClient)
}
// POST: api/session/:id/sync
sync(req, res) {
this.playbackSessionManager.syncSessionRequest(req.user, req.session, req.body, res)
this.playbackSessionManager.syncSessionRequest(req.user, req.playbackSession, req.body, res)
}
// POST: api/session/:id/close
close(req, res) {
let syncData = req.body
if (syncData && !Object.keys(syncData).length) syncData = null
this.playbackSessionManager.closeSessionRequest(req.user, req.session, syncData, res)
this.playbackSessionManager.closeSessionRequest(req.user, req.playbackSession, syncData, res)
}
// DELETE: api/session/:id
async delete(req, res) {
// if session is open then remove it
const openSession = this.playbackSessionManager.getSession(req.session.id)
const openSession = this.playbackSessionManager.getSession(req.playbackSession.id)
if (openSession) {
await this.playbackSessionManager.removeSession(req.session.id)
await this.playbackSessionManager.removeSession(req.playbackSession.id)
}
await Database.removePlaybackSession(req.session.id)
await Database.removePlaybackSession(req.playbackSession.id)
res.sendStatus(200)
}
@ -111,7 +111,7 @@ class SessionController {
return res.sendStatus(404)
}
req.session = playbackSession
req.playbackSession = playbackSession
next()
}
@ -130,7 +130,7 @@ class SessionController {
return res.sendStatus(403)
}
req.session = playbackSession
req.playbackSession = playbackSession
next()
}
}

View file

@ -100,7 +100,7 @@ class UserController {
account.id = uuidv4()
account.pash = await this.auth.hashPass(account.password)
delete account.password
account.token = await this.auth.generateAccessToken({ userId: account.id, username })
account.token = await this.auth.generateAccessToken(account)
account.createdAt = Date.now()
const newUser = new User(account)
@ -115,6 +115,13 @@ class UserController {
}
}
/**
* PATCH: /api/users/:id
* Update user
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async update(req, res) {
const user = req.reqUser
@ -126,6 +133,7 @@ class UserController {
var account = req.body
var shouldUpdateToken = false
// When changing username create a new API token
if (account.username !== undefined && account.username !== user.username) {
const usernameExists = await Database.userModel.getUserByUsername(account.username)
if (usernameExists) {
@ -142,7 +150,7 @@ class UserController {
if (user.update(account)) {
if (shouldUpdateToken) {
user.token = await this.auth.generateAccessToken({ userId: user.id, username: user.username })
user.token = await this.auth.generateAccessToken(user)
Logger.info(`[UserController] User ${user.username} was generated a new api token`)
}
await Database.updateUser(user)