From ad7384c5292c3d522543120aaeee22a85107a617 Mon Sep 17 00:00:00 2001 From: Holly Date: Sat, 28 Sep 2024 01:31:46 +0100 Subject: [PATCH] add badge management --- server/controller/AdminController.ts | 50 ++ server/index.ts | 7 + server/models/admin/AdminBadgeViewModel.ts | 7 + server/models/admin/AdminBadgesViewModel.ts | 5 + server/models/admin/AdminDeleteBadgeModel.ts | 3 + server/objects/RequestCtx.ts | 3 + server/package-lock.json | 587 ++++++++++++++++++- server/package.json | 1 + server/repos/BadgeRepo.ts | 14 + server/services/BadgeService.ts | 60 ++ server/templates/account/register.ejs | 2 +- server/templates/admin/badge.ejs | 71 +++ server/templates/admin/badges.ejs | 48 ++ server/templates/admin/index.ejs | 6 +- server/templates/admin/parties.ejs | 2 +- server/templates/admin/users.ejs | 2 +- server/templates/base/footer.ejs | 17 +- server/templates/base/header.ejs | 9 + server/templates/home/home.ejs | 17 +- server/templates/home/index.ejs | 19 +- server/wwwroot/img/missing.png | Bin 0 -> 80 bytes server/wwwroot/img/preview00.png | Bin 0 -> 29892 bytes 22 files changed, 910 insertions(+), 20 deletions(-) create mode 100644 server/models/admin/AdminBadgeViewModel.ts create mode 100644 server/models/admin/AdminBadgesViewModel.ts create mode 100644 server/models/admin/AdminDeleteBadgeModel.ts create mode 100644 server/templates/admin/badge.ejs create mode 100644 server/templates/admin/badges.ejs create mode 100644 server/wwwroot/img/missing.png create mode 100644 server/wwwroot/img/preview00.png diff --git a/server/controller/AdminController.ts b/server/controller/AdminController.ts index 4e4a01c..66f33d8 100644 --- a/server/controller/AdminController.ts +++ b/server/controller/AdminController.ts @@ -1,6 +1,10 @@ +import AdminBadgesViewModel from "../models/admin/AdminBadgesViewModel"; +import AdminBadgeViewModel from "../models/admin/AdminBadgeViewModel"; +import AdminDeleteBadgeModel from "../models/admin/AdminDeleteBadgeModel"; import AdminIndexViewModel from "../models/admin/AdminIndexViewModel"; import AdminPartiesViewModel from "../models/admin/AdminPartiesViewModel"; import AdminUsersViewModel from "../models/admin/AdminUsersViewModel"; +import BadgeService from "../services/BadgeService"; import PartyService from "../services/PartyService"; import UserService from "../services/UserService"; import Controller from "./Controller"; @@ -30,4 +34,50 @@ export default class AdminController_Auth$Admin extends Controller { return this.view(adminPartiesViewModel); } + + public async Badges_Get() { + const adminBadgesViewModel: AdminBadgesViewModel = { + badges: await BadgeService.LoadAll() + }; + + return this.view(adminBadgesViewModel); + } + + public async Badge_Get(adminBadgeViewModel: AdminBadgeViewModel) { + const badge = adminBadgeViewModel.id ? await BadgeService.LoadBadge(parseInt(adminBadgeViewModel.id)) : null; + if (typeof(adminBadgeViewModel.id) !== "undefined" && badge) { + adminBadgeViewModel.name = badge.Name; + adminBadgeViewModel.description = badge.Description; + adminBadgeViewModel.imageUrl = badge.ImageUrl; + adminBadgeViewModel.forUrl = badge.ForUrl; + } else { + adminBadgeViewModel.name = ""; + adminBadgeViewModel.description = ""; + adminBadgeViewModel.imageUrl = ""; + adminBadgeViewModel.forUrl = ""; + } + + return this.view(adminBadgeViewModel); + } + + public async Badge_Post(adminBadgeViewModel: AdminBadgeViewModel) { + if (typeof(adminBadgeViewModel.id) === "undefined") { + return this.badRequest(); + } + + await BadgeService.SaveBadge(this.session.userId, parseInt(adminBadgeViewModel.id), adminBadgeViewModel.name ?? "", adminBadgeViewModel.description ?? "", adminBadgeViewModel.imageUrl ?? "", adminBadgeViewModel.forUrl ?? ""); + + return this.redirectToAction("badges"); + } + + public async DeleteBadge_Get(adminDeleteBadgeModel: AdminDeleteBadgeModel) { + if (typeof(adminDeleteBadgeModel.id) === "undefined" || typeof(adminDeleteBadgeModel.id) !== "string") { + return this.badRequest(); + } + const badgeId = parseInt(adminDeleteBadgeModel.id); + + await BadgeService.DeleteBadge(this.session.userId, badgeId); + + return this.redirectToAction("badges"); + } } \ No newline at end of file diff --git a/server/index.ts b/server/index.ts index 0165e9d..ef4fbb3 100644 --- a/server/index.ts +++ b/server/index.ts @@ -32,6 +32,7 @@ import Fastify from "fastify"; import FastifyFormBody from "@fastify/formbody"; import FastifyCookie from "@fastify/cookie"; import FastifyView from "@fastify/view"; +import FastifyStatic from "@fastify/static"; import EJS from "ejs"; import FunkyArray from "funky-array"; import RemoteUser from "./objects/RemoteUser"; @@ -47,6 +48,7 @@ import PartyController from "./controller/PartyController"; import ApiController from "./controller/ApiController"; import PartyService from "./services/PartyService"; import AdminController_Auth$Admin from "./controller/AdminController"; +import { join } from "path"; Console.customHeader(`MultiProbe server started at ${new Date()}`); @@ -70,6 +72,11 @@ fastify.register(FastifyCookie, { } }); +fastify.register(FastifyStatic, { + root: join(__dirname, "wwwroot"), + prefix: `/` +}); + fastify.setNotFoundHandler(async (req, res) => { return res.status(404).view("templates/404.ejs", { }); }); diff --git a/server/models/admin/AdminBadgeViewModel.ts b/server/models/admin/AdminBadgeViewModel.ts new file mode 100644 index 0000000..e93a081 --- /dev/null +++ b/server/models/admin/AdminBadgeViewModel.ts @@ -0,0 +1,7 @@ +export default interface AdminBadgeViewModel { + id?: string, + name?: string, + description?: string, + imageUrl?: string, + forUrl?: string +} \ No newline at end of file diff --git a/server/models/admin/AdminBadgesViewModel.ts b/server/models/admin/AdminBadgesViewModel.ts new file mode 100644 index 0000000..741c862 --- /dev/null +++ b/server/models/admin/AdminBadgesViewModel.ts @@ -0,0 +1,5 @@ +import Badge from "../../entities/Badge"; + +export default interface AdminBadgesViewModel { + badges: Array +} \ No newline at end of file diff --git a/server/models/admin/AdminDeleteBadgeModel.ts b/server/models/admin/AdminDeleteBadgeModel.ts new file mode 100644 index 0000000..5fca472 --- /dev/null +++ b/server/models/admin/AdminDeleteBadgeModel.ts @@ -0,0 +1,3 @@ +export default interface AdminDeleteBadgeModel { + id: string +} \ No newline at end of file diff --git a/server/objects/RequestCtx.ts b/server/objects/RequestCtx.ts index 51fefca..7b451d9 100644 --- a/server/objects/RequestCtx.ts +++ b/server/objects/RequestCtx.ts @@ -1,5 +1,6 @@ import { FastifyReply, FastifyRequest } from "fastify"; import SessionUser from "./SessionUser"; +import { UserLevel } from "../enums/UserLevel"; export default class RequestCtx { public controllerName:string; @@ -29,6 +30,8 @@ export default class RequestCtx { } // @ts-ignore inject session viewModel["session"] = this.session; + // @ts-ignore inject enums + viewModel["UserLevel"] = UserLevel; return this.res.view(`templates/${this.controllerName}/${viewName}.ejs`, viewModel); } diff --git a/server/package-lock.json b/server/package-lock.json index 34de87f..82034a2 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@fastify/cookie": "^10.0.1", "@fastify/formbody": "^8.0.1", + "@fastify/static": "^8.0.1", "@fastify/view": "^10.0.1", "bufferstuff": "^1.5.1", "ejs": "^3.1.10", @@ -45,6 +46,12 @@ "node": ">=12" } }, + "node_modules/@fastify/accept-negotiator": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.0.0.tgz", + "integrity": "sha512-/Sce/kBzuTxIq5tJh85nVNOq9wKD8s+viIgX0fFMDBdw95gnpf53qmF1oBgJym3cPFliWUuSloVg/1w/rH0FcQ==", + "license": "MIT" + }, "node_modules/@fastify/ajv-compiler": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.0.tgz", @@ -100,6 +107,33 @@ "fast-deep-equal": "^3.1.3" } }, + "node_modules/@fastify/send": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@fastify/send/-/send-3.1.1.tgz", + "integrity": "sha512-LdiV2mle/2tH8vh6GwGl0ubfUAgvY+9yF9oGI1iiwVyNUVOQamvw5n+OFu6iCNNoyuCY80FFURBn4TZCbTe8LA==", + "license": "MIT", + "dependencies": { + "@lukeed/ms": "^2.0.2", + "escape-html": "~1.0.3", + "fast-decode-uri-component": "^1.0.1", + "http-errors": "^2.0.0", + "mime": "^3" + } + }, + "node_modules/@fastify/static": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-8.0.1.tgz", + "integrity": "sha512-7idyhbcgf14v4bjWzUeHEFvnVxvNJ1n5cyGPgFtwTZjnjUQ1wgC7a2FQai7OGKqCKywDEjzbPhAZRW+uEK1LMg==", + "license": "MIT", + "dependencies": { + "@fastify/accept-negotiator": "^2.0.0", + "@fastify/send": "^3.1.0", + "content-disposition": "^0.5.4", + "fastify-plugin": "^5.0.0", + "fastq": "^1.17.1", + "glob": "^11.0.0" + } + }, "node_modules/@fastify/view": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/@fastify/view/-/view-10.0.1.tgz", @@ -110,6 +144,23 @@ "toad-cache": "^3.7.0" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -188,6 +239,15 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -325,6 +385,18 @@ } } }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -631,6 +703,18 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", @@ -789,6 +873,15 @@ "node": ">=0.10" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -803,6 +896,12 @@ "resolved": "https://registry.npmjs.org/dyetty/-/dyetty-1.0.1.tgz", "integrity": "sha512-MQEccirDXkAQf5U1gIwcIz46+vMMEEyAl33nCqOJ7TeCRKgcHTZdG013gmWRWw3Q9wivnJqcJ04ohZnyF8nRew==" }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -817,6 +916,12 @@ "node": ">=0.10.0" } }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -950,6 +1055,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -1143,6 +1254,81 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/foreground-child/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1251,6 +1437,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -1263,6 +1472,30 @@ "node": ">= 6" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globalthis": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", @@ -1391,6 +1624,22 @@ "dyetty": "^1.0.1" } }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -1428,6 +1677,12 @@ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -1575,6 +1830,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -1726,8 +1990,22 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/jackspeak": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", + "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/jake": { "version": "10.8.7", @@ -1903,6 +2181,18 @@ "node": ">= 0.10.0" } }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1914,6 +2204,15 @@ "node": "*" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2109,6 +2408,12 @@ "node": ">=14.0.0" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, "node_modules/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -2137,6 +2442,31 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", + "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", @@ -2528,6 +2858,12 @@ "node": ">= 0.4" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -2576,6 +2912,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/simple-prom": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-prom/-/simple-prom-1.0.1.tgz", @@ -2674,6 +3022,15 @@ "node": ">= 0.6" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -2683,6 +3040,65 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.padend": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", @@ -2750,6 +3166,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -2832,6 +3285,15 @@ "node": ">=12" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", @@ -3065,6 +3527,127 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", diff --git a/server/package.json b/server/package.json index cb43dad..23a7c81 100644 --- a/server/package.json +++ b/server/package.json @@ -17,6 +17,7 @@ "dependencies": { "@fastify/cookie": "^10.0.1", "@fastify/formbody": "^8.0.1", + "@fastify/static": "^8.0.1", "@fastify/view": "^10.0.1", "bufferstuff": "^1.5.1", "ejs": "^3.1.10", diff --git a/server/repos/BadgeRepo.ts b/server/repos/BadgeRepo.ts index b56da6d..5124a42 100644 --- a/server/repos/BadgeRepo.ts +++ b/server/repos/BadgeRepo.ts @@ -26,6 +26,20 @@ export default abstract class BadgeRepo { return badge; } } + + public static async insertUpdate(badge:Badge) { + if (badge.Id === Number.MIN_VALUE) { + badge.Id = (await Database.Instance.query("INSERT Badge (Name, Description, ImageUrl, ForUrl, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [ + badge.Name, badge.Description, badge.ImageUrl, badge.ForUrl, badge.CreatedByUserId, badge.CreatedDatetime.getTime(), badge.LastModifiedByUserId ?? null, badge.LastModifiedDatetime?.getTime() ?? null, badge.DeletedByUserId ?? null, badge.DeletedDatetime?.getTime() ?? null, Number(badge.IsDeleted) + ]))[0]["Id"]; + } else { + await Database.Instance.query(`UPDATE Badge SET Name = ?, Description = ?, ImageUrl = ?, ForUrl = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?`, [ + badge.Name, badge.Description, badge.ImageUrl, badge.ForUrl, badge.CreatedByUserId, badge.CreatedDatetime.getTime(), badge.LastModifiedByUserId ?? null, badge.LastModifiedDatetime?.getTime() ?? null, badge.DeletedByUserId ?? null, badge.DeletedDatetime?.getTime() ?? null, Number(badge.IsDeleted), badge.Id + ]); + } + + return badge; + } } function populateBadgeFromDB(badge:Badge, dbBadge:any) { diff --git a/server/services/BadgeService.ts b/server/services/BadgeService.ts index e69de29..b2cc5cc 100644 --- a/server/services/BadgeService.ts +++ b/server/services/BadgeService.ts @@ -0,0 +1,60 @@ +import { Console } from "hsconsole"; +import Badge from "../entities/Badge"; +import BadgeRepo from "../repos/BadgeRepo"; + +export default abstract class BadgeService { + public static async SaveBadge(currentUserId:number, id:number | undefined, name:string, description:string, imageUrl: string, forUrl:string) { + try { + let badge = id ? await BadgeRepo.selectById(id) : null; + if (badge === null) { + badge = new Badge(); + badge.CreatedByUserId = currentUserId; + badge.CreatedDatetime = new Date(); + } else { + badge.LastModifiedByUserId = currentUserId; + badge.LastModifiedDatetime = new Date(); + } + + badge.Name = name; + badge.Description = description; + badge.ImageUrl = imageUrl; + badge.ForUrl = forUrl; + + return await BadgeRepo.insertUpdate(badge); + } catch (e) { + Console.printError(`MultiProbe server service error:\n${e}`); + throw e; + } + } + + public static async LoadBadge(id:number) { + try { + return await BadgeRepo.selectById(id); + } catch (e) { + Console.printError(`MultiProbe server service error:\n${e}`); + throw e; + } + } + + public static async LoadAll() { + try { + return await BadgeRepo.selectAll(); + } catch (e) { + Console.printError(`MultiProbe server service error:\n${e}`); + throw e; + } + } + + public static async DeleteBadge(currentUserId:number, id: number) { + const badge = await BadgeRepo.selectById(id); + if (badge == null) { + return null; + } + + badge.DeletedByUserId = currentUserId; + badge.DeletedDatetime = new Date(); + badge.IsDeleted = true; + + return await BadgeRepo.insertUpdate(badge); + } +} \ No newline at end of file diff --git a/server/templates/account/register.ejs b/server/templates/account/register.ejs index 37f113f..1127232 100644 --- a/server/templates/account/register.ejs +++ b/server/templates/account/register.ejs @@ -13,7 +13,7 @@
diff --git a/server/templates/admin/badge.ejs b/server/templates/admin/badge.ejs new file mode 100644 index 0000000..9dfd17d --- /dev/null +++ b/server/templates/admin/badge.ejs @@ -0,0 +1,71 @@ +<%- include("../base/header", { title: typeof(id) === "undefined" ? "Add Badge" : `Edit ${name}`, userId: session.userId, isAdmin: true }) %> + + + +
+
+

<%= typeof(id) === "undefined" ? "Add Badge" : `Edit ${name}` %>

+
+
+ +
+ " /> + +
+
+ + " required maxlength="255" /> +
+
+
+
+ + +
+
+
+
+ + " /> +
+
+ " onerror="if (this.src != '/img/missing.png') this.src = '/img/missing.png';" width="32" height="32" /> +
+
+
+
+ + " required /> +
+
+
+
+ + Cancel +
+
+
+ + + +<%- include("../base/footer") %> \ No newline at end of file diff --git a/server/templates/admin/badges.ejs b/server/templates/admin/badges.ejs new file mode 100644 index 0000000..e5fc116 --- /dev/null +++ b/server/templates/admin/badges.ejs @@ -0,0 +1,48 @@ +<%- include("../base/header", { title: "Badge Management", userId: session.userId, isAdmin: true }) %> + +
+
+ +
+
+ +
+
+

Badge Management

+
+
+ Add Badge +
+
+ +
+
+ + + + + + + + + <% for (const badge of badges) { %> + + + + + + + <% } %> + +
IconNameFor URL 
" width="32" height="32" /><%= badge.Name %><%= badge.ForUrl %> + Edit + Delete +
+
+
+<%- include("../base/footer") %> \ No newline at end of file diff --git a/server/templates/admin/index.ejs b/server/templates/admin/index.ejs index daa5fb0..b32b257 100644 --- a/server/templates/admin/index.ejs +++ b/server/templates/admin/index.ejs @@ -45,9 +45,9 @@ <%- include("../base/footer") %> \ No newline at end of file diff --git a/server/templates/admin/parties.ejs b/server/templates/admin/parties.ejs index 1f1ffe3..0f0ac07 100644 --- a/server/templates/admin/parties.ejs +++ b/server/templates/admin/parties.ejs @@ -32,7 +32,7 @@ <%= party.Id %> <%= party.PartyRef %> <%= party.Name %> - + Edit Delete diff --git a/server/templates/admin/users.ejs b/server/templates/admin/users.ejs index eb841cc..baaf1e5 100644 --- a/server/templates/admin/users.ejs +++ b/server/templates/admin/users.ejs @@ -32,7 +32,7 @@ <%= user.Id %> <%= user.Username %> <%= user.UserLevelString %> - + Edit Delete diff --git a/server/templates/base/footer.ejs b/server/templates/base/footer.ejs index b61939d..110267f 100644 --- a/server/templates/base/footer.ejs +++ b/server/templates/base/footer.ejs @@ -1,7 +1,22 @@
- + diff --git a/server/templates/home/home.ejs b/server/templates/home/home.ejs index f185a14..ccd4614 100644 --- a/server/templates/home/home.ejs +++ b/server/templates/home/home.ejs @@ -6,12 +6,17 @@
Change Username - Change Password + Change Password
+ <% if (user.UserLevel === UserLevel.Admin) { %> + + <% } %>
@@ -29,17 +34,17 @@ <% for (const party of parties) { %> <%= party.Name %> - <%= party.PartyRef %> - + <%= party.PartyRef %> + <% if (activeUserParty && activeUserParty.PartyId === party.Id) { %> Deactivate <% } else { %> - Set Current +  Activate  <% } %> <% if (party.CreatedByUserId === user.Id) { %> Delete <% } else { %> - Leave + Leave <% } %> diff --git a/server/templates/home/index.ejs b/server/templates/home/index.ejs index bb5e236..6dbf443 100644 --- a/server/templates/home/index.ejs +++ b/server/templates/home/index.ejs @@ -1,8 +1,17 @@ <%- include("../base/header", { title: "Home" }) %> -

MultiProbe

-

A way to explore Terminal 00 with friends.

-
- Register - Login +
+
+

MultiProbe

+

A way to explore Terminal 00 with friends.

+
+ Register + Login +
+
+
+
+
+ +
<%- include("../base/footer") %> \ No newline at end of file diff --git a/server/wwwroot/img/missing.png b/server/wwwroot/img/missing.png new file mode 100644 index 0000000000000000000000000000000000000000..07442e5cf27119e1fbde0c82ebed3c540858268c GIT binary patch literal 80 zcmeAS@N?(olHy`uVBq!ia0vp^Od!m`1|*BN@u~nRaZeY=5RU7t2N@ZGye5TT_O~3k bcvab*Zm|8H<(U)%6lCyp^>bP0l+XkKD^C#* literal 0 HcmV?d00001 diff --git a/server/wwwroot/img/preview00.png b/server/wwwroot/img/preview00.png new file mode 100644 index 0000000000000000000000000000000000000000..f224b056ccfbd4cc3760cccb35f1a1442000252f GIT binary patch literal 29892 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 e7V`2C^q*z;&-1@yRyFy*i?q1Hj~Y>