1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
//! Axum HTTP handlers — request entry points for routes.
//!
//! # Handler basics
//!
//! Each route points to an `async fn` in `handlers.rs` (or `handlers/` submodule).
//! Handlers extract dependencies from Axum and return HTML, redirects, or files.
//!
//! ```ignore
//! use lariv_rs::{
//! components::{SharedChromeFolder, SlotCtx},
//! http::Cap,
//! web::{Htmx, html_built_page_or_app_layout},
//! };
//! use super::templates::ListPage;
//!
//! pub async fn list(
//! Cap(chrome): Cap<SharedChromeFolder>,
//! htmx: Htmx,
//! ) -> maud::Markup {
//! let page = ListPage { items: vec![] };
//! html_built_page_or_app_layout(&page, &htmx, &chrome, &SlotCtx::default())
//! }
//! ```
//!
//! # Extracting plugin state
//!
//! Mounted plugin state is available via [`Cap<T>`](crate::http::Cap):
//!
//! ```ignore
//! use lariv_rs::http::Cap;
//! use super::MyState;
//!
//! pub async fn list(Cap(state): Cap<MyState>) -> impl IntoResponse {
//! let rows = my_entity::Entity::find().all(&state.db).await?;
//! // …
//! }
//! ```
//!
//! # Authentication
//!
//! The users plugin provides middleware extractors:
//!
//! | Extractor | Behavior |
//! |-----------|----------|
//! | `RequireAuth` | 401/redirect if not logged in; provides `AuthCtx` |
//! | `OptionalAuth` | `None` for guests; used for public pages with optional user context |
//! | `RequireRole("admin")` | Role check after authentication |
//!
//! ```ignore
//! use lariv_rs::plugins::users::middleware::{RequireAuth, OptionalAuth};
//!
//! pub async fn admin_only(RequireAuth(ctx): RequireAuth) -> impl IntoResponse {
//! // ctx.user, ctx.role available
//! }
//! ```
//!
//! # HTMX responses
//!
//! [`Htmx`](crate::web::Htmx) detects partial requests. Use
//! [`html_built_page_or_app_layout`](crate::web::html_built_page_or_app_layout) for pages that
//! support both full loads and fragment swaps.
//!
//! For table filter/sort pagination, routes declare `fragment(SwapKey)` in
//! [`define_plugin_routes!`](crate::define_plugin_routes) and return markup targeting that region.
//!
//! # Query string pagination
//!
//! Use [`QueryPage`](crate::web::QueryPage) for `page` fields and [`QueryI64`](crate::web::QueryI64)
//! for optional ID filter fields on axum [`Query`] structs (especially inside
//! `#[serde(flatten)]` list filters and FK picker routes). Raw `Option<u32>` / `Option<i64>`
//! break on empty query values — see [`crate::web::query`].
//!
//! # Create/edit POST forms
//!
//! Forms that use [`form_hx_post_main`](crate::components::form_hx_post_main) must target
//! [`AppPanePost`](crate::http::AppPanePost) routes. Handlers re-render the form pane with
//! `form_error` on validation/persistence failure and call [`Htmx::redirect`](crate::web::Htmx::redirect)
//! on success. Redirect-only handlers ([`BoostPost`](crate::http::BoostPost) — delete, logout)
//! use [`form_hx_post_redirect`](crate::components::form_hx_post_redirect) instead.
//!
//! # View layers vs handlers
//!
//! For CRUD pages that load records, validate forms, and redirect, prefer composing
//! [`View`](crate::layers::View) layer stacks instead of hand-rolling handler logic.
//! See [`super::layers`].
//!
//! Handlers remain the HTTP entry point — layers run inside them via
//! [`run_layers`](crate::layers::run_layers) or the default route wrapper.