WIP: Overrides

This commit is contained in:
Holly Stubbs 2025-04-22 09:38:38 +01:00
parent d26b4025ad
commit c1b59f77d2
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
19 changed files with 628 additions and 90 deletions

View file

@ -20,6 +20,10 @@ import UserService from "../services/UserService";
import Controller from "./Controller";
import UserBadgeListItem from "../entities/list/UserBadgeListItem";
import AdminRemoveUserBadgeModel from "../models/admin/AdminRemoveUserBadgeModel";
import OverrideService from "../services/OverrideService";
import AdminOverridesViewModel from "../models/admin/AdminOverridesViewModel";
import AdminOverrideViewModel from "../models/admin/AdminOverrideViewModel";
import OverrideType from "../enums/OverrideType";
export default class AdminController_Auth$Admin extends Controller {
public async Index_Get() {
@ -215,4 +219,39 @@ export default class AdminController_Auth$Admin extends Controller {
return this.redirectToAction("userbadges");
}
public async Overrides_Get() {
const adminOverridesViewModel: AdminOverridesViewModel = {
overrides: await OverrideService.GetOverrides()
}
return this.view(adminOverridesViewModel);
}
public async Override_Get(adminOverrideViewModel: AdminOverrideViewModel) {
const override = adminOverrideViewModel.id ? await OverrideService.GetOverride(parseInt(adminOverrideViewModel.id)) : null;
if (typeof(adminOverrideViewModel.id) !== "undefined" && override) {
adminOverrideViewModel.overrideTypeId = override.OverrideType.toString();
adminOverrideViewModel.name = override.Name;
adminOverrideViewModel.forUrl = override.ForURL;
adminOverrideViewModel.cssQuery = override.CSSQuery;
} else {
adminOverrideViewModel.overrideTypeId = OverrideType.Unknown.toString();
adminOverrideViewModel.name = "";
adminOverrideViewModel.forUrl = "";
adminOverrideViewModel.cssQuery = "";
}
return this.view(adminOverrideViewModel);
}
public async Override_Post(adminOverrideViewModel: AdminOverrideViewModel) {
if (typeof(adminOverrideViewModel.id) === "undefined") {
return this.badRequest();
}
await OverrideService.SaveOverride(this.session.userId, parseInt(adminOverrideViewModel.id), parseInt(adminOverrideViewModel.overrideTypeId), adminOverrideViewModel.name, adminOverrideViewModel.forUrl, adminOverrideViewModel.cssQuery);
return this.redirectToAction("badges");
}
}

View file

@ -63,4 +63,8 @@ export default class ApiController extends Controller {
});
}
}
public async GetOverrides_Get_AllowAnonymous() {
return this.ok("test");
}
}

View file

@ -0,0 +1,16 @@
import OverrideType from "../enums/OverrideType";
export default class Override {
public Id: number = Number.MIN_VALUE;
public OverrideType: OverrideType = OverrideType.Unknown;
public Name: string = "";
public ForURL: string = "";
public CSSQuery: string = "";
public CreatedByUserId: number = Number.MIN_VALUE;
public CreatedDatetime: Date = new Date();
public LastModifiedByUserId?: number;
public LastModifiedDatetime?: Date;
public DeletedByUserId?: number;
public DeletedDatetime?: Date;
public IsDeleted: boolean = false;
}

View file

@ -0,0 +1,15 @@
import OverrideType from "../enums/OverrideType";
export default class OverrideField {
public Id: number = Number.MIN_VALUE;
public OverrideId: number = Number.MIN_VALUE;
public FieldName: string = "";
public FieldValue: string = "";
public CreatedByUserId: number = Number.MIN_VALUE;
public CreatedDatetime: Date = new Date();
public LastModifiedByUserId?: number;
public LastModifiedDatetime?: Date;
public DeletedByUserId?: number;
public DeletedDatetime?: Date;
public IsDeleted: boolean = false;
}

View file

@ -0,0 +1,31 @@
enum OverrideType {
Unknown = 0,
AddHtml = 10,
AddLink = 20,
ReplaceText = 30,
ReplaceImage = 40,
AddStyles = 50,
ReplaceStyles = 60
}
// @ts-ignore
OverrideType.ToDescription = (overrideType: OverrideType) => {
switch (overrideType) {
case OverrideType.Unknown:
return "Please Select...";
case OverrideType.AddHtml:
return "Add HTML";
case OverrideType.AddLink:
return "Add Link";
case OverrideType.ReplaceText:
return "Replace Text";
case OverrideType.ReplaceImage:
return "Replace Image";
case OverrideType.AddStyles:
return "Add Styles";
case OverrideType.ReplaceStyles:
return "Replace Styles";
}
};
export default OverrideType;

View file

@ -0,0 +1,9 @@
import OverrideType from "../../enums/OverrideType";
export default interface AdminOverrideViewModel {
id?: string;
overrideTypeId: string;
name: string;
forUrl: string;
cssQuery: string;
}

View file

@ -0,0 +1,5 @@
import Override from "../../entities/Override";
export default interface AdminOverridesViewModel {
overrides: Override[]
}

View file

@ -1,7 +1,7 @@
import { Console } from "hsconsole";
import { createPool, Pool, RowDataPacket } from "mysql2";
export type DBInDataType = string | number | null | undefined;
export type DBInDataType = string | number | Date | null | undefined;
export default class Database {
private connectionPool:Pool;

View file

@ -1,6 +1,8 @@
import { FastifyReply, FastifyRequest } from "fastify";
import SessionUser from "./SessionUser";
import { UserLevel } from "../enums/UserLevel";
import SelectUtility from "../utilities/SelectUtility";
import OverrideType from "../enums/OverrideType";
export default class RequestCtx {
public controllerName:string;
@ -30,8 +32,14 @@ export default class RequestCtx {
}
// @ts-ignore inject session
viewModel["session"] = this.session;
// @ts-ignore inject enums
viewModel["UserLevel"] = UserLevel;
// @ts-ignore
viewModel["OverrideType"] = OverrideType;
// @ts-ignore inject methods
viewModel["SelectUtility"] = SelectUtility;
return this.res.view(`views/${this.controllerName}/${viewName}.ejs`, viewModel);
}

221
server/package-lock.json generated
View file

@ -9,29 +9,29 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@fastify/cookie": "^11.0.1",
"@fastify/formbody": "^8.0.1",
"@fastify/static": "^8.0.3",
"@fastify/view": "^10.0.1",
"@fastify/cookie": "^11.0.2",
"@fastify/formbody": "^8.0.2",
"@fastify/static": "^8.1.1",
"@fastify/view": "^11.0.0",
"bufferstuff": "^1.8.0",
"ejs": "^3.1.10",
"fastify": "^5.2.0",
"hsconsole": "^1.0.2",
"mysql2": "^3.11.5",
"fastify": "^5.3.2",
"hsconsole": "^1.1.0",
"mysql2": "^3.14.0",
"simple-prom": "^1.0.1",
"ultimate-ws": "^1.0.13"
},
"devDependencies": {
"@types/ejs": "^3.1.5",
"@types/node": "^22.10.2",
"@types/node": "^22.14.0",
"@vercel/ncc": "^0.38.3",
"check-outdated": "^2.12.0",
"check-outdated": "^2.13.0",
"node": "^22.12.0",
"nodemon": "^3.1.9",
"npm-run-all": "^4.1.5",
"terser": "^5.37.0",
"terser": "^5.39.0",
"ts-node": "^10.9.2",
"typescript": "^5.7.2"
"typescript": "^5.8.3"
}
},
"node_modules/@cspotcode/source-map-support": {
@ -64,9 +64,19 @@
}
},
"node_modules/@fastify/cookie": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/@fastify/cookie/-/cookie-11.0.1.tgz",
"integrity": "sha512-n1Ooz4bgQ5LcOlJQboWPfsMNxIrGV0SgU85UkctdpTlCQE0mtA3rlspOPUdqk9ubiiZn053ucnia4DjTquI4/g==",
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/@fastify/cookie/-/cookie-11.0.2.tgz",
"integrity": "sha512-GWdwdGlgJxyvNv+QcKiGNevSspMQXncjMZ1J8IvuDQk0jvkzgWWZFNC2En3s+nHndZBGV8IbLwOI/sxCZw/mzA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "MIT",
"dependencies": {
"cookie": "^1.0.0",
@ -98,15 +108,31 @@
}
},
"node_modules/@fastify/formbody": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/@fastify/formbody/-/formbody-8.0.1.tgz",
"integrity": "sha512-LPrcadSIK8TrQk510Zdj56fnw7cyHq0/PW0YHGGM8ycGL4X7XAex+FKcwpzB4i5lF9eykc71a4EtcO9AEoByqw==",
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@fastify/formbody/-/formbody-8.0.2.tgz",
"integrity": "sha512-84v5J2KrkXzjgBpYnaNRPqwgMsmY7ZDjuj0YVuMR3NXCJRCgKEZy/taSP1wUYGn0onfxJpLyRGDLa+NMaDJtnA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "MIT",
"dependencies": {
"fast-querystring": "^1.1.2",
"fastify-plugin": "^5.0.0"
}
},
"node_modules/@fastify/forwarded": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.0.tgz",
"integrity": "sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==",
"license": "MIT"
},
"node_modules/@fastify/merge-json-schemas": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz",
@ -116,6 +142,16 @@
"fast-deep-equal": "^3.1.3"
}
},
"node_modules/@fastify/proxy-addr": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.0.0.tgz",
"integrity": "sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA==",
"license": "MIT",
"dependencies": {
"@fastify/forwarded": "^3.0.0",
"ipaddr.js": "^2.1.0"
}
},
"node_modules/@fastify/send": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@fastify/send/-/send-3.3.0.tgz",
@ -130,9 +166,19 @@
}
},
"node_modules/@fastify/static": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/@fastify/static/-/static-8.0.3.tgz",
"integrity": "sha512-GHSoOVDIxEYEeVR5l044bRCuAKDErD/+9VE+Z9fnaTRr+DDz0Avrm4kKai1mHbPx6C0U7BVNthjd/gcMquZZUA==",
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@fastify/static/-/static-8.1.1.tgz",
"integrity": "sha512-TW9eyVHJLytZNpBlSIqd0bl1giJkEaRaPZG+5AT3L/OBKq9U8D7g/OYmc2NPQZnzPURGhMt3IAWuyVkvd2nOkQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "MIT",
"dependencies": {
"@fastify/accept-negotiator": "^2.0.0",
@ -144,9 +190,19 @@
}
},
"node_modules/@fastify/view": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/@fastify/view/-/view-10.0.1.tgz",
"integrity": "sha512-rXtBN0oVDmoRZAS7lelrCIahf+qFtlMOOas8VPdA7JvrJ9ChcF7e36pIUPU0Vbs3KmHxESUb7XatavUZEe/k5Q==",
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/@fastify/view/-/view-11.0.0.tgz",
"integrity": "sha512-uMNwZoY88uUMP6hidJYHT0/7bXzLrWgo/X5kjKdKPi3bj69kSkuNKmcG5KEU2sDF91n+IiLAnqx3dkldFfz5hQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "MIT",
"dependencies": {
"fastify-plugin": "^5.0.0",
@ -288,13 +344,13 @@
"dev": true
},
"node_modules/@types/node": {
"version": "22.10.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz",
"integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==",
"version": "22.14.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz",
"integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
"undici-types": "~6.21.0"
}
},
"node_modules/@vercel/ncc": {
@ -642,10 +698,11 @@
}
},
"node_modules/check-outdated": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/check-outdated/-/check-outdated-2.12.0.tgz",
"integrity": "sha512-kWThJFiqxAE09XSNJLLD4hWNvLhWdxFLKxOHhxB+XhGlZGyeELXP8V6R/dRrZ5vbjmp9VmoTYe0vp6egftKz7Q==",
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/check-outdated/-/check-outdated-2.13.0.tgz",
"integrity": "sha512-TXmXp/IcnV2WnF8B+LBuFLXy5zNAlv46tdN3O/2ag3Hn+CZrUzIGI/hN0EdXMq9liiD8aWJDI5/ZBqYrqgxbSw==",
"dev": true,
"license": "MIT",
"bin": {
"check-outdated": "check-outdated.js"
},
@ -1136,9 +1193,9 @@
"license": "MIT"
},
"node_modules/fastify": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/fastify/-/fastify-5.2.0.tgz",
"integrity": "sha512-3s+Qt5S14Eq5dCpnE0FxTp3z4xKChI83ZnMv+k0FwX+VUoZrgCFoLAxpfdi/vT4y6Mk+g7aAMt9pgXDoZmkefQ==",
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/fastify/-/fastify-5.3.2.tgz",
"integrity": "sha512-AIPqBgtqBAwkOkrnwesEE+dOyU30dQ4kh7udxeGVR05CRGwubZx+p2H8P0C4cRnQT0+EPK4VGea2DTL2RtWttg==",
"funding": [
{
"type": "github",
@ -1154,16 +1211,16 @@
"@fastify/ajv-compiler": "^4.0.0",
"@fastify/error": "^4.0.0",
"@fastify/fast-json-stringify-compiler": "^5.0.0",
"@fastify/proxy-addr": "^5.0.0",
"abstract-logging": "^2.0.1",
"avvio": "^9.0.0",
"fast-json-stringify": "^6.0.0",
"find-my-way": "^9.0.0",
"light-my-request": "^6.0.0",
"pino": "^9.0.0",
"process-warning": "^4.0.0",
"proxy-addr": "^2.0.7",
"process-warning": "^5.0.0",
"rfdc": "^1.3.1",
"secure-json-parse": "^3.0.1",
"secure-json-parse": "^4.0.0",
"semver": "^7.6.0",
"toad-cache": "^3.7.0"
}
@ -1174,6 +1231,22 @@
"integrity": "sha512-0725fmH/yYi8ugsjszLci+lLnGBK6cG+WSxM7edY2OXJEU7gr2JiGBoieL2h9mhTych1vFsEfXsAsGGDJ/Rd5w==",
"license": "MIT"
},
"node_modules/fastify/node_modules/process-warning": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz",
"integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "MIT"
},
"node_modules/fastq": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
@ -1321,14 +1394,6 @@
"node": ">= 8"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@ -1609,9 +1674,10 @@
"dev": true
},
"node_modules/hsconsole": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/hsconsole/-/hsconsole-1.0.2.tgz",
"integrity": "sha512-st+jaSpNw3uoIhE5vl2lVN8Op8yQF2FyLRdBG68s8vqjduJdKUGtoEXd8Zxe6du1zzpFHHRcU3zJbAq8BOmYQA==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/hsconsole/-/hsconsole-1.1.0.tgz",
"integrity": "sha512-nQtnapTLf/d090AloKJkVbf15yXNaISYYHC21cwOLClF7hlJs2rlHF3JLaltspK/O2uhz6WcRMsE+2Yn6D8UEw==",
"license": "MIT",
"dependencies": {
"dyetty": "^1.0.1"
}
@ -1690,11 +1756,12 @@
}
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
"integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
"node": ">= 10"
}
},
"node_modules/is-array-buffer": {
@ -2212,9 +2279,9 @@
"dev": true
},
"node_modules/mysql2": {
"version": "3.11.5",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.11.5.tgz",
"integrity": "sha512-0XFu8rUmFN9vC0ME36iBvCUObftiMHItrYFhlCRvFWbLgpNqtC4Br/NmZX1HNCszxT0GGy5QtP+k3Q3eCJPaYA==",
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.0.tgz",
"integrity": "sha512-8eMhmG6gt/hRkU1G+8KlGOdQi2w+CgtNoD1ksXZq9gQfkfDsX4LHaBwTe1SY0Imx//t2iZA03DFnyYKPinxSRw==",
"license": "MIT",
"dependencies": {
"aws-ssl-profiles": "^1.1.1",
@ -2590,18 +2657,6 @@
"integrity": "sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==",
"license": "MIT"
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/pstree.remy": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
@ -2813,9 +2868,19 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/secure-json-parse": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-3.0.1.tgz",
"integrity": "sha512-9QR7G96th4QJ2+dJwvZB+JoXyt8PN+DbEjOr6kL2/JU4KH8Eb2sFdU+gt8EDdzWDWoWH0uocDdfCoFzdVSixUA==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.0.0.tgz",
"integrity": "sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "BSD-3-Clause"
},
"node_modules/semver": {
@ -3254,9 +3319,9 @@
}
},
"node_modules/terser": {
"version": "5.37.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz",
"integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==",
"version": "5.39.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
"integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@ -3446,9 +3511,9 @@
}
},
"node_modules/typescript": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
"integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@ -3492,9 +3557,9 @@
"dev": true
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
},

View file

@ -12,28 +12,28 @@
"author": "tgpholly",
"license": "MIT",
"dependencies": {
"@fastify/cookie": "^11.0.1",
"@fastify/formbody": "^8.0.1",
"@fastify/static": "^8.0.3",
"@fastify/view": "^10.0.1",
"@fastify/cookie": "^11.0.2",
"@fastify/formbody": "^8.0.2",
"@fastify/static": "^8.1.1",
"@fastify/view": "^11.0.0",
"bufferstuff": "^1.8.0",
"ejs": "^3.1.10",
"fastify": "^5.2.0",
"hsconsole": "^1.0.2",
"mysql2": "^3.11.5",
"fastify": "^5.3.2",
"hsconsole": "^1.1.0",
"mysql2": "^3.14.0",
"simple-prom": "^1.0.1",
"ultimate-ws": "^1.0.13"
},
"devDependencies": {
"@types/ejs": "^3.1.5",
"@types/node": "^22.10.2",
"@types/node": "^22.14.0",
"@vercel/ncc": "^0.38.3",
"check-outdated": "^2.12.0",
"check-outdated": "^2.13.0",
"node": "^22.12.0",
"nodemon": "^3.1.9",
"npm-run-all": "^4.1.5",
"terser": "^5.37.0",
"terser": "^5.39.0",
"ts-node": "^10.9.2",
"typescript": "^5.7.2"
"typescript": "^5.8.3"
}
}

View file

@ -0,0 +1,69 @@
import Database from "../objects/Database";
import OverrideField from "../entities/OverrideField";
export default class OverrideFieldRepo {
public static async selectAll() {
const dbOverride = await Database.Instance.query("SELECT * FROM OverrideField WHERE IsDeleted = 0");
const users = new Array<OverrideField>();
for (const row of dbOverride) {
const override = new OverrideField();
populateOverrideFromDB(override, row);
users.push(override);
}
return users;
}
public static async selectById(id:number) {
const dbOverride = await Database.Instance.query("SELECT * FROM OverrideField WHERE Id = ? LIMIT 1", [id]);
if (dbOverride == null || dbOverride.length === 0) {
return null;
} else {
const override = new OverrideField();
populateOverrideFromDB(override, dbOverride[0]);
return override;
}
}
public static async selectByOverrideId(id: number) {
const dbOverride = await Database.Instance.query("SELECT * FROM OverrideField WHERE IsDeleted = 0 AND OverrideId = ?", [id]);
const users = new Array<OverrideField>();
for (const row of dbOverride) {
const override = new OverrideField();
populateOverrideFromDB(override, row);
users.push(override);
}
return users;
}
public static async insertUpdate(overrideField:OverrideField) {
if (overrideField.Id === Number.MIN_VALUE) {
overrideField.Id = (await Database.Instance.query("INSERT OverrideField (OverrideId, FieldName, FieldValue, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [
overrideField.OverrideId, overrideField.FieldName, overrideField.FieldValue, overrideField.CreatedByUserId, overrideField.CreatedDatetime.getTime(), overrideField.LastModifiedByUserId ?? null, overrideField.LastModifiedDatetime ?? null, overrideField.DeletedByUserId ?? null, overrideField.DeletedDatetime ?? null, Number(overrideField.IsDeleted)
]))[0]["Id"];
} else {
await Database.Instance.query(`UPDATE OverrideField SET OverrideId = ?, FieldName = ?, FieldValue = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?`, [
overrideField.OverrideId, overrideField.FieldName, overrideField.FieldValue, overrideField.CreatedByUserId, overrideField.CreatedDatetime.getTime(), overrideField.LastModifiedByUserId ?? null, overrideField.LastModifiedDatetime ?? null, overrideField.DeletedByUserId ?? null, overrideField.DeletedDatetime ?? null, Number(overrideField.IsDeleted), overrideField.Id
]);
}
return overrideField;
}
}
function populateOverrideFromDB(overrideField:OverrideField, dbOverrideField:any) {
overrideField.Id = dbOverrideField.Id;
overrideField.OverrideId = dbOverrideField.OverrideId;
overrideField.FieldName = dbOverrideField.FieldName;
overrideField.FieldValue = dbOverrideField.FieldValue;
overrideField.CreatedByUserId = dbOverrideField.CreatedByUserId;
overrideField.CreatedDatetime = dbOverrideField.CreatedDatetime;
overrideField.LastModifiedByUserId = dbOverrideField.LastModifiedByUserId;
overrideField.LastModifiedDatetime = dbOverrideField.LastModifiedDatetime;
overrideField.DeletedByUserId = dbOverrideField.DeletedByUserId;
overrideField.DeletedDatetime = dbOverrideField.DeletedDatetime;
overrideField.IsDeleted = dbOverrideField.IsDeleted[0] === 1;
}

View file

@ -0,0 +1,57 @@
import Database from "../objects/Database";
import Override from "../entities/Override";
export default class OverrideRepo {
public static async selectAll() {
const dbOverride = await Database.Instance.query("SELECT * FROM Override WHERE IsDeleted = 0");
const users = new Array<Override>();
for (const row of dbOverride) {
const override = new Override();
populateOverrideFromDB(override, row);
users.push(override);
}
return users;
}
public static async selectById(id:number) {
const dbOverride = await Database.Instance.query("SELECT * FROM Override WHERE Id = ? LIMIT 1", [id]);
if (dbOverride == null || dbOverride.length === 0) {
return null;
} else {
const override = new Override();
populateOverrideFromDB(override, dbOverride[0]);
return override;
}
}
public static async insertUpdate(override:Override) {
if (override.Id === Number.MIN_VALUE) {
override.Id = (await Database.Instance.query("INSERT Override (OverrideTypeId, Name, ForURL, CSSQuery, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [
override.OverrideType, override.Name, override.ForURL, override.CSSQuery, override.CreatedByUserId, override.CreatedDatetime.getTime(), override.LastModifiedByUserId ?? null, override.LastModifiedDatetime ?? null, override.DeletedByUserId ?? null, override.DeletedDatetime ?? null, Number(override.IsDeleted)
]))[0]["Id"];
} else {
await Database.Instance.query(`UPDATE Override SET OverrideTypeId = ?, Name = ?, ForURL = ?, CSSQuery = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?`, [
override.OverrideType, override.Name, override.ForURL, override.CSSQuery, override.CreatedByUserId, override.CreatedDatetime.getTime(), override.LastModifiedByUserId ?? null, override.LastModifiedDatetime ?? null, override.DeletedByUserId ?? null, override.DeletedDatetime ?? null, Number(override.IsDeleted), override.Id
]);
}
return override;
}
}
function populateOverrideFromDB(override:Override, dbOverride:any) {
override.Id = dbOverride.Id;
override.OverrideType = dbOverride.OverrideTypeId;
override.Name = dbOverride.Name;
override.ForURL = dbOverride.ForURL;
override.CSSQuery = dbOverride.CSSQuery;
override.CreatedByUserId = dbOverride.CreatedByUserId;
override.CreatedDatetime = dbOverride.CreatedDatetime;
override.LastModifiedByUserId = dbOverride.LastModifiedByUserId;
override.LastModifiedDatetime = dbOverride.LastModifiedDatetime;
override.DeletedByUserId = dbOverride.DeletedByUserId;
override.DeletedDatetime = dbOverride.DeletedDatetime;
override.IsDeleted = dbOverride.IsDeleted[0] === 1;
}

View file

@ -0,0 +1,95 @@
import { Console } from "hsconsole";
import OverrideRepo from "../repos/OverrideRepo";
import Override from "../entities/Override";
import OverrideType from "../enums/OverrideType";
import OverrideFieldRepo from "../repos/OverrideFieldRepo";
import OverrideField from "../entities/OverrideField";
export default abstract class OverrideService {
public static async GetOverride(id: number) {
try {
return await OverrideRepo.selectById(id);
} catch (e) {
Console.printError(`MultiProbe server service error:\n${e}`);
throw e;
}
}
public static async GetOverrides() {
try {
return await OverrideRepo.selectAll();
} catch (e) {
Console.printError(`MultiProbe server service error:\n${e}`);
throw e;
}
}
public static async SaveOverride(currentUserId:number, id:number | undefined, overrideType: OverrideType, name: string, forUrl: string, cssQuery: string) {
try {
let override = id ? await OverrideRepo.selectById(id) : null;
if (override === null) {
override = new Override();
override.CreatedByUserId = currentUserId;
override.CreatedDatetime = new Date();
} else {
override.LastModifiedByUserId = currentUserId;
override.LastModifiedDatetime = new Date();
}
override.OverrideType = overrideType;
override.Name = name;
override.ForURL = forUrl;
override.CSSQuery = cssQuery;
override = await OverrideRepo.insertUpdate(override);
return override;
} catch (e) {
Console.printError(`MultiProbe server service error:\n${e}`);
throw e;
}
}
public static async GetOverrideField(id: number) {
try {
return await OverrideFieldRepo.selectById(id);
} catch (e) {
Console.printError(`MultiProbe server service error:\n${e}`);
throw e;
}
}
public static async GetOverrideFields(overrideId: number) {
try {
return await OverrideFieldRepo.selectById(overrideId);
} catch (e) {
Console.printError(`MultiProbe server service error:\n${e}`);
throw e;
}
}
public static async SaveOverrideField(currentUserId:number, id:number | undefined, overrideId: number, fieldName: string, fieldValue: string) {
try {
let overrideField = id ? await OverrideFieldRepo.selectById(id) : null;
if (overrideField === null) {
overrideField = new OverrideField();
overrideField.CreatedByUserId = currentUserId;
overrideField.CreatedDatetime = new Date();
} else {
overrideField.LastModifiedByUserId = currentUserId;
overrideField.LastModifiedDatetime = new Date();
}
overrideField.OverrideId = overrideId;
overrideField.FieldName = fieldName;
overrideField.FieldValue = fieldValue;
overrideField = await OverrideFieldRepo.insertUpdate(overrideField);
return overrideField;
} catch (e) {
Console.printError(`MultiProbe server service error:\n${e}`);
throw e;
}
}
}

View file

@ -0,0 +1,17 @@
import OverrideType from "../enums/OverrideType";
export default abstract class SelectUtility {
public static EnumToSelectList(enumToList: any, selected?: any) {
const keys = Object.keys(enumToList);
let output: string = "";
for (const key of keys) {
const keyN = parseInt(key);
if (!isNaN(keyN)) {
// @ts-ignore
output += `<option value="${keyN}"${selected && selected === keyN ? " selected" : ""}>${OverrideType.ToDescription(keyN)}</option>`;
}
}
return output;
}
}

View file

@ -0,0 +1,3 @@
<div id="overrideFields">
</div>

View file

@ -49,6 +49,7 @@
<a class="btn btn-primary btn-lg me-2 mb-3" href="/admin/parties">Manage Parties</a>
<a class="btn btn-primary btn-lg me-2 mb-3" href="/admin/badges">Manage Badges</a>
<a class="btn btn-primary btn-lg me-2 mb-3" href="/admin/userbadges">Manage Unlocked Badges</a>
<a class="btn btn-primary btn-lg me-2 mb-3" href="/admin/overrides">Manage Overrides</a>
<a class="btn btn-primary btn-lg me-2 mb-3" href="/admin/websessions">Web Sessions</a>
<a class="btn btn-primary btn-lg me-2 mb-3" href="/admin/wssessions">Websocket Sessions</a>
</div>

View file

@ -0,0 +1,59 @@
<%- include("../base/header", { title: typeof(id) === "undefined" ? "Add Override" : `Edit ${name}`, userId: session.userId, isAdmin: true }) %>
<div class="row">
<div class="col">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/admin">Admin</a></li>
<li class="breadcrumb-item"><a href="/admin/overrides">Override Management</a></li>
<li class="breadcrumb-item active"><a><%= typeof(id) === "undefined" ? "Add Override" : `Edit ${name}` %></a></li>
</ol>
</nav>
</div>
</div>
<div class="row">
<div class="col">
<h1><%= typeof(id) === "undefined" ? "Add Override" : `Edit ${name}` %></h1>
</div>
</div>
<form method="post" class="needs-validation" novalidate>
<input type="hidden" name="id" value="<%= typeof(id) === "undefined" ? "" : id %>" />
<div class="row mt-5 mb-3">
<div class="col">
<label for="name" class="form-label">Name</label>
<input class="form-control" id="name" name="name" value="<%= typeof(name) === "undefined" ? "" : name %>" required maxlength="255" />
</div>
</div>
<div class="row mb-3">
<div class="col">
<label for="forUrl" class="form-label">Type</label>
<select class="form-select" id="overrideType" name="overrideType" required>
<%- SelectUtility.EnumToSelectList(OverrideType, overrideType) %>
</select>
</div>
</div>
<div class="row mb-3">
<div class="col">
<label for="forUrl" class="form-label">For URL</label>
<input class="form-control" id="forUrl" name="forUrl" value="<%= typeof(forUrl) === "undefined" ? "" : forUrl %>" required />
</div>
</div>
<% if (overrideType === OverrideType.AddHtml) { %>
<%- include("./_addhtml") %>
<% } else { %>
<div id="overrideFields"></div>
<% } %>
<div class="row mb-3">
<div class="col text-center">
<button type="submit" class="btn btn-primary">Save</button>
<a type="submit" class="btn btn-danger" href="/admin/">Cancel</a>
</div>
</div>
</form>
<%- include("../base/footer", { apiKey: session.apiKey, username: session.username }) %>

View file

@ -0,0 +1,45 @@
<%- include("../base/header", { title: "Badge Management", userId: session.userId, isAdmin: true }) %>
<div class="row">
<div class="col">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/admin">Admin</a></li>
<li class="breadcrumb-item active"><a>Override Management</a></li>
</ol>
</nav>
</div>
</div>
<div class="row">
<div class="col">
<h1>Override Management</h1>
</div>
<div class="col-auto">
<a class="btn btn-primary btn-lg me-2" href="/admin/override">Add Override</a>
</div>
</div>
<div class="row my-5">
<div class="col">
<table class="table table-striped" style="word-break:break-word">
<thead>
<th>Name</th>
<th>&nbsp;</th>
</thead>
<tbody>
<% for (const override of overrides) { %>
<tr>
<td class="align-middle"><%= override.Name %></td>
<td class="text-end text-nowrap align-middle">
<a class="btn btn-sm btn-primary" href="/admin/override?id=<%= override.Id %>">Edit</a>
<a class="btn btn-sm btn-danger" href="/admin/deleteoverride?id=<%= override.Id %>" onclick="return confirm(`Are you sure you want to delete '<%= override.Name %>'?`)">Delete</a>
</td>
</tr>
<% } %>
</tbody>
</table>
</div>
</div>
<%- include("../base/footer", { apiKey: session.apiKey, username: session.username }) %>