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 };
-