feat: account deletion request and active-user check

This commit is contained in:
2026-10-26 02:11:44 +01:00
parent 83bb5d9847
commit 0b5d18c4ee
27 changed files with 577 additions and 151 deletions

View File

@@ -26,8 +26,7 @@ and each then builds only the deps it can honestly use from it.
with no server-reachable consumer — `enrich_movie`, `reindex_search`, `merge_duplicates`,
`enrich_person`, `handle_requested` — moved into a new `application::WorkerDeps`, built by
`composition::build_worker_deps` and consumed only by `crates/worker`. Every field of `Deps` is
now reachable from `presentation` except `users.delete_account` — see the dead-code exception
recorded below — and every field of `WorkerDeps` is reachable from `worker`. No field needs an
now reachable from `presentation` and every field of `WorkerDeps` is reachable from `worker`. No field needs an
`Option` or a Noop port to express "not for this binary" — which container it lives in already
says that.
- `application::WorkerServices` is the analogous split on the services side: the strict subset of
@@ -42,11 +41,6 @@ and each then builds only the deps it can honestly use from it.
sixth, `federated_profile`, is already `Option` in `Repositories` for unrelated reasons) have no
worker-side adapter. `DatabaseOutput` only carries fields both binaries can genuinely fill, so
passing it instead of `Repositories` needed no new `Option` and no fake adapter.
- Exactly one field is unreachable from its own binary, and it is called out by name rather than
folded into a group comment: `Deps.users.delete_account` (`DeleteAccountDeps`) has zero callers
anywhere in the workspace — a pre-existing dead use case, not a consequence of this split. Every
other field that used to be described as "worker-only" or "not reachable" is now reachable from
the binary that reaches it, because it lives in that binary's own container.
- Both binaries were booted under the default feature set (`sqlite`, `sqlite-federation`) to prove
this: the worker reached its steady polling state without panicking, and the server served both
a Bearer-authenticated API call and a cookie-authenticated HTML page. A container wired to the

View File

@@ -16,7 +16,7 @@ rate limiter's own 429 text. The refusals were documented without a body schema.
**One body.** `api_types::ErrorResponse { error, code }`. `error` keeps its name and meaning (the
SPA and existing clients read it); `code` is an additive, stable machine-readable field:
`NOT_FOUND`, `INVALID_INPUT`, `UNAUTHORIZED`, `FORBIDDEN`, `RATE_LIMITED`, `INTERNAL`.
`NOT_FOUND`, `INVALID_INPUT`, `UNAUTHORIZED`, `FORBIDDEN`, `CONFLICT`, `RATE_LIMITED`, `INTERNAL`.
**One constructor.** `errors::refuse(status, code, message) -> Response` is the only place a JSON
error body is built. `ApiError` maps every `DomainError` variant to it with the status mapping that