Commit graph

19 commits

Author SHA1 Message Date
DJP
0b0b3bfadc docker-compose: mount Box secret at /run/secrets/box-config.json
Short-form `- box-config` mounted the file at /run/secrets/box-config
(no extension), but BOX_CONFIG_FILE everywhere references the .json
suffix. Switch to long-form `target:` so the mount path matches the
configured env value and isBoxConfigured() reads the right file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 22:41:30 -04:00
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
DJP
13e069d72c Hardening: Prisma pool bump + webhook rate limiting
Two production-readiness fixes called out in the scaling review
for ~40 concurrent users + daily upstream webhook traffic:

1. Prisma connection pool
   DATABASE_URL now carries ?connection_limit=20&pool_timeout=10
   both in docker-compose.yml (prod) and .env.example (local).
   Default is cpus*2+1 (~5-9 inside a container) which can exhaust
   at peak when mutations + TanStack Query polling coincide.
   Postgres max_connections is 100 so 20 × a couple of app replicas
   leaves headroom.

2. Webhook rate limiter
   New src/lib/webhooks/rate-limit.ts — in-memory sliding-window
   limiter keyed on "<scope>:<ip>". 100 req/min per IP per webhook
   (omg / deliverables / briefs). Applied to all three POST
   handlers; over the limit returns 429 with a Retry-After header.
   Dev-mode bypass honours the matching *_WEBHOOK_ALLOW_INSECURE
   env so stub testing isn't throttled.

   Single-process only — swap the Map for Redis if we scale to
   multiple Next.js instances. Single-instance dow-prod-tracker on
   optical-dev is the target today, so in-memory is sufficient.

Also updated INTEGRATION.md with a rate-limiting section so the
upstream integrator knows what to expect + how to handle 429s.
2026-04-21 17:01:19 -04:00
DJP
7e7ef7b7c1 deploy.sh: auto-detect free host ports, render Apache conf per-deploy
You were right — everything's containerized, the host ports are just
reverse-proxy targets (+ an optional psql peephole for the db). Hardcoding
them is why the local smoke test face-planted on 5492 (amazon-transcreation
was squatting it) and would have done the same any time anything else
bound :3002 or :5492 on the shared server.

docker-compose.yml:
- ports now reference `${APP_HOST_PORT:-3002}` and `${DB_HOST_PORT:-5492}`.
  Defaults match the prior-committed values; override via env vars.
  Container-internal ports (3000, 5432) never change.

apache/dow-prod-tracker.conf → .conf.tmpl:
- Moved to a committed template with `${APP_HOST_PORT}` placeholders in
  both the WebSocket rewrite and the ProxyPass/ProxyPassReverse lines.
- deploy.sh renders the real .conf from the template on every run with
  the chosen port substituted in. Rendered .conf is gitignored so it
  can vary per server without drift.

deploy.sh:
- New is_port_free() and find_free_port() using bash's /dev/tcp — no
  external tool dependency, works identically on Ubuntu and macOS.
- After `docker compose down` (which frees any of OUR ports), probe for
  APP_HOST_PORT starting from 3002 and DB_HOST_PORT from 5492. Pick the
  first free port (scan up to 50). Warn if the preferred port was busy.
  Honors explicit override: `APP_HOST_PORT=3005 ./deploy.sh` works.
- Exports the chosen ports before `docker compose up` so compose
  substitutes them into the `ports:` mappings.
- Renders apache/dow-prod-tracker.conf from the .tmpl with the same
  APP_HOST_PORT, every deploy. If the Apache Include line is already in
  the vhost, we reload Apache anyway (picks up the re-rendered snippet
  in case the port changed).
- Health check URL uses APP_HOST_PORT.
- "Deploy complete" banner now prints the chosen ports.

.gitignore:
- Added docker-compose.override.yml (per-machine local overrides) and
  apache/dow-prod-tracker.conf (rendered by deploy.sh, varies per server).

DEPLOY.md updated with the auto-detection behaviour and override recipe.

Sanity-checked locally:
- is_port_free correctly identifies 5492 busy (amazon-transcreation),
  5493 busy (our smoke-test db), 3002 busy (Docker Desktop grabs 3000-3002
  on this Mac), and picks 5494/3003 respectively.
- `APP_HOST_PORT=3999 DB_HOST_PORT=5999 docker compose config` produces
  published ports 3999 and 5999.
- `bash -n deploy.sh` clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 19:59:30 -04:00
DJP
51e0cf44c7 Phase 0: fork rebrand from hp-prod-tracker
- basePath /dow-prod-tracker, DB name dow_prod_tracker
- docker-compose: name: dow-prod-tracker (volume isolation on shared server), ports 3002/5492
- OMG webhook env vars (secret + insecure toggle)
- NEXT_PUBLIC_AUTH_ENTRA_ENABLED feature flag (MVP uses local auth)
- Dow logo at public/navbar-logo.png
- apache/hp-prod-tracker.conf → apache/dow-prod-tracker.conf
- Text rebrand across README, SETUP, CLAUDE.md, docs, UI labels

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 18:21:39 -04:00
Vadym Samoilenko
250796dd0c Replace Auth.js OAuth with MSAL.js SPA browser flow
- Token exchange now happens entirely in the browser via @azure/msal-browser
  (PKCE, no client_secret — correct for Azure SPA registrations)
- Browser stays on /hp-prod-tracker/login throughout; the /api/auth/callback
  URL never appears in the address bar
- New /api/auth/sso route validates the id_token (jose + Azure JWKS),
  creates User/Account/Session in Prisma, and sets the authjs session cookie
- Auth.js retained only for session reading (auth()) and signOut()
- Fix dev bypass safety gate: use NODE_ENV !== production instead of
  absence of AUTH_MICROSOFT_ENTRA_ID_SECRET
- Rename env vars: AUTH_MICROSOFT_ENTRA_ID_ID → AZURE_CLIENT_ID,
  AUTH_MICROSOFT_ENTRA_ID_TENANT_ID → AZURE_TENANT_ID, remove AUTH_URL
- Remove /api/auth Apache proxy rule (no longer needed)
- Delete OAuthRelay.tsx, add MsalLogin.tsx

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 18:49:43 +01:00
Vadym Samoilenko
17fc539d19 Configure SSO for Azure SPA registration: PKCE without client_secret
- Override authorization redirect_uri to match Azure SPA portal registration
  (login page URL instead of Auth.js callback URL)
- Custom token.request: public client PKCE exchange — no client_secret sent
- Add OAuthRelay client component: forwards ?code&state from login page to
  /api/auth/callback/microsoft-entra-id via window.location.replace
- Add AZURE_REDIRECT_URI env var to docker-compose.yml and .env.example
- Remove AUTH_MICROSOFT_ENTRA_ID_SECRET (SPA registrations don't issue secrets)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 15:25:57 +01:00
DJP
f41dfe6024 Pass AUTH_URL through to container for SSO callback
Auth.js needs AUTH_URL to build the correct redirect URI
including the /hp-prod-tracker basePath.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 15:39:09 -04:00
DJP
d4fa69957e Switch Ollama chat model to gemma4:latest
Gemma 4 loads successfully, supports tool calling with proper
structured output, and responds in ~100ms after initial load.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 15:11:59 -04:00
DJP
49f301f6f4 Use mistral:latest (7B) for Ollama chat — only model that loads on server
Larger models (mistral-large 122B, qwen3-coder 30B, gpt-oss 20B) all
fail to load due to resource limits. mistral:latest (7.2B) loads and
responds successfully.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 14:40:58 -04:00
DJP
93ab4a0947 Switch Ollama chat model to qwen3-coder:30b (mistral-large too large for server)
mistral-large:latest requires 420GB RAM, server only has 345GB.
qwen3-coder:30b is a 30.5B MoE model that fits in ~20GB with good
tool calling and reasoning capabilities.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 14:38:34 -04:00
DJP
6e19c1f046 Add Ollama as fallback AI provider, remove local Ollama container
- Claude is primary, Ollama (internal GPU server) is automatic fallback
- Provider auto-selects: Claude if API key set, else Ollama if reachable
- Ollama uses mistral-large:latest for chat with full tool calling support
- Removed local Ollama Docker service — uses remote at 10.24.42.219
- Chat panel badge shows "Claude" (purple) or "Ollama" (orange)
- OLLAMA_CHAT_HOST and OLLAMA_CHAT_MODEL env vars for configuration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 12:30:13 -04:00
DJP
2f1afed855 Pass ANTHROPIC_API_KEY through to Docker container
The env var was in .env but not listed in docker-compose environment
block, so the container never received it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 12:11:18 -04:00
DJP
4c0e9d32df Dev server deployment: port conflicts, auth bypass, API key, UI fixes
- Remap ports (3001, 5491) to avoid conflicts on shared server
- Remove NODE_ENV guard from DEV_BYPASS_AUTH in middleware, api-utils, layout
- Add API key authentication for external integrations
- Comment out Ollama dependency (optional for dev)
- Fix pipeline graph: topological depth layout for parallel branches
- Fix uploads: move to /data/uploads volume, serve via /api/uploads
- Fix wipe comparison: correct A/B layering, transformOrigin, ResizeObserver fit
- Fix Dockerfile: create /app/public directory for standalone build

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 17:17:54 -04:00
Leivur Djurhuus
010d29656c Clean up deployment config: remove Docker Hub refs, Cloudflare Tunnel
Source code is now on Bitbucket — IT builds from source directly.
Docker Hub and Cloudflare Tunnel are no longer needed. Removed
profiles gate from app service so docker compose up -d works without
flags. Updated .env.example with organized sections and comments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:44:09 -05:00
Leivur Djurhuus
fa55dfc25f Add deployment infrastructure: health endpoint, Docker Compose fixes, tunnel
- Add /api/health endpoint checking DB, pgvector, org, templates,
  dev bypass safety, and AUTH_SECRET presence
- Fix Docker Compose app service: AUTH_SECRET, Entra ID env vars,
  AUTH_TRUST_HOST, app health check
- Add Cloudflare Tunnel service for zero-config HTTPS access
- Exclude health endpoint from auth middleware

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 14:54:15 -05:00
Leivur Djurhuus
2e87a5ff4d Add video upload with HLS streaming infrastructure (A7.1)
FFmpeg in Docker for transcoding, thumbnail extraction, and metadata
parsing. Videos stored in /data/uploads (mounted volume), served via
streaming API route with Range headers and HLS segment caching. Upload
flow: stream-write MP4 → ffprobe metadata → thumbnail → async HLS
transcode → update revision status to ready.

New files:
- video-service.ts: FFmpeg/ffprobe wrapper (HLS, thumbnails, metadata)
- /api/uploads/[...path]: streaming file server with Range support
Modified:
- upload-service.ts: video handling, 500MB limit, async HLS pipeline
- upload route: accepts video/referenceVideo types
- Dockerfile: ffmpeg + /data/uploads directory
- docker-compose.yml: uploads_data volume

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 23:52:40 -05:00
Leivur Djurhuus
e5b398d7da feat: Implement automation event bus and rule engine
- Add event bus for dispatching automation events with handlers.
- Create rule engine to evaluate events against defined triggers.
- Introduce chat provider to interface with Claude API and Ollama fallback.
- Define tool schemas for Claude-compatible operations.
- Implement tool executor to map tool calls to service layer functions.
- Develop automation service for CRUD operations on rules and event handling.
2026-03-12 11:20:21 -05:00
Leivur Djurhuus
9d5acf1683 feat: add Smart Search Panel with semantic search capabilities
- Implemented Smart Search Panel component for enhanced project and deliverable search functionality.
- Introduced useSemanticSearch and useOllamaHealth hooks for managing search queries and AI availability.
- Developed embedding-service to generate and store vector embeddings for projects and deliverables.
- Created semantic-search-service to handle vector search, structural query detection, and LLM summarization.
- Added support for hybrid search combining structural filters and semantic queries.
- Integrated UI components for displaying search results and user interactions.
2026-03-06 16:13:36 -06:00