mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18: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