Merge remote-tracking branch 'origin/master' into auth_passportjs

This commit is contained in:
lukeIam 2023-04-14 20:27:43 +02:00
commit 812395b21b
90 changed files with 3469 additions and 1148 deletions

View file

@ -1,52 +0,0 @@
const Logger = require('../Logger')
const { isNullOrNaN } = require('../utils/index')
class EBookController {
constructor() { }
async getEbookInfo(req, res) {
const isDev = req.query.dev == 1
const json = await this.eBookManager.getBookInfo(req.libraryItem, req.user, isDev)
res.json(json)
}
async getEbookPage(req, res) {
if (isNullOrNaN(req.params.page)) {
return res.status(400).send('Invalid page params')
}
const isDev = req.query.dev == 1
const pageIndex = Number(req.params.page)
const page = await this.eBookManager.getBookPage(req.libraryItem, req.user, pageIndex, isDev)
if (!page) {
return res.status(500).send('Failed to get page')
}
res.send(page)
}
async getEbookResource(req, res) {
if (!req.query.path) {
return res.status(400).send('Invalid query path')
}
const isDev = req.query.dev == 1
this.eBookManager.getBookResource(req.libraryItem, req.user, req.query.path, isDev, res)
}
middleware(req, res, next) {
const item = this.db.libraryItems.find(li => li.id === req.params.id)
if (!item || !item.media) return res.sendStatus(404)
// Check user can access this library item
if (!req.user.checkCanAccessLibraryItem(item)) {
return res.sendStatus(403)
}
if (!item.isBook || !item.media.ebookFile) {
return res.status(400).send('Invalid ebook library item')
}
req.libraryItem = item
next()
}
}
module.exports = new EBookController()

View file

@ -417,6 +417,10 @@ class LibraryController {
return se.totalDuration
} else if (payload.sortBy === 'addedAt') {
return se.addedAt
} else if (payload.sortBy === 'lastBookUpdated') {
return Math.max(...(se.books).map(x => x.updatedAt), 0)
} else if (payload.sortBy === 'lastBookAdded') {
return Math.max(...(se.books).map(x => x.addedAt), 0)
} else { // sort by name
return this.db.serverSettings.sortingIgnorePrefix ? se.nameIgnorePrefixSort : se.name
}

View file

@ -2,6 +2,7 @@ const fs = require('../libs/fsExtra')
const Logger = require('../Logger')
const SocketAuthority = require('../SocketAuthority')
const zipHelpers = require('../utils/zipHelpers')
const { reqSupportsWebp, isNullOrNaN } = require('../utils/index')
const { ScanResult } = require('../utils/constants')
@ -69,6 +70,17 @@ class LibraryItemController {
res.sendStatus(200)
}
download(req, res) {
if (!req.user.canDownload) {
Logger.warn('User attempted to download without permission', req.user)
return res.sendStatus(403)
}
const libraryItemPath = req.libraryItem.path
const filename = `${req.libraryItem.media.metadata.title}.zip`
zipHelpers.zipDirectoryPipe(libraryItemPath, filename, res)
}
//
// PATCH: will create new authors & series if in payload
//
@ -162,12 +174,12 @@ class LibraryItemController {
// PATCH: api/items/:id/cover
async updateCover(req, res) {
var libraryItem = req.libraryItem
const libraryItem = req.libraryItem
if (!req.body.cover) {
return res.status(400).error('Invalid request no cover path')
return res.status(400).send('Invalid request no cover path')
}
var validationResult = await this.coverManager.validateCoverPath(req.body.cover, libraryItem)
const validationResult = await this.coverManager.validateCoverPath(req.body.cover, libraryItem)
if (validationResult.error) {
return res.status(500).send(validationResult.error)
}
@ -436,12 +448,12 @@ class LibraryItemController {
return res.sendStatus(500)
}
const chapters = req.body.chapters || []
if (!chapters.length) {
if (!req.body.chapters) {
Logger.error(`[LibraryItemController] Invalid payload`)
return res.sendStatus(400)
}
const chapters = req.body.chapters || []
const wasUpdated = req.libraryItem.media.updateChapters(chapters)
if (wasUpdated) {
await this.db.updateLibraryItem(req.libraryItem)
@ -470,6 +482,28 @@ class LibraryItemController {
res.json(toneData)
}
async deleteLibraryFile(req, res) {
const libraryFile = req.libraryItem.libraryFiles.find(lf => lf.ino === req.params.ino)
if (!libraryFile) {
Logger.error(`[LibraryItemController] Unable to delete library file. Not found. "${req.params.ino}"`)
return res.sendStatus(404)
}
await fs.remove(libraryFile.metadata.path)
req.libraryItem.removeLibraryFile(req.params.ino)
if (req.libraryItem.media.removeFileWithInode(req.params.ino)) {
// If book has no more media files then mark it as missing
if (req.libraryItem.mediaType === 'book' && !req.libraryItem.media.hasMediaEntities) {
req.libraryItem.setMissing()
}
}
req.libraryItem.updatedAt = Date.now()
await this.db.updateLibraryItem(req.libraryItem)
SocketAuthority.emitter('item_updated', req.libraryItem.toJSONExpanded())
res.sendStatus(200)
}
middleware(req, res, next) {
const item = this.db.libraryItems.find(li => li.id === req.params.id)
if (!item || !item.media) return res.sendStatus(404)

View file

@ -171,23 +171,6 @@ class MeController {
this.auth.userChangePassword(req, res)
}
// TODO: Remove after mobile release v0.9.61-beta
// PATCH: api/me/settings
async updateSettings(req, res) {
var settingsUpdate = req.body
if (!settingsUpdate || !isObject(settingsUpdate)) {
return res.sendStatus(500)
}
var madeUpdates = req.user.updateSettings(settingsUpdate)
if (madeUpdates) {
await this.db.updateEntity('user', req.user)
}
return res.json({
success: true,
settings: req.user.settings
})
}
// TODO: Deprecated. Removed from Android. Only used in iOS app now.
// POST: api/me/sync-local-progress
async syncLocalMediaProgress(req, res) {
@ -256,13 +239,13 @@ class MeController {
}
// GET: api/me/items-in-progress
async getAllLibraryItemsInProgress(req, res) {
getAllLibraryItemsInProgress(req, res) {
const limit = !isNaN(req.query.limit) ? Number(req.query.limit) || 25 : 25
var itemsInProgress = []
let itemsInProgress = []
for (const mediaProgress of req.user.mediaProgress) {
if (!mediaProgress.isFinished && mediaProgress.progress > 0) {
const libraryItem = await this.db.getLibraryItem(mediaProgress.libraryItemId)
if (!mediaProgress.isFinished && (mediaProgress.progress > 0 || mediaProgress.ebookProgress > 0)) {
const libraryItem = this.db.getLibraryItem(mediaProgress.libraryItemId)
if (libraryItem) {
if (mediaProgress.episodeId && libraryItem.mediaType === 'podcast') {
const episode = libraryItem.media.episodes.find(ep => ep.id === mediaProgress.episodeId)

View file

@ -90,9 +90,19 @@ class MiscController {
// GET: api/tasks
getTasks(req, res) {
res.json({
const includeArray = (req.query.include || '').split(',')
const data = {
tasks: this.taskManager.tasks.map(t => t.toJSON())
})
}
if (includeArray.includes('queue')) {
data.queuedTaskData = {
embedMetadata: this.audioMetadataManager.getQueuedTaskData()
}
}
res.json(data)
}
// PATCH: api/settings (admin)

View file

@ -14,7 +14,7 @@ class SessionController {
return res.sendStatus(404)
}
var listeningSessions = []
let listeningSessions = []
if (req.query.user) {
listeningSessions = await this.getUserListeningSessionsHelper(req.query.user)
} else {
@ -42,6 +42,25 @@ class SessionController {
res.json(payload)
}
getOpenSessions(req, res) {
if (!req.user.isAdminOrUp) {
Logger.error(`[SessionController] getOpenSessions: Non-admin user requested open session data ${req.user.id}/"${req.user.username}"`)
return res.sendStatus(404)
}
const openSessions = this.playbackSessionManager.sessions.map(se => {
const user = this.db.users.find(u => u.id === se.userId) || null
return {
...se.toJSON(),
user: user ? { id: user.id, username: user.username } : null
}
})
res.json({
sessions: openSessions
})
}
getOpenSession(req, res) {
var libraryItem = this.db.getLibraryItem(req.session.libraryItemId)
var sessionForClient = req.session.toJSONForClient(libraryItem)

View file

@ -3,14 +3,8 @@ const Logger = require('../Logger')
class ToolsController {
constructor() { }
// POST: api/tools/item/:id/encode-m4b
async encodeM4b(req, res) {
if (!req.user.isAdminOrUp) {
Logger.error('[MiscController] encodeM4b: Non-admin user attempting to make m4b', req.user)
return res.sendStatus(403)
}
if (req.libraryItem.isMissing || req.libraryItem.isInvalid) {
Logger.error(`[MiscController] encodeM4b: library item not found or invalid ${req.params.id}`)
return res.status(404).send('Audiobook not found')
@ -34,11 +28,6 @@ class ToolsController {
// DELETE: api/tools/item/:id/encode-m4b
async cancelM4bEncode(req, res) {
if (!req.user.isAdminOrUp) {
Logger.error('[MiscController] cancelM4bEncode: Non-admin user attempting to cancel m4b encode', req.user)
return res.sendStatus(403)
}
const workerTask = this.abMergeManager.getPendingTaskByLibraryItemId(req.params.id)
if (!workerTask) return res.sendStatus(404)
@ -49,14 +38,14 @@ class ToolsController {
// POST: api/tools/item/:id/embed-metadata
async embedAudioFileMetadata(req, res) {
if (!req.user.isAdminOrUp) {
Logger.error(`[LibraryItemController] Non-root user attempted to update audio metadata`, req.user)
return res.sendStatus(403)
if (req.libraryItem.isMissing || !req.libraryItem.hasAudioFiles || !req.libraryItem.isBook) {
Logger.error(`[ToolsController] Invalid library item`)
return res.sendStatus(500)
}
if (req.libraryItem.isMissing || !req.libraryItem.hasAudioFiles || !req.libraryItem.isBook) {
Logger.error(`[LibraryItemController] Invalid library item`)
return res.sendStatus(500)
if (this.audioMetadataManager.getIsLibraryItemQueuedOrProcessing(req.libraryItem.id)) {
Logger.error(`[ToolsController] Library item (${req.libraryItem.id}) is already in queue or processing`)
return res.status(500).send('Library item is already in queue or processing')
}
const options = {
@ -67,16 +56,66 @@ class ToolsController {
res.sendStatus(200)
}
itemMiddleware(req, res, next) {
var item = this.db.libraryItems.find(li => li.id === req.params.id)
if (!item || !item.media) return res.sendStatus(404)
// POST: api/tools/batch/embed-metadata
async batchEmbedMetadata(req, res) {
const libraryItemIds = req.body.libraryItemIds || []
if (!libraryItemIds.length) {
return res.status(400).send('Invalid request payload')
}
// Check user can access this library item
if (!req.user.checkCanAccessLibraryItem(item)) {
const libraryItems = []
for (const libraryItemId of libraryItemIds) {
const libraryItem = this.db.getLibraryItem(libraryItemId)
if (!libraryItem) {
Logger.error(`[ToolsController] Batch embed metadata library item (${libraryItemId}) not found`)
return res.sendStatus(404)
}
// Check user can access this library item
if (!req.user.checkCanAccessLibraryItem(libraryItem)) {
Logger.error(`[ToolsController] Batch embed metadata library item (${libraryItemId}) not accessible to user`, req.user)
return res.sendStatus(403)
}
if (libraryItem.isMissing || !libraryItem.hasAudioFiles || !libraryItem.isBook) {
Logger.error(`[ToolsController] Batch embed invalid library item (${libraryItemId})`)
return res.sendStatus(500)
}
if (this.audioMetadataManager.getIsLibraryItemQueuedOrProcessing(libraryItemId)) {
Logger.error(`[ToolsController] Batch embed library item (${libraryItemId}) is already in queue or processing`)
return res.status(500).send('Library item is already in queue or processing')
}
libraryItems.push(libraryItem)
}
const options = {
forceEmbedChapters: req.query.forceEmbedChapters === '1',
backup: req.query.backup === '1'
}
this.audioMetadataManager.handleBatchEmbed(req.user, libraryItems, options)
res.sendStatus(200)
}
middleware(req, res, next) {
if (!req.user.isAdminOrUp) {
Logger.error(`[LibraryItemController] Non-root user attempted to access tools route`, req.user)
return res.sendStatus(403)
}
req.libraryItem = item
if (req.params.id) {
const item = this.db.libraryItems.find(li => li.id === req.params.id)
if (!item || !item.media) return res.sendStatus(404)
// Check user can access this library item
if (!req.user.checkCanAccessLibraryItem(item)) {
return res.sendStatus(403)
}
req.libraryItem = item
}
next()
}
}

View file

@ -11,9 +11,9 @@ class UserController {
findAll(req, res) {
if (!req.user.isAdminOrUp) return res.sendStatus(403)
const hideRootToken = !req.user.isRoot
const users = this.db.users.map(u => this.userJsonWithItemProgressDetails(u, hideRootToken))
res.json({
users: users
// Minimal toJSONForBrowser does not include mediaProgress and bookmarks
users: this.db.users.map(u => u.toJSONForBrowser(hideRootToken, true))
})
}