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

145 lines
4.4 KiB
Markdown

# 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.
```bash
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.
```bash
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
```json
{ "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.