172 lines
8.6 KiB
Markdown
172 lines
8.6 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Project Overview
|
|
|
|
**MOPC (Monaco Ocean Protection Challenge)** — a secure jury voting platform for managing multi-round project selection. Jury members evaluate ocean conservation projects through configurable competitions with rounds (intake, filtering, evaluation, submission, mentoring, live finals, deliberation). Domain: `monaco-opc.com`.
|
|
|
|
## Common Commands
|
|
|
|
```bash
|
|
# Development
|
|
npm run dev # Next.js dev (Turbopack)
|
|
npm run build # Production build (always test before push)
|
|
npm run typecheck # tsc --noEmit
|
|
|
|
# Database
|
|
npx prisma generate # Regenerate client after schema changes
|
|
npx prisma migrate dev # Create + apply migration
|
|
npx prisma studio # GUI database browser
|
|
npm run db:seed # Run seed (tsx prisma/seed.ts)
|
|
|
|
# Testing (vitest, not jest)
|
|
npx vitest # Run all tests (watch mode)
|
|
npx vitest run # Run all tests once
|
|
npx vitest run tests/unit/round-engine.test.ts # Single file
|
|
npx vitest run -t 'test name' # Single test by name
|
|
|
|
# Code Quality
|
|
npm run lint # ESLint
|
|
npm run format # Prettier
|
|
|
|
# Docker (production)
|
|
docker compose -f docker/docker-compose.yml up -d
|
|
docker compose -f docker/docker-compose.dev.yml up -d # Dev stack
|
|
```
|
|
|
|
## Tech Stack
|
|
|
|
Next.js 15 (App Router) | TypeScript 5 (strict) | Tailwind CSS 4 | shadcn/ui | tRPC 11 (superjson) | Prisma 6 | PostgreSQL 16 | NextAuth 5 (Auth.js) | Vitest 4 | OpenAI SDK 6 | MinIO (S3) | Nodemailer + Poste.io
|
|
|
|
## Architecture
|
|
|
|
### Data Flow: Prisma → tRPC → React
|
|
|
|
End-to-end type safety: `prisma/schema.prisma` defines models → `src/server/routers/*.ts` expose tRPC procedures with Zod validation → `src/lib/trpc/client.ts` provides typed React hooks → components call `trpc.domain.procedure.useQuery()`.
|
|
|
|
### tRPC Middleware Hierarchy (`src/server/trpc.ts`)
|
|
|
|
All role-based access is enforced via procedure types:
|
|
|
|
| Procedure | Roles Allowed |
|
|
|-----------|---------------|
|
|
| `publicProcedure` | Anyone (no auth) |
|
|
| `protectedProcedure` | Any authenticated user |
|
|
| `adminProcedure` | SUPER_ADMIN, PROGRAM_ADMIN |
|
|
| `superAdminProcedure` | SUPER_ADMIN only |
|
|
| `juryProcedure` | JURY_MEMBER only |
|
|
| `mentorProcedure` | SUPER_ADMIN, PROGRAM_ADMIN, MENTOR |
|
|
| `observerProcedure` | SUPER_ADMIN, PROGRAM_ADMIN, OBSERVER |
|
|
| `awardMasterProcedure` | SUPER_ADMIN, PROGRAM_ADMIN, AWARD_MASTER |
|
|
| `audienceProcedure` | Any authenticated user |
|
|
|
|
### Route Groups (Next.js App Router)
|
|
|
|
- `src/app/(auth)/` — Public auth pages (login, verify, accept-invite, onboarding)
|
|
- `src/app/(admin)/` — Admin dashboard, competition management, round config
|
|
- `src/app/(jury)/` — Jury evaluation interface, round assignments, live voting
|
|
- `src/app/(applicant)/` — Applicant dashboard, competition progress, document uploads
|
|
- `src/app/(mentor)/` — Mentor workspace
|
|
|
|
### Competition System
|
|
|
|
The core domain model. A **Competition** represents a complete evaluation cycle:
|
|
|
|
```
|
|
Competition → has many Rounds (ordered)
|
|
```
|
|
|
|
- **Competition** (`src/server/routers/competition.ts`): Top-level competition config with program link, status, and settings
|
|
- **Round** (`RoundType: INTAKE | FILTERING | EVALUATION | SUBMISSION | MENTORING | LIVE_FINAL | DELIBERATION`): Each competition has ordered rounds with type-specific config in `configJson`
|
|
|
|
Key models:
|
|
- `JuryGroup` — Named jury panel with chair/member/observer roles
|
|
- `AdvancementRule` — Auto-advance, score threshold, top-N, admin selection between rounds
|
|
- `SubmissionWindow` — File submission deadlines per round
|
|
- `AssignmentPolicy` / `AssignmentIntent` — Governance layer for jury assignment
|
|
- `ProjectRoundState` — Per-project state within each round
|
|
- `DeliberationSession` / `DeliberationVote` / `ResultLock` — Structured deliberation and result finalization
|
|
|
|
Key services:
|
|
- `src/server/services/round-engine.ts` — State machine for round transitions
|
|
- `src/server/services/round-assignment.ts` — Jury assignment generation with policy enforcement
|
|
- `src/server/services/submission-manager.ts` — File submission + filtering with duplicate detection
|
|
- `src/server/services/deliberation.ts` — Deliberation session management and vote tallying
|
|
- `src/server/services/result-lock.ts` — Result finalization and unlock governance
|
|
- `src/server/services/live-control.ts` — Live ceremony cursor management
|
|
- `src/server/services/competition-context.ts` — Cross-cutting competition context resolver
|
|
|
|
Competition types: `src/types/competition.ts`, `src/types/competition-configs.ts`
|
|
|
|
### AI Services (`src/server/services/ai-*.ts`)
|
|
|
|
All AI calls anonymize data before sending to OpenAI. Services:
|
|
- `ai-filtering.ts` — AI-powered project screening with rubric
|
|
- `ai-assignment.ts` — GPT-suggested jury-project matching
|
|
- `ai-evaluation-summary.ts` — Strengths/weaknesses synthesis from evaluations
|
|
- `ai-tagging.ts` — Auto-tagging projects
|
|
- `ai-award-eligibility.ts` — Award eligibility assessment
|
|
- `ai-shortlist.ts` — AI-powered shortlist recommendations
|
|
- `anonymization.ts` — Strips PII before AI calls
|
|
|
|
### Auth System
|
|
|
|
NextAuth v5 with two providers: **Email** (magic links) and **Credentials** (password + invite token). Failed login tracking with 5-attempt lockout (15 min). Session includes `user.role` for RBAC.
|
|
|
|
### Docker Entrypoint (`docker/docker-entrypoint.sh`)
|
|
|
|
On container start: retry `prisma migrate deploy` → `prisma generate` → auto-seed if User table is empty → `node server.js`. A `docker compose down -v && docker compose up -d` will run all migrations from scratch and seed.
|
|
|
|
## Testing Infrastructure
|
|
|
|
- **Framework**: Vitest 4 with `fileParallelism: false` and `pool: 'forks'` (tests run sequentially)
|
|
- **Setup**: `tests/setup.ts` provides `prisma` client (uses `DATABASE_URL_TEST` or `DATABASE_URL`), `createTestContext(user)`, `createCaller(router, user)`
|
|
- **Factories**: `tests/helpers.ts` has `createTestUser()`, `createTestProgram()`, `createTestCompetition()`, `createTestRound()`, `uid()` helper, and `cleanupTestData()`
|
|
- **Pattern**: Create data with factories → build caller with `createCaller(routerModule, user)` → call procedures → assert → cleanup in `afterAll`
|
|
|
|
## User Roles
|
|
|
|
`SUPER_ADMIN` | `PROGRAM_ADMIN` | `JURY_MEMBER` | `OBSERVER` | `MENTOR` | `APPLICANT` | `AWARD_MASTER` | `AUDIENCE`
|
|
|
|
## Coding Standards
|
|
|
|
- **TypeScript**: Strict mode, `type` over `interface`, prefer `unknown` over `any`
|
|
- **Files**: kebab-case. **Components**: PascalCase. **DB models**: PascalCase in Prisma
|
|
- **React**: Server Components by default, `'use client'` only when needed
|
|
- **Styling**: Tailwind utility classes, mobile-first (`md:`, `lg:` breakpoints), shadcn/ui as base
|
|
- **tRPC**: Group by domain (`trpc.competition.create()`), Zod input validation, `TRPCError` for errors
|
|
- **Brand colors**: Primary Red `#de0f1e`, Dark Blue `#053d57`, White `#fefefe`, Teal `#557f8c`
|
|
- **Typography**: Montserrat (600/700 headings, 300/400 body)
|
|
|
|
## Key Constraints
|
|
|
|
1. Jury can only see assigned projects (enforced at query level)
|
|
2. Voting windows are strict — submissions blocked outside active window
|
|
3. All admin actions are audited via `DecisionAuditLog`
|
|
4. Files accessed via MinIO pre-signed URLs only (no public bucket)
|
|
5. COI declaration required before evaluation (blocking dialog)
|
|
6. Smart assignment skips COI-declared jurors, applies geo-diversity penalty and familiarity bonus
|
|
7. Cron endpoints protected by `CRON_SECRET` header
|
|
8. Round notifications never throw — all errors caught and logged
|
|
|
|
## Security
|
|
|
|
- CSRF: tRPC uses `application/json` (triggers CORS preflight). Do NOT add permissive CORS headers.
|
|
- Rate limiting: 100 req/min tRPC, 10 req/min auth, 5-attempt lockout
|
|
- AI privacy: All data anonymized before OpenAI calls
|
|
|
|
## Seed Data
|
|
|
|
`prisma/seed.ts` imports from `docs/Candidatures 2026 *.csv` with special handling for non-breaking spaces (U+00A0) around French guillemets. Includes ALL CSV rows (no filtering/dedup) — duplicate detection happens in `submission-manager.ts` at runtime.
|
|
|
|
## Environment Variables
|
|
|
|
Required: `DATABASE_URL`, `NEXTAUTH_URL`, `NEXTAUTH_SECRET`, `MINIO_ENDPOINT`, `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`, `MINIO_BUCKET`, `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `EMAIL_FROM`, `OPENAI_API_KEY`, `CRON_SECRET`
|
|
|
|
External services (pre-existing on VPS): MinIO `:9000`, Poste.io `:587`, Nginx reverse proxy with SSL.
|
|
|
|
## Git
|
|
|
|
Remote: `code.monaco-opc.com/MOPC/MOPC-Portal`. Branch: `main`. Always `npm run build` before pushing.
|