Redesign file browser, file upload (server)

This commit is contained in:
2024-11-12 23:50:21 +01:00
parent e690fb984d
commit 24f44e73f4
19 changed files with 431 additions and 4095 deletions

View File

@@ -1,13 +1,19 @@
import { Request, Response, NextFunction, Router } from 'express'
import { deleteAllTables, deleteExerciseProgressTables, prepopulateDatabase, prepopulateExerciseDatabase } from '../scripts/databaseHelper'
import fs from "fs"
export const api = Router()
/**
* Status check endpoint
*/
api.get("/", (req: Request, res: Response, next: NextFunction) => {
res.status(200).send()
})
/**
* Reset the whole database to factory state
* Doesn't effect ExerciseTable and ExerciseGroupTable
*/
api.get("/resetdatabase", async (req: Request, res: Response, next: NextFunction) => {
// Step 1: Delete all data tables
deleteAllTables()
@@ -19,35 +25,13 @@ api.get("/resetdatabase", async (req: Request, res: Response, next: NextFunction
res.status(200).send()
})
/**
* Reset ExerciseTable and ExerciseGroupTable to factory state
*/
api.get("/resetExerciseProgress", async (req: Request, res: Response, next: NextFunction) => {
deleteExerciseProgressTables()
await prepopulateExerciseDatabase()
res.status(200).send()
})
/**
* Get all uploaded file names
*/
api.get("/files", async (req: Request, res: Response) => {
let dirNames = fs.readdirSync("./backend/images")
let result = []
dirNames.forEach(dir => {
let fileNames = fs.readdirSync("./backend/images/" + dir)
result.push({
folder: dir,
files: fileNames.map(file => {
return {
name: file,
size: fs.statSync("./backend/images/" + dir + "/" + file).size,
url: "http://localhost:3000/static/" + dir + "/" + file
}
})
})
})
res.status(200).json(result)
})

View File

@@ -11,7 +11,9 @@ import { calcOverallRating, calcRatingValues } from "../scripts/calcScripts";
export const band = Router()
// Get all bands
/**
* Get all bands
*/
band.get("/", (req: Request, res: Response) => {
let sort = req.query.sort
let count = req.query.count
@@ -64,7 +66,9 @@ band.get("/", (req: Request, res: Response) => {
})
})
// Get all information about one band
/**
* Get all information about one band
*/
band.get("/band/:name", (req: Request, res: Response) => {
Band.findOne({
where: {
@@ -123,7 +127,9 @@ band.get("/band/:name", (req: Request, res: Response) => {
})
// Band search
/**
* Band search
*/
band.get("/search", (req: Request, res: Response) => {
Band.findAll({
where: {
@@ -139,7 +145,9 @@ band.get("/search", (req: Request, res: Response) => {
})
// Edit band
/**
* Edit band
*/
band.patch("/", (req: Request, res: Response) => {
Band.update(req.body, {
where: {
@@ -152,7 +160,9 @@ band.patch("/", (req: Request, res: Response) => {
})
// New band
/**
* New band
*/
band.post("/", (req: Request, res: Response) => {
Band.create(req.body)
.then(result => {
@@ -160,6 +170,9 @@ band.post("/", (req: Request, res: Response) => {
})
})
/**
* Delete a band
*/
band.delete("/", (req: Request, res: Response) => {
Band.destroy({
where: {

View File

@@ -0,0 +1,52 @@
import { Request, Response, NextFunction, Router } from 'express'
import fs from "fs"
import multer from "multer"
const upload = multer({ dest: './backend/images/' })
export const files = Router()
/**
* Get all folders
*/
files.get("/folders", async (req: Request, res: Response) => {
let dirNames = fs.readdirSync("./backend/images")
let result = []
dirNames.forEach(dir => {
result.push({
name: dir,
nrOfItems: fs.readdirSync("./backend/images/" + dir).length
})
})
res.status(200).json(result)
})
/**
* Get all uploaded file names by file name
*/
files.get("/:folder", async (req: Request, res: Response) => {
let result = []
let fileNames = fs.readdirSync("./backend/images/" + req.params.folder + "/")
fileNames.forEach(file => {
result.push({
name: file,
size: fs.statSync("./backend/images/" + req.params.folder + "/" + file).size,
url: "http://localhost:3000/static/" + req.params.folder + "/" + file
})
})
res.status(200).json(result)
})
/**
* Upload a file
*/
files.post("/", upload.single("file"), function (req: Request, res: Response, next: NextFunction) {
console.log(req.file)
res.status(200).send()
})

View File

@@ -11,6 +11,7 @@ import { genre } from './routes/genre.routes'
import { location } from './routes/location.routes'
import { city } from './routes/city.routes'
import { exercises } from './routes/exercise.routes'
import { files } from './routes/files.routes'
const app = express()
const port = 3000
@@ -43,7 +44,7 @@ app.use("/accounts", account)
app.use("/cities", city)
app.use("/concerts", concert)
app.use("/exercises", exercises)
app.use("/files", files)
// Start server
const server = app.listen(port, () => {

File diff suppressed because it is too large Load Diff

View File

@@ -34,6 +34,7 @@
},
"dependencies": {
"@mdi/font": "^7.4.47",
"@types/multer": "^1.4.12",
"@vueuse/core": "^11.1.0",
"axios": "^1.7.7",
"body-parser": "^1.20.2",
@@ -41,6 +42,7 @@
"electron-squirrel-startup": "^1.0.1",
"express": "^4.21.1",
"moment": "^2.30.1",
"multer": "^1.4.5-lts.1",
"pinia": "^2.2.4",
"reflect-metadata": "^0.2.2",
"sequelize": "^6.37.4",

View File

@@ -0,0 +1,45 @@
import axios from "axios"
const BASE_URL = "http://localhost:3000/files"
/**
* Fetch all public folders on server
*
* @returns Response from server
*/
export function fetchFolderNames() {
return axios.get(BASE_URL + "/folders")
}
/**
* Fetch all static file names
*
* @param dirName Name of folder where to scan files
*
* @returns Response from server
*/
export function fetchFileNames(dirName: string) {
return axios.get(BASE_URL + "/" + dirName)
}
/**
* Upload a file to the server
*
* @param file File to store on server
*
* @returns Response from server
*/
export function postFile(file, folder: string) {
let formData = new FormData()
formData.append("file", file)
formData.append("folder", folder)
console.log(formData)
return axios.post(BASE_URL, formData, {
headers: {
"Content-Type": "multipart/form-data"
}
})
}

View File

@@ -27,13 +27,4 @@ export function resetDatabase() {
*/
export function resetExerciseProgress() {
return axios.get(BASE_URL + "/resetExerciseProgress")
}
/**
* Fetch all static file names
*
* @returns Response from server
*/
export function fetchFileNames() {
return axios.get(BASE_URL + "/files")
}

View File

@@ -238,7 +238,10 @@
},
"user": "Angaben zur Person",
"registrationNumber": "Matrikelnummer",
"yourFullName": "Vollständiger Name"
"yourFullName": "Vollständiger Name",
"chooseFile": "Datei auswählen",
"chooseDestinationFolder": "Zielordner auswählen",
"upload": "Hochladen"
},
"genre": {
"withoutBand": "ohne Band"

View File

@@ -238,7 +238,10 @@
},
"user": "About person",
"registrationNumber": "Matrikel number",
"yourFullName": "Full name"
"yourFullName": "Full name",
"chooseFile": "Choose file",
"chooseDestinationFolder": "Choose destination folder",
"upload": "Upload"
},
"genre": {
"withoutBand": "without Band"

View File

@@ -8,6 +8,7 @@ import { useGenreStore } from '@/stores/genre.store';
import { usePreferencesStore } from '@/stores/preferences.store';
import dashboardCard from './dashboardCard.vue';
import { useOrderStore } from '@/stores/order.store';
import { useFilesStore } from '@/stores/files.store';
const concertStore = useConcertStore()
const bandStore = useBandStore()
@@ -17,10 +18,11 @@ const locationStore = useLocationStore()
const exerciseStore = useExerciseStore()
const preferencesStore = usePreferencesStore()
const orderStore = useOrderStore()
const filesStore = useFilesStore()
exerciseStore.solveExercise(2, 1)
preferencesStore.getStaticFiles()
filesStore.getStaticFolders()
bandStore.getBands()
locationStore.getLocations()
genreStore.getGenres()
@@ -98,10 +100,10 @@ orderStore.getAllOrders()
<dashboard-card
:title="$t('misc.file', 2)"
icon="mdi-file"
:first-line="preferencesStore.staticFiles.reduce((counter, obj) => {
return counter += obj.files.length
}, 0) + ' ' + $t('misc.file', 2)"
:second-line="preferencesStore.staticFiles.length + ' ' + $t('misc.folder', 2)"
:first-line="filesStore.staticFolders.reduce((counter, obj) => {
return counter + obj.nrOfItems
}, 0) + ' ' + $t('misc.file', 2)"
:second-line="filesStore.staticFolders.length + ' ' + $t('misc.folder', 2)"
button-route="/admin/files"
:loading="preferencesStore.fetchInProgress"
/>

View File

@@ -1,15 +0,0 @@
<script setup lang="ts">
import actionDialog from '@/components/basics/actionDialog.vue';
const showDialog = defineModel("showDialog")
defineProps({
url: String
})
</script>
<template>
<action-dialog v-model="showDialog" max-width="500">
<v-img :src="url" max-height="400" />
</action-dialog>
</template>

View File

@@ -0,0 +1,64 @@
<script setup lang="ts">
import actionDialog from '@/components/basics/actionDialog.vue';
import outlinedButton from '@/components/basics/outlinedButton.vue';
import { useFilesStore } from '@/stores/files.store';
import { ref } from 'vue';
const filesStore = useFilesStore()
const test = ref()
</script>
<template>
<action-dialog
v-model="filesStore.showFileUploadDialog"
:title="$t('misc.uploadFile')"
icon="mdi-file"
max-width="800"
>
<v-form :model-value="test">
<v-container>
<v-row>
<v-col>
<v-file-input
v-model="filesStore.fileUpload"
clearable
:label="$t('misc.chooseFile')"
:disabled="filesStore.fetchInProgress"
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-radio-group
v-model="filesStore.fileUploadDir"
:label="$t('misc.chooseDestinationFolder')"
:disabled="filesStore.fetchInProgress"
>
<v-radio
v-for="folder of filesStore.staticFolders"
:label="folder.name + '/'"
:value="folder.name"
/>
</v-radio-group>
</v-col>
</v-row>
</v-container>
<v-btn type="submit">Submit</v-btn>
</v-form>
<template #actions>
<outlined-button
@click="filesStore.uploadFile"
prepend-icon="mdi-file-upload"
color="green"
:disabled="filesStore.fileUploadDir.length == 0 || filesStore.fileUpload == undefined"
:loading="filesStore.fetchInProgress"
>
{{ $t('misc.upload') }}
</outlined-button>
</template>
</action-dialog>
</template>

View File

@@ -1,44 +1,64 @@
<script setup lang="ts">
import adminDataLayout from '@/layouts/adminDataLayout.vue';
import { usePreferencesStore } from '@/stores/preferences.store';
import filePreviewDialog from './filePreviewDialog.vue';
import { ref } from 'vue';
import FileUploadDialog from './fileUploadDialog.vue';
import { useFilesStore } from '@/stores/files.store';
const preferencesStore = usePreferencesStore()
const showDialog = ref(false)
const filesStore = useFilesStore()
const showPreviewDialog = ref(false)
const previewFile = ref("")
preferencesStore.getStaticFiles()
filesStore.getStaticFolders()
</script>
<template>
<admin-data-layout
:add-button-string="$t('misc.uploadFile')"
:fetch-in-progress="preferencesStore.fetchInProgress"
:on-add-click="() => { /** todo */ }"
:fetch-in-progress="filesStore.fetchInProgress"
:on-add-click="() => { filesStore.showFileUploadDialog = true }"
>
<v-row>
<v-col
v-for="folder of preferencesStore.staticFiles"
cols="12"
md="3"
sm="6"
>
<v-row >
<v-col cols="2" class="border">
<v-list>
<v-list-subheader>{{ folder.folder }}/</v-list-subheader>
<v-list-item
v-for="file of folder.files"
:title="file.name"
:subtitle="Math.round(file.size / 1024) + ' KB'"
@click="() => { previewFile = file.url; showDialog = true }"
<v-list-item
v-for="folder of filesStore.staticFolders"
:key="folder.name"
:value="folder"
:title="folder.name + '/'"
@click="filesStore.selectedFolder = folder; filesStore.getStaticFiles()"
/>
</v-list>
</v-col>
<v-col cols="4" class="border">
<v-skeleton-loader
:loading="filesStore.fetchInProgress"
type="list-item-two-line"
>
<v-list max-height="800" class="w-100">
<v-list-item
v-for="file of filesStore.staticFiles"
:title="file.name"
:value="file.name"
:subtitle="Math.round(file.size / 1024) + ' KB'"
@click="() => { filesStore.selectedFile = file }"
/>
</v-list>
</v-skeleton-loader>
</v-col>
<v-col class="border">
<v-img
v-if="filesStore.selectedFile != undefined"
:src="filesStore.selectedFile.url" max-height="400" />
</v-col>
</v-row>
</admin-data-layout>
<file-preview-dialog
v-model:show-dialog="showDialog"
v-model:show-dialog="showPreviewDialog"
:url="previewFile"
/>
<file-upload-dialog />
</template>

View File

@@ -0,0 +1,61 @@
import { fetchFileNames, fetchFolderNames, postFile } from "@/data/api/files.api";
import { defineStore } from "pinia";
import { ref } from "vue";
export const useFilesStore = defineStore('filesStore', {
state: () => ({
/** Request to server sent, waiting for data response */
fetchInProgress: ref(false),
staticFolders: ref<Array<{name: string, nrOfItems: number}>>([]),
selectedFolder: ref<{name: string, nrOfItems: number}>(),
/** List of files on the server */
staticFiles: ref<Array<{name: string, size: number, url: string}>>([]),
selectedFile: ref<{name: string, size: number, url: string}>(),
showFileUploadDialog: ref(false),
fileUpload: ref(),
fileUploadDir: ref(""),
}),
actions: {
async getStaticFolders() {
this.fetchInProgress = true
fetchFolderNames()
.then(res => {
this.staticFolders = res.data
this.fetchInProgress = false
})
},
/**
* Request all available static files on server
*/
async getStaticFiles() {
this.fetchInProgress = true
fetchFileNames(this.selectedFolder.name)
.then(res => {
this.staticFiles = res.data
this.fetchInProgress = false
})
},
async uploadFile() {
this.fetchInProgress = true
postFile(this.uploadFile, this.fileUploadDir)
.then(response => {
console.log(response)
this.showFileUploadDialog = false
this.fetchInProgress = false
})
},
}
})

View File

@@ -3,7 +3,7 @@ import { useLocalStorage } from "@vueuse/core";
import { ThemeEnum } from "../data/enums/themeEnums";
import { LanguageEnum } from "../data/enums/languageEnum";
import { ref } from "vue";
import { fetchFileNames, fetchServerState, resetDatabase, resetExerciseProgress } from "@/data/api/mainApi";
import { fetchServerState,resetDatabase, resetExerciseProgress } from "@/data/api/mainApi";
import { ServerStateEnum } from "@/data/enums/serverStateEnum";
import { BannerStateEnum } from "@/data/enums/bannerStateEnum";
import { useFeedbackStore } from "./feedback.store";
@@ -32,9 +32,6 @@ export const usePreferencesStore = defineStore('preferencesStore', {
/** Show the "Factory reset" confirm dialog */
showFactoryResetDialog: ref(false),
/** List of files on the server */
staticFiles: ref([]),
/** Marks the first run of the app */
firstStartup: useLocalStorage<Boolean>("hackmycart/preferencesStore/firstStartup", true),
@@ -108,19 +105,6 @@ export const usePreferencesStore = defineStore('preferencesStore', {
})
},
/**
* Request all available static files on server
*/
async getStaticFiles() {
this.fetchInProgress = true
fetchFileNames()
.then(res => {
this.staticFiles = res.data
this.fetchInProgress = false
})
},
/**
* Reset all store values to factory state
*/

View File

@@ -16,5 +16,5 @@
"backend/**/*.ts",
"backend/**/*.json",
"backend/images/**/**/*"
]
, "backend/server.js" ]
}