From 80072eb463a064e9c1303322ba5020e8748fde66 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 25 Oct 2026 22:58:46 +0100 Subject: [PATCH] docs: update for new crate layout --- CODE_STYLE.md | 17 +++-- CONTRIBUTING.md | 44 +++++++------ README.md | 39 ++++++++--- architecture.mmd | 64 +++++++++++-------- .../http-axum/src/api/social/tests.rs | 10 +-- .../src/federation/blocklist_api/tests.rs | 12 +--- .../http-axum/src/html/social/tests.rs | 12 +--- crates/adapters/http-axum/src/lib.rs | 2 + crates/adapters/http-axum/src/test_support.rs | 12 ++++ crates/application/src/deps.rs | 2 +- .../src/import/apply_profile_and_map.rs | 2 +- .../src/import/get_session_state.rs | 2 +- crates/application/src/movies/mod.rs | 2 +- .../src/users/get_federated_profile_stats.rs | 2 +- .../application/src/users/get_page_viewer.rs | 4 +- .../src/users/resolve_username_to_id.rs | 2 +- crates/bootstrap/src/worker.rs | 20 +++--- .../0007-no-repositories-in-presentation.md | 2 + ...esentation-names-renderers-not-reachers.md | 2 + ...ion-is-optional-at-the-dependency-level.md | 2 + 20 files changed, 148 insertions(+), 106 deletions(-) create mode 100644 crates/adapters/http-axum/src/test_support.rs diff --git a/CODE_STYLE.md b/CODE_STYLE.md index 12d18456..9d44b5d2 100644 --- a/CODE_STYLE.md +++ b/CODE_STYLE.md @@ -47,13 +47,17 @@ Small files with a single concern. If a file grows past ~100-150 lines, it's pro ### Tests -Never in the same file as production code. Use cases keep their tests in a sibling `tests/` module, one file per use case: +Never in the same file as production code. Each crate keeps its tests in `crates//tests/`, one file per unit under test, grouped by the same module as the production code. Cargo builds each top-level `tests/.rs` as one integration-test binary; the group file lists its members with `#[path]`: ``` crates/application/src/diary/log_review.rs -crates/application/src/diary/tests/log_review.rs +crates/application/tests/diary.rs # mod support; + #[path] list of the group +crates/application/tests/diary/log_review_test.rs +crates/application/tests/support/ # shared contexts and command builders ``` +Adapters follow the same layout (`crates/adapters/sqlite/tests/repositories/diary_repository_test.rs`). The only tests left beside source are small mapper tests in `crates/adapters/http-axum/src/**/tests.rs`, which need crate-private items; their shared fixtures live in `http-axum/src/test_support.rs`. Route smoke, API and handler tests run against the real composition and live in `crates/bootstrap/tests/`. + ## Naming ### Conventions @@ -174,7 +178,7 @@ Errors propagate with `?` and bubble up to a single handler. No scattered panic ## Configuration -Secrets (API keys, JWT secrets) and deployment settings live in environment variables, read once at startup into a typed config struct. +Secrets (API keys, JWT secrets) and deployment settings are read once at startup by the `config` crate into a typed `AppConfig`: built-in defaults, then `config.toml` (or the file named by `CONFIG_FILE`), then environment variables. Malformed values fail startup; empty values count as unset. Only the binaries load configuration; everything else receives typed config values. Zero magic numbers or strings. Everything named, everything explained by its name. @@ -207,7 +211,8 @@ When none of those can carry it, a short comment stating the reason (never resta - Domain crate: zero external dependencies beyond `thiserror`, `serde` and small value-type crates - Application crate: depends only on domain - Adapters: free to pull in platform crates (`axum`, `sqlx`, `nats`, etc.) -- Bootstrap: wiring only +- Bootstrap: wiring only; the only crate that names adapters +- Binaries (`server`, `worker`): load config, call `bootstrap`, run — nothing else (`make check-binaries-are-thin`) ## Trait Objects for Ports @@ -226,7 +231,7 @@ Prefer a closed enum over `dyn` only where exhaustiveness is the point — when ### Test Helpers Are Gated -`domain::testing` (in-memory fakes and noops) compiles only with the `test-helpers` feature, which crates enable in their `[dev-dependencies]`. Production builds must never contain fakes. `application::test_helpers` is `#[cfg(test)]`. +`domain::testing` (factories, the cascading `InMemoryStore`, noops and `Refusing*` fakes) compiles only with the `test-helpers` feature, which crates enable in their `[dev-dependencies]`. Production builds must never contain fakes. Build entities with the `domain::testing` factories instead of repeating struct literals in each test. ### Coverage Targets @@ -235,7 +240,7 @@ Prefer a closed enum over `dyn` only where exhaustiveness is the point — when | Domain | TDD (red-green-refactor) | 100% | | Application | TDD | High coverage | | Adapters | Integration tests where practical | Best effort | -| Bootstrap | No tests | — | +| Bootstrap | Route smoke, API and handler tests over the real wiring | Every route reachable | ### Fakes Must Not Be Kinder Than Production diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 33661d10..8f3ba768 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,55 +23,61 @@ cd spa && bun install && bun run dev ## Before submitting a PR ```bash -make # runs fmt-check + clippy + test +make check # the full gate; plain `make` runs it too ``` -Or individually: +`make check` runs, in order: `fmt-check`, `clippy` (workspace, all targets, `-D warnings`), `test`, `test-federation-off` (workspace tests with default features off), then the layering guards (`check-appcontext`, `check-handler-deps`, `check-handler-repos`, `check-http-adapter-deps`, `check-federation-off-social`, `check-sqlite-is-ap-free`, `check-binaries-are-thin`, `check-dockerfile-manifests`). CI runs the same steps. + +For the SPA also run: ```bash -cargo fmt --check -cargo clippy -- -D warnings -cargo test cd spa && bunx tsc --noEmit ``` -All four must pass. PRs with clippy warnings or failing tests won't be merged. +PRs with a failing gate won't be merged. ## Architecture The project follows hexagonal (ports & adapters) architecture. See `architecture.mmd` for the full diagram. **Key rules:** -- Presentation handlers never touch repositories directly — all domain logic goes through use cases in the `application` crate -- Application use cases return raw domain data — URL formatting, date display, and view model assembly belong in presentation mappers (`crates/adapters/http-axum/src/mappers/`) -- Use cases called from presentation handlers take a `&FooDeps` struct (registered in `application::Deps`, built by `bootstrap::build_deps`) — never `&AppContext` itself, which `application` cannot even depend on. A few keep individual `Arc` params instead: `enrich_movie` and `request_enrichment` because they are called from adapters rather than handlers, and `diary::log_review` because its one extra dependency comes from `Services` rather than a repository. `wrapup::compute`, `import::cleanup` and `integrations::cleanup` also take individual params, but they are only ever called from jobs. See ADR-0007 for the exact list and why each is legitimate +- HTTP handlers never touch repositories directly — all domain logic goes through use cases in the `application` crate +- Application use cases return raw domain data — URL formatting, date display, and view model assembly belong in the http-axum mappers (`crates/adapters/http-axum/src/html/mappers/` for the classic UI, the DTO conversions beside each handler under `api/`) +- Use cases called from HTTP handlers take a `&FooDeps` struct (registered in `application::Deps`, built by `bootstrap`) — never `&AppContext` itself, which `application` cannot even depend on. A few keep individual `Arc` params instead: `enrich_movie` and `request_enrichment` because they are called from adapters rather than handlers, and `diary::log_review` because its one extra dependency comes from `Services` rather than a repository. `wrapup::compute`, `import::cleanup` and `integrations::cleanup` also take individual params, but they are only ever called from jobs. See ADR-0007 for the exact list and why each is legitimate ``` domain → pure types, traits (ports), zero deps application → use cases, orchestration -http-axum → Axum handlers, routes, OpenAPI -worker → event consumer, background jobs -adapters/* → implements domain ports (sqlite, AP, etc.) +config → typed AppConfig: defaults → config.toml / CONFIG_FILE → env +bootstrap → composition root, the only crate that names adapters +server, worker → thin binaries: load config, call bootstrap, run +adapters/* → implement domain ports (sqlite, activitypub, ...); http-axum is the HTTP driving adapter spa/ → React SPA (TanStack Router + shadcn/ui) ``` +Cargo features: `federation` (default; ActivityPub) and `nats`. `sqlite` and `sqlite-federation` remain as deprecated aliases so old build commands keep working. + ### Adding a new feature -1. **Domain first** — models in `domain/src/models/`, ports in `ports.rs`, events in `events.rs` -2. **Adapters** — implement ports in the `sqlite*` adapters, add migration -3. **Application** — use cases in `application/src//`, wire into `context.rs` +1. **Domain first** — models in `domain/src/models/`, ports in `domain/src/ports/`, events in `domain/src/events.rs` +2. **Adapters** — implement ports under `crates/adapters/sqlite/src/repositories//`, add a migration +3. **Application** — use cases in `application/src//`, add the deps struct to `application/src/deps.rs`, build it in `bootstrap` 4. **API types** — DTOs in `api-types/src/` -5. **Presentation** — handler file in `handlers/.rs`, routes in `routes.rs` +5. **HTTP** — JSON handlers in `crates/adapters/http-axum/src/api/.rs`, classic HTML handlers in `.../html/.rs` (templates in `html/templates/`), routes in `router.rs` / `api/mod.rs` / `html/mod.rs` 6. **SPA** — API client in `spa/src/lib/api/`, hook in `spa/src/hooks/`, components 7. **Classic HTML** — Askama template + CSS in `static/style.css` +### Tests + +Tests live in `crates//tests/`, never in the production file (see `CODE_STYLE.md`). Build fixtures with the `domain::testing` factories and use the cascading `InMemoryStore` and `Refusing*` fakes. Route smoke, API and handler tests over the real wiring are in `crates/bootstrap/tests/`. + ### Database adapters -SQLite is the only supported backend (see [ADR-0012](docs/adr/0012-sqlite-is-the-only-backend.md)). Add migrations under the sqlite adapter; SQLite uses `?` params and text datetimes. +SQLite is the only supported backend (see [ADR-0012](docs/adr/0012-sqlite-is-the-only-backend.md)). Add migrations under `crates/adapters/sqlite`; SQLite uses `?` params and text datetimes. ### Federation (ActivityPub) -Federation is feature-gated (`#[cfg(feature = "federation")]`). If your feature should federate, add domain events, handle them in `activitypub/src/event_handler.rs`, and create an AP object + inbound handler. +Federation is feature-gated (`#[cfg(feature = "federation")]`). If your feature should federate, add domain events, handle them in `crates/adapters/activitypub/src/event_handler.rs`, and create an AP object + inbound handler. ## Code style @@ -83,5 +89,5 @@ Federation is feature-gated (`#[cfg(feature = "federation")]`). If your feature ## Areas seeking help - **TUI** (`crates/tui`) — deprecated, needs a maintainer to bring it up to feature parity -- **Tests** — the domain and application crates have 400+ unit tests; integration tests for the presentation layer are welcome +- **Tests** — the domain and application crates have 400+ tests; more route and handler tests in `crates/bootstrap/tests/` are welcome - **Docs** — API usage examples, deployment guides diff --git a/README.md b/README.md index 8de059c6..1c43fac4 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ Hexagonal (Ports & Adapters) with Domain-Driven Design: api-types — shared REST API request/response DTOs (Serialize/Deserialize + utoipa schemas) + HtmlPageContext; used by http-axum and tui config — typed configuration (defaults, config.toml, legacy env names); loaded once by the binaries bootstrap — composition root: the only crate that names adapters; builds the server router and the worker service +event-payload — serde payloads for domain events, shared by the NATS and SQLite-queue adapters domain — pure types and CQRS port traits (MovieCommand/MovieQuery, WatchEventCommand/WatchEventQuery, GoalCommand/GoalQuery, DiaryQuery, PersonCommand/PersonQuery, SearchCommand/SearchPort, SocialCommand/SocialQuery, ImageFetcher, RssFeedRenderer), no external deps except serde application — use cases (commands + queries), business logic orchestration; handlers delegate here for all domain logic; modules: auth, diary, goals, import, integrations, movies, person, search, social, users, watchlist, wrapup server — thin HTTP binary (dotenv, config, bootstrap::build_server, axum::serve); features (federation, nats) forward to bootstrap @@ -100,7 +101,7 @@ worker — thin worker binary (bootstrap::build_worker, run until c adapters/ http-axum — Axum HTTP adapter: JSON API, classic HTML UI (askama templates), OpenAPI spec, Swagger UI + Scalar, federation-gated routes; library crate, no binary of its own auth — JWT issuance and validation (Argon2 passwords) - sqlite — SQLite repositories, search (FTS5), social graph, durable event queue, connection factory + migrations + sqlite — SQLite repositories (one module per aggregate under repositories/), search (FTS5), social graph, durable event queue, connection factory + migrations; the only storage adapter metadata — TMDB / OMDb HTTP client poster-fetcher — downloads poster images object-storage — stores images (posters + user avatars) on local filesystem or S3-compatible storage @@ -110,10 +111,10 @@ adapters/ export — CSV and JSON diary serialization importer — CSV/TSV/JSON/XLSX parser and column mapper for bulk import media-server — Jellyfin and Plex webhook payload parsers (MediaServerParser adapter; Plex requires Plex Pass) - event-publisher — in-memory event channel (used in tests) + event-publisher — in-memory event channel nats — NATS Core / JetStream event publisher and consumer activitypub — ActivityPub federation adapter (follow, inbox/outbox, actor); delegates to k-ap for protocol internals - sqlite-federation — SQLite-backed federation repository + sqlite-federation — SQLite-backed federation repository (k-ap trait implementations; only built with the `federation` feature) tui — terminal UI client (ratatui); shares api-types with http-axum for typed API access spa/ — React SPA (TanStack Router + shadcn/ui + Vite); served at /app/ by the backend ``` @@ -127,7 +128,13 @@ spa/ — React SPA (TanStack Router + shadcn/ui + Vite); served a ## Configuration -Copy `.env.example` to `.env` and set the values below. Required fields must be set before the server will start. +Settings are layered, later sources winning: + +1. built-in defaults +2. `config.toml` in the working directory, or the file named by `CONFIG_FILE` (a named file that does not exist is an error; the default `config.toml` is optional) +3. environment variables (including those from `.env`), under the names in the table below + +Copy `.env.example` to `.env` and set the values you need. Malformed values (a non-numeric `PORT`, an unknown `EVENT_BUS_BACKEND`) fail startup with a message naming the setting. An empty value (`PORT=`) counts as unset, so the default applies. The variable names are unchanged from earlier releases; every one maps to a `section.field` key in `config.toml` (for example `PORT` is `server.port`, `JWT_SECRET` is `auth.jwt_secret`). | Variable | Default | Required | Description | |---|---|---|---| @@ -155,6 +162,17 @@ Copy `.env.example` to `.env` and set the values below. Required fields must be | `EVENT_BUS_BACKEND` | `db` | No | `db` (default) or `nats` | | `NATS_URL` | — | NATS only | NATS connection URL (e.g. `nats://localhost:4222`) | +Required fields must be set (in either source) before the server will start. + +### Cargo features + +| Feature | Default | Effect | +|---|---|---| +| `federation` | yes | ActivityPub federation (`activitypub`, `sqlite-federation`, federation routes) | +| `nats` | no | NATS event bus (`EVENT_BUS_BACKEND=nats`) | + +Build without federation with `--no-default-features`. `sqlite` and `sqlite-federation` are deprecated aliases kept so old build commands still work. + The `worker` binary must run alongside `server` to process events: ```bash @@ -208,23 +226,26 @@ Supports review logging, bulk CSV import, and diary browsing. A `Makefile` wraps the most common dev commands: ```bash -make # default: fmt-check + clippy + test (same order as CI) +make # make check: the full gate, same steps as CI make fix # auto-apply fmt + clippy fixes make fmt # apply rustfmt make clippy # clippy with -D warnings -make test # cargo test +make test # cargo test --workspace ``` +`make check` runs fmt-check, clippy, tests (default features and `--no-default-features`), and the layering guards (see `CONTRIBUTING.md`). + ## Test ```bash -cargo test # full workspace (requires DATABASE_URL for sqlx offline checks) -cargo test -p application # business logic tests only — no database required +cargo test --workspace # everything; needs no external services +cargo test -p application # business logic tests only — in-memory fakes, no database cargo test -p domain # domain model + value object tests +cargo test -p bootstrap # route smoke, API and handler tests over the real wiring cargo llvm-cov -p application -p domain # line coverage report (requires cargo-llvm-cov) ``` -The `application` and `domain` crates have 400+ unit tests covering all use case modules (auth, diary, goals, import, integrations, movies, person, search, users, watchlist, wrapup) backed by in-memory fakes from `domain`'s `test-helpers` feature. These run without a database and are the fastest feedback loop for business logic changes. +Each crate keeps its tests in `crates//tests/`. The `application` and `domain` crates have 400+ tests covering all use case modules (auth, diary, goals, import, integrations, movies, person, search, users, watchlist, wrapup) backed by the in-memory fakes and factories in `domain::testing` (the `test-helpers` feature). They are the fastest feedback loop for business logic changes. ## Docker diff --git a/architecture.mmd b/architecture.mmd index d8ef6cb0..c12ede18 100644 --- a/architecture.mmd +++ b/architecture.mmd @@ -2,15 +2,19 @@ title: Movies Diary — Hexagonal Architecture --- graph TB - subgraph Binaries["Binaries (Composition Root)"] - WEB["http-axum
Axum web server + HTML templates
Routes, Handlers, Mappers"] - WORKER["worker
Event consumer
Concurrent dispatch, graceful shutdown"] - TUI["tui
Terminal UI"] + subgraph Binaries["Binaries (thin: config + bootstrap + run)"] + SERVER["server
load config, bootstrap::build_server, axum::serve"] + WORKER["worker
bootstrap::build_worker
Concurrent dispatch, graceful shutdown"] + TUI["tui
Terminal UI (deprecated)"] + end + + subgraph Config["config"] + CFG["AppConfig
defaults, config.toml / CONFIG_FILE,
legacy env names
"] end subgraph Application["Application Layer"] direction TB - CTX["AppContext
Repositories + Services"] + DEPS["Deps
one narrow deps struct per use case"] APP_PORTS["ReviewLogger
application-layer port"] subgraph UseCases["Use Cases"] UC_AUTH["auth
login, register"] @@ -33,7 +37,7 @@ graph TB EH_CLEANUP["SearchCleanupHandler"] EH_REINDEX["SearchReindexHandler"] EH_WRAPUP["WrapUpEventHandler"] - EH_AP["ActivityPubEventHandler
reviews, watchlist, goals,
UserDeleted, UserAccountMoved
"] + EH_POSTER["PosterSyncHandler
poster sync"] end subgraph Jobs["Periodic Jobs"] JOB_IMPORT["ImportSessionCleanup"] @@ -78,60 +82,66 @@ graph TB end subgraph Bootstrap["bootstrap"] - IW["build_server, build_worker,
DbPool, EventBusBackend
Composition root"] + BS["build_server, build_worker
Composition root: the only crate that names adapters"] + end + + subgraph EventPayload["event-payload"] + EP["Serde payloads for domain events"] end subgraph Adapters["Adapters (implement Port Traits)"] direction TB - A_COMMON["adapter-common
Shared row conversions,
error mapping, date utils
"] + subgraph Http["HTTP driving adapter"] + A_HTTP["http-axum
JSON API, classic HTML (askama),
OpenAPI, federation-gated routes,
one refusal shape
"] + end subgraph Storage["Storage"] - A_SQLITE["sqlite
SQLite repos"] - A_SQLITE_SEARCH["sqlite-search
FTS5"] + A_SQLITE["sqlite
repositories per aggregate, FTS5 search,
social graph, durable event queue, migrations
"] A_OBJ["object-storage
S3 / filesystem"] end subgraph Messaging["Messaging"] A_NATS["nats
JetStream / Core"] - A_SQLITE_QUEUE["sqlite-event-queue
Polling, dead-letter"] - A_EVT_PUB["event-publisher
In-memory (tests)"] - A_PAYLOAD["event-payload
Serde (de)serialization"] + A_EVT_PUB["event-publisher
In-memory channel"] end subgraph External["External Services"] - A_METADATA["metadata
TMDB search/details"] + A_METADATA["metadata
TMDB / OMDb search/details"] A_TMDB["tmdb-enrichment
Credits, keywords, cast"] A_POSTER["poster-fetcher
TMDB image download"] A_AUTH["auth
JWT tokens"] end subgraph Federation["Federation (feature-gated)"] - A_AP["activitypub
k_ap library"] - A_SQLITE_FED["sqlite-federation"] + A_AP["activitypub
k_ap library; ActivityPubEventHandler
(reviews, watchlist, goals, UserDeleted, UserAccountMoved)
"] + A_SQLITE_FED["sqlite-federation
k_ap repositories"] end subgraph Media["Media Processing"] A_IMG["image-converter
AVIF/WebP"] - A_POSTER_SYNC["poster-sync"] end - subgraph Presentation["Presentation Helpers"] + subgraph Formats["Formats"] A_RSS["rss
Feed generation"] A_EXPORT["export
CSV/JSON diary"] A_IMPORT["importer
CSV/JSON/XLSX parser"] - end - subgraph Webhooks["Webhook Parsers"] - A_JELLYFIN["jellyfin"] - A_PLEX["plex"] + A_MEDIA_SERVER["media-server
Jellyfin + Plex webhook parsers"] end end %% Dependency arrows - WEB -->|"uses"| Application - WEB -->|"maps to"| ApiTypes - WORKER -->|"uses"| Application + SERVER -->|"loads"| Config + SERVER -->|"calls"| Bootstrap + WORKER -->|"loads"| Config + WORKER -->|"calls"| Bootstrap + Bootstrap -->|"wires"| Adapters + Bootstrap -->|"builds deps for"| Application + A_HTTP -->|"uses"| Application + A_HTTP -->|"maps to"| ApiTypes + TUI -->|"typed API access"| ApiTypes Application -->|"depends on"| Domain UC_WRAPUP -->|"delegates to"| DS_WRAPUP Adapters -.->|"implements"| Ports + Messaging -.->|"serialises with"| EventPayload %% Key data flows - WEB ===|"HTTP"| DTO + A_HTTP ===|"HTTP"| DTO WORKER ===|"Events"| EVENTS classDef domain fill:#1a1a2e,stroke:#e94560,color:#fff @@ -145,4 +155,6 @@ graph TB class Application app class Adapters adapter class Binaries binary + class Config app + class Bootstrap binary class ApiTypes api diff --git a/crates/adapters/http-axum/src/api/social/tests.rs b/crates/adapters/http-axum/src/api/social/tests.rs index 0a196dcf..f078380f 100644 --- a/crates/adapters/http-axum/src/api/social/tests.rs +++ b/crates/adapters/http-axum/src/api/social/tests.rs @@ -1,15 +1,7 @@ use super::*; +use crate::test_support::local_actor; use domain::value_objects::{InstanceIdentity, SocialActor, SocialIdentity, UserId}; -fn local_actor(uid: &UserId) -> SocialActor { - SocialActor { - identity: SocialIdentity::Local(uid.clone()), - handle: "@gabriel@md.example".into(), - display_name: None, - avatar_url: None, - } -} - #[test] fn local_actor_dto_url_round_trips_back_to_the_same_identity() { let instance = InstanceIdentity::new("https://md.example"); diff --git a/crates/adapters/http-axum/src/federation/blocklist_api/tests.rs b/crates/adapters/http-axum/src/federation/blocklist_api/tests.rs index 900819de..2d1a98c7 100644 --- a/crates/adapters/http-axum/src/federation/blocklist_api/tests.rs +++ b/crates/adapters/http-axum/src/federation/blocklist_api/tests.rs @@ -1,14 +1,6 @@ use super::*; -use domain::value_objects::{InstanceIdentity, SocialActor, SocialIdentity, UserId}; - -fn local_actor(uid: &UserId) -> SocialActor { - SocialActor { - identity: SocialIdentity::Local(uid.clone()), - handle: "@gabriel@md.example".into(), - display_name: None, - avatar_url: None, - } -} +use crate::test_support::local_actor; +use domain::value_objects::{InstanceIdentity, SocialIdentity, UserId}; #[test] fn local_blocked_dto_url_round_trips_back_to_the_same_identity() { diff --git a/crates/adapters/http-axum/src/html/social/tests.rs b/crates/adapters/http-axum/src/html/social/tests.rs index 9ef45c28..e03ca953 100644 --- a/crates/adapters/http-axum/src/html/social/tests.rs +++ b/crates/adapters/http-axum/src/html/social/tests.rs @@ -1,14 +1,6 @@ use super::*; -use domain::value_objects::{InstanceIdentity, SocialActor, SocialIdentity, UserId}; - -fn local_actor(uid: &UserId) -> SocialActor { - SocialActor { - identity: SocialIdentity::Local(uid.clone()), - handle: "@gabriel@md.example".into(), - display_name: None, - avatar_url: None, - } -} +use crate::test_support::local_actor; +use domain::value_objects::{InstanceIdentity, SocialIdentity, UserId}; #[test] fn local_template_url_round_trips_back_to_the_same_identity() { diff --git a/crates/adapters/http-axum/src/lib.rs b/crates/adapters/http-axum/src/lib.rs index 2dd8736a..6afdf869 100644 --- a/crates/adapters/http-axum/src/lib.rs +++ b/crates/adapters/http-axum/src/lib.rs @@ -8,3 +8,5 @@ pub mod openapi; pub mod router; pub mod state; mod support; +#[cfg(test)] +mod test_support; diff --git a/crates/adapters/http-axum/src/test_support.rs b/crates/adapters/http-axum/src/test_support.rs new file mode 100644 index 00000000..d92cac11 --- /dev/null +++ b/crates/adapters/http-axum/src/test_support.rs @@ -0,0 +1,12 @@ +//! Fixtures shared by the in-crate mapper tests. + +use domain::value_objects::{SocialActor, SocialIdentity, UserId}; + +pub(crate) fn local_actor(uid: &UserId) -> SocialActor { + SocialActor { + identity: SocialIdentity::Local(uid.clone()), + handle: "@gabriel@md.example".into(), + display_name: None, + avatar_url: None, + } +} diff --git a/crates/application/src/deps.rs b/crates/application/src/deps.rs index 969e3f8c..d5233cf2 100644 --- a/crates/application/src/deps.rs +++ b/crates/application/src/deps.rs @@ -150,7 +150,7 @@ pub struct WrapupGroup { pub list_wrapups: ListWrapUpsDeps, } -/// Every deps struct a handler can need, built once by the composition root. +/// Every deps struct a handler can need, built once by `bootstrap`. /// Use cases still receive only their own narrow struct — nothing takes `&Deps`. /// /// `bootstrap::build_deps` is called only from `bootstrap::build_server`. diff --git a/crates/application/src/import/apply_profile_and_map.rs b/crates/application/src/import/apply_profile_and_map.rs index 09ec468f..a4512322 100644 --- a/crates/application/src/import/apply_profile_and_map.rs +++ b/crates/application/src/import/apply_profile_and_map.rs @@ -1,4 +1,4 @@ -//! Absorbs `handlers/import.rs::api_apply_profile`'s three-step orchestration: +//! Absorbs `http-axum/src/api/import/sessions.rs::api_apply_profile`'s three-step orchestration: //! apply the saved profile's field mappings onto the session, reload the //! session to read back the mappings `apply_profile` just wrote, then run //! `apply_mapping` to regenerate `row_results` from them. All three steps used diff --git a/crates/application/src/import/get_session_state.rs b/crates/application/src/import/get_session_state.rs index 241d35d8..7423951d 100644 --- a/crates/application/src/import/get_session_state.rs +++ b/crates/application/src/import/get_session_state.rs @@ -1,4 +1,4 @@ -//! Backs `handlers/import.rs::api_get_session` — a plain state query, not a +//! Backs `http-axum/src/api/import/sessions.rs::api_get_session` — a plain state query, not a //! redirect-driving gate (the API has nothing to redirect to; a missing //! session is just a 404). diff --git a/crates/application/src/movies/mod.rs b/crates/application/src/movies/mod.rs index 14104074..36287631 100644 --- a/crates/application/src/movies/mod.rs +++ b/crates/application/src/movies/mod.rs @@ -6,7 +6,7 @@ pub mod event_handler; pub mod get_movie_profile; pub mod get_movies; pub mod merge_duplicates; -pub mod poster_sync; +pub mod poster_sync_handler; pub mod queries; pub mod reindex_search; pub mod request_enrichment; diff --git a/crates/application/src/users/get_federated_profile_stats.rs b/crates/application/src/users/get_federated_profile_stats.rs index 913c927e..aebe69d3 100644 --- a/crates/application/src/users/get_federated_profile_stats.rs +++ b/crates/application/src/users/get_federated_profile_stats.rs @@ -1,5 +1,5 @@ //! The federated half of the former `get_profile` (ADR-0004's closed wart). -//! `build_federated_profile_response` (`handlers/users.rs`) calls this with a +//! `build_federated_profile_response` (`http-axum/src/api/user_profile.rs`) calls this with a //! synthetic `user_id` that has no row in the local `users` table — a federated //! actor's identity is built entirely from the resolved `FederatedProfile`, so //! this type carries no `identity` field at all: the old `get_profile` computed diff --git a/crates/application/src/users/get_page_viewer.rs b/crates/application/src/users/get_page_viewer.rs index 64927bf6..483ec281 100644 --- a/crates/application/src/users/get_page_viewer.rs +++ b/crates/application/src/users/get_page_viewer.rs @@ -1,10 +1,10 @@ //! Backs page-chrome data: the nav bar's email/admin state and pending-follow -//! badge. Absorbs `handlers/helpers.rs::build_page_context`'s inline user lookup +//! badge. Absorbs `html/helpers.rs::build_page_context`'s inline user lookup //! and pending-follower count. //! //! **Error policy (deliberately no internal tolerance):** this function //! propagates every error from `user` and `follow_graph` with `?`. It does not -//! itself decide to degrade — that's a presentation-layer policy +//! itself decide to degrade — that's a http-adapter policy //! (`build_page_context` logs a warning and falls back to a zeroed `PageViewer` //! on `Err`). Page chrome degrading is a rendering decision, not a domain one. diff --git a/crates/application/src/users/resolve_username_to_id.rs b/crates/application/src/users/resolve_username_to_id.rs index 7f3421cb..0888879c 100644 --- a/crates/application/src/users/resolve_username_to_id.rs +++ b/crates/application/src/users/resolve_username_to_id.rs @@ -1,4 +1,4 @@ -//! Absorbs `handlers/users.rs::get_user_by_username`'s +//! Absorbs `http-axum/src/html/users.rs::get_user_by_username`'s //! `repos.user.find_by_username()` call. `None` means "no such username" — the //! handler turns that into a 404, same as it always has; this is not an error //! condition. diff --git a/crates/bootstrap/src/worker.rs b/crates/bootstrap/src/worker.rs index 5d9c244f..7313feda 100644 --- a/crates/bootstrap/src/worker.rs +++ b/crates/bootstrap/src/worker.rs @@ -184,15 +184,17 @@ async fn assemble( // ── Event handlers ──────────────────────────────────────────────────────── let handlers: Vec> = { - let poster = Arc::new(application::movies::poster_sync::PosterSyncHandler::new( - Arc::clone(&movie_command), - Arc::clone(&movie_query), - Arc::clone(&metadata), - Arc::clone(&poster_fetcher), - Arc::clone(&object_storage), - Arc::clone(&event_publisher), - 3, - )) as Arc; + let poster = Arc::new( + application::movies::poster_sync_handler::PosterSyncHandler::new( + Arc::clone(&movie_command), + Arc::clone(&movie_query), + Arc::clone(&metadata), + Arc::clone(&poster_fetcher), + Arc::clone(&object_storage), + Arc::clone(&event_publisher), + 3, + ), + ) as Arc; let cleanup = Arc::new(object_storage::ImageCleanupHandler::new(Arc::clone( &object_storage, diff --git a/docs/adr/0007-no-repositories-in-presentation.md b/docs/adr/0007-no-repositories-in-presentation.md index c7a251bc..d4c6a165 100644 --- a/docs/adr/0007-no-repositories-in-presentation.md +++ b/docs/adr/0007-no-repositories-in-presentation.md @@ -146,3 +146,5 @@ naming the exact file and line, before reverting. - `CONTRIBUTING.md`'s architecture rules were updated in this same commit to stop describing use cases as taking `&AppContext` (a claim already false before this plan) and instead describe the `&FooDeps`-struct convention this ADR documents. + +Now at `crates/adapters/http-axum/src` (was `crates/presentation/src`); see [ADR-0014](0014-http-adapter-and-one-refusal-shape.md). diff --git a/docs/adr/0008-presentation-names-renderers-not-reachers.md b/docs/adr/0008-presentation-names-renderers-not-reachers.md index 572dfae2..946af264 100644 --- a/docs/adr/0008-presentation-names-renderers-not-reachers.md +++ b/docs/adr/0008-presentation-names-renderers-not-reachers.md @@ -263,3 +263,5 @@ recorded under Known follow-ups below, which is also pre-existing. --- Superseded in part by [ADR-0012](0012-sqlite-is-the-only-backend.md): the postgres backends, crates and features mentioned above no longer exist; SQLite is the only backend. + +Now `make check-http-adapter-deps` over `crates/adapters/http-axum/Cargo.toml` (was `check-presentation-adapters`); see [ADR-0014](0014-http-adapter-and-one-refusal-shape.md). diff --git a/docs/adr/0009-federation-is-optional-at-the-dependency-level.md b/docs/adr/0009-federation-is-optional-at-the-dependency-level.md index f85f5f85..70665995 100644 --- a/docs/adr/0009-federation-is-optional-at-the-dependency-level.md +++ b/docs/adr/0009-federation-is-optional-at-the-dependency-level.md @@ -369,3 +369,5 @@ still fails with the same two pre-existing errors. This branch neither fixed nor --- Superseded in part by [ADR-0012](0012-sqlite-is-the-only-backend.md): the postgres backends, crates and features mentioned above no longer exist; SQLite is the only backend. + +Now `make check-sqlite-is-ap-free` (was `check-social-crates-are-ap-free`); the sqlite social crates merged into `crates/adapters/sqlite`.