Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/firebasedeploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ jobs:
- name: Deploy to Firebase Hosting
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
# repoToken is removed to prevent PR comments
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_DSGT }}
projectId: dsgt-website
channelId: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'live' }}
entryPoint: sites/mainweb
entryPoint: sites/mainweb
43 changes: 8 additions & 35 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,42 +1,15 @@
version: '3.8'

services:
postgres:
image: postgres:15-alpine
container_name: cerebra-postgres
restart: unless-stopped
db:
image: postgres:15
container_name: monorepo-postgres
ports:
- "5433:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: cerebra_dev
POSTGRES_HOST_AUTH_METHOD: trust
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5

pgadmin:
image: dpage/pgadmin4:latest
container_name: cerebra-pgadmin
restart: unless-stopped
environment:
PGADMIN_DEFAULT_EMAIL: admin@cerebra.local
PGADMIN_DEFAULT_PASSWORD: admin
PGADMIN_LISTEN_PORT: 80
ports:
- "5050:80"
POSTGRES_DB: app_db
volumes:
- pgadmin_data:/var/lib/pgadmin
depends_on:
- postgres
- pgdata:/var/lib/postgresql/data

volumes:
postgres_data:
driver: local
pgadmin_data:
driver: local
pgdata:
20 changes: 10 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "query",
"private": true,
"version": "1.0.0",
"packageManager": "pnpm@10.25.0",
"packageManager": "pnpm@10.26.1",
"engines": {
"node": ">=20.16.0",
"pnpm": ">=9"
Expand Down Expand Up @@ -30,30 +30,30 @@
"@tailwindcss/postcss": "^4.1.18",
"@tanstack/react-query": "^5.90.12",
"@trpc/client": "^11.7.2",
"@trpc/next": "^11.7.2",
"@trpc/react-query": "^11.7.2",
"@trpc/server": "^11.7.2",
"@trpc/server": "^11.8.0",
"@turbo/gen": "^2.1.3",
"@types/minimatch": "^6.0.0",
"prettier": "^3.3.3",
"turbo": "^2.6.3",
"typescript": "^5.6.3",
"zod": "^3.23.8"
},
"dependencies": {
"@tanstack/react-router": "^1.141.2",
"autoprefixer": "^10.4.22",
"chart.js": "^4.5.1",
"postcss": "^8.5.6"
},
"prettier": "@query/prettier-config",
"pnpm": {
"overrides": {
"next": "15.5.9",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.1",
"@types/node": "^22.10.1"
}
},
"dependencies": {
"@tanstack/react-router": "^1.141.2",
"autoprefixer": "^10.4.22",
"chart.js": "^4.5.1",
"postcss": "^8.5.6"
}
}
}
15 changes: 15 additions & 0 deletions packages/api/src/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { auth } from "@query/auth";
import { db } from "@query/db";
import type { FetchCreateContextFnOptions } from "@trpc/server/adapters/fetch";

export async function createContext(opts?: FetchCreateContextFnOptions) {
const session = await auth();

return {
db,
session,
userId: session?.user?.id,
};
}

export type Context = Awaited<ReturnType<typeof createContext>>;
7 changes: 3 additions & 4 deletions packages/api/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// packages/api/src/index.ts
export { appRouter, createContext } from './root';
export type { AppRouter, Context } from './root';
export { trpc, createTRPCRouter, publicProcedure } from './trpc';
export { appRouter, type AppRouter } from "./root";
export { createContext, type Context } from "./context";
export { createTRPCRouter, publicProcedure, protectedProcedure } from "./trpc";
102 changes: 102 additions & 0 deletions packages/api/src/middleware/security.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { TRPCError } from "@trpc/server";

const rateLimitStore = new Map<string, { count: number; resetAt: number }>();

setInterval(() => {
const now = Date.now();
for (const [key, value] of rateLimitStore.entries()) {
if (now > value.resetAt) {
rateLimitStore.delete(key);
}
}
}, 5 * 60 * 1000);

export function rateLimit(identifier: string, maxRequests: number, windowMs: number): boolean {
const now = Date.now();
const record = rateLimitStore.get(identifier);

if (!record || now > record.resetAt) {
rateLimitStore.set(identifier, {
count: 1,
resetAt: now + windowMs,
});
return true;
}

if (record.count >= maxRequests) {
return false;
}

record.count++;
return true;
}

export function sanitizeInput(input: any): any {
if (input === null || input === undefined) {
return input;
}

if (typeof input === 'string') {
return input
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/javascript:/gi, '')
.replace(/on\w+\s*=/gi, '')
.replace(/data:text\/html/gi, '')
.replace(/<iframe/gi, '')
.replace(/<embed/gi, '')
.replace(/<object/gi, '')
.trim()
.slice(0, 10000);
}

if (Array.isArray(input)) {
if (input.length > 1000) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Array too large",
});
}
return input.map(sanitizeInput);
}

if (typeof input === 'object') {
const keys = Object.keys(input);
if (keys.length > 100) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Object too complex",
});
}

const sanitized: any = {};
for (const [key, value] of Object.entries(input)) {
// Sanitize keys too
const cleanKey = key.replace(/[^\w\-]/g, '').slice(0, 100);
if (cleanKey) {
sanitized[cleanKey] = sanitizeInput(value);
}
}
return sanitized;
}

return input;
}

export function validateEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email) && email.length <= 254;
}

export function validateUrl(url: string): boolean {
try {
const parsed = new URL(url);
return ['http:', 'https:'].includes(parsed.protocol);
} catch {
return false;
}
}

export function validateUUID(uuid: string): boolean {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
return uuidRegex.test(uuid);
}
22 changes: 10 additions & 12 deletions packages/api/src/root.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import { createTRPCRouter } from './trpc';
import { helloRouter } from './routers/hello';
import { createTRPCRouter } from "./trpc";
import { helloRouter } from "./routers/hello";
import { userRouter } from "./routers/user";
import { adminRouter } from "./routers/admin";
import { memberRouter } from "./routers/member";
import { hackathonRouter } from "./routers/hackathon";

// Context type
export type Context = {};

// Context creator
export const createContext = async (): Promise<Context> => {
return {};
};

// Root app router
export const appRouter = createTRPCRouter({
hello: helloRouter,
user: userRouter,
admin: adminRouter,
member: memberRouter,
hackathon: hackathonRouter,
});

// Export API type
export type AppRouter = typeof appRouter;
Loading
Loading