mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-27 17:19:14 +01:00
Fix:Relative file path for single book scans, Change:Route names & refactor api
This commit is contained in:
parent
2194d55cc0
commit
66a490365a
@ -158,7 +158,7 @@ export default {
|
||||
}
|
||||
})
|
||||
this.$axios
|
||||
.patch(`/api/user/audiobooks`, updateProgressPayloads)
|
||||
.patch(`/api/me/audiobook/batch/update`, updateProgressPayloads)
|
||||
.then(() => {
|
||||
this.$toast.success('Batch update success!')
|
||||
this.$store.commit('setProcessingBatch', false)
|
||||
@ -177,7 +177,7 @@ export default {
|
||||
this.processingBatchDelete = true
|
||||
this.$store.commit('setProcessingBatch', true)
|
||||
this.$axios
|
||||
.$post(`/api/audiobooks/delete`, {
|
||||
.$post(`/api/books/batch/delete`, {
|
||||
audiobookIds: this.selectedAudiobooks
|
||||
})
|
||||
.then(() => {
|
||||
|
@ -162,7 +162,7 @@ export default {
|
||||
}
|
||||
this.isProcessingReadUpdate = true
|
||||
this.$axios
|
||||
.$patch(`/api/user/audiobook/${this.audiobookId}`, updatePayload)
|
||||
.$patch(`/api/me/audiobook/${this.audiobookId}`, updatePayload)
|
||||
.then(() => {
|
||||
this.isProcessingReadUpdate = false
|
||||
this.$toast.success(`"${this.title}" Marked as ${updatePayload.isRead ? 'Read' : 'Not Read'}`)
|
||||
|
@ -326,7 +326,7 @@ export default {
|
||||
}
|
||||
this.isProcessingReadUpdate = true
|
||||
this.$axios
|
||||
.$patch(`/api/user/audiobook/${this.audiobookId}`, updatePayload)
|
||||
.$patch(`/api/me/audiobook/${this.audiobookId}`, updatePayload)
|
||||
.then(() => {
|
||||
this.isProcessingReadUpdate = false
|
||||
this.$toast.success(`"${this.title}" Marked as ${updatePayload.isRead ? 'Read' : 'Not Read'}`)
|
||||
|
@ -131,7 +131,7 @@ export default {
|
||||
}
|
||||
this.isFetching = true
|
||||
|
||||
var searchResults = await this.$axios.$get(`/api/library/${this.currentLibraryId}/search?q=${value}`).catch((error) => {
|
||||
var searchResults = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/search?q=${value}`).catch((error) => {
|
||||
console.error('Search error', error)
|
||||
return []
|
||||
})
|
||||
|
@ -171,7 +171,7 @@ export default {
|
||||
this.processing = true
|
||||
console.log('Calling update', account)
|
||||
this.$axios
|
||||
.$patch(`/api/user/${this.account.id}`, account)
|
||||
.$patch(`/api/users/${this.account.id}`, account)
|
||||
.then((data) => {
|
||||
this.processing = false
|
||||
if (data.error) {
|
||||
@ -198,7 +198,7 @@ export default {
|
||||
var account = { ...this.newUser }
|
||||
this.processing = true
|
||||
this.$axios
|
||||
.$post('/api/user', account)
|
||||
.$post('/api/users', account)
|
||||
.then((data) => {
|
||||
this.processing = false
|
||||
if (data.error) {
|
||||
|
@ -94,7 +94,7 @@ export default {
|
||||
this.processing = true
|
||||
var collectionName = this.collectionName
|
||||
this.$axios
|
||||
.$delete(`/api/collection/${this.collection.id}`)
|
||||
.$delete(`/api/collections/${this.collection.id}`)
|
||||
.then(() => {
|
||||
this.processing = false
|
||||
this.show = false
|
||||
@ -122,7 +122,7 @@ export default {
|
||||
description: this.newCollectionDescription || null
|
||||
}
|
||||
this.$axios
|
||||
.$patch(`/api/collection/${this.collection.id}`, collectionUpdate)
|
||||
.$patch(`/api/collections/${this.collection.id}`, collectionUpdate)
|
||||
.then((collection) => {
|
||||
console.log('Collection Updated', collection)
|
||||
this.processing = false
|
||||
|
@ -220,7 +220,7 @@ export default {
|
||||
async fetchFull() {
|
||||
try {
|
||||
this.processing = true
|
||||
this.audiobook = await this.$axios.$get(`/api/audiobook/${this.selectedAudiobookId}`)
|
||||
this.audiobook = await this.$axios.$get(`/api/books/${this.selectedAudiobookId}`)
|
||||
this.processing = false
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch audiobook', this.selectedAudiobookId, error)
|
||||
|
@ -96,7 +96,7 @@ export default {
|
||||
this.processing = true
|
||||
|
||||
this.$axios
|
||||
.$delete(`/api/collection/${collection.id}/book/${this.selectedAudiobookId}`)
|
||||
.$delete(`/api/collections/${collection.id}/book/${this.selectedAudiobookId}`)
|
||||
.then((updatedCollection) => {
|
||||
console.log(`Book removed from collection`, updatedCollection)
|
||||
this.$toast.success('Book removed from collection')
|
||||
@ -114,7 +114,7 @@ export default {
|
||||
this.processing = true
|
||||
|
||||
this.$axios
|
||||
.$post(`/api/collection/${collection.id}/book`, { id: this.selectedAudiobookId })
|
||||
.$post(`/api/collections/${collection.id}/book`, { id: this.selectedAudiobookId })
|
||||
.then((updatedCollection) => {
|
||||
console.log(`Book added to collection`, updatedCollection)
|
||||
this.$toast.success('Book added to collection')
|
||||
|
@ -154,7 +154,7 @@ export default {
|
||||
var coverPayload = {
|
||||
url: updatePayload.cover
|
||||
}
|
||||
var success = await this.$axios.$post(`/api/audiobook/${this.audiobook.id}/cover`, coverPayload).catch((error) => {
|
||||
var success = await this.$axios.$post(`/api/books/${this.audiobook.id}/cover`, coverPayload).catch((error) => {
|
||||
console.error('Failed to update', error)
|
||||
return false
|
||||
})
|
||||
@ -171,7 +171,7 @@ export default {
|
||||
var bookUpdatePayload = {
|
||||
book: updatePayload
|
||||
}
|
||||
var success = await this.$axios.$patch(`/api/audiobook/${this.audiobook.id}`, bookUpdatePayload).catch((error) => {
|
||||
var success = await this.$axios.$patch(`/api/books/${this.audiobook.id}`, bookUpdatePayload).catch((error) => {
|
||||
console.error('Failed to update', error)
|
||||
return false
|
||||
})
|
||||
|
@ -155,7 +155,7 @@ export default {
|
||||
form.set('cover', this.selectedFile)
|
||||
|
||||
this.$axios
|
||||
.$post(`/api/audiobook/${this.audiobook.id}/cover`, form)
|
||||
.$post(`/api/books/${this.audiobook.id}/cover`, form)
|
||||
.then((data) => {
|
||||
if (data.error) {
|
||||
this.$toast.error(data.error)
|
||||
@ -217,7 +217,7 @@ export default {
|
||||
|
||||
// Download cover from url and use
|
||||
if (cover.startsWith('http:') || cover.startsWith('https:')) {
|
||||
success = await this.$axios.$post(`/api/audiobook/${this.audiobook.id}/cover`, { url: cover }).catch((error) => {
|
||||
success = await this.$axios.$post(`/api/books/${this.audiobook.id}/cover`, { url: cover }).catch((error) => {
|
||||
console.error('Failed to download cover from url', error)
|
||||
if (error.response && error.response.data) {
|
||||
this.$toast.error(error.response.data)
|
||||
@ -231,7 +231,7 @@ export default {
|
||||
cover: cover
|
||||
}
|
||||
}
|
||||
success = await this.$axios.$patch(`/api/audiobook/${this.audiobook.id}`, updatePayload).catch((error) => {
|
||||
success = await this.$axios.$patch(`/api/books/${this.audiobook.id}`, updatePayload).catch((error) => {
|
||||
console.error('Failed to update', error)
|
||||
if (error.response && error.response.data) {
|
||||
this.$toast.error(error.response.data)
|
||||
@ -266,7 +266,7 @@ export default {
|
||||
setCover(coverFile) {
|
||||
this.isProcessing = true
|
||||
this.$axios
|
||||
.$patch(`/api/audiobook/${this.audiobook.id}/coverfile`, coverFile)
|
||||
.$patch(`/api/books/${this.audiobook.id}/coverfile`, coverFile)
|
||||
.then((data) => {
|
||||
console.log('response data', data)
|
||||
if (data && typeof data === 'string') {
|
||||
|
@ -195,7 +195,7 @@ export default {
|
||||
tags: this.newTags
|
||||
}
|
||||
|
||||
var updatedAudiobook = await this.$axios.$patch(`/api/audiobook/${this.audiobook.id}`, updatePayload).catch((error) => {
|
||||
var updatedAudiobook = await this.$axios.$patch(`/api/books/${this.audiobook.id}`, updatePayload).catch((error) => {
|
||||
console.error('Failed to update', error)
|
||||
return false
|
||||
})
|
||||
@ -220,27 +220,11 @@ export default {
|
||||
|
||||
this.newTags = this.audiobook.tags || []
|
||||
},
|
||||
resetProgress() {
|
||||
if (confirm(`Are you sure you want to reset your progress?`)) {
|
||||
this.resettingProgress = true
|
||||
this.$axios
|
||||
.$delete(`/api/user/audiobook/${this.audiobookId}`)
|
||||
.then(() => {
|
||||
console.log('Progress reset complete')
|
||||
this.$toast.success(`Your progress was reset`)
|
||||
this.resettingProgress = false
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Progress reset failed', error)
|
||||
this.resettingProgress = false
|
||||
})
|
||||
}
|
||||
},
|
||||
deleteAudiobook() {
|
||||
if (confirm(`Are you sure you want to remove this audiobook?\n\n*Does not delete your files, only removes the audiobook from AudioBookshelf`)) {
|
||||
this.isProcessing = true
|
||||
this.$axios
|
||||
.$delete(`/api/audiobook/${this.audiobookId}`)
|
||||
.$delete(`/api/books/${this.audiobookId}`)
|
||||
.then(() => {
|
||||
console.log('Audiobook removed')
|
||||
this.$toast.success('Audiobook Removed')
|
||||
|
@ -133,7 +133,7 @@ export default {
|
||||
publisher: true,
|
||||
publishYear: true,
|
||||
series: true,
|
||||
volumeNumber: true,
|
||||
volumeNumber: true
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -198,7 +198,7 @@ export default {
|
||||
publisher: true,
|
||||
publishYear: true,
|
||||
series: true,
|
||||
volumeNumber: true,
|
||||
volumeNumber: true
|
||||
}
|
||||
|
||||
if (this.audiobook.id !== this.audiobookId) {
|
||||
@ -238,7 +238,7 @@ export default {
|
||||
var coverPayload = {
|
||||
url: updatePayload.cover
|
||||
}
|
||||
var success = await this.$axios.$post(`/api/audiobook/${this.audiobook.id}/cover`, coverPayload).catch((error) => {
|
||||
var success = await this.$axios.$post(`/api/books/${this.audiobook.id}/cover`, coverPayload).catch((error) => {
|
||||
console.error('Failed to update', error)
|
||||
return false
|
||||
})
|
||||
@ -255,7 +255,7 @@ export default {
|
||||
var bookUpdatePayload = {
|
||||
book: updatePayload
|
||||
}
|
||||
var success = await this.$axios.$patch(`/api/audiobook/${this.audiobook.id}`, bookUpdatePayload).catch((error) => {
|
||||
var success = await this.$axios.$patch(`/api/books/${this.audiobook.id}`, bookUpdatePayload).catch((error) => {
|
||||
console.error('Failed to update', error)
|
||||
return false
|
||||
})
|
||||
|
@ -105,7 +105,7 @@ export default {
|
||||
|
||||
this.$emit('update:processing', true)
|
||||
this.$axios
|
||||
.$patch(`/api/library/${this.library.id}`, newLibraryPayload)
|
||||
.$patch(`/api/libraries/${this.library.id}`, newLibraryPayload)
|
||||
.then((res) => {
|
||||
this.$emit('update:processing', false)
|
||||
this.$emit('close')
|
||||
@ -137,7 +137,7 @@ export default {
|
||||
|
||||
this.$emit('update:processing', true)
|
||||
this.$axios
|
||||
.$post('/api/library', newLibraryPayload)
|
||||
.$post('/api/libraries', newLibraryPayload)
|
||||
.then((res) => {
|
||||
this.$emit('update:processing', false)
|
||||
this.$emit('close')
|
||||
|
@ -72,7 +72,7 @@ export default {
|
||||
if (confirm(`Are you sure you want to permanently delete library "${this.library.name}"?`)) {
|
||||
this.isDeleting = true
|
||||
this.$axios
|
||||
.$delete(`/api/library/${this.library.id}`)
|
||||
.$delete(`/api/libraries/${this.library.id}`)
|
||||
.then((data) => {
|
||||
this.isDeleting = false
|
||||
if (data.error) {
|
||||
|
@ -68,7 +68,7 @@ export default {
|
||||
books: this.booksCopy.map((b) => b.id)
|
||||
}
|
||||
this.$axios
|
||||
.$patch(`/api/collection/${this.collectionId}`, collectionUpdate)
|
||||
.$patch(`/api/collections/${this.collectionId}`, collectionUpdate)
|
||||
.then((collection) => {
|
||||
console.log('Collection updated', collection)
|
||||
})
|
||||
|
@ -101,7 +101,7 @@ export default {
|
||||
if (confirm(`Are you sure you want to permanently delete user "${user.username}"?`)) {
|
||||
this.isDeletingUser = true
|
||||
this.$axios
|
||||
.$delete(`/api/user/${user.id}`)
|
||||
.$delete(`/api/users/${user.id}`)
|
||||
.then((data) => {
|
||||
this.isDeletingUser = false
|
||||
if (data.error) {
|
||||
|
@ -140,7 +140,7 @@ export default {
|
||||
}
|
||||
this.isProcessingReadUpdate = true
|
||||
this.$axios
|
||||
.$patch(`/api/user/audiobook/${this.book.id}`, updatePayload)
|
||||
.$patch(`/api/me/audiobook/${this.book.id}`, updatePayload)
|
||||
.then(() => {
|
||||
this.isProcessingReadUpdate = false
|
||||
this.$toast.success(`"${this.bookTitle}" Marked as ${updatePayload.isRead ? 'Read' : 'Not Read'}`)
|
||||
@ -155,7 +155,7 @@ export default {
|
||||
this.processingRemove = true
|
||||
|
||||
this.$axios
|
||||
.$delete(`/api/collection/${this.collectionId}/book/${this.book.id}`)
|
||||
.$delete(`/api/collections/${this.collectionId}/book/${this.book.id}`)
|
||||
.then((updatedCollection) => {
|
||||
console.log(`Book removed from collection`, updatedCollection)
|
||||
this.$toast.success('Book removed from collection')
|
||||
|
@ -90,7 +90,7 @@ export default {
|
||||
}
|
||||
this.changingPassword = true
|
||||
this.$axios
|
||||
.$patch('/api/user/password', {
|
||||
.$patch('/api/me/password', {
|
||||
password: this.password,
|
||||
newPassword: this.newPassword
|
||||
})
|
||||
|
@ -115,7 +115,7 @@ export default {
|
||||
if (!store.getters['user/getUserCanUpdate']) {
|
||||
return redirect('/?error=unauthorized')
|
||||
}
|
||||
var audiobook = await app.$axios.$get(`/api/audiobook/${params.id}`).catch((error) => {
|
||||
var audiobook = await app.$axios.$get(`/api/books/${params.id}`).catch((error) => {
|
||||
console.error('Failed', error)
|
||||
return false
|
||||
})
|
||||
@ -291,7 +291,7 @@ export default {
|
||||
|
||||
this.saving = true
|
||||
this.$axios
|
||||
.$patch(`/api/audiobook/${this.audiobook.id}/tracks`, { orderedFileData })
|
||||
.$patch(`/api/books/${this.audiobook.id}/tracks`, { orderedFileData })
|
||||
.then((data) => {
|
||||
console.log('Finished patching files', data)
|
||||
this.saving = false
|
||||
|
@ -161,7 +161,7 @@ export default {
|
||||
if (!store.state.user.user) {
|
||||
return redirect(`/login?redirect=${route.path}`)
|
||||
}
|
||||
var audiobook = await app.$axios.$get(`/api/audiobook/${params.id}`).catch((error) => {
|
||||
var audiobook = await app.$axios.$get(`/api/books/${params.id}`).catch((error) => {
|
||||
console.error('Failed', error)
|
||||
return false
|
||||
})
|
||||
@ -383,7 +383,7 @@ export default {
|
||||
}
|
||||
this.isProcessingReadUpdate = true
|
||||
this.$axios
|
||||
.$patch(`/api/user/audiobook/${this.audiobookId}`, updatePayload)
|
||||
.$patch(`/api/me/audiobook/${this.audiobookId}`, updatePayload)
|
||||
.then(() => {
|
||||
this.isProcessingReadUpdate = false
|
||||
this.$toast.success(`"${this.title}" Marked as ${updatePayload.isRead ? 'Read' : 'Not Read'}`)
|
||||
@ -417,7 +417,7 @@ export default {
|
||||
audiobookUpdated() {
|
||||
console.log('Audiobook Updated - Fetch full audiobook')
|
||||
this.$axios
|
||||
.$get(`/api/audiobook/${this.audiobookId}`)
|
||||
.$get(`/api/books/${this.audiobookId}`)
|
||||
.then((audiobook) => {
|
||||
console.log('Updated audiobook', audiobook)
|
||||
this.audiobook = audiobook
|
||||
@ -430,7 +430,7 @@ export default {
|
||||
if (confirm(`Are you sure you want to reset your progress?`)) {
|
||||
this.resettingProgress = true
|
||||
this.$axios
|
||||
.$patch(`/api/user/audiobook/${this.audiobookId}/reset-progress`)
|
||||
.$patch(`/api/me/audiobook/${this.audiobookId}/reset-progress`)
|
||||
.then(() => {
|
||||
console.log('Progress reset complete')
|
||||
this.$toast.success(`Your progress was reset`)
|
||||
|
@ -169,7 +169,7 @@ export default {
|
||||
this.isProcessing = true
|
||||
|
||||
this.$axios
|
||||
.$post('/api/audiobooks/update', this.audiobookCopies)
|
||||
.$post('/api/books/batch/update', this.audiobookCopies)
|
||||
.then((data) => {
|
||||
this.isProcessing = false
|
||||
if (data.updates) {
|
||||
|
@ -44,7 +44,7 @@ export default {
|
||||
if (!store.state.user.user) {
|
||||
return redirect(`/login?redirect=${route.path}`)
|
||||
}
|
||||
var collection = await app.$axios.$get(`/api/collection/${params.id}`).catch((error) => {
|
||||
var collection = await app.$axios.$get(`/api/collections/${params.id}`).catch((error) => {
|
||||
console.error('Failed', error)
|
||||
return false
|
||||
})
|
||||
@ -105,7 +105,7 @@ export default {
|
||||
this.processingRemove = true
|
||||
var collectionName = this.collectionName
|
||||
this.$axios
|
||||
.$delete(`/api/collection/${this.collection.id}`)
|
||||
.$delete(`/api/collections/${this.collection.id}`)
|
||||
.then(() => {
|
||||
this.processingRemove = false
|
||||
this.$toast.success(`Collection "${collectionName}" Removed`)
|
||||
|
@ -150,7 +150,7 @@ export default {
|
||||
if (confirm('WARNING! This action will remove all audiobooks from the database including any updates or matches you have made. This does not do anything to your actual files. Shall we continue?')) {
|
||||
this.isResettingAudiobooks = true
|
||||
this.$axios
|
||||
.$delete('/api/audiobooks')
|
||||
.$delete('/api/books/all')
|
||||
.then(() => {
|
||||
this.isResettingAudiobooks = false
|
||||
this.$toast.success('Successfully reset audiobooks')
|
||||
|
@ -97,7 +97,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async init() {
|
||||
this.listeningStats = await this.$axios.$get(`/api/user/${this.user.id}/listeningStats`).catch((err) => {
|
||||
this.listeningStats = await this.$axios.$get(`/api/me/listening-stats`).catch((err) => {
|
||||
console.error('Failed to load listening sesions', err)
|
||||
return []
|
||||
})
|
||||
|
@ -71,7 +71,7 @@
|
||||
<script>
|
||||
export default {
|
||||
async asyncData({ params, redirect, app }) {
|
||||
var user = await app.$axios.$get(`/api/user/${params.id}`).catch((error) => {
|
||||
var user = await app.$axios.$get(`/api/users/${params.id}`).catch((error) => {
|
||||
console.error('Failed to get user', error)
|
||||
return null
|
||||
})
|
||||
@ -115,11 +115,11 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async init() {
|
||||
this.listeningSessions = await this.$axios.$get(`/api/user/${this.user.id}/listeningSessions`).catch((err) => {
|
||||
this.listeningSessions = await this.$axios.$get(`/api/users/${this.user.id}/listening-sessions`).catch((err) => {
|
||||
console.error('Failed to load listening sesions', err)
|
||||
return []
|
||||
})
|
||||
this.listeningStats = await this.$axios.$get(`/api/user/${this.user.id}/listeningStats`).catch((err) => {
|
||||
this.listeningStats = await this.$axios.$get(`/api/users/${this.user.id}/listening-stats`).catch((err) => {
|
||||
console.error('Failed to load listening sesions', err)
|
||||
return []
|
||||
})
|
||||
|
@ -31,7 +31,7 @@ export default {
|
||||
if (params.id === 'search' && query.query) {
|
||||
searchQuery = query.query
|
||||
|
||||
searchResults = await app.$axios.$get(`/api/library/${libraryId}/search?q=${searchQuery}`).catch((error) => {
|
||||
searchResults = await app.$axios.$get(`/api/libraries/${libraryId}/search?q=${searchQuery}`).catch((error) => {
|
||||
console.error('Search error', error)
|
||||
return {}
|
||||
})
|
||||
@ -92,7 +92,7 @@ export default {
|
||||
methods: {
|
||||
async newQuery() {
|
||||
var query = this.$route.query.query
|
||||
this.searchResults = await this.$axios.$get(`/api/library/${this.libraryId}/search?q=${query}`).catch((error) => {
|
||||
this.searchResults = await this.$axios.$get(`/api/libraries/${this.libraryId}/search?q=${query}`).catch((error) => {
|
||||
console.error('Search error', error)
|
||||
return {}
|
||||
})
|
||||
|
@ -211,7 +211,7 @@ export const actions = {
|
||||
commit('setLoadedLibrary', currentLibraryId)
|
||||
|
||||
this.$axios
|
||||
.$get(`/api/library/${currentLibraryId}/audiobooks`)
|
||||
.$get(`/api/libraries/${currentLibraryId}/books`)
|
||||
.then((data) => {
|
||||
commit('set', data)
|
||||
commit('setLastLoad')
|
||||
|
@ -60,7 +60,7 @@ export const actions = {
|
||||
}
|
||||
|
||||
return this.$axios
|
||||
.$get(`/api/library/${libraryId}`)
|
||||
.$get(`/api/libraries/${libraryId}`)
|
||||
.then((data) => {
|
||||
commit('addUpdate', data)
|
||||
commit('setCurrentLibrary', libraryId)
|
||||
|
@ -64,7 +64,7 @@ export const actions = {
|
||||
}
|
||||
// Immediately update
|
||||
commit('setSettings', updatePayload)
|
||||
return this.$axios.$patch('/api/user/settings', updatePayload).then((result) => {
|
||||
return this.$axios.$patch('/api/me/settings', updatePayload).then((result) => {
|
||||
if (result.success) {
|
||||
commit('setSettings', result.settings)
|
||||
return true
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -355,9 +355,6 @@ class Scanner {
|
||||
Logger.info(`[Scanner] Updated Audiobook "${existingAudiobook.title}" library and folder to "${libraryId}" "${folderId}"`)
|
||||
}
|
||||
|
||||
// var audiobooksInLibrary = this.audiobooks.filter(ab => ab.libraryId === libraryId)
|
||||
// var existingAudiobook = audiobooksInLibrary.find(a => a.ino === audiobookData.ino)
|
||||
|
||||
// inode value may change when using shared drives, update inode if matching path is found
|
||||
// Note: inode will not change on rename
|
||||
var hasUpdatedIno = false
|
||||
|
31
server/controllers/BackupController.js
Normal file
31
server/controllers/BackupController.js
Normal file
@ -0,0 +1,31 @@
|
||||
const Logger = require('../Logger')
|
||||
|
||||
class BackupController {
|
||||
constructor() { }
|
||||
|
||||
async delete(req, res) {
|
||||
if (!req.user.isRoot) {
|
||||
Logger.error(`[ApiController] Non-Root user attempting to delete backup`, req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
var backup = this.backupManager.backups.find(b => b.id === req.params.id)
|
||||
if (!backup) {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
await this.backupManager.removeBackup(backup)
|
||||
res.json(this.backupManager.backups.map(b => b.toJSON()))
|
||||
}
|
||||
|
||||
async upload(req, res) {
|
||||
if (!req.user.isRoot) {
|
||||
Logger.error(`[ApiController] Non-Root user attempting to upload backup`, req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
if (!req.files.file) {
|
||||
Logger.error('[ApiController] Upload backup invalid')
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
this.backupManager.uploadBackup(req, res)
|
||||
}
|
||||
}
|
||||
module.exports = new BackupController()
|
220
server/controllers/BookController.js
Normal file
220
server/controllers/BookController.js
Normal file
@ -0,0 +1,220 @@
|
||||
const Logger = require('../Logger')
|
||||
|
||||
class BookController {
|
||||
constructor(db, emitter, clientEmitter, streamManager, coverController) {
|
||||
this.db = db
|
||||
this.emitter = emitter
|
||||
this.clientEmitter = clientEmitter
|
||||
this.streamManager = streamManager
|
||||
this.coverController = coverController
|
||||
}
|
||||
|
||||
findAll(req, res) {
|
||||
var audiobooks = []
|
||||
if (req.query.q) {
|
||||
audiobooks = this.db.audiobooks.filter(ab => {
|
||||
return ab.isSearchMatch(req.query.q)
|
||||
}).map(ab => ab.toJSONMinified())
|
||||
} else {
|
||||
audiobooks = this.db.audiobooks.map(ab => ab.toJSONMinified())
|
||||
}
|
||||
res.json(audiobooks)
|
||||
}
|
||||
|
||||
findOne(req, res) {
|
||||
if (!req.user) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
var audiobook = this.db.audiobooks.find(a => a.id === req.params.id)
|
||||
if (!audiobook) return res.sendStatus(404)
|
||||
|
||||
// Check user can access this audiobooks library
|
||||
if (!req.user.checkCanAccessLibrary(audiobook.libraryId)) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
res.json(audiobook.toJSONExpanded())
|
||||
}
|
||||
|
||||
async update(req, res) {
|
||||
if (!req.user.canUpdate) {
|
||||
Logger.warn('User attempted to update without permission', req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
var audiobook = this.db.audiobooks.find(a => a.id === req.params.id)
|
||||
if (!audiobook) return res.sendStatus(404)
|
||||
var hasUpdates = audiobook.update(req.body)
|
||||
if (hasUpdates) {
|
||||
await this.db.updateAudiobook(audiobook)
|
||||
}
|
||||
this.emitter('audiobook_updated', audiobook.toJSONMinified())
|
||||
res.json(audiobook.toJSON())
|
||||
}
|
||||
|
||||
async delete(req, res) {
|
||||
if (!req.user.canDelete) {
|
||||
Logger.warn('User attempted to delete without permission', req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
var audiobook = this.db.audiobooks.find(a => a.id === req.params.id)
|
||||
if (!audiobook) return res.sendStatus(404)
|
||||
|
||||
await this.handleDeleteAudiobook(audiobook)
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
// DELETE: api/books/all
|
||||
async deleteAll(req, res) {
|
||||
if (!req.user.isRoot) {
|
||||
Logger.warn('User other than root attempted to delete all audiobooks', req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
Logger.info('Removing all Audiobooks')
|
||||
var success = await this.db.recreateAudiobookDb()
|
||||
if (success) res.sendStatus(200)
|
||||
else res.sendStatus(500)
|
||||
}
|
||||
|
||||
|
||||
// POST: api/books/batch/delete
|
||||
async batchDelete(req, res) {
|
||||
if (!req.user.canDelete) {
|
||||
Logger.warn('User attempted to delete without permission', req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
var { audiobookIds } = req.body
|
||||
if (!audiobookIds || !audiobookIds.length) {
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
|
||||
var audiobooksToDelete = this.db.audiobooks.filter(ab => audiobookIds.includes(ab.id))
|
||||
if (!audiobooksToDelete.length) {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
for (let i = 0; i < audiobooksToDelete.length; i++) {
|
||||
Logger.info(`[ApiController] Deleting Audiobook "${audiobooksToDelete[i].title}"`)
|
||||
await this.handleDeleteAudiobook(audiobooksToDelete[i])
|
||||
}
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
// POST: api/books/batch/update
|
||||
async batchUpdate(req, res) {
|
||||
if (!req.user.canUpdate) {
|
||||
Logger.warn('User attempted to batch update without permission', req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
var audiobooks = req.body
|
||||
if (!audiobooks || !audiobooks.length) {
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
|
||||
var audiobooksUpdated = 0
|
||||
audiobooks = audiobooks.map((ab) => {
|
||||
var _ab = this.db.audiobooks.find(__ab => __ab.id === ab.id)
|
||||
if (!_ab) return null
|
||||
var hasUpdated = _ab.update(ab)
|
||||
if (!hasUpdated) return null
|
||||
audiobooksUpdated++
|
||||
return _ab
|
||||
}).filter(ab => ab)
|
||||
|
||||
if (audiobooksUpdated) {
|
||||
Logger.info(`[ApiController] ${audiobooksUpdated} Audiobooks have updates`)
|
||||
for (let i = 0; i < audiobooks.length; i++) {
|
||||
await this.db.updateAudiobook(audiobooks[i])
|
||||
this.emitter('audiobook_updated', audiobooks[i].toJSONMinified())
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
updates: audiobooksUpdated
|
||||
})
|
||||
}
|
||||
|
||||
// PATCH: api/books/:id/tracks
|
||||
async updateTracks(req, res) {
|
||||
if (!req.user.canUpdate) {
|
||||
Logger.warn('User attempted to update audiotracks without permission', req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
var audiobook = this.db.audiobooks.find(a => a.id === req.params.id)
|
||||
if (!audiobook) return res.sendStatus(404)
|
||||
var orderedFileData = req.body.orderedFileData
|
||||
Logger.info(`Updating audiobook tracks called ${audiobook.id}`)
|
||||
audiobook.updateAudioTracks(orderedFileData)
|
||||
await this.db.updateAudiobook(audiobook)
|
||||
this.emitter('audiobook_updated', audiobook.toJSONMinified())
|
||||
res.json(audiobook.toJSON())
|
||||
}
|
||||
|
||||
// GET: api/books/:id/stream
|
||||
openStream(req, res) {
|
||||
var audiobook = this.db.audiobooks.find(a => a.id === req.params.id)
|
||||
if (!audiobook) return res.sendStatus(404)
|
||||
|
||||
this.streamManager.openStreamApiRequest(res, req.user, audiobook)
|
||||
}
|
||||
|
||||
// POST: api/books/:id/cover
|
||||
async uploadCover(req, res) {
|
||||
if (!req.user.canUpload || !req.user.canUpdate) {
|
||||
Logger.warn('User attempted to upload a cover without permission', req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
var audiobookId = req.params.id
|
||||
var audiobook = this.db.audiobooks.find(ab => ab.id === audiobookId)
|
||||
if (!audiobook) {
|
||||
return res.status(404).send('Audiobook not found')
|
||||
}
|
||||
|
||||
var result = null
|
||||
if (req.body && req.body.url) {
|
||||
Logger.debug(`[ApiController] Requesting download cover from url "${req.body.url}"`)
|
||||
result = await this.coverController.downloadCoverFromUrl(audiobook, req.body.url)
|
||||
} else if (req.files && req.files.cover) {
|
||||
Logger.debug(`[ApiController] Handling uploaded cover`)
|
||||
var coverFile = req.files.cover
|
||||
result = await this.coverController.uploadCover(audiobook, coverFile)
|
||||
} else {
|
||||
return res.status(400).send('Invalid request no file or url')
|
||||
}
|
||||
|
||||
if (result && result.error) {
|
||||
return res.status(400).send(result.error)
|
||||
} else if (!result || !result.cover) {
|
||||
return res.status(500).send('Unknown error occurred')
|
||||
}
|
||||
|
||||
await this.db.updateAudiobook(audiobook)
|
||||
this.emitter('audiobook_updated', audiobook.toJSONMinified())
|
||||
res.json({
|
||||
success: true,
|
||||
cover: result.cover
|
||||
})
|
||||
}
|
||||
|
||||
// PATCH api/books/:id/coverfile
|
||||
async updateCoverFromFile(req, res) {
|
||||
if (!req.user.canUpdate) {
|
||||
Logger.warn('User attempted to update without permission', req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
var audiobook = this.db.audiobooks.find(a => a.id === req.params.id)
|
||||
if (!audiobook) return res.sendStatus(404)
|
||||
|
||||
var coverFile = req.body
|
||||
var updated = await audiobook.setCoverFromFile(coverFile)
|
||||
|
||||
if (updated) {
|
||||
await this.db.updateAudiobook(audiobook)
|
||||
this.emitter('audiobook_updated', audiobook.toJSONMinified())
|
||||
}
|
||||
|
||||
if (updated) res.status(200).send('Cover updated successfully')
|
||||
else res.status(200).send('No update was made to cover')
|
||||
}
|
||||
}
|
||||
module.exports = new BookController()
|
97
server/controllers/CollectionController.js
Normal file
97
server/controllers/CollectionController.js
Normal file
@ -0,0 +1,97 @@
|
||||
const Logger = require('../Logger')
|
||||
const UserCollection = require('../objects/UserCollection')
|
||||
|
||||
class CollectionController {
|
||||
constructor() { }
|
||||
|
||||
async create(req, res) {
|
||||
var newCollection = new UserCollection()
|
||||
req.body.userId = req.user.id
|
||||
var success = newCollection.setData(req.body)
|
||||
if (!success) {
|
||||
return res.status(500).send('Invalid collection data')
|
||||
}
|
||||
var jsonExpanded = newCollection.toJSONExpanded(this.db.audiobooks)
|
||||
await this.db.insertEntity('collection', newCollection)
|
||||
this.clientEmitter(req.user.id, 'collection_added', jsonExpanded)
|
||||
res.json(jsonExpanded)
|
||||
}
|
||||
|
||||
findAll(req, res) {
|
||||
var collections = this.db.collections.filter(c => c.userId === req.user.id)
|
||||
var expandedCollections = collections.map(c => c.toJSONExpanded(this.db.audiobooks))
|
||||
res.json(expandedCollections)
|
||||
}
|
||||
|
||||
findOne(req, res) {
|
||||
var collection = this.db.collections.find(c => c.id === req.params.id)
|
||||
if (!collection) {
|
||||
return res.status(404).send('Collection not found')
|
||||
}
|
||||
res.json(collection.toJSONExpanded(this.db.audiobooks))
|
||||
}
|
||||
|
||||
async update(req, res) {
|
||||
var collection = this.db.collections.find(c => c.id === req.params.id)
|
||||
if (!collection) {
|
||||
return res.status(404).send('Collection not found')
|
||||
}
|
||||
var wasUpdated = collection.update(req.body)
|
||||
var jsonExpanded = collection.toJSONExpanded(this.db.audiobooks)
|
||||
if (wasUpdated) {
|
||||
await this.db.updateEntity('collection', collection)
|
||||
this.clientEmitter(req.user.id, 'collection_updated', jsonExpanded)
|
||||
}
|
||||
res.json(jsonExpanded)
|
||||
}
|
||||
|
||||
async delete(req, res) {
|
||||
var collection = this.db.collections.find(c => c.id === req.params.id)
|
||||
if (!collection) {
|
||||
return res.status(404).send('Collection not found')
|
||||
}
|
||||
var jsonExpanded = collection.toJSONExpanded(this.db.audiobooks)
|
||||
await this.db.removeEntity('collection', collection.id)
|
||||
this.clientEmitter(req.user.id, 'collection_removed', jsonExpanded)
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
async addBook(req, res) {
|
||||
var collection = this.db.collections.find(c => c.id === req.params.id)
|
||||
if (!collection) {
|
||||
return res.status(404).send('Collection not found')
|
||||
}
|
||||
var audiobook = this.db.audiobooks.find(ab => ab.id === req.body.id)
|
||||
if (!audiobook) {
|
||||
return res.status(500).send('Book not found')
|
||||
}
|
||||
if (audiobook.libraryId !== collection.libraryId) {
|
||||
return res.status(500).send('Book in different library')
|
||||
}
|
||||
if (collection.books.includes(req.body.id)) {
|
||||
return res.status(500).send('Book already in collection')
|
||||
}
|
||||
collection.addBook(req.body.id)
|
||||
var jsonExpanded = collection.toJSONExpanded(this.db.audiobooks)
|
||||
await this.db.updateEntity('collection', collection)
|
||||
this.clientEmitter(req.user.id, 'collection_updated', jsonExpanded)
|
||||
res.json(jsonExpanded)
|
||||
}
|
||||
|
||||
// DELETE: api/collections/:id/book/:bookId
|
||||
async removeBook(req, res) {
|
||||
var collection = this.db.collections.find(c => c.id === req.params.id)
|
||||
if (!collection) {
|
||||
return res.status(404).send('Collection not found')
|
||||
}
|
||||
|
||||
if (collection.books.includes(req.params.bookId)) {
|
||||
collection.removeBook(req.params.bookId)
|
||||
var jsonExpanded = collection.toJSONExpanded(this.db.audiobooks)
|
||||
await this.db.updateEntity('collection', collection)
|
||||
this.clientEmitter(req.user.id, 'collection_updated', jsonExpanded)
|
||||
}
|
||||
res.json(collection.toJSONExpanded(this.db.audiobooks))
|
||||
}
|
||||
}
|
||||
module.exports = new CollectionController()
|
205
server/controllers/LibraryController.js
Normal file
205
server/controllers/LibraryController.js
Normal file
@ -0,0 +1,205 @@
|
||||
const Logger = require('../Logger')
|
||||
const Library = require('../objects/Library')
|
||||
|
||||
class LibraryController {
|
||||
constructor() { }
|
||||
|
||||
async create(req, res) {
|
||||
var newLibraryPayload = {
|
||||
...req.body
|
||||
}
|
||||
if (!newLibraryPayload.name || !newLibraryPayload.folders || !newLibraryPayload.folders.length) {
|
||||
return res.status(500).send('Invalid request')
|
||||
}
|
||||
|
||||
var library = new Library()
|
||||
newLibraryPayload.displayOrder = this.db.libraries.length + 1
|
||||
library.setData(newLibraryPayload)
|
||||
await this.db.insertEntity('library', library)
|
||||
this.emitter('library_added', library.toJSON())
|
||||
|
||||
// Add library watcher
|
||||
this.watcher.addLibrary(library)
|
||||
|
||||
res.json(library)
|
||||
}
|
||||
|
||||
findAll(req, res) {
|
||||
res.json(this.db.libraries.map(lib => lib.toJSON()))
|
||||
}
|
||||
|
||||
findOne(req, res) {
|
||||
if (!req.params.id) return res.status(500).send('Invalid id parameter')
|
||||
|
||||
var library = this.db.libraries.find(lib => lib.id === req.params.id)
|
||||
if (!library) {
|
||||
return res.status(404).send('Library not found')
|
||||
}
|
||||
return res.json(library.toJSON())
|
||||
}
|
||||
|
||||
async update(req, res) {
|
||||
var library = this.db.libraries.find(lib => lib.id === req.params.id)
|
||||
if (!library) {
|
||||
return res.status(404).send('Library not found')
|
||||
}
|
||||
var hasUpdates = library.update(req.body)
|
||||
if (hasUpdates) {
|
||||
// Update watcher
|
||||
this.watcher.updateLibrary(library)
|
||||
|
||||
// Remove audiobooks no longer in library
|
||||
var audiobooksToRemove = this.db.audiobooks.filter(ab => !library.checkFullPathInLibrary(ab.fullPath))
|
||||
if (audiobooksToRemove.length) {
|
||||
Logger.info(`[Scanner] Updating library, removing ${audiobooksToRemove.length} audiobooks`)
|
||||
for (let i = 0; i < audiobooksToRemove.length; i++) {
|
||||
await this.handleDeleteAudiobook(audiobooksToRemove[i])
|
||||
}
|
||||
}
|
||||
await this.db.updateEntity('library', library)
|
||||
this.emitter('library_updated', library.toJSON())
|
||||
}
|
||||
return res.json(library.toJSON())
|
||||
}
|
||||
|
||||
async delete(req, res) {
|
||||
var library = this.db.libraries.find(lib => lib.id === req.params.id)
|
||||
if (!library) {
|
||||
return res.status(404).send('Library not found')
|
||||
}
|
||||
|
||||
// Remove library watcher
|
||||
this.watcher.removeLibrary(library)
|
||||
|
||||
// Remove audiobooks in this library
|
||||
var audiobooks = this.db.audiobooks.filter(ab => ab.libraryId === library.id)
|
||||
Logger.info(`[Server] deleting library "${library.name}" with ${audiobooks.length} audiobooks"`)
|
||||
for (let i = 0; i < audiobooks.length; i++) {
|
||||
await this.handleDeleteAudiobook(audiobooks[i])
|
||||
}
|
||||
|
||||
var libraryJson = library.toJSON()
|
||||
await this.db.removeEntity('library', library.id)
|
||||
this.emitter('library_removed', libraryJson)
|
||||
return res.json(libraryJson)
|
||||
}
|
||||
|
||||
// api/libraries/:id/books
|
||||
getBooksForLibrary(req, res) {
|
||||
var libraryId = req.params.id
|
||||
var library = this.db.libraries.find(lib => lib.id === libraryId)
|
||||
if (!library) {
|
||||
return res.status(400).send('Library does not exist')
|
||||
}
|
||||
|
||||
var audiobooks = []
|
||||
if (req.query.q) {
|
||||
audiobooks = this.db.audiobooks.filter(ab => {
|
||||
return ab.libraryId === libraryId && ab.isSearchMatch(req.query.q)
|
||||
}).map(ab => ab.toJSONMinified())
|
||||
} else {
|
||||
audiobooks = this.db.audiobooks.filter(ab => ab.libraryId === libraryId).map(ab => ab.toJSONMinified())
|
||||
}
|
||||
res.json(audiobooks)
|
||||
}
|
||||
|
||||
// PATCH: Change the order of libraries
|
||||
async reorder(req, res) {
|
||||
if (!req.user.isRoot) {
|
||||
Logger.error('[ApiController] ReorderLibraries invalid user', req.user)
|
||||
return res.sendStatus(401)
|
||||
}
|
||||
|
||||
var orderdata = req.body
|
||||
var hasUpdates = false
|
||||
for (let i = 0; i < orderdata.length; i++) {
|
||||
var library = this.db.libraries.find(lib => lib.id === orderdata[i].id)
|
||||
if (!library) {
|
||||
Logger.error(`[ApiController] Invalid library not found in reorder ${orderdata[i].id}`)
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
if (library.update({ displayOrder: orderdata[i].newOrder })) {
|
||||
hasUpdates = true
|
||||
await this.db.updateEntity('library', library)
|
||||
}
|
||||
}
|
||||
|
||||
if (hasUpdates) {
|
||||
Logger.info(`[ApiController] Updated library display orders`)
|
||||
} else {
|
||||
Logger.info(`[ApiController] Library orders were up to date`)
|
||||
}
|
||||
|
||||
var libraries = this.db.libraries.map(lib => lib.toJSON())
|
||||
res.json(libraries)
|
||||
}
|
||||
|
||||
// GET: Global library search
|
||||
search(req, res) {
|
||||
var library = this.db.libraries.find(lib => lib.id === req.params.id)
|
||||
if (!library) {
|
||||
return res.status(404).send('Library not found')
|
||||
}
|
||||
if (!req.query.q) {
|
||||
return res.status(400).send('No query string')
|
||||
}
|
||||
var maxResults = req.query.max || 3
|
||||
|
||||
var bookMatches = []
|
||||
var authorMatches = {}
|
||||
var seriesMatches = {}
|
||||
var tagMatches = {}
|
||||
|
||||
var audiobooksInLibrary = this.db.audiobooks.filter(ab => ab.libraryId === library.id)
|
||||
audiobooksInLibrary.forEach((ab) => {
|
||||
var queryResult = ab.searchQuery(req.query.q)
|
||||
if (queryResult.book) {
|
||||
var bookMatchObj = {
|
||||
audiobook: ab,
|
||||
matchKey: queryResult.book,
|
||||
matchText: queryResult.bookMatchText
|
||||
}
|
||||
bookMatches.push(bookMatchObj)
|
||||
}
|
||||
if (queryResult.authors) {
|
||||
queryResult.authors.forEach((author) => {
|
||||
if (!authorMatches[author]) {
|
||||
authorMatches[author] = {
|
||||
author: author
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if (queryResult.series) {
|
||||
if (!seriesMatches[queryResult.series]) {
|
||||
seriesMatches[queryResult.series] = {
|
||||
series: queryResult.series,
|
||||
audiobooks: [ab]
|
||||
}
|
||||
} else {
|
||||
seriesMatches[queryResult.series].audiobooks.push(ab)
|
||||
}
|
||||
}
|
||||
if (queryResult.tags && queryResult.tags.length) {
|
||||
queryResult.tags.forEach((tag) => {
|
||||
if (!tagMatches[tag]) {
|
||||
tagMatches[tag] = {
|
||||
tag,
|
||||
audiobooks: [ab]
|
||||
}
|
||||
} else {
|
||||
tagMatches[tag].audiobooks.push(ab)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
res.json({
|
||||
audiobooks: bookMatches.slice(0, maxResults),
|
||||
tags: Object.values(tagMatches).slice(0, maxResults),
|
||||
authors: Object.values(authorMatches).slice(0, maxResults),
|
||||
series: Object.values(seriesMatches).slice(0, maxResults)
|
||||
})
|
||||
}
|
||||
}
|
||||
module.exports = new LibraryController()
|
96
server/controllers/MeController.js
Normal file
96
server/controllers/MeController.js
Normal file
@ -0,0 +1,96 @@
|
||||
const Logger = require('../Logger')
|
||||
const { isObject } = require('../utils/index')
|
||||
|
||||
class MeController {
|
||||
constructor() { }
|
||||
|
||||
// GET: api/me/listening-sessions
|
||||
async getListeningSessions(req, res) {
|
||||
var listeningSessions = await this.getUserListeningSessionsHelper(req.user.id)
|
||||
res.json(listeningSessions.slice(0, 10))
|
||||
}
|
||||
|
||||
// GET: api/me/listening-stats
|
||||
async getListeningStats(req, res) {
|
||||
var listeningStats = await this.getUserListeningStatsHelpers(req.user.id)
|
||||
res.json(listeningStats)
|
||||
}
|
||||
|
||||
// PATCH: api/me/audiobook/:id/reset-progress
|
||||
async resetAudiobookProgress(req, res) {
|
||||
var audiobook = this.db.audiobooks.find(ab => ab.id === req.params.id)
|
||||
if (!audiobook) {
|
||||
return res.status(404).send('Audiobook not found')
|
||||
}
|
||||
req.user.resetAudiobookProgress(audiobook)
|
||||
await this.db.updateEntity('user', req.user)
|
||||
|
||||
var userAudiobookData = req.user.audiobooks[audiobook.id]
|
||||
if (userAudiobookData) {
|
||||
this.clientEmitter(req.user.id, 'current_user_audiobook_update', { id: audiobook.id, data: userAudiobookData })
|
||||
}
|
||||
|
||||
this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
// PATCH: api/me/audiobook/:id
|
||||
async updateAudiobookData(req, res) {
|
||||
var audiobook = this.db.audiobooks.find(ab => ab.id === req.params.id)
|
||||
if (!audiobook) {
|
||||
return res.status(404).send('Audiobook not found')
|
||||
}
|
||||
var wasUpdated = req.user.updateAudiobookData(audiobook, req.body)
|
||||
if (wasUpdated) {
|
||||
await this.db.updateEntity('user', req.user)
|
||||
this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
|
||||
}
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
// PATCH: api/me/audiobook/batch/update
|
||||
async batchUpdateAudiobookData(req, res) {
|
||||
var userAbDataPayloads = req.body
|
||||
if (!userAbDataPayloads || !userAbDataPayloads.length) {
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
|
||||
var shouldUpdate = false
|
||||
userAbDataPayloads.forEach((userAbData) => {
|
||||
var audiobook = this.db.audiobooks.find(ab => ab.id === userAbData.audiobookId)
|
||||
if (audiobook) {
|
||||
var wasUpdated = req.user.updateAudiobookData(audiobook, userAbData)
|
||||
if (wasUpdated) shouldUpdate = true
|
||||
}
|
||||
})
|
||||
|
||||
if (shouldUpdate) {
|
||||
await this.db.updateEntity('user', req.user)
|
||||
this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
|
||||
}
|
||||
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
// PATCH: api/me/password
|
||||
updatePassword(req, res) {
|
||||
this.auth.userChangePassword(req, res)
|
||||
}
|
||||
|
||||
// PATCH: api/me/settings
|
||||
async updateSettings(req, res) {
|
||||
var settingsUpdate = req.body
|
||||
if (!settingsUpdate || !isObject(settingsUpdate)) {
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
var madeUpdates = req.user.updateSettings(settingsUpdate)
|
||||
if (madeUpdates) {
|
||||
await this.db.updateEntity('user', req.user)
|
||||
}
|
||||
return res.json({
|
||||
success: true,
|
||||
settings: req.user.settings
|
||||
})
|
||||
}
|
||||
}
|
||||
module.exports = new MeController()
|
152
server/controllers/UserController.js
Normal file
152
server/controllers/UserController.js
Normal file
@ -0,0 +1,152 @@
|
||||
const Logger = require('../Logger')
|
||||
const User = require('../objects/User')
|
||||
|
||||
const { getId } = require('../utils/index')
|
||||
|
||||
class UserController {
|
||||
constructor() { }
|
||||
|
||||
async create(req, res) {
|
||||
if (!req.user.isRoot) {
|
||||
Logger.warn('Non-root user attempted to create user', req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
var account = req.body
|
||||
|
||||
var username = account.username
|
||||
var usernameExists = this.db.users.find(u => u.username.toLowerCase() === username.toLowerCase())
|
||||
if (usernameExists) {
|
||||
return res.status(500).send('Username already taken')
|
||||
}
|
||||
|
||||
account.id = getId('usr')
|
||||
account.pash = await this.auth.hashPass(account.password)
|
||||
delete account.password
|
||||
account.token = await this.auth.generateAccessToken({ userId: account.id })
|
||||
account.createdAt = Date.now()
|
||||
var newUser = new User(account)
|
||||
var success = await this.db.insertEntity('user', newUser)
|
||||
if (success) {
|
||||
this.clientEmitter(req.user.id, 'user_added', newUser)
|
||||
res.json({
|
||||
user: newUser.toJSONForBrowser()
|
||||
})
|
||||
} else {
|
||||
return res.status(500).send('Failed to save new user')
|
||||
}
|
||||
}
|
||||
|
||||
findAll(req, res) {
|
||||
if (!req.user.isRoot) return res.sendStatus(403)
|
||||
var users = this.db.users.map(u => this.userJsonWithBookProgressDetails(u))
|
||||
res.json(users)
|
||||
}
|
||||
|
||||
findOne(req, res) {
|
||||
if (!req.user.isRoot) {
|
||||
Logger.error('User other than root attempting to get user', req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
var user = this.db.users.find(u => u.id === req.params.id)
|
||||
if (!user) {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
res.json(this.userJsonWithBookProgressDetails(user))
|
||||
}
|
||||
|
||||
async update(req, res) {
|
||||
if (!req.user.isRoot) {
|
||||
Logger.error('User other than root attempting to update user', req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
var user = this.db.users.find(u => u.id === req.params.id)
|
||||
if (!user) {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
var account = req.body
|
||||
|
||||
if (account.username !== undefined && account.username !== user.username) {
|
||||
var usernameExists = this.db.users.find(u => u.username.toLowerCase() === account.username.toLowerCase())
|
||||
if (usernameExists) {
|
||||
return res.status(500).send('Username already taken')
|
||||
}
|
||||
}
|
||||
|
||||
// Updating password
|
||||
if (account.password) {
|
||||
account.pash = await this.auth.hashPass(account.password)
|
||||
delete account.password
|
||||
}
|
||||
|
||||
var hasUpdated = user.update(account)
|
||||
if (hasUpdated) {
|
||||
await this.db.updateEntity('user', user)
|
||||
}
|
||||
|
||||
this.clientEmitter(req.user.id, 'user_updated', user.toJSONForBrowser())
|
||||
res.json({
|
||||
success: true,
|
||||
user: user.toJSONForBrowser()
|
||||
})
|
||||
}
|
||||
|
||||
async delete(req, res) {
|
||||
if (!req.user.isRoot) {
|
||||
Logger.error('User other than root attempting to delete user', req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
if (req.params.id === 'root') {
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
if (req.user.id === req.params.id) {
|
||||
Logger.error('Attempting to delete themselves...')
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
var user = this.db.users.find(u => u.id === req.params.id)
|
||||
if (!user) {
|
||||
Logger.error('User not found')
|
||||
return res.json({
|
||||
error: 'User not found'
|
||||
})
|
||||
}
|
||||
|
||||
// delete user collections
|
||||
var userCollections = this.db.collections.filter(c => c.userId === user.id)
|
||||
var collectionsToRemove = userCollections.map(uc => uc.id)
|
||||
for (let i = 0; i < collectionsToRemove.length; i++) {
|
||||
await this.db.removeEntity('collection', collectionsToRemove[i])
|
||||
}
|
||||
|
||||
// Todo: check if user is logged in and cancel streams
|
||||
|
||||
var userJson = user.toJSONForBrowser()
|
||||
await this.db.removeEntity('user', user.id)
|
||||
this.clientEmitter(req.user.id, 'user_removed', userJson)
|
||||
res.json({
|
||||
success: true
|
||||
})
|
||||
}
|
||||
|
||||
// GET: api/users/:id/listening-sessions
|
||||
async getListeningSessions(req, res) {
|
||||
if (!req.user.isRoot && req.user.id !== req.params.id) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
var listeningSessions = await this.getUserListeningSessionsHelper(req.params.id)
|
||||
res.json(listeningSessions.slice(0, 10))
|
||||
}
|
||||
|
||||
// GET: api/users/:id/listening-stats
|
||||
async getListeningStats(req, res) {
|
||||
if (!req.user.isRoot && req.user.id !== req.params.id) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
var listeningStats = await this.getUserListeningStatsHelpers(req.params.id)
|
||||
res.json(listeningStats)
|
||||
}
|
||||
}
|
||||
module.exports = new UserController()
|
@ -353,6 +353,7 @@ class Audiobook {
|
||||
if (imageFile) {
|
||||
data.coverFullPath = imageFile.fullPath
|
||||
var relImagePath = imageFile.path.replace(this.path, '')
|
||||
console.log('SET BOOK PATH', imageFile.path, 'REPLACE', this.path, 'RESULT', relImagePath)
|
||||
data.cover = Path.posix.join(`/s/book/${this.id}`, relImagePath)
|
||||
}
|
||||
}
|
||||
|
@ -89,10 +89,17 @@ function setFileOwner(path, uid, gid) {
|
||||
}
|
||||
module.exports.setFileOwner = setFileOwner
|
||||
|
||||
async function recurseFiles(path) {
|
||||
async function recurseFiles(path, relPathToReplace = null) {
|
||||
path = path.replace(/\\/g, '/')
|
||||
if (!path.endsWith('/')) path = path + '/'
|
||||
|
||||
if (relPathToReplace) {
|
||||
relPathToReplace = relPathToReplace.replace(/\\/g, '/')
|
||||
if (!relPathToReplace.endsWith('/')) relPathToReplace += '/'
|
||||
} else {
|
||||
relPathToReplace = path
|
||||
}
|
||||
|
||||
const options = {
|
||||
mode: rra.LIST,
|
||||
recursive: true,
|
||||
@ -116,7 +123,7 @@ async function recurseFiles(path) {
|
||||
}
|
||||
|
||||
// Ignore any file if a directory or the filename starts with "."
|
||||
var relpath = item.fullname.replace(path, '')
|
||||
var relpath = item.fullname.replace(relPathToReplace, '')
|
||||
var pathStartsWithPeriod = relpath.split('/').find(p => p.startsWith('.'))
|
||||
if (pathStartsWithPeriod) {
|
||||
Logger.debug(`[fileUtils] Ignoring path has . "${relpath}"`)
|
||||
@ -126,9 +133,9 @@ async function recurseFiles(path) {
|
||||
return true
|
||||
}).map((item) => ({
|
||||
name: item.name,
|
||||
path: item.fullname.replace(path, ''),
|
||||
path: item.fullname.replace(relPathToReplace, ''),
|
||||
dirpath: item.path,
|
||||
reldirpath: item.path.replace(path, ''),
|
||||
reldirpath: item.path.replace(relPathToReplace, ''),
|
||||
fullpath: item.fullname,
|
||||
extension: item.extension,
|
||||
deep: item.deep
|
||||
|
@ -267,7 +267,7 @@ function getAudiobookDataFromDir(folderPath, dir, parseSubtitle = false) {
|
||||
async function getAudiobookFileData(folder, audiobookPath, serverSettings = {}) {
|
||||
var parseSubtitle = !!serverSettings.scannerParseSubtitle
|
||||
|
||||
var fileItems = await recurseFiles(audiobookPath)
|
||||
var fileItems = await recurseFiles(audiobookPath, folder.fullPath)
|
||||
|
||||
audiobookPath = audiobookPath.replace(/\\/g, '/')
|
||||
var folderFullPath = folder.fullPath.replace(/\\/g, '/')
|
||||
|
Loading…
Reference in New Issue
Block a user