From 2302290d344e26d9c1f5bdbf91ac381adac3394c Mon Sep 17 00:00:00 2001 From: zombieb Date: Sat, 26 Jul 2025 00:17:14 -0400 Subject: [PATCH] changed network log format, player settings --- .gitignore | 5 +-- src/main.ts | 22 +++++++----- src/routes/accounts/routes/account.ts | 6 ++-- src/routes/api/routes/settings.ts | 28 +++++++++++++++ src/server/profiles/content/Settings.ts | 45 +++++++++++++++++++++++++ src/server/profiles/content/base.ts | 12 ++++++- src/server/profiles/profile.ts | 17 ++++++---- src/util/validators.ts | 18 ++++++---- 8 files changed, 126 insertions(+), 27 deletions(-) create mode 100644 src/routes/api/routes/settings.ts create mode 100644 src/server/profiles/content/Settings.ts diff --git a/.gitignore b/.gitignore index b8b3e92..fe7016f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -.env -/persist/ \ No newline at end of file +/.env +/persist/ +/worklist.txt \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index e7dcdb5..aad330c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -10,6 +10,7 @@ import { verify } from "@hono/hono/jwt"; import { type ProfileToken } from "./server/profiles/types/profile.ts"; import { SignalRSocketHandler } from "./server/socket/signalr/socket.ts"; import { PushNotificationId } from "./server/socket/signalr/types.ts"; +import { genericResponse } from "./util/api.ts"; LoggingConfiguration.resetTimeFormat = TimeFormat.Unix; LoggingConfiguration.resetLogTiming = LogTiming.Microtask; @@ -40,10 +41,7 @@ await routeImporter(AppRoot.app, 'src/', [ // deno-lint-ignore require-await AppRoot.app.use('*', async c => { - if (!logBlacklist.includes(c.req.url)) log.e(detailedLog([c.get('srcAddr'), - `404 ${c.req.method} ${getFullPathFromUrl(new URL(c.req.url))}` - ])); - c.res = new Response("Not Found", { status: 404 }); + return c.json(genericResponse(false, "Resource Not Found"), 404); }); export const consoleSockets: Set = new Set(); @@ -91,7 +89,7 @@ const server = Deno.serve({ hostname: "10.0.1.39", port: 13370, onListen: addr = const payload = (await verify(splitHeader, secret)) as ProfileToken; const profile = await Server.Profiles.get(payload.sub); - if (!profile) return new Response("Internal Server Error (profile)", { status: 500 });; + if (!profile) return new Response("Internal Server Error (profile)", { status: 500 }); const { response, socket } = Deno.upgradeWebSocket(req); const handler = new SignalRSocketHandler(socket, profile); gameSockets.add(handler); @@ -119,15 +117,21 @@ const server = Deno.serve({ hostname: "10.0.1.39", port: 13370, onListen: addr = } } + + const res = await AppRoot.app.fetch(req, { srcAddr }); - if (!logBlacklist.includes(url.pathname)) log.n(detailedLog([srcAddr, - `${req.method} ${getFullPathFromUrl(new URL(req.url))}`, + const netlog = detailedLog([srcAddr, + `${res.status}: ${req.method} ${getFullPathFromUrl(new URL(req.url))}`, formatHeader(req.headers, 'Content-Type'), formatHeader(req.headers, 'Connection'), formatHeader(req.headers, 'User-Agent'), - ])); + ]); + if (!logBlacklist.includes(url.pathname)) { + if (res.status === 404) log.e(netlog); + else log.n(netlog); + } - return await AppRoot.app.fetch(req, { srcAddr }); + return res; }); diff --git a/src/routes/accounts/routes/account.ts b/src/routes/accounts/routes/account.ts index e79ace7..1dd69b9 100644 --- a/src/routes/accounts/routes/account.ts +++ b/src/routes/accounts/routes/account.ts @@ -3,6 +3,9 @@ import { authenticate } from "../../../util/api.ts"; import Server from "../../../server/server.ts"; import z from "zod"; import { typedZValidator } from "../../../util/validators.ts"; +import Logging from "@proxnet/undead-logging"; + +const log = new Logging("AccountDebug"); export const route = createHonoRoute('/account'); @@ -29,10 +32,9 @@ route.app.get('/bulk', typedZValidator('query', bulkAccountQuerySchema), async c ); }); -route.app.use('*', authenticate); +route.app.use(authenticate); route.app.get('/me', c => { const profile = c.get('profile'); - return c.json(profile.selfExport()); }); \ No newline at end of file diff --git a/src/routes/api/routes/settings.ts b/src/routes/api/routes/settings.ts new file mode 100644 index 0000000..be2f710 --- /dev/null +++ b/src/routes/api/routes/settings.ts @@ -0,0 +1,28 @@ +import z from "zod"; +import { authenticate, genericResponse } from "../../../util/api.ts"; +import { createHonoRoute } from "../../../util/import.ts"; +import { transformStringToEnum, typedZValidator } from "../../../util/validators.ts"; +import { ProfileSetting } from "../../../server/profiles/content/Settings.ts"; +import { trimTrailingSlash } from "@hono/hono/trailing-slash"; + +export const route = createHonoRoute('/settings'); + +route.app.use(authenticate, trimTrailingSlash()); + +route.app.get('/v2', async c => { + const profile = c.get('profile'); + const settings = await profile.Settings.getAllSettings(); + c.json(settings); +}); + +const settingsSetSchema = z.object({ + Key: z.string().transform(transformStringToEnum(ProfileSetting, true)), + Value: z.string() +}); +route.app.post('/v2/set', typedZValidator('json', settingsSetSchema), async c => { + const { Key, Value } = c.req.valid('json'); + if (!Key) return c.json(genericResponse(false, "Internal Server Error"), 500); + + await c.get('profile').Settings.setSetting(Key, Value); + return c.status(200); +}); \ No newline at end of file diff --git a/src/server/profiles/content/Settings.ts b/src/server/profiles/content/Settings.ts new file mode 100644 index 0000000..502c868 --- /dev/null +++ b/src/server/profiles/content/Settings.ts @@ -0,0 +1,45 @@ +import z from "zod"; +import ProfileContentManager from "./base.ts"; + +export enum ProfileSetting { + Test = "test" +} + +const genericSettingSchema = z.object({ + Key: z.string(), + Value: z.string() +}); +const settingsSchema = z.array(genericSettingSchema); +type Setting = z.infer; + +export class ProfileSettingsManager extends ProfileContentManager { + + static settingsKey = "settings"; + #key = this.profile.constructProfilePropertyKey(ProfileSettingsManager.settingsKey); + + async getAllSettings(): Promise { + const items = await this.kv.getKv().get(this.#key); + if (items.value == null) return []; + const parsed = settingsSchema.safeParse(items.value); + if (parsed.success) return parsed.data; + else return []; + } + + async getSetting(setting: ProfileSetting) { + const settings = await this.getAllSettings(); + return settings.find(s => s.Key == setting)?.Value || null; + } + + async #updateSettings(settings: Setting[]) { + await this.kv.getKv().set(this.#key, settings); + } + + async setSetting(Key: ProfileSetting, Value: string) { + const settings = await this.getAllSettings(); + const s = settings.find(setting => setting.Key === Key); + if (!s) settings.push({ Key, Value }); + else s.Value = Value; + await this.#updateSettings(settings); + } + +} \ No newline at end of file diff --git a/src/server/profiles/content/base.ts b/src/server/profiles/content/base.ts index fff2e3a..b57d80b 100644 --- a/src/server/profiles/content/base.ts +++ b/src/server/profiles/content/base.ts @@ -1,5 +1,15 @@ +import type KV from "../../persistence/kv.ts"; +import type Profile from "../profile.ts"; + class ProfileContentManager { - + + protected profile: Profile; + protected kv: KV; + constructor(profile: Profile, kv: KV) { + this.profile = profile; + this.kv = kv; + } + } export default ProfileContentManager; \ No newline at end of file diff --git a/src/server/profiles/profile.ts b/src/server/profiles/profile.ts index 5c60607..68822d9 100644 --- a/src/server/profiles/profile.ts +++ b/src/server/profiles/profile.ts @@ -2,6 +2,7 @@ import KV from "../persistence/kv.ts"; import { ProfileRole } from "../platforms/base.ts"; import { type ServerBase } from "../server.ts"; import { type SignalRSocketHandler } from "../socket/signalr/socket.ts"; +import { ProfileSettingsManager } from "./content/Settings.ts"; import ProfileManagerBase from "./manager.ts"; import { recNetAccountSchema, SelfAccount, type RecNetAccount } from "./types/profile.ts"; @@ -15,19 +16,23 @@ class Profile { #selfAcc: SelfAccount; + Settings: ProfileSettingsManager; + constructor(acc: SelfAccount, kv: KV, server: ServerBase) { this.#id = acc.accountId; this.#selfAcc = acc; this.#kv = kv; this.#server = server; + + this.Settings = new ProfileSettingsManager(this, this.#kv); } async #saveSelfAcc() { - await this.#kv.getKv().set(this.#constructProfilePropertyKey(), this.#selfAcc); + await this.#kv.getKv().set(this.constructProfilePropertyKey(), this.#selfAcc); this.#server.emit('profile.update', { profile: this }); } - #constructProfilePropertyKey(...keys: (string | undefined)[]) { + constructProfilePropertyKey(...keys: (string | undefined)[]) { return [ ProfileManagerBase.profilesKey, this.#id, ...keys.filter(val => typeof val == 'string') ]; } @@ -51,24 +56,24 @@ class Profile { } async getBio(){ - const key = this.#constructProfilePropertyKey('bio'); + const key = this.constructProfilePropertyKey('bio'); const val = await this.#kv.getKv().get(key); if (!val.value) return null; else return val.value; } async setBio(bio: string) { - const key = this.#constructProfilePropertyKey('bio'); + const key = this.constructProfilePropertyKey('bio'); await this.#kv.getKv().set(key, bio); } async getRole(): Promise { - const val = (await this.#kv.getKv().get(this.#constructProfilePropertyKey('role'))).value; + const val = (await this.#kv.getKv().get(this.constructProfilePropertyKey('role'))).value; if (!val) return ProfileRole.User; else return val; } async setRole(role: ProfileRole) { - await this.#kv.getKv().set(this.#constructProfilePropertyKey('role'), role); + await this.#kv.getKv().set(this.constructProfilePropertyKey('role'), role); this.#server.emit('profile.roleupdate', { profile: this, newRole: role }); } diff --git a/src/util/validators.ts b/src/util/validators.ts index 8c87c91..7f72794 100644 --- a/src/util/validators.ts +++ b/src/util/validators.ts @@ -18,13 +18,17 @@ export const typedZValidator = ( >; }; -export const transformStringToEnum = (anEnum: { [s: number]: string }) => { - return (arg: string, ctx: z.RefinementCtx) => { - const int = parseInt(arg); - if (isNaN(int)) ctx.addIssue("Must be parseable as a number"); - else { - if (anEnum[int]) return int as T; - else ctx.addIssue("Number must be a valid enum member"); +export const transformStringToEnum = (anEnum: { [s: string]: string | number }, str?: boolean) => { + return (arg: string, ctx: z.RefinementCtx) => { + if (!str) { + const int = parseInt(arg); + if (isNaN(int) || !Number.isSafeInteger(int)) ctx.addIssue("number was not valid"); + else if (anEnum[int]) return int as T; + else ctx.addIssue("number was not a valid enum member"); + } else { + const vals = Object.values(anEnum); + if (vals.includes(arg)) return arg as T; + else ctx.addIssue("string was not a valid enum member"); } } } \ No newline at end of file