forked from Mike/freno-dev
nessa: retire the API copy now served by nessa-api.freno.me
The Nessa API (nessaDb.*, nessa.community.*, Clerk auth and the Clerk user
webhook) lives in the Nessa repo's server/ on pan. This copy never served
the app: RemoteAPIClient wraps every input in {"input": …}, which the stock
tRPC adapter here reads as the input itself (queries 404, writes no-op).
Clerk's webhook already targets nessa-api.freno.me.
Drops the routers, nessa-auth, the webhook, NessaConnectionFactory, the
NESSA_* env and the now-unused @clerk/backend and svix. Nessa's marketing,
privacy and deletion pages stay.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,15 +2,12 @@ import { initTRPC, TRPCError } from "@trpc/server";
|
||||
import type { APIEvent } from "@solidjs/start/server";
|
||||
import { logVisit, enrichAnalyticsEntry } from "~/server/analytics";
|
||||
import { getRequestIP } from "vinxi/http";
|
||||
import { verifyNessaToken } from "~/server/nessa-auth";
|
||||
import { getAuthPayloadFromEvent } from "~/server/auth";
|
||||
import { NessaConnectionFactory } from "~/server/database";
|
||||
|
||||
export type Context = {
|
||||
event: APIEvent;
|
||||
userId: string | null;
|
||||
isAdmin: boolean;
|
||||
nessaUserId: string | null;
|
||||
};
|
||||
|
||||
/** Safely get a header value from either Fetch API Headers or Node.js IncomingHttpHeaders */
|
||||
@@ -55,44 +52,6 @@ async function createContextInner(event: APIEvent): Promise<Context> {
|
||||
getHeader(req.headers, "referrer") ||
|
||||
getHeader(event.request?.headers, "referer");
|
||||
const ipAddress = getRequestIP(event.nativeEvent) || undefined;
|
||||
const authHeader =
|
||||
getHeader(req.headers, "authorization") ||
|
||||
getHeader(event.request?.headers, "authorization") ||
|
||||
null;
|
||||
|
||||
let nessaUserId: string | null = null;
|
||||
if (authHeader && authHeader.startsWith("Bearer ")) {
|
||||
const token = authHeader.replace("Bearer ", "").trim();
|
||||
try {
|
||||
// Verify the Clerk session JWT — `sub` is the Clerk user id.
|
||||
const clerkPayload = await verifyNessaToken(token);
|
||||
|
||||
// Resolve the Clerk user id to the local users.id via the indexed
|
||||
// clerkUserId column. One indexed query per request is acceptable;
|
||||
// no premature caching (the row is created by the Clerk webhook).
|
||||
const conn = NessaConnectionFactory();
|
||||
const result = await conn.execute({
|
||||
sql: "SELECT id FROM users WHERE clerkUserId = ?",
|
||||
args: [clerkPayload.sub]
|
||||
});
|
||||
if (result.rows.length === 0) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "Nessa user not found — Clerk account not linked"
|
||||
});
|
||||
}
|
||||
// `nessaUserId` is the LOCAL users.id — router bodies reference it
|
||||
// exactly as before (club ownership, membership, row scoping).
|
||||
nessaUserId = (result.rows[0] as { id: string }).id;
|
||||
} catch (error) {
|
||||
// Re-throw typed TRPCError (lookup miss) so the caller gets UNAUTHORIZED;
|
||||
// swallow Clerk verification failures (expired/invalid token) the same
|
||||
// way the legacy path did — the enforceNessaUser middleware rejects
|
||||
// null nessaUserId with UNAUTHORIZED.
|
||||
if (error instanceof TRPCError) throw error;
|
||||
console.error("Nessa JWT verification failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Don't log the performance logging endpoint itself to avoid circular tracking
|
||||
if (!path.includes("analytics.logPerformance")) {
|
||||
@@ -111,8 +70,7 @@ async function createContextInner(event: APIEvent): Promise<Context> {
|
||||
return {
|
||||
event,
|
||||
userId,
|
||||
isAdmin,
|
||||
nessaUserId
|
||||
isAdmin
|
||||
};
|
||||
}
|
||||
|
||||
@@ -152,24 +110,8 @@ const enforceUserIsAdmin = t.middleware(({ ctx, next }) => {
|
||||
});
|
||||
});
|
||||
|
||||
const enforceNessaUser = t.middleware(({ ctx, next }) => {
|
||||
if (!ctx.nessaUserId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "Nessa authentication required"
|
||||
});
|
||||
}
|
||||
return next({
|
||||
ctx: {
|
||||
...ctx,
|
||||
nessaUserId: ctx.nessaUserId
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export const protectedProcedure = t.procedure.use(enforceUserIsAuthed);
|
||||
export const adminProcedure = t.procedure.use(enforceUserIsAdmin);
|
||||
export const nessaProcedure = t.procedure.use(enforceNessaUser);
|
||||
|
||||
// CSRF protection middleware - defined here to avoid circular dependency
|
||||
const csrfProtection = t.middleware(async ({ ctx, next }) => {
|
||||
|
||||
Reference in New Issue
Block a user