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

9.9 KiB
Raw Blame History

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