# Apiway gallery & storage architecture > How generated and uploaded images flow through Apiway: dual-storage model (Supabase legacy + AWS S3 worker), presigned URL serving, the `gallery_images` table, and the `asset_type` CHECK constraint. Source of truth: `lib/s3.ts`, `scripts/credits-setup.sql`, `docs/GALLERY_ASSET_TYPES.md`, `docs/STORAGE_OPTIMIZATIONS.md`. ## Dual-storage model Apiway's gallery has **two storage backends** decided at write time: | `storage_provider` | Where the bytes live | When | |---|---|---| | `supabase` | Supabase Storage bucket (legacy) | Older generations + light templates that still run in-process | | `s3` | AWS S3 `apiwayimages` (eu-north-1) | Worker-generated images (heavy templates: `ghost-mannequin`, `ai-photoshoots`) | Every read path in the code (`/api/gallery`, `[id]/image`, `[id]/download`) checks `storage_provider` and: - For `supabase`: streams from Supabase Storage with the user's RLS-scoped client. - For `s3`: redirects to a 5-minute presigned S3 URL (`getSignedUrl` from `@aws-sdk/s3-request-presigner`). ## S3 SDK import discipline (production outage) `lib/s3.ts` MUST use **static imports**: ```ts import { S3Client, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; ``` NEVER `createRequire`, `require()`, or dynamic `import()` for the AWS SDK. It silently breaks in the `.next/server` bundle (module not found at runtime), returns null from the getter, and **all S3 gallery images 500**. This was a production outage; see `MEMORY.md` for the post-mortem. ## `gallery_images` table Key columns: - `id uuid pk` - `user_id uuid` — owner; RLS enforces `auth.uid() = user_id` - `storage_provider text` — `'supabase'` or `'s3'` - `storage_key text` — Supabase path or S3 object key - `asset_type text` — bound by a CHECK constraint (see below) - `source_image_id uuid` — links generations to their input - `folder_id uuid` — optional, for shared folders - `mime_type text`, `width int`, `height int` - `created_at timestamptz` ## `gallery_images_asset_type_check` (CHECK constraint) The constraint pins `asset_type` to a known set. **Adding a new asset type without ALTER first → INSERT fails with `violates check constraint`**. Run: ```sql ALTER TABLE public.gallery_images DROP CONSTRAINT IF EXISTS gallery_images_asset_type_check; ALTER TABLE public.gallery_images ADD CONSTRAINT gallery_images_asset_type_check CHECK (asset_type IN ('generated','uploaded_model','uploaded_garment','ai_model','virtual_try_on','ghost_mannequin','photoshoot','reference','background_changed','marketplace_avatar','marketplace_reference','white_studio')); ``` This is documented in `.cursor/rules/s3-and-landing-protection.mdc` and `docs/GALLERY_ASSET_TYPES.md`. ## Three Supabase clients | Client | Use | RLS | |---|---|---| | `createClient()` (server) | Server components, route handlers | Yes (user JWT) | | `createBrowserClient()` | Client components | Yes (user JWT) | | Admin (service role) | Background jobs, cross-user writes | **Bypasses RLS** | Mistakes here cause "missing image" or "invalid token" errors. The admin client is for trusted backend code only. ## Public sharing - Owner-only download endpoint: `/api/gallery//download` (signed, RLS-checked). - Public folder share: `/gallery/p/` and `/g/` — these are middleware-tagged `X-Robots-Tag: noindex, nofollow, noimageindex` to keep them out of Google and AI image indexes. ## Common bugs - Inserting a new `asset_type` value without first running the CHECK ALTER → 500 on every generation of that type. - Calling `createRequire` for the AWS SDK in `lib/s3.ts` → silent null return, all S3 images 500. - Reading worker-generated rows with the user RLS client and forgetting to redirect to S3 → user sees a broken image. - Putting AWS keys in `render.yaml` (it's in git) instead of Render Dashboard → secret leak risk. ## Cross-references - Doc: https://localhost:10000/docs/gallery/finding-images, https://localhost:10000/docs/gallery/download-formats - Doc: https://localhost:10000/docs/gallery/share-public-folder - Long-form site brief: https://localhost:10000/llms-full.txt --- *Generated on 2026-05-01. Source: `lib/s3.ts`, `docs/GALLERY_ASSET_TYPES.md`, `docs/STORAGE_OPTIMIZATIONS.md`, `scripts/credits-setup.sql`.*