From 42757bc93dff70d456dccdba7ba7096aac0ae67c Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Wed, 22 Jul 2026 22:23:44 -0400 Subject: [PATCH] feat: add Nessa club events router (CRUD/RSVP/participants), suppress expected JWT verify logs, drop unused GOOGLE_CLIENT_ID env --- src/env/server.ts | 2 - src/server/api/routers/lineage/auth.ts | 112 ++++---- src/server/api/routers/nessa-community.ts | 328 ++++++++++++++++++++++ src/server/auth.ts | 19 +- 4 files changed, 403 insertions(+), 58 deletions(-) diff --git a/src/env/server.ts b/src/env/server.ts index 8f9812b..ad568ab 100644 --- a/src/env/server.ts +++ b/src/env/server.ts @@ -49,7 +49,6 @@ const serverEnvSchema = z.object({ VITE_DOWNLOAD_BUCKET_STRING: z.string().min(1), VITE_GOOGLE_CLIENT_ID: z.string().min(1), VITE_GOOGLE_CLIENT_ID_MAGIC_DELVE: z.string().min(1), - GOOGLE_CLIENT_ID: z.string().min(1), VITE_GITHUB_CLIENT_ID: z.string().min(1), VITE_WEBSOCKET: z.string().min(1), VITE_INFILL_ENDPOINT: z.string().min(1), @@ -164,7 +163,6 @@ export const getMissingEnvVars = (): string[] => { "VITE_DOWNLOAD_BUCKET_STRING", "VITE_GOOGLE_CLIENT_ID", "VITE_GOOGLE_CLIENT_ID_MAGIC_DELVE", - "GOOGLE_CLIENT_ID", "VITE_GITHUB_CLIENT_ID", "VITE_WEBSOCKET", "REDIS_URL", diff --git a/src/server/api/routers/lineage/auth.ts b/src/server/api/routers/lineage/auth.ts index 6d7855c..a5e03db 100644 --- a/src/server/api/routers/lineage/auth.ts +++ b/src/server/api/routers/lineage/auth.ts @@ -1,4 +1,8 @@ -import { createTRPCRouter, publicProcedure, csrfProtectedProcedure } from "../../utils"; +import { + createTRPCRouter, + publicProcedure, + csrfProtectedProcedure +} from "../../utils"; import { z } from "zod"; import { LineageConnectionFactory, @@ -6,7 +10,7 @@ import { hashPassword, checkPassword, sendEmailVerification, - LINEAGE_JWT_EXPIRY, + LINEAGE_JWT_EXPIRY } from "~/server/utils"; import { env } from "~/env/server"; import { LINEAGE_CONFIG } from "~/config"; @@ -20,7 +24,7 @@ export const lineageAuthRouter = createTRPCRouter({ .input( z.object({ email: z.string().email(), - password: z.string().min(8), + password: z.string().min(8) }) ) .mutation(async ({ input }) => { @@ -34,7 +38,7 @@ export const lineageAuthRouter = createTRPCRouter({ if (res.rows.length === 0) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "Invalid Credentials", + message: "Invalid Credentials" }); } @@ -43,7 +47,7 @@ export const lineageAuthRouter = createTRPCRouter({ if (user.email_verified === 0) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "Email not yet verified!", + message: "Email not yet verified!" }); } @@ -51,7 +55,7 @@ export const lineageAuthRouter = createTRPCRouter({ if (!valid) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "Invalid Credentials", + message: "Invalid Credentials" }); } @@ -70,7 +74,7 @@ export const lineageAuthRouter = createTRPCRouter({ success: true, message: "Login successful", token, - email, + email }; }), @@ -79,7 +83,7 @@ export const lineageAuthRouter = createTRPCRouter({ z.object({ email: z.string().email(), password: z.string().min(8), - password_conf: z.string().min(8), + password_conf: z.string().min(8) }) ) .mutation(async ({ input }) => { @@ -88,7 +92,7 @@ export const lineageAuthRouter = createTRPCRouter({ if (password !== password_conf) { throw new TRPCError({ code: "BAD_REQUEST", - message: "Password mismatch", + message: "Password mismatch" }); } @@ -107,12 +111,12 @@ export const lineageAuthRouter = createTRPCRouter({ if (emailResult.success && emailResult.messageId) { return { success: true, - message: "Email verification sent!", + message: "Email verification sent!" }; } else { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", - message: emailResult.message || "Failed to send verification email", + message: emailResult.message || "Failed to send verification email" }); } } catch (e) { @@ -120,13 +124,13 @@ export const lineageAuthRouter = createTRPCRouter({ if (e instanceof LibsqlError && e.code === "SQLITE_CONSTRAINT") { throw new TRPCError({ code: "BAD_REQUEST", - message: "User already exists", + message: "User already exists" }); } if (e instanceof TRPCError) throw e; throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", - message: "An error occurred while creating the user", + message: "An error occurred while creating the user" }); } }), @@ -135,7 +139,7 @@ export const lineageAuthRouter = createTRPCRouter({ .input( z.object({ email: z.string().email(), - token: z.string(), + token: z.string() }) ) .mutation(async ({ input }) => { @@ -152,13 +156,13 @@ export const lineageAuthRouter = createTRPCRouter({ const secret = new TextEncoder().encode(env.LINEAGE_JWT_SECRET); const { payload } = await jwtVerify(token, secret, { issuer: LINEAGE_CONFIG.JWT_ISSUER, - audience: LINEAGE_CONFIG.JWT_AUDIENCE, + audience: LINEAGE_CONFIG.JWT_AUDIENCE }); if (payload.email !== userEmail) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "Authentication failed: email mismatch", + message: "Authentication failed: email mismatch" }); } @@ -178,7 +182,7 @@ export const lineageAuthRouter = createTRPCRouter({ return { success: true, message: - "Email verification success. You may close this window and sign in within the app.", + "Email verification success. You may close this window and sign in within the app." }; } catch (err) { console.error("Error in email verification:", err); @@ -187,7 +191,7 @@ export const lineageAuthRouter = createTRPCRouter({ try { const turso = createAPIClient({ org: "mikefreno", - token: env.TURSO_DB_API_TOKEN, + token: env.TURSO_DB_API_TOKEN }); await turso.databases.delete(dbName); console.log(`Database ${dbName} deleted due to error`); @@ -200,7 +204,7 @@ export const lineageAuthRouter = createTRPCRouter({ try { await conn.execute({ sql: `UPDATE User SET email_verified = ?, database_name = ?, database_token = ? WHERE email = ?`, - args: [false, null, null, userEmail], + args: [false, null, null, userEmail] }); console.log("User table update reverted"); } catch (revertErr) { @@ -212,7 +216,7 @@ export const lineageAuthRouter = createTRPCRouter({ throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: - "Authentication failed: An error occurred during email verification. Please try again.", + "Authentication failed: An error occurred during email verification. Please try again." }); } }), @@ -230,7 +234,7 @@ export const lineageAuthRouter = createTRPCRouter({ if (res.rows.length === 0 || res.rows[0].email_verified) { throw new TRPCError({ code: "CONFLICT", - message: "Invalid Request", + message: "Invalid Request" }); } @@ -238,12 +242,12 @@ export const lineageAuthRouter = createTRPCRouter({ if (emailResult.success && emailResult.messageId) { return { success: true, - message: "Email verification sent!", + message: "Email verification sent!" }; } else { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", - message: emailResult.message || "Failed to send verification email", + message: emailResult.message || "Failed to send verification email" }); } }), @@ -257,12 +261,12 @@ export const lineageAuthRouter = createTRPCRouter({ const secret = new TextEncoder().encode(env.LINEAGE_JWT_SECRET); const { payload } = await jwtVerify(token, secret, { issuer: LINEAGE_CONFIG.JWT_ISSUER, - audience: LINEAGE_CONFIG.JWT_AUDIENCE, + audience: LINEAGE_CONFIG.JWT_AUDIENCE }); const newToken = await new SignJWT({ userId: payload.userId, - email: payload.email, + email: payload.email }) .setProtectedHeader({ alg: "HS256" }) .setIssuer(LINEAGE_CONFIG.JWT_ISSUER) @@ -275,12 +279,12 @@ export const lineageAuthRouter = createTRPCRouter({ ok: true, valid: true, token: newToken, - email: payload.email, + email: payload.email }; } catch (error) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "Invalid or expired token", + message: "Invalid or expired token" }); } }), @@ -296,7 +300,7 @@ export const lineageAuthRouter = createTRPCRouter({ const checkUserQuery = "SELECT * FROM User WHERE email = ?"; const checkUserResult = await conn.execute({ sql: checkUserQuery, - args: [email], + args: [email] }); if (checkUserResult.rows.length > 0) { @@ -307,18 +311,18 @@ export const lineageAuthRouter = createTRPCRouter({ `; const updateRes = await conn.execute({ sql: updateQuery, - args: ["google", email], + args: ["google", email] }); if (updateRes.rowsAffected !== 0) { return { success: true, - message: "User information updated", + message: "User information updated" }; } else { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", - message: "User update failed!", + message: "User update failed!" }); } } else { @@ -333,27 +337,27 @@ export const lineageAuthRouter = createTRPCRouter({ `; await conn.execute({ sql: insertQuery, - args: [email, true, "google", dbName, token], + args: [email, true, "google", dbName, token] }); console.log("insert success"); return { success: true, - message: "New user created", + message: "New user created" }; } catch (error) { if (db_name) { const turso = createAPIClient({ org: "mikefreno", - token: env.TURSO_DB_API_TOKEN, + token: env.TURSO_DB_API_TOKEN }); await turso.databases.delete(db_name); } console.error(error); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", - message: "Failed to create user", + message: "Failed to create user" }); } } @@ -362,7 +366,7 @@ export const lineageAuthRouter = createTRPCRouter({ if (error instanceof TRPCError) throw error; throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", - message: "An error occurred while processing the request", + message: "An error occurred while processing the request" }); } }), @@ -371,7 +375,7 @@ export const lineageAuthRouter = createTRPCRouter({ .input( z.object({ email: z.string().email().optional(), - idToken: z.string(), + idToken: z.string() }) ) .mutation(async ({ input }) => { @@ -384,7 +388,7 @@ export const lineageAuthRouter = createTRPCRouter({ if (!appleKeysResponse.ok) { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", - message: "Failed to fetch Apple public keys", + message: "Failed to fetch Apple public keys" }); } @@ -404,7 +408,7 @@ export const lineageAuthRouter = createTRPCRouter({ if (!headerB64) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "Invalid Apple ID token format", + message: "Invalid Apple ID token format" }); } const headerJson = Buffer.from(headerB64, "base64url").toString("utf8"); @@ -413,14 +417,14 @@ export const lineageAuthRouter = createTRPCRouter({ if (!jwk) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "Apple public key not found", + message: "Apple public key not found" }); } const publicKey = await importJWK(jwk, "RS256"); const jwtOptions: Parameters[2] = { algorithms: ["RS256"], - issuer: "https://appleid.apple.com", + issuer: "https://appleid.apple.com" }; if (env.APPLE_CLIENT_ID) { jwtOptions.audience = env.APPLE_CLIENT_ID; @@ -441,14 +445,14 @@ export const lineageAuthRouter = createTRPCRouter({ try { let checkUserQuery = "SELECT * FROM User WHERE apple_user_string = ?"; - let args: string[] = [userString]; + const args: string[] = [userString]; if (email) { args.push(email); checkUserQuery += " OR email = ?"; } const checkUserResult = await conn.execute({ sql: checkUserQuery, - args: args, + args: args }); if (checkUserResult.rows.length > 0) { @@ -474,19 +478,19 @@ export const lineageAuthRouter = createTRPCRouter({ )} ${whereClause}`; const updateRes = await conn.execute({ sql: updateQuery, - args: values, + args: values }); if (updateRes.rowsAffected !== 0) { return { success: true, message: "User information updated", - email: checkUserResult.rows[0].email as string, + email: checkUserResult.rows[0].email as string }; } else { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", - message: "User update failed!", + message: "User update failed!" }); } } else { @@ -501,27 +505,27 @@ export const lineageAuthRouter = createTRPCRouter({ `; await conn.execute({ sql: insertQuery, - args: [email, true, userString, "apple", dbName, dbToken], + args: [email, true, userString, "apple", dbName, dbToken] }); return { success: true, message: "New user created", dbName, - dbToken, + dbToken }; } catch (error) { if (dbName) { const turso = createAPIClient({ org: "mikefreno", - token: env.TURSO_DB_API_TOKEN, + token: env.TURSO_DB_API_TOKEN }); await turso.databases.delete(dbName); } console.error(error); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", - message: "Failed to create user", + message: "Failed to create user" }); } } @@ -530,7 +534,7 @@ export const lineageAuthRouter = createTRPCRouter({ try { const turso = createAPIClient({ org: "mikefreno", - token: env.TURSO_DB_API_TOKEN, + token: env.TURSO_DB_API_TOKEN }); await turso.databases.delete(dbName); } catch (deleteErr) { @@ -541,7 +545,7 @@ export const lineageAuthRouter = createTRPCRouter({ if (error instanceof TRPCError) throw error; throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", - message: "An error occurred while processing the request", + message: "An error occurred while processing the request" }); } }), @@ -560,8 +564,8 @@ export const lineageAuthRouter = createTRPCRouter({ } else { throw new TRPCError({ code: "NOT_FOUND", - message: "User not found", + message: "User not found" }); } - }), + }) }); diff --git a/src/server/api/routers/nessa-community.ts b/src/server/api/routers/nessa-community.ts index e6471cf..468b743 100644 --- a/src/server/api/routers/nessa-community.ts +++ b/src/server/api/routers/nessa-community.ts @@ -1272,5 +1272,333 @@ export const nessaCommunityRouter = createTRPCRouter({ }); } }) + }), + + // ========================================================================== + // Events (clubEvents) + // ========================================================================== + events: createTRPCRouter({ + list: nessaProcedure + .input(paginationSchema.extend({ + clubId: z.string().min(1).optional(), + eventType: z.string().optional(), + startDate: z.string().optional(), + endDate: z.string().optional(), + location: z.string().optional(), + rsvpStatus: z.enum(["going", "maybe", "not-going"]).optional() + })) + .query(async ({ input, ctx }) => { + const limit = input.limit ?? 50; + const offset = input.offset ?? 0; + + try { + const conn = NessaConnectionFactory(); + const where: string[] = []; + const args: (string | number)[] = []; + + if (input.clubId) { + where.push("e.clubId = ?"); + args.push(input.clubId); + } + if (input.eventType) { + where.push("e.eventType = ?"); + args.push(input.eventType); + } + if (input.startDate) { + where.push("e.startDate >= ?"); + args.push(input.startDate); + } + if (input.endDate) { + where.push("e.startDate <= ?"); + args.push(input.endDate); + } + if (input.location) { + where.push("(e.location LIKE ?)"); + args.push(`%${input.location}%`); + } + + const whereClause = where.length + ? `WHERE ${where.join(" AND ")}` + : ""; + args.push(limit, offset); + + const result = await conn.execute({ + sql: `SELECT e.id, e.clubId, e.title, e.description, e.eventType, + e.location, e.latitude, e.longitude, e.startDate, e.endDate, + e.createdBy, e.maxParticipants, e.participantCount, + e.createdAt, e.updatedAt, + u.displayName AS creatorDisplayName, u.avatarUrl AS creatorAvatarUrl, + (SELECT COUNT(*) FROM clubEventRSVPs WHERE eventId = e.id) AS rsvpCount, + (SELECT status FROM clubEventRSVPs WHERE eventId = e.id AND userId = ?) AS userRsvpStatus + FROM clubEvents e + JOIN users u ON e.createdBy = u.id + ${whereClause} + ORDER BY e.startDate ASC LIMIT ? OFFSET ?`, + args: [...args, ctx.nessaUserId] + }); + + return { events: result.rows }; + } catch (error) { + if (error instanceof TRPCError) throw error; + console.error("Failed to list Nessa events:", error); + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to list events" + }); + } + }), + + get: nessaProcedure + .input(idSchema) + .query(async ({ input, ctx }) => { + try { + const conn = NessaConnectionFactory(); + const result = await conn.execute({ + sql: `SELECT e.id, e.clubId, e.title, e.description, e.eventType, + e.location, e.latitude, e.longitude, e.startDate, e.endDate, + e.createdBy, e.maxParticipants, e.participantCount, + e.createdAt, e.updatedAt, + u.displayName AS creatorDisplayName, u.avatarUrl AS creatorAvatarUrl, + (SELECT COUNT(*) FROM clubEventRSVPs WHERE eventId = e.id) AS rsvpCount, + (SELECT status FROM clubEventRSVPs WHERE eventId = e.id AND userId = ?) AS userRsvpStatus + FROM clubEvents e + JOIN users u ON e.createdBy = u.id + WHERE e.id = ?`, + args: [ctx.nessaUserId, input.id] + }); + if (!result.rows.length) { + throw new TRPCError({ code: "NOT_FOUND", message: "Event not found" }); + } + return { event: result.rows[0] }; + } catch (error) { + if (error instanceof TRPCError) throw error; + console.error("Failed to get Nessa event:", error); + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to get event" + }); + } + }), + + create: nessaProcedure + .input(z.object({ + clubId: z.string().min(1), + title: z.string().min(1).max(200), + description: z.string().max(2000).nullable().optional(), + eventType: z.string().min(1), + location: z.string().nullable().optional(), + latitude: z.number().nullable().optional(), + longitude: z.number().nullable().optional(), + startDate: z.string().min(1), + endDate: z.string().nullable().optional(), + maxParticipants: z.number().int().min(1).nullable().optional() + })) + .mutation(async ({ input, ctx }) => { + try { + const conn = NessaConnectionFactory(); + await requireClubMembership(conn, input.clubId, ctx.nessaUserId); + + const eventId = crypto.randomUUID(); + await conn.execute({ + sql: `INSERT INTO clubEvents + (id, clubId, title, description, eventType, location, latitude, longitude, + startDate, endDate, createdBy, maxParticipants) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + args: [ + eventId, + input.clubId, + input.title, + input.description ?? null, + input.eventType, + input.location ?? null, + input.latitude ?? null, + input.longitude ?? null, + input.startDate, + input.endDate ?? null, + ctx.nessaUserId, + input.maxParticipants ?? null + ] + }); + + return { success: true, eventId }; + } catch (error) { + if (error instanceof TRPCError) throw error; + console.error("Failed to create Nessa event:", error); + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to create event" + }); + } + }), + + update: nessaProcedure + .input(z.object({ + id: z.string().min(1), + title: z.string().min(1).max(200).optional(), + description: z.string().max(2000).nullable().optional(), + eventType: z.string().min(1).optional(), + location: z.string().nullable().optional(), + latitude: z.number().nullable().optional(), + longitude: z.number().nullable().optional(), + startDate: z.string().min(1).optional(), + endDate: z.string().nullable().optional(), + maxParticipants: z.number().int().min(1).nullable().optional() + })) + .mutation(async ({ input, ctx }) => { + try { + const conn = NessaConnectionFactory(); + + const ownerCheck = await conn.execute({ + sql: "SELECT createdBy FROM clubEvents WHERE id = ?", + args: [input.id] + }); + if (!ownerCheck.rows.length) { + throw new TRPCError({ code: "NOT_FOUND", message: "Event not found" }); + } + if ((ownerCheck.rows[0] as unknown as { createdBy: string }).createdBy !== ctx.nessaUserId) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "Only the creator can update the event" + }); + } + + const fields: string[] = []; + const args: (string | number | null)[] = []; + const map: Record = { + title: "title", + description: "description", + eventType: "eventType", + location: "location", + latitude: "latitude", + longitude: "longitude", + startDate: "startDate", + endDate: "endDate", + maxParticipants: "maxParticipants" + }; + for (const [key, col] of Object.entries(map)) { + if ((input as Record)[key] !== undefined) { + fields.push(`${col} = ?`); + args.push((input as Record)[key] as string | number | null); + } + } + if (fields.length === 0) { + return { success: true }; + } + fields.push("updatedAt = datetime('now')"); + args.push(input.id); + + await conn.execute({ + sql: `UPDATE clubEvents SET ${fields.join(", ")} WHERE id = ?`, + args + }); + return { success: true }; + } catch (error) { + if (error instanceof TRPCError) throw error; + console.error("Failed to update Nessa event:", error); + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to update event" + }); + } + }), + + delete: nessaProcedure + .input(idSchema) + .mutation(async ({ input, ctx }) => { + try { + const conn = NessaConnectionFactory(); + const ownerCheck = await conn.execute({ + sql: "SELECT createdBy FROM clubEvents WHERE id = ?", + args: [input.id] + }); + if (!ownerCheck.rows.length) { + throw new TRPCError({ code: "NOT_FOUND", message: "Event not found" }); + } + if ((ownerCheck.rows[0] as unknown as { createdBy: string }).createdBy !== ctx.nessaUserId) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "Only the creator can delete the event" + }); + } + await conn.execute({ + sql: "DELETE FROM clubEvents WHERE id = ?", + args: [input.id] + }); + return { success: true }; + } catch (error) { + if (error instanceof TRPCError) throw error; + console.error("Failed to delete Nessa event:", error); + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to delete event" + }); + } + }), + + rsvp: nessaProcedure + .input(z.object({ + eventId: z.string().min(1), + status: z.enum(["going", "maybe", "not-going"]).default("going") + })) + .mutation(async ({ input, ctx }) => { + try { + const conn = NessaConnectionFactory(); + + const event = await conn.execute({ + sql: "SELECT clubId FROM clubEvents WHERE id = ?", + args: [input.eventId] + }); + if (!event.rows.length) { + throw new TRPCError({ code: "NOT_FOUND", message: "Event not found" }); + } + const clubId = (event.rows[0] as unknown as { clubId: string }).clubId; + await requireClubMembership(conn, clubId, ctx.nessaUserId); + + // Delete existing RSVP if any + await conn.execute({ + sql: "DELETE FROM clubEventRSVPs WHERE eventId = ? AND userId = ?", + args: [input.eventId, ctx.nessaUserId] + }); + + await conn.execute({ + sql: "INSERT INTO clubEventRSVPs (id, eventId, userId, status) VALUES (?, ?, ?, ?)", + args: [crypto.randomUUID(), input.eventId, ctx.nessaUserId, input.status] + }); + + return { success: true, eventId: input.eventId, userId: ctx.nessaUserId, status: input.status }; + } catch (error) { + if (error instanceof TRPCError) throw error; + console.error("Failed to RSVP to Nessa event:", error); + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to RSVP to event" + }); + } + }), + + participants: nessaProcedure + .input(idSchema) + .query(async ({ input }) => { + try { + const conn = NessaConnectionFactory(); + const result = await conn.execute({ + sql: `SELECT r.id, r.eventId, r.userId, r.status, r.createdAt, + u.firstName, u.lastName, u.displayName, u.avatarUrl + FROM clubEventRSVPs r + JOIN users u ON r.userId = u.id + WHERE r.eventId = ? + ORDER BY r.createdAt ASC`, + args: [input.id] + }); + return { participants: result.rows }; + } catch (error) { + if (error instanceof TRPCError) throw error; + console.error("Failed to list event participants:", error); + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to list participants" + }); + } + }) }) }); diff --git a/src/server/auth.ts b/src/server/auth.ts index d2ac3e8..14b31bb 100644 --- a/src/server/auth.ts +++ b/src/server/auth.ts @@ -5,7 +5,12 @@ import type { Row } from "@libsql/client/web"; import { SignJWT, jwtVerify } from "jose"; import { env } from "~/env/server"; import { ConnectionFactory } from "./db-connections"; -import { AUTH_CONFIG, LINEAGE_CONFIG, expiryToSeconds, getAccessTokenExpiry } from "~/config"; +import { + AUTH_CONFIG, + LINEAGE_CONFIG, + expiryToSeconds, + getAccessTokenExpiry +} from "~/config"; export const authCookieName = "auth_token"; @@ -65,7 +70,17 @@ export async function verifyAuthToken( exp: payload.exp }; } catch (error) { - console.error("Auth token verification failed:", error); + // Signature mismatch, expired token, malformed JWT — these are expected + // when a user has a stale/invalid cookie and are NOT server errors. Only + // log unexpected failures to keep prod console noise-free. + const code = (error as { code?: string }).code; + if ( + code !== "ERR_JWS_SIGNATURE_VERIFICATION_FAILED" && + code !== "ERR_JWT_EXPIRED" && + code !== "ERR_JWT_MALFORMED" + ) { + console.error("Auth token verification failed:", error); + } return null; } }