From 4e2fc9906578a4634a5374a5d975d435856643ca Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 31 May 2026 05:32:54 +0200 Subject: [PATCH] docs: add proper README and MIT license --- LICENSE | 21 +++++++ README.md | 178 ++++++++++++++++++++---------------------------------- 2 files changed, 86 insertions(+), 113 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..31bafd2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Gabriel Kaszewski + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 88155bf..dbf7ddd 100644 --- a/README.md +++ b/README.md @@ -1,137 +1,89 @@ -# k-template +# K-Photos -A cargo-generate template for personal Rust web services. Gives you auth, persistence, logging, CORS, and API docs out of the box so you can start writing domain code immediately. +Self-hosted media orchestrator and gallery. Alternative to Apple Photos, Google Photos, and Immich. -Follows the same hexagonal/ports-and-adapters architecture used in [thoughts](https://git.gabrielkaszewski.dev/GKaszewski/thoughts) and [movies-diary](https://git.gabrielkaszewski.dev/GKaszewski/movies-diary). +## Philosophy -## What you get +- **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. -- **Full hexagonal architecture** — `domain` → `application` → `adapters` → `presentation` → `bootstrap`, each as a separate crate with clear boundaries -- **JWT auth wired end-to-end** — register, login, and `GET /auth/me` working from day one -- **SQLite or PostgreSQL** — chosen at generation time, migrations included -- **CORS + structured logging** — tower-http middleware configured in bootstrap -- **Scalar API docs** at `/scalar`, OpenAPI JSON at `/api-docs/openapi.json` -- **Optional worker binary** — tokio-based background job runner with an example job -- **Optional OIDC stub** — placeholder adapter for OAuth2/OpenID Connect flows -- **Docker-ready** — multi-stage Dockerfile with dependency layer caching, no live DB needed at build time +## Architecture -## Generate a new project +Hexagonal / DDD with CQRS. Dependencies point inward: -```bash -cargo generate --git https://git.gabrielkaszewski.dev/GKaszewski/k-template.git +``` +Infrastructure (Axum, Postgres, NATS, S3) + -> Adapters (Controllers, Repos, Storage Providers) + -> Application (Commands / Queries) + -> Domain (Entities, Value Objects, Ports, Services) ``` -You'll be prompted for: +### Bounded Contexts -| Option | Choices | Default | -|--------|---------|---------| -| `project_name` | any snake_case string | — | -| `database` | `sqlite`, `postgres` | `sqlite` | -| `worker` | bool | false | -| `auth_oidc` | bool | false | +| Context | Purpose | +|---|---| +| **Identity** | Users, roles, RBAC permissions, groups | +| **Storage** | Volumes, library paths, ingestion, quotas, BYOS | +| **Catalog** | Assets, metadata layers, stacks, derivatives, duplicates | +| **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 | -## Generated project structure +### Project Structure ``` crates/ - domain/ pure Rust — entities, value objects, port traits, errors - application/ use cases (RegisterUser, LoginUser, GetProfile) + test fakes - api-types/ shared request/response DTOs with OpenAPI derives - adapters/ - sqlite/ sqlx SQLite UserRepository + migrations - postgres/ sqlx PostgreSQL UserRepository + migrations - auth/ BcryptPasswordHasher, JwtTokenIssuer, OidcAdapter stub - presentation/ axum handlers, JwtClaims extractor, routes, Scalar mount - bootstrap/ Config from env, factory wiring, main entry point - worker/ (optional) Job trait, JobRunner, ExampleJob, WorkerConfig + domain/ pure Rust — entities, value objects, ports, services + common/ errors, events, value objects (SystemId, Checksum, etc.) + identity/ user, role, permission, group + 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 + identity/queries/ GetProfile + storage/commands/ RegisterVolume, RegisterLibraryPath, IngestAsset + storage/queries/ CheckQuota + catalog/commands/ RegisterAsset, UpdateMetadata + catalog/queries/ GetTimeline, GetAsset + organization/ CreateAlbum, ManageAlbumEntries, TagAsset, GetAlbum + sharing/ ShareResource, GenerateShareLink, RevokeShare, AccessSharedResource + sidecar/ ExportSidecar, DetectChanges, Import, ResolveConflict, FullExport/Import + processing/ EnqueueJob, StartJob, CompleteJob, FailJob, ManagePlugin, ConfigurePipeline + testing/ in-memory repo fakes + stub ports + + api-types/ HTTP request/response DTOs with OpenAPI derives + adapters/ postgres, auth (bcrypt, JWT), object storage + presentation/ axum handlers, routes, extractors + bootstrap/ config, DI wiring, entry point + worker/ background job runner ``` -## Running locally +## Development ```bash -cp .env.example .env -cargo run -p bootstrap +# run tests (no DB required) +cargo test -p domain -p application + +# format + lint +cargo fmt --all +cargo clippy -p domain -p application ``` -The server starts at `http://localhost:3000`. +148 tests cover all domain entities, services, and application use cases. -## Endpoints (out of the box) +## Status -| Method | Path | Auth | Description | -|--------|------|------|-------------| -| `POST` | `/api/v1/auth/register` | — | Create account → `AuthResponse` | -| `POST` | `/api/v1/auth/login` | — | Login → `AuthResponse` | -| `GET` | `/api/v1/auth/me` | Bearer | Current user profile | -| `GET` | `/health` | — | `{"status":"ok"}` | -| `GET` | `/scalar` | — | Interactive API docs | -| `GET` | `/api-docs/openapi.json` | — | OpenAPI spec | - -```bash -# Register -curl -s -X POST http://localhost:3000/api/v1/auth/register \ - -H "Content-Type: application/json" \ - -d '{"email":"me@example.com","password":"password123"}' | jq - -# Login and get token -TOKEN=$(curl -s -X POST http://localhost:3000/api/v1/auth/login \ - -H "Content-Type: application/json" \ - -d '{"email":"me@example.com","password":"password123"}' | jq -r '.token') - -# Profile -curl -s http://localhost:3000/api/v1/auth/me \ - -H "Authorization: Bearer $TOKEN" | jq -``` - -## Configuration - -| Variable | Default | Description | -|----------|---------|-------------| -| `DATABASE_URL` | `sqlite://data.db` | Database connection string | -| `JWT_SECRET` | *(required)* | Signing secret — min 32 chars in production | -| `HOST` | `0.0.0.0` | Bind address | -| `PORT` | `3000` | Listen port | -| `CORS_ALLOWED_ORIGINS` | `http://localhost:3000` | Comma-separated allowed origins | - -## Tests - -```bash -# Unit tests (no DB required) -cargo test -p domain -p application -p adapters-auth -``` - -13 unit tests cover email validation, use case logic (register/login/get_profile), bcrypt roundtrip, and JWT encode/verify. - -## Docker - -```bash -# Build -docker build -t my-app . - -# Run -docker run -p 3000:3000 \ - -e DATABASE_URL=sqlite:///data/app.db \ - -e JWT_SECRET=change-me-32-chars-minimum-here \ - my-app -``` - -Or with compose: - -```bash -docker compose up -``` - -The Dockerfile uses dependency layer caching (manifests copied and fetched before source) so rebuilds after source-only changes are fast. No live database is needed at compile time — the `.sqlx` offline cache is committed. - -## What to do after generating - -1. Add your domain entities and value objects to `crates/domain/` -2. Write use cases in `crates/application/` -3. Add DB columns/tables via new migration files in `crates/adapters/sqlite/migrations/` -4. Add handlers in `crates/presentation/src/handlers/` -5. Wire new use cases in `crates/bootstrap/src/factory.rs` - -Auth, CORS, logging, and docs are already done — focus on what makes your project unique. +Domain and application layers complete. Next: adapters (Postgres, NATS, filesystem) and presentation layer. ## License -MIT +[MIT](LICENSE)