Adds .planning/codebase/ with parallel-mapper outputs covering stack, integrations, architecture, structure, conventions, testing, and concerns.
16 KiB
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.63viacreateOpenAI(...).chat(model) - API key supplied per-call from the resume's
aiconfig; not read from server env
- SDK:
- Anthropic — Default base URL
https://api.anthropic.com/v1- SDK:
@ai-sdk/anthropic ^3.0.76viacreateAnthropic(...).languageModel(model)
- SDK:
- Google Gemini — Default base URL
https://generativelanguage.googleapis.com/v1beta- SDK:
@ai-sdk/google ^3.0.71viacreateGoogleGenerativeAI(...).languageModel(model)
- SDK:
- Vercel AI Gateway — Default base URL
https://ai-gateway.vercel.sh/v3/ai- SDK:
ai ^6.0.177viacreateGateway(...).languageModel(model)
- SDK:
- OpenRouter — Default base URL
https://openrouter.ai/api/v1- SDK:
@ai-sdk/openai-compatible ^2.0.47viacreateOpenAICompatible({ name: "openrouter", ... })
- SDK:
- Ollama — Default base URL
https://ollama.com/api- SDK:
ollama-ai-provider-v2 ^3.5.0viacreateOllama(...)
- SDK:
- Security:
packages/api/src/services/ai.tsresolveBaseUrlrejects non-HTTPS URLs, credentialed URLs, and private/loopback hosts (viapackages/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 inpackages/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 eitherOAUTH_DISCOVERY_URLor all three ofOAUTH_AUTHORIZATION_URL/OAUTH_TOKEN_URL/OAUTH_USER_INFO_URL. Scopes fromOAUTH_SCOPES(defaultsopenid profile email). Wired via Better Auth'sgenericOAuthplugin. - Trusted providers for account linking (
packages/auth/src/config.ts):google,github,linkedin.
Translation / Localization:
- Crowdin —
CROWDIN_PROJECT_ID,CROWDIN_API_TOKEN(env) /CROWDIN_PERSONAL_TOKEN(CI). Config incrowdin.yml; pull request automation labelledl10n. Source catalogapps/web/locales/en-US.po.
Font Catalog Tooling:
- Google Fonts Developer API —
GOOGLE_CLOUD_API_KEYconsumed bypackages/scripts/fonts/generate.tshittinghttps://www.googleapis.com/webfonts/v1/webfonts. Output committed atpackages/fonts/src/webfontlist.json.
Data Storage
Databases:
- PostgreSQL — Required relational database.
- Connection env:
DATABASE_URL(validated aspostgres(ql)://...inpackages/env/src/server.ts) - Client:
drizzle-orm 1.0.0-beta.22withpg ^8.20.0Pool, instantiated inpackages/db/src/client.ts(singleton viaglobalThis.__pool/globalThis.__drizzle). - Schema location:
packages/db/src/schema/index.ts(auth + resume tables) withpackages/db/src/relations.ts. - Migrations: generated by
drizzle-kitinto repo-rootmigrations/(e.g.20260507144406_fast_nova/). Generator config atpackages/db/drizzle.config.ts. - Auto-migration on app start:
apps/web/plugins/1.migrate.tsrunsdrizzle-orm/node-postgres/migratoragainst the resolved migrations folder. drizzle-kitdoes NOT auto-load.env—DATABASE_URLmust be exported before runningpnpm db:generate/pnpm db:migrate(seeAGENTS.md"Important" callout).- Health probe:
packages/api/src/services/storage.tshealthcheck +apps/web/src/routes/api/health.tsrunsSELECT 1through Drizzle (1.5s timeout).
- Connection env:
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_BUCKETare set.- Client:
@aws-sdk/client-s3 ^3.1045.0(S3Clientconstructed inS3StorageService). - Options:
S3_REGION(defaultus-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).
- Client:
- Local filesystem fallback (
LocalStorageService) used when any of the three S3 vars is missing.- Path:
LOCAL_STORAGE_PATHif set (must be absolute); otherwise<workspace>/datain dev or/app/datain the Docker image. - Validated at boot by
apps/web/plugins/2.storage.ts(mkdir + access check).
- Path:
- Key layout (built in
packages/api/src/services/storage.ts):uploads/{userId}/pictures/{timestamp}.jpeguploads/{userId}/screenshots/{resumeId}/{timestamp}.jpeguploads/{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.5resizes to 800x800 and re-encodes JPEG at quality 80, unlessFLAG_DISABLE_IMAGE_PROCESSING=true.
Caching:
- No external cache (Redis/Memcached) configured.
- In-process rate limiter:
@orpc/experimental-ratelimitMemoryRatelimiter(packages/api/src/middleware/rate-limit/index.ts). - Workbox precache in the service worker (PWA) —
apps/web/vite.config.tsVitePWAsetup (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/*viaapps/web/src/routes/api/auth.$.ts. - Drizzle adapter:
@better-auth/drizzle-adapterbound todb+ schema (providerpg). - Trusted origins:
http://localhost:3000,http://127.0.0.1:3000, and the normalized origin ofAPP_URL. - Secure cookies enabled automatically when
APP_URLishttps://. - Trusted IP headers (used by both Better Auth
advanced.ipAddress.ipAddressHeadersand 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) viahash/comparefrombcrypt ^6.0.0. - Min 8, max 64 char passwords.
- Email verification: sent on signup using
VerifyEmailtemplate from@reactive-resume/email/templates/auth. - Reset password:
sendResetPassword->ResetPasswordEmailtemplate. - Email change:
sendChangeEmailConfirmation->VerifyEmailChangetemplate.
Better Auth Plugins (in packages/auth/src/config.ts):
jwt()— JWKS published at/api/auth/jwks(also referenced byverifyOAuthToken).admin()— Admin operations.passkey()(@better-auth/passkey ^1.6.10) — WebAuthn passkeys.genericOAuth({ config })— Driven byOAUTH_*env vars when configured.twoFactor({ issuer: "Reactive Resume" })— TOTP + backup codes; UI routes/auth/verify-2faand/auth/verify-2fa-backup.apiKey()(@better-auth/api-key ^1.6.10) — Headerx-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 authhooks.beforemiddleware andOAUTH_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 whenBETTER_AUTH_API_KEYis set).
Social Providers:
- Google, GitHub, LinkedIn — Each activates only when both
*_CLIENT_IDand*_CLIENT_SECRETenv vars are set (packages/auth/src/config.ts).disableImplicitSignUp: true; account linking enabled.
Session / Access in API context:
packages/api/src/context.tsexposesprotectedProcedure(referenced inAGENTS.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-keyheader recognised by Better Auth API Key plugin.- OpenAPI spec at
/api/openapi/spec.jsondeclaresapiKeysecurity scheme (apps/web/src/routes/api/openapi.$.ts). - MCP server first tries OAuth Bearer (
Authorization: Bearer ...), then falls back tox-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 interceptorsonErrorinapps/web/src/routes/api/rpc.$.tsandapps/web/src/routes/api/openapi.$.ts).
Health Checks:
GET /api/health—apps/web/src/routes/api/health.tsreturns JSON with DB and storage status; 503 if unhealthy. Used by DockerHEALTHCHECK.
Logs:
console.info/console.warn/console.erroronly. SMTP failures, missing config, sanitization diagnostics, and migration progress are all written to stdout/stderr.
CI/CD & Deployment
Hosting / Distribution:
- Self-hosted Docker image —
Dockerfilebuilds final image aroundnode:24-slim, expose port 3000, defaultLOCAL_STORAGE_PATH=/app/data. Multi-stage usesturbo prunefor both the web app and theruntime-externalspackage (which carriesbcrypt,sharp,@aws-sdk/client-s3as native deps). - Container labels point to
https://rxresu.meandhttps://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 thetest:citask.
Local Orchestration:
compose.dev.yml—postgres:latest,seaweedfs:latest,seaweedfs_create_bucket(usesquay.io/minio/mc:latest).compose.yml— Same services plusreactive_resumeapp container, networksdata_network/storage_network.
Git Hooks:
- Lefthook (
lefthook.yml) —pre-commitrunsbiome check --write --unsafeon staged JS/TS/JSON files;commit-msgrunscommitlint --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_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 inpackages/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(defaultus-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,CROWDIN_API_TOKEN(orCROWDIN_PERSONAL_TOKENin CI).GOOGLE_CLOUD_API_KEY— Forpackages/scripts/fonts/generate.ts.
Secrets location:
.envat the repo root (gitignored)..env.exampleprovides documented defaults. Existence noted:.env.local,.env.productionfiles present locally (contents not read; may contain secrets).- Turbo cache invalidation tied to env vars via
turbo.jsonglobalEnvwhitelist.
Webhooks & Callbacks
Incoming:
- OAuth provider callbacks served by Better Auth:
/api/auth/oauth2/callback/{providerId}(e.g./api/auth/oauth2/callback/customconfigured inpackages/auth/src/config.ts). - OAuth Authorization Server endpoints (when this app acts as an OAuth provider for MCP clients): handled by Better Auth
oauthProviderplugin under/api/auth/oauth2/*. Login/consent pages at/auth/oauth(apps/web/src/routes/auth/oauth.ts). .well-knowndiscovery routes:apps/web/src/routes/[.]well-known/oauth-authorization-server.tsand.../oauth-authorization-server.$.tsapps/web/src/routes/[.]well-known/oauth-protected-resource.tsand.../oauth-protected-resource.$.tsapps/web/src/routes/[.]well-known/openid-configuration.tsapps/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(usesWebStandardStreamableHTTPServerTransport). Authenticates via OAuth Bearer orx-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-s3when 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
| 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. |
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:
register5/60s,authorize30/60s,token20/60s,introspect60/60s,revoke30/60s,userinfo60/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
| 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