docs: update for new crate layout

This commit is contained in:
2026-10-25 22:58:46 +01:00
parent b76f5996da
commit 80072eb463
20 changed files with 148 additions and 106 deletions

View File

@@ -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/<crate>/tests/`, one file per unit under test, grouped by the same module as the production code. Cargo builds each top-level `tests/<group>.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

View File

@@ -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<dyn Trait>` 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<dyn Trait>` 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/<domain>/`, 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/<aggregate>/`, add a migration
3. **Application** — use cases in `application/src/<domain>/`, 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/<domain>.rs`, routes in `routes.rs`
5. **HTTP** — JSON handlers in `crates/adapters/http-axum/src/api/<domain>.rs`, classic HTML handlers in `.../html/<domain>.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/<crate>/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

View File

@@ -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/<crate>/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

View File

@@ -2,15 +2,19 @@
title: Movies Diary — Hexagonal Architecture
---
graph TB
subgraph Binaries["Binaries (Composition Root)"]
WEB["http-axum<br/><i>Axum web server + HTML templates</i><br/>Routes, Handlers, Mappers"]
WORKER["worker<br/><i>Event consumer</i><br/>Concurrent dispatch, graceful shutdown"]
TUI["tui<br/><i>Terminal UI</i>"]
subgraph Binaries["Binaries (thin: config + bootstrap + run)"]
SERVER["server<br/><i>load config, bootstrap::build_server, axum::serve</i>"]
WORKER["worker<br/><i>bootstrap::build_worker</i><br/>Concurrent dispatch, graceful shutdown"]
TUI["tui<br/><i>Terminal UI (deprecated)</i>"]
end
subgraph Config["config"]
CFG["AppConfig<br/><i>defaults, config.toml / CONFIG_FILE,<br/>legacy env names</i>"]
end
subgraph Application["Application Layer"]
direction TB
CTX["AppContext<br/><i>Repositories + Services</i>"]
DEPS["Deps<br/><i>one narrow deps struct per use case</i>"]
APP_PORTS["ReviewLogger<br/><i>application-layer port</i>"]
subgraph UseCases["Use Cases"]
UC_AUTH["auth<br/>login, register"]
@@ -33,7 +37,7 @@ graph TB
EH_CLEANUP["SearchCleanupHandler"]
EH_REINDEX["SearchReindexHandler"]
EH_WRAPUP["WrapUpEventHandler"]
EH_AP["ActivityPubEventHandler<br/><i>reviews, watchlist, goals,<br/>UserDeleted, UserAccountMoved</i>"]
EH_POSTER["PosterSyncHandler<br/><i>poster sync</i>"]
end
subgraph Jobs["Periodic Jobs"]
JOB_IMPORT["ImportSessionCleanup"]
@@ -78,60 +82,66 @@ graph TB
end
subgraph Bootstrap["bootstrap"]
IW["build_server, build_worker,<br/>DbPool, EventBusBackend<br/><i>Composition root</i>"]
BS["build_server, build_worker<br/><i>Composition root: the only crate that names adapters</i>"]
end
subgraph EventPayload["event-payload"]
EP["Serde payloads for domain events"]
end
subgraph Adapters["Adapters (implement Port Traits)"]
direction TB
A_COMMON["adapter-common<br/><i>Shared row conversions,<br/>error mapping, date utils</i>"]
subgraph Http["HTTP driving adapter"]
A_HTTP["http-axum<br/><i>JSON API, classic HTML (askama),<br/>OpenAPI, federation-gated routes,<br/>one refusal shape</i>"]
end
subgraph Storage["Storage"]
A_SQLITE["sqlite<br/><i>SQLite repos</i>"]
A_SQLITE_SEARCH["sqlite-search<br/><i>FTS5</i>"]
A_SQLITE["sqlite<br/><i>repositories per aggregate, FTS5 search,<br/>social graph, durable event queue, migrations</i>"]
A_OBJ["object-storage<br/><i>S3 / filesystem</i>"]
end
subgraph Messaging["Messaging"]
A_NATS["nats<br/><i>JetStream / Core</i>"]
A_SQLITE_QUEUE["sqlite-event-queue<br/><i>Polling, dead-letter</i>"]
A_EVT_PUB["event-publisher<br/><i>In-memory (tests)</i>"]
A_PAYLOAD["event-payload<br/><i>Serde (de)serialization</i>"]
A_EVT_PUB["event-publisher<br/><i>In-memory channel</i>"]
end
subgraph External["External Services"]
A_METADATA["metadata<br/><i>TMDB search/details</i>"]
A_METADATA["metadata<br/><i>TMDB / OMDb search/details</i>"]
A_TMDB["tmdb-enrichment<br/><i>Credits, keywords, cast</i>"]
A_POSTER["poster-fetcher<br/><i>TMDB image download</i>"]
A_AUTH["auth<br/><i>JWT tokens</i>"]
end
subgraph Federation["Federation (feature-gated)"]
A_AP["activitypub<br/><i>k_ap library</i>"]
A_SQLITE_FED["sqlite-federation"]
A_AP["activitypub<br/><i>k_ap library; ActivityPubEventHandler<br/>(reviews, watchlist, goals, UserDeleted, UserAccountMoved)</i>"]
A_SQLITE_FED["sqlite-federation<br/><i>k_ap repositories</i>"]
end
subgraph Media["Media Processing"]
A_IMG["image-converter<br/><i>AVIF/WebP</i>"]
A_POSTER_SYNC["poster-sync"]
end
subgraph Presentation["Presentation Helpers"]
subgraph Formats["Formats"]
A_RSS["rss<br/><i>Feed generation</i>"]
A_EXPORT["export<br/><i>CSV/JSON diary</i>"]
A_IMPORT["importer<br/><i>CSV/JSON/XLSX parser</i>"]
end
subgraph Webhooks["Webhook Parsers"]
A_JELLYFIN["jellyfin"]
A_PLEX["plex"]
A_MEDIA_SERVER["media-server<br/><i>Jellyfin + Plex webhook parsers</i>"]
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

View File

@@ -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");

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -8,3 +8,5 @@ pub mod openapi;
pub mod router;
pub mod state;
mod support;
#[cfg(test)]
mod test_support;

View File

@@ -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,
}
}

View File

@@ -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`.

View File

@@ -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

View File

@@ -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).

View File

@@ -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;

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -184,15 +184,17 @@ async fn assemble(
// ── Event handlers ────────────────────────────────────────────────────────
let handlers: Vec<Arc<dyn EventHandler>> = {
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<dyn EventHandler>;
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<dyn EventHandler>;
let cleanup = Arc::new(object_storage::ImageCleanupHandler::new(Arc::clone(
&object_storage,

View File

@@ -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).

View File

@@ -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).

View File

@@ -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`.