-
Both are pluggable seams (dev-plan-4.md §1.2 items 1-2), same shape as T20's sendVerificationEmail: a fixed env var gates a real vendor call that isn't implemented yet (none has been picked), and the no-vendor path is what actually runs today. describeGarmentImage() — mode 2 has no admin around to hand-write garments.appearanceDescriptor the way admin_seed garments do, so it needs *something* non-empty to fill that field; today that's a fixed placeholder string, swapped for a real vision-model call once a vendor's picked. moderateGarmentImage() — the requirements doc flags image-content moderation as vendor-TBD but never actually lists it as its own task; this fills that gap. Every call writes a moderation_log row (new dressup-store.js export, M.9 schema) regardless of which branch runs, so the audit trail exists before a real vendor does. Default-permissive when unconfigured — T2's upload endpoint already enforces the real trust-boundary checks (magic-byte type check, 8MB cap, EXIF-stripped re-encode); this only covers the *content* check, which nothing here can perform without a vendor. Neither is unit-tested standalone (server.js doesn't export internal functions, matching sendVerificationEmail's existing precedent) — T27 integration-tests both through the actual upload endpoint. Also adds pricing.js to the tmpdir file-copy list in every test that spawns server.js in isolation — server.js now requires it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011DUiYs6UjxDMkfvnjtsKFo
-
POST /api/auth/register, POST /api/auth/login, POST /api/auth/logout, and a debug GET /api/auth/me (T19's own acceptance criteria calls for it explicitly). Passwords hashed with node:crypto's scryptSync (salt stored alongside the hash, verified with timingSafeEqual) — still zero new dependencies. Sessions live in a separate user_session cookie from admin_session (T6) — the two are unrelated systems, confirmed by a test that a user session can't unlock /api/admin/*. Login and "email not registered" return the identical 401 body so the endpoint can't be used to enumerate registered emails. Every test file that spawns server.js in an isolated tmpdir now also copies db.js alongside it (dance-channel/dressup-generate/dressup-crud/ admin-pages-flow/uploads-route .test.js) — server.js requires db.js unconditionally now, so those child processes were crashing silently without it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Deddn3sM3oQnmvqWSMb4y
-
Spawned server.js with cwd: __dirname (the real repo path, not an isolated copy), so its own test.after() cleanup — rm -rf on "uploads/" — wiped every real uploaded file in the repo on every `node --test` run, not just what the test itself created. Discovered after running the suite several times today silently deleted this session's real character/garment/dance-ref source images (the generated dance videos in library.json were unaffected — those proxy from H3, not served from disk — but admin/dance.html thumbnails broke until the images were re-uploaded). Now spawns from an fs.mkdtempSync isolated copy and cleans up only that tmpdir, matching the pattern already used correctly in dressup-generate.test.js and dance-channel.test.js. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Deddn3sM3oQnmvqWSMb4y
-
POST /api/admin/upload-video: mp4 magic-byte sniff (checks the ftyp box, not offset-0 like images), 50MB cap, no re-encode needed (unlike images, no EXIF/GPS concern for video). Reuses the multipart parser and uploads/ layout from T2's image path via a shared placeInUploads helper.
-
Single shared password (.env ADMIN_PASSWORD) + in-memory HttpOnly session cookie, no accounts/roles — MVCP M.1/M.10's real account system supersedes this in period 3. Covers the existing /api/admin/library/* routes (previously unauthenticated, see architecture.md 1.2's tech-debt note) plus the new upload endpoint. admin.html gets a fetch() interceptor that redirects to the new admin-login.html on any 401 from /api/admin/*, rather than patching every call site.
-
POST /api/admin/upload: hand-rolled multipart parser (no deps installed in this project), real magic-byte sniffing (rejects extension-spoofed files), 8MB cap, and EXIF/GPS stripping via a sips bmp round-trip (macOS-native, no dependency — sips's direct jpeg->jpeg re-encode alone does NOT strip EXIF, only the bmp intermediate does). GET /uploads/* serves the result with a path-traversal guard. forwardToH3Assets() is the T7/T8 hook for pushing a saved upload to H3 Console's own POST /v1/assets (found in the S1-S4 spike) — not covered by the automated suite since it needs a live network call, same treatment as the spike scripts.