refactor/tighten-architecture #26
@@ -45,11 +45,11 @@ ALLOW_REGISTRATION=true
|
|||||||
# PORT=3000
|
# PORT=3000
|
||||||
# RATE_LIMIT=60
|
# RATE_LIMIT=60
|
||||||
# SECURE_COOKIES=true
|
# SECURE_COOKIES=true
|
||||||
# Handler-level logs come from the `presentation` crate; startup/wiring logs come
|
# Handler-level logs come from the `http_axum` crate; startup/wiring logs come
|
||||||
# from `server`. Include both — `server=info` alone silently drops handler logs.
|
# from `server`. Include both — `server=info` alone silently drops handler logs.
|
||||||
# Crates not named here (adapters, etc.) still log warnings and errors unless
|
# Crates not named here (adapters, etc.) still log warnings and errors unless
|
||||||
# you set a bare level yourself (e.g. `info,presentation=debug`).
|
# you set a bare level yourself (e.g. `info,http_axum=debug`).
|
||||||
# RUST_LOG=server=info,presentation=info,tower_http=info,worker=info
|
# RUST_LOG=server=info,http_axum=info,tower_http=info,worker=info
|
||||||
|
|
||||||
# CORS (for SPA development only)
|
# CORS (for SPA development only)
|
||||||
# CORS_ORIGINS=http://localhost:5173
|
# CORS_ORIGINS=http://localhost:5173
|
||||||
|
|||||||
@@ -43,13 +43,13 @@ The project follows hexagonal (ports & adapters) architecture. See `architecture
|
|||||||
|
|
||||||
**Key rules:**
|
**Key rules:**
|
||||||
- Presentation handlers never touch repositories directly — all domain logic goes through use cases in the `application` crate
|
- 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 (`presentation/src/mappers/`)
|
- 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
|
- 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
|
||||||
|
|
||||||
```
|
```
|
||||||
domain → pure types, traits (ports), zero deps
|
domain → pure types, traits (ports), zero deps
|
||||||
application → use cases, orchestration
|
application → use cases, orchestration
|
||||||
presentation → Axum handlers, routes, OpenAPI
|
http-axum → Axum handlers, routes, OpenAPI
|
||||||
worker → event consumer, background jobs
|
worker → event consumer, background jobs
|
||||||
adapters/* → implements domain ports (sqlite, AP, etc.)
|
adapters/* → implements domain ports (sqlite, AP, etc.)
|
||||||
spa/ → React SPA (TanStack Router + shadcn/ui)
|
spa/ → React SPA (TanStack Router + shadcn/ui)
|
||||||
|
|||||||
81
Cargo.lock
generated
81
Cargo.lock
generated
@@ -919,6 +919,7 @@ dependencies = [
|
|||||||
"config",
|
"config",
|
||||||
"domain",
|
"domain",
|
||||||
"export",
|
"export",
|
||||||
|
"http-axum",
|
||||||
"image-converter",
|
"image-converter",
|
||||||
"importer",
|
"importer",
|
||||||
"media-server",
|
"media-server",
|
||||||
@@ -926,7 +927,6 @@ dependencies = [
|
|||||||
"nats",
|
"nats",
|
||||||
"object-storage",
|
"object-storage",
|
||||||
"poster-fetcher",
|
"poster-fetcher",
|
||||||
"presentation",
|
|
||||||
"rss 0.1.0",
|
"rss 0.1.0",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlite",
|
"sqlite",
|
||||||
@@ -2409,6 +2409,39 @@ dependencies = [
|
|||||||
"itoa",
|
"itoa",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-axum"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"api-types",
|
||||||
|
"application",
|
||||||
|
"askama",
|
||||||
|
"async-trait",
|
||||||
|
"axum",
|
||||||
|
"axum-governor",
|
||||||
|
"bootstrap",
|
||||||
|
"bytes",
|
||||||
|
"chrono",
|
||||||
|
"chrono-tz",
|
||||||
|
"domain",
|
||||||
|
"dotenvy",
|
||||||
|
"futures",
|
||||||
|
"infer",
|
||||||
|
"percent-encoding",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"tower",
|
||||||
|
"tower-http",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
"utoipa",
|
||||||
|
"utoipa-scalar",
|
||||||
|
"utoipa-swagger-ui",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http-body"
|
name = "http-body"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@@ -3983,39 +4016,6 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "presentation"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"api-types",
|
|
||||||
"application",
|
|
||||||
"async-trait",
|
|
||||||
"axum",
|
|
||||||
"axum-governor",
|
|
||||||
"bootstrap",
|
|
||||||
"bytes",
|
|
||||||
"chrono",
|
|
||||||
"chrono-tz",
|
|
||||||
"domain",
|
|
||||||
"dotenvy",
|
|
||||||
"futures",
|
|
||||||
"infer",
|
|
||||||
"percent-encoding",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"template-askama",
|
|
||||||
"tokio",
|
|
||||||
"tower",
|
|
||||||
"tower-http",
|
|
||||||
"tracing",
|
|
||||||
"tracing-subscriber",
|
|
||||||
"utoipa",
|
|
||||||
"utoipa-scalar",
|
|
||||||
"utoipa-swagger-ui",
|
|
||||||
"uuid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.2.37"
|
version = "0.2.37"
|
||||||
@@ -4938,8 +4938,8 @@ dependencies = [
|
|||||||
"domain",
|
"domain",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"futures",
|
"futures",
|
||||||
|
"http-axum",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"presentation",
|
|
||||||
"rss 0.1.0",
|
"rss 0.1.0",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlite",
|
"sqlite",
|
||||||
@@ -5610,17 +5610,6 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "template-askama"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"api-types",
|
|
||||||
"askama",
|
|
||||||
"chrono",
|
|
||||||
"domain",
|
|
||||||
"uuid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tendril"
|
name = "tendril"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ members = [
|
|||||||
"crates/adapters/rss",
|
"crates/adapters/rss",
|
||||||
"crates/adapters/sqlite",
|
"crates/adapters/sqlite",
|
||||||
"crates/adapters/sqlite-federation",
|
"crates/adapters/sqlite-federation",
|
||||||
"crates/adapters/template-askama",
|
|
||||||
"crates/adapters/activitypub",
|
"crates/adapters/activitypub",
|
||||||
"crates/adapters/export",
|
"crates/adapters/export",
|
||||||
"crates/event-payload",
|
"crates/event-payload",
|
||||||
@@ -19,7 +18,7 @@ members = [
|
|||||||
"crates/adapters/image-converter",
|
"crates/adapters/image-converter",
|
||||||
"crates/domain",
|
"crates/domain",
|
||||||
"crates/bootstrap",
|
"crates/bootstrap",
|
||||||
"crates/presentation",
|
"crates/adapters/http-axum",
|
||||||
"crates/server",
|
"crates/server",
|
||||||
"crates/tui",
|
"crates/tui",
|
||||||
"crates/worker",
|
"crates/worker",
|
||||||
@@ -68,7 +67,7 @@ domain = { path = "crates/domain" }
|
|||||||
tmdb-enrichment = { path = "crates/adapters/tmdb-enrichment" }
|
tmdb-enrichment = { path = "crates/adapters/tmdb-enrichment" }
|
||||||
application = { path = "crates/application" }
|
application = { path = "crates/application" }
|
||||||
bootstrap = { path = "crates/bootstrap" }
|
bootstrap = { path = "crates/bootstrap" }
|
||||||
presentation = { path = "crates/presentation" }
|
http-axum = { path = "crates/adapters/http-axum" }
|
||||||
auth = { path = "crates/adapters/auth" }
|
auth = { path = "crates/adapters/auth" }
|
||||||
metadata = { path = "crates/adapters/metadata" }
|
metadata = { path = "crates/adapters/metadata" }
|
||||||
poster-fetcher = { path = "crates/adapters/poster-fetcher" }
|
poster-fetcher = { path = "crates/adapters/poster-fetcher" }
|
||||||
@@ -78,7 +77,6 @@ rss = { path = "crates/adapters/rss" }
|
|||||||
export = { path = "crates/adapters/export" }
|
export = { path = "crates/adapters/export" }
|
||||||
sqlite = { path = "crates/adapters/sqlite" }
|
sqlite = { path = "crates/adapters/sqlite" }
|
||||||
sqlite-federation = { path = "crates/adapters/sqlite-federation" }
|
sqlite-federation = { path = "crates/adapters/sqlite-federation" }
|
||||||
template-askama = { path = "crates/adapters/template-askama" }
|
|
||||||
activitypub = { path = "crates/adapters/activitypub" }
|
activitypub = { path = "crates/adapters/activitypub" }
|
||||||
event-payload = { path = "crates/event-payload" }
|
event-payload = { path = "crates/event-payload" }
|
||||||
media-server = { path = "crates/adapters/media-server" }
|
media-server = { path = "crates/adapters/media-server" }
|
||||||
|
|||||||
@@ -32,13 +32,12 @@ COPY crates/adapters/media-server/Cargo.toml crates/adapters/media-server/C
|
|||||||
COPY crates/adapters/rss/Cargo.toml crates/adapters/rss/Cargo.toml
|
COPY crates/adapters/rss/Cargo.toml crates/adapters/rss/Cargo.toml
|
||||||
COPY crates/adapters/sqlite/Cargo.toml crates/adapters/sqlite/Cargo.toml
|
COPY crates/adapters/sqlite/Cargo.toml crates/adapters/sqlite/Cargo.toml
|
||||||
COPY crates/adapters/sqlite-federation/Cargo.toml crates/adapters/sqlite-federation/Cargo.toml
|
COPY crates/adapters/sqlite-federation/Cargo.toml crates/adapters/sqlite-federation/Cargo.toml
|
||||||
COPY crates/adapters/template-askama/Cargo.toml crates/adapters/template-askama/Cargo.toml
|
|
||||||
COPY crates/api-types/Cargo.toml crates/api-types/Cargo.toml
|
COPY crates/api-types/Cargo.toml crates/api-types/Cargo.toml
|
||||||
COPY crates/application/Cargo.toml crates/application/Cargo.toml
|
COPY crates/application/Cargo.toml crates/application/Cargo.toml
|
||||||
COPY crates/adapters/tmdb-enrichment/Cargo.toml crates/adapters/tmdb-enrichment/Cargo.toml
|
COPY crates/adapters/tmdb-enrichment/Cargo.toml crates/adapters/tmdb-enrichment/Cargo.toml
|
||||||
COPY crates/domain/Cargo.toml crates/domain/Cargo.toml
|
COPY crates/domain/Cargo.toml crates/domain/Cargo.toml
|
||||||
COPY crates/bootstrap/Cargo.toml crates/bootstrap/Cargo.toml
|
COPY crates/bootstrap/Cargo.toml crates/bootstrap/Cargo.toml
|
||||||
COPY crates/presentation/Cargo.toml crates/presentation/Cargo.toml
|
COPY crates/adapters/http-axum/Cargo.toml crates/adapters/http-axum/Cargo.toml
|
||||||
COPY crates/server/Cargo.toml crates/server/Cargo.toml
|
COPY crates/server/Cargo.toml crates/server/Cargo.toml
|
||||||
COPY crates/tui/Cargo.toml crates/tui/Cargo.toml
|
COPY crates/tui/Cargo.toml crates/tui/Cargo.toml
|
||||||
COPY crates/adapters/image-converter/Cargo.toml crates/adapters/image-converter/Cargo.toml
|
COPY crates/adapters/image-converter/Cargo.toml crates/adapters/image-converter/Cargo.toml
|
||||||
|
|||||||
14
Makefile
14
Makefile
@@ -12,26 +12,26 @@ check-appcontext:
|
|||||||
@echo "✅ No AppContext in application crate"
|
@echo "✅ No AppContext in application crate"
|
||||||
|
|
||||||
check-handler-deps:
|
check-handler-deps:
|
||||||
@if grep -rnE "[A-Za-z0-9_]*Deps(\s*\{|::from\()" crates/presentation/src --include="*.rs" --exclude-dir=tests | grep -q .; then \
|
@if grep -rnE "[A-Za-z0-9_]*Deps(\s*\{|::from\()" crates/adapters/http-axum/src --include="*.rs" --exclude-dir=tests | grep -q .; then \
|
||||||
echo "❌ hand-built deps struct found in presentation:"; \
|
echo "❌ hand-built deps struct found in presentation:"; \
|
||||||
grep -rnE "[A-Za-z0-9_]*Deps(\s*\{|::from\()" crates/presentation/src --include="*.rs" --exclude-dir=tests; \
|
grep -rnE "[A-Za-z0-9_]*Deps(\s*\{|::from\()" crates/adapters/http-axum/src --include="*.rs" --exclude-dir=tests; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
@echo "✅ No hand-built deps structs in presentation"
|
@echo "✅ No hand-built deps structs in presentation"
|
||||||
|
|
||||||
check-handler-repos:
|
check-handler-repos:
|
||||||
@if grep -rn "app_ctx\.repos" crates/presentation/src --include="*.rs" --exclude-dir=tests | grep -q .; then \
|
@if grep -rn "app_ctx\.repos" crates/adapters/http-axum/src --include="*.rs" --exclude-dir=tests | grep -q .; then \
|
||||||
echo "❌ app_ctx.repos referenced in presentation:"; \
|
echo "❌ app_ctx.repos referenced in presentation:"; \
|
||||||
grep -rn "app_ctx\.repos" crates/presentation/src --include="*.rs" --exclude-dir=tests; \
|
grep -rn "app_ctx\.repos" crates/adapters/http-axum/src --include="*.rs" --exclude-dir=tests; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
@echo "✅ No app_ctx.repos references in presentation"
|
@echo "✅ No app_ctx.repos references in presentation"
|
||||||
|
|
||||||
|
|
||||||
PRESENTATION_ALLOWED_ADAPTERS := template-askama
|
PRESENTATION_ALLOWED_ADAPTERS :=
|
||||||
|
|
||||||
check-presentation-adapters:
|
check-presentation-adapters:
|
||||||
@deps=$$(awk '/^\[dependencies\]/{f=1;next} /^\[/{f=0} f' crates/presentation/Cargo.toml \
|
@deps=$$(awk '/^\[dependencies\]/{f=1;next} /^\[/{f=0} f' crates/adapters/http-axum/Cargo.toml \
|
||||||
| sed -n 's/^\([A-Za-z0-9_-]\{1,\}\)[[:space:]]*=.*/\1/p'); \
|
| sed -n 's/^\([A-Za-z0-9_-]\{1,\}\)[[:space:]]*=.*/\1/p'); \
|
||||||
bad=""; \
|
bad=""; \
|
||||||
for a in $$(ls crates/adapters); do \
|
for a in $$(ls crates/adapters); do \
|
||||||
@@ -43,7 +43,7 @@ check-presentation-adapters:
|
|||||||
echo " presentation may name renderers, not reachers — see ADR-0008."; \
|
echo " presentation may name renderers, not reachers — see ADR-0008."; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi; \
|
fi; \
|
||||||
echo "✅ presentation depends on no adapter crate but $(PRESENTATION_ALLOWED_ADAPTERS)"
|
echo "✅ http-axum depends on no other adapter crate"
|
||||||
|
|
||||||
|
|
||||||
check-federation-off-social:
|
check-federation-off-social:
|
||||||
|
|||||||
@@ -90,12 +90,12 @@ Open `http://localhost:3000`. The HTTP server and background worker start togeth
|
|||||||
Hexagonal (Ports & Adapters) with Domain-Driven Design:
|
Hexagonal (Ports & Adapters) with Domain-Driven Design:
|
||||||
|
|
||||||
```
|
```
|
||||||
api-types — shared REST API request/response DTOs (Serialize/Deserialize + utoipa schemas) + HtmlPageContext; used by presentation, tui, and template adapters
|
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
|
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
|
bootstrap — composition root: the only crate that names adapters; builds the server router and the worker service
|
||||||
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
|
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
|
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
|
||||||
presentation — Axum HTTP router, OpenAPI spec assembly, Swagger UI + Scalar serving; library crate, no binary of its own
|
adapters/http-axum — Axum HTTP router, OpenAPI spec assembly, Swagger UI + Scalar serving; library crate, no binary of its own
|
||||||
server — thin HTTP binary (dotenv, config, bootstrap::build_server, axum::serve); features (federation, nats) forward to bootstrap
|
server — thin HTTP binary (dotenv, config, bootstrap::build_server, axum::serve); features (federation, nats) forward to bootstrap
|
||||||
worker — thin worker binary (bootstrap::build_worker, run until ctrl-c)
|
worker — thin worker binary (bootstrap::build_worker, run until ctrl-c)
|
||||||
adapters/
|
adapters/
|
||||||
@@ -106,7 +106,6 @@ adapters/
|
|||||||
object-storage — stores images (posters + user avatars) on local filesystem or S3-compatible storage
|
object-storage — stores images (posters + user avatars) on local filesystem or S3-compatible storage
|
||||||
image-converter — optional background worker: converts stored images to AVIF or WebP; backfills existing images via a 24h periodic job
|
image-converter — optional background worker: converts stored images to AVIF or WebP; backfills existing images via a 24h periodic job
|
||||||
tmdb-enrichment — TMDb HTTP client implementing MovieEnrichmentClient and PersonEnrichmentClient; event handlers (MovieEnrichmentHandler, PersonEnrichmentHandler) live in the application layer
|
tmdb-enrichment — TMDb HTTP client implementing MovieEnrichmentClient and PersonEnrichmentClient; event handlers (MovieEnrichmentHandler, PersonEnrichmentHandler) live in the application layer
|
||||||
template-askama — Askama HTML rendering
|
|
||||||
rss — RSS/Atom feed generation
|
rss — RSS/Atom feed generation
|
||||||
export — CSV and JSON diary serialization
|
export — CSV and JSON diary serialization
|
||||||
importer — CSV/TSV/JSON/XLSX parser and column mapper for bulk import
|
importer — CSV/TSV/JSON/XLSX parser and column mapper for bulk import
|
||||||
@@ -115,7 +114,7 @@ adapters/
|
|||||||
nats — NATS Core / JetStream event publisher and consumer
|
nats — NATS Core / JetStream event publisher and consumer
|
||||||
activitypub — ActivityPub federation adapter (follow, inbox/outbox, actor); delegates to k-ap for protocol internals
|
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
|
||||||
tui — terminal UI client (ratatui); shares api-types with presentation for typed API access
|
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
|
spa/ — React SPA (TanStack Router + shadcn/ui + Vite); served at /app/ by the backend
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ title: Movies Diary — Hexagonal Architecture
|
|||||||
---
|
---
|
||||||
graph TB
|
graph TB
|
||||||
subgraph Binaries["Binaries (Composition Root)"]
|
subgraph Binaries["Binaries (Composition Root)"]
|
||||||
WEB["presentation<br/><i>Axum web server</i><br/>Routes, Handlers, Mappers"]
|
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"]
|
WORKER["worker<br/><i>Event consumer</i><br/>Concurrent dispatch, graceful shutdown"]
|
||||||
TUI["tui<br/><i>Terminal UI</i>"]
|
TUI["tui<br/><i>Terminal UI</i>"]
|
||||||
end
|
end
|
||||||
@@ -110,7 +110,6 @@ graph TB
|
|||||||
A_POSTER_SYNC["poster-sync"]
|
A_POSTER_SYNC["poster-sync"]
|
||||||
end
|
end
|
||||||
subgraph Presentation["Presentation Helpers"]
|
subgraph Presentation["Presentation Helpers"]
|
||||||
A_TEMPLATE["template-askama<br/><i>HTML templates</i>"]
|
|
||||||
A_RSS["rss<br/><i>Feed generation</i>"]
|
A_RSS["rss<br/><i>Feed generation</i>"]
|
||||||
A_EXPORT["export<br/><i>CSV/JSON diary</i>"]
|
A_EXPORT["export<br/><i>CSV/JSON diary</i>"]
|
||||||
A_IMPORT["importer<br/><i>CSV/JSON/XLSX parser</i>"]
|
A_IMPORT["importer<br/><i>CSV/JSON/XLSX parser</i>"]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "presentation"
|
name = "http-axum"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
description = "Self-hosted movie diary with REST API and ActivityPub federation"
|
description = "Self-hosted movie diary with REST API and ActivityPub federation"
|
||||||
@@ -33,7 +33,7 @@ futures = { workspace = true }
|
|||||||
api-types = { workspace = true }
|
api-types = { workspace = true }
|
||||||
domain = { workspace = true }
|
domain = { workspace = true }
|
||||||
application = { workspace = true }
|
application = { workspace = true }
|
||||||
template-askama = { workspace = true }
|
askama = { version = "0.16.0" }
|
||||||
utoipa = { version = "5.5.0", features = ["axum_extras", "uuid"] }
|
utoipa = { version = "5.5.0", features = ["axum_extras", "uuid"] }
|
||||||
utoipa-scalar = { version = "0.3.0", features = ["axum"], default-features = false }
|
utoipa-scalar = { version = "0.3.0", features = ["axum"], default-features = false }
|
||||||
utoipa-swagger-ui = { version = "9.0.2", features = ["axum", "vendored"] }
|
utoipa-swagger-ui = { version = "9.0.2", features = ["axum", "vendored"] }
|
||||||
@@ -10,6 +10,7 @@ use application::auth::{
|
|||||||
commands::RegisterCommand, login as login_uc, queries::LoginCommand, register as register_uc,
|
commands::RegisterCommand, login as login_uc, queries::LoginCommand, register as register_uc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::html::templates::{LoginTemplate, RegisterTemplate};
|
||||||
use crate::{
|
use crate::{
|
||||||
csrf::{CsrfToken, secure_flag},
|
csrf::{CsrfToken, secure_flag},
|
||||||
errors::ApiError,
|
errors::ApiError,
|
||||||
@@ -21,7 +22,6 @@ use api_types::HtmlPageContext;
|
|||||||
use api_types::{
|
use api_types::{
|
||||||
LoginRequest, LoginResponse, LogoutRequest, RefreshRequest, RefreshResponse, RegisterRequest,
|
LoginRequest, LoginResponse, LogoutRequest, RefreshRequest, RefreshResponse, RegisterRequest,
|
||||||
};
|
};
|
||||||
use template_askama::{LoginTemplate, RegisterTemplate};
|
|
||||||
|
|
||||||
// ── HTML helpers ─────────────────────────────────────────────────────────────
|
// ── HTML helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -12,6 +12,7 @@ use application::diary::{
|
|||||||
queries::GetActivityFeedQuery,
|
queries::GetActivityFeedQuery,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::html::templates::{ActivityFeedTemplate, NewReviewTemplate, build_page_items};
|
||||||
use crate::{
|
use crate::{
|
||||||
csrf::CsrfToken,
|
csrf::CsrfToken,
|
||||||
errors::ApiError,
|
errors::ApiError,
|
||||||
@@ -24,7 +25,6 @@ use api_types::{
|
|||||||
ActivityFeedQueryParams, ActivityFeedResponse, DiaryQueryParams, DiaryResponse,
|
ActivityFeedQueryParams, ActivityFeedResponse, DiaryQueryParams, DiaryResponse,
|
||||||
EditReviewRequest, ExportQueryParams, LogReviewRequest,
|
EditReviewRequest, ExportQueryParams, LogReviewRequest,
|
||||||
};
|
};
|
||||||
use template_askama::{ActivityFeedTemplate, NewReviewTemplate, build_page_items};
|
|
||||||
|
|
||||||
use super::helpers::{build_export_response, build_page_context, encode_domain_error};
|
use super::helpers::{build_export_response, build_page_context, encode_domain_error};
|
||||||
|
|
||||||
@@ -11,6 +11,10 @@ use axum::{
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::html::templates::{
|
||||||
|
ImportMappingTemplate, ImportPreviewRow, ImportPreviewTemplate, ImportProfileView,
|
||||||
|
ImportUploadTemplate,
|
||||||
|
};
|
||||||
use crate::render::render_page;
|
use crate::render::render_page;
|
||||||
use application::import::{
|
use application::import::{
|
||||||
apply_mapping as apply_import_mapping, apply_profile_and_map,
|
apply_mapping as apply_import_mapping, apply_profile_and_map,
|
||||||
@@ -28,10 +32,6 @@ use domain::models::{
|
|||||||
import::{DomainField, Transform},
|
import::{DomainField, Transform},
|
||||||
};
|
};
|
||||||
use domain::value_objects::ImportSessionId;
|
use domain::value_objects::ImportSessionId;
|
||||||
use template_askama::{
|
|
||||||
ImportMappingTemplate, ImportPreviewRow, ImportPreviewTemplate, ImportProfileView,
|
|
||||||
ImportUploadTemplate,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
csrf::CsrfToken,
|
csrf::CsrfToken,
|
||||||
@@ -18,11 +18,11 @@ use application::integrations::{
|
|||||||
revoke_token as revoke_webhook_token,
|
revoke_token as revoke_webhook_token,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::html::templates::{IntegrationsTemplate, WatchQueueTemplate};
|
||||||
use crate::{
|
use crate::{
|
||||||
csrf::CsrfToken, extractors::RequiredCookieUser, forms::ErrorQuery, render::render_page,
|
csrf::CsrfToken, extractors::RequiredCookieUser, forms::ErrorQuery, render::render_page,
|
||||||
state::AppState,
|
state::AppState,
|
||||||
};
|
};
|
||||||
use template_askama::{IntegrationsTemplate, WatchQueueTemplate};
|
|
||||||
|
|
||||||
use super::helpers::{build_page_context, encode_domain_error};
|
use super::helpers::{build_page_context, encode_domain_error};
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ pub async fn get_integrations_page(
|
|||||||
.await
|
.await
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let token_views: Vec<template_askama::WebhookTokenView> = tokens
|
let token_views: Vec<crate::html::templates::WebhookTokenView> = tokens
|
||||||
.iter()
|
.iter()
|
||||||
.map(crate::mappers::integrations::webhook_token_view)
|
.map(crate::mappers::integrations::webhook_token_view)
|
||||||
.collect();
|
.collect();
|
||||||
@@ -146,7 +146,7 @@ pub async fn get_watch_queue_page(
|
|||||||
.await
|
.await
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let entries: Vec<template_askama::WatchQueueDisplayEntry> = events
|
let entries: Vec<crate::html::templates::WatchQueueDisplayEntry> = events
|
||||||
.iter()
|
.iter()
|
||||||
.map(crate::mappers::integrations::watch_queue_entry)
|
.map(crate::mappers::integrations::watch_queue_entry)
|
||||||
.collect();
|
.collect();
|
||||||
@@ -17,6 +17,7 @@ use application::{
|
|||||||
};
|
};
|
||||||
use domain::services::review_history::Trend;
|
use domain::services::review_history::Trend;
|
||||||
|
|
||||||
|
use crate::html::templates::MovieDetailTemplate;
|
||||||
use crate::{
|
use crate::{
|
||||||
csrf::CsrfToken,
|
csrf::CsrfToken,
|
||||||
errors::ApiError,
|
errors::ApiError,
|
||||||
@@ -29,7 +30,6 @@ use api_types::{
|
|||||||
MovieStatsDto, MoviesQueryParams, MoviesResponse, PaginationQueryParams, ReviewHistoryResponse,
|
MovieStatsDto, MoviesQueryParams, MoviesResponse, PaginationQueryParams, ReviewHistoryResponse,
|
||||||
SocialFeedResponse, SocialReviewDto,
|
SocialFeedResponse, SocialReviewDto,
|
||||||
};
|
};
|
||||||
use template_askama::MovieDetailTemplate;
|
|
||||||
|
|
||||||
use super::helpers::build_page_context;
|
use super::helpers::build_page_context;
|
||||||
|
|
||||||
@@ -6,6 +6,9 @@ use axum::{
|
|||||||
};
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[cfg(feature = "federation")]
|
||||||
|
use crate::html::templates::{BlockedActorsTemplate, BlockedDomainsTemplate};
|
||||||
|
use crate::html::templates::{FollowersTemplate, FollowingTemplate, RemoteActorData};
|
||||||
use crate::{
|
use crate::{
|
||||||
csrf::CsrfToken,
|
csrf::CsrfToken,
|
||||||
errors::ApiError,
|
errors::ApiError,
|
||||||
@@ -26,9 +29,6 @@ use api_types::{
|
|||||||
#[cfg(feature = "federation")]
|
#[cfg(feature = "federation")]
|
||||||
use api_types::{AddBlockedDomainRequest, BlockedActorResponse, BlockedDomainResponse};
|
use api_types::{AddBlockedDomainRequest, BlockedActorResponse, BlockedDomainResponse};
|
||||||
use domain::value_objects::{FollowTarget, InstanceIdentity, SocialActor, SocialIdentity};
|
use domain::value_objects::{FollowTarget, InstanceIdentity, SocialActor, SocialIdentity};
|
||||||
#[cfg(feature = "federation")]
|
|
||||||
use template_askama::{BlockedActorsTemplate, BlockedDomainsTemplate};
|
|
||||||
use template_askama::{FollowersTemplate, FollowingTemplate, RemoteActorData};
|
|
||||||
|
|
||||||
use super::helpers::{build_page_context, encode_domain_error};
|
use super::helpers::{build_page_context, encode_domain_error};
|
||||||
|
|
||||||
@@ -917,9 +917,9 @@ pub async fn get_blocked_domains_page(
|
|||||||
ctx.canonical_url = format!("{}/admin/blocked-domains", state.app_ctx.config.base_url);
|
ctx.canonical_url = format!("{}/admin/blocked-domains", state.app_ctx.config.base_url);
|
||||||
match state.app_ctx.ap_blocklist.get_blocked_domains().await {
|
match state.app_ctx.ap_blocklist.get_blocked_domains().await {
|
||||||
Ok(domains) => {
|
Ok(domains) => {
|
||||||
let entries: Vec<template_askama::BlockedDomainEntry> = domains
|
let entries: Vec<crate::html::templates::BlockedDomainEntry> = domains
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|d| template_askama::BlockedDomainEntry {
|
.map(|d| crate::html::templates::BlockedDomainEntry {
|
||||||
domain: d.domain,
|
domain: d.domain,
|
||||||
reason: d.reason,
|
reason: d.reason,
|
||||||
blocked_at: d.blocked_at,
|
blocked_at: d.blocked_at,
|
||||||
@@ -1008,9 +1008,9 @@ pub async fn get_blocked_actors_page(
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(blocked) => {
|
Ok(blocked) => {
|
||||||
let entries: Vec<template_askama::BlockedActorEntry> = blocked
|
let entries: Vec<crate::html::templates::BlockedActorEntry> = blocked
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|a| template_askama::BlockedActorEntry {
|
.map(|a| crate::html::templates::BlockedActorEntry {
|
||||||
url: instance.actor_url_of(&a.identity),
|
url: instance.actor_url_of(&a.identity),
|
||||||
handle: a.handle,
|
handle: a.handle,
|
||||||
display_name: a.display_name,
|
display_name: a.display_name,
|
||||||
@@ -16,6 +16,11 @@ use application::users::{
|
|||||||
};
|
};
|
||||||
use domain::{errors::DomainError, value_objects::Username};
|
use domain::{errors::DomainError, value_objects::Username};
|
||||||
|
|
||||||
|
use crate::html::templates::{
|
||||||
|
EmbedProfileTemplate, MonthlyRatingRow, ProfileSettingsTemplate, ProfileTemplate,
|
||||||
|
RemoteActorData, RemoteActorDisplay, UserSummaryView, UsersTemplate, bar_height_px,
|
||||||
|
build_heatmap, build_page_items,
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
csrf::CsrfToken,
|
csrf::CsrfToken,
|
||||||
errors::ApiError,
|
errors::ApiError,
|
||||||
@@ -28,11 +33,6 @@ use api_types::{
|
|||||||
ProfileResponse, UserProfileQueryParams, UserProfileResponse, UserStatsDto, UserSummaryDto,
|
ProfileResponse, UserProfileQueryParams, UserProfileResponse, UserStatsDto, UserSummaryDto,
|
||||||
UserTrendsDto, UsersResponse, WatchMediumStatDto,
|
UserTrendsDto, UsersResponse, WatchMediumStatDto,
|
||||||
};
|
};
|
||||||
use template_askama::{
|
|
||||||
EmbedProfileTemplate, MonthlyRatingRow, ProfileSettingsTemplate, ProfileTemplate,
|
|
||||||
RemoteActorData, RemoteActorDisplay, UserSummaryView, UsersTemplate, bar_height_px,
|
|
||||||
build_heatmap, build_page_items,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::goals::goal_with_progress_to_dto;
|
use super::goals::goal_with_progress_to_dto;
|
||||||
use super::helpers::build_page_context;
|
use super::helpers::build_page_context;
|
||||||
@@ -502,7 +502,7 @@ struct PaginationInfo {
|
|||||||
offset: u32,
|
offset: u32,
|
||||||
has_more: bool,
|
has_more: bool,
|
||||||
limit: u32,
|
limit: u32,
|
||||||
page_items: Vec<template_askama::PageItem>,
|
page_items: Vec<crate::html::templates::PageItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_pagination(
|
fn compute_pagination(
|
||||||
@@ -571,7 +571,7 @@ fn build_monthly_rating_rows<'a>(
|
|||||||
async fn fetch_profile_goals(
|
async fn fetch_profile_goals(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
user_id: Uuid,
|
user_id: Uuid,
|
||||||
) -> Vec<template_askama::GoalViewData> {
|
) -> Vec<crate::html::templates::GoalViewData> {
|
||||||
let goals_list = application::goals::list::execute(
|
let goals_list = application::goals::list::execute(
|
||||||
&state.app_ctx.deps.goals.query,
|
&state.app_ctx.deps.goals.query,
|
||||||
application::goals::queries::ListGoalsQuery { user_id },
|
application::goals::queries::ListGoalsQuery { user_id },
|
||||||
@@ -580,7 +580,7 @@ async fn fetch_profile_goals(
|
|||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
goals_list
|
goals_list
|
||||||
.iter()
|
.iter()
|
||||||
.map(|g| template_askama::GoalViewData {
|
.map(|g| crate::html::templates::GoalViewData {
|
||||||
year: g.goal.year(),
|
year: g.goal.year(),
|
||||||
target_count: g.goal.target_count(),
|
target_count: g.goal.target_count(),
|
||||||
current_count: g.current_count,
|
current_count: g.current_count,
|
||||||
@@ -18,6 +18,7 @@ use application::{
|
|||||||
};
|
};
|
||||||
use domain::errors::DomainError;
|
use domain::errors::DomainError;
|
||||||
|
|
||||||
|
use crate::html::templates::WatchlistTemplate;
|
||||||
use crate::{
|
use crate::{
|
||||||
csrf::CsrfToken,
|
csrf::CsrfToken,
|
||||||
errors::ApiError,
|
errors::ApiError,
|
||||||
@@ -29,7 +30,6 @@ use api_types::{
|
|||||||
AddToWatchlistRequest, PaginationQueryParams, WatchlistEntryDto, WatchlistResponse,
|
AddToWatchlistRequest, PaginationQueryParams, WatchlistEntryDto, WatchlistResponse,
|
||||||
WatchlistStatusResponse,
|
WatchlistStatusResponse,
|
||||||
};
|
};
|
||||||
use template_askama::WatchlistTemplate;
|
|
||||||
|
|
||||||
use super::helpers::{build_page_context, encode_error};
|
use super::helpers::{build_page_context, encode_error};
|
||||||
|
|
||||||
@@ -219,7 +219,7 @@ fn render_wrapup(
|
|||||||
.take(8)
|
.take(8)
|
||||||
.map(|g| g.count as f64 / genre_max as f64 * 100.0)
|
.map(|g| g.count as f64 / genre_max as f64 * 100.0)
|
||||||
.collect();
|
.collect();
|
||||||
let tmpl = template_askama::WrapUpPageTemplate {
|
let tmpl = crate::html::templates::WrapUpPageTemplate {
|
||||||
ctx,
|
ctx,
|
||||||
report,
|
report,
|
||||||
year_label: year.to_string(),
|
year_label: year.to_string(),
|
||||||
1
crates/adapters/http-axum/src/html/mod.rs
Normal file
1
crates/adapters/http-axum/src/html/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod templates;
|
||||||
@@ -136,7 +136,7 @@ pub struct MovieDetailTemplate<'a> {
|
|||||||
pub struct WatchlistTemplate<'a> {
|
pub struct WatchlistTemplate<'a> {
|
||||||
pub ctx: &'a HtmlPageContext,
|
pub ctx: &'a HtmlPageContext,
|
||||||
pub owner_id: uuid::Uuid,
|
pub owner_id: uuid::Uuid,
|
||||||
pub display_entries: &'a [crate::WatchlistDisplayEntry],
|
pub display_entries: &'a [WatchlistDisplayEntry],
|
||||||
pub current_offset: u32,
|
pub current_offset: u32,
|
||||||
pub has_more: bool,
|
pub has_more: bool,
|
||||||
pub limit: u32,
|
pub limit: u32,
|
||||||
@@ -4,6 +4,7 @@ pub mod errors;
|
|||||||
pub mod extractors;
|
pub mod extractors;
|
||||||
pub mod forms;
|
pub mod forms;
|
||||||
pub mod handlers;
|
pub mod handlers;
|
||||||
|
pub mod html;
|
||||||
pub mod mappers;
|
pub mod mappers;
|
||||||
pub mod openapi;
|
pub mod openapi;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
use crate::html::templates::{ImportPreviewRow, ImportRowStatus};
|
||||||
use domain::models::AnnotatedRow;
|
use domain::models::AnnotatedRow;
|
||||||
use domain::models::import::RowResult;
|
use domain::models::import::RowResult;
|
||||||
use template_askama::{ImportPreviewRow, ImportRowStatus};
|
|
||||||
|
|
||||||
pub fn annotated_to_preview_row(idx: usize, annotated: &AnnotatedRow) -> ImportPreviewRow {
|
pub fn annotated_to_preview_row(idx: usize, annotated: &AnnotatedRow) -> ImportPreviewRow {
|
||||||
match &annotated.result {
|
match &annotated.result {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
use crate::html::templates::{WatchQueueDisplayEntry, WebhookTokenView};
|
||||||
use domain::models::{WatchEvent, WebhookToken};
|
use domain::models::{WatchEvent, WebhookToken};
|
||||||
use template_askama::{WatchQueueDisplayEntry, WebhookTokenView};
|
|
||||||
|
|
||||||
pub fn webhook_token_view(t: &WebhookToken) -> WebhookTokenView {
|
pub fn webhook_token_view(t: &WebhookToken) -> WebhookTokenView {
|
||||||
WebhookTokenView {
|
WebhookTokenView {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
|
use crate::html::templates::{RemoteActorData, RemoteActorDisplay, UserSummaryView};
|
||||||
use application::users::get_local_profile::PendingFollowerView;
|
use application::users::get_local_profile::PendingFollowerView;
|
||||||
use domain::models::RemoteActorInfo;
|
use domain::models::RemoteActorInfo;
|
||||||
use domain::models::UserSummary;
|
use domain::models::UserSummary;
|
||||||
use template_askama::{RemoteActorData, RemoteActorDisplay, UserSummaryView};
|
|
||||||
|
|
||||||
pub fn user_summary_view(u: &UserSummary) -> UserSummaryView {
|
pub fn user_summary_view(u: &UserSummary) -> UserSummaryView {
|
||||||
let name = u
|
let name = u
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
use crate::html::templates::WatchlistDisplayEntry;
|
||||||
use domain::models::{RemoteWatchlistEntry, WatchlistWithMovie, collections::Paginated};
|
use domain::models::{RemoteWatchlistEntry, WatchlistWithMovie, collections::Paginated};
|
||||||
use template_askama::WatchlistDisplayEntry;
|
|
||||||
|
|
||||||
pub struct WatchlistPageResult {
|
pub struct WatchlistPageResult {
|
||||||
pub display_entries: Vec<WatchlistDisplayEntry>,
|
pub display_entries: Vec<WatchlistDisplayEntry>,
|
||||||
@@ -3,7 +3,7 @@ use axum::{
|
|||||||
response::{Html, IntoResponse, Response},
|
response::{Html, IntoResponse, Response},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn render_page(template: impl template_askama::askama::Template) -> Response {
|
pub fn render_page(template: impl askama::Template) -> Response {
|
||||||
match template.render() {
|
match template.render() {
|
||||||
Ok(html) => Html(html).into_response(),
|
Ok(html) => Html(html).into_response(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "template-askama"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition.workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
askama = { version = "0.16.0" }
|
|
||||||
|
|
||||||
chrono = { workspace = true }
|
|
||||||
uuid = { workspace = true }
|
|
||||||
|
|
||||||
domain = { workspace = true }
|
|
||||||
api-types = { workspace = true }
|
|
||||||
@@ -8,7 +8,7 @@ default = []
|
|||||||
nats = ["dep:nats"]
|
nats = ["dep:nats"]
|
||||||
federation = [
|
federation = [
|
||||||
"application/federation",
|
"application/federation",
|
||||||
"presentation/federation",
|
"http-axum/federation",
|
||||||
"dep:sqlite-federation",
|
"dep:sqlite-federation",
|
||||||
"dep:activitypub",
|
"dep:activitypub",
|
||||||
]
|
]
|
||||||
@@ -17,7 +17,7 @@ federation = [
|
|||||||
config = { workspace = true }
|
config = { workspace = true }
|
||||||
domain = { workspace = true }
|
domain = { workspace = true }
|
||||||
application = { workspace = true }
|
application = { workspace = true }
|
||||||
presentation = { path = "../presentation", default-features = false }
|
http-axum = { path = "../adapters/http-axum", default-features = false }
|
||||||
auth = { workspace = true }
|
auth = { workspace = true }
|
||||||
metadata = { workspace = true }
|
metadata = { workspace = true }
|
||||||
poster-fetcher = { workspace = true }
|
poster-fetcher = { workspace = true }
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ use application::config::AppConfig as AppSettings;
|
|||||||
use domain::ports::{DiaryExporter, DocumentParser, EventPublisher};
|
use domain::ports::{DiaryExporter, DocumentParser, EventPublisher};
|
||||||
use domain::value_objects::InstanceIdentity;
|
use domain::value_objects::InstanceIdentity;
|
||||||
use export::ExportAdapter;
|
use export::ExportAdapter;
|
||||||
|
use http_axum::context::AppContext;
|
||||||
|
use http_axum::{openapi, routes, state::AppState};
|
||||||
use importer::ImporterDocumentParser;
|
use importer::ImporterDocumentParser;
|
||||||
use presentation::context::AppContext;
|
|
||||||
use presentation::{openapi, routes, state::AppState};
|
|
||||||
use rss::RssAdapter;
|
use rss::RssAdapter;
|
||||||
|
|
||||||
use crate::database::{self, DatabaseOutput};
|
use crate::database::{self, DatabaseOutput};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ pub fn init_tracing(default_directives: &str) {
|
|||||||
|
|
||||||
/// `RUST_LOG` names the crates to trace in detail; anything it doesn't name
|
/// `RUST_LOG` names the crates to trace in detail; anything it doesn't name
|
||||||
/// still reports warnings and errors. Without a baseline, a filter like
|
/// still reports warnings and errors. Without a baseline, a filter like
|
||||||
/// `presentation=debug` silently drops adapter errors (e.g. the sqlx detail
|
/// `http_axum=debug` silently drops adapter errors (e.g. the sqlx detail
|
||||||
/// behind "Database operation failed").
|
/// behind "Database operation failed").
|
||||||
fn log_filter(default: &str) -> tracing_subscriber::EnvFilter {
|
fn log_filter(default: &str) -> tracing_subscriber::EnvFilter {
|
||||||
let directives = std::env::var("RUST_LOG").unwrap_or_else(|_| default.to_string());
|
let directives = std::env::var("RUST_LOG").unwrap_or_else(|_| default.to_string());
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ tracing = { workspace = true }
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
domain = { workspace = true, features = ["test-helpers"] }
|
domain = { workspace = true, features = ["test-helpers"] }
|
||||||
application = { workspace = true }
|
application = { workspace = true }
|
||||||
presentation = { path = "../presentation", default-features = false }
|
http-axum = { path = "../adapters/http-axum", default-features = false }
|
||||||
rss = { workspace = true }
|
rss = { workspace = true }
|
||||||
sqlite = { workspace = true }
|
sqlite = { workspace = true }
|
||||||
sqlx = { workspace = true }
|
sqlx = { workspace = true }
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ use domain::{
|
|||||||
},
|
},
|
||||||
value_objects::{Email, ExternalMetadataId, PasswordHash, PosterUrl, UserId},
|
value_objects::{Email, ExternalMetadataId, PasswordHash, PosterUrl, UserId},
|
||||||
};
|
};
|
||||||
|
use http_axum::context::AppContext;
|
||||||
|
use http_axum::{routes, state::AppState};
|
||||||
use http_body_util::BodyExt;
|
use http_body_util::BodyExt;
|
||||||
use presentation::context::AppContext;
|
|
||||||
use presentation::{routes, state::AppState};
|
|
||||||
use rss::RssAdapter;
|
use rss::RssAdapter;
|
||||||
use sqlite::{
|
use sqlite::{
|
||||||
SqliteDiaryRepository, SqliteInsightsQuery, SqliteMovieRepository, SqliteReviewRepository,
|
SqliteDiaryRepository, SqliteInsightsQuery, SqliteMovieRepository, SqliteReviewRepository,
|
||||||
|
|||||||
Reference in New Issue
Block a user