From 9e285570e18d5fe03253c04174ae7fadd6683d25 Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Fri, 24 Jul 2026 07:40:58 -0400 Subject: [PATCH] feat:lineage compat, nessa made more similar --- src/components/SubdomainHeader.tsx | 70 +++++++- src/lib/site-context.ts | 5 +- src/routes/api/lineage/_lib.ts | 65 ++++++++ src/routes/api/lineage/analytics.ts | 8 + src/routes/api/lineage/apple/email.ts | 7 + src/routes/api/lineage/apple/registration.ts | 7 + src/routes/api/lineage/database/creds.ts | 7 + .../api/lineage/database/deletion/cancel.ts | 7 + .../api/lineage/database/deletion/check.ts | 7 + .../api/lineage/database/deletion/init.ts | 7 + src/routes/api/lineage/email/login.ts | 7 + src/routes/api/lineage/email/refresh/token.ts | 7 + .../api/lineage/email/refresh/verification.ts | 7 + src/routes/api/lineage/email/registration.ts | 7 + src/routes/api/lineage/email/verification.ts | 7 + src/routes/api/lineage/google/registration.ts | 7 + .../api/lineage/json_service/attacks.ts | 6 + .../api/lineage/json_service/conditions.ts | 6 + .../api/lineage/json_service/dungeons.ts | 6 + .../api/lineage/json_service/enemies.ts | 6 + src/routes/api/lineage/json_service/items.ts | 6 + src/routes/api/lineage/json_service/misc.ts | 6 + src/routes/api/lineage/offline_secret.ts | 22 +++ src/routes/api/lineage/pvp/battle_result.ts | 7 + src/routes/api/lineage/pvp/index.ts | 13 ++ src/routes/nessa/index.tsx | 3 +- src/server/api/root.ts | 5 + src/server/api/routers/lineage/database.ts | 80 ++++++++- .../api/routers/lineage/json-service.ts | 156 +++++++++--------- src/server/api/routers/lineage/misc.ts | 35 ++-- src/server/api/routers/lineage/pvp.ts | 75 +++++---- src/server/api/utils.ts | 1 - 32 files changed, 536 insertions(+), 129 deletions(-) create mode 100644 src/routes/api/lineage/_lib.ts create mode 100644 src/routes/api/lineage/analytics.ts create mode 100644 src/routes/api/lineage/apple/email.ts create mode 100644 src/routes/api/lineage/apple/registration.ts create mode 100644 src/routes/api/lineage/database/creds.ts create mode 100644 src/routes/api/lineage/database/deletion/cancel.ts create mode 100644 src/routes/api/lineage/database/deletion/check.ts create mode 100644 src/routes/api/lineage/database/deletion/init.ts create mode 100644 src/routes/api/lineage/email/login.ts create mode 100644 src/routes/api/lineage/email/refresh/token.ts create mode 100644 src/routes/api/lineage/email/refresh/verification.ts create mode 100644 src/routes/api/lineage/email/registration.ts create mode 100644 src/routes/api/lineage/email/verification.ts create mode 100644 src/routes/api/lineage/google/registration.ts create mode 100644 src/routes/api/lineage/json_service/attacks.ts create mode 100644 src/routes/api/lineage/json_service/conditions.ts create mode 100644 src/routes/api/lineage/json_service/dungeons.ts create mode 100644 src/routes/api/lineage/json_service/enemies.ts create mode 100644 src/routes/api/lineage/json_service/items.ts create mode 100644 src/routes/api/lineage/json_service/misc.ts create mode 100644 src/routes/api/lineage/offline_secret.ts create mode 100644 src/routes/api/lineage/pvp/battle_result.ts create mode 100644 src/routes/api/lineage/pvp/index.ts diff --git a/src/components/SubdomainHeader.tsx b/src/components/SubdomainHeader.tsx index 1b5289d..799216d 100644 --- a/src/components/SubdomainHeader.tsx +++ b/src/components/SubdomainHeader.tsx @@ -11,14 +11,66 @@ import { For, Show } from "solid-js"; import { A, useLocation } from "@solidjs/router"; import { useSite } from "~/context/SiteContext"; +import { useDarkMode } from "~/context/darkMode"; import { NAV_CONFIG, BACK_TO_FRENO } from "~/lib/nav-config"; +/** Simple SVG sun icon for dark mode toggle. */ +function SunIcon() { + return ( + + ); +} + +/** Simple SVG moon icon for dark mode toggle. */ +function MoonIcon() { + return ( + + ); +} + export default function SubdomainHeader() { const site = useSite(); const location = useLocation(); + const { isDark, toggleDarkMode } = useDarkMode(); const brandName = () => site().displayName; - const brandColor = () => site().brandColor; + const brandColor = () => + isDark() ? (site().brandColorDark ?? site().brandColor) : site().brandColor; const navItems = () => NAV_CONFIG[site().id].filter((item) => item.label !== "Home"); @@ -80,6 +132,22 @@ export default function SubdomainHeader() { > {BACK_TO_FRENO.label} + + {/* Dark mode toggle */} + diff --git a/src/lib/site-context.ts b/src/lib/site-context.ts index 23668e9..2a720d2 100644 --- a/src/lib/site-context.ts +++ b/src/lib/site-context.ts @@ -33,6 +33,8 @@ export interface Site { titleSuffix: string; /** Hex brand color used for theming accents / OG image backgrounds. */ brandColor: string; + /** Dark mode variant of the brand color (used when dark mode is active). */ + brandColorDark?: string; /** Default OpenGraph image path (resolved against the site root). */ ogDefaultImage: string; /** Favicon path for this site. */ @@ -83,7 +85,8 @@ export const SITE_CONFIG: Record = { baseRoutePrefix: "/nessa", displayName: "Nessa", titleSuffix: " | Nessa", - brandColor: "#cba6f7", + brandColor: "#527640", + brandColorDark: "#6CA86C", ogDefaultImage: "/nessa/og-default.png", faviconPath: "/nessa/favicon.ico" }, diff --git a/src/routes/api/lineage/_lib.ts b/src/routes/api/lineage/_lib.ts new file mode 100644 index 0000000..5c2093c --- /dev/null +++ b/src/routes/api/lineage/_lib.ts @@ -0,0 +1,65 @@ +// Shared REST handler for the Lineage REST shim. +// +// Each route file calls `rest()` with a function that receives a typed tRPC +// caller (scoped to `caller.lineage.*`) and the SolidStart APIEvent. The +// handler maps `TRPCError` codes → HTTP status + `{ message }` body (the +// legacy client parses `result.message` on non-OK responses), and returns +// the procedure's return value as JSON on success. + +import type { APIEvent } from "@solidjs/start/server"; +import { TRPCError } from "@trpc/server"; +import { createServerCaller } from "~/server/api/root"; + +const codeToStatus: Record = { + BAD_REQUEST: 400, + UNAUTHORIZED: 401, + FORBIDDEN: 403, + NOT_FOUND: 404, + CONFLICT: 409, + TIMEOUT: 408, + PAYLOAD_TOO_LARGE: 413, + METHOD_NOT_SUPPORTED: 405, + TOO_MANY_REQUESTS: 429, + INTERNAL_SERVER_ERROR: 500 +}; + +type Caller = Awaited>; + +export async function rest( + fn: (caller: Caller, event: APIEvent) => Promise, + event: APIEvent +): Promise { + try { + const caller = await createServerCaller(event); + const result = await fn(caller, event); + return new Response(JSON.stringify(result), { + status: 200, + headers: { "content-type": "application/json" } + }); + } catch (e) { + if (e instanceof TRPCError) { + const status = codeToStatus[e.code] ?? 500; + return new Response(JSON.stringify({ message: e.message }), { + status, + headers: { "content-type": "application/json" } + }); + } + console.error("Lineage REST shim error:", e); + return new Response(JSON.stringify({ message: "Internal server error" }), { + status: 500, + headers: { "content-type": "application/json" } + }); + } +} + +/** Extract the Bearer token from the Authorization header. */ +export function bearerToken(event: APIEvent): string | null { + const auth = event.request.headers.get("authorization") ?? ""; + const m = auth.match(/^Bearer\s+(.+)$/i); + return m?.[1]?.trim() ?? null; +} + +/** Parse the JSON request body. */ +export async function jsonBody(event: APIEvent): Promise { + return await event.request.json(); +} diff --git a/src/routes/api/lineage/analytics.ts b/src/routes/api/lineage/analytics.ts new file mode 100644 index 0000000..888d588 --- /dev/null +++ b/src/routes/api/lineage/analytics.ts @@ -0,0 +1,8 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "./_lib"; + +export const POST = (event: APIEvent) => + rest(async (caller) => { + const input = await event.request.json(); + return caller.lineage.misc.analytics(input); + }, event); diff --git a/src/routes/api/lineage/apple/email.ts b/src/routes/api/lineage/apple/email.ts new file mode 100644 index 0000000..6c9bf04 --- /dev/null +++ b/src/routes/api/lineage/apple/email.ts @@ -0,0 +1,7 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "../_lib"; + +export const POST = (event: APIEvent) => rest(async (caller) => { + const input = await event.request.json(); + return caller.lineage.auth.appleGetEmail(input); +}, event); diff --git a/src/routes/api/lineage/apple/registration.ts b/src/routes/api/lineage/apple/registration.ts new file mode 100644 index 0000000..07b8a58 --- /dev/null +++ b/src/routes/api/lineage/apple/registration.ts @@ -0,0 +1,7 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "../_lib"; + +export const POST = (event: APIEvent) => rest(async (caller) => { + const input = await event.request.json(); + return caller.lineage.auth.appleRegistration(input); +}, event); diff --git a/src/routes/api/lineage/database/creds.ts b/src/routes/api/lineage/database/creds.ts new file mode 100644 index 0000000..ab6793d --- /dev/null +++ b/src/routes/api/lineage/database/creds.ts @@ -0,0 +1,7 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "../_lib"; + +export const POST = (event: APIEvent) => rest(async (caller) => { + const input = await event.request.json(); + return caller.lineage.database.databaseCreds(input); +}, event); diff --git a/src/routes/api/lineage/database/deletion/cancel.ts b/src/routes/api/lineage/database/deletion/cancel.ts new file mode 100644 index 0000000..ab48422 --- /dev/null +++ b/src/routes/api/lineage/database/deletion/cancel.ts @@ -0,0 +1,7 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "../../_lib"; + +export const POST = (event: APIEvent) => rest(async (caller) => { + const input = await event.request.json(); + return caller.lineage.database.deletionCancel(input); +}, event); diff --git a/src/routes/api/lineage/database/deletion/check.ts b/src/routes/api/lineage/database/deletion/check.ts new file mode 100644 index 0000000..302a1e2 --- /dev/null +++ b/src/routes/api/lineage/database/deletion/check.ts @@ -0,0 +1,7 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "../../_lib"; + +export const POST = (event: APIEvent) => rest(async (caller) => { + const input = await event.request.json(); + return caller.lineage.database.deletionCheck(input); +}, event); diff --git a/src/routes/api/lineage/database/deletion/init.ts b/src/routes/api/lineage/database/deletion/init.ts new file mode 100644 index 0000000..a1c459d --- /dev/null +++ b/src/routes/api/lineage/database/deletion/init.ts @@ -0,0 +1,7 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "../../_lib"; + +export const POST = (event: APIEvent) => rest(async (caller) => { + const input = await event.request.json(); + return caller.lineage.database.deletionInit(input); +}, event); diff --git a/src/routes/api/lineage/email/login.ts b/src/routes/api/lineage/email/login.ts new file mode 100644 index 0000000..72ad190 --- /dev/null +++ b/src/routes/api/lineage/email/login.ts @@ -0,0 +1,7 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "../_lib"; + +export const POST = (event: APIEvent) => rest(async (caller) => { + const input = await event.request.json(); + return caller.lineage.auth.emailLogin(input); +}, event); diff --git a/src/routes/api/lineage/email/refresh/token.ts b/src/routes/api/lineage/email/refresh/token.ts new file mode 100644 index 0000000..d4bb429 --- /dev/null +++ b/src/routes/api/lineage/email/refresh/token.ts @@ -0,0 +1,7 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest, bearerToken } from "../../_lib"; + +export const GET = (event: APIEvent) => rest(async (caller) => { + const token = bearerToken(event); + return caller.lineage.auth.refreshToken({ token: token ?? "" }); +}, event); diff --git a/src/routes/api/lineage/email/refresh/verification.ts b/src/routes/api/lineage/email/refresh/verification.ts new file mode 100644 index 0000000..b138c03 --- /dev/null +++ b/src/routes/api/lineage/email/refresh/verification.ts @@ -0,0 +1,7 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "../../_lib"; + +export const POST = (event: APIEvent) => rest(async (caller) => { + const input = await event.request.json(); + return caller.lineage.auth.refreshVerification(input); +}, event); diff --git a/src/routes/api/lineage/email/registration.ts b/src/routes/api/lineage/email/registration.ts new file mode 100644 index 0000000..4f245cf --- /dev/null +++ b/src/routes/api/lineage/email/registration.ts @@ -0,0 +1,7 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "../_lib"; + +export const POST = (event: APIEvent) => rest(async (caller) => { + const input = await event.request.json(); + return caller.lineage.auth.emailRegistration(input); +}, event); diff --git a/src/routes/api/lineage/email/verification.ts b/src/routes/api/lineage/email/verification.ts new file mode 100644 index 0000000..fee5f26 --- /dev/null +++ b/src/routes/api/lineage/email/verification.ts @@ -0,0 +1,7 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "../_lib"; + +export const POST = (event: APIEvent) => rest(async (caller) => { + const input = await event.request.json(); + return caller.lineage.auth.emailVerification(input); +}, event); diff --git a/src/routes/api/lineage/google/registration.ts b/src/routes/api/lineage/google/registration.ts new file mode 100644 index 0000000..3ddac61 --- /dev/null +++ b/src/routes/api/lineage/google/registration.ts @@ -0,0 +1,7 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "../_lib"; + +export const POST = (event: APIEvent) => rest(async (caller) => { + const input = await event.request.json(); + return caller.lineage.auth.googleRegistration(input); +}, event); diff --git a/src/routes/api/lineage/json_service/attacks.ts b/src/routes/api/lineage/json_service/attacks.ts new file mode 100644 index 0000000..64c391f --- /dev/null +++ b/src/routes/api/lineage/json_service/attacks.ts @@ -0,0 +1,6 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "../_lib"; + +export const GET = (event: APIEvent) => rest(async (caller) => { + return caller.lineage.jsonService.attacks(); +}, event); diff --git a/src/routes/api/lineage/json_service/conditions.ts b/src/routes/api/lineage/json_service/conditions.ts new file mode 100644 index 0000000..e72422e --- /dev/null +++ b/src/routes/api/lineage/json_service/conditions.ts @@ -0,0 +1,6 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "../_lib"; + +export const GET = (event: APIEvent) => rest(async (caller) => { + return caller.lineage.jsonService.conditions(); +}, event); diff --git a/src/routes/api/lineage/json_service/dungeons.ts b/src/routes/api/lineage/json_service/dungeons.ts new file mode 100644 index 0000000..71d5107 --- /dev/null +++ b/src/routes/api/lineage/json_service/dungeons.ts @@ -0,0 +1,6 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "../_lib"; + +export const GET = (event: APIEvent) => rest(async (caller) => { + return caller.lineage.jsonService.dungeons(); +}, event); diff --git a/src/routes/api/lineage/json_service/enemies.ts b/src/routes/api/lineage/json_service/enemies.ts new file mode 100644 index 0000000..6951990 --- /dev/null +++ b/src/routes/api/lineage/json_service/enemies.ts @@ -0,0 +1,6 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "../_lib"; + +export const GET = (event: APIEvent) => rest(async (caller) => { + return caller.lineage.jsonService.enemies(); +}, event); diff --git a/src/routes/api/lineage/json_service/items.ts b/src/routes/api/lineage/json_service/items.ts new file mode 100644 index 0000000..ca30874 --- /dev/null +++ b/src/routes/api/lineage/json_service/items.ts @@ -0,0 +1,6 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "../_lib"; + +export const GET = (event: APIEvent) => rest(async (caller) => { + return caller.lineage.jsonService.items(); +}, event); diff --git a/src/routes/api/lineage/json_service/misc.ts b/src/routes/api/lineage/json_service/misc.ts new file mode 100644 index 0000000..a97d67c --- /dev/null +++ b/src/routes/api/lineage/json_service/misc.ts @@ -0,0 +1,6 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "../_lib"; + +export const GET = (event: APIEvent) => rest(async (caller) => { + return caller.lineage.jsonService.misc(); +}, event); diff --git a/src/routes/api/lineage/offline_secret.ts b/src/routes/api/lineage/offline_secret.ts new file mode 100644 index 0000000..85d2f3e --- /dev/null +++ b/src/routes/api/lineage/offline_secret.ts @@ -0,0 +1,22 @@ +// Plain-text route for the Lineage offline secret. +// +// `misc.offlineSecret` is a tRPC `.query()` returning `{ secret: "…" }`. But +// the Lineage client's `IAPStore.fetchOfflineSecret` does +// `await response.text()` and uses the raw string directly as the decryption +// key. trpc-openapi always JSON-encodes responses, so exposing it via the openapi +// shim would yield `'{"secret":"…"}'` as text and break decryption. +// +// This dedicated route returns the secret verbatim as `text/plain`, matching +// exactly what the legacy endpoint returned. Auth: none (the legacy endpoint +// had none either — the secret is a server-side decryption key shared to all +// clients; it rotates with `LINEAGE_OFFLINE_SERIALIZATION_SECRET`). + +import type { APIEvent } from "@solidjs/start/server"; +import { env } from "~/env/server"; + +export const GET = (event: APIEvent) => { + return new Response(env.LINEAGE_OFFLINE_SERIALIZATION_SECRET, { + status: 200, + headers: { "content-type": "text/plain; charset=utf-8" } + }); +}; diff --git a/src/routes/api/lineage/pvp/battle_result.ts b/src/routes/api/lineage/pvp/battle_result.ts new file mode 100644 index 0000000..987a906 --- /dev/null +++ b/src/routes/api/lineage/pvp/battle_result.ts @@ -0,0 +1,7 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "../_lib"; + +export const POST = (event: APIEvent) => rest(async (caller) => { + const input = await event.request.json(); + return caller.lineage.pvp.battleResult(input); +}, event); diff --git a/src/routes/api/lineage/pvp/index.ts b/src/routes/api/lineage/pvp/index.ts new file mode 100644 index 0000000..a5c75b4 --- /dev/null +++ b/src/routes/api/lineage/pvp/index.ts @@ -0,0 +1,13 @@ +import type { APIEvent } from "@solidjs/start/server"; +import { rest } from "../_lib"; + +// GET /api/lineage/pvp — retrieve opponents +export const GET = (event: APIEvent) => rest(async (caller) => { + return caller.lineage.pvp.getOpponents(); +}, event); + +// POST /api/lineage/pvp — register/update player character +export const POST = (event: APIEvent) => rest(async (caller) => { + const input = await event.request.json(); + return caller.lineage.pvp.registerCharacter(input); +}, event); diff --git a/src/routes/nessa/index.tsx b/src/routes/nessa/index.tsx index 5362e8e..a6645f6 100644 --- a/src/routes/nessa/index.tsx +++ b/src/routes/nessa/index.tsx @@ -54,7 +54,8 @@ export default function NessaLanding() { const { isDark } = useDarkMode(); const iconSrc = () => (isDark() ? ICON_DARK : ICON_DEFAULT); - const brandColor = () => site().brandColor; + const brandColor = () => + isDark() ? (site().brandColorDark ?? site().brandColor) : site().brandColor; return ( <> diff --git a/src/server/api/root.ts b/src/server/api/root.ts index 8f2160a..601e852 100644 --- a/src/server/api/root.ts +++ b/src/server/api/root.ts @@ -3,6 +3,11 @@ import { auditRouter } from "./routers/audit"; import { analyticsRouter } from "./routers/analytics"; import { databaseRouter } from "./routers/database"; import { lineageRouter } from "./routers/lineage"; +// Re-exported so the REST shim (src/routes/api/lineage/[...path].ts) can mount +// ONLY the lineage sub-router as the trpc-openapi REST surface — not the +// whole appRouter. Keeps nessa/community/auth/audit routers unreachable via +// `/api/lineage/*`. +export { lineageRouter }; import { miscRouter } from "./routers/misc"; import { userRouter } from "./routers/user"; import { blogRouter } from "./routers/blog"; diff --git a/src/server/api/routers/lineage/database.ts b/src/server/api/routers/lineage/database.ts index a074121..a99b920 100644 --- a/src/server/api/routers/lineage/database.ts +++ b/src/server/api/routers/lineage/database.ts @@ -6,7 +6,11 @@ import { } from "~/server/utils"; import { env } from "~/env/server"; import { TRPCError } from "@trpc/server"; -import { createTRPCRouter, publicProcedure, csrfProtectedProcedure } from "~/server/api/utils"; +import { + createTRPCRouter, + publicProcedure, + csrfProtectedProcedure +} from "~/server/api/utils"; import { fetchWithTimeout, checkResponse, @@ -16,8 +20,78 @@ import { } from "~/server/fetch-utils"; export const lineageDatabaseRouter = createTRPCRouter({ - // credentials endpoint removed (p8-008): was exposing persistent DB tokens to clients. - // Database access should be proxied through tRPC server-side procedures. + // Per-user DB credentials endpoint. + // + // Returns the caller's OWN remote Turso DB name + token so the client can + // open a direct libsql connection (game data syncs across devices). This is + // safe to expose to the authenticated user: the token grants full access only + // to that user's own DB, equivalent to an on-device SQLite that travels + // between devices. The generic p8-008 "exposing persistent DB tokens" + // concern does not apply — there is no cross-user escalation possible. + // + // REST surface: POST /api/lineage/database/creds (auth via Bearer header). + databaseCreds: csrfProtectedProcedure + .input( + z.object({ + email: z.string().email(), + provider: z.enum(["email", "apple", "google"]) + }) + ) + .mutation(async ({ input, ctx }) => { + const { email, provider } = input; + + // Bearer auth token comes from the Authorization header, exactly as the + // legacy REST endpoint read it. For `email` it's a Lineage JWT; for + // `apple` the apple_user_string; for `google` a Google id token. + const authHeader = + (ctx.event.nativeEvent.node?.req?.headers?.authorization as + | string + | undefined) ?? + (ctx.event.request?.headers?.get("authorization") as + | string + | undefined); + const authToken = authHeader?.replace(/^Bearer\s+/i, "").trim(); + if (!authToken) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "Missing Authorization header" + }); + } + + const conn = LineageConnectionFactory(); + const res = await conn.execute({ + sql: `SELECT * FROM User WHERE email = ? AND provider = ? LIMIT 1`, + args: [email, provider] + }); + + if (res.rows.length === 0) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "User not found" + }); + } + + const userRow = res.rows[0]; + + const valid = await validateLineageRequest({ + auth_token: authToken, + userRow + }); + + if (!valid) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "Invalid Verification" + }); + } + + // The legacy endpoint returned `{ db_name, db_token }`; the User row + // stores these as `database_name` / `database_token`. + return { + db_name: userRow.database_name, + db_token: userRow.database_token + }; + }), deletionInit: csrfProtectedProcedure .input( diff --git a/src/server/api/routers/lineage/json-service.ts b/src/server/api/routers/lineage/json-service.ts index e7d87ae..e6fdb03 100644 --- a/src/server/api/routers/lineage/json-service.ts +++ b/src/server/api/routers/lineage/json-service.ts @@ -52,84 +52,90 @@ import sanityOptions from "~/lineage-json/misc-route/sanityOptions.json"; import pvpRewards from "~/lineage-json/misc-route/pvpRewards.json"; export const lineageJsonServiceRouter = createTRPCRouter({ - attacks: publicProcedure.query(() => { - return { - ok: true, - playerAttacks, - mageBooks, - mageSpells, - necroBooks, - necroSpells, - rangerBooks, - rangerSpells, - paladinBooks, - paladinSpells, - summons - }; - }), + attacks: publicProcedure + .query(() => { + return { + ok: true, + playerAttacks, + mageBooks, + mageSpells, + necroBooks, + necroSpells, + rangerBooks, + rangerSpells, + paladinBooks, + paladinSpells, + summons + }; + }), - conditions: publicProcedure.query(() => { - return { - ok: true, - conditions, - debilitations, - sanityDebuffs - }; - }), + conditions: publicProcedure + .query(() => { + return { + ok: true, + conditions, + debilitations, + sanityDebuffs + }; + }), - dungeons: publicProcedure.query(() => { - return { - ok: true, - dungeons, - specialEncounters - }; - }), + dungeons: publicProcedure + .query(() => { + return { + ok: true, + dungeons, + specialEncounters + }; + }), - enemies: publicProcedure.query(() => { - return { - ok: true, - bosses, - enemies, - enemyAttacks - }; - }), + enemies: publicProcedure + .query(() => { + return { + ok: true, + bosses, + enemies, + enemyAttacks + }; + }), - items: publicProcedure.query(() => { - return { - ok: true, - arrows, - bows, - foci, - hats, - junk, - melee, - robes, - wands, - ingredients, - storyItems, - artifacts, - shields, - bodyArmor, - helmets, - suffix, - prefix, - potions, - poison, - staves - }; - }), + items: publicProcedure + .query(() => { + return { + ok: true, + arrows, + bows, + foci, + hats, + junk, + melee, + robes, + wands, + ingredients, + storyItems, + artifacts, + shields, + bodyArmor, + helmets, + suffix, + prefix, + potions, + poison, + staves + }; + }), - misc: publicProcedure.query(() => { - return { - ok: true, - activities, - investments, - jobs, - manaOptions, - otherOptions, - healthOptions, - sanityOptions, - pvpRewards - }; - }) + misc: publicProcedure + .query(() => { + return { + ok: true, + activities, + investments, + jobs, + manaOptions, + otherOptions, + healthOptions, + sanityOptions, + pvpRewards + }; + }) }); diff --git a/src/server/api/routers/lineage/misc.ts b/src/server/api/routers/lineage/misc.ts index 2f88bf6..a7d5915 100644 --- a/src/server/api/routers/lineage/misc.ts +++ b/src/server/api/routers/lineage/misc.ts @@ -1,4 +1,9 @@ -import { createTRPCRouter, publicProcedure, adminProcedure, csrfProtectedProcedure } from "../../utils"; +import { + createTRPCRouter, + publicProcedure, + adminProcedure, + csrfProtectedProcedure +} from "../../utils"; import { z } from "zod"; import { LineageConnectionFactory } from "~/server/utils"; import { env } from "~/env/server"; @@ -15,7 +20,7 @@ export const lineageMiscRouter = createTRPCRouter({ proficiencies: z.record(z.unknown()), jobs: z.record(z.unknown()), resistanceTable: z.record(z.unknown()), - damageTable: z.record(z.unknown()), + damageTable: z.record(z.unknown()) }) ) .mutation(async ({ input }) => { @@ -27,7 +32,7 @@ export const lineageMiscRouter = createTRPCRouter({ proficiencies, jobs, resistanceTable, - damageTable, + damageTable } = input; const conn = LineageConnectionFactory(); @@ -47,8 +52,8 @@ export const lineageMiscRouter = createTRPCRouter({ JSON.stringify(proficiencies), JSON.stringify(jobs), JSON.stringify(resistanceTable), - JSON.stringify(damageTable), - ], + JSON.stringify(damageTable) + ] }); return { success: true, status: 200 }; @@ -56,7 +61,7 @@ export const lineageMiscRouter = createTRPCRouter({ console.error("Analytics error:", e); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", - message: "Failed to store analytics", + message: "Failed to store analytics" }); } }), @@ -69,7 +74,7 @@ export const lineageMiscRouter = createTRPCRouter({ if (!token) { throw new TRPCError({ code: "BAD_REQUEST", - message: "Missing token in body", + message: "Missing token in body" }); } @@ -80,11 +85,17 @@ export const lineageMiscRouter = createTRPCRouter({ if (res.rows.length > 0) { const queryUpdate = "UPDATE Token SET last_updated_at = datetime('now') WHERE token = ?"; - const resUpdate = await conn.execute({ sql: queryUpdate, args: [token] }); + const resUpdate = await conn.execute({ + sql: queryUpdate, + args: [token] + }); return { success: true, action: "updated", result: resUpdate }; } else { const queryInsert = "INSERT INTO Token (token) VALUES (?)"; - const resInsert = await conn.execute({ sql: queryInsert, args: [token] }); + const resInsert = await conn.execute({ + sql: queryInsert, + args: [token] + }); return { success: true, action: "inserted", result: resInsert }; } }), @@ -99,7 +110,7 @@ export const lineageMiscRouter = createTRPCRouter({ try { const res = await conn.execute({ sql: `SELECT * FROM Analytics`, - args: [], + args: [] }); const rows = res.rows.map((row: any) => ({ @@ -110,7 +121,7 @@ export const lineageMiscRouter = createTRPCRouter({ proficiencies: safeJsonParse(row.proficiencies), jobs: safeJsonParse(row.jobs), resistanceTable: safeJsonParse(row.resistanceTable), - damageTable: safeJsonParse(row.damageTable), + damageTable: safeJsonParse(row.damageTable) })); return { success: true, players: rows }; @@ -118,7 +129,7 @@ export const lineageMiscRouter = createTRPCRouter({ console.error("Failed to fetch lineage analytics:", e); return { success: false, players: [] }; } - }), + }) }); function safeJsonParse(val: unknown): unknown { diff --git a/src/server/api/routers/lineage/pvp.ts b/src/server/api/routers/lineage/pvp.ts index 26677de..9901162 100644 --- a/src/server/api/routers/lineage/pvp.ts +++ b/src/server/api/routers/lineage/pvp.ts @@ -1,4 +1,8 @@ -import { createTRPCRouter, publicProcedure, csrfProtectedProcedure } from "../../utils"; +import { + createTRPCRouter, + publicProcedure, + csrfProtectedProcedure +} from "../../utils"; import { z } from "zod"; import { LineageConnectionFactory } from "~/server/utils"; import { TRPCError } from "@trpc/server"; @@ -17,7 +21,7 @@ const characterSchema = z.object({ resistanceTable: z.string(), damageTable: z.string(), attackStrings: z.string(), - knownSpells: z.string(), + knownSpells: z.string() }); export const lineagePvpRouter = createTRPCRouter({ @@ -27,7 +31,7 @@ export const lineagePvpRouter = createTRPCRouter({ character: characterSchema, linkID: z.string(), pushToken: z.string().optional(), - pushCurrentlyEnabled: z.boolean().optional(), + pushCurrentlyEnabled: z.boolean().optional() }) ) .mutation(async ({ input }) => { @@ -37,7 +41,7 @@ export const lineagePvpRouter = createTRPCRouter({ const conn = LineageConnectionFactory(); const res = await conn.execute({ sql: `SELECT * FROM PvP_Characters WHERE linkID = ?`, - args: [linkID], + args: [linkID] }); if (res.rows.length === 0) { @@ -78,8 +82,8 @@ export const lineagePvpRouter = createTRPCRouter({ character.attackStrings, character.knownSpells, pushToken, - pushCurrentlyEnabled, - ], + pushCurrentlyEnabled + ] }); return { @@ -87,7 +91,7 @@ export const lineagePvpRouter = createTRPCRouter({ winCount: 0, lossCount: 0, tokenRedemptionCount: 0, - status: 201, + status: 201 }; } else { await conn.execute({ @@ -126,8 +130,8 @@ export const lineagePvpRouter = createTRPCRouter({ character.knownSpells, pushToken, pushCurrentlyEnabled, - linkID, - ], + linkID + ] }); return { @@ -135,24 +139,25 @@ export const lineagePvpRouter = createTRPCRouter({ winCount: res.rows[0].winCount as number, lossCount: res.rows[0].lossCount as number, tokenRedemptionCount: res.rows[0].tokenRedemptionCount as number, - status: 200, + status: 200 }; } } catch (e) { console.error(e); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", - message: "Failed to register character", + message: "Failed to register character" }); } }), - getOpponents: publicProcedure.query(async () => { - const conn = LineageConnectionFactory(); + getOpponents: publicProcedure + .query(async () => { + const conn = LineageConnectionFactory(); - try { - const res = await conn.execute( - ` + try { + const res = await conn.execute( + ` SELECT playerClass, blessing, name, @@ -174,27 +179,27 @@ export const lineagePvpRouter = createTRPCRouter({ ORDER BY RANDOM() LIMIT 3 ` - ); + ); - return { - ok: true, - characters: res.rows, - status: 200, - }; - } catch (e) { - console.error(e); - throw new TRPCError({ - code: "INTERNAL_SERVER_ERROR", - message: "Failed to get opponents", - }); - } - }), + return { + ok: true, + characters: res.rows, + status: 200 + }; + } catch (e) { + console.error(e); + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to get opponents" + }); + } + }), battleResult: csrfProtectedProcedure .input( z.object({ winnerLinkID: z.string(), - loserLinkID: z.string(), + loserLinkID: z.string() }) ) .mutation(async ({ input }) => { @@ -211,19 +216,19 @@ export const lineagePvpRouter = createTRPCRouter({ lossCount = lossCount + CASE WHEN linkID = ? THEN 1 ELSE 0 END WHERE linkID IN (?, ?) `, - args: [winnerLinkID, loserLinkID, winnerLinkID, loserLinkID], + args: [winnerLinkID, loserLinkID, winnerLinkID, loserLinkID] }); return { ok: true, - status: 200, + status: 200 }; } catch (e) { console.error(e); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", - message: "Failed to record battle result", + message: "Failed to record battle result" }); } - }), + }) }); diff --git a/src/server/api/utils.ts b/src/server/api/utils.ts index 3dca1e4..7a0c44f 100644 --- a/src/server/api/utils.ts +++ b/src/server/api/utils.ts @@ -181,4 +181,3 @@ const csrfProtection = t.middleware(async ({ ctx, next }) => { // CSRF-protected procedure export const csrfProtectedProcedure = t.procedure.use(csrfProtection); export { csrfProtection }; -