dow-prod-tracker/docker-compose.yml
DJP 1b73d6b8db L'Oréal rebuild: restore review workflow, full rename, /api/v1, Box integration
Four phases shipped together. Each is a logical deploy unit on its own;
keeping the diff atomic so the rename runbook + migrations stay aligned.

Phase 1 — restore HP's formal review workflow
  - Prisma: FeedbackItem, ReviewSession, ReviewSessionItem + enums
  - New ApprovalType (NONE | SIMPLE | FORMAL) on PipelineStageDefinition
    and PipelineStageTemplate. Stage row UI branches per type.
  - feedback-service + review-session-service ported from HP (no ColorProbe)
  - annotation-service auto-creates a FeedbackItem; revision-service
    carries forward unresolved action items into the new revision.
  - API: /api/reviews/*, /api/stages/[id]/feedback, /api/feedback/[id]
  - Hooks: use-feedback, use-review-sessions
  - UI: feedback-checklist, feedback-item-card, feedback-progress-bar,
    create-session-dialog, session-builder, session-presenter,
    session-summary, plus a new stage-review-panel
  - Pages: /reviews list + detail, deliverable annotation review page
  - Pipeline editor gets the approvalType select; sidebar gets Reviews

Phase 2 — full Dow Jones → L'Oréal rebrand + slug rename
  - URL slug /dow-prod-tracker → /loreal-prod-tracker (next.config,
    base path, redirects)
  - docker-compose name + DB → loreal_prod_tracker; server path
    /opt/loreal-prod-tracker; apache template renamed
  - All visible strings → L'Oréal; sidebar bg #002B5C → black
  - docs/RENAME_RUNBOOK.md describes the one-shot server migration
  - Internal modules dow-excel-service/dow-import + OMG webhook domain
    dowjones.com deliberately preserved (orthogonal to the rebrand)

Phase 3 — external /api/v1 for projects + deliverables
  - API-key auth already in middleware; finished idempotency support
    via new IdempotencyRecord model + src/lib/api/idempotency.ts
  - Default-pipeline fallback in createProject when no template id given
  - POST/GET /api/v1/projects + POST /api/v1/projects/[id]/deliverables
  - docs/EXTERNAL_API.md with curl examples

Phase 4 — Box bidirectional integration
  - JWT app-auth via jose (no extra deps). Config mounted as a docker
    compose secret; deploy.sh stubs an empty {} so compose can start
    before the operator drops the real JSON.
  - Outbound: pushDeliverableToBox auto-fires on !APPROVED → APPROVED
    in deliverable-status-service; "Send to client (Box)" manual button
    on the approval stage row. Folder naming
    {omgJobNumber}_{slug}_v{round}. 3-attempt exp backoff. BoxPushLog
    audit.
  - Inbound: /api/webhooks/box receives Box's signed events, matches by
    OMG # + slug, creates a new Revision, routes to assignee or notifies
    project owner. BoxInboundLog audit + two new NotificationType
    values (BOX_UNMATCHED_FILE, NEW_FILE_AWAITING_REVIEWER).
  - Naming-convention logic isolated in external-delivery-service so an
    OMG-API transport can swap in later without touching matchers.
  - Admin /settings/box page surfaces config status + recent activity.

Three Prisma migrations to apply on next deploy:
  20260512000000_restore_review_workflow
  20260512100000_idempotency_records
  20260512200000_box_integration

URL rename is a one-shot — see docs/RENAME_RUNBOOK.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:51:53 -04:00

105 lines
4.6 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

name: loreal-prod-tracker
services:
# ─── PostgreSQL with pgvector ───────────────────────────
db:
image: pgvector/pgvector:pg17
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
POSTGRES_DB: loreal_prod_tracker
# Host port is overridable via DB_HOST_PORT env var — deploy.sh auto-picks
# a free one if 5492 is taken on the host. The container-internal port
# (5432) never changes — the app connects to db:5432 over the Docker
# network and doesn't care what host port (if any) is mapped.
ports:
- "${DB_HOST_PORT:-5492}:5432"
volumes:
- pgdata:/var/lib/postgresql/data
- ./docker/db-init.sql:/docker-entrypoint-initdb.d/01-pgvector.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
# ─── Next.js app ───────────────────────────────────────
app:
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
# Host port is overridable via APP_HOST_PORT env var — deploy.sh auto-picks
# a free one if 3002 is taken, and writes the chosen port into the Apache
# reverse-proxy config (apache/loreal-prod-tracker.conf) at the same time.
ports:
- "${APP_HOST_PORT:-3002}:3000"
environment:
# DATABASE_URL tuning knobs matter at ~40 concurrent users:
# connection_limit — how many pooled connections Prisma will
# open per app instance. Default is cpus*2+1 (~5-9 inside a
# container), which can run out at peak when mutations + query
# polling coincide. 20 gives plenty of headroom for this scale.
# pool_timeout — seconds to wait for a free connection before
# failing the request (default 10). 10s matches the default
# and stays explicit.
# Postgres side: default max_connections is 100, so 20 × a few
# app replicas is well below the ceiling.
DATABASE_URL: postgresql://postgres:${DB_PASSWORD:-postgres}@db:5432/loreal_prod_tracker?schema=public&connection_limit=20&pool_timeout=10
# Ollama — points to internal GPU server for embeddings + chat fallback
OLLAMA_HOST: ${OLLAMA_HOST:-http://10.24.42.219:11434}
OLLAMA_CHAT_HOST: ${OLLAMA_CHAT_HOST:-http://10.24.42.219:11434}
OLLAMA_CHAT_MODEL: ${OLLAMA_CHAT_MODEL:-gemma4:latest}
OLLAMA_EMBED_MODEL: ${OLLAMA_EMBED_MODEL:-nomic-embed-text}
NODE_ENV: production
AUTH_SECRET: ${AUTH_SECRET}
AUTH_TRUST_HOST: "true"
# Azure SPA registration — PKCE in browser, no client secret
AZURE_CLIENT_ID: ${AZURE_CLIENT_ID}
AZURE_TENANT_ID: ${AZURE_TENANT_ID}
AZURE_REDIRECT_URI: ${AZURE_REDIRECT_URI:-}
CRON_SECRET: ${CRON_SECRET:-change-me}
API_KEY: ${API_KEY:-}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
ANTHROPIC_MODEL: ${ANTHROPIC_MODEL:-}
DEV_BYPASS_AUTH: ${DEV_BYPASS_AUTH:-false}
DEV_USER_ID: ${DEV_USER_ID:-}
# OMG webhook (Shashank pending — stub until payload confirmed)
OMG_WEBHOOK_SECRET: ${OMG_WEBHOOK_SECRET:-}
OMG_WEBHOOK_ALLOW_INSECURE: ${OMG_WEBHOOK_ALLOW_INSECURE:-false}
# Auth: Entra SSO stays coded but gated. Flip to "true" post-MVP once redirect URI is live.
NEXT_PUBLIC_AUTH_ENTRA_ENABLED: ${NEXT_PUBLIC_AUTH_ENTRA_ENABLED:-false}
# Box integration (Phase 4). BOX_CONFIG_FILE points at the mounted
# JSON secret below. The other vars come from .env on the host.
BOX_CONFIG_FILE: ${BOX_CONFIG_FILE:-/run/secrets/box-config.json}
BOX_OUT_FOLDER_ID: ${BOX_OUT_FOLDER_ID:-}
BOX_WATCH_FOLDER_ID: ${BOX_WATCH_FOLDER_ID:-}
BOX_WEBHOOK_PRIMARY_KEY: ${BOX_WEBHOOK_PRIMARY_KEY:-}
BOX_WEBHOOK_SECONDARY_KEY: ${BOX_WEBHOOK_SECONDARY_KEY:-}
BOX_WEBHOOK_ALLOW_INSECURE: ${BOX_WEBHOOK_ALLOW_INSECURE:-false}
volumes:
- uploads_data:/data/uploads
secrets:
- box-config
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://localhost:3000/api/health || exit 1"]
interval: 15s
timeout: 5s
retries: 3
start_period: 30s
volumes:
pgdata:
uploads_data:
# Box JWT app config. Drop the JSON downloaded from the Box developer
# console at ./secrets/box-config.json on the host before `docker compose
# up`. Until that file exists, the app starts but Box features are
# disabled (isBoxConfigured() returns false; UI hides "Send to client").
secrets:
box-config:
file: ./secrets/box-config.json