This commit is contained in:
2026-08-22 02:59:16 +02:00
commit 6a5bb1d699
100 changed files with 17409 additions and 0 deletions
+322
View File
@@ -0,0 +1,322 @@
# Northline Delivery Studio
Northline 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+, `net/http`, 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)
- 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: Northline Studio
```
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
```
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
```
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/ API 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
```
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