mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +01:00
Update:No longer creating initial root user and initial library, add init root user page, web app works with no libraries
This commit is contained in:
parent
63a8e2433e
commit
c962090c3a
@ -3,7 +3,6 @@ set -e
|
|||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
FFMPEG_INSTALL_DIR="/usr/lib/audiobookshelf-ffmpeg/"
|
FFMPEG_INSTALL_DIR="/usr/lib/audiobookshelf-ffmpeg/"
|
||||||
DEFAULT_AUDIOBOOK_PATH="/usr/share/audiobookshelf/audiobooks"
|
|
||||||
DEFAULT_DATA_PATH="/usr/share/audiobookshelf"
|
DEFAULT_DATA_PATH="/usr/share/audiobookshelf"
|
||||||
DEFAULT_PORT=7331
|
DEFAULT_PORT=7331
|
||||||
DEFAULT_HOST="0.0.0.0"
|
DEFAULT_HOST="0.0.0.0"
|
||||||
@ -54,14 +53,6 @@ setup_config_interactive() {
|
|||||||
if should_build_config; then
|
if should_build_config; then
|
||||||
echo "Okay, let's setup a new config."
|
echo "Okay, let's setup a new config."
|
||||||
|
|
||||||
AUDIOBOOK_PATH=""
|
|
||||||
read -p "
|
|
||||||
Enter path for your audiobooks [Default: $DEFAULT_AUDIOBOOK_PATH]:" AUDIOBOOK_PATH
|
|
||||||
|
|
||||||
if [[ -z "$AUDIOBOOK_PATH" ]]; then
|
|
||||||
AUDIOBOOK_PATH="$DEFAULT_AUDIOBOOK_PATH"
|
|
||||||
fi
|
|
||||||
|
|
||||||
DATA_PATH=""
|
DATA_PATH=""
|
||||||
read -p "
|
read -p "
|
||||||
Enter path for data files, i.e. streams, downloads, database [Default: $DEFAULT_DATA_PATH]:" DATA_PATH
|
Enter path for data files, i.e. streams, downloads, database [Default: $DEFAULT_DATA_PATH]:" DATA_PATH
|
||||||
@ -78,8 +69,7 @@ setup_config_interactive() {
|
|||||||
PORT="$DEFAULT_PORT"
|
PORT="$DEFAULT_PORT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
config_text="AUDIOBOOK_PATH=$AUDIOBOOK_PATH
|
config_text="METADATA_PATH=$DATA_PATH/metadata
|
||||||
METADATA_PATH=$DATA_PATH/metadata
|
|
||||||
CONFIG_PATH=$DATA_PATH/config
|
CONFIG_PATH=$DATA_PATH/config
|
||||||
FFMPEG_PATH=/usr/lib/audiobookshelf-ffmpeg/ffmpeg
|
FFMPEG_PATH=/usr/lib/audiobookshelf-ffmpeg/ffmpeg
|
||||||
FFPROBE_PATH=/usr/lib/audiobookshelf-ffmpeg/ffprobe
|
FFPROBE_PATH=/usr/lib/audiobookshelf-ffmpeg/ffprobe
|
||||||
@ -102,8 +92,7 @@ setup_config() {
|
|||||||
else
|
else
|
||||||
echo "Creating default config."
|
echo "Creating default config."
|
||||||
|
|
||||||
config_text="AUDIOBOOK_PATH=$DEFAULT_AUDIOBOOK_PATH
|
config_text="METADATA_PATH=$DEFAULT_DATA_PATH/metadata
|
||||||
METADATA_PATH=$DEFAULT_DATA_PATH/metadata
|
|
||||||
CONFIG_PATH=$DEFAULT_DATA_PATH/config
|
CONFIG_PATH=$DEFAULT_DATA_PATH/config
|
||||||
FFMPEG_PATH=/usr/lib/audiobookshelf-ffmpeg/ffmpeg
|
FFMPEG_PATH=/usr/lib/audiobookshelf-ffmpeg/ffmpeg
|
||||||
FFPROBE_PATH=/usr/lib/audiobookshelf-ffmpeg/ffprobe
|
FFPROBE_PATH=/usr/lib/audiobookshelf-ffmpeg/ffprobe
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
<ui-libraries-dropdown />
|
<ui-libraries-dropdown />
|
||||||
|
|
||||||
<controls-global-search class="hidden md:block" />
|
<controls-global-search v-if="currentLibrary" class="hidden md:block" />
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
|
|
||||||
<span v-if="showExperimentalFeatures" class="material-icons text-4xl text-warning pr-0 sm:pr-2 md:pr-4">logo_dev</span>
|
<span v-if="showExperimentalFeatures" class="material-icons text-4xl text-warning pr-0 sm:pr-2 md:pr-4">logo_dev</span>
|
||||||
@ -24,11 +24,11 @@
|
|||||||
<google-cast-launcher></google-cast-launcher>
|
<google-cast-launcher></google-cast-launcher>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nuxt-link to="/config/stats" class="outline-none hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
|
<nuxt-link v-if="currentLibrary" to="/config/stats" class="outline-none hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
|
||||||
<span class="material-icons" aria-label="User Stats" role="button">equalizer</span>
|
<span class="material-icons" aria-label="User Stats" role="button">equalizer</span>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|
||||||
<nuxt-link v-if="userCanUpload" to="/upload" class="outline-none hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
|
<nuxt-link v-if="userCanUpload && currentLibrary" to="/upload" class="outline-none hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
|
||||||
<span class="material-icons" aria-label="Upload Media" role="button">upload</span>
|
<span class="material-icons" aria-label="Upload Media" role="button">upload</span>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|
||||||
|
@ -25,6 +25,9 @@ export default {
|
|||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
currentLibraryId() {
|
||||||
|
return this.$store.state.libraries.currentLibraryId
|
||||||
|
},
|
||||||
userIsAdminOrUp() {
|
userIsAdminOrUp() {
|
||||||
return this.$store.getters['user/getIsAdminOrUp']
|
return this.$store.getters['user/getIsAdminOrUp']
|
||||||
},
|
},
|
||||||
@ -38,7 +41,7 @@ export default {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
return [
|
const configRoutes = [
|
||||||
{
|
{
|
||||||
id: 'config',
|
id: 'config',
|
||||||
title: 'Settings',
|
title: 'Settings',
|
||||||
@ -63,18 +66,23 @@ export default {
|
|||||||
id: 'config-log',
|
id: 'config-log',
|
||||||
title: 'Log',
|
title: 'Log',
|
||||||
path: '/config/log'
|
path: '/config/log'
|
||||||
},
|
}
|
||||||
{
|
]
|
||||||
|
|
||||||
|
if (this.currentLibraryId) {
|
||||||
|
configRoutes.push({
|
||||||
id: 'config-library-stats',
|
id: 'config-library-stats',
|
||||||
title: 'Library Stats',
|
title: 'Library Stats',
|
||||||
path: '/config/library-stats'
|
path: '/config/library-stats'
|
||||||
},
|
})
|
||||||
{
|
configRoutes.push({
|
||||||
id: 'config-stats',
|
id: 'config-stats',
|
||||||
title: 'Your Stats',
|
title: 'Your Stats',
|
||||||
path: '/config/stats'
|
path: '/config/stats'
|
||||||
}
|
})
|
||||||
]
|
}
|
||||||
|
|
||||||
|
return configRoutes
|
||||||
},
|
},
|
||||||
wrapperClass() {
|
wrapperClass() {
|
||||||
var classes = []
|
var classes = []
|
||||||
|
@ -95,7 +95,7 @@ export default {
|
|||||||
settings: {
|
settings: {
|
||||||
disableWatcher: false,
|
disableWatcher: false,
|
||||||
skipMatchingMediaWithAsin: false,
|
skipMatchingMediaWithAsin: false,
|
||||||
skipMatchingMediaWithIsbn: false,
|
skipMatchingMediaWithIsbn: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -193,6 +193,11 @@ export default {
|
|||||||
this.processing = false
|
this.processing = false
|
||||||
this.show = false
|
this.show = false
|
||||||
this.$toast.success(`Library "${res.name}" created successfully`)
|
this.$toast.success(`Library "${res.name}" created successfully`)
|
||||||
|
if (!this.$store.state.libraries.currentLibraryId) {
|
||||||
|
console.log('Setting initially library id', res.id)
|
||||||
|
// First library added
|
||||||
|
this.$store.dispatch('libraries/fetch', res.id)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
@ -6,18 +6,20 @@
|
|||||||
<span class="material-icons" style="font-size: 1.4rem">add</span>
|
<span class="material-icons" style="font-size: 1.4rem">add</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<draggable :list="libraryCopies" v-bind="dragOptions" class="list-group" draggable=".item" tag="div" @start="startDrag" @end="endDrag">
|
<draggable v-if="libraryCopies.length" :list="libraryCopies" v-bind="dragOptions" class="list-group" draggable=".item" tag="div" @start="startDrag" @end="endDrag">
|
||||||
<template v-for="library in libraryCopies">
|
<template v-for="library in libraryCopies">
|
||||||
<div :key="library.id" class="item">
|
<div :key="library.id" class="item">
|
||||||
<tables-library-item :library="library" :selected="currentLibraryId === library.id" :show-edit="true" :dragging="drag" @edit="editLibrary" @click="setLibrary" />
|
<tables-library-item :library="library" :selected="currentLibraryId === library.id" :show-edit="true" :dragging="drag" @edit="editLibrary" @click="setLibrary" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</draggable>
|
</draggable>
|
||||||
<modals-libraries-edit-modal v-model="showLibraryModal" :library="selectedLibrary" />
|
<div v-if="!libraries.length" class="pb-4">
|
||||||
|
<ui-btn @click="clickAddLibrary">Add your first library</ui-btn>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="text-xs mt-4 text-gray-200">*<strong>Force Re-Scan</strong> will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be probed/parsed and used for book details.</p>
|
<p v-if="libraries.length" class="text-xs mt-4 text-gray-200">*<strong>Force Re-Scan</strong> will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be probed/parsed and used for book details.</p>
|
||||||
|
|
||||||
<p class="text-xs mt-4 text-gray-200">**<strong>Match Books</strong> will attempt to match books in library with a book from the selected search provider and fill in empty details and cover art. Does not overwrite details.</p>
|
<p v-if="libraries.length && libraries.some((li) => li.mediaType === 'book')" class="text-xs mt-4 text-gray-200">**<strong>Match Books</strong> will attempt to match books in library with a book from the selected search provider and fill in empty details and cover art. Does not overwrite details.</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -32,8 +34,6 @@ export default {
|
|||||||
return {
|
return {
|
||||||
libraryCopies: [],
|
libraryCopies: [],
|
||||||
currentOrder: [],
|
currentOrder: [],
|
||||||
showLibraryModal: false,
|
|
||||||
selectedLibrary: null,
|
|
||||||
drag: false,
|
drag: false,
|
||||||
dragOptions: {
|
dragOptions: {
|
||||||
animation: 200,
|
animation: 200,
|
||||||
@ -97,12 +97,10 @@ export default {
|
|||||||
this.$router.push(`/library/${library.id}`)
|
this.$router.push(`/library/${library.id}`)
|
||||||
},
|
},
|
||||||
clickAddLibrary() {
|
clickAddLibrary() {
|
||||||
this.selectedLibrary = null
|
this.$emit('showLibraryModal', null)
|
||||||
this.showLibraryModal = true
|
|
||||||
},
|
},
|
||||||
editLibrary(library) {
|
editLibrary(library) {
|
||||||
this.selectedLibrary = library
|
this.$emit('showLibraryModal', library)
|
||||||
this.showLibraryModal = true
|
|
||||||
},
|
},
|
||||||
init() {
|
init() {
|
||||||
this.libraryCopies = this.libraries.map((lib) => {
|
this.libraryCopies = this.libraries.map((lib) => {
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="relative">
|
<div ref="wrapper" class="relative">
|
||||||
<input ref="input" v-model="inputValue" :type="type" :readonly="readonly" :disabled="disabled" :placeholder="placeholder" class="rounded bg-primary text-gray-200 focus:border-gray-300 focus:bg-bg focus:outline-none border border-gray-600 h-full w-full" :class="classList" @keyup="keyup" @change="change" @focus="focused" @blur="blurred" />
|
<input ref="input" v-model="inputValue" :type="actualType" :readonly="readonly" :disabled="disabled" :placeholder="placeholder" class="rounded bg-primary text-gray-200 focus:border-gray-300 focus:bg-bg focus:outline-none border border-gray-600 h-full w-full" :class="classList" @keyup="keyup" @change="change" @focus="focused" @blur="blurred" />
|
||||||
<div v-if="clearable && inputValue" class="absolute top-0 right-0 h-full px-2 flex items-center justify-center">
|
<div v-if="clearable && inputValue" class="absolute top-0 right-0 h-full px-2 flex items-center justify-center">
|
||||||
<span class="material-icons text-gray-300 cursor-pointer" style="font-size: 1.1rem" @click.stop.prevent="clear">close</span>
|
<span class="material-icons text-gray-300 cursor-pointer" style="font-size: 1.1rem" @click.stop.prevent="clear">close</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="type === 'password' && isHovering" class="absolute top-0 right-0 h-full px-4 flex items-center justify-center">
|
||||||
|
<span class="material-icons-outlined text-gray-400 cursor-pointer text-lg" @click.stop.prevent="showPassword = !showPassword">{{ !showPassword ? 'visibility' : 'visibility_off' }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -31,7 +34,10 @@ export default {
|
|||||||
clearable: Boolean
|
clearable: Boolean
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {
|
||||||
|
showPassword: false,
|
||||||
|
isHovering: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
inputValue: {
|
inputValue: {
|
||||||
@ -49,6 +55,10 @@ export default {
|
|||||||
if (this.noSpinner) _list.push('no-spinner')
|
if (this.noSpinner) _list.push('no-spinner')
|
||||||
if (this.textCenter) _list.push('text-center')
|
if (this.textCenter) _list.push('text-center')
|
||||||
return _list.join(' ')
|
return _list.join(' ')
|
||||||
|
},
|
||||||
|
actualType() {
|
||||||
|
if (this.type === 'password' && this.showPassword) return 'text'
|
||||||
|
return this.type
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -69,9 +79,20 @@ export default {
|
|||||||
},
|
},
|
||||||
blur() {
|
blur() {
|
||||||
if (this.$refs.input) this.$refs.input.blur()
|
if (this.$refs.input) this.$refs.input.blur()
|
||||||
|
},
|
||||||
|
mouseover() {
|
||||||
|
this.isHovering = true
|
||||||
|
},
|
||||||
|
mouseleave() {
|
||||||
|
this.isHovering = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {}
|
mounted() {
|
||||||
|
if (this.type === 'password' && this.$refs.wrapper) {
|
||||||
|
this.$refs.wrapper.addEventListener('mouseover', this.mouseover)
|
||||||
|
this.$refs.wrapper.addEventListener('mouseleave', this.mouseleave)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<p class="px-1 text-sm font-semibold" :class="disabled ? 'text-gray-400' : ''">
|
<p class="px-1 text-sm font-semibold" :class="{ 'text-gray-400': disabled }">
|
||||||
{{ label }}<em v-if="note" class="font-normal text-xs pl-2">{{ note }}</em>
|
{{ label }}<em v-if="note" class="font-normal text-xs pl-2">{{ note }}</em>
|
||||||
</p>
|
</p>
|
||||||
<ui-text-input ref="input" v-model="inputValue" :disabled="disabled" :readonly="readonly" :type="type" class="w-full" @blur="inputBlurred" />
|
<ui-text-input ref="input" v-model="inputValue" :disabled="disabled" :readonly="readonly" :type="type" class="w-full" @blur="inputBlurred" />
|
||||||
|
@ -51,7 +51,7 @@ export default {
|
|||||||
},
|
},
|
||||||
isShowingSideRail() {
|
isShowingSideRail() {
|
||||||
if (!this.$route.name) return false
|
if (!this.$route.name) return false
|
||||||
return !this.$route.name.startsWith('config')
|
return !this.$route.name.startsWith('config') && this.$store.state.libraries.currentLibraryId
|
||||||
},
|
},
|
||||||
appContentMarginLeft() {
|
appContentMarginLeft() {
|
||||||
return this.isShowingSideRail ? 80 : 0
|
return this.isShowingSideRail ? 80 : 0
|
||||||
@ -173,6 +173,7 @@ export default {
|
|||||||
this.$store.commit('libraries/addUpdate', library)
|
this.$store.commit('libraries/addUpdate', library)
|
||||||
},
|
},
|
||||||
async libraryRemoved(library) {
|
async libraryRemoved(library) {
|
||||||
|
console.log('Library removed', library)
|
||||||
this.$store.commit('libraries/remove', library)
|
this.$store.commit('libraries/remove', library)
|
||||||
|
|
||||||
// When removed currently selected library then set next accessible library
|
// When removed currently selected library then set next accessible library
|
||||||
@ -191,7 +192,8 @@ export default {
|
|||||||
this.$router.push(`/library/${nextLibrary.id}`)
|
this.$router.push(`/library/${nextLibrary.id}`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('User has no accessible libraries')
|
console.error('User has no more accessible libraries')
|
||||||
|
this.$store.commit('libraries/setCurrentLibrary', null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,16 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<tables-library-libraries-table />
|
<tables-library-libraries-table @showLibraryModal="setShowLibraryModal" />
|
||||||
|
|
||||||
|
<modals-libraries-edit-modal v-model="showLibraryModal" :library="selectedLibrary" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {
|
||||||
|
showLibraryModal: false,
|
||||||
|
selectedLibrary: null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
computed: {},
|
computed: {},
|
||||||
methods: {},
|
methods: {
|
||||||
|
setShowLibraryModal(selectedLibrary) {
|
||||||
|
this.selectedLibrary = selectedLibrary
|
||||||
|
this.showLibraryModal = true
|
||||||
|
}
|
||||||
|
},
|
||||||
mounted() {}
|
mounted() {}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
@ -67,6 +67,12 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
asyncData({ redirect, store }) {
|
||||||
|
if (!store.state.libraries.currentLibraryId) {
|
||||||
|
return redirect('/config')
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
libraryStats: null
|
libraryStats: null
|
||||||
|
@ -5,6 +5,9 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
asyncData({ redirect, store }) {
|
asyncData({ redirect, store }) {
|
||||||
|
if (!store.state.libraries.currentLibraryId) {
|
||||||
|
return redirect('/oops?message=No libraries')
|
||||||
|
}
|
||||||
redirect(`/library/${store.state.libraries.currentLibraryId}`)
|
redirect(`/library/${store.state.libraries.currentLibraryId}`)
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -1,7 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full h-screen bg-bg">
|
<div class="w-full h-screen bg-bg">
|
||||||
<div class="w-full flex h-1/2 items-center justify-center">
|
<div class="w-full flex h-full items-center justify-center">
|
||||||
<div class="w-full max-w-md border border-opacity-0 rounded-xl px-8 pb-8 pt-4">
|
<div v-if="criticalError" class="w-full max-w-md rounded border border-error border-opacity-25 bg-error bg-opacity-10 p-4">
|
||||||
|
<p class="text-center text-lg font-semibold">Server could not be reached</p>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="showInitScreen" class="w-full max-w-lg px-4 md:px-8 pb-8 pt-4">
|
||||||
|
<p class="text-3xl text-white text-center mb-4">Initial Server Setup</p>
|
||||||
|
<div class="w-full h-px bg-white bg-opacity-10 my-4" />
|
||||||
|
|
||||||
|
<form @submit.prevent="submitServerSetup">
|
||||||
|
<p class="text-lg font-semibold mb-2 pl-1 text-center">Create Root User</p>
|
||||||
|
<ui-text-input-with-label v-model="newRoot.username" label="Username" :disabled="processing" class="w-full mb-3 text-sm" />
|
||||||
|
<ui-text-input-with-label v-model="newRoot.password" label="Password" type="password" :disabled="processing" class="w-full mb-3 text-sm" />
|
||||||
|
<ui-text-input-with-label v-model="confirmPassword" label="Confirm Password" type="password" :disabled="processing" class="w-full mb-3 text-sm" />
|
||||||
|
|
||||||
|
<p class="text-lg font-semibold mt-6 mb-2 pl-1 text-center">Directory Paths</p>
|
||||||
|
<ui-text-input-with-label v-model="ConfigPath" label="Config Path" disabled class="w-full mb-3 text-sm" />
|
||||||
|
<ui-text-input-with-label v-model="MetadataPath" label="Metadata Path" disabled class="w-full mb-3 text-sm" />
|
||||||
|
|
||||||
|
<div class="w-full flex justify-end py-3">
|
||||||
|
<ui-btn type="submit" :disabled="processing" color="primary" class="leading-none">{{ processing ? 'Initializing...' : 'Submit' }}</ui-btn>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="isInit" class="w-full max-w-md px-8 pb-8 pt-4 -mt-40">
|
||||||
<p class="text-3xl text-white text-center mb-4">Login</p>
|
<p class="text-3xl text-white text-center mb-4">Login</p>
|
||||||
<div class="w-full h-px bg-white bg-opacity-10 my-4" />
|
<div class="w-full h-px bg-white bg-opacity-10 my-4" />
|
||||||
<p v-if="error" class="text-error text-center py-2">{{ error }}</p>
|
<p v-if="error" class="text-error text-center py-2">{{ error }}</p>
|
||||||
@ -11,8 +33,8 @@
|
|||||||
|
|
||||||
<label class="text-xs text-gray-300 uppercase">Password</label>
|
<label class="text-xs text-gray-300 uppercase">Password</label>
|
||||||
<ui-text-input v-model="password" type="password" :disabled="processing" class="w-full mb-3" />
|
<ui-text-input v-model="password" type="password" :disabled="processing" class="w-full mb-3" />
|
||||||
<div class="w-full flex justify-end">
|
<div class="w-full flex justify-end py-3">
|
||||||
<button type="submit" :disabled="processing" class="bg-blue-600 hover:bg-blue-800 px-8 py-1 mt-3 rounded-md text-white text-center transition duration-300 ease-in-out focus:outline-none">{{ processing ? 'Checking...' : 'Submit' }}</button>
|
<ui-btn type="submit" :disabled="processing" color="primary" class="leading-none">{{ processing ? 'Checking...' : 'Submit' }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -26,15 +48,33 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
error: null,
|
error: null,
|
||||||
|
criticalError: null,
|
||||||
processing: false,
|
processing: false,
|
||||||
username: '',
|
username: '',
|
||||||
password: null
|
password: null,
|
||||||
|
showInitScreen: false,
|
||||||
|
isInit: false,
|
||||||
|
newRoot: {
|
||||||
|
username: 'root',
|
||||||
|
password: ''
|
||||||
|
},
|
||||||
|
confirmPassword: '',
|
||||||
|
ConfigPath: '',
|
||||||
|
MetadataPath: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
user(newVal) {
|
user(newVal) {
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
if (this.$route.query.redirect) {
|
if (!this.$store.state.libraries.currentLibraryId) {
|
||||||
|
// No libraries available to this user
|
||||||
|
if (this.$store.getters['user/getIsRoot']) {
|
||||||
|
// If root user go to config/libraries
|
||||||
|
this.$router.replace('/config/libraries')
|
||||||
|
} else {
|
||||||
|
this.$router.replace('/oops?message=No libraries available')
|
||||||
|
}
|
||||||
|
} else if (this.$route.query.redirect) {
|
||||||
this.$router.replace(this.$route.query.redirect)
|
this.$router.replace(this.$route.query.redirect)
|
||||||
} else {
|
} else {
|
||||||
this.$router.replace(`/library/${this.$store.state.libraries.currentLibraryId}`)
|
this.$router.replace(`/library/${this.$store.state.libraries.currentLibraryId}`)
|
||||||
@ -48,6 +88,42 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async submitServerSetup() {
|
||||||
|
if (!this.newRoot.username || !this.newRoot.username.trim()) {
|
||||||
|
this.$toast.error('Must enter a root username')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.newRoot.password !== this.confirmPassword) {
|
||||||
|
this.$toast.error('Password mismatch')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.newRoot.password) {
|
||||||
|
if (!confirm('Are you sure you want to create the root user with no password?')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.processing = true
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
newRoot: { ...this.newRoot }
|
||||||
|
}
|
||||||
|
var success = await this.$axios
|
||||||
|
.$post('/init', payload)
|
||||||
|
.then(() => true)
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed', error.response)
|
||||||
|
const errorMsg = error.response ? error.response.data || 'Unknown Error' : 'Unknown Error'
|
||||||
|
this.$toast.error(errorMsg)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
this.processing = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
location.reload()
|
||||||
|
},
|
||||||
setUser({ user, userDefaultLibraryId, serverSettings }) {
|
setUser({ user, userDefaultLibraryId, serverSettings }) {
|
||||||
this.$store.commit('setServerSettings', serverSettings)
|
this.$store.commit('setServerSettings', serverSettings)
|
||||||
|
|
||||||
@ -81,32 +157,54 @@ export default {
|
|||||||
this.processing = false
|
this.processing = false
|
||||||
},
|
},
|
||||||
checkAuth() {
|
checkAuth() {
|
||||||
if (localStorage.getItem('token')) {
|
var token = localStorage.getItem('token')
|
||||||
var token = localStorage.getItem('token')
|
if (!token) return false
|
||||||
|
|
||||||
if (token) {
|
this.processing = true
|
||||||
this.processing = true
|
|
||||||
|
|
||||||
this.$axios
|
return this.$axios
|
||||||
.$post('/api/authorize', null, {
|
.$post('/api/authorize', null, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${token}`
|
Authorization: `Bearer ${token}`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.setUser(res)
|
this.setUser(res)
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
return true
|
||||||
.catch((error) => {
|
})
|
||||||
console.error('Authorize error', error)
|
.catch((error) => {
|
||||||
this.processing = false
|
console.error('Authorize error', error)
|
||||||
})
|
this.processing = false
|
||||||
}
|
return false
|
||||||
}
|
})
|
||||||
|
},
|
||||||
|
checkStatus() {
|
||||||
|
this.processing = true
|
||||||
|
this.$axios
|
||||||
|
.$get('/status')
|
||||||
|
.then((res) => {
|
||||||
|
this.processing = false
|
||||||
|
this.isInit = res.isInit
|
||||||
|
this.showInitScreen = !res.isInit
|
||||||
|
if (this.showInitScreen) {
|
||||||
|
this.ConfigPath = res.ConfigPath || ''
|
||||||
|
this.MetadataPath = res.MetadataPath || ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Status check failed', error)
|
||||||
|
this.processing = false
|
||||||
|
this.criticalError = 'Status check failed'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
async mounted() {
|
||||||
this.checkAuth()
|
if (localStorage.getItem('token')) {
|
||||||
|
var userfound = await this.checkAuth()
|
||||||
|
if (userfound) return // if valid user no need to check status
|
||||||
|
}
|
||||||
|
this.checkStatus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
@ -2,7 +2,7 @@ export const state = () => ({
|
|||||||
libraries: [],
|
libraries: [],
|
||||||
lastLoad: 0,
|
lastLoad: 0,
|
||||||
listeners: [],
|
listeners: [],
|
||||||
currentLibraryId: 'main',
|
currentLibraryId: null,
|
||||||
folders: [],
|
folders: [],
|
||||||
issues: 0,
|
issues: 0,
|
||||||
folderLastUpdate: 0,
|
folderLastUpdate: 0,
|
||||||
|
10
index.js
10
index.js
@ -1,4 +1,4 @@
|
|||||||
if(process.env.TOKEN_SECRET == null) process.env.TOKEN_SECRET = '09f26e402586e2faa8da4c98a35f1b20d6b033c6097befa8be3486a829587fe2f90a832bd3ff9d42710a4da095a2ce285b009f0c3730cd9b8e1af3eb84df6611'
|
if (process.env.TOKEN_SECRET == null) process.env.TOKEN_SECRET = '09f26e402586e2faa8da4c98a35f1b20d6b033c6097befa8be3486a829587fe2f90a832bd3ff9d42710a4da095a2ce285b009f0c3730cd9b8e1af3eb84df6611'
|
||||||
const server = require('./server/Server')
|
const server = require('./server/Server')
|
||||||
global.appRoot = __dirname
|
global.appRoot = __dirname
|
||||||
|
|
||||||
@ -9,20 +9,20 @@ if (isDev) {
|
|||||||
process.env.PORT = devEnv.Port
|
process.env.PORT = devEnv.Port
|
||||||
process.env.CONFIG_PATH = devEnv.ConfigPath
|
process.env.CONFIG_PATH = devEnv.ConfigPath
|
||||||
process.env.METADATA_PATH = devEnv.MetadataPath
|
process.env.METADATA_PATH = devEnv.MetadataPath
|
||||||
process.env.AUDIOBOOK_PATH = devEnv.AudiobookPath
|
|
||||||
process.env.FFMPEG_PATH = devEnv.FFmpegPath
|
process.env.FFMPEG_PATH = devEnv.FFmpegPath
|
||||||
process.env.FFPROBE_PATH = devEnv.FFProbePath
|
process.env.FFPROBE_PATH = devEnv.FFProbePath
|
||||||
|
process.env.SOURCE = 'local'
|
||||||
}
|
}
|
||||||
|
|
||||||
const PORT = process.env.PORT || 80
|
const PORT = process.env.PORT || 80
|
||||||
const HOST = process.env.HOST || '0.0.0.0'
|
const HOST = process.env.HOST || '0.0.0.0'
|
||||||
const CONFIG_PATH = process.env.CONFIG_PATH || '/config'
|
const CONFIG_PATH = process.env.CONFIG_PATH || '/config'
|
||||||
const AUDIOBOOK_PATH = process.env.AUDIOBOOK_PATH || '/audiobooks'
|
|
||||||
const METADATA_PATH = process.env.METADATA_PATH || '/metadata'
|
const METADATA_PATH = process.env.METADATA_PATH || '/metadata'
|
||||||
const UID = process.env.AUDIOBOOKSHELF_UID || 99
|
const UID = process.env.AUDIOBOOKSHELF_UID || 99
|
||||||
const GID = process.env.AUDIOBOOKSHELF_GID || 100
|
const GID = process.env.AUDIOBOOKSHELF_GID || 100
|
||||||
|
const SOURCE = process.env.SOURCE || 'docker'
|
||||||
|
|
||||||
console.log('Config', CONFIG_PATH, METADATA_PATH, AUDIOBOOK_PATH)
|
console.log('Config', CONFIG_PATH, METADATA_PATH)
|
||||||
|
|
||||||
const Server = new server(PORT, HOST, UID, GID, CONFIG_PATH, METADATA_PATH, AUDIOBOOK_PATH)
|
const Server = new server(SOURCE, PORT, HOST, UID, GID, CONFIG_PATH, METADATA_PATH)
|
||||||
Server.start()
|
Server.start()
|
||||||
|
13
prod.js
13
prod.js
@ -1,16 +1,16 @@
|
|||||||
const optionDefinitions = [
|
const optionDefinitions = [
|
||||||
{ name: 'config', alias: 'c', type: String },
|
{ name: 'config', alias: 'c', type: String },
|
||||||
{ name: 'audiobooks', alias: 'a', type: String },
|
|
||||||
{ name: 'metadata', alias: 'm', type: String },
|
{ name: 'metadata', alias: 'm', type: String },
|
||||||
{ name: 'port', alias: 'p', type: String },
|
{ name: 'port', alias: 'p', type: String },
|
||||||
{ name: 'host', alias: 'h', type: String }
|
{ name: 'host', alias: 'h', type: String },
|
||||||
|
{ name: 'source', alias: 's', type: String }
|
||||||
]
|
]
|
||||||
|
|
||||||
const commandLineArgs = require('command-line-args')
|
const commandLineArgs = require('command-line-args')
|
||||||
const options = commandLineArgs(optionDefinitions)
|
const options = commandLineArgs(optionDefinitions)
|
||||||
|
|
||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
if(process.env.TOKEN_SECRET == null) process.env.TOKEN_SECRET = '09f26e402586e2faa8da4c98a35f1b20d6b033c6097befa8be3486a829587fe2f90a832bd3ff9d42710a4da095a2ce285b009f0c3730cd9b8e1af3eb84df6611'
|
if (process.env.TOKEN_SECRET == null) process.env.TOKEN_SECRET = '09f26e402586e2faa8da4c98a35f1b20d6b033c6097befa8be3486a829587fe2f90a832bd3ff9d42710a4da095a2ce285b009f0c3730cd9b8e1af3eb84df6611'
|
||||||
process.env.NODE_ENV = 'production'
|
process.env.NODE_ENV = 'production'
|
||||||
|
|
||||||
const server = require('./server/Server')
|
const server = require('./server/Server')
|
||||||
@ -18,18 +18,17 @@ const server = require('./server/Server')
|
|||||||
global.appRoot = __dirname
|
global.appRoot = __dirname
|
||||||
|
|
||||||
var inputConfig = options.config ? Path.resolve(options.config) : null
|
var inputConfig = options.config ? Path.resolve(options.config) : null
|
||||||
var inputAudiobook = options.audiobooks ? Path.resolve(options.audiobooks) : null
|
|
||||||
var inputMetadata = options.metadata ? Path.resolve(options.metadata) : null
|
var inputMetadata = options.metadata ? Path.resolve(options.metadata) : null
|
||||||
|
|
||||||
const PORT = options.port || process.env.PORT || 3333
|
const PORT = options.port || process.env.PORT || 3333
|
||||||
const HOST = options.host || process.env.HOST || "0.0.0.0"
|
const HOST = options.host || process.env.HOST || "0.0.0.0"
|
||||||
const CONFIG_PATH = inputConfig || process.env.CONFIG_PATH || Path.resolve('config')
|
const CONFIG_PATH = inputConfig || process.env.CONFIG_PATH || Path.resolve('config')
|
||||||
const AUDIOBOOK_PATH = inputAudiobook || process.env.AUDIOBOOK_PATH || Path.resolve('audiobooks')
|
|
||||||
const METADATA_PATH = inputMetadata || process.env.METADATA_PATH || Path.resolve('metadata')
|
const METADATA_PATH = inputMetadata || process.env.METADATA_PATH || Path.resolve('metadata')
|
||||||
const UID = 99
|
const UID = 99
|
||||||
const GID = 100
|
const GID = 100
|
||||||
|
const SOURCE = options.source || 'debian'
|
||||||
|
|
||||||
console.log(process.env.NODE_ENV, 'Config', CONFIG_PATH, METADATA_PATH, AUDIOBOOK_PATH)
|
console.log(process.env.NODE_ENV, 'Config', CONFIG_PATH, METADATA_PATH)
|
||||||
|
|
||||||
const Server = new server(PORT, HOST, UID, GID, CONFIG_PATH, METADATA_PATH, AUDIOBOOK_PATH)
|
const Server = new server(SOURCE, PORT, HOST, UID, GID, CONFIG_PATH, METADATA_PATH)
|
||||||
Server.start()
|
Server.start()
|
||||||
|
@ -17,14 +17,6 @@ class Auth {
|
|||||||
return this.db.users
|
return this.db.users
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
|
||||||
var root = this.users.find(u => u.type === 'root')
|
|
||||||
if (!root) {
|
|
||||||
Logger.fatal('No Root User', this.users)
|
|
||||||
throw new Error('No Root User')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cors(req, res, next) {
|
cors(req, res, next) {
|
||||||
res.header('Access-Control-Allow-Origin', '*')
|
res.header('Access-Control-Allow-Origin', '*')
|
||||||
res.header("Access-Control-Allow-Methods", 'GET, POST, PATCH, PUT, DELETE, OPTIONS')
|
res.header("Access-Control-Allow-Methods", 'GET, POST, PATCH, PUT, DELETE, OPTIONS')
|
||||||
|
68
server/Db.js
68
server/Db.js
@ -46,6 +46,10 @@ class Db {
|
|||||||
this.previousVersion = null
|
this.previousVersion = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hasRootUser() {
|
||||||
|
return this.users.some(u => u.id === 'root')
|
||||||
|
}
|
||||||
|
|
||||||
getEntityDb(entityName) {
|
getEntityDb(entityName) {
|
||||||
if (entityName === 'user') return this.usersDb
|
if (entityName === 'user') return this.usersDb
|
||||||
else if (entityName === 'session') return this.sessionsDb
|
else if (entityName === 'session') return this.sessionsDb
|
||||||
@ -70,33 +74,6 @@ class Db {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultUser(token) {
|
|
||||||
return new User({
|
|
||||||
id: 'root',
|
|
||||||
type: 'root',
|
|
||||||
username: 'root',
|
|
||||||
pash: '',
|
|
||||||
stream: null,
|
|
||||||
token,
|
|
||||||
isActive: true,
|
|
||||||
createdAt: Date.now()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
getDefaultLibrary() {
|
|
||||||
var defaultLibrary = new Library()
|
|
||||||
defaultLibrary.setData({
|
|
||||||
id: 'main',
|
|
||||||
name: 'Main',
|
|
||||||
folder: { // Generates default folder
|
|
||||||
id: 'audiobooks',
|
|
||||||
fullPath: global.AudiobookPath,
|
|
||||||
libraryId: 'main'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return defaultLibrary
|
|
||||||
}
|
|
||||||
|
|
||||||
reinit() {
|
reinit() {
|
||||||
this.libraryItemsDb = new njodb.Database(this.LibraryItemsPath)
|
this.libraryItemsDb = new njodb.Database(this.LibraryItemsPath)
|
||||||
this.usersDb = new njodb.Database(this.UsersPath)
|
this.usersDb = new njodb.Database(this.UsersPath)
|
||||||
@ -123,23 +100,36 @@ class Db {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createRootUser(username, pash, token) {
|
||||||
|
const newRoot = new User({
|
||||||
|
id: 'root',
|
||||||
|
type: 'root',
|
||||||
|
username,
|
||||||
|
pash,
|
||||||
|
token,
|
||||||
|
isActive: true,
|
||||||
|
createdAt: Date.now()
|
||||||
|
})
|
||||||
|
return this.insertEntity('user', newRoot)
|
||||||
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
await this.load()
|
await this.load()
|
||||||
|
|
||||||
// Insert Defaults
|
// Insert Defaults
|
||||||
var rootUser = this.users.find(u => u.type === 'root')
|
// var rootUser = this.users.find(u => u.type === 'root')
|
||||||
if (!rootUser) {
|
// if (!rootUser) {
|
||||||
var token = await jwt.sign({ userId: 'root' }, process.env.TOKEN_SECRET)
|
// var token = await jwt.sign({ userId: 'root' }, process.env.TOKEN_SECRET)
|
||||||
Logger.debug('Generated default token', token)
|
// Logger.debug('Generated default token', token)
|
||||||
Logger.info('[Db] Root user created')
|
// Logger.info('[Db] Root user created')
|
||||||
await this.insertEntity('user', this.getDefaultUser(token))
|
// await this.insertEntity('user', this.getDefaultUser(token))
|
||||||
} else {
|
// } else {
|
||||||
Logger.info(`[Db] Root user exists, pw: ${rootUser.hasPw}`)
|
// Logger.info(`[Db] Root user exists, pw: ${rootUser.hasPw}`)
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!this.libraries.length) {
|
// if (!this.libraries.length) {
|
||||||
await this.insertEntity('library', this.getDefaultLibrary())
|
// await this.insertEntity('library', this.getDefaultLibrary())
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!this.serverSettings) {
|
if (!this.serverSettings) {
|
||||||
this.serverSettings = new ServerSettings()
|
this.serverSettings = new ServerSettings()
|
||||||
|
@ -34,18 +34,18 @@ const AudioMetadataMangaer = require('./managers/AudioMetadataManager')
|
|||||||
const RssFeedManager = require('./managers/RssFeedManager')
|
const RssFeedManager = require('./managers/RssFeedManager')
|
||||||
|
|
||||||
class Server {
|
class Server {
|
||||||
constructor(PORT, HOST, UID, GID, CONFIG_PATH, METADATA_PATH, AUDIOBOOK_PATH) {
|
constructor(SOURCE, PORT, HOST, UID, GID, CONFIG_PATH, METADATA_PATH) {
|
||||||
|
this.Source = SOURCE
|
||||||
this.Port = PORT
|
this.Port = PORT
|
||||||
this.Host = HOST
|
this.Host = HOST
|
||||||
global.Uid = isNaN(UID) ? 0 : Number(UID)
|
global.Uid = isNaN(UID) ? 0 : Number(UID)
|
||||||
global.Gid = isNaN(GID) ? 0 : Number(GID)
|
global.Gid = isNaN(GID) ? 0 : Number(GID)
|
||||||
global.ConfigPath = Path.normalize(CONFIG_PATH)
|
global.ConfigPath = Path.normalize(CONFIG_PATH)
|
||||||
global.AudiobookPath = Path.normalize(AUDIOBOOK_PATH)
|
|
||||||
global.MetadataPath = Path.normalize(METADATA_PATH)
|
global.MetadataPath = Path.normalize(METADATA_PATH)
|
||||||
|
|
||||||
// Fix backslash if not on Windows
|
// Fix backslash if not on Windows
|
||||||
if (process.platform !== 'win32') {
|
if (process.platform !== 'win32') {
|
||||||
global.ConfigPath = global.ConfigPath.replace(/\\/g, '/')
|
global.ConfigPath = global.ConfigPath.replace(/\\/g, '/')
|
||||||
global.AudiobookPath = global.AudiobookPath.replace(/\\/g, '/')
|
|
||||||
global.MetadataPath = global.MetadataPath.replace(/\\/g, '/')
|
global.MetadataPath = global.MetadataPath.replace(/\\/g, '/')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,10 +57,6 @@ class Server {
|
|||||||
fs.mkdirSync(global.MetadataPath)
|
fs.mkdirSync(global.MetadataPath)
|
||||||
filePerms.setDefaultDirSync(global.MetadataPath, false)
|
filePerms.setDefaultDirSync(global.MetadataPath, false)
|
||||||
}
|
}
|
||||||
if (!fs.pathExistsSync(global.AudiobookPath)) {
|
|
||||||
fs.mkdirSync(global.AudiobookPath)
|
|
||||||
filePerms.setDefaultDirSync(global.AudiobookPath, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.db = new Db()
|
this.db = new Db()
|
||||||
this.watcher = new Watcher()
|
this.watcher = new Watcher()
|
||||||
@ -140,8 +136,6 @@ class Server {
|
|||||||
await this.db.init()
|
await this.db.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.auth.init()
|
|
||||||
|
|
||||||
await this.checkUserMediaProgress() // Remove invalid user item progress
|
await this.checkUserMediaProgress() // Remove invalid user item progress
|
||||||
await this.purgeMetadata() // Remove metadata folders without library item
|
await this.purgeMetadata() // Remove metadata folders without library item
|
||||||
|
|
||||||
@ -231,6 +225,25 @@ class Server {
|
|||||||
|
|
||||||
app.post('/login', this.getLoginRateLimiter(), (req, res) => this.auth.login(req, res))
|
app.post('/login', this.getLoginRateLimiter(), (req, res) => this.auth.login(req, res))
|
||||||
app.post('/logout', this.authMiddleware.bind(this), this.logout.bind(this))
|
app.post('/logout', this.authMiddleware.bind(this), this.logout.bind(this))
|
||||||
|
app.post('/init', (req, res) => {
|
||||||
|
if (this.db.hasRootUser) {
|
||||||
|
Logger.error(`[Server] attempt to init server when server already has a root user`)
|
||||||
|
return res.sendStatus(500)
|
||||||
|
}
|
||||||
|
this.initializeServer(req, res)
|
||||||
|
})
|
||||||
|
app.get('/status', (req, res) => {
|
||||||
|
// status check for client to see if server has been initialized
|
||||||
|
// server has been initialized if a root user exists
|
||||||
|
const payload = {
|
||||||
|
isInit: this.db.hasRootUser
|
||||||
|
}
|
||||||
|
if (!payload.isInit) {
|
||||||
|
payload.ConfigPath = global.ConfigPath
|
||||||
|
payload.MetadataPath = global.MetadataPath
|
||||||
|
}
|
||||||
|
res.json(payload)
|
||||||
|
})
|
||||||
app.get('/ping', (req, res) => {
|
app.get('/ping', (req, res) => {
|
||||||
Logger.info('Recieved ping')
|
Logger.info('Recieved ping')
|
||||||
res.json({ success: true })
|
res.json({ success: true })
|
||||||
@ -293,6 +306,17 @@ class Server {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async initializeServer(req, res) {
|
||||||
|
Logger.info(`[Server] Initializing new server`)
|
||||||
|
const newRoot = req.body.newRoot
|
||||||
|
let rootPash = newRoot.password ? await this.auth.hashPass(newRoot.password) : ''
|
||||||
|
if (!rootPash) Logger.warn(`[Server] Creating root user with no password`)
|
||||||
|
let rootToken = await this.auth.generateAccessToken({ userId: 'root' })
|
||||||
|
await this.db.createRootUser(newRoot.username, rootPash, rootToken)
|
||||||
|
|
||||||
|
res.sendStatus(200)
|
||||||
|
}
|
||||||
|
|
||||||
async filesChanged(fileUpdates) {
|
async filesChanged(fileUpdates) {
|
||||||
Logger.info('[Server]', fileUpdates.length, 'Files Changed')
|
Logger.info('[Server]', fileUpdates.length, 'Files Changed')
|
||||||
await this.scanner.scanFilesChanged(fileUpdates)
|
await this.scanner.scanFilesChanged(fileUpdates)
|
||||||
@ -433,7 +457,6 @@ class Server {
|
|||||||
const initialPayload = {
|
const initialPayload = {
|
||||||
// TODO: this is sent with user auth now, update mobile app to use that then remove this
|
// TODO: this is sent with user auth now, update mobile app to use that then remove this
|
||||||
serverSettings: this.db.serverSettings.toJSON(),
|
serverSettings: this.db.serverSettings.toJSON(),
|
||||||
audiobookPath: global.AudiobookPath,
|
|
||||||
metadataPath: global.MetadataPath,
|
metadataPath: global.MetadataPath,
|
||||||
configPath: global.ConfigPath,
|
configPath: global.ConfigPath,
|
||||||
user: client.user.toJSONForBrowser(),
|
user: client.user.toJSONForBrowser(),
|
||||||
|
@ -42,6 +42,7 @@ class LibraryController {
|
|||||||
newLibraryPayload.displayOrder = this.db.libraries.length + 1
|
newLibraryPayload.displayOrder = this.db.libraries.length + 1
|
||||||
library.setData(newLibraryPayload)
|
library.setData(newLibraryPayload)
|
||||||
await this.db.insertEntity('library', library)
|
await this.db.insertEntity('library', library)
|
||||||
|
// TODO: Only emit to users that have access
|
||||||
this.emitter('library_added', library.toJSON())
|
this.emitter('library_added', library.toJSON())
|
||||||
|
|
||||||
// Add library watcher
|
// Add library watcher
|
||||||
|
@ -242,13 +242,6 @@ class DownloadManager {
|
|||||||
if (shouldIncludeCover) {
|
if (shouldIncludeCover) {
|
||||||
var _cover = audiobook.book.coverFullPath.replace(/\\/g, '/')
|
var _cover = audiobook.book.coverFullPath.replace(/\\/g, '/')
|
||||||
|
|
||||||
// Supporting old local file prefix
|
|
||||||
var bookCoverPath = audiobook.book.cover ? audiobook.book.cover.replace(/\\/g, '/') : null
|
|
||||||
if (!_cover && bookCoverPath && bookCoverPath.startsWith('/local')) {
|
|
||||||
_cover = Path.posix.join(global.AudiobookPath, _cover.replace('/local', ''))
|
|
||||||
Logger.debug('Local cover url', _cover)
|
|
||||||
}
|
|
||||||
|
|
||||||
ffmpegInputs.push({
|
ffmpegInputs.push({
|
||||||
input: _cover,
|
input: _cover,
|
||||||
options: ['-f image2pipe']
|
options: ['-f image2pipe']
|
||||||
|
Loading…
Reference in New Issue
Block a user