mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-12-15 16:29:37 +00:00
Merge remote-tracking branch 'origin/master' into auth_passportjs
This commit is contained in:
commit
812395b21b
90 changed files with 3469 additions and 1148 deletions
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue