49f77a78b95e82959e5a79c70563d5b887b41540
routes/auth.rs — public (register, login, refresh) + protected (me, logout) routes/catalog.rs — assets, stacks, duplicates routes/organization.rs — albums routes/sharing.rs — public (access_by_token) + protected (share, link, revoke) routes/storage.rs — volumes, library paths, quota routes/sidecar.rs — export, import, detect, resolve, full ops routes/processing.rs — jobs, batches, plugins, pipelines routes/mod.rs — merges all, applies require_auth to protected group
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<dyn Port> 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 tokensPOST /auth/refresh— rotates refresh token, issues new pairPOST /auth/logout— revokes all refresh tokens for userrequire_authmiddleware on all protected routes (defense in depth)- Handlers still use
JwtClaimsextractor 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
# 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
docker build -t k-photos .
docker run -e DATABASE_URL=... -e JWT_SECRET=... -e NATS_URL=... -p 8000:8000 k-photos
Worker (background jobs):
docker run -e DATABASE_URL=... -e NATS_URL=... k-photos ./k_photos-worker
License
Languages
Rust
67.7%
TypeScript
31.5%
CSS
0.5%
Dockerfile
0.2%