Commit graph

14 commits

Author SHA1 Message Date
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
04c18ab263 Deploy script: persist ports to .env + auto-seed on empty DB
Two recurring deploy headaches fixed:
- Port collisions on rerun: find_free_port picks a new port, but without
  saving it, subsequent `docker compose up` calls ignore it and collide
  again. Now written back to .env so Compose picks them up.
- Manual `npm run db:seed` after every fresh deploy. Now auto-runs iff
  the organizations table is empty (first deploy or clean-slate),
  otherwise skips.

Result: `./deploy.sh` is now a single, idempotent command.
2026-04-21 09:37:02 -04:00
DJP
096f0cbe93 deploy.sh: write Apache Include to sites-enabled when it isn't a symlink
Standard Ubuntu Apache uses `a2ensite` to create a symlink from
sites-enabled → sites-available. On optical-dev.oliver.solutions the
sites-enabled file is a separately-managed real file (not a symlink),
so our sed-into-sites-available edited one file while Apache was loading
from another. The deploy reported "Include already present" or "Added
Include" but Apache kept 404-ing because it was reading a different
file that had no such Include.

Detect which case we're in and edit the right one:
- symlink → edit sites-available (edit propagates through the symlink)
- separate file → edit sites-enabled directly, with a warning so ops
  knows the setup drifted from a2ensite convention

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 20:59:12 -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
69f293682a Fix deploy port clash + wire admin invite → add-user flow
Deploy fixes (critical — Phase 0 string-rebrand didn't touch numeric ports):
- deploy.sh APP_PORT 3001 → 3002 (health check was hitting HP's app!)
- apache/dow-prod-tracker.conf — all proxy/websocket rules 3001 → 3002
  (traffic to /dow-prod-tracker would have been served by HP's container)
- deploy.sh: added COMPOSE_PROJECT=dow-prod-tracker and `-p $COMPOSE_PROJECT`
  on every `docker compose` invocation (down, up, exec, logs, ps). This is
  the CLAUDE.md belt-and-braces rule — without it, a future move of the
  deploy dir to `deploy/` would collapse the compose project name to
  `deploy` and collide with any other app in a sibling `deploy/` dir on
  the shared server. The `name:` field in compose covers us today, -p
  covers us tomorrow.
- apache conf header comment rewritten to explain the port convention and
  where to keep it in sync.

Admin add-user flow (answers the open question):
- createInvitation now creates/upserts the placeholder User row
  (email + role + organizationId + isExternal + mustChangePassword=true)
  in addition to the Invitation bookkeeping row. It stores a 24-byte
  password-reset token on BOTH the User (passwordResetToken) and the
  Invitation (token) — same token, so the existing /reset-password/[token]
  page accepts the invite URL without a separate accept endpoint.
- Role enum now includes CLIENT_VIEWER. isExternal auto-derives from role
  but can be overridden. When admin invites a CLIENT_VIEWER, the placeholder
  user lands correctly pre-flagged for external handling.
- POST /api/org/invitations now returns {acceptUrl} — the full
  /reset-password/<token> link admin can hand over out-of-band while SMTP
  is unwired.
- revokeInvitation also clears the reset token on the placeholder user so
  a leaked URL can't be used to claim the account after revocation.
- Deleted /api/invitations/accept (SSO-era — the accept IS the password
  reset now) and removed acceptInvitationSchema from the validator.

Team settings UI (src/app/(app)/settings/team/page.tsx):
- Role dropdown now has "Client (read-only)" alongside Admin/Producer/Artist.
- After a successful invite, a banner shows the accept URL with a Copy
  button so admin can paste it into Teams/email. Dismissible.
- Current-members list renders CLIENT_VIEWER with an amber badge.

Plumbing verified: tsc --noEmit ✓ zero errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 19:06:47 -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
cadba79f55 deploy.sh: replace SECRET check with AZURE_REDIRECT_URI
SPA registration has no client_secret; check the new required var instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 15:30:52 +01:00
Vadym Samoilenko
c412ad7bdf Fix health check URL to include basePath
App is served under /hp-prod-tracker basePath, so the health endpoint
is at /hp-prod-tracker/api/health not /api/health.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 15:29:43 +01:00
Vadym Samoilenko
1950ecc7d6 Restore Apache step: add Include to /etc/apache2 on deploy
On first deploy replaces the old inline hp-prod-tracker block in
optical-dev.oliver.solutions.conf with an Include pointing to
apache/hp-prod-tracker.conf. Idempotent — skips if Include already present.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 15:16:30 +01:00
Vadym Samoilenko
b7d50ad715 Move Apache config to apache/hp-prod-tracker.conf, remove auto-management
Apache config on this server is managed manually in optical-dev.oliver.solutions.conf
(same pattern as cc-dashboard). Deploy script no longer touches Apache.
Config moved to apache/hp-prod-tracker.conf matching amazon-transcreation pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 15:15:44 +01:00
Vadym Samoilenko
63818bc6e2 Rewrite deploy.sh following ppt-tool pattern
Numbered steps matching server conventions: prerequisites install,
git pull with SSH auto-switch, .env validation, docker compose build,
postgres + health-check waits, idempotent Apache Include management,
UFW firewall. Apache step replaces old inline block with a canonical
Include pointing to deploy/apache.conf.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 15:13:24 +01:00
Vadym Samoilenko
548a9d8ef5 Add Apache config snippet and wire into deploy script
deploy/apache.conf: canonical Apache proxy config for hp-prod-tracker —
adds WebSocket passthrough and 500 MB upload limit missing from the
current inline config. deploy.sh now replaces the inline block with an
Include directive on each deploy so the config stays in source control.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 15:11:00 +01:00
Vadym Samoilenko
7e32bbc430 Add idempotent deploy script
Handles initial deploy and updates: git pull via SSH, docker compose
rebuild, health check with timeout, pre-flight .env validation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 15:08:29 +01:00