License system implemented

This commit is contained in:
2024-11-25 18:55:28 +01:00
parent 1b0f48d374
commit 2e15d4a960
22 changed files with 313 additions and 18 deletions

182
backend/data/licenses.json Normal file
View File

@@ -0,0 +1,182 @@
[
{
"image": "alex-turner.jpg",
"license": "CC BY 2.0",
"creator": "Raph_PH",
"url": "https://upload.wikimedia.org/wikipedia/commons/9/95/Alex_Turner%2C_Way_Out_West_2018.jpg"
},
{
"image": "andy-nicholson.jpg",
"license": "CC BY 2.0",
"creator": "Lola's Big Adventure!",
"url": "https://upload.wikimedia.org/wikipedia/commons/6/6c/Andy_Nicholson_%28cropped%29.jpg"
},
{
"image": "anthony-kiedis.jpg",
"license": "CC BY 2.0",
"creator": "Hel Davies",
"url": "https://upload.wikimedia.org/wikipedia/commons/c/ca/Anthony_Kiedis_2022.jpg"
},
{
"image": "chris-martin.jpg",
"license": "CC BY 2.0",
"creator": "Raph_PH",
"url": "https://upload.wikimedia.org/wikipedia/commons/6/68/ChrisMartinManch030623_%28cropped%29.jpg"
},
{
"image": "chris-wolstenholme.jpg",
"license": "CC BY-SA 4.0",
"creator": "Markus Felix",
"url": "https://upload.wikimedia.org/wikipedia/commons/e/eb/2018_Chris_Wolstenholme_%28cropped%29.jpg"
},
{
"image": "flea.jpg",
"license": "CC BY 2.0",
"creator": "Piyush Kumar",
"url": "https://upload.wikimedia.org/wikipedia/commons/8/8e/Flea_1012_%282%29.jpg"
},
{
"image": "chad-smith.jpg",
"license": "Gemeinfrei",
"creator": "Bojosoto",
"url": "https://upload.wikimedia.org/wikipedia/commons/4/42/Chadsmithclinic.jpg"
},
{
"image": "john-frusciante.jpg",
"license": "CC BY-SA 2.0",
"creator": "Hel Davies",
"url": "https://upload.wikimedia.org/wikipedia/commons/1/1f/John_Frusciante_%2852279466415%29.jpg"
},
{
"image": "logo.png",
"license": "MIT",
"creator": "Tobias Zoghaib",
"url": ""
},
{
"image": "lanxess-arena-indoor.jpg",
"license": "CC BY-SA 3.0",
"creator": "Admin Kübelbeck",
"url": "https://upload.wikimedia.org/wikipedia/commons/f/f3/Koelnarena_inside.jpg"
},
{
"image": "lanxess-arena-outdoor.jpg",
"license": "CC BY-SA 2.0",
"creator": "Rolf H.",
"url": "https://upload.wikimedia.org/wikipedia/commons/1/18/Lanxess_Arena_Flight_over_Cologne.jpg"
},
{
"image": "red-hot-chili-peppers-1.jpg",
"license": "CC BY-SA 4.0",
"creator": "Kreepin Deth",
"url": "https://upload.wikimedia.org/wikipedia/commons/1/14/RHCP_Live_in_London_26_June_2022.jpg"
},
{
"image": "swiss-life-hall-indoor.jpg",
"license": "CC BY-SA 3.0",
"creator": "Bernd Schwabe in Hannover",
"url": "https://upload.wikimedia.org/wikipedia/commons/8/89/2013-09-18_Besuch_14._Dalai_Lama_Tendzin_Gyatsho_in_Hannover%2C_future4children%2C_Swiss_Life_Hall%2C_%2876%29.JPG"
},
{
"image": "swiss-life-hall-outdoor.jpg",
"license": "Public Domain",
"creator": "AxelHH",
"url": "https://upload.wikimedia.org/wikipedia/commons/6/66/AWD_Hall_Seite.jpg"
},
{
"image": "astra-kulturhaus-outdoor.jpg",
"license": "CC BY 2.0",
"creator": "Marcus Grbac",
"url": "https://upload.wikimedia.org/wikipedia/commons/f/fd/Astra_Kulturhaus_Biergarten_RAW_Berlin_July_2017.jpg"
},
{
"image": "thom-yorke.jpg",
"license": "CC BY 2.0",
"creator": "Raph_PH",
"url": "https://upload.wikimedia.org/wikipedia/commons/2/25/RadioheadMontreal170718-70_%2843600493681%29_%28cropped%29.jpg"
},
{
"image": "rami-jaffee.jpg",
"license": "CC BY 2.0",
"creator": "Raph_PH",
"url": "https://upload.wikimedia.org/wikipedia/commons/f/fa/Rami_Jaffee_1.jpg"
},
{
"image": "philip-selway.jpg",
"license": "CC BY-SA 2.0",
"creator": "Michell Zappa",
"url": "https://upload.wikimedia.org/wikipedia/commons/1/1f/Phil_Selway.jpg"
},
{
"image": "phil-harvey.jpg",
"license": "CC BY-SA 3.0",
"creator": "Hayley St. James",
"url": "https://upload.wikimedia.org/wikipedia/commons/3/36/PhilHarveyNewYork17062021.png"
},
{
"image": "pat-smear.jpg",
"license": "GNU v.1.2",
"creator": "Andrew Burns",
"url": "https://upload.wikimedia.org/wikipedia/commons/6/66/Patsmear.jpg"
},
{
"image": "mike-kerr.jpg",
"license": "CC BY 4.0",
"creator": "Dena Flows",
"url": "https://upload.wikimedia.org/wikipedia/commons/8/85/017-BIME-2017-Royal-Blood-27X17-por-Dena-Flows.jpg"
},
{
"image": "matthew-bellamy.jpg",
"license": "CC BY 3.0",
"creator": "Minerva97",
"url": "https://upload.wikimedia.org/wikipedia/commons/d/d0/2009_Matthew_Bellamy_%28cropped%29.jpg"
},
{
"image": "capitol-outside.jpg",
"license": "",
"creator": "AxelHH",
"url": "https://upload.wikimedia.org/wikipedia/commons/6/6c/Hannover_Capitol_ganz.jpg"
},
{
"image": "red-hot-chili-peppers-logo.png",
"license": "",
"creator": "Viiticus",
"url": "https://upload.wikimedia.org/wikipedia/commons/3/31/Red_Hot_Chili_Peppers_logo.svg"
},
{
"image": "red-hot-chili-peppers-2.jpg",
"license": "CC BY-SA 4.0",
"creator": "Roberto Gianardi",
"url": "https://upload.wikimedia.org/wikipedia/commons/6/64/Red_Hot_Chili_Peppers_Bologna_2016.jpg"
},
{
"image": "arctic-monkeys-1.jpg",
"license": "CC BY 3.0",
"creator": "Bill Ebbesen",
"url": "https://upload.wikimedia.org/wikipedia/commons/0/04/Arctic_Monkeys_-_Orange_Stage_-_Roskilde_Festival_2014.jpg"
},
{
"image": ".jpg",
"license": "CC BY-SA 2.0",
"creator": "",
"url": ""
},
{
"image": "arctic-monkeys-3.jpg",
"license": "CC BY-SA 2.0",
"creator": "Aurelien Guichard",
"url": "https://upload.wikimedia.org/wikipedia/commons/f/f8/Arctic_Monkeys_%40_Shepherds_Bush_Empire.jpg"
},
{
"image": "european-tour-arctic-monkeys.jpg",
"license": "Gemeinfrei",
"creator": "Matthew Cooper",
"url": "https://upload.wikimedia.org/wikipedia/commons/e/e7/%22AM%22_%28Arctic_Monkeys%29.jpg"
},
{
"image": ".jpg",
"license": "CC BY-SA 2.0",
"creator": "",
"url": ""
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 539 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 953 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 6.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 356 KiB

After

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 447 KiB

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 648 KiB

After

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 276 KiB

View File

@@ -1,7 +1,8 @@
import { Request, Response, NextFunction, Router } from 'express' import { Request, Response, NextFunction, Router } from 'express'
import fs from "fs" import fs, { createReadStream } from "fs"
import multer from "multer" import multer from "multer"
const upload = multer({ dest: './backend/images/' }) const upload = multer({ dest: './backend/images/' })
import licenses from "../data/licenses.json"
export const files = Router() export const files = Router()
@@ -32,17 +33,20 @@ files.get("/:folder", async (req: Request, res: Response) => {
let result = [] let result = []
let fileNames = fs.readdirSync("./backend/images/" + req.params.folder + "/") let fileNames = fs.readdirSync("./backend/images/" + req.params.folder + "/")
fileNames.forEach(file => { fileNames.forEach(file => {
let resData = fs.readFileSync("./backend/images/" + req.params.folder + "/" + file, "utf8") let resData = ""
let url = "http://localhost:3000/static/" + req.params.folder + "/" + file
if (file.endsWith("html") || file.endsWith("js")) {
resData = fs.readFileSync("./backend/images/" + req.params.folder + "/" + file, "utf8")
}
// todo License, Author, URL
result.push({ result.push({
name: file, name: file,
size: fs.statSync("./backend/images/" + req.params.folder + "/" + file).size, size: fs.statSync("./backend/images/" + req.params.folder + "/" + file).size,
content: resData, content: resData,
url: "http://localhost:3000/static/" + req.params.folder + "/" + file, url: url,
copyright: licenses.find(data => data.image == file)
}) })
}) })

View File

@@ -29,6 +29,7 @@ startDatabase()
const path = require('path') const path = require('path')
app.use('/static', express.static(path.join(__dirname, 'images'))) app.use('/static', express.static(path.join(__dirname, 'images')))
app.use("/exercises", exercises) app.use("/exercises", exercises)
app.use("/files", files)
// Add delay for more realistic response times // Add delay for more realistic response times
app.use((req, res, next) => { app.use((req, res, next) => {
@@ -44,7 +45,6 @@ app.use("/orders", order)
app.use("/accounts", account) app.use("/accounts", account)
app.use("/cities", city) app.use("/cities", city)
app.use("/concerts", concert) app.use("/concerts", concert)
app.use("/files", files)
// Start server // Start server
const server = app.listen(port, () => { const server = app.listen(port, () => {

50
package-lock.json generated
View File

@@ -14,6 +14,8 @@
"axios": "^1.7.7", "axios": "^1.7.7",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"cors": "^2.8.5", "cors": "^2.8.5",
"csv": "^6.3.11",
"csv-reader": "^1.0.12",
"exif-js": "^2.3.0", "exif-js": "^2.3.0",
"exifreader": "^4.25.0", "exifreader": "^4.25.0",
"express": "^4.21.1", "express": "^4.21.1",
@@ -4286,6 +4288,48 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/csv": {
"version": "6.3.11",
"resolved": "https://registry.npmjs.org/csv/-/csv-6.3.11.tgz",
"integrity": "sha512-a8bhT76Q546jOElHcTrkzWY7Py925mfLO/jqquseH61ThOebYwOjLbWHBqdRB4K1VpU36sTyIei6Jwj7QdEZ7g==",
"license": "MIT",
"dependencies": {
"csv-generate": "^4.4.2",
"csv-parse": "^5.6.0",
"csv-stringify": "^6.5.2",
"stream-transform": "^3.3.3"
},
"engines": {
"node": ">= 0.1.90"
}
},
"node_modules/csv-generate": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/csv-generate/-/csv-generate-4.4.2.tgz",
"integrity": "sha512-W6nVsf+rz0J3yo9FOjeer7tmzBJKaTTxf7K0uw6GZgRocZYPVpuSWWa5/aoWWrjQZj4/oNIKTYapOM7hiNjVMA==",
"license": "MIT"
},
"node_modules/csv-parse": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.6.0.tgz",
"integrity": "sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q==",
"license": "MIT"
},
"node_modules/csv-reader": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/csv-reader/-/csv-reader-1.0.12.tgz",
"integrity": "sha512-0AAgazKJUywtjvZbclNuovIiQY/WyvojWw15Y2k3kPixE+pDiOFnfg5FcH3CfDqqnrB2f3p5oPAc446EXD01Tw==",
"license": "MIT",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/csv-stringify": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.5.2.tgz",
"integrity": "sha512-RFPahj0sXcmUyjrObAK+DOWtMvMIFV328n4qZJhgX3x2RqkQgOTU2mCUmiFR0CzM6AzChlRSUErjiJeEt8BaQA==",
"license": "MIT"
},
"node_modules/de-indent": { "node_modules/de-indent": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
@@ -8618,6 +8662,12 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/stream-transform": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-3.3.3.tgz",
"integrity": "sha512-dALXrXe+uq4aO5oStdHKlfCM/b3NBdouigvxVPxCdrMRAU6oHh3KNss20VbTPQNQmjAHzZGKGe66vgwegFEIog==",
"license": "MIT"
},
"node_modules/streamsearch": { "node_modules/streamsearch": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",

View File

@@ -38,6 +38,8 @@
"axios": "^1.7.7", "axios": "^1.7.7",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"cors": "^2.8.5", "cors": "^2.8.5",
"csv": "^6.3.11",
"csv-reader": "^1.0.12",
"exif-js": "^2.3.0", "exif-js": "^2.3.0",
"exifreader": "^4.25.0", "exifreader": "^4.25.0",
"express": "^4.21.1", "express": "^4.21.1",

View File

@@ -0,0 +1,13 @@
export class FilesApiModel {
name: string
size: number
content: string
url: string
copyright: CopyRightModel = new CopyRightModel()
}
class CopyRightModel {
license: string = ""
creator: string = ""
url: string = ""
}

View File

@@ -19,6 +19,7 @@ filesStore.getStaticFolders()
:hide-add-button="true" :hide-add-button="true"
> >
<v-row > <v-row >
<!-- Column folder -->
<v-col cols="2" class="border"> <v-col cols="2" class="border">
<v-list> <v-list>
<v-list-item <v-list-item
@@ -27,10 +28,13 @@ filesStore.getStaticFolders()
:value="folder" :value="folder"
:title="folder.name + '/'" :title="folder.name + '/'"
@click="filesStore.selectedFolder = folder; filesStore.getStaticFiles()" @click="filesStore.selectedFolder = folder; filesStore.getStaticFiles()"
prepend-icon="mdi-folder"
/> />
</v-list> </v-list>
</v-col> </v-col>
<!-- Column files in folder -->
<v-col cols="4" class="border"> <v-col cols="4" class="border">
<v-skeleton-loader <v-skeleton-loader
:loading="filesStore.fetchInProgress" :loading="filesStore.fetchInProgress"
@@ -41,36 +45,68 @@ filesStore.getStaticFolders()
v-for="file of filesStore.staticFiles" v-for="file of filesStore.staticFiles"
:title="file.name" :title="file.name"
:value="file.name" :value="file.name"
:subtitle="Math.round(file.size / 1024) + ' KB'"
@click="() => { filesStore.selectedFile = file }" @click="() => { filesStore.selectedFile = file }"
/> >
<template #prepend>
<v-icon
:icon="file.name.endsWith('js') ? 'mdi-file' : 'mdi-image'"
:color="file.copyright != undefined ? 'green' : 'red'"
/>
</template>
</v-list-item>
</v-list> </v-list>
</v-skeleton-loader> </v-skeleton-loader>
</v-col> </v-col>
<!-- File detail viewer -->
<v-col class="border"> <v-col class="border">
<v-row> <v-row>
<v-col v-if="filesStore.selectedFile != undefined"> <v-col v-if="filesStore.selectedFile != undefined">
{{ filesStore.selectedFile.url }}
</v-col>
</v-row>
<v-row>
<v-col>
<v-textarea <v-textarea
v-if="filesStore.selectedFile != undefined && filesStore.selectedFile.name.endsWith('html')" v-if="filesStore.selectedFile != undefined && filesStore.selectedFile.name.endsWith('js')"
:model-value="filesStore.selectedFile.content" :model-value="filesStore.selectedFile.content"
variant="outlined" variant="outlined"
label="Content" label="Content"
height="300" height="300"
rows="30" rows="30"
/> />
<v-img <v-img
v-else-if="filesStore.selectedFile != undefined" v-else-if="filesStore.selectedFile != undefined"
:src="filesStore.selectedFile.url" max-height="400" :src="filesStore.selectedFile.url" max-height="400"
/> />
</v-col> </v-col>
</v-row> </v-row>
<!-- File details -->
<v-row>
<v-col v-if="filesStore.selectedFile != undefined">
<v-list>
<v-list-item prepend-icon="mdi-server">
{{ filesStore.selectedFile.url }}
</v-list-item>
<v-list-item prepend-icon="mdi-package">
{{ Math.round(filesStore.selectedFile.size / 1024) + ' KB' }}
</v-list-item>
<template v-if="filesStore.selectedFile['copyright'] != undefined">
<v-list-item prepend-icon="mdi-copyright">
{{ filesStore.selectedFile.copyright.license }}
</v-list-item>
<v-list-item prepend-icon="mdi-account">
{{ filesStore.selectedFile.copyright.creator }}
</v-list-item>
<v-list-item prepend-icon="mdi-web">
<a :href="filesStore.selectedFile.copyright.url" >Quelle</a>
</v-list-item>
</template>
</v-list>
</v-col>
</v-row>
</v-col> </v-col>
</v-row> </v-row>
</admin-data-layout> </admin-data-layout>

View File

@@ -1,4 +1,5 @@
import { fetchFileNames, fetchFolderNames, postFile } from "@/data/api/files.api"; import { fetchFileNames, fetchFolderNames, postFile } from "@/data/api/files.api";
import { FilesApiModel } from "@/data/models/files/filesApiModel";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { ref } from "vue"; import { ref } from "vue";
@@ -7,14 +8,17 @@ export const useFilesStore = defineStore('filesStore', {
/** Request to server sent, waiting for data response */ /** Request to server sent, waiting for data response */
fetchInProgress: ref(false), fetchInProgress: ref(false),
/** List of all folders on the server */
staticFolders: ref<Array<{name: string, nrOfItems: number}>>([]), staticFolders: ref<Array<{name: string, nrOfItems: number}>>([]),
/** Current selected folder in file browsre */
selectedFolder: ref<{name: string, nrOfItems: number}>(), selectedFolder: ref<{name: string, nrOfItems: number}>(),
/** List of files on the server */ /** List of files on the server */
staticFiles: ref<Array<{name: string, size: number, content: string, url: string}>>([]), staticFiles: ref<Array<FilesApiModel>>([]),
selectedFile: ref<{name: string, size: number, content: string, url: string}>(), /** Current selected file in file browser */
selectedFile: ref<FilesApiModel>(),
showFileUploadDialog: ref(false), showFileUploadDialog: ref(false),
@@ -24,6 +28,9 @@ export const useFilesStore = defineStore('filesStore', {
}), }),
actions: { actions: {
/**
* Fetch all static folders on the server
*/
async getStaticFolders() { async getStaticFolders() {
this.fetchInProgress = true this.fetchInProgress = true
@@ -47,6 +54,7 @@ export const useFilesStore = defineStore('filesStore', {
}) })
}, },
async uploadFile() { async uploadFile() {
this.fetchInProgress = true this.fetchInProgress = true