9.9 KiB
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_KEYenv var oropenai_api_keySystemSetting - Base URL:
OPENAI_BASE_URLenv var oropenai_base_urlSystemSetting (for OpenAI-compatible proxies) - Model:
OPENAI_MODELenv var orai_modelSystemSetting (default:gpt-4o) - Client:
src/lib/openai.ts- lazy singleton, reset viaresetOpenAIClient()
- SDK:
-
Anthropic Claude - Alternative AI provider, same AI feature set as OpenAI
- SDK:
@anthropic-ai/sdk^0.78.0 - Auth:
ANTHROPIC_API_KEYenv var oranthropic_api_keySystemSetting - Adapter:
src/lib/openai.tswraps 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
- SDK:
-
LiteLLM proxy - Third option for ChatGPT subscription routing (no real API key needed)
- Config:
ai_provider = 'litellm'in SystemSettings +openai_base_urlpointing to proxy - Token limit fields stripped for
chatgpt/*model prefix
- Config:
-
AI provider selection:
getConfiguredProvider()insrc/lib/openai.tsreadsai_providerSystemSetting; defaults toopenai
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 againsthttps://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()readswhatsapp_providerSystemSetting
- Meta provider:
- 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_URLenv var (e.g.,postgresql://mopc:${password}@postgres:5432/mopc) - Client: Prisma 6 ORM,
src/lib/prisma.tssingleton with connection pool (limit=20, timeout=10) - Schema:
prisma/schema.prisma(~95KB, ~100+ models) - Migrations:
prisma/migrations/directory, deployed viaprisma migrate deployon startup - Test DB:
DATABASE_URL_TESTenv var (falls back toDATABASE_URLin test setup)
- Connection:
File Storage:
- MinIO (S3-compatible, self-hosted on VPS)
- Internal endpoint:
MINIO_ENDPOINTenv var (server-to-server) - Public endpoint:
MINIO_PUBLIC_ENDPOINTenv 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
- Internal endpoint:
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:
- Email (Magic Links)
- NextAuth
EmailProvider— magic link sent via Nodemailer - Link expiry: 15 minutes (
MAGIC_LINK_EXPIRYenv var or default 900s) - Custom send function:
sendMagicLinkEmail()insrc/lib/email.ts
- NextAuth
- 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)
mustSetPasswordflag forces redirect to/set-passwordbefore any other page
- Email/password with bcryptjs hashing (
Role System:
- User model has
role(primary, legacy scalar) androles(array, multi-role) userHasRole()helper insrc/server/trpc.tschecksroles[]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:
debugin development,warnin production - Configurable via
LOG_LEVELenv var - All output to
console.*(stdout/stderr)
Audit Logging:
- All auth events and admin mutations logged to
AuditLogDB 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_URLenv var configures the registry indocker/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 stringNEXTAUTH_URL- Full URL of the app (e.g.,https://monaco-opc.com)NEXTAUTH_SECRET/AUTH_SECRET- JWT signing secretMINIO_ENDPOINT- MinIO server URL (internal)MINIO_ACCESS_KEY- MinIO access keyMINIO_SECRET_KEY- MinIO secret keyMINIO_BUCKET- MinIO bucket nameSMTP_HOST,SMTP_PORT,SMTP_USER,SMTP_PASS- SMTP credentialsEMAIL_FROM- Sender addressCRON_SECRET- Shared secret for cron endpoint authentication
Optional env vars:
MINIO_PUBLIC_ENDPOINT- Public-facing MinIO URL for pre-signed URLsOPENAI_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 providersANTHROPIC_API_KEY- Anthropic Claude API keyPOSTE_API_URL- Poste.io mail server API URLPOSTE_ADMIN_EMAIL,POSTE_ADMIN_PASSWORD,POSTE_MAIL_DOMAIN- Poste.io adminSESSION_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:
.envfile at repo root (read by Docker Compose viaenv_file: .env)- Runtime secrets also configurable via
SystemSettingsDB 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
WebhookDB model andsrc/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 (0–10), 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 viasrc/server/services/evaluation-reminders.tsGET /api/cron/digest- Email digests viasrc/server/services/email-digest.tsGET /api/cron/draft-cleanup- Remove stale draft evaluationsGET /api/cron/audit-cleanup- Purge old audit log entries
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