Skip to content

Commit 51c3b85

Browse files
authored
Merge pull request #61 from DataScience-GT/refactor/routes
Refactor/routes
2 parents 87a4c0a + e8fd971 commit 51c3b85

File tree

30 files changed

+1067
-252
lines changed

30 files changed

+1067
-252
lines changed

.env.cloudrun.example

Lines changed: 0 additions & 27 deletions
This file was deleted.

.env.example

Lines changed: 0 additions & 92 deletions
This file was deleted.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,4 @@ migrations/
5656
.claude/settings.local.json
5757
packages/db/scripts/seed.ts
5858
packages/db/scripts/seed2.ts
59+
scripts/seed-stripe.ts

apphosting.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,13 @@ env:
2929
secret: AUTH_GOOGLE_SECRET
3030
- variable: AUTH_URL
3131
value: https://query--dsgt-website.us-east4.hosted.app
32+
# Stripe Configuration
33+
- variable: STRIPE_SECRET_KEY
34+
# Project Number identifier (safe to commit) used for robust resolution
35+
secret: projects/672446353769/secrets/STRIPE_SECRET_KEY
36+
- variable: NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
37+
secret: projects/672446353769/secrets/NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
38+
- variable: STRIPE_WEBHOOK_SECRET
39+
secret: projects/672446353769/secrets/STRIPE_WEBHOOK_SECRET
3240
- variable: NODE_ENV
3341
value: production

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@
2323
"typecheck": "turbo run typecheck"
2424
},
2525
"dependencies": {
26+
"@query/db": "workspace:*",
2627
"@tanstack/react-router": "^1.141.2",
2728
"autoprefixer": "^10.4.22",
2829
"chart.js": "^4.5.1",
30+
"csv-parse": "^6.1.0",
2931
"minimatch": "^10.1.1",
30-
"postcss": "^8.5.6",
3132
"next": "^16.0.0",
33+
"postcss": "^8.5.6",
3234
"react": "^19.0.0",
3335
"react-dom": "^19.0.0"
3436
},

packages/api/src/middleware/security.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,11 +260,66 @@ export type SecurityEvent = {
260260
const securityLog: SecurityEvent[] = [];
261261
const MAX_LOG_SIZE = 1000;
262262

263+
import { db, auditLogs } from "@query/db";
264+
265+
const flushQueue: Omit<SecurityEvent, 'timestamp'>[] = [];
266+
const FLUSH_INTERVAL = 5000;
267+
const MAX_BATCH_SIZE = 50;
268+
let flushTimer: NodeJS.Timeout | null = null;
269+
270+
async function flushLogs() {
271+
if (flushQueue.length === 0) return;
272+
273+
const batch = flushQueue.splice(0, MAX_BATCH_SIZE);
274+
275+
if (!db) {
276+
console.warn(`[Security] DB unavailable, dropping ${batch.length} logs`);
277+
return;
278+
}
279+
280+
try {
281+
const values = batch.map(event => {
282+
const safeDetails = event.details ? event.details.replace(/(password|token|secret)=[^&]*/gi, '$1=***') : undefined;
283+
const severity = event.type === 'injection_attempt' ? 'critical' :
284+
event.type === 'auth_failure' ? 'warn' : 'info';
285+
286+
return {
287+
action: event.type,
288+
userId: event.identifier.startsWith('user:') ? event.identifier.split(':')[1] : null,
289+
resourceId: event.identifier,
290+
metadata: { details: safeDetails },
291+
severity,
292+
};
293+
});
294+
295+
await db.insert(auditLogs).values(values);
296+
} catch (err) {
297+
console.error(`[Security] Failed to flush ${batch.length} logs:`, err);
298+
// In a real system, might want to re-queue, but for now we drop to avoid endless growth
299+
}
300+
}
301+
302+
// Start flush timer
303+
if (!flushTimer) {
304+
flushTimer = setInterval(() => {
305+
void flushLogs();
306+
}, FLUSH_INTERVAL);
307+
}
308+
263309
export function logSecurityEvent(event: Omit<SecurityEvent, 'timestamp'>) {
310+
// Keep in-memory for immediate/short-term checks
264311
securityLog.push({ ...event, timestamp: Date.now() });
265312
if (securityLog.length > MAX_LOG_SIZE) {
266313
securityLog.shift();
267314
}
315+
316+
// Queue for DB persistence
317+
flushQueue.push(event);
318+
319+
// Instant flush if critical or queue full
320+
if (event.type === 'injection_attempt' || flushQueue.length >= MAX_BATCH_SIZE) {
321+
void flushLogs();
322+
}
268323
}
269324

270325
export function getRecentSecurityEvents(minutes: number = 60): SecurityEvent[] {

packages/api/src/root.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { hackathonRouter } from "./routers/hackathon";
77
import { eventRouter } from "./routers/events";
88
import { judgeRouter } from "./routers/judge";
99
import { stripeRouter } from "./routers/stripe";
10+
import { auditRouter } from "./routers/audit";
1011

1112
export const appRouter = createTRPCRouter({
1213
hello: helloRouter,
@@ -17,6 +18,7 @@ export const appRouter = createTRPCRouter({
1718
events: eventRouter,
1819
judge: judgeRouter,
1920
stripe: stripeRouter,
21+
audit: auditRouter,
2022
});
2123

2224
export type AppRouter = typeof appRouter;

packages/api/src/routers/admin.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ export const adminRouter = createTRPCRouter({
3535
}),
3636

3737
list: isAdmin.query(async ({ ctx }) => {
38+
const cacheKey = `admins:list`;
39+
const cached = ctx.cache.get<typeof allAdmins>(cacheKey);
40+
if (cached) return cached;
41+
3842
const allAdmins = await ctx.db!.query.admins.findMany({
3943
with: {
4044
user: {
@@ -50,6 +54,8 @@ export const adminRouter = createTRPCRouter({
5054
limit: 100,
5155
});
5256

57+
ctx.cache.set(cacheKey, allAdmins, 60);
58+
5359
return allAdmins;
5460
}),
5561

packages/api/src/routers/audit.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { z } from "zod";
2+
import { createTRPCRouter } from "../trpc";
3+
import { auditLogs, securitySeverityEnum } from "@query/db";
4+
import { isAdmin } from "../middleware/procedures";
5+
import { desc, eq, and, sql } from "drizzle-orm";
6+
7+
export const auditRouter = createTRPCRouter({
8+
list: isAdmin
9+
.input(
10+
z.object({
11+
limit: z.number().min(1).max(100).default(50),
12+
offset: z.number().min(0).default(0),
13+
severity: z.enum(["info", "warn", "critical"]).optional(),
14+
userId: z.string().optional(),
15+
})
16+
)
17+
.query(async ({ ctx, input }) => {
18+
const filters = and(
19+
input.severity ? eq(auditLogs.severity, input.severity) : undefined,
20+
input.userId ? eq(auditLogs.userId, input.userId) : undefined
21+
);
22+
23+
const logs = await ctx.db!.query.auditLogs.findMany({
24+
where: filters,
25+
orderBy: [desc(auditLogs.createdAt)],
26+
limit: input.limit,
27+
offset: input.offset,
28+
});
29+
30+
const totalResult = await ctx.db!
31+
.select({ count: sql<number>`count(*)` })
32+
.from(auditLogs)
33+
.where(filters);
34+
35+
const total = Number(totalResult[0]?.count || 0);
36+
37+
return {
38+
logs,
39+
pagination: {
40+
total,
41+
limit: input.limit,
42+
offset: input.offset,
43+
hasMore: input.offset + input.limit < total,
44+
}
45+
};
46+
}),
47+
});

packages/api/src/routers/hackathon.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,10 @@ export const hackathonRouter = createTRPCRouter({
261261
participants: publicProcedure
262262
.input(z.object({ hackathonId: z.string().uuid("Invalid hackathon ID") }))
263263
.query(async ({ ctx, input }) => {
264+
const cacheKey = `hackathon:${input.hackathonId}:participants`;
265+
const cached = ctx.cache.get<typeof participants>(cacheKey);
266+
if (cached) return cached;
267+
264268
const participants = await ctx.db!.query.hackathonParticipants.findMany({
265269
where: eq(hackathonParticipants.hackathonId, input.hackathonId),
266270
with: {
@@ -275,6 +279,8 @@ export const hackathonRouter = createTRPCRouter({
275279
},
276280
});
277281

282+
ctx.cache.set(cacheKey, participants, 60);
283+
278284
return participants;
279285
}),
280286

@@ -341,6 +347,10 @@ export const hackathonRouter = createTRPCRouter({
341347
projects: publicProcedure
342348
.input(z.object({ hackathonId: z.string().uuid("Invalid hackathon ID") }))
343349
.query(async ({ ctx, input }) => {
350+
const cacheKey = `hackathon:${input.hackathonId}:projects`;
351+
const cached = ctx.cache.get<typeof projects>(cacheKey);
352+
if (cached) return cached;
353+
344354
const projects = await ctx.db!.query.hackathonProjects.findMany({
345355
where: eq(hackathonProjects.hackathonId, input.hackathonId),
346356
with: {
@@ -363,6 +373,8 @@ export const hackathonRouter = createTRPCRouter({
363373
orderBy: (hackathonProjects, { desc }) => [desc(hackathonProjects.submittedAt)],
364374
});
365375

376+
ctx.cache.set(cacheKey, projects, 120);
377+
366378
return projects;
367379
}),
368380
});

0 commit comments

Comments
 (0)