feat(photos): S3 multipart BLOB storage + original/processed GET + 410 after purge + attach UI

- backend/photos.py: POST /slabs/{id}/photos (multipart original+processed ->
  slab_photos BLOBs, re-upload replaces), GET original/processed (image/png, 410
  once purged), GET list. Cites CAPTURE-95/105/130, PUBLISH-40.
- frontend: create/photos/PhotoAttach.svelte (slab id, original/processed files,
  calibration flag, lineup) + dev toggle.
- tests: 5 photos tests (upload+get both variants, 410 after purge, 404 slab,
  reupload replaces, list).

Verified: pytest 36 passed; gates exit 0; npm build ok; live QA upload->200,
get original/processed->200, purge->410.
This commit is contained in:
2026-09-19 16:47:07 -07:00
parent d83c9d5c21
commit 714a227e57
5 changed files with 340 additions and 0 deletions
+6
View File
@@ -11,6 +11,7 @@ import AxisPanel from './capture/AxisPanel.svelte';
import MeasurePanel from './capture/MeasurePanel.svelte';
import Settings from './create/settings/Settings.svelte';
import SlabList from './create/slabs/SlabList.svelte';
import PhotoAttach from './create/photos/PhotoAttach.svelte';
const gate = typeof window !== 'undefined' ? supported() : 'window unavailable';
const session: CaptureSession = createCaptureSession();
@@ -20,6 +21,7 @@ import SlabList from './create/slabs/SlabList.svelte';
let pngUrl: string | null = null;
let showSettings = false;
let showSlabs = false;
let showPhotos = false;
const unsubCap = session.capture.subscribe((c) => {
cap = c;
@@ -95,6 +97,7 @@ import SlabList from './create/slabs/SlabList.svelte';
<div style="margin-top:1rem;display:flex;gap:8px">
<button on:click={() => (showSettings = !showSettings)}>Settings (dev)</button>
<button on:click={() => (showSlabs = !showSlabs)}>Slabs (dev)</button>
<button on:click={() => (showPhotos = !showPhotos)}>Photos (dev)</button>
</div>
{#if showSettings}
<Settings />
@@ -102,6 +105,9 @@ import SlabList from './create/slabs/SlabList.svelte';
{#if showSlabs}
<SlabList />
{/if}
{#if showPhotos}
<PhotoAttach />
{/if}
</main>
{/if}