Back to Reactive Resume

External Integrations

.planning/codebase/INTEGRATIONS.md

5.1.316.4 KB
Original Source

External Integrations

Analysis Date: 2026-05-11

APIs & External Services

AI Providers (user-supplied API keys, called per-request from packages/api/src/services/ai.ts):

  • OpenAI — Default base URL https://api.openai.com/v1 (packages/ai/src/types.ts)
    • SDK: @ai-sdk/openai ^3.0.63 via createOpenAI(...).chat(model)
    • API key supplied per-call from the resume's ai config; not read from server env
  • Anthropic — Default base URL https://api.anthropic.com/v1
    • SDK: @ai-sdk/anthropic ^3.0.76 via createAnthropic(...).languageModel(model)
  • Google Gemini — Default base URL https://generativelanguage.googleapis.com/v1beta
    • SDK: @ai-sdk/google ^3.0.71 via createGoogleGenerativeAI(...).languageModel(model)
  • Vercel AI Gateway — Default base URL https://ai-gateway.vercel.sh/v3/ai
    • SDK: ai ^6.0.177 via createGateway(...).languageModel(model)
  • OpenRouter — Default base URL https://openrouter.ai/api/v1
    • SDK: @ai-sdk/openai-compatible ^2.0.47 via createOpenAICompatible({ name: "openrouter", ... })
  • Ollama — Default base URL https://ollama.com/api
    • SDK: ollama-ai-provider-v2 ^3.5.0 via createOllama(...)
  • Security: 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).
  • File-input AI calls are capped at 10MB (MAX_AI_FILE_BYTES).

OAuth Identity Providers (server-side env-configured, optional):

  • Google — GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET (config in packages/auth/src/config.ts).
  • GitHub — GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET.
  • LinkedIn — LINKEDIN_CLIENT_ID / LINKEDIN_CLIENT_SECRET.
  • Custom Generic OAuth — 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.
  • Trusted providers for account linking (packages/auth/src/config.ts): google, github, linkedin.

Translation / Localization:

  • Crowdin — 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 Fonts Developer API — 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.

Data Storage

Databases:

  • PostgreSQL — Required relational database.
    • Connection env: DATABASE_URL (validated as postgres(ql)://... in packages/env/src/server.ts)
    • Client: 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).
    • Schema location: packages/db/src/schema/index.ts (auth + resume tables) with packages/db/src/relations.ts.
    • Migrations: generated by drizzle-kit into repo-root migrations/ (e.g. 20260507144406_fast_nova/). Generator config at packages/db/drizzle.config.ts.
    • Auto-migration on app start: apps/web/plugins/1.migrate.ts runs drizzle-orm/node-postgres/migrator against the resolved migrations folder.
    • drizzle-kit does NOT auto-load .envDATABASE_URL must be exported before running pnpm db:generate / pnpm db:migrate (see AGENTS.md "Important" callout).
    • Health probe: 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-compatible object storage when all three of S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_BUCKET are set.
    • Client: @aws-sdk/client-s3 ^3.1045.0 (S3Client constructed in S3StorageService).
    • Options: S3_REGION (default us-east-1), S3_ENDPOINT (for non-AWS), S3_FORCE_PATH_STYLE.
    • Tested compatible backends: AWS S3 and SeaweedFS (see compose.dev.yml / compose.yml).
    • All writes use ACL: "public-read" (consumed publicly from /uploads/$userId/$ route, with path validation).
  • Local filesystem fallback (LocalStorageService) used when any of the three S3 vars is missing.
    • Path: LOCAL_STORAGE_PATH if set (must be absolute); otherwise <workspace>/data in dev or /app/data in the Docker image.
    • Validated at boot by apps/web/plugins/2.storage.ts (mkdir + access check).
  • Key layout (built in packages/api/src/services/storage.ts):
    • uploads/{userId}/pictures/{timestamp}.jpeg
    • uploads/{userId}/screenshots/{resumeId}/{timestamp}.jpeg
    • uploads/{userId}/pdfs/{resumeId}/{timestamp}.pdf
  • Public read route: apps/web/src/routes/uploads/$userId.$.tsx (ETag, content-type sniffing, path traversal protection).
  • Image preprocessing: sharp ^0.34.5 resizes to 800x800 and re-encodes JPEG at quality 80, unless FLAG_DISABLE_IMAGE_PROCESSING=true.

Caching:

  • No external cache (Redis/Memcached) configured.
  • In-process rate limiter: @orpc/experimental-ratelimit MemoryRatelimiter (packages/api/src/middleware/rate-limit/index.ts).
  • Workbox precache in the service worker (PWA) — apps/web/vite.config.ts VitePWA setup (skipWaiting, clientsClaim, cleanupOutdatedCaches).

Authentication & Identity

Auth Provider: Better Auth 1.6.10, configured in packages/auth/src/config.ts.

  • Mounted as a TanStack Start server handler at /api/auth/* via apps/web/src/routes/api/auth.$.ts.
  • Drizzle adapter: @better-auth/drizzle-adapter bound to db + schema (provider pg).
  • Trusted origins: http://localhost:3000, http://127.0.0.1:3000, and the normalized origin of APP_URL.
  • Secure cookies enabled automatically when APP_URL is https://.
  • Trusted IP headers (used by both Better Auth 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).
  • Telemetry: explicitly disabled.

Email/Password:

  • Enabled unless FLAG_DISABLE_EMAIL_AUTH=true.
  • Password hash: bcrypt (cost 10) via hash / compare from bcrypt ^6.0.0.
  • Min 8, max 64 char passwords.
  • Email verification: sent on signup using VerifyEmail template from @reactive-resume/email/templates/auth.
  • Reset password: sendResetPassword -> ResetPasswordEmail template.
  • Email change: 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:

  • Google, GitHub, LinkedIn — Each activates only when both *_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.
  • oRPC middleware reads request headers via 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.
  • OpenAPI spec at /api/openapi/spec.json declares apiKey security scheme (apps/web/src/routes/api/openapi.$.ts).
  • MCP server first tries OAuth Bearer (Authorization: Bearer ...), then falls back to x-api-key (apps/web/src/routes/mcp/index.ts).

Monitoring & Observability

Error Tracking:

  • None — no Sentry/Datadog/Rollbar SDK present.
  • Errors are logged via 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/healthapps/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.

CI/CD & Deployment

Hosting / Distribution:

  • Self-hosted Docker image — 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).
  • Container labels point to https://rxresu.me and https://docs.rxresu.me.

CI Pipeline:

  • Not present in this worktree (no .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.ymlpostgres: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 (lefthook.yml) — pre-commit runs biome check --write --unsafe on staged JS/TS/JSON files; commit-msg runs commitlint --edit.

Environment Configuration

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_URLpostgres(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 cache invalidation tied to env vars via turbo.json globalEnv whitelist.

Webhooks & Callbacks

Incoming:

  • OAuth provider callbacks served by Better Auth: /api/auth/oauth2/callback/{providerId} (e.g. /api/auth/oauth2/callback/custom configured in packages/auth/src/config.ts).
  • OAuth Authorization Server endpoints (when this app acts as an OAuth provider for MCP clients): handled by Better Auth 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.$.ts
    • apps/web/src/routes/[.]well-known/oauth-protected-resource.ts and .../oauth-protected-resource.$.ts
    • apps/web/src/routes/[.]well-known/openid-configuration.ts
    • apps/web/src/routes/[.]well-known/mcp/server-card[.]json.ts
    • Generic catch-all: apps/web/src/routes/[.]well-known/$.ts
  • MCP Streamable HTTP transport: apps/web/src/routes/mcp/index.ts (uses WebStandardStreamableHTTPServerTransport). Authenticates via OAuth Bearer or x-api-key.

Outgoing:

  • AI provider HTTPS requests (see "AI Providers" above) — initiated per user request from packages/api/src/services/ai.ts.
  • SMTP outbound (packages/email/src/transport.ts) for verification, password reset, and email-change confirmations.
  • S3 PUT/GET/DELETE/LIST through @aws-sdk/client-s3 when S3 storage is configured.
  • Crowdin and Google Fonts requests only from CI / scripts, not from the runtime web app.

API Endpoints Exposed by the App

EndpointHandlerPurpose
/api/healthapps/web/src/routes/api/health.tsLiveness/readiness JSON (DB + storage).
/api/auth/*apps/web/src/routes/api/auth.$.tsBetter Auth handler (sessions, OAuth, passkey, JWKS, etc.).
/api/rpc/*apps/web/src/routes/api/rpc.$.tsoRPC server (packages/api/src/routers/index.ts: ai, auth, flags, resume, statistics, storage).
/api/openapi/*apps/web/src/routes/api/openapi.$.tsOpenAPI-style REST wrapper of the oRPC router; spec at /api/openapi/spec.json. Auth via x-api-key.
/mcpapps/web/src/routes/mcp/index.tsMCP Streamable HTTP server (@modelcontextprotocol/sdk).
/uploads/{userId}/...apps/web/src/routes/uploads/$userId.$.tsxPublic read of stored images/PDFs with ETag + path validation.
/schema.jsonapps/web/src/routes/schema[.]json.tsResume JSON schema.
/.well-known/...apps/web/src/routes/[.]well-known/*OAuth/OIDC/MCP discovery documents.

Rate Limiting

Better Auth (production only, see packages/auth/src/config.ts isRateLimitEnabled):

  • Global default: 60 req / 60s.
  • /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.
  • OAuth provider endpoints: register 5/60s, authorize 30/60s, token 20/60s, introspect 60/60s, revoke 30/60s, userinfo 60/60s.
  • API keys: 1000 req / hour per key.
  • Source: 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.
  • Keys are derived from authenticated user id when present, otherwise from the first trusted-IP header or a UA+language fingerprint.

Optional Services Declared in Compose Files

ServiceImagePurposeFile
postgrespostgres:latestRequired PostgreSQL DBcompose.dev.yml, compose.yml
seaweedfschrislusf/seaweedfs:latestOptional S3-compatible storage for dev/prodcompose.dev.yml, compose.yml
seaweedfs_create_bucketquay.io/minio/mc:latestOne-shot init that creates reactive-resume bucketcompose.dev.yml, compose.yml
reactive_resumeBuilt from DockerfileThe app itself in prod composecompose.yml

Integration audit: 2026-05-11