docs: add proper README and MIT license
This commit is contained in:
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -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.
|
||||
178
README.md
178
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<dyn Port> 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)
|
||||
|
||||
Reference in New Issue
Block a user