fix(fe): upload slab photos to the server before publish

ensureServerSlab sent calibration_photo_id at create, but slab_photos rows are
only created by POST /slabs/{id}/photos, which the frontend never called - so
the INSERT hit FOREIGN KEY (calibration_photo_id REFERENCES slab_photos(id))
and every create failed with a generic 400, blocking the whole create->publish
walk.

- stop sending calibration_photo_id at create
- upload every draft photo (original + processed PNG) right after the slab row
  exists, via new review/photoUpload.ts (multipart FormData, same error
  envelope so failures surface through fail(e))
- link calibration_photo_id in the existing review PUT, once the row exists
- extract the role rule into lineup.photoRole() so the review strip and the
  uploader cannot disagree
This commit is contained in:
2026-09-25 12:14:35 -07:00
parent 5c7e5c663e
commit e5afc7524b
5 changed files with 87 additions and 4 deletions
+12
View File
@@ -5,6 +5,7 @@ import {
moveItem,
missingMinimums,
lineupValid,
photoRole,
roleLabel,
resolvePhotoUrl,
type LineupItem,
@@ -63,6 +64,17 @@ test('lineup: missingMinimums only counts KEPT photos', () => {
assert.deepEqual(missingMinimums([p('cal', 'calibration', false), p('ov', 'overlay')]), ['calibration']);
});
test('lineup: photoRole is the single role rule (cal > overlay > batch)', () => {
assert.equal(photoRole('cal', 'cal', true), 'calibration');
assert.equal(photoRole('ov', 'cal', true), 'overlay');
assert.equal(photoRole('b1', 'cal', false), 'batch');
// Calibration wins even if it carries a processed copy.
assert.equal(photoRole('cal', 'cal', true), 'calibration');
// No calibration selected: processed photos are overlays.
assert.equal(photoRole('ov', '', true), 'overlay');
assert.equal(photoRole('b1', '', false), 'batch');
});
test('lineup: roleLabel is owner-facing', () => {
assert.match(roleLabel('calibration'), /clean/);
assert.match(roleLabel('overlay'), /overlay/);