From 2d19208340b4cf44f3924d4ac5066f3de2ce2d47 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 12 Mar 2022 18:50:31 -0600 Subject: [PATCH] New model updates for series, collections, authors routes --- .../components/app/BookShelfCategorized.vue | 56 ++-- client/components/app/LazyBookshelf.vue | 2 +- client/components/cards/AuthorCard.vue | 3 + client/components/cards/LazySeriesCard.vue | 10 +- client/components/covers/CollectionCover.vue | 4 +- .../tables/collection/BookTableRow.vue | 21 +- client/layouts/default.vue | 2 - client/pages/collection/_id.vue | 3 - client/pages/item/_id/edit.vue | 280 ++++++++++++++++++ .../pages/library/_library/authors/index.vue | 2 +- client/pages/library/_library/series/_id.vue | 9 +- client/store/audiobooks.js | 53 ---- server/ApiController.js | 5 +- server/controllers/CollectionController.js | 30 +- server/controllers/LibraryController.js | 121 ++------ server/controllers/SeriesController.js | 20 ++ server/objects/UserCollection.js | 4 +- server/objects/metadata/BookMetadata.js | 8 +- server/utils/libraryHelpers.js | 46 +-- 19 files changed, 432 insertions(+), 247 deletions(-) create mode 100644 client/pages/item/_id/edit.vue diff --git a/client/components/app/BookShelfCategorized.vue b/client/components/app/BookShelfCategorized.vue index 17e1327f..24c4d211 100644 --- a/client/components/app/BookShelfCategorized.vue +++ b/client/components/app/BookShelfCategorized.vue @@ -155,72 +155,72 @@ export default { scan() { this.$root.socket.emit('scan', this.$store.state.libraries.currentLibraryId) }, - audiobookAdded(audiobook) { - console.log('Audiobook added', audiobook) - // TODO: Check if audiobook would be on this shelf + libraryItemAdded(libraryItem) { + console.log('libraryItem added', libraryItem) + // TODO: Check if libraryItem would be on this shelf if (!this.search) { this.fetchCategories() } }, - audiobookUpdated(audiobook) { - console.log('Audiobook updated', audiobook) + libraryItemUpdated(libraryItem) { + console.log('libraryItem updated', libraryItem) this.shelves.forEach((shelf) => { if (shelf.type === 'books') { shelf.entities = shelf.entities.map((ent) => { - if (ent.id === audiobook.id) { - return audiobook + if (ent.id === libraryItem.id) { + return libraryItem } return ent }) } else if (shelf.type === 'series') { shelf.entities.forEach((ent) => { ent.books = ent.books.map((book) => { - if (book.id === audiobook.id) return audiobook + if (book.id === libraryItem.id) return libraryItem return book }) }) } }) }, - removeBookFromShelf(audiobook) { + removeBookFromShelf(libraryItem) { this.shelves.forEach((shelf) => { if (shelf.type === 'books') { shelf.entities = shelf.entities.filter((ent) => { - return ent.id !== audiobook.id + return ent.id !== libraryItem.id }) } else if (shelf.type === 'series') { shelf.entities.forEach((ent) => { ent.books = ent.books.filter((book) => { - return book.id !== audiobook.id + return book.id !== libraryItem.id }) }) } }) }, - audiobookRemoved(audiobook) { - this.removeBookFromShelf(audiobook) + libraryItemRemoved(libraryItem) { + this.removeBookFromShelf(libraryItem) }, - audiobooksAdded(audiobooks) { - console.log('audiobooks added', audiobooks) + libraryItemsAdded(libraryItems) { + console.log('libraryItems added', libraryItems) // TODO: Check if audiobook would be on this shelf if (!this.search) { this.fetchCategories() } }, - audiobooksUpdated(audiobooks) { - audiobooks.forEach((ab) => { - this.audiobookUpdated(ab) + libraryItemsUpdated(items) { + items.forEach((li) => { + this.libraryItemUpdated(li) }) }, initListeners() { this.$store.commit('user/addSettingsListener', { id: 'bookshelf', meth: this.settingsUpdated }) if (this.$root.socket) { - this.$root.socket.on('audiobook_updated', this.audiobookUpdated) - this.$root.socket.on('audiobook_added', this.audiobookAdded) - this.$root.socket.on('audiobook_removed', this.audiobookRemoved) - this.$root.socket.on('audiobooks_updated', this.audiobooksUpdated) - this.$root.socket.on('audiobooks_added', this.audiobooksAdded) + this.$root.socket.on('item_updated', this.libraryItemUpdated) + this.$root.socket.on('item_added', this.libraryItemAdded) + this.$root.socket.on('item_removed', this.libraryItemRemoved) + this.$root.socket.on('items_updated', this.libraryItemsUpdated) + this.$root.socket.on('items_added', this.libraryItemsAdded) } else { console.error('Error socket not initialized') } @@ -229,11 +229,11 @@ export default { this.$store.commit('user/removeSettingsListener', 'bookshelf') if (this.$root.socket) { - this.$root.socket.off('audiobook_updated', this.audiobookUpdated) - this.$root.socket.off('audiobook_added', this.audiobookAdded) - this.$root.socket.off('audiobook_removed', this.audiobookRemoved) - this.$root.socket.off('audiobooks_updated', this.audiobooksUpdated) - this.$root.socket.off('audiobooks_added', this.audiobooksAdded) + this.$root.socket.off('item_updated', this.libraryItemUpdated) + this.$root.socket.off('item_added', this.libraryItemAdded) + this.$root.socket.off('item_removed', this.libraryItemRemoved) + this.$root.socket.off('items_updated', this.libraryItemsUpdated) + this.$root.socket.off('items_added', this.libraryItemsAdded) } else { console.error('Error socket not initialized') } diff --git a/client/components/app/LazyBookshelf.vue b/client/components/app/LazyBookshelf.vue index 2985ffc3..4a91a107 100644 --- a/client/components/app/LazyBookshelf.vue +++ b/client/components/app/LazyBookshelf.vue @@ -374,7 +374,7 @@ export default { let searchParams = new URLSearchParams() if (this.page === 'series-books') { - searchParams.set('filter', `series.${this.seriesId}`) + searchParams.set('filter', `series.${this.$encode(this.seriesId)}`) searchParams.set('sort', 'book.volumeNumber') searchParams.set('desc', 0) } else { diff --git a/client/components/cards/AuthorCard.vue b/client/components/cards/AuthorCard.vue index bfe65b1e..fddb9b55 100644 --- a/client/components/cards/AuthorCard.vue +++ b/client/components/cards/AuthorCard.vue @@ -48,6 +48,9 @@ export default { _author() { return this.author || {} }, + authorId() { + return this._author.id + }, name() { return this._author.name || '' }, diff --git a/client/components/cards/LazySeriesCard.vue b/client/components/cards/LazySeriesCard.vue index 75293122..3d545086 100644 --- a/client/components/cards/LazySeriesCard.vue +++ b/client/components/cards/LazySeriesCard.vue @@ -51,6 +51,9 @@ export default { if (this.bookCoverAspectRatio === 1) return this.width / (120 * 1.6 * 2) return this.width / 240 }, + seriesId() { + return this.series ? this.series.id : '' + }, title() { return this.series ? this.series.name : '' }, @@ -64,13 +67,10 @@ export default { return this.store.state.libraries.currentLibraryId }, seriesBooksRoute() { - return `/library/${this.currentLibraryId}/series/${this.$encode(this.title)}` - }, - seriesId() { - return this.series ? this.$encode(this.title) : null + return `/library/${this.currentLibraryId}/series/${this.seriesId}` }, hasValidCovers() { - var validCovers = this.books.map((bookItem) => bookItem.book.cover) + var validCovers = this.books.map((bookItem) => bookItem.media.coverPath) return !!validCovers.length } }, diff --git a/client/components/covers/CollectionCover.vue b/client/components/covers/CollectionCover.vue index 921a451c..5ca53645 100644 --- a/client/components/covers/CollectionCover.vue +++ b/client/components/covers/CollectionCover.vue @@ -13,8 +13,8 @@
- - + +
diff --git a/client/components/tables/collection/BookTableRow.vue b/client/components/tables/collection/BookTableRow.vue index 3fee336b..3c9874ff 100644 --- a/client/components/tables/collection/BookTableRow.vue +++ b/client/components/tables/collection/BookTableRow.vue @@ -7,7 +7,7 @@
- +
play_arrow @@ -16,8 +16,8 @@
- {{ bookTitle }} - {{ bookAuthor }} + {{ bookTitle }} +
@@ -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() }) },