diff --git a/.env.example b/.env.example index 84a966e..6e86ecf 100644 --- a/.env.example +++ b/.env.example @@ -1,72 +1,62 @@ -# Database backend — "sqlite" (default) or "postgres" -DATABASE_BACKEND=sqlite +# ── Required ────────────────────────────────────────────────── -# Option A: SQLite (default, zero external dependencies) -DATABASE_URL=sqlite://movies.db - -# Option B: PostgreSQL -# DATABASE_BACKEND=postgres -# DATABASE_URL=postgres://user:password@localhost:5432/movies_diary +# Database (SQLite — file auto-created on first run) +DATABASE_URL=sqlite:///data/movies.db # Authentication -JWT_SECRET=change-me -JWT_TTL_SECONDS=86400 +JWT_SECRET=change-me-to-a-random-string -# OMDb/TMDB metadata -OMDB_API_KEY=your-key -TMDB_API_KEY=your-key +# Movie metadata — one of these is required (TMDB preferred) +# TMDB_API_KEY=your-tmdb-key +OMDB_API_KEY=your-omdb-key -# Poster storage — Option A (local) is active. To use S3, comment it out and uncomment Option B: +# ── Recommended ────────────────────────────────────────────── -# Option A: local filesystem (zero external dependencies) -POSTER_STORAGE_BACKEND=local -POSTER_STORAGE_PATH=./posters +# Public URL (used for ActivityPub federation and canonical links) +BASE_URL=https://yourdomain.example.com -# Option B: S3-compatible (MinIO, AWS S3, etc.) -# POSTER_STORAGE_BACKEND=s3 +# Enable sign-ups (default: false — set true so you can register) +ALLOW_REGISTRATION=true + +# ── Image Storage (defaults to local filesystem) ───────────── + +# IMAGE_STORAGE_BACKEND=local # default +# IMAGE_STORAGE_PATH=./images # default + +# S3-compatible alternative (MinIO, AWS S3, etc.): +# IMAGE_STORAGE_BACKEND=s3 # MINIO_ENDPOINT=http://localhost:9000 # MINIO_BUCKET=posters # MINIO_REGION=minio # MINIO_ACCESS_KEY_ID=minioadmin # MINIO_SECRET_ACCESS_KEY=minioadmin -# Optional -HOST=0.0.0.0 -PORT=3000 -BASE_URL=http://localhost:3000 -SECURE_COOKIES=false -ALLOW_REGISTRATION=false -RATE_LIMIT=20 -POSTER_FETCH_TIMEOUT_SECONDS=30 +# ── Optional ───────────────────────────────────────────────── -# Event bus — "db" (default) or "nats" -# The worker binary must run alongside the presentation to process events. -EVENT_BUS_BACKEND=db +# TMDb enrichment (cast, crew, genres — separate from search metadata above) +# TMDB_API_KEY=your-tmdb-key -# Option A: DB queue (default — no extra infrastructure needed) -# Events are persisted in the same database as the app and polled by the worker. -# EVENT_QUEUE_POLL_INTERVAL_MS=500 # polling interval (default: 500ms) -# EVENT_QUEUE_BATCH_SIZE=10 # rows claimed per poll cycle (default: 10) -# EVENT_QUEUE_MAX_ATTEMPTS=5 # retries before dead-lettering (default: 5) - -# Option B: NATS (at-least-once delivery, recommended for higher throughput) -# EVENT_BUS_BACKEND=nats -# NATS_URL=nats://localhost:4222 -# NATS_MODE=jetstream # "jetstream" (default, at-least-once) or "core" (fire-and-forget) -# NATS_SUBJECT_PREFIX=movies-diary.events -# NATS_STREAM_NAME=MOVIES_DIARY_EVENTS -# NATS_CONSUMER_NAME=worker - -# Image conversion (optional — converts stored images to save disk space) -# Disable by default; enable in the worker by setting ENABLED=true. +# Image conversion (converts uploaded posters to AVIF or WebP) # IMAGE_CONVERSION_ENABLED=false -# IMAGE_CONVERSION_FORMAT=avif # avif | webp +# IMAGE_CONVERSION_FORMAT=avif -# Annual Wrap-Up (video generation — optional, requires ffmpeg) +# Wrap-Up video generation (requires ffmpeg in container — included in Docker image) # WRAPUP_FONT_PATH=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf # WRAPUP_LOGO_PATH=./static/logo.webp -# WRAPUP_BG_DIR=./static/wrapup-backgrounds # directory of jpg/png/webp images for video slide backgrounds +# WRAPUP_BG_DIR=./static/wrapup-backgrounds # FFMPEG_PATH=ffmpeg # WRAPUP_MAX_CONCURRENT=2 -RUST_LOG=presentation=debug,tower_http=debug,worker=info,application=info +# Server +# HOST=0.0.0.0 +# PORT=3000 +# RATE_LIMIT=60 +# SECURE_COOKIES=true +# RUST_LOG=presentation=info,tower_http=info,worker=info + +# CORS (for SPA development only) +# CORS_ORIGINS=http://localhost:5173 + +# Event bus — "db" (default, uses same database) or "nats" +# EVENT_BUS_BACKEND=db +# NATS_URL=nats://localhost:4222 diff --git a/README.md b/README.md index b7d11cd..311fecc 100644 --- a/README.md +++ b/README.md @@ -203,38 +203,28 @@ The `application` crate has unit tests for core use cases backed by in-memory fa ## Docker -The image contains both `presentation` (HTTP server) and `worker` (event processor), plus `ffmpeg` and DejaVu fonts for wrap-up video generation. Run them as separate containers sharing the same data volume: +### Quick start ```bash -# Build (SQLite + federation + NATS support) -docker build -t movies-diary \ - --build-arg FEATURES=sqlite,sqlite-federation,nats . - -# HTTP server -docker run -p 3000:3000 \ - -e DATABASE_URL=sqlite:///data/movies.db \ - -e JWT_SECRET=change-me \ - -e OMDB_API_KEY=your-key \ - -e BASE_URL=https://yourdomain.example.com \ - -e EVENT_BUS_BACKEND=nats \ - -e NATS_URL=nats://nats:4222 \ - -v $(pwd)/data:/data \ - movies-diary - -# Event worker (separate container, same image) -docker run \ - -e DATABASE_URL=sqlite:///data/movies.db \ - -e JWT_SECRET=change-me \ - -e OMDB_API_KEY=your-key \ - -e BASE_URL=https://yourdomain.example.com \ - -e EVENT_BUS_BACKEND=nats \ - -e NATS_URL=nats://nats:4222 \ - -v $(pwd)/data:/data \ - --entrypoint ./worker \ - movies-diary +cp .env.example .env +# Edit .env — set JWT_SECRET and OMDB_API_KEY (or TMDB_API_KEY) +docker compose up -d ``` -To build for PostgreSQL: `--build-arg FEATURES=postgres,postgres-federation,nats` +This builds and starts the HTTP server (port 3000) and event worker. Data is persisted in a Docker volume. + +### Manual docker run + +The image contains both `presentation` and `worker` binaries. Run them as separate containers sharing the same data volume: + +```bash +docker build -t movies-diary . + +docker run -p 3000:3000 --env-file .env -v movies-diary-data:/data movies-diary +docker run --env-file .env -v movies-diary-data:/data --entrypoint ./worker movies-diary +``` + +Build for PostgreSQL: `--build-arg FEATURES=postgres,postgres-federation` ## Media Server Integration diff --git a/crates/adapters/object-storage/src/config.rs b/crates/adapters/object-storage/src/config.rs index 4a8137b..73dc546 100644 --- a/crates/adapters/object-storage/src/config.rs +++ b/crates/adapters/object-storage/src/config.rs @@ -6,8 +6,7 @@ pub struct StorageConfig(Arc); impl StorageConfig { pub fn from_env() -> anyhow::Result { - let backend = std::env::var("IMAGE_STORAGE_BACKEND") - .context("IMAGE_STORAGE_BACKEND required (valid values: s3, local)")?; + let backend = std::env::var("IMAGE_STORAGE_BACKEND").unwrap_or_else(|_| "local".into()); let store: Arc = match backend.as_str() { "s3" => build_s3_store( @@ -19,8 +18,7 @@ impl StorageConfig { &std::env::var("MINIO_REGION").unwrap_or_else(|_| "minio".to_string()), )?, "local" => build_local_store( - &std::env::var("IMAGE_STORAGE_PATH") - .context("IMAGE_STORAGE_PATH required when IMAGE_STORAGE_BACKEND=local")?, + &std::env::var("IMAGE_STORAGE_PATH").unwrap_or_else(|_| "./images".into()), )?, other => { anyhow::bail!("Unknown IMAGE_STORAGE_BACKEND: {other:?}. Valid values: s3, local") diff --git a/crates/application/src/config.rs b/crates/application/src/config.rs index ab17dbd..2e9c584 100644 --- a/crates/application/src/config.rs +++ b/crates/application/src/config.rs @@ -25,7 +25,7 @@ impl AppConfig { let rate_limit = std::env::var("RATE_LIMIT") .ok() .and_then(|v| v.parse().ok()) - .unwrap_or(20); + .unwrap_or(60); Self { allow_registration, base_url, diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ab581fe --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,28 @@ +services: + app: + build: + context: . + args: + FEATURES: sqlite,sqlite-federation + ports: + - "3000:3000" + env_file: .env + volumes: + - data:/data + restart: unless-stopped + + worker: + build: + context: . + args: + FEATURES: sqlite,sqlite-federation + entrypoint: ["./worker"] + env_file: .env + volumes: + - data:/data + restart: unless-stopped + depends_on: + - app + +volumes: + data: