mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-13 01:18:30 +01:00
Adding permissions per user, add volume number sort
This commit is contained in:
parent
1d7d2a1dac
commit
ee452d41ee
@ -30,8 +30,10 @@
|
||||
<ui-btn small class="text-sm mx-2" @click="toggleSelectAll">{{ isAllSelected ? 'Select None' : 'Select All' }}</ui-btn>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
<template v-if="userCanUpdate">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@ -69,6 +71,12 @@ export default {
|
||||
},
|
||||
audiobooksShowing() {
|
||||
return this.$store.getters['audiobooks/getFiltered']()
|
||||
},
|
||||
userCanUpdate() {
|
||||
return this.$store.getters['user/getUserCanUpdate']
|
||||
},
|
||||
userCanDelete() {
|
||||
return this.$store.getters['user/getUserCanDelete']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -19,13 +19,16 @@
|
||||
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">play_circle_filled</span>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
|
||||
<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')
|
||||
}
|
||||
return classes
|
||||
},
|
||||
userCanUpdate() {
|
||||
return this.$store.getters['user/getUserCanUpdate']
|
||||
},
|
||||
userCanDelete() {
|
||||
return this.$store.getters['user/getUserCanDelete']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -48,6 +48,10 @@ export default {
|
||||
text: 'Added At',
|
||||
value: 'addedAt'
|
||||
},
|
||||
{
|
||||
text: 'Volume #',
|
||||
value: 'book.volumeNumber'
|
||||
},
|
||||
{
|
||||
text: 'Duration',
|
||||
value: 'duration'
|
||||
|
@ -18,7 +18,7 @@
|
||||
</div>
|
||||
<div class="flex py-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 class="flex-grow" />
|
||||
<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" />
|
||||
</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-grow" />
|
||||
<ui-btn color="success" type="submit">Submit</ui-btn>
|
||||
@ -144,6 +175,13 @@ export default {
|
||||
toggleActive() {
|
||||
this.newUser.isActive = !this.newUser.isActive
|
||||
},
|
||||
userTypeUpdated(type) {
|
||||
this.newUser.permissions = {
|
||||
download: type !== 'guest',
|
||||
update: type === 'admin',
|
||||
delete: type === 'admin'
|
||||
}
|
||||
},
|
||||
init() {
|
||||
this.isNew = !this.account
|
||||
if (this.account) {
|
||||
@ -151,14 +189,20 @@ export default {
|
||||
username: this.account.username,
|
||||
password: this.account.password,
|
||||
type: this.account.type,
|
||||
isActive: this.account.isActive
|
||||
isActive: this.account.isActive,
|
||||
permissions: { ...this.account.permissions }
|
||||
}
|
||||
} else {
|
||||
this.newUser = {
|
||||
username: null,
|
||||
password: null,
|
||||
type: 'user',
|
||||
isActive: true
|
||||
isActive: true,
|
||||
permissions: {
|
||||
download: true,
|
||||
update: false,
|
||||
delete: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<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>
|
||||
</template>
|
||||
</div>
|
||||
@ -58,6 +58,15 @@ export default {
|
||||
show: {
|
||||
handler(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.fetchOnShow) this.fetchFull()
|
||||
return
|
||||
@ -86,6 +95,20 @@ export default {
|
||||
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() {
|
||||
var maxHeightAllowed = window.innerHeight - 150
|
||||
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="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" />
|
||||
<ui-btn type="submit">Submit</ui-btn>
|
||||
</div>
|
||||
@ -113,12 +113,9 @@ export default {
|
||||
book() {
|
||||
return this.audiobook ? this.audiobook.book || {} : {}
|
||||
},
|
||||
// userAudiobook() {
|
||||
// return this.$store.getters['user/getUserAudiobook'](this.audiobookId)
|
||||
// },
|
||||
// userProgress() {
|
||||
// return this.userAudiobook ? this.userAudiobook.progress : 0
|
||||
// },
|
||||
userCanDelete() {
|
||||
return this.$store.getters['user/getUserCanDelete']
|
||||
},
|
||||
genres() {
|
||||
return this.$store.state.audiobooks.genres
|
||||
},
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="w-full h-full overflow-y-auto overflow-x-hidden px-4 py-6">
|
||||
<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>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
@ -11,7 +11,7 @@
|
||||
<th class="text-left">Filename</th>
|
||||
<th class="text-left">Size</th>
|
||||
<th class="text-left">Duration</th>
|
||||
<th class="text-center">Download</th>
|
||||
<th v-if="userCanDownload" class="text-center">Download</th>
|
||||
</tr>
|
||||
<template v-for="track in tracks">
|
||||
<tr :key="track.index">
|
||||
@ -27,7 +27,7 @@
|
||||
<td class="font-mono">
|
||||
{{ $secondsToTimestamp(track.duration) }}
|
||||
</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>
|
||||
</td>
|
||||
</tr>
|
||||
@ -58,12 +58,18 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
computed: {
|
||||
userCanUpdate() {
|
||||
return this.$store.getters['user/getUserCanUpdate']
|
||||
},
|
||||
userCanDownload() {
|
||||
return this.$store.getters['user/getUserCanDownload']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.audioFiles = this.audiobook.audioFiles
|
||||
this.tracks = this.audiobook.tracks
|
||||
console.log('INIT', this.audiobook)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
<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>
|
||||
<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>
|
||||
</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' : ''">
|
||||
@ -56,7 +56,11 @@ export default {
|
||||
showTracks: false
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
computed: {
|
||||
userCanUpdate() {
|
||||
return this.$store.getters['user/getUserCanUpdate']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickBar() {
|
||||
this.showTracks = !this.showTracks
|
||||
|
@ -4,7 +4,7 @@
|
||||
<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>
|
||||
<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>
|
||||
</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' : ''">
|
||||
@ -19,7 +19,7 @@
|
||||
<th class="text-left">Filename</th>
|
||||
<th class="text-left">Size</th>
|
||||
<th class="text-left">Duration</th>
|
||||
<th class="text-center">Download</th>
|
||||
<th v-if="userCanDownload" class="text-center">Download</th>
|
||||
</tr>
|
||||
<template v-for="track in tracks">
|
||||
<tr :key="track.index">
|
||||
@ -35,7 +35,7 @@
|
||||
<td class="font-mono">
|
||||
{{ $secondsToTimestamp(track.duration) }}
|
||||
</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>
|
||||
</td>
|
||||
</tr>
|
||||
@ -60,7 +60,14 @@ export default {
|
||||
showTracks: false
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
computed: {
|
||||
userCanDownload() {
|
||||
return this.$store.getters['user/getUserCanDownload']
|
||||
},
|
||||
userCanUpdate() {
|
||||
return this.$store.getters['user/getUserCanUpdate']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickBar() {
|
||||
this.showTracks = !this.showTracks
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.8",
|
||||
"description": "Audiobook manager and player",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -71,6 +71,9 @@ export default {
|
||||
if (!store.state.user.user) {
|
||||
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) => {
|
||||
console.error('Failed', error)
|
||||
return false
|
||||
@ -82,7 +85,6 @@ export default {
|
||||
return {
|
||||
audiobook,
|
||||
files: audiobook.audioFiles ? audiobook.audioFiles.map((af) => ({ ...af, include: !af.exclude })) : []
|
||||
// files: audiobook.audioFiles ? audiobook.audioFiles.map((af) => ({ ...af, index: ++index })) : []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
@ -36,11 +36,11 @@
|
||||
{{ streaming ? 'Streaming' : 'Play' }}
|
||||
</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-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-tooltip>
|
||||
|
||||
@ -240,6 +240,15 @@ export default {
|
||||
},
|
||||
streaming() {
|
||||
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: {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import Vue from "vue";
|
||||
import Toast from "vue-toastification";
|
||||
import "vue-toastification/dist/index.css";
|
||||
import Vue from "vue"
|
||||
import Toast from "vue-toastification"
|
||||
import "vue-toastification/dist/index.css"
|
||||
|
||||
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) => {
|
||||
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",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.8",
|
||||
"description": "Self-hosted audiobook server for managing and playing audiobooks.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -89,6 +89,10 @@ class ApiController {
|
||||
}
|
||||
|
||||
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')
|
||||
var success = await this.db.recreateAudiobookDb()
|
||||
if (success) res.sendStatus(200)
|
||||
@ -130,6 +134,10 @@ class ApiController {
|
||||
}
|
||||
|
||||
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)
|
||||
if (!audiobook) return res.sendStatus(404)
|
||||
|
||||
@ -138,6 +146,10 @@ class ApiController {
|
||||
}
|
||||
|
||||
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
|
||||
if (!audiobookIds || !audiobookIds.length) {
|
||||
return res.sendStatus(500)
|
||||
@ -155,6 +167,10 @@ class ApiController {
|
||||
}
|
||||
|
||||
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
|
||||
if (!audiobooks || !audiobooks.length) {
|
||||
return res.sendStatus(500)
|
||||
@ -185,6 +201,10 @@ class ApiController {
|
||||
}
|
||||
|
||||
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)
|
||||
if (!audiobook) return res.sendStatus(404)
|
||||
var orderedFileData = req.body.orderedFileData
|
||||
@ -196,6 +216,10 @@ class ApiController {
|
||||
}
|
||||
|
||||
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)
|
||||
if (!audiobook) return res.sendStatus(404)
|
||||
var hasUpdates = audiobook.update(req.body)
|
||||
@ -276,6 +300,10 @@ class ApiController {
|
||||
}
|
||||
|
||||
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
|
||||
account.id = (Math.trunc(Math.random() * 1000) + Date.now()).toString(36)
|
||||
account.pash = await this.auth.hashPass(account.password)
|
||||
@ -297,7 +325,7 @@ class ApiController {
|
||||
}
|
||||
|
||||
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)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
@ -327,6 +355,10 @@ class ApiController {
|
||||
}
|
||||
|
||||
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') {
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
@ -353,6 +385,10 @@ class ApiController {
|
||||
}
|
||||
|
||||
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
|
||||
if (!settingsUpdate || !isObject(settingsUpdate)) {
|
||||
return res.sendStatus(500)
|
||||
@ -368,6 +404,10 @@ class ApiController {
|
||||
}
|
||||
|
||||
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
|
||||
Logger.info('Download Request', downloadId)
|
||||
var download = this.downloadManager.getDownload(downloadId)
|
||||
|
@ -75,11 +75,11 @@ class Db {
|
||||
async load() {
|
||||
var p1 = this.audiobooksDb.select(() => true).then((results) => {
|
||||
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) => {
|
||||
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) => {
|
||||
if (results.data && results.data.length) {
|
||||
|
@ -11,13 +11,28 @@ class User {
|
||||
this.isActive = true
|
||||
this.createdAt = null
|
||||
this.audiobooks = null
|
||||
|
||||
this.settings = {}
|
||||
this.permissions = {}
|
||||
|
||||
if (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() {
|
||||
return {
|
||||
orderBy: 'book.title',
|
||||
@ -28,6 +43,14 @@ class User {
|
||||
}
|
||||
}
|
||||
|
||||
getDefaultUserPermissions() {
|
||||
return {
|
||||
download: true,
|
||||
update: true,
|
||||
delete: this.id === 'root'
|
||||
}
|
||||
}
|
||||
|
||||
audiobooksToJSON() {
|
||||
if (!this.audiobooks) return null
|
||||
var _map = {}
|
||||
@ -50,7 +73,8 @@ class User {
|
||||
audiobooks: this.audiobooksToJSON(),
|
||||
isActive: this.isActive,
|
||||
createdAt: this.createdAt,
|
||||
settings: this.settings
|
||||
settings: this.settings,
|
||||
permissions: this.permissions
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +88,8 @@ class User {
|
||||
audiobooks: this.audiobooksToJSON(),
|
||||
isActive: this.isActive,
|
||||
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.createdAt = user.createdAt || Date.now()
|
||||
this.settings = user.settings || this.getDefaultUserSettings()
|
||||
this.permissions = user.permissions || this.getDefaultUserPermissions()
|
||||
}
|
||||
|
||||
update(payload) {
|
||||
var hasUpdates = false
|
||||
// Update the following keys:
|
||||
const keysToCheck = ['pash', 'type', 'username', 'isActive']
|
||||
keysToCheck.forEach((key) => {
|
||||
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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user