mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-06-02 00:40:39 +02:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 25b7f005c6 | |||
| 777c59458d | |||
| 9785bc02ea | |||
| 6780ef9b37 | |||
| 88a0e75576 | |||
| 476933a144 |
@@ -138,7 +138,6 @@ export default {
|
|||||||
.$post(`/api/collections/${collection.id}/batch/remove`, { books: this.selectedBookIds })
|
.$post(`/api/collections/${collection.id}/batch/remove`, { books: this.selectedBookIds })
|
||||||
.then((updatedCollection) => {
|
.then((updatedCollection) => {
|
||||||
console.log(`Books removed from collection`, updatedCollection)
|
console.log(`Books removed from collection`, updatedCollection)
|
||||||
this.$toast.success(this.$strings.ToastCollectionItemsRemoveSuccess)
|
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -152,7 +151,6 @@ export default {
|
|||||||
.$delete(`/api/collections/${collection.id}/book/${this.selectedLibraryItemId}`)
|
.$delete(`/api/collections/${collection.id}/book/${this.selectedLibraryItemId}`)
|
||||||
.then((updatedCollection) => {
|
.then((updatedCollection) => {
|
||||||
console.log(`Book removed from collection`, updatedCollection)
|
console.log(`Book removed from collection`, updatedCollection)
|
||||||
this.$toast.success(this.$strings.ToastCollectionItemsRemoveSuccess)
|
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -167,12 +165,11 @@ export default {
|
|||||||
this.processing = true
|
this.processing = true
|
||||||
|
|
||||||
if (this.showBatchCollectionModal) {
|
if (this.showBatchCollectionModal) {
|
||||||
// BATCH Remove books
|
// BATCH Add books
|
||||||
this.$axios
|
this.$axios
|
||||||
.$post(`/api/collections/${collection.id}/batch/add`, { books: this.selectedBookIds })
|
.$post(`/api/collections/${collection.id}/batch/add`, { books: this.selectedBookIds })
|
||||||
.then((updatedCollection) => {
|
.then((updatedCollection) => {
|
||||||
console.log(`Books added to collection`, updatedCollection)
|
console.log(`Books added to collection`, updatedCollection)
|
||||||
this.$toast.success(this.$strings.ToastCollectionItemsAddSuccess)
|
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -187,7 +184,6 @@ export default {
|
|||||||
.$post(`/api/collections/${collection.id}/book`, { id: this.selectedLibraryItemId })
|
.$post(`/api/collections/${collection.id}/book`, { id: this.selectedLibraryItemId })
|
||||||
.then((updatedCollection) => {
|
.then((updatedCollection) => {
|
||||||
console.log(`Book added to collection`, updatedCollection)
|
console.log(`Book added to collection`, updatedCollection)
|
||||||
this.$toast.success(this.$strings.ToastCollectionItemsAddSuccess)
|
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -214,7 +210,6 @@ export default {
|
|||||||
.$post('/api/collections', newCollection)
|
.$post('/api/collections', newCollection)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
console.log('New Collection Created', data)
|
console.log('New Collection Created', data)
|
||||||
this.$toast.success(`Collection "${data.name}" created`)
|
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.newCollectionName = ''
|
this.newCollectionName = ''
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -130,7 +130,6 @@ export default {
|
|||||||
.$post(`/api/playlists/${playlist.id}/batch/remove`, { items: itemObjects })
|
.$post(`/api/playlists/${playlist.id}/batch/remove`, { items: itemObjects })
|
||||||
.then((updatedPlaylist) => {
|
.then((updatedPlaylist) => {
|
||||||
console.log(`Items removed from playlist`, updatedPlaylist)
|
console.log(`Items removed from playlist`, updatedPlaylist)
|
||||||
this.$toast.success(this.$strings.ToastPlaylistUpdateSuccess)
|
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -148,7 +147,6 @@ export default {
|
|||||||
.$post(`/api/playlists/${playlist.id}/batch/add`, { items: itemObjects })
|
.$post(`/api/playlists/${playlist.id}/batch/add`, { items: itemObjects })
|
||||||
.then((updatedPlaylist) => {
|
.then((updatedPlaylist) => {
|
||||||
console.log(`Items added to playlist`, updatedPlaylist)
|
console.log(`Items added to playlist`, updatedPlaylist)
|
||||||
this.$toast.success(this.$strings.ToastPlaylistUpdateSuccess)
|
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -174,7 +172,6 @@ export default {
|
|||||||
.$post('/api/playlists', newPlaylist)
|
.$post('/api/playlists', newPlaylist)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
console.log('New playlist created', data)
|
console.log('New playlist created', data)
|
||||||
this.$toast.success(this.$strings.ToastPlaylistCreateSuccess + ': ' + data.name)
|
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.newPlaylistName = ''
|
this.newPlaylistName = ''
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -218,7 +218,6 @@ export default {
|
|||||||
this.$toast.success(this.$strings.ToastPlaylistRemoveSuccess)
|
this.$toast.success(this.$strings.ToastPlaylistRemoveSuccess)
|
||||||
} else {
|
} else {
|
||||||
console.log(`Item removed from playlist`, updatedPlaylist)
|
console.log(`Item removed from playlist`, updatedPlaylist)
|
||||||
this.$toast.success(this.$strings.ToastPlaylistUpdateSuccess)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|||||||
@@ -729,7 +729,6 @@
|
|||||||
"ToastBookmarkUpdateSuccess": "Отметката е обновена",
|
"ToastBookmarkUpdateSuccess": "Отметката е обновена",
|
||||||
"ToastChaptersHaveErrors": "Главите имат грешки",
|
"ToastChaptersHaveErrors": "Главите имат грешки",
|
||||||
"ToastChaptersMustHaveTitles": "Главите трябва да имат заглавия",
|
"ToastChaptersMustHaveTitles": "Главите трябва да имат заглавия",
|
||||||
"ToastCollectionItemsRemoveSuccess": "Елемент(и) премахнати от колекция",
|
|
||||||
"ToastCollectionRemoveSuccess": "Колекцията е премахната",
|
"ToastCollectionRemoveSuccess": "Колекцията е премахната",
|
||||||
"ToastCollectionUpdateSuccess": "Колекцията е обновена",
|
"ToastCollectionUpdateSuccess": "Колекцията е обновена",
|
||||||
"ToastItemCoverUpdateSuccess": "Корицата на елемента е обновена",
|
"ToastItemCoverUpdateSuccess": "Корицата на елемента е обновена",
|
||||||
|
|||||||
@@ -951,8 +951,6 @@
|
|||||||
"ToastChaptersRemoved": "অধ্যায়গুলো মুছে ফেলা হয়েছে",
|
"ToastChaptersRemoved": "অধ্যায়গুলো মুছে ফেলা হয়েছে",
|
||||||
"ToastChaptersUpdated": "অধ্যায় আপডেট করা হয়েছে",
|
"ToastChaptersUpdated": "অধ্যায় আপডেট করা হয়েছে",
|
||||||
"ToastCollectionItemsAddFailed": "আইটেম(গুলি) সংগ্রহে যোগ করা ব্যর্থ হয়েছে",
|
"ToastCollectionItemsAddFailed": "আইটেম(গুলি) সংগ্রহে যোগ করা ব্যর্থ হয়েছে",
|
||||||
"ToastCollectionItemsAddSuccess": "আইটেম(গুলি) সংগ্রহে যোগ করা সফল হয়েছে",
|
|
||||||
"ToastCollectionItemsRemoveSuccess": "আইটেম(গুলি) সংগ্রহ থেকে সরানো হয়েছে",
|
|
||||||
"ToastCollectionRemoveSuccess": "সংগ্রহ সরানো হয়েছে",
|
"ToastCollectionRemoveSuccess": "সংগ্রহ সরানো হয়েছে",
|
||||||
"ToastCollectionUpdateSuccess": "সংগ্রহ আপডেট করা হয়েছে",
|
"ToastCollectionUpdateSuccess": "সংগ্রহ আপডেট করা হয়েছে",
|
||||||
"ToastCoverUpdateFailed": "কভার আপডেট ব্যর্থ হয়েছে",
|
"ToastCoverUpdateFailed": "কভার আপডেট ব্যর্থ হয়েছে",
|
||||||
|
|||||||
@@ -904,8 +904,6 @@
|
|||||||
"ToastChaptersRemoved": "Capítols eliminats",
|
"ToastChaptersRemoved": "Capítols eliminats",
|
||||||
"ToastChaptersUpdated": "Capítols actualitzats",
|
"ToastChaptersUpdated": "Capítols actualitzats",
|
||||||
"ToastCollectionItemsAddFailed": "Error en afegir elements a la col·lecció",
|
"ToastCollectionItemsAddFailed": "Error en afegir elements a la col·lecció",
|
||||||
"ToastCollectionItemsAddSuccess": "Elements afegits a la col·lecció",
|
|
||||||
"ToastCollectionItemsRemoveSuccess": "Elements eliminats de la col·lecció",
|
|
||||||
"ToastCollectionRemoveSuccess": "Col·lecció eliminada",
|
"ToastCollectionRemoveSuccess": "Col·lecció eliminada",
|
||||||
"ToastCollectionUpdateSuccess": "Col·lecció actualitzada",
|
"ToastCollectionUpdateSuccess": "Col·lecció actualitzada",
|
||||||
"ToastCoverUpdateFailed": "Error en actualitzar la portada",
|
"ToastCoverUpdateFailed": "Error en actualitzar la portada",
|
||||||
|
|||||||
@@ -943,7 +943,6 @@
|
|||||||
"ToastChaptersHaveErrors": "Kapitoly obsahují chyby",
|
"ToastChaptersHaveErrors": "Kapitoly obsahují chyby",
|
||||||
"ToastChaptersMustHaveTitles": "Kapitoly musí mít názvy",
|
"ToastChaptersMustHaveTitles": "Kapitoly musí mít názvy",
|
||||||
"ToastChaptersRemoved": "Kapitoly odstraněny",
|
"ToastChaptersRemoved": "Kapitoly odstraněny",
|
||||||
"ToastCollectionItemsRemoveSuccess": "Položky odstraněny z kolekce",
|
|
||||||
"ToastCollectionRemoveSuccess": "Kolekce odstraněna",
|
"ToastCollectionRemoveSuccess": "Kolekce odstraněna",
|
||||||
"ToastCollectionUpdateSuccess": "Kolekce aktualizována",
|
"ToastCollectionUpdateSuccess": "Kolekce aktualizována",
|
||||||
"ToastCoverUpdateFailed": "Aktualizace obálky selhala",
|
"ToastCoverUpdateFailed": "Aktualizace obálky selhala",
|
||||||
|
|||||||
@@ -640,7 +640,6 @@
|
|||||||
"ToastBookmarkUpdateSuccess": "Bogmærke opdateret",
|
"ToastBookmarkUpdateSuccess": "Bogmærke opdateret",
|
||||||
"ToastChaptersHaveErrors": "Kapitler har fejl",
|
"ToastChaptersHaveErrors": "Kapitler har fejl",
|
||||||
"ToastChaptersMustHaveTitles": "Kapitler skal have titler",
|
"ToastChaptersMustHaveTitles": "Kapitler skal have titler",
|
||||||
"ToastCollectionItemsRemoveSuccess": "Element(er) fjernet fra samlingen",
|
|
||||||
"ToastCollectionRemoveSuccess": "Samling fjernet",
|
"ToastCollectionRemoveSuccess": "Samling fjernet",
|
||||||
"ToastCollectionUpdateSuccess": "Samling opdateret",
|
"ToastCollectionUpdateSuccess": "Samling opdateret",
|
||||||
"ToastItemCoverUpdateSuccess": "Varens omslag opdateret",
|
"ToastItemCoverUpdateSuccess": "Varens omslag opdateret",
|
||||||
|
|||||||
@@ -959,8 +959,6 @@
|
|||||||
"ToastChaptersRemoved": "Kapitel entfernt",
|
"ToastChaptersRemoved": "Kapitel entfernt",
|
||||||
"ToastChaptersUpdated": "Kapitel aktualisiert",
|
"ToastChaptersUpdated": "Kapitel aktualisiert",
|
||||||
"ToastCollectionItemsAddFailed": "Das Hinzufügen von Element(en) zur Sammlung ist fehlgeschlagen",
|
"ToastCollectionItemsAddFailed": "Das Hinzufügen von Element(en) zur Sammlung ist fehlgeschlagen",
|
||||||
"ToastCollectionItemsAddSuccess": "Element(e) erfolgreich zur Sammlung hinzugefügt",
|
|
||||||
"ToastCollectionItemsRemoveSuccess": "Medien aus der Sammlung entfernt",
|
|
||||||
"ToastCollectionRemoveSuccess": "Sammlung entfernt",
|
"ToastCollectionRemoveSuccess": "Sammlung entfernt",
|
||||||
"ToastCollectionUpdateSuccess": "Sammlung aktualisiert",
|
"ToastCollectionUpdateSuccess": "Sammlung aktualisiert",
|
||||||
"ToastCoverUpdateFailed": "Cover-Update fehlgeschlagen",
|
"ToastCoverUpdateFailed": "Cover-Update fehlgeschlagen",
|
||||||
|
|||||||
@@ -961,8 +961,6 @@
|
|||||||
"ToastChaptersRemoved": "Chapters removed",
|
"ToastChaptersRemoved": "Chapters removed",
|
||||||
"ToastChaptersUpdated": "Chapters updated",
|
"ToastChaptersUpdated": "Chapters updated",
|
||||||
"ToastCollectionItemsAddFailed": "Item(s) added to collection failed",
|
"ToastCollectionItemsAddFailed": "Item(s) added to collection failed",
|
||||||
"ToastCollectionItemsAddSuccess": "Item(s) added to collection success",
|
|
||||||
"ToastCollectionItemsRemoveSuccess": "Item(s) removed from collection",
|
|
||||||
"ToastCollectionRemoveSuccess": "Collection removed",
|
"ToastCollectionRemoveSuccess": "Collection removed",
|
||||||
"ToastCollectionUpdateSuccess": "Collection updated",
|
"ToastCollectionUpdateSuccess": "Collection updated",
|
||||||
"ToastCoverUpdateFailed": "Cover update failed",
|
"ToastCoverUpdateFailed": "Cover update failed",
|
||||||
|
|||||||
@@ -959,8 +959,6 @@
|
|||||||
"ToastChaptersRemoved": "Capítulos eliminados",
|
"ToastChaptersRemoved": "Capítulos eliminados",
|
||||||
"ToastChaptersUpdated": "Capítulos actualizados",
|
"ToastChaptersUpdated": "Capítulos actualizados",
|
||||||
"ToastCollectionItemsAddFailed": "Artículo(s) añadido(s) a la colección fallido(s)",
|
"ToastCollectionItemsAddFailed": "Artículo(s) añadido(s) a la colección fallido(s)",
|
||||||
"ToastCollectionItemsAddSuccess": "Artículo(s) añadido(s) a la colección correctamente",
|
|
||||||
"ToastCollectionItemsRemoveSuccess": "Elementos(s) removidos de la colección",
|
|
||||||
"ToastCollectionRemoveSuccess": "Colección removida",
|
"ToastCollectionRemoveSuccess": "Colección removida",
|
||||||
"ToastCollectionUpdateSuccess": "Colección actualizada",
|
"ToastCollectionUpdateSuccess": "Colección actualizada",
|
||||||
"ToastCoverUpdateFailed": "Error al actualizar la cubierta",
|
"ToastCoverUpdateFailed": "Error al actualizar la cubierta",
|
||||||
|
|||||||
@@ -713,7 +713,6 @@
|
|||||||
"ToastBookmarkUpdateSuccess": "Järjehoidja värskendatud",
|
"ToastBookmarkUpdateSuccess": "Järjehoidja värskendatud",
|
||||||
"ToastChaptersHaveErrors": "Peatükkidel on vigu",
|
"ToastChaptersHaveErrors": "Peatükkidel on vigu",
|
||||||
"ToastChaptersMustHaveTitles": "Peatükkidel peab olema pealkiri",
|
"ToastChaptersMustHaveTitles": "Peatükkidel peab olema pealkiri",
|
||||||
"ToastCollectionItemsRemoveSuccess": "Üksus(ed) eemaldatud kogumist",
|
|
||||||
"ToastCollectionRemoveSuccess": "Kogum eemaldatud",
|
"ToastCollectionRemoveSuccess": "Kogum eemaldatud",
|
||||||
"ToastCollectionUpdateSuccess": "Kogum värskendatud",
|
"ToastCollectionUpdateSuccess": "Kogum värskendatud",
|
||||||
"ToastItemCoverUpdateSuccess": "Üksuse kaas värskendatud",
|
"ToastItemCoverUpdateSuccess": "Üksuse kaas värskendatud",
|
||||||
|
|||||||
@@ -953,8 +953,6 @@
|
|||||||
"ToastChaptersRemoved": "Chapitres supprimés",
|
"ToastChaptersRemoved": "Chapitres supprimés",
|
||||||
"ToastChaptersUpdated": "Chapitres mis à jour",
|
"ToastChaptersUpdated": "Chapitres mis à jour",
|
||||||
"ToastCollectionItemsAddFailed": "Échec de l’ajout de(s) élément(s) à la collection",
|
"ToastCollectionItemsAddFailed": "Échec de l’ajout de(s) élément(s) à la collection",
|
||||||
"ToastCollectionItemsAddSuccess": "Ajout de(s) élément(s) à la collection réussi",
|
|
||||||
"ToastCollectionItemsRemoveSuccess": "Élément(s) supprimé(s) de la collection",
|
|
||||||
"ToastCollectionRemoveSuccess": "Collection supprimée",
|
"ToastCollectionRemoveSuccess": "Collection supprimée",
|
||||||
"ToastCollectionUpdateSuccess": "Collection mise à jour",
|
"ToastCollectionUpdateSuccess": "Collection mise à jour",
|
||||||
"ToastCoverUpdateFailed": "Échec de la mise à jour de la couverture",
|
"ToastCoverUpdateFailed": "Échec de la mise à jour de la couverture",
|
||||||
|
|||||||
@@ -744,7 +744,6 @@
|
|||||||
"ToastBookmarkUpdateSuccess": "הסימניה עודכנה בהצלחה",
|
"ToastBookmarkUpdateSuccess": "הסימניה עודכנה בהצלחה",
|
||||||
"ToastChaptersHaveErrors": "פרקים מכילים שגיאות",
|
"ToastChaptersHaveErrors": "פרקים מכילים שגיאות",
|
||||||
"ToastChaptersMustHaveTitles": "פרקים חייבים לכלול כותרות",
|
"ToastChaptersMustHaveTitles": "פרקים חייבים לכלול כותרות",
|
||||||
"ToastCollectionItemsRemoveSuccess": "הפריט(ים) הוסרו מהאוסף בהצלחה",
|
|
||||||
"ToastCollectionRemoveSuccess": "האוסף הוסר בהצלחה",
|
"ToastCollectionRemoveSuccess": "האוסף הוסר בהצלחה",
|
||||||
"ToastCollectionUpdateSuccess": "האוסף עודכן בהצלחה",
|
"ToastCollectionUpdateSuccess": "האוסף עודכן בהצלחה",
|
||||||
"ToastItemCoverUpdateSuccess": "כריכת הפריט עודכנה בהצלחה",
|
"ToastItemCoverUpdateSuccess": "כריכת הפריט עודכנה בהצלחה",
|
||||||
|
|||||||
@@ -959,8 +959,6 @@
|
|||||||
"ToastChaptersRemoved": "Poglavlja uklonjena",
|
"ToastChaptersRemoved": "Poglavlja uklonjena",
|
||||||
"ToastChaptersUpdated": "Poglavlja su ažurirana",
|
"ToastChaptersUpdated": "Poglavlja su ažurirana",
|
||||||
"ToastCollectionItemsAddFailed": "Neuspješno dodavanje stavki u zbirku",
|
"ToastCollectionItemsAddFailed": "Neuspješno dodavanje stavki u zbirku",
|
||||||
"ToastCollectionItemsAddSuccess": "Uspješno dodavanje stavki u zbirku",
|
|
||||||
"ToastCollectionItemsRemoveSuccess": "Stavke izbrisane iz zbirke",
|
|
||||||
"ToastCollectionRemoveSuccess": "Zbirka izbrisana",
|
"ToastCollectionRemoveSuccess": "Zbirka izbrisana",
|
||||||
"ToastCollectionUpdateSuccess": "Zbirka ažurirana",
|
"ToastCollectionUpdateSuccess": "Zbirka ažurirana",
|
||||||
"ToastCoverUpdateFailed": "Ažuriranje naslovnice nije uspjelo",
|
"ToastCoverUpdateFailed": "Ažuriranje naslovnice nije uspjelo",
|
||||||
|
|||||||
@@ -945,7 +945,6 @@
|
|||||||
"ToastChaptersMustHaveTitles": "A fejezeteknek címekkel kell rendelkezniük",
|
"ToastChaptersMustHaveTitles": "A fejezeteknek címekkel kell rendelkezniük",
|
||||||
"ToastChaptersRemoved": "Fejezetek eltávolítva",
|
"ToastChaptersRemoved": "Fejezetek eltávolítva",
|
||||||
"ToastChaptersUpdated": "Fejezetek frissítve",
|
"ToastChaptersUpdated": "Fejezetek frissítve",
|
||||||
"ToastCollectionItemsRemoveSuccess": "Elem(ek) eltávolítva a gyűjteményből",
|
|
||||||
"ToastCollectionRemoveSuccess": "Gyűjtemény eltávolítva",
|
"ToastCollectionRemoveSuccess": "Gyűjtemény eltávolítva",
|
||||||
"ToastCollectionUpdateSuccess": "Gyűjtemény frissítve",
|
"ToastCollectionUpdateSuccess": "Gyűjtemény frissítve",
|
||||||
"ToastCoverUpdateFailed": "A borító frissítése nem sikerült",
|
"ToastCoverUpdateFailed": "A borító frissítése nem sikerült",
|
||||||
|
|||||||
@@ -950,8 +950,6 @@
|
|||||||
"ToastChaptersRemoved": "Capitoli rimossi",
|
"ToastChaptersRemoved": "Capitoli rimossi",
|
||||||
"ToastChaptersUpdated": "Capitoli aggiornati",
|
"ToastChaptersUpdated": "Capitoli aggiornati",
|
||||||
"ToastCollectionItemsAddFailed": "l'aggiunta dell'elemento(i) alla raccolta non è riuscito",
|
"ToastCollectionItemsAddFailed": "l'aggiunta dell'elemento(i) alla raccolta non è riuscito",
|
||||||
"ToastCollectionItemsAddSuccess": "L'aggiunta dell'elemento(i) alla raccolta è riuscito",
|
|
||||||
"ToastCollectionItemsRemoveSuccess": "Oggetto(i) rimossi dalla Raccolta",
|
|
||||||
"ToastCollectionRemoveSuccess": "Collezione rimossa",
|
"ToastCollectionRemoveSuccess": "Collezione rimossa",
|
||||||
"ToastCollectionUpdateSuccess": "Raccolta aggiornata",
|
"ToastCollectionUpdateSuccess": "Raccolta aggiornata",
|
||||||
"ToastCoverUpdateFailed": "Aggiornamento cover fallito",
|
"ToastCoverUpdateFailed": "Aggiornamento cover fallito",
|
||||||
|
|||||||
@@ -666,8 +666,6 @@
|
|||||||
"ToastChaptersMustHaveTitles": "Skyriai turi turėti pavadinimus",
|
"ToastChaptersMustHaveTitles": "Skyriai turi turėti pavadinimus",
|
||||||
"ToastChaptersRemoved": "Skyriai pašalinti",
|
"ToastChaptersRemoved": "Skyriai pašalinti",
|
||||||
"ToastCollectionItemsAddFailed": "Nepavyko pridėti į kolekciją",
|
"ToastCollectionItemsAddFailed": "Nepavyko pridėti į kolekciją",
|
||||||
"ToastCollectionItemsAddSuccess": "Pridėta į kolekciją",
|
|
||||||
"ToastCollectionItemsRemoveSuccess": "Elementai pašalinti iš kolekcijos",
|
|
||||||
"ToastCollectionRemoveSuccess": "Kolekcija pašalinta",
|
"ToastCollectionRemoveSuccess": "Kolekcija pašalinta",
|
||||||
"ToastCollectionUpdateSuccess": "Kolekcija atnaujinta",
|
"ToastCollectionUpdateSuccess": "Kolekcija atnaujinta",
|
||||||
"ToastCoverUpdateFailed": "Viršelio atnaujinimas nepavyko",
|
"ToastCoverUpdateFailed": "Viršelio atnaujinimas nepavyko",
|
||||||
|
|||||||
@@ -946,8 +946,6 @@
|
|||||||
"ToastChaptersRemoved": "Hoofdstukken verwijderd",
|
"ToastChaptersRemoved": "Hoofdstukken verwijderd",
|
||||||
"ToastChaptersUpdated": "Hoofdstukken bijgewerkt",
|
"ToastChaptersUpdated": "Hoofdstukken bijgewerkt",
|
||||||
"ToastCollectionItemsAddFailed": "Item(s) toegevoegd aan collectie mislukt",
|
"ToastCollectionItemsAddFailed": "Item(s) toegevoegd aan collectie mislukt",
|
||||||
"ToastCollectionItemsAddSuccess": "Item(s) toegevoegd aan collectie gelukt",
|
|
||||||
"ToastCollectionItemsRemoveSuccess": "Onderdeel (of onderdelen) verwijderd uit collectie",
|
|
||||||
"ToastCollectionRemoveSuccess": "Collectie verwijderd",
|
"ToastCollectionRemoveSuccess": "Collectie verwijderd",
|
||||||
"ToastCollectionUpdateSuccess": "Collectie bijgewerkt",
|
"ToastCollectionUpdateSuccess": "Collectie bijgewerkt",
|
||||||
"ToastCoverUpdateFailed": "Cover update mislukt",
|
"ToastCoverUpdateFailed": "Cover update mislukt",
|
||||||
|
|||||||
@@ -882,8 +882,6 @@
|
|||||||
"ToastChaptersRemoved": "Kapitler fjernet",
|
"ToastChaptersRemoved": "Kapitler fjernet",
|
||||||
"ToastChaptersUpdated": "Kapitler oppdatert",
|
"ToastChaptersUpdated": "Kapitler oppdatert",
|
||||||
"ToastCollectionItemsAddFailed": "Feil med å legge til element(er)",
|
"ToastCollectionItemsAddFailed": "Feil med å legge til element(er)",
|
||||||
"ToastCollectionItemsAddSuccess": "Element(er) lagt til samlingen",
|
|
||||||
"ToastCollectionItemsRemoveSuccess": "Gjenstand(er) fjernet fra samling",
|
|
||||||
"ToastCollectionRemoveSuccess": "Samling fjernet",
|
"ToastCollectionRemoveSuccess": "Samling fjernet",
|
||||||
"ToastCollectionUpdateSuccess": "samlingupdated",
|
"ToastCollectionUpdateSuccess": "samlingupdated",
|
||||||
"ToastCoverUpdateFailed": "Oppdatering av bilde feilet",
|
"ToastCoverUpdateFailed": "Oppdatering av bilde feilet",
|
||||||
|
|||||||
@@ -772,7 +772,6 @@
|
|||||||
"ToastBookmarkCreateSuccess": "Dodano zakładkę",
|
"ToastBookmarkCreateSuccess": "Dodano zakładkę",
|
||||||
"ToastBookmarkRemoveSuccess": "Zakładka została usunięta",
|
"ToastBookmarkRemoveSuccess": "Zakładka została usunięta",
|
||||||
"ToastBookmarkUpdateSuccess": "Zaktualizowano zakładkę",
|
"ToastBookmarkUpdateSuccess": "Zaktualizowano zakładkę",
|
||||||
"ToastCollectionItemsRemoveSuccess": "Przedmiot(y) zostały usunięte z kolekcji",
|
|
||||||
"ToastCollectionRemoveSuccess": "Kolekcja usunięta",
|
"ToastCollectionRemoveSuccess": "Kolekcja usunięta",
|
||||||
"ToastCollectionUpdateSuccess": "Zaktualizowano kolekcję",
|
"ToastCollectionUpdateSuccess": "Zaktualizowano kolekcję",
|
||||||
"ToastItemCoverUpdateSuccess": "Zaktualizowano okładkę",
|
"ToastItemCoverUpdateSuccess": "Zaktualizowano okładkę",
|
||||||
|
|||||||
@@ -735,7 +735,6 @@
|
|||||||
"ToastCachePurgeSuccess": "Cache apagado com sucesso",
|
"ToastCachePurgeSuccess": "Cache apagado com sucesso",
|
||||||
"ToastChaptersHaveErrors": "Capítulos com erro",
|
"ToastChaptersHaveErrors": "Capítulos com erro",
|
||||||
"ToastChaptersMustHaveTitles": "Capítulos precisam ter títulos",
|
"ToastChaptersMustHaveTitles": "Capítulos precisam ter títulos",
|
||||||
"ToastCollectionItemsRemoveSuccess": "Item(ns) removidos da coleção",
|
|
||||||
"ToastCollectionRemoveSuccess": "Coleção removida",
|
"ToastCollectionRemoveSuccess": "Coleção removida",
|
||||||
"ToastCollectionUpdateSuccess": "Coleção atualizada",
|
"ToastCollectionUpdateSuccess": "Coleção atualizada",
|
||||||
"ToastDeleteFileFailed": "Falha ao apagar arquivo",
|
"ToastDeleteFileFailed": "Falha ao apagar arquivo",
|
||||||
|
|||||||
@@ -959,8 +959,6 @@
|
|||||||
"ToastChaptersRemoved": "Удалены главы",
|
"ToastChaptersRemoved": "Удалены главы",
|
||||||
"ToastChaptersUpdated": "Обновленные главы",
|
"ToastChaptersUpdated": "Обновленные главы",
|
||||||
"ToastCollectionItemsAddFailed": "Не удалось добавить элемент(ы) в коллекцию",
|
"ToastCollectionItemsAddFailed": "Не удалось добавить элемент(ы) в коллекцию",
|
||||||
"ToastCollectionItemsAddSuccess": "Элемент(ы) добавлены в коллекцию",
|
|
||||||
"ToastCollectionItemsRemoveSuccess": "Элемент(ы), удалены из коллекции",
|
|
||||||
"ToastCollectionRemoveSuccess": "Коллекция удалена",
|
"ToastCollectionRemoveSuccess": "Коллекция удалена",
|
||||||
"ToastCollectionUpdateSuccess": "Коллекция обновлена",
|
"ToastCollectionUpdateSuccess": "Коллекция обновлена",
|
||||||
"ToastCoverUpdateFailed": "Не удалось обновить обложку",
|
"ToastCoverUpdateFailed": "Не удалось обновить обложку",
|
||||||
|
|||||||
@@ -959,8 +959,6 @@
|
|||||||
"ToastChaptersRemoved": "Poglavja so odstranjena",
|
"ToastChaptersRemoved": "Poglavja so odstranjena",
|
||||||
"ToastChaptersUpdated": "Poglavja so posodobljena",
|
"ToastChaptersUpdated": "Poglavja so posodobljena",
|
||||||
"ToastCollectionItemsAddFailed": "Dodajanje elementov v zbirko ni uspelo",
|
"ToastCollectionItemsAddFailed": "Dodajanje elementov v zbirko ni uspelo",
|
||||||
"ToastCollectionItemsAddSuccess": "Dodajanje elementov v zbirko je bilo uspešno",
|
|
||||||
"ToastCollectionItemsRemoveSuccess": "Elementi so bili odstranjeni iz zbirke",
|
|
||||||
"ToastCollectionRemoveSuccess": "Zbirka je bila odstranjena",
|
"ToastCollectionRemoveSuccess": "Zbirka je bila odstranjena",
|
||||||
"ToastCollectionUpdateSuccess": "Zbirka je bila posodobljena",
|
"ToastCollectionUpdateSuccess": "Zbirka je bila posodobljena",
|
||||||
"ToastCoverUpdateFailed": "Posodobitev naslovnice ni uspela",
|
"ToastCoverUpdateFailed": "Posodobitev naslovnice ni uspela",
|
||||||
|
|||||||
@@ -705,7 +705,6 @@
|
|||||||
"ToastBookmarkUpdateSuccess": "Bokmärket har uppdaterats",
|
"ToastBookmarkUpdateSuccess": "Bokmärket har uppdaterats",
|
||||||
"ToastChaptersHaveErrors": "Kapitlen har fel",
|
"ToastChaptersHaveErrors": "Kapitlen har fel",
|
||||||
"ToastChaptersMustHaveTitles": "Kapitel måste ha titlar",
|
"ToastChaptersMustHaveTitles": "Kapitel måste ha titlar",
|
||||||
"ToastCollectionItemsRemoveSuccess": "Objekt borttagna från samlingen",
|
|
||||||
"ToastCollectionRemoveSuccess": "Samlingen har raderats",
|
"ToastCollectionRemoveSuccess": "Samlingen har raderats",
|
||||||
"ToastCollectionUpdateSuccess": "Samlingen har uppdaterats",
|
"ToastCollectionUpdateSuccess": "Samlingen har uppdaterats",
|
||||||
"ToastItemCoverUpdateSuccess": "Objektets omslag uppdaterat",
|
"ToastItemCoverUpdateSuccess": "Objektets omslag uppdaterat",
|
||||||
|
|||||||
@@ -959,8 +959,6 @@
|
|||||||
"ToastChaptersRemoved": "Розділи видалені",
|
"ToastChaptersRemoved": "Розділи видалені",
|
||||||
"ToastChaptersUpdated": "Розділи оновлені",
|
"ToastChaptersUpdated": "Розділи оновлені",
|
||||||
"ToastCollectionItemsAddFailed": "Не вдалося додати елемент(и) до колекції",
|
"ToastCollectionItemsAddFailed": "Не вдалося додати елемент(и) до колекції",
|
||||||
"ToastCollectionItemsAddSuccess": "Елемент(и) успішно додано до колекції",
|
|
||||||
"ToastCollectionItemsRemoveSuccess": "Елемент(и) видалено з добірки",
|
|
||||||
"ToastCollectionRemoveSuccess": "Добірку видалено",
|
"ToastCollectionRemoveSuccess": "Добірку видалено",
|
||||||
"ToastCollectionUpdateSuccess": "Добірку оновлено",
|
"ToastCollectionUpdateSuccess": "Добірку оновлено",
|
||||||
"ToastCoverUpdateFailed": "Не вдалося оновити обкладинку",
|
"ToastCoverUpdateFailed": "Не вдалося оновити обкладинку",
|
||||||
|
|||||||
@@ -683,7 +683,6 @@
|
|||||||
"ToastBookmarkUpdateSuccess": "Đánh dấu đã được cập nhật",
|
"ToastBookmarkUpdateSuccess": "Đánh dấu đã được cập nhật",
|
||||||
"ToastChaptersHaveErrors": "Các chương có lỗi",
|
"ToastChaptersHaveErrors": "Các chương có lỗi",
|
||||||
"ToastChaptersMustHaveTitles": "Các chương phải có tiêu đề",
|
"ToastChaptersMustHaveTitles": "Các chương phải có tiêu đề",
|
||||||
"ToastCollectionItemsRemoveSuccess": "Mục đã được xóa khỏi bộ sưu tập",
|
|
||||||
"ToastCollectionRemoveSuccess": "Bộ sưu tập đã được xóa",
|
"ToastCollectionRemoveSuccess": "Bộ sưu tập đã được xóa",
|
||||||
"ToastCollectionUpdateSuccess": "Bộ sưu tập đã được cập nhật",
|
"ToastCollectionUpdateSuccess": "Bộ sưu tập đã được cập nhật",
|
||||||
"ToastItemCoverUpdateSuccess": "Ảnh bìa mục đã được cập nhật",
|
"ToastItemCoverUpdateSuccess": "Ảnh bìa mục đã được cập nhật",
|
||||||
|
|||||||
@@ -959,8 +959,6 @@
|
|||||||
"ToastChaptersRemoved": "已删除章节",
|
"ToastChaptersRemoved": "已删除章节",
|
||||||
"ToastChaptersUpdated": "章节已更新",
|
"ToastChaptersUpdated": "章节已更新",
|
||||||
"ToastCollectionItemsAddFailed": "项目添加到收藏夹失败",
|
"ToastCollectionItemsAddFailed": "项目添加到收藏夹失败",
|
||||||
"ToastCollectionItemsAddSuccess": "项目添加到收藏夹成功",
|
|
||||||
"ToastCollectionItemsRemoveSuccess": "项目从收藏夹移除",
|
|
||||||
"ToastCollectionRemoveSuccess": "收藏夹已删除",
|
"ToastCollectionRemoveSuccess": "收藏夹已删除",
|
||||||
"ToastCollectionUpdateSuccess": "收藏夹已更新",
|
"ToastCollectionUpdateSuccess": "收藏夹已更新",
|
||||||
"ToastCoverUpdateFailed": "封面更新失败",
|
"ToastCoverUpdateFailed": "封面更新失败",
|
||||||
|
|||||||
@@ -727,7 +727,6 @@
|
|||||||
"ToastBookmarkUpdateSuccess": "書籤已更新",
|
"ToastBookmarkUpdateSuccess": "書籤已更新",
|
||||||
"ToastChaptersHaveErrors": "章節有錯誤",
|
"ToastChaptersHaveErrors": "章節有錯誤",
|
||||||
"ToastChaptersMustHaveTitles": "章節必須有標題",
|
"ToastChaptersMustHaveTitles": "章節必須有標題",
|
||||||
"ToastCollectionItemsRemoveSuccess": "項目從收藏夾移除",
|
|
||||||
"ToastCollectionRemoveSuccess": "收藏夾已刪除",
|
"ToastCollectionRemoveSuccess": "收藏夾已刪除",
|
||||||
"ToastCollectionUpdateSuccess": "收藏夾已更新",
|
"ToastCollectionUpdateSuccess": "收藏夾已更新",
|
||||||
"ToastItemCoverUpdateSuccess": "項目封面已更新",
|
"ToastItemCoverUpdateSuccess": "項目封面已更新",
|
||||||
|
|||||||
@@ -406,21 +406,6 @@ class Database {
|
|||||||
return Promise.all(oldBooks.map((oldBook) => this.models.book.saveFromOld(oldBook)))
|
return Promise.all(oldBooks.map((oldBook) => this.models.book.saveFromOld(oldBook)))
|
||||||
}
|
}
|
||||||
|
|
||||||
createBulkCollectionBooks(collectionBooks) {
|
|
||||||
if (!this.sequelize) return false
|
|
||||||
return this.models.collectionBook.bulkCreate(collectionBooks)
|
|
||||||
}
|
|
||||||
|
|
||||||
createPlaylistMediaItem(playlistMediaItem) {
|
|
||||||
if (!this.sequelize) return false
|
|
||||||
return this.models.playlistMediaItem.create(playlistMediaItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
createBulkPlaylistMediaItems(playlistMediaItems) {
|
|
||||||
if (!this.sequelize) return false
|
|
||||||
return this.models.playlistMediaItem.bulkCreate(playlistMediaItems)
|
|
||||||
}
|
|
||||||
|
|
||||||
async createLibraryItem(oldLibraryItem) {
|
async createLibraryItem(oldLibraryItem) {
|
||||||
if (!this.sequelize) return false
|
if (!this.sequelize) return false
|
||||||
await oldLibraryItem.saveMetadata()
|
await oldLibraryItem.saveMetadata()
|
||||||
|
|||||||
@@ -5,13 +5,17 @@ const SocketAuthority = require('../SocketAuthority')
|
|||||||
const Database = require('../Database')
|
const Database = require('../Database')
|
||||||
|
|
||||||
const RssFeedManager = require('../managers/RssFeedManager')
|
const RssFeedManager = require('../managers/RssFeedManager')
|
||||||
const Collection = require('../objects/Collection')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef RequestUserObject
|
* @typedef RequestUserObject
|
||||||
* @property {import('../models/User')} user
|
* @property {import('../models/User')} user
|
||||||
*
|
*
|
||||||
* @typedef {Request & RequestUserObject} RequestWithUser
|
* @typedef {Request & RequestUserObject} RequestWithUser
|
||||||
|
*
|
||||||
|
* @typedef RequestEntityObject
|
||||||
|
* @property {import('../models/Collection')} collection
|
||||||
|
*
|
||||||
|
* @typedef {RequestWithUser & RequestEntityObject} CollectionControllerRequest
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class CollectionController {
|
class CollectionController {
|
||||||
@@ -25,36 +29,71 @@ class CollectionController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async create(req, res) {
|
async create(req, res) {
|
||||||
const newCollection = new Collection()
|
const reqBody = req.body || {}
|
||||||
req.body.userId = req.user.id
|
|
||||||
if (!newCollection.setData(req.body)) {
|
// Validation
|
||||||
|
if (!reqBody.name || !reqBody.libraryId) {
|
||||||
return res.status(400).send('Invalid collection data')
|
return res.status(400).send('Invalid collection data')
|
||||||
}
|
}
|
||||||
|
if (reqBody.description && typeof reqBody.description !== 'string') {
|
||||||
|
return res.status(400).send('Invalid collection description')
|
||||||
|
}
|
||||||
|
const libraryItemIds = (reqBody.books || []).filter((b) => !!b && typeof b == 'string')
|
||||||
|
if (!libraryItemIds.length) {
|
||||||
|
return res.status(400).send('Invalid collection data. No books')
|
||||||
|
}
|
||||||
|
|
||||||
// Create collection record
|
// Load library items
|
||||||
await Database.collectionModel.createFromOld(newCollection)
|
const libraryItems = await Database.libraryItemModel.findAll({
|
||||||
|
attributes: ['id', 'mediaId', 'mediaType', 'libraryId'],
|
||||||
// Get library items in collection
|
where: {
|
||||||
const libraryItemsInCollection = await Database.libraryItemModel.getForCollection(newCollection)
|
id: libraryItemIds,
|
||||||
|
libraryId: reqBody.libraryId,
|
||||||
// Create collectionBook records
|
mediaType: 'book'
|
||||||
let order = 1
|
|
||||||
const collectionBooksToAdd = []
|
|
||||||
for (const libraryItemId of newCollection.books) {
|
|
||||||
const libraryItem = libraryItemsInCollection.find((li) => li.id === libraryItemId)
|
|
||||||
if (libraryItem) {
|
|
||||||
collectionBooksToAdd.push({
|
|
||||||
collectionId: newCollection.id,
|
|
||||||
bookId: libraryItem.media.id,
|
|
||||||
order: order++
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
if (collectionBooksToAdd.length) {
|
if (libraryItems.length !== libraryItemIds.length) {
|
||||||
await Database.createBulkCollectionBooks(collectionBooksToAdd)
|
return res.status(400).send('Invalid collection data. Invalid books')
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsonExpanded = newCollection.toJSONExpanded(libraryItemsInCollection)
|
/** @type {import('../models/Collection')} */
|
||||||
|
let newCollection = null
|
||||||
|
|
||||||
|
const transaction = await Database.sequelize.transaction()
|
||||||
|
try {
|
||||||
|
// Create collection
|
||||||
|
newCollection = await Database.collectionModel.create(
|
||||||
|
{
|
||||||
|
libraryId: reqBody.libraryId,
|
||||||
|
name: reqBody.name,
|
||||||
|
description: reqBody.description || null
|
||||||
|
},
|
||||||
|
{ transaction }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create collectionBooks
|
||||||
|
const collectionBookPayloads = libraryItemIds.map((llid, index) => {
|
||||||
|
const libraryItem = libraryItems.find((li) => li.id === llid)
|
||||||
|
return {
|
||||||
|
collectionId: newCollection.id,
|
||||||
|
bookId: libraryItem.mediaId,
|
||||||
|
order: index + 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await Database.collectionBookModel.bulkCreate(collectionBookPayloads, { transaction })
|
||||||
|
|
||||||
|
await transaction.commit()
|
||||||
|
} catch (error) {
|
||||||
|
await transaction.rollback()
|
||||||
|
Logger.error('[CollectionController] create:', error)
|
||||||
|
return res.status(500).send('Failed to create collection')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load books expanded
|
||||||
|
newCollection.books = await newCollection.getBooksExpandedWithLibraryItem()
|
||||||
|
|
||||||
|
// Note: The old collection model stores expanded libraryItems in the books property
|
||||||
|
const jsonExpanded = newCollection.toOldJSONExpanded()
|
||||||
SocketAuthority.emitter('collection_added', jsonExpanded)
|
SocketAuthority.emitter('collection_added', jsonExpanded)
|
||||||
res.json(jsonExpanded)
|
res.json(jsonExpanded)
|
||||||
}
|
}
|
||||||
@@ -75,7 +114,7 @@ class CollectionController {
|
|||||||
/**
|
/**
|
||||||
* GET: /api/collections/:id
|
* GET: /api/collections/:id
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* @param {CollectionControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async findOne(req, res) {
|
async findOne(req, res) {
|
||||||
@@ -94,7 +133,7 @@ class CollectionController {
|
|||||||
* PATCH: /api/collections/:id
|
* PATCH: /api/collections/:id
|
||||||
* Update collection
|
* Update collection
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* @param {CollectionControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async update(req, res) {
|
async update(req, res) {
|
||||||
@@ -158,7 +197,7 @@ class CollectionController {
|
|||||||
*
|
*
|
||||||
* @this {import('../routers/ApiRouter')}
|
* @this {import('../routers/ApiRouter')}
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* @param {CollectionControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async delete(req, res) {
|
async delete(req, res) {
|
||||||
@@ -178,7 +217,7 @@ class CollectionController {
|
|||||||
* Add a single book to a collection
|
* Add a single book to a collection
|
||||||
* Req.body { id: <library item id> }
|
* Req.body { id: <library item id> }
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* @param {CollectionControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async addBook(req, res) {
|
async addBook(req, res) {
|
||||||
@@ -212,7 +251,7 @@ class CollectionController {
|
|||||||
* Remove a single book from a collection. Re-order books
|
* Remove a single book from a collection. Re-order books
|
||||||
* TODO: bookId is actually libraryItemId. Clients need updating to use bookId
|
* TODO: bookId is actually libraryItemId. Clients need updating to use bookId
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* @param {CollectionControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async removeBook(req, res) {
|
async removeBook(req, res) {
|
||||||
@@ -257,29 +296,31 @@ class CollectionController {
|
|||||||
* Add multiple books to collection
|
* Add multiple books to collection
|
||||||
* Req.body { books: <Array of library item ids> }
|
* Req.body { books: <Array of library item ids> }
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* @param {CollectionControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async addBatch(req, res) {
|
async addBatch(req, res) {
|
||||||
// filter out invalid libraryItemIds
|
// filter out invalid libraryItemIds
|
||||||
const bookIdsToAdd = (req.body.books || []).filter((b) => !!b && typeof b == 'string')
|
const bookIdsToAdd = (req.body.books || []).filter((b) => !!b && typeof b == 'string')
|
||||||
if (!bookIdsToAdd.length) {
|
if (!bookIdsToAdd.length) {
|
||||||
return res.status(500).send('Invalid request body')
|
return res.status(400).send('Invalid request body')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get library items associated with ids
|
// Get library items associated with ids
|
||||||
const libraryItems = await Database.libraryItemModel.findAll({
|
const libraryItems = await Database.libraryItemModel.findAll({
|
||||||
|
attributes: ['id', 'mediaId', 'mediaType', 'libraryId'],
|
||||||
where: {
|
where: {
|
||||||
id: {
|
id: bookIdsToAdd,
|
||||||
[Sequelize.Op.in]: bookIdsToAdd
|
libraryId: req.collection.libraryId,
|
||||||
}
|
mediaType: 'book'
|
||||||
},
|
|
||||||
include: {
|
|
||||||
model: Database.bookModel
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
if (!libraryItems.length) {
|
||||||
|
return res.status(400).send('Invalid request body. No valid books')
|
||||||
|
}
|
||||||
|
|
||||||
// Get collection books already in collection
|
// Get collection books already in collection
|
||||||
|
/** @type {import('../models/CollectionBook')[]} */
|
||||||
const collectionBooks = await req.collection.getCollectionBooks()
|
const collectionBooks = await req.collection.getCollectionBooks()
|
||||||
|
|
||||||
let order = collectionBooks.length + 1
|
let order = collectionBooks.length + 1
|
||||||
@@ -288,10 +329,10 @@ class CollectionController {
|
|||||||
|
|
||||||
// Check and set new collection books to add
|
// Check and set new collection books to add
|
||||||
for (const libraryItem of libraryItems) {
|
for (const libraryItem of libraryItems) {
|
||||||
if (!collectionBooks.some((cb) => cb.bookId === libraryItem.media.id)) {
|
if (!collectionBooks.some((cb) => cb.bookId === libraryItem.mediaId)) {
|
||||||
collectionBooksToAdd.push({
|
collectionBooksToAdd.push({
|
||||||
collectionId: req.collection.id,
|
collectionId: req.collection.id,
|
||||||
bookId: libraryItem.media.id,
|
bookId: libraryItem.mediaId,
|
||||||
order: order++
|
order: order++
|
||||||
})
|
})
|
||||||
hasUpdated = true
|
hasUpdated = true
|
||||||
@@ -302,7 +343,8 @@ class CollectionController {
|
|||||||
|
|
||||||
let jsonExpanded = null
|
let jsonExpanded = null
|
||||||
if (hasUpdated) {
|
if (hasUpdated) {
|
||||||
await Database.createBulkCollectionBooks(collectionBooksToAdd)
|
await Database.collectionBookModel.bulkCreate(collectionBooksToAdd)
|
||||||
|
|
||||||
jsonExpanded = await req.collection.getOldJsonExpanded()
|
jsonExpanded = await req.collection.getOldJsonExpanded()
|
||||||
SocketAuthority.emitter('collection_updated', jsonExpanded)
|
SocketAuthority.emitter('collection_updated', jsonExpanded)
|
||||||
} else {
|
} else {
|
||||||
@@ -316,7 +358,7 @@ class CollectionController {
|
|||||||
* Remove multiple books from collection
|
* Remove multiple books from collection
|
||||||
* Req.body { books: <Array of library item ids> }
|
* Req.body { books: <Array of library item ids> }
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* @param {CollectionControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async removeBatch(req, res) {
|
async removeBatch(req, res) {
|
||||||
@@ -329,9 +371,7 @@ class CollectionController {
|
|||||||
// Get library items associated with ids
|
// Get library items associated with ids
|
||||||
const libraryItems = await Database.libraryItemModel.findAll({
|
const libraryItems = await Database.libraryItemModel.findAll({
|
||||||
where: {
|
where: {
|
||||||
id: {
|
id: bookIdsToRemove
|
||||||
[Sequelize.Op.in]: bookIdsToRemove
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
model: Database.bookModel
|
model: Database.bookModel
|
||||||
@@ -339,6 +379,7 @@ class CollectionController {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Get collection books already in collection
|
// Get collection books already in collection
|
||||||
|
/** @type {import('../models/CollectionBook')[]} */
|
||||||
const collectionBooks = await req.collection.getCollectionBooks({
|
const collectionBooks = await req.collection.getCollectionBooks({
|
||||||
order: [['order', 'ASC']]
|
order: [['order', 'ASC']]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,13 +3,16 @@ const Logger = require('../Logger')
|
|||||||
const SocketAuthority = require('../SocketAuthority')
|
const SocketAuthority = require('../SocketAuthority')
|
||||||
const Database = require('../Database')
|
const Database = require('../Database')
|
||||||
|
|
||||||
const Playlist = require('../objects/Playlist')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef RequestUserObject
|
* @typedef RequestUserObject
|
||||||
* @property {import('../models/User')} user
|
* @property {import('../models/User')} user
|
||||||
*
|
*
|
||||||
* @typedef {Request & RequestUserObject} RequestWithUser
|
* @typedef {Request & RequestUserObject} RequestWithUser
|
||||||
|
*
|
||||||
|
* @typedef RequestEntityObject
|
||||||
|
* @property {import('../models/Playlist')} playlist
|
||||||
|
*
|
||||||
|
* @typedef {RequestWithUser & RequestEntityObject} PlaylistControllerRequest
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class PlaylistController {
|
class PlaylistController {
|
||||||
@@ -23,48 +26,103 @@ class PlaylistController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async create(req, res) {
|
async create(req, res) {
|
||||||
const oldPlaylist = new Playlist()
|
const reqBody = req.body || {}
|
||||||
req.body.userId = req.user.id
|
|
||||||
const success = oldPlaylist.setData(req.body)
|
// Validation
|
||||||
if (!success) {
|
if (!reqBody.name || !reqBody.libraryId) {
|
||||||
return res.status(400).send('Invalid playlist request data')
|
return res.status(400).send('Invalid playlist data')
|
||||||
|
}
|
||||||
|
if (reqBody.description && typeof reqBody.description !== 'string') {
|
||||||
|
return res.status(400).send('Invalid playlist description')
|
||||||
|
}
|
||||||
|
const items = reqBody.items || []
|
||||||
|
const isPodcast = items.some((i) => i.episodeId)
|
||||||
|
const libraryItemIds = new Set()
|
||||||
|
for (const item of items) {
|
||||||
|
if (!item.libraryItemId || typeof item.libraryItemId !== 'string') {
|
||||||
|
return res.status(400).send('Invalid playlist item')
|
||||||
|
}
|
||||||
|
if (isPodcast && (!item.episodeId || typeof item.episodeId !== 'string')) {
|
||||||
|
return res.status(400).send('Invalid playlist item episodeId')
|
||||||
|
} else if (!isPodcast && item.episodeId) {
|
||||||
|
return res.status(400).send('Invalid playlist item episodeId')
|
||||||
|
}
|
||||||
|
libraryItemIds.add(item.libraryItemId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Playlist record
|
// Load library items
|
||||||
const newPlaylist = await Database.playlistModel.createFromOld(oldPlaylist)
|
const libraryItems = await Database.libraryItemModel.findAll({
|
||||||
|
attributes: ['id', 'mediaId', 'mediaType', 'libraryId'],
|
||||||
// Lookup all library items in playlist
|
|
||||||
const libraryItemIds = oldPlaylist.items.map((i) => i.libraryItemId).filter((i) => i)
|
|
||||||
const libraryItemsInPlaylist = await Database.libraryItemModel.findAll({
|
|
||||||
where: {
|
where: {
|
||||||
id: libraryItemIds
|
id: Array.from(libraryItemIds),
|
||||||
|
libraryId: reqBody.libraryId,
|
||||||
|
mediaType: isPodcast ? 'podcast' : 'book'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
if (libraryItems.length !== libraryItemIds.size) {
|
||||||
|
return res.status(400).send('Invalid playlist data. Invalid items')
|
||||||
|
}
|
||||||
|
|
||||||
// Create playlistMediaItem records
|
// Validate podcast episodes
|
||||||
const mediaItemsToAdd = []
|
if (isPodcast) {
|
||||||
let order = 1
|
const podcastEpisodeIds = items.map((i) => i.episodeId)
|
||||||
for (const mediaItemObj of oldPlaylist.items) {
|
const podcastEpisodes = await Database.podcastEpisodeModel.findAll({
|
||||||
const libraryItem = libraryItemsInPlaylist.find((li) => li.id === mediaItemObj.libraryItemId)
|
attributes: ['id'],
|
||||||
if (!libraryItem) continue
|
where: {
|
||||||
|
id: podcastEpisodeIds
|
||||||
mediaItemsToAdd.push({
|
}
|
||||||
mediaItemId: mediaItemObj.episodeId || libraryItem.mediaId,
|
|
||||||
mediaItemType: mediaItemObj.episodeId ? 'podcastEpisode' : 'book',
|
|
||||||
playlistId: oldPlaylist.id,
|
|
||||||
order: order++
|
|
||||||
})
|
})
|
||||||
}
|
if (podcastEpisodes.length !== podcastEpisodeIds.length) {
|
||||||
if (mediaItemsToAdd.length) {
|
return res.status(400).send('Invalid playlist data. Invalid podcast episodes')
|
||||||
await Database.createBulkPlaylistMediaItems(mediaItemsToAdd)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsonExpanded = await newPlaylist.getOldJsonExpanded()
|
const transaction = await Database.sequelize.transaction()
|
||||||
SocketAuthority.clientEmitter(newPlaylist.userId, 'playlist_added', jsonExpanded)
|
try {
|
||||||
res.json(jsonExpanded)
|
// Create playlist
|
||||||
|
const newPlaylist = await Database.playlistModel.create(
|
||||||
|
{
|
||||||
|
libraryId: reqBody.libraryId,
|
||||||
|
userId: req.user.id,
|
||||||
|
name: reqBody.name,
|
||||||
|
description: reqBody.description || null
|
||||||
|
},
|
||||||
|
{ transaction }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create playlistMediaItems
|
||||||
|
const playlistItemPayloads = []
|
||||||
|
for (const [index, item] of items.entries()) {
|
||||||
|
const libraryItem = libraryItems.find((li) => li.id === item.libraryItemId)
|
||||||
|
playlistItemPayloads.push({
|
||||||
|
playlistId: newPlaylist.id,
|
||||||
|
mediaItemId: item.episodeId || libraryItem.mediaId,
|
||||||
|
mediaItemType: item.episodeId ? 'podcastEpisode' : 'book',
|
||||||
|
order: index + 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await Database.playlistMediaItemModel.bulkCreate(playlistItemPayloads, { transaction })
|
||||||
|
|
||||||
|
await transaction.commit()
|
||||||
|
|
||||||
|
newPlaylist.playlistMediaItems = await newPlaylist.getMediaItemsExpandedWithLibraryItem()
|
||||||
|
|
||||||
|
const jsonExpanded = newPlaylist.toOldJSONExpanded()
|
||||||
|
SocketAuthority.clientEmitter(newPlaylist.userId, 'playlist_added', jsonExpanded)
|
||||||
|
res.json(jsonExpanded)
|
||||||
|
} catch (error) {
|
||||||
|
await transaction.rollback()
|
||||||
|
Logger.error('[PlaylistController] create:', error)
|
||||||
|
res.status(500).send('Failed to create playlist')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @deprecated - Use /api/libraries/:libraryId/playlists
|
||||||
|
* This is not used by Abs web client or mobile apps
|
||||||
|
* TODO: Remove this endpoint or make it the primary
|
||||||
|
*
|
||||||
* GET: /api/playlists
|
* GET: /api/playlists
|
||||||
* Get all playlists for user
|
* Get all playlists for user
|
||||||
*
|
*
|
||||||
@@ -72,68 +130,89 @@ class PlaylistController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async findAllForUser(req, res) {
|
async findAllForUser(req, res) {
|
||||||
const playlistsForUser = await Database.playlistModel.findAll({
|
const playlistsForUser = await Database.playlistModel.getOldPlaylistsForUserAndLibrary(req.user.id)
|
||||||
where: {
|
|
||||||
userId: req.user.id
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const playlists = []
|
|
||||||
for (const playlist of playlistsForUser) {
|
|
||||||
const jsonExpanded = await playlist.getOldJsonExpanded()
|
|
||||||
playlists.push(jsonExpanded)
|
|
||||||
}
|
|
||||||
res.json({
|
res.json({
|
||||||
playlists
|
playlists: playlistsForUser
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET: /api/playlists/:id
|
* GET: /api/playlists/:id
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* @param {PlaylistControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async findOne(req, res) {
|
async findOne(req, res) {
|
||||||
const jsonExpanded = await req.playlist.getOldJsonExpanded()
|
req.playlist.playlistMediaItems = await req.playlist.getMediaItemsExpandedWithLibraryItem()
|
||||||
res.json(jsonExpanded)
|
res.json(req.playlist.toOldJSONExpanded())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PATCH: /api/playlists/:id
|
* PATCH: /api/playlists/:id
|
||||||
* Update playlist
|
* Update playlist
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* Used for updating name and description or reordering items
|
||||||
|
*
|
||||||
|
* @param {PlaylistControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async update(req, res) {
|
async update(req, res) {
|
||||||
const updatedPlaylist = req.playlist.set(req.body)
|
// Validation
|
||||||
let wasUpdated = false
|
const reqBody = req.body || {}
|
||||||
const changed = updatedPlaylist.changed()
|
if (reqBody.libraryId || reqBody.userId) {
|
||||||
if (changed?.length) {
|
// Could allow support for this if needed with additional validation
|
||||||
await req.playlist.save()
|
return res.status(400).send('Invalid playlist data. Cannot update libraryId or userId')
|
||||||
Logger.debug(`[PlaylistController] Updated playlist ${req.playlist.id} keys [${changed.join(',')}]`)
|
}
|
||||||
wasUpdated = true
|
if (reqBody.name && typeof reqBody.name !== 'string') {
|
||||||
|
return res.status(400).send('Invalid playlist name')
|
||||||
|
}
|
||||||
|
if (reqBody.description && typeof reqBody.description !== 'string') {
|
||||||
|
return res.status(400).send('Invalid playlist description')
|
||||||
|
}
|
||||||
|
if (reqBody.items && (!Array.isArray(reqBody.items) || reqBody.items.some((i) => !i.libraryItemId || typeof i.libraryItemId !== 'string' || (i.episodeId && typeof i.episodeId !== 'string')))) {
|
||||||
|
return res.status(400).send('Invalid playlist items')
|
||||||
}
|
}
|
||||||
|
|
||||||
// If array of items is passed in then update order of playlist media items
|
const playlistUpdatePayload = {}
|
||||||
const libraryItemIds = req.body.items?.map((i) => i.libraryItemId).filter((i) => i) || []
|
if (reqBody.name) playlistUpdatePayload.name = reqBody.name
|
||||||
if (libraryItemIds.length) {
|
if (reqBody.description) playlistUpdatePayload.description = reqBody.description
|
||||||
|
|
||||||
|
// Update name and description
|
||||||
|
let wasUpdated = false
|
||||||
|
if (Object.keys(playlistUpdatePayload).length) {
|
||||||
|
req.playlist.set(playlistUpdatePayload)
|
||||||
|
const changed = req.playlist.changed()
|
||||||
|
if (changed?.length) {
|
||||||
|
await req.playlist.save()
|
||||||
|
Logger.debug(`[PlaylistController] Updated playlist ${req.playlist.id} keys [${changed.join(',')}]`)
|
||||||
|
wasUpdated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If array of items is set then update order of playlist media items
|
||||||
|
if (reqBody.items?.length) {
|
||||||
|
const libraryItemIds = Array.from(new Set(reqBody.items.map((i) => i.libraryItemId)))
|
||||||
const libraryItems = await Database.libraryItemModel.findAll({
|
const libraryItems = await Database.libraryItemModel.findAll({
|
||||||
|
attributes: ['id', 'mediaId', 'mediaType'],
|
||||||
where: {
|
where: {
|
||||||
id: libraryItemIds
|
id: libraryItemIds
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const existingPlaylistMediaItems = await updatedPlaylist.getPlaylistMediaItems({
|
if (libraryItems.length !== libraryItemIds.length) {
|
||||||
|
return res.status(400).send('Invalid playlist items. Items not found')
|
||||||
|
}
|
||||||
|
/** @type {import('../models/PlaylistMediaItem')[]} */
|
||||||
|
const existingPlaylistMediaItems = await req.playlist.getPlaylistMediaItems({
|
||||||
order: [['order', 'ASC']]
|
order: [['order', 'ASC']]
|
||||||
})
|
})
|
||||||
|
if (existingPlaylistMediaItems.length !== reqBody.items.length) {
|
||||||
|
return res.status(400).send('Invalid playlist items. Length mismatch')
|
||||||
|
}
|
||||||
|
|
||||||
// Set an array of mediaItemId
|
// Set an array of mediaItemId
|
||||||
const newMediaItemIdOrder = []
|
const newMediaItemIdOrder = []
|
||||||
for (const item of req.body.items) {
|
for (const item of reqBody.items) {
|
||||||
const libraryItem = libraryItems.find((li) => li.id === item.libraryItemId)
|
const libraryItem = libraryItems.find((li) => li.id === item.libraryItemId)
|
||||||
if (!libraryItem) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const mediaItemId = item.episodeId || libraryItem.mediaId
|
const mediaItemId = item.episodeId || libraryItem.mediaId
|
||||||
newMediaItemIdOrder.push(mediaItemId)
|
newMediaItemIdOrder.push(mediaItemId)
|
||||||
}
|
}
|
||||||
@@ -146,21 +225,21 @@ class PlaylistController {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Update order on playlistMediaItem records
|
// Update order on playlistMediaItem records
|
||||||
let order = 1
|
for (const [index, playlistMediaItem] of existingPlaylistMediaItems.entries()) {
|
||||||
for (const playlistMediaItem of existingPlaylistMediaItems) {
|
if (playlistMediaItem.order !== index + 1) {
|
||||||
if (playlistMediaItem.order !== order) {
|
|
||||||
await playlistMediaItem.update({
|
await playlistMediaItem.update({
|
||||||
order
|
order: index + 1
|
||||||
})
|
})
|
||||||
wasUpdated = true
|
wasUpdated = true
|
||||||
}
|
}
|
||||||
order++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsonExpanded = await updatedPlaylist.getOldJsonExpanded()
|
req.playlist.playlistMediaItems = await req.playlist.getMediaItemsExpandedWithLibraryItem()
|
||||||
|
|
||||||
|
const jsonExpanded = req.playlist.toOldJSONExpanded()
|
||||||
if (wasUpdated) {
|
if (wasUpdated) {
|
||||||
SocketAuthority.clientEmitter(updatedPlaylist.userId, 'playlist_updated', jsonExpanded)
|
SocketAuthority.clientEmitter(req.playlist.userId, 'playlist_updated', jsonExpanded)
|
||||||
}
|
}
|
||||||
res.json(jsonExpanded)
|
res.json(jsonExpanded)
|
||||||
}
|
}
|
||||||
@@ -169,11 +248,13 @@ class PlaylistController {
|
|||||||
* DELETE: /api/playlists/:id
|
* DELETE: /api/playlists/:id
|
||||||
* Remove playlist
|
* Remove playlist
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* @param {PlaylistControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async delete(req, res) {
|
async delete(req, res) {
|
||||||
const jsonExpanded = await req.playlist.getOldJsonExpanded()
|
req.playlist.playlistMediaItems = await req.playlist.getMediaItemsExpandedWithLibraryItem()
|
||||||
|
const jsonExpanded = req.playlist.toOldJSONExpanded()
|
||||||
|
|
||||||
await req.playlist.destroy()
|
await req.playlist.destroy()
|
||||||
SocketAuthority.clientEmitter(jsonExpanded.userId, 'playlist_removed', jsonExpanded)
|
SocketAuthority.clientEmitter(jsonExpanded.userId, 'playlist_removed', jsonExpanded)
|
||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
@@ -183,12 +264,13 @@ class PlaylistController {
|
|||||||
* POST: /api/playlists/:id/item
|
* POST: /api/playlists/:id/item
|
||||||
* Add item to playlist
|
* Add item to playlist
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* This is not used by Abs web client or mobile apps. Only the batch endpoints are used.
|
||||||
|
*
|
||||||
|
* @param {PlaylistControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async addItem(req, res) {
|
async addItem(req, res) {
|
||||||
const oldPlaylist = await Database.playlistModel.getById(req.playlist.id)
|
const itemToAdd = req.body || {}
|
||||||
const itemToAdd = req.body
|
|
||||||
|
|
||||||
if (!itemToAdd.libraryItemId) {
|
if (!itemToAdd.libraryItemId) {
|
||||||
return res.status(400).send('Request body has no libraryItemId')
|
return res.status(400).send('Request body has no libraryItemId')
|
||||||
@@ -198,12 +280,9 @@ class PlaylistController {
|
|||||||
if (!libraryItem) {
|
if (!libraryItem) {
|
||||||
return res.status(400).send('Library item not found')
|
return res.status(400).send('Library item not found')
|
||||||
}
|
}
|
||||||
if (libraryItem.libraryId !== oldPlaylist.libraryId) {
|
if (libraryItem.libraryId !== req.playlist.libraryId) {
|
||||||
return res.status(400).send('Library item in different library')
|
return res.status(400).send('Library item in different library')
|
||||||
}
|
}
|
||||||
if (oldPlaylist.containsItem(itemToAdd)) {
|
|
||||||
return res.status(400).send('Item already in playlist')
|
|
||||||
}
|
|
||||||
if ((itemToAdd.episodeId && !libraryItem.isPodcast) || (libraryItem.isPodcast && !itemToAdd.episodeId)) {
|
if ((itemToAdd.episodeId && !libraryItem.isPodcast) || (libraryItem.isPodcast && !itemToAdd.episodeId)) {
|
||||||
return res.status(400).send('Invalid item to add for this library type')
|
return res.status(400).send('Invalid item to add for this library type')
|
||||||
}
|
}
|
||||||
@@ -211,15 +290,38 @@ class PlaylistController {
|
|||||||
return res.status(400).send('Episode not found in library item')
|
return res.status(400).send('Episode not found in library item')
|
||||||
}
|
}
|
||||||
|
|
||||||
const playlistMediaItem = {
|
req.playlist.playlistMediaItems = await req.playlist.getMediaItemsExpandedWithLibraryItem()
|
||||||
playlistId: oldPlaylist.id,
|
|
||||||
mediaItemId: itemToAdd.episodeId || libraryItem.media.id,
|
if (req.playlist.checkHasMediaItem(itemToAdd.libraryItemId, itemToAdd.episodeId)) {
|
||||||
mediaItemType: itemToAdd.episodeId ? 'podcastEpisode' : 'book',
|
return res.status(400).send('Item already in playlist')
|
||||||
order: oldPlaylist.items.length + 1
|
}
|
||||||
|
|
||||||
|
const jsonExpanded = req.playlist.toOldJSONExpanded()
|
||||||
|
|
||||||
|
const playlistMediaItem = {
|
||||||
|
playlistId: req.playlist.id,
|
||||||
|
mediaItemId: itemToAdd.episodeId || libraryItem.media.id,
|
||||||
|
mediaItemType: itemToAdd.episodeId ? 'podcastEpisode' : 'book',
|
||||||
|
order: req.playlist.playlistMediaItems.length + 1
|
||||||
|
}
|
||||||
|
await Database.playlistMediaItemModel.create(playlistMediaItem)
|
||||||
|
|
||||||
|
// Add the new item to to the old json expanded to prevent having to fully reload the playlist media items
|
||||||
|
if (itemToAdd.episodeId) {
|
||||||
|
const episode = libraryItem.media.episodes.find((ep) => ep.id === itemToAdd.episodeId)
|
||||||
|
jsonExpanded.items.push({
|
||||||
|
episodeId: itemToAdd.episodeId,
|
||||||
|
episode: episode.toJSONExpanded(),
|
||||||
|
libraryItemId: libraryItem.id,
|
||||||
|
libraryItem: libraryItem.toJSONMinified()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
jsonExpanded.items.push({
|
||||||
|
libraryItemId: libraryItem.id,
|
||||||
|
libraryItem: libraryItem.toJSONExpanded()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await Database.createPlaylistMediaItem(playlistMediaItem)
|
|
||||||
const jsonExpanded = await req.playlist.getOldJsonExpanded()
|
|
||||||
SocketAuthority.clientEmitter(jsonExpanded.userId, 'playlist_updated', jsonExpanded)
|
SocketAuthority.clientEmitter(jsonExpanded.userId, 'playlist_updated', jsonExpanded)
|
||||||
res.json(jsonExpanded)
|
res.json(jsonExpanded)
|
||||||
}
|
}
|
||||||
@@ -228,43 +330,36 @@ class PlaylistController {
|
|||||||
* DELETE: /api/playlists/:id/item/:libraryItemId/:episodeId?
|
* DELETE: /api/playlists/:id/item/:libraryItemId/:episodeId?
|
||||||
* Remove item from playlist
|
* Remove item from playlist
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* @param {PlaylistControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async removeItem(req, res) {
|
async removeItem(req, res) {
|
||||||
const oldLibraryItem = await Database.libraryItemModel.getOldById(req.params.libraryItemId)
|
req.playlist.playlistMediaItems = await req.playlist.getMediaItemsExpandedWithLibraryItem()
|
||||||
if (!oldLibraryItem) {
|
|
||||||
return res.status(404).send('Library item not found')
|
let playlistMediaItem = null
|
||||||
|
if (req.params.episodeId) {
|
||||||
|
playlistMediaItem = req.playlist.playlistMediaItems.find((pmi) => pmi.mediaItemId === req.params.episodeId)
|
||||||
|
} else {
|
||||||
|
playlistMediaItem = req.playlist.playlistMediaItems.find((pmi) => pmi.mediaItem.libraryItem?.id === req.params.libraryItemId)
|
||||||
}
|
}
|
||||||
|
if (!playlistMediaItem) {
|
||||||
// Get playlist media items
|
|
||||||
const mediaItemId = req.params.episodeId || oldLibraryItem.media.id
|
|
||||||
const playlistMediaItems = await req.playlist.getPlaylistMediaItems({
|
|
||||||
order: [['order', 'ASC']]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Check if media item to delete is in playlist
|
|
||||||
const mediaItemToRemove = playlistMediaItems.find((pmi) => pmi.mediaItemId === mediaItemId)
|
|
||||||
if (!mediaItemToRemove) {
|
|
||||||
return res.status(404).send('Media item not found in playlist')
|
return res.status(404).send('Media item not found in playlist')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove record
|
// Remove record
|
||||||
await mediaItemToRemove.destroy()
|
await playlistMediaItem.destroy()
|
||||||
|
req.playlist.playlistMediaItems = req.playlist.playlistMediaItems.filter((pmi) => pmi.id !== playlistMediaItem.id)
|
||||||
|
|
||||||
// Update playlist media items order
|
// Update playlist media items order
|
||||||
let order = 1
|
for (const [index, mediaItem] of req.playlist.playlistMediaItems.entries()) {
|
||||||
for (const mediaItem of playlistMediaItems) {
|
if (mediaItem.order !== index + 1) {
|
||||||
if (mediaItem.mediaItemId === mediaItemId) continue
|
|
||||||
if (mediaItem.order !== order) {
|
|
||||||
await mediaItem.update({
|
await mediaItem.update({
|
||||||
order
|
order: index + 1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
order++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsonExpanded = await req.playlist.getOldJsonExpanded()
|
const jsonExpanded = req.playlist.toOldJSONExpanded()
|
||||||
|
|
||||||
// Playlist is removed when there are no items
|
// Playlist is removed when there are no items
|
||||||
if (!jsonExpanded.items.length) {
|
if (!jsonExpanded.items.length) {
|
||||||
@@ -282,64 +377,68 @@ class PlaylistController {
|
|||||||
* POST: /api/playlists/:id/batch/add
|
* POST: /api/playlists/:id/batch/add
|
||||||
* Batch add playlist items
|
* Batch add playlist items
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* @param {PlaylistControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async addBatch(req, res) {
|
async addBatch(req, res) {
|
||||||
if (!req.body.items?.length) {
|
if (!req.body.items?.length || !Array.isArray(req.body.items) || req.body.items.some((i) => !i?.libraryItemId || typeof i.libraryItemId !== 'string' || (i.episodeId && typeof i.episodeId !== 'string'))) {
|
||||||
return res.status(400).send('Invalid request body')
|
return res.status(400).send('Invalid request body items')
|
||||||
}
|
|
||||||
const itemsToAdd = req.body.items
|
|
||||||
|
|
||||||
const libraryItemIds = itemsToAdd.map((i) => i.libraryItemId).filter((i) => i)
|
|
||||||
if (!libraryItemIds.length) {
|
|
||||||
return res.status(400).send('Invalid request body')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all library items
|
// Find all library items
|
||||||
const libraryItems = await Database.libraryItemModel.findAll({
|
const libraryItemIds = new Set(req.body.items.map((i) => i.libraryItemId).filter((i) => i))
|
||||||
where: {
|
|
||||||
id: libraryItemIds
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Get all existing playlist media items
|
const oldLibraryItems = await Database.libraryItemModel.getAllOldLibraryItems({ id: Array.from(libraryItemIds) })
|
||||||
const existingPlaylistMediaItems = await req.playlist.getPlaylistMediaItems({
|
if (oldLibraryItems.length !== libraryItemIds.size) {
|
||||||
order: [['order', 'ASC']]
|
return res.status(400).send('Invalid request body items')
|
||||||
})
|
}
|
||||||
|
|
||||||
|
req.playlist.playlistMediaItems = await req.playlist.getMediaItemsExpandedWithLibraryItem()
|
||||||
|
|
||||||
const mediaItemsToAdd = []
|
const mediaItemsToAdd = []
|
||||||
|
const jsonExpanded = req.playlist.toOldJSONExpanded()
|
||||||
|
|
||||||
// Setup array of playlistMediaItem records to add
|
// Setup array of playlistMediaItem records to add
|
||||||
let order = existingPlaylistMediaItems.length + 1
|
let order = req.playlist.playlistMediaItems.length + 1
|
||||||
for (const item of itemsToAdd) {
|
for (const item of req.body.items) {
|
||||||
const libraryItem = libraryItems.find((li) => li.id === item.libraryItemId)
|
const libraryItem = oldLibraryItems.find((li) => li.id === item.libraryItemId)
|
||||||
if (!libraryItem) {
|
|
||||||
return res.status(404).send('Item not found with id ' + item.libraryItemId)
|
const mediaItemId = item.episodeId || libraryItem.media.id
|
||||||
|
if (req.playlist.playlistMediaItems.some((pmi) => pmi.mediaItemId === mediaItemId)) {
|
||||||
|
// Already exists in playlist
|
||||||
|
continue
|
||||||
} else {
|
} else {
|
||||||
const mediaItemId = item.episodeId || libraryItem.mediaId
|
mediaItemsToAdd.push({
|
||||||
if (existingPlaylistMediaItems.some((pmi) => pmi.mediaItemId === mediaItemId)) {
|
playlistId: req.playlist.id,
|
||||||
// Already exists in playlist
|
mediaItemId,
|
||||||
continue
|
mediaItemType: item.episodeId ? 'podcastEpisode' : 'book',
|
||||||
|
order: order++
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add the new item to to the old json expanded to prevent having to fully reload the playlist media items
|
||||||
|
if (item.episodeId) {
|
||||||
|
const episode = libraryItem.media.episodes.find((ep) => ep.id === item.episodeId)
|
||||||
|
jsonExpanded.items.push({
|
||||||
|
episodeId: item.episodeId,
|
||||||
|
episode: episode.toJSONExpanded(),
|
||||||
|
libraryItemId: libraryItem.id,
|
||||||
|
libraryItem: libraryItem.toJSONMinified()
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
mediaItemsToAdd.push({
|
jsonExpanded.items.push({
|
||||||
playlistId: req.playlist.id,
|
libraryItemId: libraryItem.id,
|
||||||
mediaItemId,
|
libraryItem: libraryItem.toJSONExpanded()
|
||||||
mediaItemType: item.episodeId ? 'podcastEpisode' : 'book',
|
|
||||||
order: order++
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let jsonExpanded = null
|
|
||||||
if (mediaItemsToAdd.length) {
|
if (mediaItemsToAdd.length) {
|
||||||
await Database.createBulkPlaylistMediaItems(mediaItemsToAdd)
|
await Database.playlistMediaItemModel.bulkCreate(mediaItemsToAdd)
|
||||||
jsonExpanded = await req.playlist.getOldJsonExpanded()
|
|
||||||
SocketAuthority.clientEmitter(req.playlist.userId, 'playlist_updated', jsonExpanded)
|
SocketAuthority.clientEmitter(req.playlist.userId, 'playlist_updated', jsonExpanded)
|
||||||
} else {
|
|
||||||
jsonExpanded = await req.playlist.getOldJsonExpanded()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json(jsonExpanded)
|
res.json(jsonExpanded)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,50 +446,40 @@ class PlaylistController {
|
|||||||
* POST: /api/playlists/:id/batch/remove
|
* POST: /api/playlists/:id/batch/remove
|
||||||
* Batch remove playlist items
|
* Batch remove playlist items
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* @param {PlaylistControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async removeBatch(req, res) {
|
async removeBatch(req, res) {
|
||||||
if (!req.body.items?.length) {
|
if (!req.body.items?.length || !Array.isArray(req.body.items) || req.body.items.some((i) => !i?.libraryItemId || typeof i.libraryItemId !== 'string' || (i.episodeId && typeof i.episodeId !== 'string'))) {
|
||||||
return res.status(400).send('Invalid request body')
|
return res.status(400).send('Invalid request body items')
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemsToRemove = req.body.items
|
req.playlist.playlistMediaItems = await req.playlist.getMediaItemsExpandedWithLibraryItem()
|
||||||
const libraryItemIds = itemsToRemove.map((i) => i.libraryItemId).filter((i) => i)
|
|
||||||
if (!libraryItemIds.length) {
|
|
||||||
return res.status(400).send('Invalid request body')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find all library items
|
|
||||||
const libraryItems = await Database.libraryItemModel.findAll({
|
|
||||||
where: {
|
|
||||||
id: libraryItemIds
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Get all existing playlist media items for playlist
|
|
||||||
const existingPlaylistMediaItems = await req.playlist.getPlaylistMediaItems({
|
|
||||||
order: [['order', 'ASC']]
|
|
||||||
})
|
|
||||||
let numMediaItems = existingPlaylistMediaItems.length
|
|
||||||
|
|
||||||
// Remove playlist media items
|
// Remove playlist media items
|
||||||
let hasUpdated = false
|
let hasUpdated = false
|
||||||
for (const item of itemsToRemove) {
|
for (const item of req.body.items) {
|
||||||
const libraryItem = libraryItems.find((li) => li.id === item.libraryItemId)
|
let playlistMediaItem = null
|
||||||
if (!libraryItem) continue
|
if (item.episodeId) {
|
||||||
const mediaItemId = item.episodeId || libraryItem.mediaId
|
playlistMediaItem = req.playlist.playlistMediaItems.find((pmi) => pmi.mediaItemId === item.episodeId)
|
||||||
const existingMediaItem = existingPlaylistMediaItems.find((pmi) => pmi.mediaItemId === mediaItemId)
|
} else {
|
||||||
if (!existingMediaItem) continue
|
playlistMediaItem = req.playlist.playlistMediaItems.find((pmi) => pmi.mediaItem.libraryItem?.id === item.libraryItemId)
|
||||||
await existingMediaItem.destroy()
|
}
|
||||||
|
if (!playlistMediaItem) {
|
||||||
|
Logger.warn(`[PlaylistController] Playlist item not found in playlist ${req.playlist.id}`, item)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
await playlistMediaItem.destroy()
|
||||||
|
req.playlist.playlistMediaItems = req.playlist.playlistMediaItems.filter((pmi) => pmi.id !== playlistMediaItem.id)
|
||||||
|
|
||||||
hasUpdated = true
|
hasUpdated = true
|
||||||
numMediaItems--
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsonExpanded = await req.playlist.getOldJsonExpanded()
|
const jsonExpanded = req.playlist.toOldJSONExpanded()
|
||||||
if (hasUpdated) {
|
if (hasUpdated) {
|
||||||
// Playlist is removed when there are no items
|
// Playlist is removed when there are no items
|
||||||
if (!numMediaItems) {
|
if (!req.playlist.playlistMediaItems.length) {
|
||||||
Logger.info(`[PlaylistController] Playlist "${req.playlist.name}" has no more items - removing it`)
|
Logger.info(`[PlaylistController] Playlist "${req.playlist.name}" has no more items - removing it`)
|
||||||
await req.playlist.destroy()
|
await req.playlist.destroy()
|
||||||
SocketAuthority.clientEmitter(jsonExpanded.userId, 'playlist_removed', jsonExpanded)
|
SocketAuthority.clientEmitter(jsonExpanded.userId, 'playlist_removed', jsonExpanded)
|
||||||
@@ -425,33 +514,41 @@ class PlaylistController {
|
|||||||
return res.status(400).send('Collection has no books')
|
return res.status(400).send('Collection has no books')
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldPlaylist = new Playlist()
|
const transaction = await Database.sequelize.transaction()
|
||||||
oldPlaylist.setData({
|
try {
|
||||||
userId: req.user.id,
|
const playlist = await Database.playlistModel.create(
|
||||||
libraryId: collection.libraryId,
|
{
|
||||||
name: collection.name,
|
userId: req.user.id,
|
||||||
description: collection.description || null
|
libraryId: collection.libraryId,
|
||||||
})
|
name: collection.name,
|
||||||
|
description: collection.description || null
|
||||||
|
},
|
||||||
|
{ transaction }
|
||||||
|
)
|
||||||
|
|
||||||
// Create Playlist record
|
const mediaItemsToAdd = []
|
||||||
const newPlaylist = await Database.playlistModel.createFromOld(oldPlaylist)
|
for (const [index, libraryItem] of collectionExpanded.books.entries()) {
|
||||||
|
mediaItemsToAdd.push({
|
||||||
|
playlistId: playlist.id,
|
||||||
|
mediaItemId: libraryItem.media.id,
|
||||||
|
mediaItemType: 'book',
|
||||||
|
order: index + 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await Database.playlistMediaItemModel.bulkCreate(mediaItemsToAdd, { transaction })
|
||||||
|
|
||||||
// Create PlaylistMediaItem records
|
await transaction.commit()
|
||||||
const mediaItemsToAdd = []
|
|
||||||
let order = 1
|
playlist.playlistMediaItems = await playlist.getMediaItemsExpandedWithLibraryItem()
|
||||||
for (const libraryItem of collectionExpanded.books) {
|
|
||||||
mediaItemsToAdd.push({
|
const jsonExpanded = playlist.toOldJSONExpanded()
|
||||||
playlistId: newPlaylist.id,
|
SocketAuthority.clientEmitter(playlist.userId, 'playlist_added', jsonExpanded)
|
||||||
mediaItemId: libraryItem.media.id,
|
res.json(jsonExpanded)
|
||||||
mediaItemType: 'book',
|
} catch (error) {
|
||||||
order: order++
|
await transaction.rollback()
|
||||||
})
|
Logger.error('[PlaylistController] createFromCollection:', error)
|
||||||
|
res.status(500).send('Failed to create playlist')
|
||||||
}
|
}
|
||||||
await Database.createBulkPlaylistMediaItems(mediaItemsToAdd)
|
|
||||||
|
|
||||||
const jsonExpanded = await newPlaylist.getOldJsonExpanded()
|
|
||||||
SocketAuthority.clientEmitter(newPlaylist.userId, 'playlist_added', jsonExpanded)
|
|
||||||
res.json(jsonExpanded)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+38
-138
@@ -1,7 +1,5 @@
|
|||||||
const { DataTypes, Model, Sequelize } = require('sequelize')
|
const { DataTypes, Model, Sequelize } = require('sequelize')
|
||||||
|
|
||||||
const oldCollection = require('../objects/Collection')
|
|
||||||
|
|
||||||
class Collection extends Model {
|
class Collection extends Model {
|
||||||
constructor(values, options) {
|
constructor(values, options) {
|
||||||
super(values, options)
|
super(values, options)
|
||||||
@@ -26,12 +24,12 @@ class Collection extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all old collections toJSONExpanded, items filtered for user permissions
|
* Get all toOldJSONExpanded, items filtered for user permissions
|
||||||
*
|
*
|
||||||
* @param {import('./User')} user
|
* @param {import('./User')} user
|
||||||
* @param {string} [libraryId]
|
* @param {string} [libraryId]
|
||||||
* @param {string[]} [include]
|
* @param {string[]} [include]
|
||||||
* @returns {Promise<oldCollection[]>} oldCollection.toJSONExpanded
|
* @async
|
||||||
*/
|
*/
|
||||||
static async getOldCollectionsJsonExpanded(user, libraryId, include) {
|
static async getOldCollectionsJsonExpanded(user, libraryId, include) {
|
||||||
let collectionWhere = null
|
let collectionWhere = null
|
||||||
@@ -79,8 +77,6 @@ class Collection extends Model {
|
|||||||
// TODO: Handle user permission restrictions on initial query
|
// TODO: Handle user permission restrictions on initial query
|
||||||
return collections
|
return collections
|
||||||
.map((c) => {
|
.map((c) => {
|
||||||
const oldCollection = this.getOldCollection(c)
|
|
||||||
|
|
||||||
// Filter books using user permissions
|
// Filter books using user permissions
|
||||||
const books =
|
const books =
|
||||||
c.books?.filter((b) => {
|
c.books?.filter((b) => {
|
||||||
@@ -95,20 +91,14 @@ class Collection extends Model {
|
|||||||
return true
|
return true
|
||||||
}) || []
|
}) || []
|
||||||
|
|
||||||
// Map to library items
|
|
||||||
const libraryItems = books.map((b) => {
|
|
||||||
const libraryItem = b.libraryItem
|
|
||||||
delete b.libraryItem
|
|
||||||
libraryItem.media = b
|
|
||||||
return this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Users with restricted permissions will not see this collection
|
// Users with restricted permissions will not see this collection
|
||||||
if (!books.length && oldCollection.books.length) {
|
if (!books.length && c.books.length) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const collectionExpanded = oldCollection.toJSONExpanded(libraryItems)
|
this.books = books
|
||||||
|
|
||||||
|
const collectionExpanded = c.toOldJSONExpanded()
|
||||||
|
|
||||||
// Map feed if found
|
// Map feed if found
|
||||||
if (c.feeds?.length) {
|
if (c.feeds?.length) {
|
||||||
@@ -153,69 +143,6 @@ class Collection extends Model {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get old collection from Collection
|
|
||||||
* @param {Collection} collectionExpanded
|
|
||||||
* @returns {oldCollection}
|
|
||||||
*/
|
|
||||||
static getOldCollection(collectionExpanded) {
|
|
||||||
const libraryItemIds = collectionExpanded.books?.map((b) => b.libraryItem?.id || null).filter((lid) => lid) || []
|
|
||||||
return new oldCollection({
|
|
||||||
id: collectionExpanded.id,
|
|
||||||
libraryId: collectionExpanded.libraryId,
|
|
||||||
name: collectionExpanded.name,
|
|
||||||
description: collectionExpanded.description,
|
|
||||||
books: libraryItemIds,
|
|
||||||
lastUpdate: collectionExpanded.updatedAt.valueOf(),
|
|
||||||
createdAt: collectionExpanded.createdAt.valueOf()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {oldCollection} oldCollection
|
|
||||||
* @returns {Promise<Collection>}
|
|
||||||
*/
|
|
||||||
static createFromOld(oldCollection) {
|
|
||||||
const collection = this.getFromOld(oldCollection)
|
|
||||||
return this.create(collection)
|
|
||||||
}
|
|
||||||
|
|
||||||
static getFromOld(oldCollection) {
|
|
||||||
return {
|
|
||||||
id: oldCollection.id,
|
|
||||||
name: oldCollection.name,
|
|
||||||
description: oldCollection.description,
|
|
||||||
libraryId: oldCollection.libraryId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static removeById(collectionId) {
|
|
||||||
return this.destroy({
|
|
||||||
where: {
|
|
||||||
id: collectionId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get old collection by id
|
|
||||||
* @param {string} collectionId
|
|
||||||
* @returns {Promise<oldCollection|null>} returns null if not found
|
|
||||||
*/
|
|
||||||
static async getOldById(collectionId) {
|
|
||||||
if (!collectionId) return null
|
|
||||||
const collection = await this.findByPk(collectionId, {
|
|
||||||
include: {
|
|
||||||
model: this.sequelize.models.book,
|
|
||||||
include: this.sequelize.models.libraryItem
|
|
||||||
},
|
|
||||||
order: [[this.sequelize.models.book, this.sequelize.models.collectionBook, 'order', 'ASC']]
|
|
||||||
})
|
|
||||||
if (!collection) return null
|
|
||||||
return this.getOldCollection(collection)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all collections belonging to library
|
* Remove all collections belonging to library
|
||||||
* @param {string} libraryId
|
* @param {string} libraryId
|
||||||
@@ -286,64 +213,37 @@ class Collection extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get old collection toJSONExpanded, items filtered for user permissions
|
* Get toOldJSONExpanded, items filtered for user permissions
|
||||||
*
|
*
|
||||||
* @param {import('./User')|null} user
|
* @param {import('./User')|null} user
|
||||||
* @param {string[]} [include]
|
* @param {string[]} [include]
|
||||||
* @returns {Promise<oldCollection>} oldCollection.toJSONExpanded
|
* @async
|
||||||
*/
|
*/
|
||||||
async getOldJsonExpanded(user, include) {
|
async getOldJsonExpanded(user, include) {
|
||||||
this.books =
|
this.books = await this.getBooksExpandedWithLibraryItem()
|
||||||
(await this.getBooks({
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: this.sequelize.models.libraryItem
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: this.sequelize.models.author,
|
|
||||||
through: {
|
|
||||||
attributes: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: this.sequelize.models.series,
|
|
||||||
through: {
|
|
||||||
attributes: ['sequence']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
order: [Sequelize.literal('`collectionBook.order` ASC')]
|
|
||||||
})) || []
|
|
||||||
|
|
||||||
// Filter books using user permissions
|
// Filter books using user permissions
|
||||||
// TODO: Handle user permission restrictions on initial query
|
// TODO: Handle user permission restrictions on initial query
|
||||||
const books =
|
if (user) {
|
||||||
this.books?.filter((b) => {
|
const books = this.books.filter((b) => {
|
||||||
if (user) {
|
if (b.tags?.length && !user.checkCanAccessLibraryItemWithTags(b.tags)) {
|
||||||
if (b.tags?.length && !user.checkCanAccessLibraryItemWithTags(b.tags)) {
|
return false
|
||||||
return false
|
}
|
||||||
}
|
if (b.explicit === true && !user.canAccessExplicitContent) {
|
||||||
if (b.explicit === true && !user.canAccessExplicitContent) {
|
return false
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}) || []
|
})
|
||||||
|
|
||||||
// Map to library items
|
// Users with restricted permissions will not see this collection
|
||||||
const libraryItems = books.map((b) => {
|
if (!books.length && this.books.length) {
|
||||||
const libraryItem = b.libraryItem
|
return null
|
||||||
delete b.libraryItem
|
}
|
||||||
libraryItem.media = b
|
|
||||||
return this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Users with restricted permissions will not see this collection
|
this.books = books
|
||||||
if (!books.length && this.books.length) {
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const collectionExpanded = this.toOldJSONExpanded(libraryItems)
|
const collectionExpanded = this.toOldJSONExpanded()
|
||||||
|
|
||||||
if (include?.includes('rssfeed')) {
|
if (include?.includes('rssfeed')) {
|
||||||
const feeds = await this.getFeeds()
|
const feeds = await this.getFeeds()
|
||||||
@@ -357,10 +257,10 @@ class Collection extends Model {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string[]} libraryItemIds
|
* @param {string[]} [libraryItemIds=[]]
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
toOldJSON(libraryItemIds) {
|
toOldJSON(libraryItemIds = []) {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
libraryId: this.libraryId,
|
libraryId: this.libraryId,
|
||||||
@@ -372,19 +272,19 @@ class Collection extends Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
toOldJSONExpanded() {
|
||||||
*
|
if (!this.books) {
|
||||||
* @param {import('../objects/LibraryItem')} oldLibraryItems
|
throw new Error('Books are required to expand Collection')
|
||||||
* @returns
|
}
|
||||||
*/
|
|
||||||
toOldJSONExpanded(oldLibraryItems) {
|
const json = this.toOldJSON()
|
||||||
const json = this.toOldJSON(oldLibraryItems.map((li) => li.id))
|
json.books = this.books.map((book) => {
|
||||||
json.books = json.books
|
const libraryItem = book.libraryItem
|
||||||
.map((libraryItemId) => {
|
delete book.libraryItem
|
||||||
const book = oldLibraryItems.find((li) => li.id === libraryItemId)
|
libraryItem.media = book
|
||||||
return book ? book.toJSONExpanded() : null
|
return this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem).toJSONExpanded()
|
||||||
})
|
})
|
||||||
.filter((b) => !!b)
|
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,15 +16,6 @@ class CollectionBook extends Model {
|
|||||||
this.createdAt
|
this.createdAt
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeByIds(collectionId, bookId) {
|
|
||||||
return this.destroy({
|
|
||||||
where: {
|
|
||||||
bookId,
|
|
||||||
collectionId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static init(sequelize) {
|
static init(sequelize) {
|
||||||
super.init(
|
super.init(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class LibraryItem extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Currently unused because this is too slow and uses too much mem
|
*
|
||||||
* @param {import('sequelize').WhereOptions} [where]
|
* @param {import('sequelize').WhereOptions} [where]
|
||||||
* @returns {Array<objects.LibraryItem>} old library items
|
* @returns {Array<objects.LibraryItem>} old library items
|
||||||
*/
|
*/
|
||||||
|
|||||||
+141
-156
@@ -1,8 +1,6 @@
|
|||||||
const { DataTypes, Model, Op, literal } = require('sequelize')
|
const { DataTypes, Model, Op } = require('sequelize')
|
||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
|
|
||||||
const oldPlaylist = require('../objects/Playlist')
|
|
||||||
|
|
||||||
class Playlist extends Model {
|
class Playlist extends Model {
|
||||||
constructor(values, options) {
|
constructor(values, options) {
|
||||||
super(values, options)
|
super(values, options)
|
||||||
@@ -21,134 +19,23 @@ class Playlist extends Model {
|
|||||||
this.createdAt
|
this.createdAt
|
||||||
/** @type {Date} */
|
/** @type {Date} */
|
||||||
this.updatedAt
|
this.updatedAt
|
||||||
}
|
|
||||||
|
|
||||||
static getOldPlaylist(playlistExpanded) {
|
// Expanded properties
|
||||||
const items = playlistExpanded.playlistMediaItems
|
|
||||||
.map((pmi) => {
|
|
||||||
const mediaItem = pmi.mediaItem || pmi.dataValues?.mediaItem
|
|
||||||
const libraryItemId = mediaItem?.podcast?.libraryItem?.id || mediaItem?.libraryItem?.id || null
|
|
||||||
if (!libraryItemId) {
|
|
||||||
Logger.error(`[Playlist] Invalid playlist media item - No library item id found`, JSON.stringify(pmi, null, 2))
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
episodeId: pmi.mediaItemType === 'podcastEpisode' ? pmi.mediaItemId : '',
|
|
||||||
libraryItemId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter((pmi) => pmi)
|
|
||||||
|
|
||||||
return new oldPlaylist({
|
/** @type {import('./PlaylistMediaItem')[]} - only set when expanded */
|
||||||
id: playlistExpanded.id,
|
this.playlistMediaItems
|
||||||
libraryId: playlistExpanded.libraryId,
|
|
||||||
userId: playlistExpanded.userId,
|
|
||||||
name: playlistExpanded.name,
|
|
||||||
description: playlistExpanded.description,
|
|
||||||
items,
|
|
||||||
lastUpdate: playlistExpanded.updatedAt.valueOf(),
|
|
||||||
createdAt: playlistExpanded.createdAt.valueOf()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get old playlist toJSONExpanded
|
* Get old playlists for user and library
|
||||||
* @param {string[]} [include]
|
|
||||||
* @returns {Promise<oldPlaylist>} oldPlaylist.toJSONExpanded
|
|
||||||
*/
|
|
||||||
async getOldJsonExpanded(include) {
|
|
||||||
this.playlistMediaItems =
|
|
||||||
(await this.getPlaylistMediaItems({
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: this.sequelize.models.book,
|
|
||||||
include: this.sequelize.models.libraryItem
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: this.sequelize.models.podcastEpisode,
|
|
||||||
include: {
|
|
||||||
model: this.sequelize.models.podcast,
|
|
||||||
include: this.sequelize.models.libraryItem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
order: [['order', 'ASC']]
|
|
||||||
})) || []
|
|
||||||
|
|
||||||
const oldPlaylist = this.sequelize.models.playlist.getOldPlaylist(this)
|
|
||||||
const libraryItemIds = oldPlaylist.items.map((i) => i.libraryItemId)
|
|
||||||
|
|
||||||
let libraryItems = await this.sequelize.models.libraryItem.getAllOldLibraryItems({
|
|
||||||
id: libraryItemIds
|
|
||||||
})
|
|
||||||
|
|
||||||
const playlistExpanded = oldPlaylist.toJSONExpanded(libraryItems)
|
|
||||||
|
|
||||||
return playlistExpanded
|
|
||||||
}
|
|
||||||
|
|
||||||
static createFromOld(oldPlaylist) {
|
|
||||||
const playlist = this.getFromOld(oldPlaylist)
|
|
||||||
return this.create(playlist)
|
|
||||||
}
|
|
||||||
|
|
||||||
static getFromOld(oldPlaylist) {
|
|
||||||
return {
|
|
||||||
id: oldPlaylist.id,
|
|
||||||
name: oldPlaylist.name,
|
|
||||||
description: oldPlaylist.description,
|
|
||||||
userId: oldPlaylist.userId,
|
|
||||||
libraryId: oldPlaylist.libraryId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static removeById(playlistId) {
|
|
||||||
return this.destroy({
|
|
||||||
where: {
|
|
||||||
id: playlistId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get playlist by id
|
|
||||||
* @param {string} playlistId
|
|
||||||
* @returns {Promise<oldPlaylist|null>} returns null if not found
|
|
||||||
*/
|
|
||||||
static async getById(playlistId) {
|
|
||||||
if (!playlistId) return null
|
|
||||||
const playlist = await this.findByPk(playlistId, {
|
|
||||||
include: {
|
|
||||||
model: this.sequelize.models.playlistMediaItem,
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: this.sequelize.models.book,
|
|
||||||
include: this.sequelize.models.libraryItem
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: this.sequelize.models.podcastEpisode,
|
|
||||||
include: {
|
|
||||||
model: this.sequelize.models.podcast,
|
|
||||||
include: this.sequelize.models.libraryItem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
order: [['playlistMediaItems', 'order', 'ASC']]
|
|
||||||
})
|
|
||||||
if (!playlist) return null
|
|
||||||
return this.getOldPlaylist(playlist)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get old playlists for user and optionally for library
|
|
||||||
*
|
*
|
||||||
* @param {string} userId
|
* @param {string} userId
|
||||||
* @param {string} [libraryId]
|
* @param {string} libraryId
|
||||||
* @returns {Promise<oldPlaylist[]>}
|
* @async
|
||||||
*/
|
*/
|
||||||
static async getOldPlaylistsForUserAndLibrary(userId, libraryId = null) {
|
static async getOldPlaylistsForUserAndLibrary(userId, libraryId) {
|
||||||
if (!userId && !libraryId) return []
|
if (!userId && !libraryId) return []
|
||||||
|
|
||||||
const whereQuery = {}
|
const whereQuery = {}
|
||||||
if (userId) {
|
if (userId) {
|
||||||
whereQuery.userId = userId
|
whereQuery.userId = userId
|
||||||
@@ -163,7 +50,23 @@ class Playlist extends Model {
|
|||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: this.sequelize.models.book,
|
model: this.sequelize.models.book,
|
||||||
include: this.sequelize.models.libraryItem
|
include: [
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.libraryItem
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.author,
|
||||||
|
through: {
|
||||||
|
attributes: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.series,
|
||||||
|
through: {
|
||||||
|
attributes: ['sequence']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: this.sequelize.models.podcastEpisode,
|
model: this.sequelize.models.podcastEpisode,
|
||||||
@@ -174,42 +77,13 @@ class Playlist extends Model {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
order: [
|
order: [['playlistMediaItems', 'order', 'ASC']]
|
||||||
[literal('name COLLATE NOCASE'), 'ASC'],
|
|
||||||
['playlistMediaItems', 'order', 'ASC']
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const oldPlaylists = []
|
// Sort by name asc
|
||||||
for (const playlistExpanded of playlistsExpanded) {
|
playlistsExpanded.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
const oldPlaylist = this.getOldPlaylist(playlistExpanded)
|
|
||||||
const libraryItems = []
|
|
||||||
for (const pmi of playlistExpanded.playlistMediaItems) {
|
|
||||||
let mediaItem = pmi.mediaItem || pmi.dataValues.mediaItem
|
|
||||||
|
|
||||||
if (!mediaItem) {
|
return playlistsExpanded.map((playlist) => playlist.toOldJSONExpanded())
|
||||||
Logger.error(`[Playlist] Invalid playlist media item - No media item found`, JSON.stringify(mediaItem, null, 2))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
let libraryItem = mediaItem.libraryItem || mediaItem.podcast?.libraryItem
|
|
||||||
|
|
||||||
if (mediaItem.podcast) {
|
|
||||||
libraryItem.media = mediaItem.podcast
|
|
||||||
libraryItem.media.podcastEpisodes = [mediaItem]
|
|
||||||
delete mediaItem.podcast.libraryItem
|
|
||||||
} else {
|
|
||||||
libraryItem.media = mediaItem
|
|
||||||
delete mediaItem.libraryItem
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldLibraryItem = this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
|
|
||||||
libraryItems.push(oldLibraryItem)
|
|
||||||
}
|
|
||||||
const oldPlaylistJson = oldPlaylist.toJSONExpanded(libraryItems)
|
|
||||||
oldPlaylists.push(oldPlaylistJson)
|
|
||||||
}
|
|
||||||
|
|
||||||
return oldPlaylists
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -345,6 +219,117 @@ class Playlist extends Model {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all media items in playlist expanded with library item
|
||||||
|
*
|
||||||
|
* @returns {Promise<import('./PlaylistMediaItem')[]>}
|
||||||
|
*/
|
||||||
|
getMediaItemsExpandedWithLibraryItem() {
|
||||||
|
return this.getPlaylistMediaItems({
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.book,
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.libraryItem
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.author,
|
||||||
|
through: {
|
||||||
|
attributes: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.series,
|
||||||
|
through: {
|
||||||
|
attributes: ['sequence']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.podcastEpisode,
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.podcast,
|
||||||
|
include: this.sequelize.models.libraryItem
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
order: [['order', 'ASC']]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get playlists toOldJSONExpanded
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
*/
|
||||||
|
async getOldJsonExpanded() {
|
||||||
|
this.playlistMediaItems = await this.getMediaItemsExpandedWithLibraryItem()
|
||||||
|
return this.toOldJSONExpanded()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Old model used libraryItemId instead of bookId
|
||||||
|
*
|
||||||
|
* @param {string} libraryItemId
|
||||||
|
* @param {string} [episodeId]
|
||||||
|
*/
|
||||||
|
checkHasMediaItem(libraryItemId, episodeId) {
|
||||||
|
if (!this.playlistMediaItems) {
|
||||||
|
throw new Error('playlistMediaItems are required to check Playlist')
|
||||||
|
}
|
||||||
|
if (episodeId) {
|
||||||
|
return this.playlistMediaItems.some((pmi) => pmi.mediaItemId === episodeId)
|
||||||
|
}
|
||||||
|
return this.playlistMediaItems.some((pmi) => pmi.mediaItem.libraryItem.id === libraryItemId)
|
||||||
|
}
|
||||||
|
|
||||||
|
toOldJSON() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
name: this.name,
|
||||||
|
libraryId: this.libraryId,
|
||||||
|
userId: this.userId,
|
||||||
|
description: this.description,
|
||||||
|
lastUpdate: this.updatedAt.valueOf(),
|
||||||
|
createdAt: this.createdAt.valueOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toOldJSONExpanded() {
|
||||||
|
if (!this.playlistMediaItems) {
|
||||||
|
throw new Error('playlistMediaItems are required to expand Playlist')
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = this.toOldJSON()
|
||||||
|
json.items = this.playlistMediaItems.map((pmi) => {
|
||||||
|
if (pmi.mediaItemType === 'book') {
|
||||||
|
const libraryItem = pmi.mediaItem.libraryItem
|
||||||
|
delete pmi.mediaItem.libraryItem
|
||||||
|
libraryItem.media = pmi.mediaItem
|
||||||
|
return {
|
||||||
|
libraryItemId: libraryItem.id,
|
||||||
|
libraryItem: this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem).toJSONExpanded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const libraryItem = pmi.mediaItem.podcast.libraryItem
|
||||||
|
delete pmi.mediaItem.podcast.libraryItem
|
||||||
|
libraryItem.media = pmi.mediaItem.podcast
|
||||||
|
return {
|
||||||
|
episodeId: pmi.mediaItemId,
|
||||||
|
episode: pmi.mediaItem.toOldJSONExpanded(libraryItem.id),
|
||||||
|
libraryItemId: libraryItem.id,
|
||||||
|
libraryItem: this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem).toJSONMinified()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return json
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Playlist
|
module.exports = Playlist
|
||||||
|
|||||||
@@ -16,15 +16,11 @@ class PlaylistMediaItem extends Model {
|
|||||||
this.playlistId
|
this.playlistId
|
||||||
/** @type {Date} */
|
/** @type {Date} */
|
||||||
this.createdAt
|
this.createdAt
|
||||||
}
|
|
||||||
|
|
||||||
static removeByIds(playlistId, mediaItemId) {
|
// Expanded properties
|
||||||
return this.destroy({
|
|
||||||
where: {
|
/** @type {import('./Book')|import('./PodcastEpisode')} - only set when expanded */
|
||||||
playlistId,
|
this.mediaItem
|
||||||
mediaItemId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getMediaItem(options) {
|
getMediaItem(options) {
|
||||||
|
|||||||
@@ -170,6 +170,62 @@ class PodcastEpisode extends Model {
|
|||||||
})
|
})
|
||||||
PodcastEpisode.belongsTo(podcast)
|
PodcastEpisode.belongsTo(podcast)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AudioTrack object used in old model
|
||||||
|
*
|
||||||
|
* @returns {import('./Book').AudioFileObject|null}
|
||||||
|
*/
|
||||||
|
get track() {
|
||||||
|
if (!this.audioFile) return null
|
||||||
|
const track = structuredClone(this.audioFile)
|
||||||
|
track.startOffset = 0
|
||||||
|
track.title = this.audioFile.metadata.title
|
||||||
|
return track
|
||||||
|
}
|
||||||
|
|
||||||
|
toOldJSON(libraryItemId) {
|
||||||
|
let enclosure = null
|
||||||
|
if (this.enclosureURL) {
|
||||||
|
enclosure = {
|
||||||
|
url: this.enclosureURL,
|
||||||
|
type: this.enclosureType,
|
||||||
|
length: this.enclosureSize !== null ? String(this.enclosureSize) : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
libraryItemId: libraryItemId,
|
||||||
|
podcastId: this.podcastId,
|
||||||
|
id: this.id,
|
||||||
|
oldEpisodeId: this.extraData?.oldEpisodeId || null,
|
||||||
|
index: this.index,
|
||||||
|
season: this.season,
|
||||||
|
episode: this.episode,
|
||||||
|
episodeType: this.episodeType,
|
||||||
|
title: this.title,
|
||||||
|
subtitle: this.subtitle,
|
||||||
|
description: this.description,
|
||||||
|
enclosure,
|
||||||
|
guid: this.extraData?.guid || null,
|
||||||
|
pubDate: this.pubDate,
|
||||||
|
chapters: this.chapters?.map((ch) => ({ ...ch })) || [],
|
||||||
|
audioFile: this.audioFile || null,
|
||||||
|
publishedAt: this.publishedAt?.valueOf() || null,
|
||||||
|
addedAt: this.createdAt.valueOf(),
|
||||||
|
updatedAt: this.updatedAt.valueOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toOldJSONExpanded(libraryItemId) {
|
||||||
|
const json = this.toOldJSON(libraryItemId)
|
||||||
|
|
||||||
|
json.audioTrack = this.track
|
||||||
|
json.size = this.audioFile?.metadata.size || 0
|
||||||
|
json.duration = this.audioFile?.duration || 0
|
||||||
|
|
||||||
|
return json
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = PodcastEpisode
|
module.exports = PodcastEpisode
|
||||||
|
|||||||
@@ -1,115 +0,0 @@
|
|||||||
const uuidv4 = require("uuid").v4
|
|
||||||
|
|
||||||
class Collection {
|
|
||||||
constructor(collection) {
|
|
||||||
this.id = null
|
|
||||||
this.libraryId = 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,
|
|
||||||
name: this.name,
|
|
||||||
description: this.description,
|
|
||||||
cover: this.cover,
|
|
||||||
coverFullPath: this.coverFullPath,
|
|
||||||
books: [...this.books],
|
|
||||||
lastUpdate: this.lastUpdate,
|
|
||||||
createdAt: this.createdAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSONExpanded(libraryItems, minifiedBooks = false) {
|
|
||||||
const json = this.toJSON()
|
|
||||||
json.books = json.books.map(bookId => {
|
|
||||||
const book = libraryItems.find(li => li.id === bookId)
|
|
||||||
return book ? minifiedBooks ? book.toJSONMinified() : book.toJSONExpanded() : null
|
|
||||||
}).filter(b => !!b)
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expanded and filtered out items not accessible to user
|
|
||||||
toJSONExpandedForUser(user, libraryItems) {
|
|
||||||
const json = this.toJSON()
|
|
||||||
json.books = json.books.map(libraryItemId => {
|
|
||||||
const libraryItem = libraryItems.find(li => li.id === libraryItemId)
|
|
||||||
return libraryItem ? libraryItem.toJSONExpanded() : null
|
|
||||||
}).filter(li => {
|
|
||||||
return li && user.checkCanAccessLibraryItem(li)
|
|
||||||
})
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
|
|
||||||
construct(collection) {
|
|
||||||
this.id = collection.id
|
|
||||||
this.libraryId = collection.libraryId
|
|
||||||
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.libraryId || !data.name) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
this.id = uuidv4()
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
update(payload) {
|
|
||||||
let hasUpdates = false
|
|
||||||
for (const key in payload) {
|
|
||||||
if (key === 'books') {
|
|
||||||
if (payload.books && this.books.join(',') !== payload.books.join(',')) {
|
|
||||||
this.books = [...payload.books]
|
|
||||||
hasUpdates = true
|
|
||||||
}
|
|
||||||
} else if (this[key] !== undefined && this[key] !== payload[key]) {
|
|
||||||
hasUpdates = true
|
|
||||||
this[key] = payload[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasUpdates) {
|
|
||||||
this.lastUpdate = Date.now()
|
|
||||||
}
|
|
||||||
return hasUpdates
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = Collection
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
const uuidv4 = require("uuid").v4
|
|
||||||
|
|
||||||
class Playlist {
|
|
||||||
constructor(playlist) {
|
|
||||||
this.id = null
|
|
||||||
this.libraryId = null
|
|
||||||
this.userId = null
|
|
||||||
|
|
||||||
this.name = null
|
|
||||||
this.description = null
|
|
||||||
|
|
||||||
this.coverPath = null
|
|
||||||
|
|
||||||
// Array of objects like { libraryItemId: "", episodeId: "" } (episodeId optional)
|
|
||||||
this.items = []
|
|
||||||
|
|
||||||
this.lastUpdate = null
|
|
||||||
this.createdAt = null
|
|
||||||
|
|
||||||
if (playlist) {
|
|
||||||
this.construct(playlist)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
id: this.id,
|
|
||||||
libraryId: this.libraryId,
|
|
||||||
userId: this.userId,
|
|
||||||
name: this.name,
|
|
||||||
description: this.description,
|
|
||||||
coverPath: this.coverPath,
|
|
||||||
items: [...this.items],
|
|
||||||
lastUpdate: this.lastUpdate,
|
|
||||||
createdAt: this.createdAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expands the items array
|
|
||||||
toJSONExpanded(libraryItems) {
|
|
||||||
var json = this.toJSON()
|
|
||||||
json.items = json.items.map(item => {
|
|
||||||
const libraryItem = libraryItems.find(li => li.id === item.libraryItemId)
|
|
||||||
if (!libraryItem) {
|
|
||||||
// Not found
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
if (item.episodeId) {
|
|
||||||
if (!libraryItem.isPodcast) {
|
|
||||||
// Invalid
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
const episode = libraryItem.media.episodes.find(ep => ep.id === item.episodeId)
|
|
||||||
if (!episode) {
|
|
||||||
// Not found
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
episode: episode.toJSONExpanded(),
|
|
||||||
libraryItem: libraryItem.toJSONMinified()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
libraryItem: libraryItem.toJSONExpanded()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).filter(i => i)
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
|
|
||||||
construct(playlist) {
|
|
||||||
this.id = playlist.id
|
|
||||||
this.libraryId = playlist.libraryId
|
|
||||||
this.userId = playlist.userId
|
|
||||||
this.name = playlist.name
|
|
||||||
this.description = playlist.description || null
|
|
||||||
this.coverPath = playlist.coverPath || null
|
|
||||||
this.items = playlist.items ? playlist.items.map(i => ({ ...i })) : []
|
|
||||||
this.lastUpdate = playlist.lastUpdate || null
|
|
||||||
this.createdAt = playlist.createdAt || null
|
|
||||||
}
|
|
||||||
|
|
||||||
setData(data) {
|
|
||||||
if (!data.userId || !data.libraryId || !data.name) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
this.id = uuidv4()
|
|
||||||
this.userId = data.userId
|
|
||||||
this.libraryId = data.libraryId
|
|
||||||
this.name = data.name
|
|
||||||
this.description = data.description || null
|
|
||||||
this.coverPath = data.coverPath || null
|
|
||||||
this.items = data.items ? data.items.map(i => ({ ...i })) : []
|
|
||||||
this.lastUpdate = Date.now()
|
|
||||||
this.createdAt = Date.now()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
addItem(libraryItemId, episodeId = null) {
|
|
||||||
this.items.push({
|
|
||||||
libraryItemId,
|
|
||||||
episodeId: episodeId || null
|
|
||||||
})
|
|
||||||
this.lastUpdate = Date.now()
|
|
||||||
}
|
|
||||||
|
|
||||||
removeItem(libraryItemId, episodeId = null) {
|
|
||||||
if (episodeId) this.items = this.items.filter(i => i.libraryItemId !== libraryItemId || i.episodeId !== episodeId)
|
|
||||||
else this.items = this.items.filter(i => i.libraryItemId !== libraryItemId)
|
|
||||||
this.lastUpdate = Date.now()
|
|
||||||
}
|
|
||||||
|
|
||||||
update(payload) {
|
|
||||||
let hasUpdates = false
|
|
||||||
for (const key in payload) {
|
|
||||||
if (key === 'items') {
|
|
||||||
if (payload.items && JSON.stringify(payload.items) !== JSON.stringify(this.items)) {
|
|
||||||
this.items = payload.items.map(i => ({ ...i }))
|
|
||||||
hasUpdates = true
|
|
||||||
}
|
|
||||||
} else if (this[key] !== undefined && this[key] !== payload[key]) {
|
|
||||||
hasUpdates = true
|
|
||||||
this[key] = payload[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasUpdates) {
|
|
||||||
this.lastUpdate = Date.now()
|
|
||||||
}
|
|
||||||
return hasUpdates
|
|
||||||
}
|
|
||||||
|
|
||||||
containsItem(item) {
|
|
||||||
if (item.episodeId) return this.items.some(i => i.libraryItemId === item.libraryItemId && i.episodeId === item.episodeId)
|
|
||||||
return this.items.some(i => i.libraryItemId === item.libraryItemId)
|
|
||||||
}
|
|
||||||
|
|
||||||
hasItemsForLibraryItem(libraryItemId) {
|
|
||||||
return this.items.some(i => i.libraryItemId === libraryItemId)
|
|
||||||
}
|
|
||||||
|
|
||||||
removeItemsForLibraryItem(libraryItemId) {
|
|
||||||
this.items = this.items.filter(i => i.libraryItemId !== libraryItemId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = Playlist
|
|
||||||
Reference in New Issue
Block a user