mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-11-04 03:17:00 -05:00 
			
		
		
		
	Adding permissions per user, add volume number sort
This commit is contained in:
		
							parent
							
								
									c8d857edb9
								
							
						
					
					
						commit
						2e82370408
					
				@ -30,8 +30,10 @@
 | 
				
			|||||||
        <ui-btn small class="text-sm mx-2" @click="toggleSelectAll">{{ isAllSelected ? 'Select None' : 'Select All' }}</ui-btn>
 | 
					        <ui-btn small class="text-sm mx-2" @click="toggleSelectAll">{{ isAllSelected ? 'Select None' : 'Select All' }}</ui-btn>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div class="flex-grow" />
 | 
					        <div class="flex-grow" />
 | 
				
			||||||
        <ui-btn v-show="!processingBatchDelete" color="warning" small class="mx-2" @click="batchEditClick"><span class="material-icons text-gray-200 pt-1">edit</span></ui-btn>
 | 
					        <template v-if="userCanUpdate">
 | 
				
			||||||
        <ui-btn color="error" small class="mx-2" :loading="processingBatchDelete" @click="batchDeleteClick"><span class="material-icons text-gray-200 pt-1">delete</span></ui-btn>
 | 
					          <ui-btn v-show="!processingBatchDelete" color="warning" small class="mx-2" @click="batchEditClick"><span class="material-icons text-gray-200 pt-1">edit</span></ui-btn>
 | 
				
			||||||
 | 
					        </template>
 | 
				
			||||||
 | 
					        <ui-btn v-if="userCanDelete" color="error" small class="mx-2" :loading="processingBatchDelete" @click="batchDeleteClick"><span class="material-icons text-gray-200 pt-1">delete</span></ui-btn>
 | 
				
			||||||
        <span class="material-icons text-4xl px-4 hover:text-gray-100 cursor-pointer" :class="processingBatchDelete ? 'text-gray-400' : ''" @click="cancelSelectionMode">close</span>
 | 
					        <span class="material-icons text-4xl px-4 hover:text-gray-100 cursor-pointer" :class="processingBatchDelete ? 'text-gray-400' : ''" @click="cancelSelectionMode">close</span>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
@ -69,6 +71,12 @@ export default {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    audiobooksShowing() {
 | 
					    audiobooksShowing() {
 | 
				
			||||||
      return this.$store.getters['audiobooks/getFiltered']()
 | 
					      return this.$store.getters['audiobooks/getFiltered']()
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    userCanUpdate() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanUpdate']
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    userCanDelete() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanDelete']
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
 | 
				
			|||||||
@ -19,13 +19,16 @@
 | 
				
			|||||||
                <span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">play_circle_filled</span>
 | 
					                <span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">play_circle_filled</span>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div v-show="!isSelectionMode" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-50" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="editClick">
 | 
					
 | 
				
			||||||
 | 
					            <div v-if="userCanUpdate" v-show="!isSelectionMode" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-50" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="editClick">
 | 
				
			||||||
              <span class="material-icons" :style="{ fontSize: sizeMultiplier + 'rem' }">edit</span>
 | 
					              <span class="material-icons" :style="{ fontSize: sizeMultiplier + 'rem' }">edit</span>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-100" :style="{ top: 0.375 * sizeMultiplier + 'rem', left: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="selectBtnClick">
 | 
					
 | 
				
			||||||
 | 
					            <div v-if="userCanUpdate || userCanDelete" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-100" :style="{ top: 0.375 * sizeMultiplier + 'rem', left: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="selectBtnClick">
 | 
				
			||||||
              <span class="material-icons" :class="selected ? 'text-yellow-400' : ''" :style="{ fontSize: 1.25 * sizeMultiplier + 'rem' }">{{ selected ? 'radio_button_checked' : 'radio_button_unchecked' }}</span>
 | 
					              <span class="material-icons" :class="selected ? 'text-yellow-400' : ''" :style="{ fontSize: 1.25 * sizeMultiplier + 'rem' }">{{ selected ? 'radio_button_checked' : 'radio_button_unchecked' }}</span>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <div v-show="!isSelectionMode" class="absolute bottom-0 left-0 h-1 shadow-sm" :class="userIsRead ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
 | 
					          <div v-show="!isSelectionMode" class="absolute bottom-0 left-0 h-1 shadow-sm" :class="userIsRead ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <ui-tooltip v-if="showError" :text="errorText" class="absolute bottom-4 left-0">
 | 
					          <ui-tooltip v-if="showError" :text="errorText" class="absolute bottom-4 left-0">
 | 
				
			||||||
@ -156,6 +159,12 @@ export default {
 | 
				
			|||||||
        classes.push('border-2 border-yellow-400')
 | 
					        classes.push('border-2 border-yellow-400')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return classes
 | 
					      return classes
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    userCanUpdate() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanUpdate']
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    userCanDelete() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanDelete']
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
 | 
				
			|||||||
@ -48,6 +48,10 @@ export default {
 | 
				
			|||||||
          text: 'Added At',
 | 
					          text: 'Added At',
 | 
				
			||||||
          value: 'addedAt'
 | 
					          value: 'addedAt'
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          text: 'Volume #',
 | 
				
			||||||
 | 
					          value: 'book.volumeNumber'
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          text: 'Duration',
 | 
					          text: 'Duration',
 | 
				
			||||||
          value: 'duration'
 | 
					          value: 'duration'
 | 
				
			||||||
 | 
				
			|||||||
@ -18,7 +18,7 @@
 | 
				
			|||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div class="flex py-2">
 | 
					          <div class="flex py-2">
 | 
				
			||||||
            <div class="px-2">
 | 
					            <div class="px-2">
 | 
				
			||||||
              <ui-input-dropdown v-model="newUser.type" label="Account Type" :disabled="isEditingRoot" :editable="false" :items="accountTypes" />
 | 
					              <ui-input-dropdown v-model="newUser.type" label="Account Type" :disabled="isEditingRoot" :editable="false" :items="accountTypes" @input="userTypeUpdated" />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="flex-grow" />
 | 
					            <div class="flex-grow" />
 | 
				
			||||||
            <div v-show="!isEditingRoot" class="flex items-center pt-4 px-2">
 | 
					            <div v-show="!isEditingRoot" class="flex items-center pt-4 px-2">
 | 
				
			||||||
@ -26,6 +26,37 @@
 | 
				
			|||||||
              <ui-toggle-switch v-model="newUser.isActive" :disabled="isEditingRoot" />
 | 
					              <ui-toggle-switch v-model="newUser.isActive" :disabled="isEditingRoot" />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <div v-if="!isEditingRoot && newUser.permissions" class="w-full border-t border-b border-black-200 py-2 mt-4">
 | 
				
			||||||
 | 
					            <p class="text-lg mb-2">Permissions</p>
 | 
				
			||||||
 | 
					            <div class="flex items-center my-2 max-w-lg">
 | 
				
			||||||
 | 
					              <div class="w-1/2">
 | 
				
			||||||
 | 
					                <p>Can Download</p>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					              <div class="w-1/2">
 | 
				
			||||||
 | 
					                <ui-toggle-switch v-model="newUser.permissions.download" />
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="flex items-center my-2 max-w-lg">
 | 
				
			||||||
 | 
					              <div class="w-1/2">
 | 
				
			||||||
 | 
					                <p>Can Update</p>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					              <div class="w-1/2">
 | 
				
			||||||
 | 
					                <ui-toggle-switch v-model="newUser.permissions.update" />
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="flex items-center my-2 max-w-lg">
 | 
				
			||||||
 | 
					              <div class="w-1/2">
 | 
				
			||||||
 | 
					                <p>Can Delete</p>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					              <div class="w-1/2">
 | 
				
			||||||
 | 
					                <ui-toggle-switch v-model="newUser.permissions.delete" />
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <div class="flex pt-4">
 | 
					          <div class="flex pt-4">
 | 
				
			||||||
            <div class="flex-grow" />
 | 
					            <div class="flex-grow" />
 | 
				
			||||||
            <ui-btn color="success" type="submit">Submit</ui-btn>
 | 
					            <ui-btn color="success" type="submit">Submit</ui-btn>
 | 
				
			||||||
@ -144,6 +175,13 @@ export default {
 | 
				
			|||||||
    toggleActive() {
 | 
					    toggleActive() {
 | 
				
			||||||
      this.newUser.isActive = !this.newUser.isActive
 | 
					      this.newUser.isActive = !this.newUser.isActive
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    userTypeUpdated(type) {
 | 
				
			||||||
 | 
					      this.newUser.permissions = {
 | 
				
			||||||
 | 
					        download: type !== 'guest',
 | 
				
			||||||
 | 
					        update: type === 'admin',
 | 
				
			||||||
 | 
					        delete: type === 'admin'
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    init() {
 | 
					    init() {
 | 
				
			||||||
      this.isNew = !this.account
 | 
					      this.isNew = !this.account
 | 
				
			||||||
      if (this.account) {
 | 
					      if (this.account) {
 | 
				
			||||||
@ -151,14 +189,20 @@ export default {
 | 
				
			|||||||
          username: this.account.username,
 | 
					          username: this.account.username,
 | 
				
			||||||
          password: this.account.password,
 | 
					          password: this.account.password,
 | 
				
			||||||
          type: this.account.type,
 | 
					          type: this.account.type,
 | 
				
			||||||
          isActive: this.account.isActive
 | 
					          isActive: this.account.isActive,
 | 
				
			||||||
 | 
					          permissions: { ...this.account.permissions }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        this.newUser = {
 | 
					        this.newUser = {
 | 
				
			||||||
          username: null,
 | 
					          username: null,
 | 
				
			||||||
          password: null,
 | 
					          password: null,
 | 
				
			||||||
          type: 'user',
 | 
					          type: 'user',
 | 
				
			||||||
          isActive: true
 | 
					          isActive: true,
 | 
				
			||||||
 | 
					          permissions: {
 | 
				
			||||||
 | 
					            download: true,
 | 
				
			||||||
 | 
					            update: false,
 | 
				
			||||||
 | 
					            delete: false
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,7 @@
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </template>
 | 
					    </template>
 | 
				
			||||||
    <div class="absolute -top-10 left-0 w-full flex">
 | 
					    <div class="absolute -top-10 left-0 w-full flex">
 | 
				
			||||||
      <template v-for="tab in tabs">
 | 
					      <template v-for="tab in availableTabs">
 | 
				
			||||||
        <div :key="tab.id" class="w-28 rounded-t-lg flex items-center justify-center mr-1 cursor-pointer hover:bg-bg font-book border-t border-l border-r border-black-300 tab" :class="selectedTab === tab.id ? 'tab-selected bg-bg pb-px' : 'bg-primary text-gray-400'" @click="selectTab(tab.id)">{{ tab.title }}</div>
 | 
					        <div :key="tab.id" class="w-28 rounded-t-lg flex items-center justify-center mr-1 cursor-pointer hover:bg-bg font-book border-t border-l border-r border-black-300 tab" :class="selectedTab === tab.id ? 'tab-selected bg-bg pb-px' : 'bg-primary text-gray-400'" @click="selectTab(tab.id)">{{ tab.title }}</div>
 | 
				
			||||||
      </template>
 | 
					      </template>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
@ -58,6 +58,15 @@ export default {
 | 
				
			|||||||
    show: {
 | 
					    show: {
 | 
				
			||||||
      handler(newVal) {
 | 
					      handler(newVal) {
 | 
				
			||||||
        if (newVal) {
 | 
					        if (newVal) {
 | 
				
			||||||
 | 
					          var availableTabIds = this.availableTabs.map((tab) => tab.id)
 | 
				
			||||||
 | 
					          if (!availableTabIds.length) {
 | 
				
			||||||
 | 
					            this.show = false
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          if (!availableTabIds.includes(this.selectedTab)) {
 | 
				
			||||||
 | 
					            this.selectedTab = availableTabIds[0]
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if (this.audiobook && this.audiobook.id === this.selectedAudiobookId) {
 | 
					          if (this.audiobook && this.audiobook.id === this.selectedAudiobookId) {
 | 
				
			||||||
            if (this.fetchOnShow) this.fetchFull()
 | 
					            if (this.fetchOnShow) this.fetchFull()
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
@ -86,6 +95,20 @@ export default {
 | 
				
			|||||||
        this.$store.commit('setEditModalTab', val)
 | 
					        this.$store.commit('setEditModalTab', val)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    userCanUpdate() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanUpdate']
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    userCanDownload() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanDownload']
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    availableTabs() {
 | 
				
			||||||
 | 
					      if (!this.userCanUpdate && !this.userCanDownload) return []
 | 
				
			||||||
 | 
					      return this.tabs.filter((tab) => {
 | 
				
			||||||
 | 
					        if ((tab.id === 'download' || tab.id === 'tracks') && this.userCanDownload) return true
 | 
				
			||||||
 | 
					        if (tab.id !== 'download' && tab.id !== 'tracks' && this.userCanUpdate) return true
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    height() {
 | 
					    height() {
 | 
				
			||||||
      var maxHeightAllowed = window.innerHeight - 150
 | 
					      var maxHeightAllowed = window.innerHeight - 150
 | 
				
			||||||
      return Math.min(maxHeightAllowed, 650)
 | 
					      return Math.min(maxHeightAllowed, 650)
 | 
				
			||||||
 | 
				
			|||||||
@ -54,7 +54,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      <div class="absolute bottom-0 left-0 w-full py-4 bg-bg" :class="isScrollable ? 'box-shadow-md-up' : 'box-shadow-sm-up border-t border-primary border-opacity-50'">
 | 
					      <div class="absolute bottom-0 left-0 w-full py-4 bg-bg" :class="isScrollable ? 'box-shadow-md-up' : 'box-shadow-sm-up border-t border-primary border-opacity-50'">
 | 
				
			||||||
        <div class="flex px-4">
 | 
					        <div class="flex px-4">
 | 
				
			||||||
          <ui-btn color="error" type="button" small @click.stop.prevent="deleteAudiobook">Remove</ui-btn>
 | 
					          <ui-btn v-if="userCanDelete" color="error" type="button" small @click.stop.prevent="deleteAudiobook">Remove</ui-btn>
 | 
				
			||||||
          <div class="flex-grow" />
 | 
					          <div class="flex-grow" />
 | 
				
			||||||
          <ui-btn type="submit">Submit</ui-btn>
 | 
					          <ui-btn type="submit">Submit</ui-btn>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
@ -113,12 +113,9 @@ export default {
 | 
				
			|||||||
    book() {
 | 
					    book() {
 | 
				
			||||||
      return this.audiobook ? this.audiobook.book || {} : {}
 | 
					      return this.audiobook ? this.audiobook.book || {} : {}
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    // userAudiobook() {
 | 
					    userCanDelete() {
 | 
				
			||||||
    //   return this.$store.getters['user/getUserAudiobook'](this.audiobookId)
 | 
					      return this.$store.getters['user/getUserCanDelete']
 | 
				
			||||||
    // },
 | 
					    },
 | 
				
			||||||
    // userProgress() {
 | 
					 | 
				
			||||||
    //   return this.userAudiobook ? this.userAudiobook.progress : 0
 | 
					 | 
				
			||||||
    // },
 | 
					 | 
				
			||||||
    genres() {
 | 
					    genres() {
 | 
				
			||||||
      return this.$store.state.audiobooks.genres
 | 
					      return this.$store.state.audiobooks.genres
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div class="w-full h-full overflow-y-auto overflow-x-hidden px-4 py-6">
 | 
					  <div class="w-full h-full overflow-y-auto overflow-x-hidden px-4 py-6">
 | 
				
			||||||
    <div class="flex mb-4">
 | 
					    <div class="flex mb-4">
 | 
				
			||||||
      <nuxt-link :to="`/audiobook/${audiobook.id}/edit`">
 | 
					      <nuxt-link v-if="userCanUpdate" :to="`/audiobook/${audiobook.id}/edit`">
 | 
				
			||||||
        <ui-btn color="primary">Edit Track Order</ui-btn>
 | 
					        <ui-btn color="primary">Edit Track Order</ui-btn>
 | 
				
			||||||
      </nuxt-link>
 | 
					      </nuxt-link>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
@ -11,7 +11,7 @@
 | 
				
			|||||||
        <th class="text-left">Filename</th>
 | 
					        <th class="text-left">Filename</th>
 | 
				
			||||||
        <th class="text-left">Size</th>
 | 
					        <th class="text-left">Size</th>
 | 
				
			||||||
        <th class="text-left">Duration</th>
 | 
					        <th class="text-left">Duration</th>
 | 
				
			||||||
        <th class="text-center">Download</th>
 | 
					        <th v-if="userCanDownload" class="text-center">Download</th>
 | 
				
			||||||
      </tr>
 | 
					      </tr>
 | 
				
			||||||
      <template v-for="track in tracks">
 | 
					      <template v-for="track in tracks">
 | 
				
			||||||
        <tr :key="track.index">
 | 
					        <tr :key="track.index">
 | 
				
			||||||
@ -27,7 +27,7 @@
 | 
				
			|||||||
          <td class="font-mono">
 | 
					          <td class="font-mono">
 | 
				
			||||||
            {{ $secondsToTimestamp(track.duration) }}
 | 
					            {{ $secondsToTimestamp(track.duration) }}
 | 
				
			||||||
          </td>
 | 
					          </td>
 | 
				
			||||||
          <td class="font-mono text-center">
 | 
					          <td v-if="userCanDownload" class="font-mono text-center">
 | 
				
			||||||
            <a :href="`/local/${track.path}`" download><span class="material-icons icon-text">download</span></a>
 | 
					            <a :href="`/local/${track.path}`" download><span class="material-icons icon-text">download</span></a>
 | 
				
			||||||
          </td>
 | 
					          </td>
 | 
				
			||||||
        </tr>
 | 
					        </tr>
 | 
				
			||||||
@ -58,12 +58,18 @@ export default {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  computed: {},
 | 
					  computed: {
 | 
				
			||||||
 | 
					    userCanUpdate() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanUpdate']
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    userCanDownload() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanDownload']
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
    init() {
 | 
					    init() {
 | 
				
			||||||
      this.audioFiles = this.audiobook.audioFiles
 | 
					      this.audioFiles = this.audiobook.audioFiles
 | 
				
			||||||
      this.tracks = this.audiobook.tracks
 | 
					      this.tracks = this.audiobook.tracks
 | 
				
			||||||
      console.log('INIT', this.audiobook)
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,7 @@
 | 
				
			|||||||
      <p class="pr-4">Other Audio Files</p>
 | 
					      <p class="pr-4">Other Audio Files</p>
 | 
				
			||||||
      <span class="bg-black-400 rounded-xl py-1 px-2 text-sm font-mono">{{ files.length }}</span>
 | 
					      <span class="bg-black-400 rounded-xl py-1 px-2 text-sm font-mono">{{ files.length }}</span>
 | 
				
			||||||
      <div class="flex-grow" />
 | 
					      <div class="flex-grow" />
 | 
				
			||||||
      <nuxt-link :to="`/audiobook/${audiobookId}/edit`" class="mr-4">
 | 
					      <nuxt-link v-if="userCanUpdate" :to="`/audiobook/${audiobookId}/edit`" class="mr-4">
 | 
				
			||||||
        <ui-btn small color="primary">Manage Tracks</ui-btn>
 | 
					        <ui-btn small color="primary">Manage Tracks</ui-btn>
 | 
				
			||||||
      </nuxt-link>
 | 
					      </nuxt-link>
 | 
				
			||||||
      <div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showTracks ? 'transform rotate-180' : ''">
 | 
					      <div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showTracks ? 'transform rotate-180' : ''">
 | 
				
			||||||
@ -56,7 +56,11 @@ export default {
 | 
				
			|||||||
      showTracks: false
 | 
					      showTracks: false
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  computed: {},
 | 
					  computed: {
 | 
				
			||||||
 | 
					    userCanUpdate() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanUpdate']
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
    clickBar() {
 | 
					    clickBar() {
 | 
				
			||||||
      this.showTracks = !this.showTracks
 | 
					      this.showTracks = !this.showTracks
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,7 @@
 | 
				
			|||||||
      <p class="pr-4">Audio Tracks</p>
 | 
					      <p class="pr-4">Audio Tracks</p>
 | 
				
			||||||
      <span class="bg-black-400 rounded-xl py-1 px-2 text-sm font-mono">{{ tracks.length }}</span>
 | 
					      <span class="bg-black-400 rounded-xl py-1 px-2 text-sm font-mono">{{ tracks.length }}</span>
 | 
				
			||||||
      <div class="flex-grow" />
 | 
					      <div class="flex-grow" />
 | 
				
			||||||
      <nuxt-link :to="`/audiobook/${audiobookId}/edit`" class="mr-4">
 | 
					      <nuxt-link v-if="userCanUpdate" :to="`/audiobook/${audiobookId}/edit`" class="mr-4">
 | 
				
			||||||
        <ui-btn small color="primary">Manage Tracks</ui-btn>
 | 
					        <ui-btn small color="primary">Manage Tracks</ui-btn>
 | 
				
			||||||
      </nuxt-link>
 | 
					      </nuxt-link>
 | 
				
			||||||
      <div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showTracks ? 'transform rotate-180' : ''">
 | 
					      <div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showTracks ? 'transform rotate-180' : ''">
 | 
				
			||||||
@ -19,7 +19,7 @@
 | 
				
			|||||||
            <th class="text-left">Filename</th>
 | 
					            <th class="text-left">Filename</th>
 | 
				
			||||||
            <th class="text-left">Size</th>
 | 
					            <th class="text-left">Size</th>
 | 
				
			||||||
            <th class="text-left">Duration</th>
 | 
					            <th class="text-left">Duration</th>
 | 
				
			||||||
            <th class="text-center">Download</th>
 | 
					            <th v-if="userCanDownload" class="text-center">Download</th>
 | 
				
			||||||
          </tr>
 | 
					          </tr>
 | 
				
			||||||
          <template v-for="track in tracks">
 | 
					          <template v-for="track in tracks">
 | 
				
			||||||
            <tr :key="track.index">
 | 
					            <tr :key="track.index">
 | 
				
			||||||
@ -35,7 +35,7 @@
 | 
				
			|||||||
              <td class="font-mono">
 | 
					              <td class="font-mono">
 | 
				
			||||||
                {{ $secondsToTimestamp(track.duration) }}
 | 
					                {{ $secondsToTimestamp(track.duration) }}
 | 
				
			||||||
              </td>
 | 
					              </td>
 | 
				
			||||||
              <td class="text-center">
 | 
					              <td v-if="userCanDownload" class="text-center">
 | 
				
			||||||
                <a :href="`/local/${track.path}`" download><span class="material-icons icon-text">download</span></a>
 | 
					                <a :href="`/local/${track.path}`" download><span class="material-icons icon-text">download</span></a>
 | 
				
			||||||
              </td>
 | 
					              </td>
 | 
				
			||||||
            </tr>
 | 
					            </tr>
 | 
				
			||||||
@ -60,7 +60,14 @@ export default {
 | 
				
			|||||||
      showTracks: false
 | 
					      showTracks: false
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  computed: {},
 | 
					  computed: {
 | 
				
			||||||
 | 
					    userCanDownload() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanDownload']
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    userCanUpdate() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanUpdate']
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
    clickBar() {
 | 
					    clickBar() {
 | 
				
			||||||
      this.showTracks = !this.showTracks
 | 
					      this.showTracks = !this.showTracks
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "audiobookshelf-client",
 | 
					  "name": "audiobookshelf-client",
 | 
				
			||||||
  "version": "1.0.7",
 | 
					  "version": "1.0.8",
 | 
				
			||||||
  "description": "Audiobook manager and player",
 | 
					  "description": "Audiobook manager and player",
 | 
				
			||||||
  "main": "index.js",
 | 
					  "main": "index.js",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
 | 
				
			|||||||
@ -71,6 +71,9 @@ export default {
 | 
				
			|||||||
    if (!store.state.user.user) {
 | 
					    if (!store.state.user.user) {
 | 
				
			||||||
      return redirect(`/login?redirect=${route.path}`)
 | 
					      return redirect(`/login?redirect=${route.path}`)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    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/audiobook/${params.id}`).catch((error) => {
 | 
				
			||||||
      console.error('Failed', error)
 | 
					      console.error('Failed', error)
 | 
				
			||||||
      return false
 | 
					      return false
 | 
				
			||||||
@ -82,7 +85,6 @@ export default {
 | 
				
			|||||||
    return {
 | 
					    return {
 | 
				
			||||||
      audiobook,
 | 
					      audiobook,
 | 
				
			||||||
      files: audiobook.audioFiles ? audiobook.audioFiles.map((af) => ({ ...af, include: !af.exclude })) : []
 | 
					      files: audiobook.audioFiles ? audiobook.audioFiles.map((af) => ({ ...af, include: !af.exclude })) : []
 | 
				
			||||||
      // files: audiobook.audioFiles ? audiobook.audioFiles.map((af) => ({ ...af, index: ++index })) : []
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  data() {
 | 
					  data() {
 | 
				
			||||||
 | 
				
			|||||||
@ -36,11 +36,11 @@
 | 
				
			|||||||
              {{ streaming ? 'Streaming' : 'Play' }}
 | 
					              {{ streaming ? 'Streaming' : 'Play' }}
 | 
				
			||||||
            </ui-btn>
 | 
					            </ui-btn>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <ui-tooltip text="Edit" direction="top">
 | 
					            <ui-tooltip v-if="userCanUpdate" text="Edit" direction="top">
 | 
				
			||||||
              <ui-icon-btn icon="edit" class="mx-0.5" @click="editClick" />
 | 
					              <ui-icon-btn icon="edit" class="mx-0.5" @click="editClick" />
 | 
				
			||||||
            </ui-tooltip>
 | 
					            </ui-tooltip>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <ui-tooltip text="Download" direction="top">
 | 
					            <ui-tooltip v-if="userCanDownload" text="Download" direction="top">
 | 
				
			||||||
              <ui-icon-btn icon="download" class="mx-0.5" @click="downloadClick" />
 | 
					              <ui-icon-btn icon="download" class="mx-0.5" @click="downloadClick" />
 | 
				
			||||||
            </ui-tooltip>
 | 
					            </ui-tooltip>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -240,6 +240,15 @@ export default {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    streaming() {
 | 
					    streaming() {
 | 
				
			||||||
      return this.streamAudiobook && this.streamAudiobook.id === this.audiobookId
 | 
					      return this.streamAudiobook && this.streamAudiobook.id === this.audiobookId
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    userCanUpdate() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanUpdate']
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    userCanDelete() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanDelete']
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    userCanDownload() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanDownload']
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,10 @@
 | 
				
			|||||||
import Vue from "vue";
 | 
					import Vue from "vue"
 | 
				
			||||||
import Toast from "vue-toastification";
 | 
					import Toast from "vue-toastification"
 | 
				
			||||||
import "vue-toastification/dist/index.css";
 | 
					import "vue-toastification/dist/index.css"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const options = {
 | 
					const options = {
 | 
				
			||||||
  hideProgressBar: true
 | 
					  hideProgressBar: true,
 | 
				
			||||||
};
 | 
					  draggable: false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Vue.use(Toast, options)
 | 
				
			||||||
Vue.use(Toast, options);
 | 
					 | 
				
			||||||
@ -24,6 +24,15 @@ export const getters = {
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  getFilterOrderKey: (state) => {
 | 
					  getFilterOrderKey: (state) => {
 | 
				
			||||||
    return Object.values(state.settings).join('-')
 | 
					    return Object.values(state.settings).join('-')
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  getUserCanUpdate: (state) => {
 | 
				
			||||||
 | 
					    return state.user && state.user.permissions ? !!state.user.permissions.update : false
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  getUserCanDelete: (state) => {
 | 
				
			||||||
 | 
					    return state.user && state.user.permissions ? !!state.user.permissions.delete : false
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  getUserCanDownload: (state) => {
 | 
				
			||||||
 | 
					    return state.user && state.user.permissions ? !!state.user.permissions.download : false
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "audiobookshelf",
 | 
					  "name": "audiobookshelf",
 | 
				
			||||||
  "version": "1.0.7",
 | 
					  "version": "1.0.8",
 | 
				
			||||||
  "description": "Self-hosted audiobook server for managing and playing audiobooks.",
 | 
					  "description": "Self-hosted audiobook server for managing and playing audiobooks.",
 | 
				
			||||||
  "main": "index.js",
 | 
					  "main": "index.js",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
 | 
				
			|||||||
@ -89,6 +89,10 @@ class ApiController {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async deleteAllAudiobooks(req, res) {
 | 
					  async deleteAllAudiobooks(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')
 | 
					    Logger.info('Removing all Audiobooks')
 | 
				
			||||||
    var success = await this.db.recreateAudiobookDb()
 | 
					    var success = await this.db.recreateAudiobookDb()
 | 
				
			||||||
    if (success) res.sendStatus(200)
 | 
					    if (success) res.sendStatus(200)
 | 
				
			||||||
@ -130,6 +134,10 @@ class ApiController {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async deleteAudiobook(req, res) {
 | 
					  async deleteAudiobook(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)
 | 
					    var audiobook = this.db.audiobooks.find(a => a.id === req.params.id)
 | 
				
			||||||
    if (!audiobook) return res.sendStatus(404)
 | 
					    if (!audiobook) return res.sendStatus(404)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -138,6 +146,10 @@ class ApiController {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async batchDeleteAudiobooks(req, res) {
 | 
					  async batchDeleteAudiobooks(req, res) {
 | 
				
			||||||
 | 
					    if (!req.user.canDelete) {
 | 
				
			||||||
 | 
					      Logger.warn('User attempted to delete without permission', req.user)
 | 
				
			||||||
 | 
					      return res.sendStatus(403)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    var { audiobookIds } = req.body
 | 
					    var { audiobookIds } = req.body
 | 
				
			||||||
    if (!audiobookIds || !audiobookIds.length) {
 | 
					    if (!audiobookIds || !audiobookIds.length) {
 | 
				
			||||||
      return res.sendStatus(500)
 | 
					      return res.sendStatus(500)
 | 
				
			||||||
@ -155,6 +167,10 @@ class ApiController {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async batchUpdateAudiobooks(req, res) {
 | 
					  async batchUpdateAudiobooks(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
 | 
					    var audiobooks = req.body
 | 
				
			||||||
    if (!audiobooks || !audiobooks.length) {
 | 
					    if (!audiobooks || !audiobooks.length) {
 | 
				
			||||||
      return res.sendStatus(500)
 | 
					      return res.sendStatus(500)
 | 
				
			||||||
@ -185,6 +201,10 @@ class ApiController {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async updateAudiobookTracks(req, res) {
 | 
					  async updateAudiobookTracks(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)
 | 
					    var audiobook = this.db.audiobooks.find(a => a.id === req.params.id)
 | 
				
			||||||
    if (!audiobook) return res.sendStatus(404)
 | 
					    if (!audiobook) return res.sendStatus(404)
 | 
				
			||||||
    var orderedFileData = req.body.orderedFileData
 | 
					    var orderedFileData = req.body.orderedFileData
 | 
				
			||||||
@ -196,6 +216,10 @@ class ApiController {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async updateAudiobook(req, res) {
 | 
					  async updateAudiobook(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)
 | 
					    var audiobook = this.db.audiobooks.find(a => a.id === req.params.id)
 | 
				
			||||||
    if (!audiobook) return res.sendStatus(404)
 | 
					    if (!audiobook) return res.sendStatus(404)
 | 
				
			||||||
    var hasUpdates = audiobook.update(req.body)
 | 
					    var hasUpdates = audiobook.update(req.body)
 | 
				
			||||||
@ -276,6 +300,10 @@ class ApiController {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async createUser(req, res) {
 | 
					  async createUser(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 account = req.body
 | 
				
			||||||
    account.id = (Math.trunc(Math.random() * 1000) + Date.now()).toString(36)
 | 
					    account.id = (Math.trunc(Math.random() * 1000) + Date.now()).toString(36)
 | 
				
			||||||
    account.pash = await this.auth.hashPass(account.password)
 | 
					    account.pash = await this.auth.hashPass(account.password)
 | 
				
			||||||
@ -297,7 +325,7 @@ class ApiController {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async updateUser(req, res) {
 | 
					  async updateUser(req, res) {
 | 
				
			||||||
    if (req.user.type !== 'root') {
 | 
					    if (!req.user.isRoot) {
 | 
				
			||||||
      Logger.error('User other than root attempting to update user', req.user)
 | 
					      Logger.error('User other than root attempting to update user', req.user)
 | 
				
			||||||
      return res.sendStatus(403)
 | 
					      return res.sendStatus(403)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -327,6 +355,10 @@ class ApiController {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async deleteUser(req, res) {
 | 
					  async deleteUser(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') {
 | 
					    if (req.params.id === 'root') {
 | 
				
			||||||
      return res.sendStatus(500)
 | 
					      return res.sendStatus(500)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -353,6 +385,10 @@ class ApiController {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async updateServerSettings(req, res) {
 | 
					  async updateServerSettings(req, res) {
 | 
				
			||||||
 | 
					    if (!req.user.isRoot) {
 | 
				
			||||||
 | 
					      Logger.error('User other than root attempting to update server settings', req.user)
 | 
				
			||||||
 | 
					      return res.sendStatus(403)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    var settingsUpdate = req.body
 | 
					    var settingsUpdate = req.body
 | 
				
			||||||
    if (!settingsUpdate || !isObject(settingsUpdate)) {
 | 
					    if (!settingsUpdate || !isObject(settingsUpdate)) {
 | 
				
			||||||
      return res.sendStatus(500)
 | 
					      return res.sendStatus(500)
 | 
				
			||||||
@ -368,6 +404,10 @@ class ApiController {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async download(req, res) {
 | 
					  async download(req, res) {
 | 
				
			||||||
 | 
					    if (!req.user.canDownload) {
 | 
				
			||||||
 | 
					      Logger.error('User attempting to download without permission', req.user)
 | 
				
			||||||
 | 
					      return res.sendStatus(403)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    var downloadId = req.params.id
 | 
					    var downloadId = req.params.id
 | 
				
			||||||
    Logger.info('Download Request', downloadId)
 | 
					    Logger.info('Download Request', downloadId)
 | 
				
			||||||
    var download = this.downloadManager.getDownload(downloadId)
 | 
					    var download = this.downloadManager.getDownload(downloadId)
 | 
				
			||||||
 | 
				
			|||||||
@ -75,11 +75,11 @@ class Db {
 | 
				
			|||||||
  async load() {
 | 
					  async load() {
 | 
				
			||||||
    var p1 = this.audiobooksDb.select(() => true).then((results) => {
 | 
					    var p1 = this.audiobooksDb.select(() => true).then((results) => {
 | 
				
			||||||
      this.audiobooks = results.data.map(a => new Audiobook(a))
 | 
					      this.audiobooks = results.data.map(a => new Audiobook(a))
 | 
				
			||||||
      Logger.info(`Audiobooks Loaded ${this.audiobooks.length}`)
 | 
					      Logger.info(`[DB] Audiobooks Loaded ${this.audiobooks.length}`)
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    var p2 = this.usersDb.select(() => true).then((results) => {
 | 
					    var p2 = this.usersDb.select(() => true).then((results) => {
 | 
				
			||||||
      this.users = results.data.map(u => new User(u))
 | 
					      this.users = results.data.map(u => new User(u))
 | 
				
			||||||
      Logger.info(`Users Loaded ${this.users.length}`)
 | 
					      Logger.info(`[DB] Users Loaded ${this.users.length}`)
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    var p3 = this.settingsDb.select(() => true).then((results) => {
 | 
					    var p3 = this.settingsDb.select(() => true).then((results) => {
 | 
				
			||||||
      if (results.data && results.data.length) {
 | 
					      if (results.data && results.data.length) {
 | 
				
			||||||
 | 
				
			|||||||
@ -11,13 +11,28 @@ class User {
 | 
				
			|||||||
    this.isActive = true
 | 
					    this.isActive = true
 | 
				
			||||||
    this.createdAt = null
 | 
					    this.createdAt = null
 | 
				
			||||||
    this.audiobooks = null
 | 
					    this.audiobooks = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.settings = {}
 | 
					    this.settings = {}
 | 
				
			||||||
 | 
					    this.permissions = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (user) {
 | 
					    if (user) {
 | 
				
			||||||
      this.construct(user)
 | 
					      this.construct(user)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get isRoot() {
 | 
				
			||||||
 | 
					    return this.type === 'root'
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  get canDelete() {
 | 
				
			||||||
 | 
					    return !!this.permissions.delete
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  get canUpdate() {
 | 
				
			||||||
 | 
					    return !!this.permissions.update
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  get canDownload() {
 | 
				
			||||||
 | 
					    return !!this.permissions.download
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getDefaultUserSettings() {
 | 
					  getDefaultUserSettings() {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      orderBy: 'book.title',
 | 
					      orderBy: 'book.title',
 | 
				
			||||||
@ -28,6 +43,14 @@ class User {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getDefaultUserPermissions() {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      download: true,
 | 
				
			||||||
 | 
					      update: true,
 | 
				
			||||||
 | 
					      delete: this.id === 'root'
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  audiobooksToJSON() {
 | 
					  audiobooksToJSON() {
 | 
				
			||||||
    if (!this.audiobooks) return null
 | 
					    if (!this.audiobooks) return null
 | 
				
			||||||
    var _map = {}
 | 
					    var _map = {}
 | 
				
			||||||
@ -50,7 +73,8 @@ class User {
 | 
				
			|||||||
      audiobooks: this.audiobooksToJSON(),
 | 
					      audiobooks: this.audiobooksToJSON(),
 | 
				
			||||||
      isActive: this.isActive,
 | 
					      isActive: this.isActive,
 | 
				
			||||||
      createdAt: this.createdAt,
 | 
					      createdAt: this.createdAt,
 | 
				
			||||||
      settings: this.settings
 | 
					      settings: this.settings,
 | 
				
			||||||
 | 
					      permissions: this.permissions
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -64,7 +88,8 @@ class User {
 | 
				
			|||||||
      audiobooks: this.audiobooksToJSON(),
 | 
					      audiobooks: this.audiobooksToJSON(),
 | 
				
			||||||
      isActive: this.isActive,
 | 
					      isActive: this.isActive,
 | 
				
			||||||
      createdAt: this.createdAt,
 | 
					      createdAt: this.createdAt,
 | 
				
			||||||
      settings: this.settings
 | 
					      settings: this.settings,
 | 
				
			||||||
 | 
					      permissions: this.permissions
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -86,10 +111,12 @@ class User {
 | 
				
			|||||||
    this.isActive = (user.isActive === undefined || user.id === 'root') ? true : !!user.isActive
 | 
					    this.isActive = (user.isActive === undefined || user.id === 'root') ? true : !!user.isActive
 | 
				
			||||||
    this.createdAt = user.createdAt || Date.now()
 | 
					    this.createdAt = user.createdAt || Date.now()
 | 
				
			||||||
    this.settings = user.settings || this.getDefaultUserSettings()
 | 
					    this.settings = user.settings || this.getDefaultUserSettings()
 | 
				
			||||||
 | 
					    this.permissions = user.permissions || this.getDefaultUserPermissions()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  update(payload) {
 | 
					  update(payload) {
 | 
				
			||||||
    var hasUpdates = false
 | 
					    var hasUpdates = false
 | 
				
			||||||
 | 
					    // Update the following keys:
 | 
				
			||||||
    const keysToCheck = ['pash', 'type', 'username', 'isActive']
 | 
					    const keysToCheck = ['pash', 'type', 'username', 'isActive']
 | 
				
			||||||
    keysToCheck.forEach((key) => {
 | 
					    keysToCheck.forEach((key) => {
 | 
				
			||||||
      if (payload[key] !== undefined) {
 | 
					      if (payload[key] !== undefined) {
 | 
				
			||||||
@ -101,6 +128,15 @@ class User {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					    // And update permissions
 | 
				
			||||||
 | 
					    if (payload.permissions) {
 | 
				
			||||||
 | 
					      for (const key in payload.permissions) {
 | 
				
			||||||
 | 
					        if (payload.permissions[key] !== this.permissions[key]) {
 | 
				
			||||||
 | 
					          hasUpdates = true
 | 
				
			||||||
 | 
					          this.permissions[key] = payload.permissions[key]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    return hasUpdates
 | 
					    return hasUpdates
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user