chore: scaffold monorepo for EventSnap
- Rust/Axum backend skeleton with all crates, multi-stage Dockerfile - SvelteKit + TypeScript frontend with Tailwind CSS v4, adapter-node, Dockerfile - docker-compose.yml: db (postgres:16) → app → frontend → caddy with healthcheck and named volumes - Caddyfile: TLS via Let's Encrypt, cache headers, API/media routing to backend - .env.example: all environment variables documented with defaults - README.md: project overview, features, stack, deploy guide, roadmap - .gitignore: excludes secrets, build artifacts, node_modules, media uploads Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
197
README.md
Normal file
197
README.md
Normal file
@@ -0,0 +1,197 @@
|
||||
# EventSnap
|
||||
|
||||
> A private, QR-code-accessed photo & video sharing platform for weddings, birthdays, and personal events — built for guests, run by you.
|
||||
|
||||
---
|
||||
|
||||
## What is EventSnap?
|
||||
|
||||
At private events, photos and videos are scattered across dozens of guests' phones and never truly shared. Existing solutions (WhatsApp groups, Google Photos) require accounts, expose personal data, and lack event-specific social features.
|
||||
|
||||
**EventSnap** gives every guest instant, frictionless access to a shared, living gallery — **no app store, no email, no password.**
|
||||
|
||||
A guest scans the QR code on their way in, types their name, and is immediately part of a shared moment. They upload, react, and comment throughout the day. After the event, the host releases the gallery — every guest walks away with a beautiful offline HTML keepsake and the full archive.
|
||||
|
||||
**Project type:** Mobile-first PWA — runs in any browser, no installation required.
|
||||
**Scale:** Personal / private use — one event at a time, ~100 guests, ~1,000 files.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### MVP
|
||||
|
||||
| Area | Feature |
|
||||
|------|---------|
|
||||
| **Onboarding** | QR code join flow, name-only registration, persistent JWT + recovery PIN, 30-day sessions |
|
||||
| **Uploads** | Photo & video from library or live camera, client-side IndexedDB queue, per-file progress & retry, captions + #hashtags |
|
||||
| **Processing** | Lossless server-side compression, feed preview generation, ffmpeg video thumbnails |
|
||||
| **Feed** | Chronological grid, real-time SSE updates, hashtag filtering, likes & comments |
|
||||
| **Host Dashboard** | Ban/unban guests, delete content, promote to host, lock event, release gallery for export |
|
||||
| **Admin Dashboard** | All host permissions + configure limits, rates, quota tolerance, disk usage widget |
|
||||
| **Export** | On-demand ZIP (full-quality originals) + self-contained offline `Memories.html` viewer |
|
||||
|
||||
### Planned (v1.x)
|
||||
|
||||
- Individual file download button
|
||||
- Low-disk alert (< 10 GB free)
|
||||
- Event banner / cover image
|
||||
- Chunked resumable upload for large videos
|
||||
- Host-curated story highlights
|
||||
- Slideshow / presentation mode
|
||||
|
||||
---
|
||||
|
||||
## Tech Stack
|
||||
|
||||
| Layer | Technology |
|
||||
|-------|-----------|
|
||||
| Frontend | SvelteKit + TypeScript |
|
||||
| Styling | Tailwind CSS v4 |
|
||||
| Backend | Rust + Axum |
|
||||
| Async | Tokio |
|
||||
| Database | PostgreSQL 16 via SQLx (compile-time query checking) |
|
||||
| Auth | Custom JWT (`jsonwebtoken`) + bcrypt PINs |
|
||||
| Image processing | `image` crate + `oxipng` (lossless compression) |
|
||||
| Video processing | ffmpeg via `tokio::process::Command` |
|
||||
| File storage | Local disk (`/media/`) |
|
||||
| Real-time | Axum SSE + `tokio::sync::broadcast` |
|
||||
| Export | `async-zip` (streaming ZIP) + `minijinja` (HTML bundle) |
|
||||
| Rate limiting | `tower-governor` (token-bucket, DB-configurable) |
|
||||
| Reverse proxy | Caddy 2 (automatic HTTPS via Let's Encrypt) |
|
||||
| Containers | Docker + Docker Compose |
|
||||
| Infrastructure | Hetzner CX33 (4 vCPU, 8 GB RAM, 80 GB SSD) |
|
||||
|
||||
---
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
eventsnap/
|
||||
├── backend/ # Rust + Axum API server
|
||||
│ ├── src/
|
||||
│ ├── Cargo.toml
|
||||
│ └── Dockerfile
|
||||
├── frontend/ # SvelteKit PWA
|
||||
│ ├── src/
|
||||
│ ├── svelte.config.js
|
||||
│ └── Dockerfile
|
||||
├── docker-compose.yml
|
||||
├── Caddyfile
|
||||
└── .env.example
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [Docker](https://docs.docker.com/engine/install/) (includes Compose plugin)
|
||||
- A domain name with an A record pointing to your server
|
||||
|
||||
### Deploy on a fresh VPS
|
||||
|
||||
```bash
|
||||
# 1. Clone the repository
|
||||
git clone https://git.mc02.dev/fabi/EventSnap.git
|
||||
cd EventSnap
|
||||
|
||||
# 2. Configure environment
|
||||
cp .env.example .env
|
||||
nano .env # set DOMAIN, JWT_SECRET, ADMIN_PASSWORD_HASH, EVENT_NAME, etc.
|
||||
|
||||
# 3. Start the stack
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Caddy automatically obtains a Let's Encrypt certificate on first start. The app is live at `https://DOMAIN` within ~30 seconds.
|
||||
|
||||
### Generate required secrets
|
||||
|
||||
```bash
|
||||
# JWT secret (64 random bytes)
|
||||
openssl rand -hex 64
|
||||
|
||||
# Admin password hash (bcrypt, cost 12)
|
||||
htpasswd -bnBC 12 "" yourpassword | tr -d ':\n'
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
See [.env.example](.env.example) for the full list with descriptions and defaults. Key variables:
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `DOMAIN` | Public domain for TLS (e.g. `my-wedding.example.com`) |
|
||||
| `JWT_SECRET` | 64-byte random hex string for signing JWTs |
|
||||
| `ADMIN_PASSWORD_HASH` | bcrypt hash of the admin dashboard password |
|
||||
| `EVENT_NAME` | Display name shown to guests |
|
||||
| `EVENT_SLUG` | URL-safe event identifier |
|
||||
| `DATABASE_URL` | PostgreSQL connection string |
|
||||
|
||||
---
|
||||
|
||||
## Docker Compose Stack
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Caddy :80 / :443 (TLS termination) │
|
||||
└────────────┬────────────────────────┘
|
||||
│
|
||||
┌────────┴────────┐
|
||||
│ │
|
||||
┌───▼────┐ ┌─────▼──────┐
|
||||
│ app │ │ frontend │
|
||||
│ :3000 │ │ :3001 │
|
||||
│ (Rust) │ │ (SvelteKit)│
|
||||
└───┬────┘ └────────────┘
|
||||
│
|
||||
┌───▼────┐
|
||||
│ db │
|
||||
│ :5432 │
|
||||
│(Postgres│
|
||||
└────────┘
|
||||
```
|
||||
|
||||
- `/api/*` and `/media/*` → Rust backend
|
||||
- Everything else → SvelteKit frontend
|
||||
- Named volumes: `postgres_data`, `media_data`, `caddy_data`
|
||||
|
||||
---
|
||||
|
||||
## Backup
|
||||
|
||||
```bash
|
||||
# Database snapshot
|
||||
pg_dump $DATABASE_URL | gzip > /media/backups/db_$(date +%Y-%m-%d).sql.gz
|
||||
|
||||
# Weekly offsite sync (Hetzner Storage Box or similar)
|
||||
rsync -az /opt/eventsnap/media/ user@storagebox.example.com:backup/eventsnap/
|
||||
```
|
||||
|
||||
The `/media` volume holds originals, previews, thumbnails, exports, and DB backups — a single path to back up.
|
||||
|
||||
---
|
||||
|
||||
## Development Roadmap
|
||||
|
||||
- [x] Project blueprint & architecture
|
||||
- [x] Monorepo scaffold (`backend/`, `frontend/`, Docker Compose)
|
||||
- [ ] DB schema + SQLx migrations
|
||||
- [ ] Auth flow (join, JWT, PIN recovery)
|
||||
- [ ] Upload pipeline (multipart → compression worker → SSE broadcast)
|
||||
- [ ] Client upload queue (IndexedDB, progress, retry)
|
||||
- [ ] Gallery feed (grid, SSE, hashtag filters)
|
||||
- [ ] Camera capture (`getUserMedia`)
|
||||
- [ ] Host Dashboard
|
||||
- [ ] Admin Dashboard
|
||||
- [ ] Export engine (ZIP + offline HTML)
|
||||
- [ ] Rate limiting middleware
|
||||
- [ ] End-to-end test event (10+ real devices)
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
Private project — all rights reserved.
|
||||
Reference in New Issue
Block a user