init
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user