214 lines
9.9 KiB
Markdown
214 lines
9.9 KiB
Markdown
|
|
# 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 (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 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*
|