dow-prod-tracker/scripts/create-admin.ts
DJP bdb133d49a Phase 5: unified versioning + single-asset-per-revision + holding pen
Reshapes the Revision model around how producers + clients actually
work: one upload per round (image OR video, no references), per-stage
version chain V0.1 → V0.2 → V1 → V1.1 → V1.2 → V2 …, holding pen for
inbound files received while a previous version is in client approval,
and per-pipeline filename matching for inbound ingest.

Schema:
  - Revision: drop roundNumber + multi-key attachments. Add major/minor/
    sentToClient/sentAt/asset (single image-or-video object) + unique
    (deliverableStageId, major, minor).
  - PipelineTemplate: add inboundFilenameRegex (per-pipeline matcher).
  - NEW HoldingPenFile model + HoldingPenSource enum (MANUAL/API/BOX).
  - Empty prod DB → clean ALTER TABLE migration, no backfill.

Send-to-client semantics:
  - The latest internal revision IS the V{n} — promote in place
    (major+=1, minor=0, sentToClient=true). Chain reads V0.1, V0.2, V1,
    V1.1, V1.2, V2, ...
  - sendToClient is the ONLY Box-push trigger. Auto-on-APPROVED removed
    from deliverable-status-service. APPROVED is now an internal "done
    iterating" state, separate from "shipped to client".

Three input channels, one matcher:
  - NEW src/lib/services/inbound-ingest-service.ts — consolidates Box
    webhook, /api/v1/upload, and manual upload-by-filename. One regex
    resolver, one project/deliverable matcher, one routing + notification
    fan-out.
  - box-inbound-service is now a thin wrapper that fetches Box metadata
    and delegates.
  - external-delivery-service.parseInboundFileName takes an optional
    regex override. Default: ^(\d+)_([a-z0-9-]+)(?:_v(\d+))?(?:\.[a-z0-9]+)?$
    captures (1) OMG #, (2) slug, (3) optional version.
  - buildDeliveryNaming now uses {omg}_{slug}_V{major} for Box folders.

Holding pen:
  - When a deliverable is IN_REVIEW (a V{n} is awaiting client decision)
    and a new file arrives via any channel, it lands in HoldingPenFile —
    NOT in the active chain. Producer manually promotes (creates the
    next minor on the chosen stage) or discards.
  - Held files render in a new HoldingPenPanel on the deliverable detail
    page when present. Source pill (MANUAL/API/BOX), parsed identifiers,
    target-stage picker, Promote + Discard buttons.

Per-pipeline regex UI:
  - NEW InboundMatchingRules section in the pipeline editor. Live regex
    compile, sample-filename match test with echoed captures, save with
    server-side regex-compile validation.

Upload simplification:
  - storeRevisionAsset replaces the old processAndStoreImage +
    processAndStoreVideo + multi-key attachment merge. MIME-detects kind,
    preserves PNG alpha flatten + TIFF→PNG + thumbnail for images, and
    keeps the async HLS transcode pipeline for videos.
  - The single revision upload route drops the `type=` parameter.
  - Three legacy components deleted: image-gallery, image-upload-zone,
    video-upload-zone. NEW asset-upload-zone (unified drop zone).

New API:
  - POST /api/stages/:stageId/revisions/:revisionId/send-to-client
  - GET  /api/deliverables/:id/holding-pen
  - POST /api/deliverables/:id/holding-pen/:fileId (promote)
  - DELETE /api/deliverables/:id/holding-pen/:fileId (discard)
  - POST /api/v1/upload (multipart; same matcher as Box webhook)

UI label rollup:
  - src/lib/format-revision-label.ts is the single source of truth.
    Sent revisions render `V{major}`; internal `V{major}.{minor}`.
  - Revision node/timeline, session presenter/builder/summary, revision
    list, stage review panel — all read the helper.
  - Comparison toolbar simplified to cross-revision picker (no more
    reference-vs-current within a single revision).

The deliverable annotation review page is a temporary stub (links back
to the deliverable detail page where the in-row controls live). The
full annotation overlay + comparison surface will be rebuilt against
the single-asset model in a follow-up.

Run on next deploy:
  docker compose -p loreal-prod-tracker exec app npx prisma migrate deploy

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

67 lines
1.8 KiB
TypeScript

// One-shot: wipe all users except the admin, then upsert a single admin
// user with the given email + password. Run inside the app container:
// docker compose -p loreal-prod-tracker exec app \
// npx tsx scripts/create-admin.ts admin@loreal.com 'YourPasswordHere'
import { prisma } from "@/lib/prisma";
import bcrypt from "bcryptjs";
async function main() {
const [, , emailArg, passwordArg] = process.argv;
const email = emailArg ?? "admin@loreal.com";
const password = passwordArg ?? "ChangeMe123!";
if (!email.includes("@")) {
console.error("Usage: tsx scripts/create-admin.ts <email> <password>");
process.exit(1);
}
let org = await prisma.organization.findFirst({ select: { id: true } });
if (!org) {
org = await prisma.organization.create({
data: { name: "L'Oreal", domain: "loreal.com" },
select: { id: true },
});
console.log("Created organization:", org.id);
}
// Wipe everyone else first so the new admin is the only user.
const removed = await prisma.user.deleteMany({
where: { email: { not: email } },
});
if (removed.count > 0) {
console.log(`Removed ${removed.count} existing user(s).`);
}
const hash = await bcrypt.hash(password, 12);
const u = await prisma.user.upsert({
where: { email },
create: {
email,
name: "Admin",
role: "ADMIN",
organizationId: org.id,
passwordHash: hash,
mustChangePassword: false,
isExternal: false,
},
update: {
passwordHash: hash,
role: "ADMIN",
mustChangePassword: false,
organizationId: org.id,
},
});
console.log("✓ Admin ready");
console.log(" email: ", u.email);
console.log(" password:", password);
console.log(" login at /loreal-prod-tracker/login");
await prisma.$disconnect();
}
main().catch((e) => {
console.error(e);
process.exit(1);
});