.planning/codebase/INTEGRATIONS.md
Analysis Date: 2026-05-11
AI Providers (user-supplied API keys, called per-request from packages/api/src/services/ai.ts):
https://api.openai.com/v1 (packages/ai/src/types.ts)
@ai-sdk/openai ^3.0.63 via createOpenAI(...).chat(model)ai config; not read from server envhttps://api.anthropic.com/v1
@ai-sdk/anthropic ^3.0.76 via createAnthropic(...).languageModel(model)https://generativelanguage.googleapis.com/v1beta
@ai-sdk/google ^3.0.71 via createGoogleGenerativeAI(...).languageModel(model)https://ai-gateway.vercel.sh/v3/ai
ai ^6.0.177 via createGateway(...).languageModel(model)https://openrouter.ai/api/v1
@ai-sdk/openai-compatible ^2.0.47 via createOpenAICompatible({ name: "openrouter", ... })https://ollama.com/api
ollama-ai-provider-v2 ^3.5.0 via createOllama(...)packages/api/src/services/ai.ts resolveBaseUrl rejects non-HTTPS URLs, credentialed URLs, and private/loopback hosts (via packages/utils/src/url-security.node.ts).MAX_AI_FILE_BYTES).OAuth Identity Providers (server-side env-configured, optional):
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET (config in packages/auth/src/config.ts).GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET.LINKEDIN_CLIENT_ID / LINKEDIN_CLIENT_SECRET.OAUTH_PROVIDER_NAME, OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, plus either OAUTH_DISCOVERY_URL or all three of OAUTH_AUTHORIZATION_URL/OAUTH_TOKEN_URL/OAUTH_USER_INFO_URL. Scopes from OAUTH_SCOPES (defaults openid profile email). Wired via Better Auth's genericOAuth plugin.packages/auth/src/config.ts): google, github, linkedin.Translation / Localization:
CROWDIN_PROJECT_ID, CROWDIN_PERSONAL_TOKEN. Config in crowdin.yml; pull request automation labelled l10n. Source catalog apps/web/locales/en-US.po.Font Catalog Tooling:
GOOGLE_CLOUD_API_KEY consumed by packages/scripts/fonts/generate.ts hitting https://www.googleapis.com/webfonts/v1/webfonts. Output committed at packages/fonts/src/webfontlist.json.Databases:
DATABASE_URL (validated as postgres(ql)://... in packages/env/src/server.ts)drizzle-orm 1.0.0-beta.22 with pg ^8.20.0 Pool, instantiated in packages/db/src/client.ts (singleton via globalThis.__pool / globalThis.__drizzle).packages/db/src/schema/index.ts (auth + resume tables) with packages/db/src/relations.ts.drizzle-kit into repo-root migrations/ (e.g. 20260507144406_fast_nova/). Generator config at packages/db/drizzle.config.ts.apps/web/plugins/1.migrate.ts runs drizzle-orm/node-postgres/migrator against the resolved migrations folder.drizzle-kit does NOT auto-load .env — DATABASE_URL must be exported before running pnpm db:generate / pnpm db:migrate (see AGENTS.md "Important" callout).packages/api/src/services/storage.ts healthcheck + apps/web/src/routes/api/health.ts runs SELECT 1 through Drizzle (1.5s timeout).File Storage (selected at runtime in packages/api/src/services/storage.ts):
S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_BUCKET are set.
@aws-sdk/client-s3 ^3.1045.0 (S3Client constructed in S3StorageService).S3_REGION (default us-east-1), S3_ENDPOINT (for non-AWS), S3_FORCE_PATH_STYLE.compose.dev.yml / compose.yml).ACL: "public-read" (consumed publicly from /uploads/$userId/$ route, with path validation).LocalStorageService) used when any of the three S3 vars is missing.
LOCAL_STORAGE_PATH if set (must be absolute); otherwise <workspace>/data in dev or /app/data in the Docker image.apps/web/plugins/2.storage.ts (mkdir + access check).packages/api/src/services/storage.ts):
uploads/{userId}/pictures/{timestamp}.jpeguploads/{userId}/screenshots/{resumeId}/{timestamp}.jpeguploads/{userId}/pdfs/{resumeId}/{timestamp}.pdfapps/web/src/routes/uploads/$userId.$.tsx (ETag, content-type sniffing, path traversal protection).sharp ^0.34.5 resizes to 800x800 and re-encodes JPEG at quality 80, unless FLAG_DISABLE_IMAGE_PROCESSING=true.Caching:
@orpc/experimental-ratelimit MemoryRatelimiter (packages/api/src/middleware/rate-limit/index.ts).apps/web/vite.config.ts VitePWA setup (skipWaiting, clientsClaim, cleanupOutdatedCaches).Auth Provider: Better Auth 1.6.10, configured in packages/auth/src/config.ts.
/api/auth/* via apps/web/src/routes/api/auth.$.ts.@better-auth/drizzle-adapter bound to db + schema (provider pg).http://localhost:3000, http://127.0.0.1:3000, and the normalized origin of APP_URL.APP_URL is https://.advanced.ipAddress.ipAddressHeaders and the oRPC rate limiter): CF-Connecting-IP, CF-Connecting-IPv6, True-Client-IP, X-Forwarded-For, X-Real-IP (packages/utils/src/rate-limit.ts).Email/Password:
FLAG_DISABLE_EMAIL_AUTH=true.bcrypt (cost 10) via hash / compare from bcrypt ^6.0.0.VerifyEmail template from @reactive-resume/email/templates/auth.sendResetPassword -> ResetPasswordEmail template.sendChangeEmailConfirmation -> VerifyEmailChange template.Better Auth Plugins (in packages/auth/src/config.ts):
jwt() — JWKS published at /api/auth/jwks (also referenced by verifyOAuthToken).admin() — Admin operations.passkey() (@better-auth/passkey ^1.6.10) — WebAuthn passkeys.genericOAuth({ config }) — Driven by OAUTH_* env vars when configured.twoFactor({ issuer: "Reactive Resume" }) — TOTP + backup codes; UI routes /auth/verify-2fa and /auth/verify-2fa-backup.apiKey() (@better-auth/api-key ^1.6.10) — Header x-api-key; enableSessionForAPIKeys: true; per-key rate limit 1000 req/hour.oauthProvider() (@better-auth/oauth-provider ^1.6.10) — Makes this app act as an OAuth 2.1 authorization server for MCP clients. Allows dynamic and unauthenticated client registration (RFC 7591) but redirect URIs are gated by an allowlist in the auth hooks.before middleware and OAUTH_DYNAMIC_CLIENT_REDIRECT_HOSTS. Audiences whitelist: ${APP_URL}, ${APP_URL}/, ${APP_URL}/mcp, ${APP_URL}/mcp/.username() — Normalized lowercase usernames (^[a-z0-9._-]+$, length 3–64).dash({ apiKey: BETTER_AUTH_API_KEY }) — Better Auth Dashboard (only when BETTER_AUTH_API_KEY is set).Social Providers:
*_CLIENT_ID and *_CLIENT_SECRET env vars are set (packages/auth/src/config.ts). disableImplicitSignUp: true; account linking enabled.Session / Access in API context:
packages/api/src/context.ts exposes protectedProcedure (referenced in AGENTS.md) for authenticated procedures.RequestHeadersPlugin (apps/web/src/routes/api/rpc.$.ts, apps/web/src/routes/api/openapi.$.ts).API Key Auth for HTTP / MCP:
x-api-key header recognised by Better Auth API Key plugin./api/openapi/spec.json declares apiKey security scheme (apps/web/src/routes/api/openapi.$.ts).Authorization: Bearer ...), then falls back to x-api-key (apps/web/src/routes/mcp/index.ts).Error Tracking:
console.error (oRPC interceptors onError in apps/web/src/routes/api/rpc.$.ts and apps/web/src/routes/api/openapi.$.ts).Health Checks:
GET /api/health — apps/web/src/routes/api/health.ts returns JSON with DB and storage status; 503 if unhealthy. Used by Docker HEALTHCHECK.Logs:
console.info/console.warn/console.error only. SMTP failures, missing config, sanitization diagnostics, and migration progress are all written to stdout/stderr.Hosting / Distribution:
Dockerfile builds final image around node:24-slim, expose port 3000, default LOCAL_STORAGE_PATH=/app/data. Multi-stage uses turbo prune for both the web app and the runtime-externals package (which carries bcrypt, sharp, @aws-sdk/client-s3 as native deps).https://rxresu.me and https://docs.rxresu.me.CI Pipeline:
.github/workflows/ enumerated here). Scripts produce CI-friendly Vitest reports (reports/vitest-junit.xml, reports/vitest-results.json) via the test:ci task.Local Orchestration:
compose.dev.yml — postgres:latest, seaweedfs:latest, seaweedfs_create_bucket (uses quay.io/minio/mc:latest).compose.yml — Same services plus reactive_resume app container, networks data_network / storage_network.Git Hooks:
lefthook.yml) — pre-commit runs biome check --write --unsafe on staged JS/TS/JSON files; commit-msg runs commitlint --edit.Required env vars (validated by Zod in packages/env/src/server.ts):
APP_URL — http(s) URL, used for auth base URL, OAuth audiences, OG metadata, and public upload URLs.DATABASE_URL — postgres(ql)://....AUTH_SECRET — Better Auth signing secret (openssl rand -hex 32).Optional auth env vars:
BETTER_AUTH_API_KEY — Enables Better Auth Dashboard plugin.GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET.GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET.LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET.OAUTH_PROVIDER_NAME, OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_DISCOVERY_URL, OAUTH_AUTHORIZATION_URL, OAUTH_TOKEN_URL, OAUTH_USER_INFO_URL, OAUTH_SCOPES, OAUTH_DYNAMIC_CLIENT_REDIRECT_HOSTS.Optional SMTP env vars (all required together to enable real sends):
SMTP_HOST, SMTP_PORT (default 587), SMTP_USER, SMTP_PASS, SMTP_FROM, SMTP_SECURE (default false). Fallback in packages/email/src/transport.ts: log to console with subject/body when SMTP is not fully configured.Optional storage env vars:
LOCAL_STORAGE_PATH — Must be absolute when set.S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_REGION (default us-east-1), S3_ENDPOINT, S3_BUCKET, S3_FORCE_PATH_STYLE (default false).Optional feature flags:
FLAG_DISABLE_SIGNUPS — Disables signups across all providers.FLAG_DISABLE_EMAIL_AUTH — Disables email/password auth and verification flows.FLAG_DISABLE_IMAGE_PROCESSING — Skips Sharp resize/encode (useful on resource-constrained hardware).Optional tooling env vars:
CROWDIN_PROJECT_ID and CROWDIN_PERSONAL_TOKEN for Crowdin translation sync.GOOGLE_CLOUD_API_KEY — For packages/scripts/fonts/generate.ts.Secrets location:
.env at the repo root (gitignored). .env.example provides documented defaults. Existence noted: .env.local, .env.production files present locally (contents not read; may contain secrets).turbo.json globalEnv whitelist.Incoming:
/api/auth/oauth2/callback/{providerId} (e.g. /api/auth/oauth2/callback/custom configured in packages/auth/src/config.ts).oauthProvider plugin under /api/auth/oauth2/*. Login/consent pages at /auth/oauth (apps/web/src/routes/auth/oauth.ts)..well-known discovery routes:
apps/web/src/routes/[.]well-known/oauth-authorization-server.ts and .../oauth-authorization-server.$.tsapps/web/src/routes/[.]well-known/oauth-protected-resource.ts and .../oauth-protected-resource.$.tsapps/web/src/routes/[.]well-known/openid-configuration.tsapps/web/src/routes/[.]well-known/mcp/server-card[.]json.tsapps/web/src/routes/[.]well-known/$.tsapps/web/src/routes/mcp/index.ts (uses WebStandardStreamableHTTPServerTransport). Authenticates via OAuth Bearer or x-api-key.Outgoing:
packages/api/src/services/ai.ts.packages/email/src/transport.ts) for verification, password reset, and email-change confirmations.@aws-sdk/client-s3 when S3 storage is configured.| Endpoint | Handler | Purpose |
|---|---|---|
/api/health | apps/web/src/routes/api/health.ts | Liveness/readiness JSON (DB + storage). |
/api/auth/* | apps/web/src/routes/api/auth.$.ts | Better Auth handler (sessions, OAuth, passkey, JWKS, etc.). |
/api/rpc/* | apps/web/src/routes/api/rpc.$.ts | oRPC server (packages/api/src/routers/index.ts: ai, auth, flags, resume, statistics, storage). |
/api/openapi/* | apps/web/src/routes/api/openapi.$.ts | OpenAPI-style REST wrapper of the oRPC router; spec at /api/openapi/spec.json. Auth via x-api-key. |
/mcp | apps/web/src/routes/mcp/index.ts | MCP Streamable HTTP server (@modelcontextprotocol/sdk). |
/uploads/{userId}/... | apps/web/src/routes/uploads/$userId.$.tsx | Public read of stored images/PDFs with ETag + path validation. |
/schema.json | apps/web/src/routes/schema[.]json.ts | Resume JSON schema. |
/.well-known/... | apps/web/src/routes/[.]well-known/* | OAuth/OIDC/MCP discovery documents. |
Better Auth (production only, see packages/auth/src/config.ts isRateLimitEnabled):
/sign-in/email: 5/60s; /sign-up/email: 3/60s./request-password-reset, /send-verification-email: 3/600s./two-factor/verify-otp, /verify-totp, /verify-backup-code: 5/600s./is-username-available: 20/60s.register 5/60s, authorize 30/60s, token 20/60s, introspect 60/60s, revoke 30/60s, userinfo 60/60s.packages/utils/src/rate-limit.ts.oRPC procedure-level (in-memory, production only, see packages/api/src/middleware/rate-limit/index.ts):
resumePassword: 5 / 10min.pdfExport: 5 / 60s.aiRequest: 20 / 60s.jobsSearch: 30 / 60s.jobsTestConnection: 10 / 60s.storageUpload: 20 / 60s.storageDelete: 30 / 60s.resumeMutations: 60 / 60s.| Service | Image | Purpose | File |
|---|---|---|---|
postgres | postgres:latest | Required PostgreSQL DB | compose.dev.yml, compose.yml |
seaweedfs | chrislusf/seaweedfs:latest | Optional S3-compatible storage for dev/prod | compose.dev.yml, compose.yml |
seaweedfs_create_bucket | quay.io/minio/mc:latest | One-shot init that creates reactive-resume bucket | compose.dev.yml, compose.yml |
reactive_resume | Built from Dockerfile | The app itself in prod compose | compose.yml |
Integration audit: 2026-05-11