mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-12-31 16:19:39 +00:00
Add: Experimental collections add/remove & db #151
This commit is contained in:
parent
3d35b7dc3d
commit
bf0893d759
27 changed files with 784 additions and 62 deletions
|
|
@ -10,6 +10,7 @@ const BookFinder = require('./BookFinder')
|
|||
|
||||
const Library = require('./objects/Library')
|
||||
const User = require('./objects/User')
|
||||
const UserCollection = require('./objects/UserCollection')
|
||||
|
||||
class ApiController {
|
||||
constructor(MetadataPath, db, scanner, auth, streamManager, rssFeeds, downloadManager, coverController, backupManager, watcher, emitter, clientEmitter) {
|
||||
|
|
@ -75,6 +76,14 @@ class ApiController {
|
|||
this.router.patch('/user/:id', this.updateUser.bind(this))
|
||||
this.router.delete('/user/:id', this.deleteUser.bind(this))
|
||||
|
||||
this.router.get('/collections', this.getUserCollections.bind(this))
|
||||
this.router.get('/collection/:id', this.getUserCollection.bind(this))
|
||||
this.router.post('/collection', this.createUserCollection.bind(this))
|
||||
this.router.post('/collection/:id/book', this.addBookToUserCollection.bind(this))
|
||||
this.router.delete('/collection/:id/book/:bookId', this.removeBookFromUserCollection.bind(this))
|
||||
this.router.patch('/collection/:id', this.updateUserCollection.bind(this))
|
||||
this.router.delete('/collection/:id', this.deleteUserCollection.bind(this))
|
||||
|
||||
this.router.patch('/serverSettings', this.updateServerSettings.bind(this))
|
||||
|
||||
this.router.delete('/backup/:id', this.deleteBackup.bind(this))
|
||||
|
|
@ -82,8 +91,6 @@ class ApiController {
|
|||
|
||||
this.router.post('/authorize', this.authorize.bind(this))
|
||||
|
||||
this.router.get('/genres', this.getGenres.bind(this))
|
||||
|
||||
this.router.post('/feed', this.openRssFeed.bind(this))
|
||||
|
||||
this.router.get('/download/:id', this.download.bind(this))
|
||||
|
|
@ -368,6 +375,15 @@ class ApiController {
|
|||
}
|
||||
}
|
||||
|
||||
// remove book from collections
|
||||
var collectionsWithBook = this.db.collections.filter(c => c.books.includes(audiobook.id))
|
||||
for (let i = 0; i < collectionsWithBook.length; i++) {
|
||||
var collection = collectionsWithBook[i]
|
||||
collection.removeBook(audiobook.id)
|
||||
await this.db.updateEntity('collection', collection)
|
||||
this.clientEmitter(collection.userId, 'collection_updated', collection.toJSONExpanded(this.db.audiobooks))
|
||||
}
|
||||
|
||||
var audiobookJSON = audiobook.toJSONMinified()
|
||||
await this.db.removeEntity('audiobook', audiobook.id)
|
||||
this.emitter('audiobook_removed', audiobookJSON)
|
||||
|
|
@ -761,6 +777,13 @@ class ApiController {
|
|||
})
|
||||
}
|
||||
|
||||
// delete user collections
|
||||
var userCollections = this.db.collections.filter(c => c.userId === user.id)
|
||||
var collectionsToRemove = userCollections.map(uc => uc.id)
|
||||
for (let i = 0; i < collectionsToRemove.length; i++) {
|
||||
await this.db.removeEntity('collection', collectionsToRemove[i])
|
||||
}
|
||||
|
||||
// Todo: check if user is logged in and cancel streams
|
||||
|
||||
var userJson = user.toJSONForBrowser()
|
||||
|
|
@ -771,6 +794,95 @@ class ApiController {
|
|||
})
|
||||
}
|
||||
|
||||
async getUserCollections(req, res) {
|
||||
var collections = this.db.collections.filter(c => c.userId === req.user.id)
|
||||
var expandedCollections = collections.map(c => c.toJSONExpanded(this.db.audiobooks))
|
||||
res.json(expandedCollections)
|
||||
}
|
||||
|
||||
async getUserCollection(req, res) {
|
||||
var collection = this.db.collections.find(c => c.id === req.params.id)
|
||||
if (!collection) {
|
||||
return res.status(404).send('Collection not found')
|
||||
}
|
||||
res.json(collection.toJSONExpanded(this.db.audiobooks))
|
||||
}
|
||||
|
||||
async createUserCollection(req, res) {
|
||||
var newCollection = new UserCollection()
|
||||
req.body.userId = req.user.id
|
||||
var success = newCollection.setData(req.body)
|
||||
if (!success) {
|
||||
return res.status(500).send('Invalid collection data')
|
||||
}
|
||||
var jsonExpanded = newCollection.toJSONExpanded(this.db.audiobooks)
|
||||
await this.db.insertEntity('collection', newCollection)
|
||||
this.clientEmitter(req.user.id, 'collection_added', jsonExpanded)
|
||||
res.json(jsonExpanded)
|
||||
}
|
||||
|
||||
async addBookToUserCollection(req, res) {
|
||||
var collection = this.db.collections.find(c => c.id === req.params.id)
|
||||
if (!collection) {
|
||||
return res.status(404).send('Collection not found')
|
||||
}
|
||||
var audiobook = this.db.audiobooks.find(ab => ab.id === req.body.id)
|
||||
if (!audiobook) {
|
||||
return res.status(500).send('Book not found')
|
||||
}
|
||||
if (audiobook.libraryId !== collection.libraryId) {
|
||||
return res.status(500).send('Book in different library')
|
||||
}
|
||||
if (collection.books.includes(req.body.id)) {
|
||||
return res.status(500).send('Book already in collection')
|
||||
}
|
||||
collection.addBook(req.body.id)
|
||||
var jsonExpanded = collection.toJSONExpanded(this.db.audiobooks)
|
||||
await this.db.updateEntity('collection', collection)
|
||||
this.clientEmitter(req.user.id, 'collection_updated', jsonExpanded)
|
||||
res.json(jsonExpanded)
|
||||
}
|
||||
|
||||
async removeBookFromUserCollection(req, res) {
|
||||
var collection = this.db.collections.find(c => c.id === req.params.id)
|
||||
if (!collection) {
|
||||
return res.status(404).send('Collection not found')
|
||||
}
|
||||
|
||||
if (collection.books.includes(req.params.bookId)) {
|
||||
collection.removeBook(req.params.bookId)
|
||||
var jsonExpanded = collection.toJSONExpanded(this.db.audiobooks)
|
||||
await this.db.updateEntity('collection', collection)
|
||||
this.clientEmitter(req.user.id, 'collection_updated', jsonExpanded)
|
||||
}
|
||||
res.json(collection.toJSONExpanded(this.db.audiobooks))
|
||||
}
|
||||
|
||||
async updateUserCollection(req, res) {
|
||||
var collection = this.db.collections.find(c => c.id === req.params.id)
|
||||
if (!collection) {
|
||||
return res.status(404).send('Collection not found')
|
||||
}
|
||||
var wasUpdated = collection.update(req.body)
|
||||
var jsonExpanded = collection.toJSONExpanded(this.db.audiobooks)
|
||||
if (wasUpdated) {
|
||||
await this.db.updateEntity('collection', collection)
|
||||
this.clientEmitter(req.user.id, 'collection_updated', jsonExpanded)
|
||||
}
|
||||
res.json(jsonExpanded)
|
||||
}
|
||||
|
||||
async deleteUserCollection(req, res) {
|
||||
var collection = this.db.collections.find(c => c.id === req.params.id)
|
||||
if (!collection) {
|
||||
return res.status(404).send('Collection not found')
|
||||
}
|
||||
var jsonExpanded = collection.toJSONExpanded(this.db.audiobooks)
|
||||
await this.db.removeEntity('collection', collection.id)
|
||||
this.clientEmitter(req.user.id, 'collection_removed', jsonExpanded)
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
async updateServerSettings(req, res) {
|
||||
if (!req.user.isRoot) {
|
||||
Logger.error('User other than root attempting to update server settings', req.user)
|
||||
|
|
@ -846,12 +958,6 @@ class ApiController {
|
|||
})
|
||||
}
|
||||
|
||||
getGenres(req, res) {
|
||||
res.json({
|
||||
genres: this.db.getGenres()
|
||||
})
|
||||
}
|
||||
|
||||
async getDirectories(dir, relpath, excludedDirs, level = 0) {
|
||||
try {
|
||||
var paths = await fs.readdir(dir)
|
||||
|
|
|
|||
|
|
@ -311,11 +311,13 @@ class BackupManager {
|
|||
var librariesDbDir = Path.join(configPath, 'libraries')
|
||||
var settingsDbDir = Path.join(configPath, 'settings')
|
||||
var usersDbDir = Path.join(configPath, 'users')
|
||||
var collectionsDbDir = Path.join(configPath, 'collections')
|
||||
|
||||
archive.directory(audiobooksDbDir, 'config/audiobooks')
|
||||
archive.directory(librariesDbDir, 'config/libraries')
|
||||
archive.directory(settingsDbDir, 'config/settings')
|
||||
archive.directory(usersDbDir, 'config/users')
|
||||
archive.directory(collectionsDbDir, 'config/collections')
|
||||
|
||||
if (metadataBooksPath) {
|
||||
Logger.debug(`[BackupManager] Backing up Metadata Books "${metadataBooksPath}"`)
|
||||
|
|
|
|||
37
server/Db.js
37
server/Db.js
|
|
@ -5,6 +5,7 @@ const jwt = require('jsonwebtoken')
|
|||
const Logger = require('./Logger')
|
||||
const Audiobook = require('./objects/Audiobook')
|
||||
const User = require('./objects/User')
|
||||
const UserCollection = require('./objects/UserCollection')
|
||||
const Library = require('./objects/Library')
|
||||
const ServerSettings = require('./objects/ServerSettings')
|
||||
|
||||
|
|
@ -16,16 +17,19 @@ class Db {
|
|||
this.UsersPath = Path.join(ConfigPath, 'users')
|
||||
this.LibrariesPath = Path.join(ConfigPath, 'libraries')
|
||||
this.SettingsPath = Path.join(ConfigPath, 'settings')
|
||||
this.CollectionsPath = Path.join(ConfigPath, 'collections')
|
||||
|
||||
this.audiobooksDb = new njodb.Database(this.AudiobooksPath)
|
||||
this.usersDb = new njodb.Database(this.UsersPath)
|
||||
this.librariesDb = new njodb.Database(this.LibrariesPath, { datastores: 2 })
|
||||
this.settingsDb = new njodb.Database(this.SettingsPath, { datastores: 2 })
|
||||
this.collectionsDb = new njodb.Database(this.CollectionsPath, { datastores: 2 })
|
||||
|
||||
this.users = []
|
||||
this.libraries = []
|
||||
this.audiobooks = []
|
||||
this.settings = []
|
||||
this.collections = []
|
||||
|
||||
this.serverSettings = null
|
||||
}
|
||||
|
|
@ -34,14 +38,18 @@ class Db {
|
|||
if (entityName === 'user') return this.usersDb
|
||||
else if (entityName === 'audiobook') return this.audiobooksDb
|
||||
else if (entityName === 'library') return this.librariesDb
|
||||
return this.settingsDb
|
||||
else if (entityName === 'settings') return this.settingsDb
|
||||
else if (entityName === 'collection') return this.collectionsDb
|
||||
return null
|
||||
}
|
||||
|
||||
getEntityArrayKey(entityName) {
|
||||
if (entityName === 'user') return 'users'
|
||||
else if (entityName === 'audiobook') return 'audiobooks'
|
||||
else if (entityName === 'library') return 'libraries'
|
||||
return 'settings'
|
||||
else if (entityName === 'settings') return 'settings'
|
||||
else if (entityName === 'collection') return 'collections'
|
||||
return null
|
||||
}
|
||||
|
||||
getDefaultUser(token) {
|
||||
|
|
@ -76,6 +84,7 @@ class Db {
|
|||
this.usersDb = new njodb.Database(this.UsersPath)
|
||||
this.librariesDb = new njodb.Database(this.LibrariesPath, { datastores: 2 })
|
||||
this.settingsDb = new njodb.Database(this.SettingsPath, { datastores: 2 })
|
||||
this.collectionsDb = new njodb.Database(this.CollectionsPath, { datastores: 2 })
|
||||
return this.init()
|
||||
}
|
||||
|
||||
|
|
@ -125,7 +134,11 @@ class Db {
|
|||
}
|
||||
}
|
||||
})
|
||||
await Promise.all([p1, p2, p3, p4])
|
||||
var p5 = this.collectionsDb.select(() => true).then((results) => {
|
||||
this.collections = results.data.map(l => new UserCollection(l))
|
||||
Logger.info(`[DB] ${this.collections.length} Collections Loaded`)
|
||||
})
|
||||
await Promise.all([p1, p2, p3, p4, p5])
|
||||
}
|
||||
|
||||
updateAudiobook(audiobook) {
|
||||
|
|
@ -286,23 +299,5 @@ class Db {
|
|||
return false
|
||||
})
|
||||
}
|
||||
|
||||
getGenres() {
|
||||
var allGenres = []
|
||||
this.db.audiobooks.forEach((audiobook) => {
|
||||
allGenres = allGenres.concat(audiobook.genres)
|
||||
})
|
||||
allGenres = [...new Set(allGenres)] // Removes duplicates
|
||||
return allGenres
|
||||
}
|
||||
|
||||
getTags() {
|
||||
var allTags = []
|
||||
this.db.audiobooks.forEach((audiobook) => {
|
||||
allTags = allTags.concat(audiobook.tags)
|
||||
})
|
||||
allTags = [...new Set(allTags)] // Removes duplicates
|
||||
return allTags
|
||||
}
|
||||
}
|
||||
module.exports = Db
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ const { secondsToTimestamp } = require('../utils/fileUtils')
|
|||
const { writeConcatFile } = require('../utils/ffmpegHelpers')
|
||||
const hlsPlaylistGenerator = require('../utils/hlsPlaylistGenerator')
|
||||
|
||||
// const UserListeningSession = require('./UserListeningSession')
|
||||
|
||||
class Stream extends EventEmitter {
|
||||
constructor(streamPath, client, audiobook) {
|
||||
super()
|
||||
|
|
@ -32,6 +34,9 @@ class Stream extends EventEmitter {
|
|||
this.furthestSegmentCreated = 0
|
||||
this.clientCurrentTime = 0
|
||||
|
||||
// this.listeningSession = new UserListeningSession()
|
||||
// this.listeningSession.setData(audiobook, client.user)
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
|
|
|
|||
88
server/objects/UserCollection.js
Normal file
88
server/objects/UserCollection.js
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
const Logger = require('../Logger')
|
||||
|
||||
class UserCollection {
|
||||
constructor(collection) {
|
||||
this.id = null
|
||||
this.libraryId = null
|
||||
this.userId = null
|
||||
|
||||
this.name = null
|
||||
this.description = null
|
||||
|
||||
this.cover = null
|
||||
this.coverFullPath = null
|
||||
this.books = []
|
||||
|
||||
this.lastUpdate = null
|
||||
this.createdAt = null
|
||||
|
||||
if (collection) {
|
||||
this.construct(collection)
|
||||
}
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
libraryId: this.libraryId,
|
||||
userId: this.userId,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
cover: this.cover,
|
||||
coverFullPath: this.coverFullPath,
|
||||
books: [...this.books],
|
||||
lastUpdate: this.lastUpdate,
|
||||
createdAt: this.createdAt
|
||||
}
|
||||
}
|
||||
|
||||
toJSONExpanded(audiobooks) {
|
||||
var json = this.toJSON()
|
||||
json.books = json.books.map(bookId => {
|
||||
var _ab = audiobooks.find(ab => ab.id === bookId)
|
||||
return _ab ? _ab.toJSON() : null
|
||||
}).filter(b => !!b)
|
||||
return json
|
||||
}
|
||||
|
||||
construct(collection) {
|
||||
this.id = collection.id
|
||||
this.libraryId = collection.libraryId
|
||||
this.userId = collection.userId
|
||||
this.name = collection.name
|
||||
this.description = collection.description || null
|
||||
this.cover = collection.cover || null
|
||||
this.coverFullPath = collection.coverFullPath || null
|
||||
this.books = collection.books ? [...collection.books] : []
|
||||
this.lastUpdate = collection.lastUpdate || null
|
||||
this.createdAt = collection.createdAt || null
|
||||
}
|
||||
|
||||
setData(data) {
|
||||
if (!data.userId || !data.libraryId || !data.name) {
|
||||
return false
|
||||
}
|
||||
this.id = (Math.trunc(Math.random() * 1000) + Date.now()).toString(36)
|
||||
this.userId = data.userId
|
||||
this.libraryId = data.libraryId
|
||||
this.name = data.name
|
||||
this.description = data.description || null
|
||||
this.cover = data.cover || null
|
||||
this.coverFullPath = data.coverFullPath || null
|
||||
this.books = data.books ? [...data.books] : []
|
||||
this.lastUpdate = Date.now()
|
||||
this.createdAt = Date.now()
|
||||
return true
|
||||
}
|
||||
|
||||
addBook(bookId) {
|
||||
this.books.push(bookId)
|
||||
this.lastUpdate = Date.now()
|
||||
}
|
||||
|
||||
removeBook(bookId) {
|
||||
this.books = this.books.filter(bid => bid !== bookId)
|
||||
this.lastUpdate = Date.now()
|
||||
}
|
||||
}
|
||||
module.exports = UserCollection
|
||||
57
server/objects/UserListeningSession.js
Normal file
57
server/objects/UserListeningSession.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
const Logger = require('../Logger')
|
||||
|
||||
class UserListeningSession {
|
||||
constructor(session) {
|
||||
this.userId = null
|
||||
this.audiobookId = null
|
||||
this.audiobookTitle = null
|
||||
this.audiobookAuthor = null
|
||||
|
||||
this.timeListening = null
|
||||
this.lastUpdate = null
|
||||
this.startedAt = null
|
||||
this.finishedAt = null
|
||||
|
||||
if (session) {
|
||||
this.construct(session)
|
||||
}
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
userId: this.userId,
|
||||
audiobookId: this.audiobookId,
|
||||
audiobookTitle: this.audiobookTitle,
|
||||
audiobookAuthor: this.audiobookAuthor,
|
||||
timeListening: this.timeListening,
|
||||
lastUpdate: this.lastUpdate,
|
||||
startedAt: this.startedAt,
|
||||
finishedAt: this.finishedAt
|
||||
}
|
||||
}
|
||||
|
||||
construct(session) {
|
||||
this.userId = session.userId
|
||||
this.audiobookId = session.audiobookId
|
||||
this.audiobookTitle = session.audiobookTitle
|
||||
this.audiobookAuthor = session.audiobookAuthor
|
||||
|
||||
this.timeListening = session.timeListening || null
|
||||
this.lastUpdate = session.lastUpdate || null
|
||||
this.startedAt = session.startedAt
|
||||
this.finishedAt = session.finishedAt || null
|
||||
}
|
||||
|
||||
setData(audiobook, user) {
|
||||
this.userId = user.id
|
||||
this.audiobookId = audiobook.id
|
||||
this.audiobookTitle = audiobook.title || ''
|
||||
this.audiobookAuthor = audiobook.author || ''
|
||||
|
||||
this.timeListening = 0
|
||||
this.lastUpdate = Date.now()
|
||||
this.startedAt = Date.now()
|
||||
this.finishedAt = null
|
||||
}
|
||||
}
|
||||
module.exports = UserListeningSession
|
||||
Loading…
Add table
Add a link
Reference in a new issue