@@ -83,17 +83,20 @@ export default {
}
},
computed: {
- _book() {
- return this.book.book || {}
+ media() {
+ return this.book.media || {}
+ },
+ mediaMetadata() {
+ return this.media.metadata || {}
},
bookTitle() {
- return this._book.title || ''
+ return this.mediaMetadata.title || ''
},
bookAuthor() {
- return this._book.authorFL || ''
+ return (this.mediaMetadata.authors || []).map((au) => au.name).join(', ')
},
bookDuration() {
- return this.$secondsToTimestamp(this.book.duration)
+ return this.$secondsToTimestamp(this.media.duration)
},
isMissing() {
return this.book.isMissing
@@ -102,7 +105,7 @@ export default {
return this.book.isInvalid
},
numTracks() {
- return this.book.numTracks
+ return this.media.tracks.length
},
isStreaming() {
return this.$store.getters['getAudiobookIdStreaming'] === this.book.id
diff --git a/client/layouts/default.vue b/client/layouts/default.vue
index ece8963f..73699763 100644
--- a/client/layouts/default.vue
+++ b/client/layouts/default.vue
@@ -168,7 +168,6 @@ export default {
if (this.$refs.streamContainer) this.$refs.streamContainer.streamError(id)
},
audiobookAdded(audiobook) {
- // this.$store.commit('audiobooks/addUpdate', audiobook)
this.$store.commit('libraries/updateFilterDataWithAudiobook', audiobook)
},
audiobooksAdded(audiobooks) {
@@ -179,7 +178,6 @@ export default {
audiobooksUpdated(audiobooks) {
audiobooks.forEach((ab) => {
this.audiobookUpdated(ab)
- // this.$store.commit('audiobooks/addUpdate', ab)
})
},
libraryAdded(library) {
diff --git a/client/pages/collection/_id.vue b/client/pages/collection/_id.vue
index 31978b49..a01fb615 100644
--- a/client/pages/collection/_id.vue
+++ b/client/pages/collection/_id.vue
@@ -52,9 +52,6 @@ export default {
return redirect('/')
}
store.commit('user/addUpdateCollection', collection)
- collection.books.forEach((book) => {
- store.commit('audiobooks/addUpdate', book)
- })
return {
collectionId: collection.id
}
diff --git a/client/pages/item/_id/edit.vue b/client/pages/item/_id/edit.vue
new file mode 100644
index 00000000..4542f171
--- /dev/null
+++ b/client/pages/item/_id/edit.vue
@@ -0,0 +1,280 @@
+
+
+
diff --git a/client/pages/library/_library/authors/index.vue b/client/pages/library/_library/authors/index.vue
index b9553e33..acf410e8 100644
--- a/client/pages/library/_library/authors/index.vue
+++ b/client/pages/library/_library/authors/index.vue
@@ -7,7 +7,7 @@
-
+
diff --git a/client/pages/library/_library/series/_id.vue b/client/pages/library/_library/series/_id.vue
index ef513b2d..dce9ec01 100644
--- a/client/pages/library/_library/series/_id.vue
+++ b/client/pages/library/_library/series/_id.vue
@@ -18,9 +18,16 @@ export default {
if (!library) {
return redirect('/oops?message=Library not found')
}
+ var series = await app.$axios.$get(`/api/series/${params.id}`).catch((error) => {
+ console.error('Failed', error)
+ return false
+ })
+ if (!series) {
+ return redirect('/oops?message=Series not found')
+ }
return {
- series: app.$decode(params.id),
+ series: series.name,
seriesId: params.id
}
},
diff --git a/client/store/audiobooks.js b/client/store/audiobooks.js
index da412176..6e8a3fef 100644
--- a/client/store/audiobooks.js
+++ b/client/store/audiobooks.js
@@ -73,59 +73,6 @@ export const mutations = {
listener.meth()
})
},
- addUpdate(state, audiobook) {
- if (state.loadedLibraryId && audiobook.libraryId !== state.loadedLibraryId) {
- console.warn('Invalid library', audiobook, 'loaded library', state.loadedLibraryId, '"')
- return
- }
-
- var index = state.audiobooks.findIndex(a => a.id === audiobook.id)
- var origAudiobook = null
- if (index >= 0) {
- origAudiobook = { ...state.audiobooks[index] }
- state.audiobooks.splice(index, 1, audiobook)
- } else {
- state.audiobooks.push(audiobook)
- }
-
- if (audiobook.book) {
- // GENRES
- var newGenres = []
- audiobook.book.genres.forEach((genre) => {
- if (!state.genres.includes(genre)) newGenres.push(genre)
- })
- if (newGenres.length) {
- state.genres = state.genres.concat(newGenres)
- state.genres.sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
- }
-
- // SERIES
- if (audiobook.book.series && !state.series.includes(audiobook.book.series)) {
- state.series.push(audiobook.book.series)
- state.series.sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
- }
- if (origAudiobook && origAudiobook.book && origAudiobook.book.series) {
- var isInAB = state.audiobooks.find(ab => ab.book && ab.book.series === origAudiobook.book.series)
- if (!isInAB) state.series = state.series.filter(series => series !== origAudiobook.book.series)
- }
- }
-
- // TAGS
- var newTags = []
- audiobook.tags.forEach((tag) => {
- if (!state.tags.includes(tag)) newTags.push(tag)
- })
- if (newTags.length) {
- state.tags = state.tags.concat(newTags)
- state.tags.sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
- }
-
- state.listeners.forEach((listener) => {
- if (!listener.audiobookId || listener.audiobookId === audiobook.id) {
- listener.meth()
- }
- })
- },
remove(state, audiobook) {
state.audiobooks = state.audiobooks.filter(a => a.id !== audiobook.id)
diff --git a/server/ApiController.js b/server/ApiController.js
index 9b1a2569..586d2068 100644
--- a/server/ApiController.js
+++ b/server/ApiController.js
@@ -67,7 +67,6 @@ class ApiController {
this.router.post('/libraries/order', LibraryController.reorder.bind(this))
// Legacy
- this.router.get('/libraries/:id/books/all', LibraryController.middleware.bind(this), LibraryController.getBooksForLibrary.bind(this))
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))
@@ -171,6 +170,7 @@ class ApiController {
//
// Series Routes
//
+ this.router.get('/series/:id', SeriesController.middleware.bind(this), SeriesController.findOne.bind(this))
this.router.get('/series/search', SeriesController.search.bind(this))
@@ -230,8 +230,7 @@ class ApiController {
}
async getAuthors(req, res) {
- var authors = this.db.authors.filter(p => p.isAuthor)
- res.json(authors)
+ res.json(this.db.authors)
}
searchAuthors(req, res) {
diff --git a/server/controllers/CollectionController.js b/server/controllers/CollectionController.js
index d4a74d9c..5d14530a 100644
--- a/server/controllers/CollectionController.js
+++ b/server/controllers/CollectionController.js
@@ -11,7 +11,7 @@ class CollectionController {
if (!success) {
return res.status(500).send('Invalid collection data')
}
- var jsonExpanded = newCollection.toJSONExpanded(this.db.audiobooks)
+ var jsonExpanded = newCollection.toJSONExpanded(this.db.libraryItems)
await this.db.insertEntity('collection', newCollection)
this.emitter('collection_added', jsonExpanded)
res.json(jsonExpanded)
@@ -19,7 +19,7 @@ class CollectionController {
findAll(req, res) {
var collections = this.db.collections.filter(c => c.userId === req.user.id)
- var expandedCollections = collections.map(c => c.toJSONExpanded(this.db.audiobooks))
+ var expandedCollections = collections.map(c => c.toJSONExpanded(this.db.libraryItems))
res.json(expandedCollections)
}
@@ -28,7 +28,7 @@ class CollectionController {
if (!collection) {
return res.status(404).send('Collection not found')
}
- res.json(collection.toJSONExpanded(this.db.audiobooks))
+ res.json(collection.toJSONExpanded(this.db.libraryItems))
}
async update(req, res) {
@@ -37,7 +37,7 @@ class CollectionController {
return res.status(404).send('Collection not found')
}
var wasUpdated = collection.update(req.body)
- var jsonExpanded = collection.toJSONExpanded(this.db.audiobooks)
+ var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems)
if (wasUpdated) {
await this.db.updateEntity('collection', collection)
this.emitter('collection_updated', jsonExpanded)
@@ -50,7 +50,7 @@ class CollectionController {
if (!collection) {
return res.status(404).send('Collection not found')
}
- var jsonExpanded = collection.toJSONExpanded(this.db.audiobooks)
+ var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems)
await this.db.removeEntity('collection', collection.id)
this.emitter('collection_removed', jsonExpanded)
res.sendStatus(200)
@@ -61,18 +61,18 @@ class CollectionController {
if (!collection) {
return res.status(404).send('Collection not found')
}
- var audiobook = this.db.audiobooks.find(ab => ab.id === req.body.id)
- if (!audiobook) {
+ var libraryItem = this.db.libraryItems.find(li => li.id === req.body.id)
+ if (!libraryItem) {
return res.status(500).send('Book not found')
}
- if (audiobook.libraryId !== collection.libraryId) {
+ if (libraryItem.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)
+ var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems)
await this.db.updateEntity('collection', collection)
this.emitter('collection_updated', jsonExpanded)
res.json(jsonExpanded)
@@ -87,11 +87,11 @@ class CollectionController {
if (collection.books.includes(req.params.bookId)) {
collection.removeBook(req.params.bookId)
- var jsonExpanded = collection.toJSONExpanded(this.db.audiobooks)
+ var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems)
await this.db.updateEntity('collection', collection)
this.emitter('collection_updated', jsonExpanded)
}
- res.json(collection.toJSONExpanded(this.db.audiobooks))
+ res.json(collection.toJSONExpanded(this.db.libraryItems))
}
// POST: api/collections/:id/batch/add
@@ -113,9 +113,9 @@ class CollectionController {
}
if (hasUpdated) {
await this.db.updateEntity('collection', collection)
- this.emitter('collection_updated', collection.toJSONExpanded(this.db.audiobooks))
+ this.emitter('collection_updated', collection.toJSONExpanded(this.db.libraryItems))
}
- res.json(collection.toJSONExpanded(this.db.audiobooks))
+ res.json(collection.toJSONExpanded(this.db.libraryItems))
}
// POST: api/collections/:id/batch/remove
@@ -137,9 +137,9 @@ class CollectionController {
}
if (hasUpdated) {
await this.db.updateEntity('collection', collection)
- this.emitter('collection_updated', collection.toJSONExpanded(this.db.audiobooks))
+ this.emitter('collection_updated', collection.toJSONExpanded(this.db.libraryItems))
}
- res.json(collection.toJSONExpanded(this.db.audiobooks))
+ res.json(collection.toJSONExpanded(this.db.libraryItems))
}
}
module.exports = new CollectionController()
\ No newline at end of file
diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js
index a701b35c..bc5c754f 100644
--- a/server/controllers/LibraryController.js
+++ b/server/controllers/LibraryController.js
@@ -93,10 +93,8 @@ class LibraryController {
// api/libraries/:id/items
// TODO: Optimize this method, items are iterated through several times but can be combined
getLibraryItems(req, res) {
- var libraryId = req.library.id
var media = req.query.media || 'all'
- var libraryItems = this.db.libraryItems.filter(li => {
- if (li.libraryId !== libraryId) return false
+ var libraryItems = req.libraryItems.filter(li => {
if (media != 'all') return li.mediaType == media
return true
})
@@ -151,85 +149,9 @@ class LibraryController {
res.json(payload)
}
- // api/libraries/:id/books/all
- // TODO: Optimize this method, audiobooks are iterated through several times but can be combined
- getBooksForLibrary(req, res) {
- var libraryId = req.library.id
-
- var audiobooks = this.db.audiobooks.filter(ab => ab.libraryId === libraryId)
- var payload = {
- results: [],
- total: audiobooks.length,
- limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0,
- page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0,
- sortBy: req.query.sort,
- sortDesc: req.query.desc === '1',
- filterBy: req.query.filter,
- minified: req.query.minified === '1',
- collapseseries: req.query.collapseseries === '1'
- }
-
- if (payload.filterBy) {
- audiobooks = libraryHelpers.getFiltered(audiobooks, payload.filterBy, req.user)
- payload.total = audiobooks.length
- }
-
- if (payload.sortBy) {
- var sortKey = payload.sortBy
-
- // Handle server setting sortingIgnorePrefix
- if ((sortKey === 'book.series' || sortKey === 'book.title') && this.db.serverSettings.sortingIgnorePrefix) {
- // Book.js has seriesIgnorePrefix and titleIgnorePrefix getters
- sortKey += 'IgnorePrefix'
- }
-
- var direction = payload.sortDesc ? 'desc' : 'asc'
- audiobooks = naturalSort(audiobooks)[direction]((ab) => {
-
- // Supports dot notation strings i.e. "book.title"
- return sortKey.split('.').reduce((a, b) => a[b], ab)
- })
- }
-
- if (payload.collapseseries) {
- var series = {}
- // Group abs by series
- for (let i = 0; i < audiobooks.length; i++) {
- var ab = audiobooks[i]
- if (ab.book.series) {
- if (!series[ab.book.series]) series[ab.book.series] = []
- series[ab.book.series].push(ab)
- }
- }
-
- // Sort series by volume number and filter out all but the first book in series
- var seriesBooksToKeep = Object.values(series).map((_series) => {
- var sorted = naturalSort(_series).asc(_ab => _ab.book.volumeNumber)
- return sorted[0].id
- })
- // Add "booksInSeries" field to audiobook payload
- audiobooks = audiobooks.filter(ab => !ab.book.series || seriesBooksToKeep.includes(ab.id)).map(ab => {
- var abJson = payload.minified ? ab.toJSONMinified() : ab.toJSONExpanded()
- if (ab.book.series) abJson.booksInSeries = series[ab.book.series].length
- return abJson
- })
- payload.total = audiobooks.length
- } else {
- audiobooks = audiobooks.map(ab => payload.minified ? ab.toJSONMinified() : ab.toJSONExpanded())
- }
-
- if (payload.limit) {
- var startIndex = payload.page * payload.limit
- audiobooks = audiobooks.slice(startIndex, startIndex + payload.limit)
- }
- payload.results = audiobooks
- res.json(payload)
- }
-
// api/libraries/:id/series
async getAllSeriesForLibrary(req, res) {
- var audiobooks = this.db.audiobooks.filter(ab => ab.libraryId === req.library.id)
-
+ var libraryItems = req.libraryItems
var payload = {
results: [],
total: 0,
@@ -241,7 +163,7 @@ class LibraryController {
minified: req.query.minified === '1'
}
- var series = libraryHelpers.getSeriesFromBooks(audiobooks, payload.minified)
+ var series = libraryHelpers.getSeriesFromBooks(libraryItems, payload.minified)
var sortingIgnorePrefix = this.db.serverSettings.sortingIgnorePrefix
series = sort(series).asc(s => {
@@ -263,24 +185,23 @@ class LibraryController {
// GET: api/libraries/:id/series/:series
async getSeriesForLibrary(req, res) {
- var series = libraryHelpers.decode(req.params.series)
- if (!series) {
+ if (!req.params.series) {
return res.status(403).send('Invalid series')
}
- var audiobooks = this.db.audiobooks.filter(ab => ab.libraryId === req.library.id && ab.book.series === series)
- if (!audiobooks.length) {
+ var libraryItems = this.db.libraryItems.filter(li => li.libraryId === req.library.id && li.book.series === req.params.series)
+ if (!libraryItems.length) {
return res.status(404).send('Series not found')
}
- var sortedBooks = libraryHelpers.sortSeriesBooks(audiobooks, false)
+ var sortedBooks = libraryHelpers.sortSeriesBooks(libraryItems, false)
res.json({
results: sortedBooks,
- total: audiobooks.length
+ total: libraryItems.length
})
}
- // api/libraries/:id/series
+ // api/libraries/:id/collections
async getCollectionsForLibrary(req, res) {
- var audiobooks = this.db.audiobooks.filter(ab => ab.libraryId === req.library.id)
+ var libraryItems = req.libraryItems
var payload = {
results: [],
@@ -293,7 +214,7 @@ class LibraryController {
minified: req.query.minified === '1'
}
- var collections = this.db.collections.filter(c => c.libraryId === req.library.id).map(c => c.toJSONExpanded(audiobooks, payload.minified))
+ var collections = this.db.collections.filter(c => c.libraryId === req.library.id).map(c => c.toJSONExpanded(libraryItems, payload.minified))
payload.total = collections.length
if (payload.limit) {
@@ -521,19 +442,19 @@ class LibraryController {
}
async getAuthors(req, res) {
- var audiobooksInLibrary = this.db.audiobooks.filter(ab => ab.libraryId === req.library.id)
+ var libraryItems = req.libraryItems
var authors = {}
- audiobooksInLibrary.forEach((ab) => {
- if (ab.book._authorsList.length) {
- ab.book._authorsList.forEach((author) => {
- if (!author) return
- if (!authors[author]) {
- authors[author] = {
- name: author,
- numBooks: 1
+ libraryItems.forEach((li) => {
+ if (li.media.metadata.authors && li.media.metadata.authors.length) {
+ li.media.metadata.authors.forEach((au) => {
+ if (!authors[au.id]) {
+ var _author = this.db.authors.find(_au => _au.id === au.id)
+ if (_author) {
+ authors[au.id] = _author.toJSON()
+ authors[au.id].numBooks = 1
}
} else {
- authors[author].numBooks++
+ authors[au.id].numBooks++
}
})
}
diff --git a/server/controllers/SeriesController.js b/server/controllers/SeriesController.js
index 734cc6f6..799075fa 100644
--- a/server/controllers/SeriesController.js
+++ b/server/controllers/SeriesController.js
@@ -3,6 +3,10 @@ const Logger = require('../Logger')
class SeriesController {
constructor() { }
+ async findOne(req, res) {
+ return res.json(req.series)
+ }
+
async search(req, res) {
var q = (req.query.q || '').toLowerCase()
if (!q) return res.json([])
@@ -11,5 +15,21 @@ class SeriesController {
series = series.slice(0, limit)
res.json(series)
}
+
+ middleware(req, res, next) {
+ var series = this.db.series.find(se => se.id === req.params.id)
+ if (!series) return res.sendStatus(404)
+
+ if (req.method == 'DELETE' && !req.user.canDelete) {
+ Logger.warn(`[SeriesController] User attempted to delete without permission`, req.user)
+ return res.sendStatus(403)
+ } else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) {
+ Logger.warn('[SeriesController] User attempted to update without permission', req.user)
+ return res.sendStatus(403)
+ }
+
+ req.series = series
+ next()
+ }
}
module.exports = new SeriesController()
\ No newline at end of file
diff --git a/server/objects/UserCollection.js b/server/objects/UserCollection.js
index 780b2a42..b3151f78 100644
--- a/server/objects/UserCollection.js
+++ b/server/objects/UserCollection.js
@@ -37,10 +37,10 @@ class UserCollection {
}
}
- toJSONExpanded(audiobooks, minifiedBooks = false) {
+ toJSONExpanded(libraryItems, minifiedBooks = false) {
var json = this.toJSON()
json.books = json.books.map(bookId => {
- var _ab = audiobooks.find(ab => ab.id === bookId)
+ var _ab = libraryItems.find(li => li.id === bookId)
return _ab ? minifiedBooks ? _ab.toJSONMinified() : _ab.toJSONExpanded() : null
}).filter(b => !!b)
return json
diff --git a/server/objects/metadata/BookMetadata.js b/server/objects/metadata/BookMetadata.js
index 5ca6c372..55168317 100644
--- a/server/objects/metadata/BookMetadata.js
+++ b/server/objects/metadata/BookMetadata.js
@@ -99,11 +99,11 @@ class BookMetadata {
return this.title + '&' + this.authorName
}
- hasAuthor(authorName) {
- return !!this.authors.find(au => au.name == authorName)
+ hasAuthor(id) {
+ return !!this.authors.find(au => au.id == id)
}
- hasSeries(seriesName) {
- return !!this.series.find(se => se.name == seriesName)
+ hasSeries(seriesId) {
+ return !!this.series.find(se => se.id == seriesId)
}
hasNarrator(narratorName) {
return this.narrators.includes(narratorName)
diff --git a/server/utils/libraryHelpers.js b/server/utils/libraryHelpers.js
index 52f6de50..bbee20b9 100644
--- a/server/utils/libraryHelpers.js
+++ b/server/utils/libraryHelpers.js
@@ -20,7 +20,9 @@ module.exports = {
else if (group === 'tags') filtered = filtered.filter(li => li.media.tags.includes(filter))
else if (group === 'series') {
if (filter === 'No Series') filtered = filtered.filter(li => li.media.metadata && !li.media.metadata.series.length)
- else filtered = filtered.filter(li => li.media.metadata && li.media.metadata.hasSeries(filter))
+ else {
+ filtered = filtered.filter(li => li.media.metadata && li.media.metadata.hasSeries(filter))
+ }
}
else if (group === 'authors') filtered = filtered.filter(li => li.media.metadata && li.media.metadata.hasAuthor(filter))
else if (group === 'narrators') filtered = filtered.filter(li => li.media.metadata && li.media.metadata.hasNarrator(filter))
@@ -177,23 +179,26 @@ module.exports = {
getSeriesFromBooks(books, minified = false) {
var _series = {}
- books.forEach((audiobook) => {
- if (audiobook.book.series) {
- var abJson = minified ? audiobook.toJSONMinified() : audiobook.toJSONExpanded()
- if (!_series[audiobook.book.series]) {
- _series[audiobook.book.series] = {
- id: audiobook.book.series,
- name: audiobook.book.series,
- type: 'series',
- books: [abJson]
+ books.forEach((libraryItem) => {
+ if (libraryItem.media.metadata.series && libraryItem.media.metadata.series.length) {
+ libraryItem.media.metadata.series.forEach((series) => {
+ var abJson = minified ? libraryItem.toJSONMinified() : libraryItem.toJSONExpanded()
+ abJson.sequence = series.sequence
+ if (!_series[series.id]) {
+ _series[series.id] = {
+ id: series.id,
+ name: series.name,
+ type: 'series',
+ books: [abJson]
+ }
+ } else {
+ _series[series.id].books.push(abJson)
}
- } else {
- _series[audiobook.book.series].books.push(abJson)
- }
+ })
}
})
return Object.values(_series).map((series) => {
- series.books = naturalSort(series.books).asc(ab => ab.book.volumeNumber)
+ series.books = naturalSort(series.books).asc(li => li.sequence)
return series
})
},
@@ -221,10 +226,15 @@ module.exports = {
}).filter((series) => series.books.some((book) => book.userAudiobook && book.userAudiobook.isRead))
},
- sortSeriesBooks(books, minified = false) {
- return naturalSort(books).asc(ab => ab.book.volumeNumber).map(ab => {
- if (minified) return ab.toJSONMinified()
- return ab.toJSONExpanded()
+ sortSeriesBooks(books, seriesId, minified = false) {
+ return naturalSort(books).asc(li => {
+ if (!li.media.metadata.series) return null
+ var series = li.media.metadata.series.find(se => se.id === seriesId)
+ if (!series) return null
+ return series.sequence
+ }).map(li => {
+ if (minified) return li.toJSONMinified()
+ return li.toJSONExpanded()
})
},