Files
MOPC-Portal/.planning/codebase/INTEGRATIONS.md
Matt 8cc86bae20 docs: map existing codebase
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 23:14:08 +01:00

214 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# External Integrations
**Analysis Date:** 2026-02-26
## APIs & External Services
**AI Providers (configurable via SystemSettings DB table):**
- OpenAI - AI filtering, jury assignment suggestions, evaluation summaries, project tagging, award eligibility, shortlist recommendations
- SDK: `openai` ^6.16.0
- Auth: `OPENAI_API_KEY` env var or `openai_api_key` SystemSetting
- Base URL: `OPENAI_BASE_URL` env var or `openai_base_url` SystemSetting (for OpenAI-compatible proxies)
- Model: `OPENAI_MODEL` env var or `ai_model` SystemSetting (default: `gpt-4o`)
- Client: `src/lib/openai.ts` - lazy singleton, reset via `resetOpenAIClient()`
- Anthropic Claude - Alternative AI provider, same AI feature set as OpenAI
- SDK: `@anthropic-ai/sdk` ^0.78.0
- Auth: `ANTHROPIC_API_KEY` env var or `anthropic_api_key` SystemSetting
- Adapter: `src/lib/openai.ts` wraps Anthropic SDK behind OpenAI `.chat.completions.create()` interface
- Supported models: `claude-opus-4-5-20250514`, `claude-sonnet-4-5-20250514`, `claude-haiku-3-5-20241022`, `claude-opus-4-20250514`, `claude-sonnet-4-20250514`
- Extended thinking enabled automatically for Opus models
- LiteLLM proxy - Third option for ChatGPT subscription routing (no real API key needed)
- Config: `ai_provider = 'litellm'` in SystemSettings + `openai_base_url` pointing to proxy
- Token limit fields stripped for `chatgpt/*` model prefix
- AI provider selection: `getConfiguredProvider()` in `src/lib/openai.ts` reads `ai_provider` SystemSetting; defaults to `openai`
**All AI data is anonymized before sending** via `src/server/services/anonymization.ts`
**Notion:**
- Used for project import (alternative to CSV)
- SDK: `@notionhq/client` ^2.3.0
- Auth: API key stored in SystemSettings (`notion_api_key`), per-import flow
- Client: `src/lib/notion.ts` - `createNotionClient(apiKey)` per-request (not singleton)
- Router: `src/server/routers/notion-import.ts`
**Typeform:**
- Used for project import from form responses
- Auth: API key stored in SystemSettings per-import
- Client: `src/lib/typeform.ts` - plain fetch against `https://api.typeform.com`, no SDK
- Router: `src/server/routers/typeform-import.ts`
**WhatsApp (optional, configurable):**
- Two provider options: Meta WhatsApp Business Cloud API or Twilio WhatsApp
- Meta provider: `src/lib/whatsapp/meta-provider.ts`
- Twilio provider: `src/lib/whatsapp/twilio-provider.ts`
- Abstraction: `src/lib/whatsapp/index.ts` - `getWhatsAppProvider()` reads `whatsapp_provider` SystemSetting
- Auth: API keys stored in SystemSettings (`whatsapp_enabled`, `whatsapp_provider`, provider-specific keys)
- Used for: notification delivery (alternative to email)
## Data Storage
**Databases:**
- PostgreSQL 16 (primary datastore)
- Connection: `DATABASE_URL` env var (e.g., `postgresql://mopc:${password}@postgres:5432/mopc`)
- Client: Prisma 6 ORM, `src/lib/prisma.ts` singleton with connection pool (limit=20, timeout=10)
- Schema: `prisma/schema.prisma` (~95KB, ~100+ models)
- Migrations: `prisma/migrations/` directory, deployed via `prisma migrate deploy` on startup
- Test DB: `DATABASE_URL_TEST` env var (falls back to `DATABASE_URL` in test setup)
**File Storage:**
- MinIO (S3-compatible, self-hosted on VPS)
- Internal endpoint: `MINIO_ENDPOINT` env var (server-to-server)
- Public endpoint: `MINIO_PUBLIC_ENDPOINT` env var (browser-accessible pre-signed URLs)
- Auth: `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`
- Bucket: `MINIO_BUCKET` (default: `mopc-files`)
- Client: `src/lib/minio.ts` - lazy singleton via Proxy, `getMinioClient()`
- Access pattern: pre-signed URLs only (15-minute expiry by default), never direct public bucket
- Key structure: `{ProjectName}/{RoundName}/{timestamp}-{fileName}`
- File types stored: EXEC_SUMMARY, PRESENTATION, VIDEO, BUSINESS_PLAN, VIDEO_PITCH, SUPPORTING_DOC, OTHER
**Caching:**
- None (in-memory rate limiter in `src/lib/rate-limit.ts`, not a caching layer)
- Note: rate limiter is in-memory only — not suitable for multi-instance deployments
## Authentication & Identity
**Auth Provider: NextAuth v5 (self-hosted)**
- Implementation: `src/lib/auth.ts` + `src/lib/auth.config.ts`
- Adapter: `@auth/prisma-adapter` (stores sessions/tokens in PostgreSQL)
- Strategy: JWT sessions (24-hour default, configurable via `SESSION_MAX_AGE`)
- Session includes: `user.id`, `user.email`, `user.name`, `user.role`, `user.roles[]`, `user.mustSetPassword`
**Auth Providers:**
1. Email (Magic Links)
- NextAuth `EmailProvider` — magic link sent via Nodemailer
- Link expiry: 15 minutes (`MAGIC_LINK_EXPIRY` env var or default 900s)
- Custom send function: `sendMagicLinkEmail()` in `src/lib/email.ts`
2. Credentials (Password + Invite Token)
- Email/password with bcryptjs hashing (`src/lib/password.ts`)
- Invite token flow: one-time token clears on first use, sets `mustSetPassword: true`
- Failed login tracking: 5-attempt lockout, 15-minute duration (in-memory, not persistent)
- `mustSetPassword` flag forces redirect to `/set-password` before any other page
**Role System:**
- User model has `role` (primary, legacy scalar) and `roles` (array, multi-role)
- `userHasRole()` helper in `src/server/trpc.ts` checks `roles[]` with `[role]` fallback
- 8 roles: `SUPER_ADMIN`, `PROGRAM_ADMIN`, `JURY_MEMBER`, `MENTOR`, `OBSERVER`, `APPLICANT`, `AWARD_MASTER`, `AUDIENCE`
## Monitoring & Observability
**Error Tracking:**
- Not detected (no Sentry, Datadog, or similar third-party service)
**Logs:**
- Custom structured logger: `src/lib/logger.ts`
- Tagged format: `{timestamp} [LEVEL] [Tag] message data`
- Levels: debug, info, warn, error
- Default: `debug` in development, `warn` in production
- Configurable via `LOG_LEVEL` env var
- All output to `console.*` (stdout/stderr)
**Audit Logging:**
- All auth events and admin mutations logged to `AuditLog` DB table
- `src/server/utils/audit.ts` - `logAudit()` helper
- Events tracked: LOGIN_SUCCESS, LOGIN_FAILED, INVITATION_ACCEPTED, all CRUD operations on major entities
- Cron for cleanup: `src/app/api/cron/audit-cleanup/`
**Application Metrics:**
- Health check endpoint: `GET /api/health` (used by Docker healthcheck)
## CI/CD & Deployment
**Hosting:**
- Self-hosted VPS with Docker
- Nginx reverse proxy with SSL (external to compose stack)
- Domain: `monaco-opc.com`
**CI Pipeline:**
- Gitea Actions (self-hosted Gitea at `code.monaco-opc.com/MOPC/MOPC-Portal`)
- Pipeline builds Docker image and pushes to private container registry
- `REGISTRY_URL` env var configures the registry in `docker/docker-compose.yml`
**Docker Setup:**
- Production: `docker/docker-compose.yml` — app + postgres services
- Dev: `docker/docker-compose.dev.yml` — dev stack variant
- App image: standalone Next.js build (`output: 'standalone'`)
- Entrypoint: `docker/docker-entrypoint.sh` — migrations → generate → seed → start
## Environment Configuration
**Required env vars:**
- `DATABASE_URL` - PostgreSQL connection string
- `NEXTAUTH_URL` - Full URL of the app (e.g., `https://monaco-opc.com`)
- `NEXTAUTH_SECRET` / `AUTH_SECRET` - JWT signing secret
- `MINIO_ENDPOINT` - MinIO server URL (internal)
- `MINIO_ACCESS_KEY` - MinIO access key
- `MINIO_SECRET_KEY` - MinIO secret key
- `MINIO_BUCKET` - MinIO bucket name
- `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS` - SMTP credentials
- `EMAIL_FROM` - Sender address
- `CRON_SECRET` - Shared secret for cron endpoint authentication
**Optional env vars:**
- `MINIO_PUBLIC_ENDPOINT` - Public-facing MinIO URL for pre-signed URLs
- `OPENAI_API_KEY` - OpenAI API key (also in SystemSettings)
- `OPENAI_MODEL` - Default AI model (default: `gpt-4o`)
- `OPENAI_BASE_URL` - Custom base URL for OpenAI-compatible providers
- `ANTHROPIC_API_KEY` - Anthropic Claude API key
- `POSTE_API_URL` - Poste.io mail server API URL
- `POSTE_ADMIN_EMAIL`, `POSTE_ADMIN_PASSWORD`, `POSTE_MAIL_DOMAIN` - Poste.io admin
- `SESSION_MAX_AGE` - JWT session duration in seconds (default: 86400)
- `MAX_FILE_SIZE` - Max upload size in bytes (default: 524288000 = 500MB)
- `LOG_LEVEL` - Logging verbosity (debug/info/warn/error)
- `MAGIC_LINK_EXPIRY` - Magic link lifetime in seconds (default: 900)
**Secrets location:**
- `.env` file at repo root (read by Docker Compose via `env_file: .env`)
- Runtime secrets also configurable via `SystemSettings` DB table (admin UI)
## Webhooks & Callbacks
**Incoming:**
- `/api/auth/[...nextauth]` - NextAuth callback routes (magic link verification, OAuth if added)
- No third-party webhook receivers detected
**Outgoing:**
- Configurable webhooks via `Webhook` DB model and `src/server/services/webhook-dispatcher.ts`
- Admin-managed via `src/server/routers/webhook.ts` (SUPER_ADMIN only)
- Signed with HMAC-SHA256 (`X-Webhook-Signature: sha256={sig}`)
- Events dispatched: `evaluation.submitted`, `evaluation.updated`, `project.created`, `project.statusChanged`, `round.activated`, `round.closed`, `assignment.created`, `assignment.completed`, `user.invited`, `user.activated`
- Retry logic: configurable max retries per webhook (010), retry via cron
## Real-Time
**Server-Sent Events (SSE):**
- Endpoint: `/api/sse/` - in-app notifications push
- Used for: real-time notification delivery to connected clients
**Live Voting Stream:**
- Endpoint: `/api/live-voting/stream/` - SSE stream for live ceremony voting cursor
- Service: `src/server/services/live-control.ts`
## Cron Jobs
All cron endpoints protected by `CRON_SECRET` header check:
- `GET /api/cron/reminders` - Evaluation reminders via `src/server/services/evaluation-reminders.ts`
- `GET /api/cron/digest` - Email digests via `src/server/services/email-digest.ts`
- `GET /api/cron/draft-cleanup` - Remove stale draft evaluations
- `GET /api/cron/audit-cleanup` - Purge old audit log entries
## Email
**SMTP Transport:**
- Provider: Poste.io (self-hosted mail server, port 587)
- Client: Nodemailer 7 via `src/lib/email.ts`
- Config priority: SystemSettings DB > env vars
- Transporter cached, rebuilt when config hash changes
- Error handling: email errors logged but never thrown (non-blocking)
---
*Integration audit: 2026-02-26*