# K-Photos Self-hosted media orchestrator and gallery. Alternative to Apple Photos, Google Photos, and Immich. ## Philosophy - **Files are sacred** — original file bytes and embedded metadata are never modified. Ever. - **No lock-in** — all metadata exportable to standard formats (XMP, JSON). Stop using K-Photos and your tagged, organized file system remains intact. - **Virtual layer** — all edits (dates, tags, albums, face regions) live in a separate layer. DB at runtime, sidecar exports for portability. Corrupt the layer? Rebuild from originals. - **Modular** — core works without AI/ML. Face detection, classification, smart search are optional plugins. - **BYOS** — bring your own storage. Local NAS, S3, GCS — the domain doesn't care. ## Architecture Hexagonal / DDD with CQRS. Dependencies point inward: ``` Infrastructure (Axum, Postgres, NATS, S3) -> Adapters (Controllers, Repos, Storage Providers) -> Application (Commands / Queries) -> Domain (Entities, Value Objects, Ports, Services) ``` ### Bounded Contexts | Context | Purpose | |---|---| | **Identity** | Users, roles, RBAC permissions, groups, refresh tokens | | **Storage** | Volumes, library paths, ingestion, quotas, BYOS | | **Catalog** | Assets, metadata layers, stacks, derivatives, duplicates, visibility filtering | | **Organization** | Albums, tags, collections (smart albums) | | **Sharing** | Share scopes, targets, links, invite codes, visibility filters | | **Sidecar** | XMP/JSON export, sync state, conflict resolution | | **Processing** | Jobs, batches, plugins, pipelines, directory scanner | ### Project Structure ``` crates/ domain/ pure Rust — entities, value objects, ports, services common/ errors, events, value objects (SystemId, Checksum, Email, Username, MimeType, RelativePath, etc.) identity/ user, role, permission, group, refresh token storage/ volumes, library paths, ingestion, quotas catalog/ assets, metadata, stacks, derivatives, duplicates organization/ albums, tags, collections sharing/ share scopes, targets, links, invites sidecar/ sidecar records, sync config processing/ jobs, batches, plugins, pipelines application/ CQRS commands + queries with Arc injection identity/commands/ RegisterUser, LoginUser, RefreshToken, Logout identity/queries/ GetProfile storage/commands/ RegisterVolume, RegisterLibraryPath, IngestAsset storage/queries/ CheckQuota catalog/commands/ RegisterAsset, UpdateMetadata, CreateStack, DeleteStack, DeleteAsset, DetectLivePhotos, ResolveDuplicate catalog/queries/ GetTimeline, GetAsset, GetStack, ReadAssetFile, ReadDerivative, SearchAssets organization/ CreateAlbum, ManageAlbumEntries, TagAsset, GetAlbum sharing/ ShareResource, GenerateShareLink, RevokeShare, AccessSharedResource sidecar/ ExportSidecar, DetectChanges, Import, ResolveConflict, FullExport/Import processing/ EnqueueJob, StartJob, CompleteJob, FailJob, ExecutePipeline, ManagePlugin, ConfigurePipeline, ListJobs, BatchProgress testing/ in-memory repo fakes + stub ports (in_memory_repo! macro) api-types/ HTTP request/response DTOs with OpenAPI derives adapters/ auth/ bcrypt password hashing, JWT token issuer (configurable expiry) postgres/ all repository implementations, event store, migrations storage/ local filesystem + S3 (feature-gated) exif/ EXIF metadata extraction thumbnail/ derivative generation sidecar/ XMP sidecar reader/writer event-payload/ domain event serialization event-transport/ composite publisher (NATS + event store) nats/ NATS JetStream transport presentation/ axum handlers, routes, extractors, middleware, parsers bootstrap/ config, DI wiring, entry point worker/ background job runner (NATS consumer) ``` ### Auth - JWT access tokens (1h expiry, configurable) - Refresh token rotation (30d, SHA-256 hashed, stored in Postgres) - `POST /auth/login` — returns access + refresh tokens - `POST /auth/refresh` — rotates refresh token, issues new pair - `POST /auth/logout` — revokes all refresh tokens for user - `require_auth` middleware on all protected routes (defense in depth) - Handlers still use `JwtClaims` extractor for user_id — middleware is the safety net - Admin-only endpoints gated by role check (processing, storage, sidecar management) ## Environment Variables | Variable | Required | Default | Description | |---|---|---|---| | `DATABASE_URL` | yes | — | Postgres connection string | | `JWT_SECRET` | yes | — | HMAC secret for JWT signing | | `NATS_URL` | no | `nats://localhost:4222` | NATS server URL | | `STORAGE_PATH` | no | `./data/media` | Local file storage root | | `HOST` | no | `0.0.0.0` | Bind address | | `PORT` | no | `8000` | Bind port | | `CORS_ALLOWED_ORIGINS` | no | — | Comma-separated origins | | `RUST_LOG` | no | `info` | Log level filter | ## Development ```bash # run tests (no DB required) cargo test --workspace # format + lint cargo fmt --all cargo clippy --workspace ``` 206 tests cover domain entities, services, application use cases, and visibility filtering. ## Docker ```bash docker build -t k-photos . docker run -e DATABASE_URL=... -e JWT_SECRET=... -e NATS_URL=... -p 8000:8000 k-photos ``` Worker (background jobs): ```bash docker run -e DATABASE_URL=... -e NATS_URL=... k-photos ./k_photos-worker ``` ## License [MIT](LICENSE)