dow-prod-tracker/docs/EXTERNAL_API.md
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

4.4 KiB

L'Oréal Studio Tracker — External API (v1)

Server-to-server API for creating projects and deliverables from external systems (e.g. OMG, brief intake forms, integration scripts).

Base URL: https://optical-dev.oliver.solutions/loreal-prod-tracker/api/v1

Auth

Send the shared API key on every request:

x-api-key: <API_KEY>

The key matches process.env.API_KEY on the server. Requests with a missing or wrong key get 401 Unauthorized.

Optionally specify which organization the request applies to:

x-org-id: <organization-id>

If omitted, requests apply to the first organization in the database (useful in single-org deployments).

Idempotency

POST endpoints honour Idempotency-Key. A retried request with the same key + same payload returns the cached response without re-executing:

idempotency-key: 7f3c8e2a-...-deterministic-uuid

The same key with a different payload returns 409 Conflict. Records expire after 24 hours.

Endpoints

GET /api/v1/projects

List all projects visible to the caller. Returns the same shape as the in-app /api/projects.

POST /api/v1/projects

Create a project. If pipelineTemplateId is omitted, the org's default pipeline template is auto-applied. Body matches createProjectSchema (see src/lib/validators/project.ts).

Required fields:

  • projectCode (string, unique per org)
  • name (string)

Recommended fields:

  • omgJobNumber (string) — enables Box-folder matching downstream
  • clientTeamId (string) — drives team visibility
  • requestorUserId (string) — project owner
  • priority (LOW | MEDIUM | HIGH | URGENT)
  • status (PIPELINE | ACTIVE | …)
  • startDate, dueDate (ISO 8601)

Returns 201 + the created project.

curl -X POST \
  -H "x-api-key: $API_KEY" \
  -H "idempotency-key: $(uuidgen)" \
  -H "content-type: application/json" \
  -d '{
        "projectCode": "API-001",
        "name": "Test integration project",
        "omgJobNumber": "12345",
        "priority": "MEDIUM"
      }' \
  https://optical-dev.oliver.solutions/loreal-prod-tracker/api/v1/projects

GET /api/v1/projects/{projectId}

Read-back. Same shape as in-app /api/projects/{id} — includes deliverables, stages, attachments, requestor user, client team.

POST /api/v1/projects/{projectId}/deliverables

Create a deliverable on the given project. Pipeline stages auto-applied from the project's template (Deliverable.pipelineTemplate cascades to DeliverableStage rows). Body matches createDeliverableSchema.

Required fields:

  • name (string)

Common fields:

  • priority, dueDate, notes, externalId (idempotency for upstream webhook callers — distinct from the Idempotency-Key header)

Returns 201 + the created deliverable with its stage chain populated.

curl -X POST \
  -H "x-api-key: $API_KEY" \
  -H "idempotency-key: $(uuidgen)" \
  -H "content-type: application/json" \
  -d '{
        "name": "Hero banner",
        "priority": "HIGH",
        "dueDate": "2026-06-30T12:00:00Z"
      }' \
  https://optical-dev.oliver.solutions/loreal-prod-tracker/api/v1/projects/$PROJECT_ID/deliverables

Error shapes

{ "error": "Project code \"X\" already exists" }

All errors return JSON with an error string. Status codes:

  • 400 — validation error (zod issues joined into one message)
  • 401 — missing/wrong API key
  • 404 — project not visible (RBAC) or not found
  • 409 — Idempotency-Key reused with a different payload
  • 500 — server error (check server logs)

What's NOT here

  • Update/delete endpoints — out of scope for v1. External callers create; in-app users update.
  • Deliverable read-back — GET /api/v1/projects/{id} includes deliverables in its response. A dedicated deliverable read endpoint can be added if external callers need it.
  • Bulk endpoints — call POST per project/deliverable.

Internal notes

  • Auth path: src/middleware.ts short-circuits API-key requests before the session check. src/lib/api-utils.ts getAuthSession() resolves the key into a synthetic ADMIN session for the chosen org.
  • Idempotency: src/lib/api/idempotency.ts + IdempotencyRecord prisma model. 24-hour TTL — sweep via cron (see scripts/).
  • Default pipeline fallback: src/lib/services/project-service.ts createProject() — looks up the org's PipelineTemplate.isDefault if no pipelineTemplateId is supplied.