mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-12-29 15:19:38 +00:00
New data model batch routes and batch editor
This commit is contained in:
parent
6597fca576
commit
4bdef893af
19 changed files with 743 additions and 604 deletions
|
|
@ -21,6 +21,9 @@ const AuthorController = require('./controllers/AuthorController')
|
|||
const BookFinder = require('./finders/BookFinder')
|
||||
const AuthorFinder = require('./finders/AuthorFinder')
|
||||
const PodcastFinder = require('./finders/PodcastFinder')
|
||||
|
||||
const Author = require('./objects/entities/Author')
|
||||
const Series = require('./objects/entities/Series')
|
||||
const FileSystemController = require('./controllers/FileSystemController')
|
||||
|
||||
class ApiController {
|
||||
|
|
@ -65,11 +68,8 @@ class ApiController {
|
|||
this.router.get('/libraries/:id/stats', LibraryController.middleware.bind(this), LibraryController.stats.bind(this))
|
||||
this.router.get('/libraries/:id/authors', LibraryController.middleware.bind(this), LibraryController.getAuthors.bind(this))
|
||||
this.router.post('/libraries/:id/matchbooks', LibraryController.middleware.bind(this), LibraryController.matchBooks.bind(this))
|
||||
this.router.post('/libraries/order', LibraryController.reorder.bind(this))
|
||||
|
||||
// Legacy
|
||||
this.router.get('/libraries/:id/categories', LibraryController.middleware.bind(this), LibraryController.getLibraryCategories.bind(this))
|
||||
this.router.get('/libraries/:id/filters', LibraryController.middleware.bind(this), LibraryController.getLibraryFilters.bind(this))
|
||||
this.router.post('/libraries/order', LibraryController.reorder.bind(this))
|
||||
|
||||
//
|
||||
// Item Routes
|
||||
|
|
@ -84,6 +84,10 @@ class ApiController {
|
|||
this.router.delete('/items/:id/cover', LibraryItemController.middleware.bind(this), LibraryItemController.removeCover.bind(this))
|
||||
this.router.get('/items/:id/stream', LibraryItemController.middleware.bind(this), LibraryItemController.openStream.bind(this))
|
||||
|
||||
this.router.post('/items/batch/delete', LibraryItemController.batchDelete.bind(this))
|
||||
this.router.post('/items/batch/update', LibraryItemController.batchUpdate.bind(this))
|
||||
this.router.post('/items/batch/get', LibraryItemController.batchGet.bind(this))
|
||||
|
||||
//
|
||||
// Book Routes
|
||||
//
|
||||
|
|
@ -93,9 +97,6 @@ class ApiController {
|
|||
this.router.delete('/books/:id', BookController.delete.bind(this))
|
||||
|
||||
this.router.delete('/books/all', BookController.deleteAll.bind(this))
|
||||
this.router.post('/books/batch/delete', BookController.batchDelete.bind(this))
|
||||
this.router.post('/books/batch/update', BookController.batchUpdate.bind(this))
|
||||
this.router.post('/books/batch/get', BookController.batchGet.bind(this))
|
||||
this.router.patch('/books/:id/tracks', BookController.updateTracks.bind(this))
|
||||
this.router.get('/books/:id/stream', BookController.openStream.bind(this))
|
||||
this.router.post('/books/:id/cover', BookController.uploadCover.bind(this))
|
||||
|
|
@ -162,17 +163,16 @@ class ApiController {
|
|||
//
|
||||
// Author Routes
|
||||
//
|
||||
this.router.get('/authors/search', AuthorController.search.bind(this))
|
||||
this.router.get('/authors/:id', AuthorController.middleware.bind(this), AuthorController.findOne.bind(this))
|
||||
this.router.post('/authors/:id/match', AuthorController.middleware.bind(this), AuthorController.match.bind(this))
|
||||
this.router.get('/authors/:id/image', AuthorController.middleware.bind(this), AuthorController.getImage.bind(this))
|
||||
this.router.get('/authors/search', AuthorController.search.bind(this))
|
||||
|
||||
//
|
||||
// Series Routes
|
||||
//
|
||||
this.router.get('/series/:id', SeriesController.middleware.bind(this), SeriesController.findOne.bind(this))
|
||||
this.router.get('/series/search', SeriesController.search.bind(this))
|
||||
|
||||
this.router.get('/series/:id', SeriesController.middleware.bind(this), SeriesController.findOne.bind(this))
|
||||
|
||||
//
|
||||
// Misc Routes
|
||||
|
|
@ -488,6 +488,60 @@ class ApiController {
|
|||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
async createAuthorsAndSeriesForItemUpdate(mediaPayload) {
|
||||
if (mediaPayload.metadata) {
|
||||
var mediaMetadata = mediaPayload.metadata
|
||||
|
||||
// Create new authors if in payload
|
||||
if (mediaMetadata.authors && mediaMetadata.authors.length) {
|
||||
// TODO: validate authors
|
||||
var newAuthors = []
|
||||
for (let i = 0; i < mediaMetadata.authors.length; i++) {
|
||||
if (mediaMetadata.authors[i].id.startsWith('new')) {
|
||||
var author = this.db.authors.find(au => au.checkNameEquals(mediaMetadata.authors[i].name))
|
||||
if (!author) {
|
||||
author = new Author()
|
||||
author.setData(mediaMetadata.authors[i])
|
||||
Logger.debug(`[ApiController] Created new author "${author.name}"`)
|
||||
newAuthors.push(author)
|
||||
}
|
||||
|
||||
// Update ID in original payload
|
||||
mediaMetadata.authors[i].id = author.id
|
||||
}
|
||||
}
|
||||
if (newAuthors.length) {
|
||||
await this.db.insertEntities('author', newAuthors)
|
||||
this.emitter('authors_added', newAuthors)
|
||||
}
|
||||
}
|
||||
|
||||
// Create new series if in payload
|
||||
if (mediaMetadata.series && mediaMetadata.series.length) {
|
||||
// TODO: validate series
|
||||
var newSeries = []
|
||||
for (let i = 0; i < mediaMetadata.series.length; i++) {
|
||||
if (mediaMetadata.series[i].id.startsWith('new')) {
|
||||
var seriesItem = this.db.series.find(se => se.checkNameEquals(mediaMetadata.series[i].name))
|
||||
if (!seriesItem) {
|
||||
seriesItem = new Series()
|
||||
seriesItem.setData(mediaMetadata.series[i])
|
||||
Logger.debug(`[ApiController] Created new series "${seriesItem.name}"`)
|
||||
newSeries.push(seriesItem)
|
||||
}
|
||||
|
||||
// Update ID in original payload
|
||||
mediaMetadata.series[i].id = seriesItem.id
|
||||
}
|
||||
}
|
||||
if (newSeries.length) {
|
||||
await this.db.insertEntities('series', newSeries)
|
||||
this.emitter('authors_added', newSeries)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getPodcastFeed(req, res) {
|
||||
var url = req.body.rssFeed
|
||||
if (!url) {
|
||||
|
|
|
|||
|
|
@ -226,14 +226,6 @@ class LibraryController {
|
|||
res.json(payload)
|
||||
}
|
||||
|
||||
// LEGACY
|
||||
// api/libraries/:id/books/filters
|
||||
async getLibraryFilters(req, res) {
|
||||
var library = req.library
|
||||
var books = this.db.audiobooks.filter(ab => ab.libraryId === library.id)
|
||||
res.json(libraryHelpers.getDistinctFilterData(books))
|
||||
}
|
||||
|
||||
async getLibraryFilterData(req, res) {
|
||||
res.json(libraryHelpers.getDistinctFilterDataNew(req.libraryItems))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
const Logger = require('../Logger')
|
||||
const Author = require('../objects/entities/Author')
|
||||
const Series = require('../objects/entities/Series')
|
||||
const { reqSupportsWebp } = require('../utils/index')
|
||||
|
||||
class LibraryItemController {
|
||||
|
|
@ -43,49 +41,7 @@ class LibraryItemController {
|
|||
await this.cacheManager.purgeCoverCache(libraryItem.id)
|
||||
}
|
||||
|
||||
if (mediaPayload.metadata) {
|
||||
var mediaMetadata = mediaPayload.metadata
|
||||
|
||||
// Create new authors if in payload
|
||||
if (mediaMetadata.authors && mediaMetadata.authors.length) {
|
||||
// TODO: validate authors
|
||||
var newAuthors = []
|
||||
for (let i = 0; i < mediaMetadata.authors.length; i++) {
|
||||
if (mediaMetadata.authors[i].id.startsWith('new')) {
|
||||
var newAuthor = new Author()
|
||||
newAuthor.setData(mediaMetadata.authors[i])
|
||||
Logger.debug(`[LibraryItemController] Created new author "${newAuthor.name}"`)
|
||||
newAuthors.push(newAuthor)
|
||||
// Update ID in original payload
|
||||
mediaMetadata.authors[i].id = newAuthor.id
|
||||
}
|
||||
}
|
||||
if (newAuthors.length) {
|
||||
await this.db.insertEntities('author', newAuthors)
|
||||
this.emitter('authors_added', newAuthors)
|
||||
}
|
||||
}
|
||||
|
||||
// Create new series if in payload
|
||||
if (mediaMetadata.series && mediaMetadata.series.length) {
|
||||
// TODO: validate series
|
||||
var newSeries = []
|
||||
for (let i = 0; i < mediaMetadata.series.length; i++) {
|
||||
if (mediaMetadata.series[i].id.startsWith('new')) {
|
||||
var newSeriesItem = new Series()
|
||||
newSeriesItem.setData(mediaMetadata.series[i])
|
||||
Logger.debug(`[LibraryItemController] Created new series "${newSeriesItem.name}"`)
|
||||
newSeries.push(newSeriesItem)
|
||||
// Update ID in original payload
|
||||
mediaMetadata.series[i].id = newSeriesItem.id
|
||||
}
|
||||
}
|
||||
if (newSeries.length) {
|
||||
await this.db.insertEntities('series', newSeries)
|
||||
this.emitter('authors_added', newSeries)
|
||||
}
|
||||
}
|
||||
}
|
||||
await this.createAuthorsAndSeriesForItemUpdate(mediaPayload)
|
||||
|
||||
var hasUpdates = libraryItem.media.update(mediaPayload)
|
||||
if (hasUpdates) {
|
||||
|
|
@ -182,11 +138,76 @@ class LibraryItemController {
|
|||
this.streamManager.openStreamApiRequest(res, req.user, req.libraryItem)
|
||||
}
|
||||
|
||||
// POST: api/items/batch/delete
|
||||
async batchDelete(req, res) {
|
||||
if (!req.user.canDelete) {
|
||||
Logger.warn(`[LibraryItemController] User attempted to delete without permission`, req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
var { libraryItemIds } = req.body
|
||||
if (!libraryItemIds || !libraryItemIds.length) {
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
|
||||
var itemsToDelete = this.db.libraryItems.filter(li => libraryItemIds.includes(li.id))
|
||||
if (!itemsToDelete.length) {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
for (let i = 0; i < itemsToDelete.length; i++) {
|
||||
Logger.info(`[LibraryItemController] Deleting Library Item "${itemsToDelete[i].media.metadata.title}"`)
|
||||
await this.handleDeleteLibraryItem(itemsToDelete[i])
|
||||
}
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
// POST: api/items/batch/update
|
||||
async batchUpdate(req, res) {
|
||||
var updatePayloads = req.body
|
||||
if (!updatePayloads || !updatePayloads.length) {
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
|
||||
var itemsUpdated = 0
|
||||
|
||||
for (let i = 0; i < updatePayloads.length; i++) {
|
||||
var mediaPayload = updatePayloads[i].mediaPayload
|
||||
var libraryItem = this.db.libraryItems.find(_li => _li.id === updatePayloads[i].id)
|
||||
if (!libraryItem) return null
|
||||
|
||||
await this.createAuthorsAndSeriesForItemUpdate(mediaPayload)
|
||||
|
||||
var hasUpdates = libraryItem.media.update(mediaPayload)
|
||||
if (hasUpdates) {
|
||||
Logger.debug(`[LibraryItemController] Updated library item media ${libraryItem.media.metadata.title}`)
|
||||
await this.db.updateLibraryItem(libraryItem)
|
||||
this.emitter('item_updated', libraryItem.toJSONExpanded())
|
||||
itemsUpdated++
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
updates: itemsUpdated
|
||||
})
|
||||
}
|
||||
|
||||
// POST: api/items/batch/get
|
||||
async batchGet(req, res) {
|
||||
var libraryItemIds = req.body.libraryItemIds || []
|
||||
if (!libraryItemIds.length) {
|
||||
return res.status(403).send('Invalid payload')
|
||||
}
|
||||
var libraryItems = this.db.libraryItems.filter(li => libraryItemIds.includes(li.id)).map((li) => li.toJSONExpanded())
|
||||
res.json(libraryItems)
|
||||
}
|
||||
|
||||
|
||||
middleware(req, res, next) {
|
||||
var item = this.db.libraryItems.find(li => li.id === req.params.id)
|
||||
if (!item || !item.media) return res.sendStatus(404)
|
||||
|
||||
// Check user can access this audiobooks library
|
||||
// Check user can access this library
|
||||
if (!req.user.checkCanAccessLibrary(item.libraryId)) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,50 +133,6 @@ module.exports = {
|
|||
return data
|
||||
},
|
||||
|
||||
|
||||
// TODO: Remove legacy
|
||||
getDistinctFilterData(audiobooks) {
|
||||
var data = {
|
||||
authors: [],
|
||||
genres: [],
|
||||
tags: [],
|
||||
series: [],
|
||||
narrators: [],
|
||||
languages: []
|
||||
}
|
||||
audiobooks.forEach((ab) => {
|
||||
if (ab.book._authorsList.length) {
|
||||
ab.book._authorsList.forEach((author) => {
|
||||
if (author && !data.authors.includes(author)) data.authors.push(author)
|
||||
})
|
||||
}
|
||||
if (ab.book._genres.length) {
|
||||
ab.book._genres.forEach((genre) => {
|
||||
if (genre && !data.genres.includes(genre)) data.genres.push(genre)
|
||||
})
|
||||
}
|
||||
if (ab.tags.length) {
|
||||
ab.tags.forEach((tag) => {
|
||||
if (tag && !data.tags.includes(tag)) data.tags.push(tag)
|
||||
})
|
||||
}
|
||||
if (ab.book._series && !data.series.includes(ab.book._series)) data.series.push(ab.book._series)
|
||||
if (ab.book._narratorsList.length) {
|
||||
ab.book._narratorsList.forEach((narrator) => {
|
||||
if (narrator && !data.narrators.includes(narrator)) data.narrators.push(narrator)
|
||||
})
|
||||
}
|
||||
if (ab.book._language && !data.languages.includes(ab.book._language)) data.languages.push(ab.book._language)
|
||||
})
|
||||
data.authors = naturalSort(data.authors).asc()
|
||||
data.genres = naturalSort(data.genres).asc()
|
||||
data.tags = naturalSort(data.tags).asc()
|
||||
data.series = naturalSort(data.series).asc()
|
||||
data.narrators = naturalSort(data.narrators).asc()
|
||||
data.languages = naturalSort(data.languages).asc()
|
||||
return data
|
||||
},
|
||||
|
||||
getSeriesFromBooks(books, minified = false) {
|
||||
var _series = {}
|
||||
books.forEach((libraryItem) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue