Asset Reorganization
Date: 2026-03-26 PR: #173 Issue: Closes #172
What changed
All uploaded media is now organized by type and section instead of a flat uploads/ folder.
New structure:
uploads/
photos/{section_id}/ images grouped by the section they belong to
video/ original video files
video/thumbnails/ poster frames (5 per video)
video/processed/ H.264 transcodes (720p, 480p)Upload flow changes
- Backend
/uploadsendpoint acceptssection_idas a Form parameter - Images are stored at
photos/{section_id}/{filename}when section context is available - Videos are stored at
video/{filename}regardless of section - Frontend editor forms thread
section_idthroughRichTextEditortouseImageUploadto the upload proxy - Photo essay editor appends
section_idto its direct FormData uploads
Cloud function changes
- Trigger filter changed from
uploads/touploads/video/prefix - Thumbnails stored at
uploads/video/thumbnails/(wasthumbnails/) - Processed transcodes stored at
uploads/video/processed/(wasprocessed/) - Cascade events (thumbnails, processed files) explicitly skipped
- Portrait video detection via rotation metadata
- Aspect-ratio-preserving thumbnails (640:-2 instead of 640:360)
- Actual output dimensions probed from transcoded files
Migration 0014
Moves all existing GCS files and rewrites all content URLs:
- Images moved from
uploads/{file}.webptouploads/photos/{section_id}/{file}.webp(section determined from containing document) - Videos moved from
uploads/{file}.movtouploads/video/{file}.mov - Thumbnails consolidated from
thumbnails/anduploads/thumbnails/touploads/video/thumbnails/ - Processed consolidated from
processed/anduploads/processed/touploads/video/processed/ - All
src,srcset,poster, anddata-original-srcattributes in HTML content updated - Photo essay
photos[].url,cover_image_url, and srcset fields updated - Video processing job paths updated
- Idempotent: checks destination before moving, skips already-moved URLs
Deploy order
Deploy cloud function first (so new uploads go to video/), then deploy backend (runs migration). If a video processing job is in-flight during migration, the cloud function may write to the old path — re-trigger the video after migration completes.