acf60c2a07
Wave 6. Swaps react-scripts' build/dev tooling for Next.js while keeping the app's actual routing/rendering model unchanged -- the app is almost entirely behind auth with no proven SSR/SEO need, so a real App Router rewrite would touch ~90 files for zero user-visible benefit. - next.config.js: output:'export' (static HTML+JS, same "single index.html served by nginx with try_files fallback" deploy as CRA). - app/layout.tsx + app/page.tsx: root shell ports public/index.html's <head>, mounts the whole existing App tree client-only (ssr:false) since it reads window/localStorage during initial render and Next's static prerender would otherwise execute that on the server. - Renamed src/pages/ -> src/views/ (Next's Pages Router auto-detects any `pages/` dir under the app root and tried to build our React Router page components as its own routes). - REACT_APP_* -> NEXT_PUBLIC_* across code, .env.development, Dockerfile, docker-compose.yml build args. - Replaced the CRA SVGR import (`ReactComponent` from .svg, unsupported under Turbopack) with a small inline JobbjaktMark component. - TypeScript 4.9 -> 5.9 (MUI v8's type-checked build needs syntax 4.9's parser rejects; CRA never hit this because babel doesn't type-check). - Dropped CRA-only files (index.tsx, reportWebVitals, react-app-env.d.ts, public/index.html); kept react-scripts as the Jest test runner only (next/jest migration not needed -- the existing config already works). Verified: `next build` static export succeeds, `next dev` serves the landing page and client-side routes (login etc.) correctly, all 57 frontend tests + 172 backend tests still green. Known caveat: deep-linking straight to a sub-route (e.g. /login) 404s in `next dev` since there's no server route for it -- the app only ever mounts at "/". Production is unaffected: nginx's existing try_files fallback still serves index.html for any path.
140 lines
4.8 KiB
YAML
140 lines
4.8 KiB
YAML
services:
|
|
|
|
backend:
|
|
build:
|
|
context: .
|
|
dockerfile: JobTrackerApi/Dockerfile
|
|
volumes:
|
|
- jobtracker_data:/data
|
|
environment:
|
|
- ASPNETCORE_URLS=http://+:8080
|
|
- Data__Root=/data
|
|
- Exports__DailyFolder=/data/exports
|
|
- Database__Provider=${DATABASE_PROVIDER:-sqlite}
|
|
- ConnectionStrings__JobTracker=${JOBTRACKER_CONNECTION_STRING}
|
|
# If you enable HTTPS at a reverse proxy (recommended), handle redirects there.
|
|
- HttpsRedirection__Enabled=false
|
|
# Authentication (recommended for any non-local deployment)
|
|
- Auth__Require=true
|
|
- Auth__JwtKey=${AUTH_JWT_KEY}
|
|
- Auth__AdminEmail=${AUTH_ADMIN_EMAIL}
|
|
- Auth__AdminPassword=${AUTH_ADMIN_PASSWORD}
|
|
# Optional: allow Google ID-token bearer auth
|
|
- Auth__GoogleClientId=${AUTH_GOOGLE_CLIENT_ID}
|
|
- Google__GmailClientSecret=${GOOGLE_GMAIL_CLIENT_SECRET}
|
|
- Google__GmailRedirectUri=${GOOGLE_GMAIL_REDIRECT_URI}
|
|
# Optional: Outlook / Microsoft 365 mail linking via Microsoft Graph
|
|
- Microsoft__ClientId=${MICROSOFT_CLIENT_ID}
|
|
- Microsoft__ClientSecret=${MICROSOFT_CLIENT_SECRET}
|
|
- Microsoft__TenantId=${MICROSOFT_TENANT_ID}
|
|
- Microsoft__RedirectUri=${MICROSOFT_REDIRECT_URI}
|
|
- Ai__BaseUrl=${AI_SERVICE_BASE_URL:-http://ai-service:8001}
|
|
- Summarizer__BaseUrl=${SUMMARIZER_BASE_URL:-http://ai-service:8001}
|
|
# Email (SMTP)
|
|
# Build metadata should be resolved before deployment. Examples:
|
|
# APP_VERSION=1.0.0
|
|
# APP_COMMIT_SHA=abc1234
|
|
# APP_BUILD_STAMP=2026-03-22 14:00 UTC
|
|
# Do not set literal placeholders like $(git rev-parse --short HEAD) in .env.
|
|
- App__PublicBaseUrl=${APP_PUBLIC_BASE_URL}
|
|
- App__Version=${APP_VERSION}
|
|
- App__CommitSha=${APP_COMMIT_SHA}
|
|
- App__BuildStamp=${APP_BUILD_STAMP}
|
|
- Email__Enabled=${EMAIL_ENABLED}
|
|
- Email__SmtpHost=${EMAIL_SMTP_HOST}
|
|
- Email__SmtpPort=${EMAIL_SMTP_PORT}
|
|
- Email__SmtpEnableSsl=${EMAIL_SMTP_ENABLE_SSL}
|
|
- Email__SmtpTimeoutMs=${EMAIL_SMTP_TIMEOUT_MS}
|
|
- Email__SmtpUser=${EMAIL_SMTP_USER}
|
|
- Email__SmtpPassword=${EMAIL_SMTP_PASSWORD}
|
|
- Email__From=${EMAIL_FROM}
|
|
- Email__FromName=${EMAIL_FROM_NAME}
|
|
expose:
|
|
- "8080"
|
|
networks:
|
|
- default
|
|
- shared_services
|
|
restart: unless-stopped
|
|
|
|
frontend:
|
|
build:
|
|
context: ./job-tracker-ui
|
|
# Next's build type-checker needs more than Docker's default 64MB /dev/shm; too little
|
|
# causes a SIGSEGV during `npm run build`.
|
|
shm_size: '1gb'
|
|
args:
|
|
- NEXT_PUBLIC_GOOGLE_CLIENT_ID=${AUTH_GOOGLE_CLIENT_ID}
|
|
# Optional override; default in production is `/api`
|
|
- NEXT_PUBLIC_API_BASE_URL=${REACT_APP_API_BASE_URL}
|
|
ports:
|
|
- "3000:80"
|
|
depends_on:
|
|
- backend
|
|
networks:
|
|
- default
|
|
- shared_services
|
|
restart: unless-stopped
|
|
|
|
ai-service:
|
|
build:
|
|
context: ./tools/summarizer
|
|
dockerfile: Dockerfile
|
|
environment:
|
|
# Point at an existing/shared Ollama by setting OLLAMA_BASE_URL in .env
|
|
# (e.g. http://<host-ip>:11435). The in-compose ollama service below is
|
|
# opt-in via the "bundled-ollama" profile, so it is NOT started by default
|
|
# and no duplicate Ollama container is created.
|
|
- OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://ollama:11434}
|
|
- OLLAMA_MODEL=${OLLAMA_MODEL:-qwen2.5:7b}
|
|
# AI provider for heavy /cv/* calls: ollama (default) | gemini | groq.
|
|
# Set AI_PROVIDER=gemini + GEMINI_API_KEY in prod to offload a weak local GPU.
|
|
- AI_PROVIDER=${AI_PROVIDER:-ollama}
|
|
- GEMINI_API_KEY=${GEMINI_API_KEY:-}
|
|
- GEMINI_MODEL=${GEMINI_MODEL:-gemini-2.0-flash}
|
|
- GROQ_API_KEY=${GROQ_API_KEY:-}
|
|
- GROQ_MODEL=${GROQ_MODEL:-llama-3.3-70b-versatile}
|
|
ports:
|
|
- "8001:8001"
|
|
networks:
|
|
- default
|
|
- shared_services
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8001/health', timeout=5).read()"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
|
|
# Opt-in only: start with `docker compose --profile bundled-ollama up`.
|
|
# Left out of the default set so deploys reuse an existing/shared Ollama
|
|
# (configured via OLLAMA_BASE_URL) instead of spinning up a duplicate.
|
|
ollama:
|
|
profiles: ["bundled-ollama"]
|
|
image: ollama/ollama:latest
|
|
ports:
|
|
- "11434:11434"
|
|
environment:
|
|
- OLLAMA_HOST=0.0.0.0:11434
|
|
volumes:
|
|
- ollama_data:/root/.ollama
|
|
networks:
|
|
- default
|
|
- shared_services
|
|
restart: unless-stopped
|
|
gpus: all
|
|
healthcheck:
|
|
test: ["CMD", "ollama", "list"]
|
|
interval: 20s
|
|
timeout: 15s
|
|
retries: 10
|
|
start_period: 20s
|
|
|
|
volumes:
|
|
jobtracker_data:
|
|
ollama_data:
|
|
|
|
networks:
|
|
shared_services:
|
|
external: true
|
|
name: jobtracker_shared
|