# Noah Bianchi Studio Noah Bianchi is an original photographer client-delivery platform inspired by the category of premium gallery products such as DLVRD. It is built around one loop: ```text Upload -> Curate -> Customize -> Publish -> Send a private link ``` Photographers get a focused studio dashboard. Clients get a private editorial gallery without creating an account. ## Stack - Frontend: React, TypeScript, Vite, React Router, Tailwind CSS, Framer Motion - Backend: Go 1.24+, Gin, REST/JSON, `database/sql` - Database: PostgreSQL by default; SQLite remains available for lightweight local testing - Storage: MinIO locally through an S3-compatible storage interface - Media: direct browser uploads with presigned URLs, asynchronous preview processing, signed download URLs ## Run Locally Prerequisites: - Docker Desktop with the Compose plugin - Go 1.24 or newer - Node.js 20 or newer - GNU Make, or use the direct commands below From the repository root: ```bash cp .env.example .env npm --prefix frontend install docker compose up -d --wait make migrate make seed ``` Start the API: ```bash make dev ``` Start the frontend in a second terminal: ```bash make frontend-dev ``` Open: - Dashboard: [http://localhost:5173/login](http://localhost:5173/login) - Dev diagnostics: [http://localhost:5173/dashboard/dev](http://localhost:5173/dashboard/dev) - Demo client gallery: [http://localhost:5173/g/emma-james-wedding](http://localhost:5173/g/emma-james-wedding) - API documentation: [http://localhost:8080/swagger/index.html](http://localhost:8080/swagger/index.html) - MinIO console: [http://localhost:9001](http://localhost:9001) ### PowerShell ```powershell Copy-Item .env.example .env npm --prefix frontend install docker compose up -d --wait go -C backend run ./cmd/migrate -dir ../migrations go -C backend run ./cmd/seed ``` In separate terminals: ```powershell go -C backend run ./cmd/server npm --prefix frontend run dev ``` ## Demo Credentials The seed command creates: ```text Email: demo@example.com Password: DemoPassword123! Studio: Noah Bianchi ``` It also creates the published demo gallery: ```text Title: Emma & James Slug: emma-james-wedding Client: Emma & James ``` The demo media uses external placeholder URLs so the gallery is visually useful immediately. Real uploads use MinIO/S3 objects and never store media bytes in PostgreSQL. If an upload stops at 0%, open **Dashboard -> Dev**. The workbench shows the effective browser origin, allowed CORS origins, MinIO reachability, bucket, and the latest client-side upload error. ## Storage Configuration The default `.env.example` uses MinIO: ```dotenv STORAGE_ENDPOINT=localhost:9000 STORAGE_ACCESS_KEY=minioadmin STORAGE_SECRET_KEY=minioadmin STORAGE_BUCKET=gallery-media STORAGE_USE_SSL=false ``` The Go `storage.Storage` interface supports: - `CreateUploadURL` - `CreateDownloadURL` - `Delete` - `Stat` - `Get` - `Put` The current implementation is `MinIO`, using the MinIO Go SDK. Replacing it with AWS S3, Cloudflare R2, or Backblaze B2 only requires another implementation of that interface and provider configuration. Upload flow: ```text Browser -> POST /api/galleries/:id/uploads API -> creates media row + presigned PUT URL Browser -> PUT file directly to MinIO Browser -> POST /api/uploads/:id/complete Worker -> generates image previews and marks media READY ``` Original object keys are private. The API exposes only short-lived signed URLs to authenticated owners or authorized gallery visitors. ## SQLite Mode SQLite is retained as a development/test option. It still requires MinIO for the complete upload workflow, but it does not require PostgreSQL. ```dotenv DB_DRIVER=sqlite SQLITE_PATH=./data/surprise.db ``` Then run the same migration and seed commands: ```bash make migrate make seed make dev ``` PowerShell process variables use `$env:` syntax: ```powershell $env:DB_DRIVER = "sqlite" $env:SQLITE_PATH = ".\data\surprise.db" go -C backend run ./cmd/migrate -dir ../migrations go -C backend run ./cmd/seed go -C backend run ./cmd/server ``` The migration runner selects `migrations/sqlite/` for SQLite and the root migration files for PostgreSQL. ## Product Workflow ### Photographer 1. Register or sign in. 2. Create a gallery with title, client, and description. 3. Drag in photos or videos. 4. Uploads go directly to MinIO using presigned URLs with per-file progress. 5. Set a cover image, ordering, layout, appearance, branding, password, expiry, and client controls. 6. Preview the exact public renderer. 7. Publish and copy `/g/:slug`. ### Client 1. Open the private gallery link. 2. Enter a gallery password if required. 3. Browse responsive image and video media. 4. Open photos in an immersive fullscreen viewer. 5. Favorite photographs anonymously. 6. Download individual originals or request an asynchronous gallery ZIP. ## API Overview Authentication: ```text POST /api/auth/register POST /api/auth/login POST /api/auth/logout GET /api/auth/me PATCH /api/auth/me POST /api/auth/password ``` Authenticated galleries: ```text GET /api/galleries POST /api/galleries GET /api/galleries/:id PATCH /api/galleries/:id DELETE /api/galleries/:id POST /api/galleries/:id/publish POST /api/galleries/:id/unpublish GET /api/galleries/:id/preview ``` Uploads and media: ```text GET /api/galleries/:id/media POST /api/galleries/:id/uploads POST /api/uploads/:id/complete PATCH /api/media/:id POST /api/media/:id/download DELETE /api/media/:id DELETE /api/uploads/:id ``` Public gallery: ```text GET /api/public/galleries/:slug POST /api/public/galleries/:slug/authenticate POST /api/public/galleries/:slug/media/:mediaId/favorite DELETE /api/public/galleries/:slug/media/:mediaId/favorite POST /api/public/galleries/:slug/media/:mediaId/download POST /api/public/galleries/:slug/download-all GET /api/public/galleries/:slug/download-all/:jobId ``` Health: ```text GET /health ``` Swagger UI is available at `/swagger/index.html`; the generated Swagger document is also served at `/swagger/doc.json`. Regenerate the checked-in documentation after changing API annotations with: ```bash make swagger ``` Photographer routes use an HTTP-only signed session cookie. Public visitor identity and gallery access are separate signed cookies; clients do not need accounts. ## Database The gallery platform migration adds: - `users`: photographer accounts and bcrypt password hashes - `galleries`: ownership, slug, publication state, controls, expiry, theme, and branding JSON - `media`: original object metadata, preview keys, processing state, dimensions, and ordering - `favorites`: anonymous gallery-scoped favorite records - `downloads`: individual download audit records - `download_jobs`: asynchronous ZIP status and object key Migration files: - `migrations/003_gallery_platform.sql` for PostgreSQL - `migrations/sqlite/003_gallery_platform.sql` for SQLite The schema does not store uploaded media content. `external_url` exists only to make the development seed gallery useful without shipping copyrighted or binary assets; real uploads have private `storage_key` values instead. ## Project Structure ```text backend/ cmd/server/ Gin API router and worker startup cmd/migrate/ dialect-aware SQL migration runner cmd/seed/ demo photographer and gallery seed internal/auth/ bcrypt auth, signed sessions, visitor access cookies internal/galleries/ gallery CRUD and public gallery responses internal/media/ upload lifecycle, media repository, preview worker internal/storage/ MinIO/S3-compatible storage interface and adapter internal/downloads async individual/ZIP download handling frontend/ src/components/dashboard/ photographer workspace, editor, upload queue src/components/gallery/ shared public/preview renderer and viewer src/features/auth/ authenticated session context src/pages/ auth, dashboard, editor, public gallery routes src/lib/ typed API client and formatting helpers migrations/ PostgreSQL and SQLite SQL migrations docker-compose.yml PostgreSQL and MinIO ``` Preview and public delivery use the same `ClientGallery` component. The preview endpoint changes only authorization and the `preview` banner; it does not create a second mock gallery UI. ## Checks Backend: ```bash make test make lint ``` Frontend: ```bash npm --prefix frontend run build npm --prefix frontend run lint npm --prefix frontend run format:check ``` The backend test suite includes real SQLite migration/repository/auth coverage. A live PostgreSQL/MinIO run requires Docker and should be performed with the clean-checkout commands above. ## Deliberate MVP Boundaries Implemented: - Photographer registration/login/logout - Gallery CRUD and ownership checks - Gallery publication and preview - MinIO presigned direct uploads - Per-file upload progress and cancellation - Image preview/thumbnail worker - Video playback using optimized/original object URLs - Gallery themes, layouts, branding, controls, password, expiry, and cover selection - Public responsive gallery at `/g/:slug` - Fullscreen photo viewer with keyboard and touch navigation - Anonymous favorites - Signed individual downloads - Background ZIP generation and polling Not implemented yet: - Billing, subscriptions, teams, organizations, and CRM - Persistent distributed job queue - FFmpeg video transcoding and streaming manifests - Full HEIC decoding; unsupported image formats fall back to the original object - Production email delivery - Advanced proofing/comments/analytics - Custom domains and cloud archive workflows