autumn_admin_plugin/lib.rs
1//! # autumn-admin-plugin
2//!
3//! Out-of-the-box admin panel plugin for autumn-web applications.
4//!
5//! Provides auto-generated CRUD views, search, filtering, and audit trails
6//! for any model registered via the [`AdminPlugin`] builder. The UI is
7//! server-rendered with Maud + HTMX — no JS build step required.
8//!
9//! # Quick start
10//!
11//! ```rust,ignore
12//! use autumn_admin_plugin::AdminPlugin;
13//!
14//! autumn_web::app()
15//! .plugin(
16//! AdminPlugin::new()
17//! .register(ProjectAdmin::default())
18//! .register(TicketAdmin::default()),
19//! )
20//! .routes(routes![...])
21//! .run()
22//! .await;
23//! ```
24//!
25//! # Security
26//!
27//! The plugin requires the `"admin"` role in the session by default. Override
28//! with [`AdminPlugin::require_role`] (pass `None` to disable; not recommended
29//! for production).
30//!
31//! # Naming convention
32//!
33//! First-party plugin: `autumn-<name>-plugin`.
34
35mod auth;
36mod registry;
37mod routes;
38mod templates;
39mod traits;
40
41pub use registry::AdminRegistry;
42pub use traits::{AdminAction, AdminField, AdminFieldKind, AdminModel};
43
44use std::borrow::Cow;
45use std::sync::Arc;
46
47use autumn_web::app::AppBuilder;
48use autumn_web::plugin::Plugin;
49
50/// The admin panel plugin.
51///
52/// Register models via `.register()` and the plugin will mount a full admin
53/// UI under the configured prefix (default: `/admin`).
54pub struct AdminPlugin {
55 registry: AdminRegistry,
56 prefix: String,
57 actuator_prefix: String,
58 auth_session_key: String,
59 require_role: Option<String>,
60}
61
62impl AdminPlugin {
63 /// Create a new admin plugin with default settings.
64 ///
65 /// Mounts at `/admin` and requires the `"admin"` role in the session.
66 /// Links to the actuator UI under `/actuator`. Reads the user
67 /// identifier from session key `"user_id"` (Autumn's default
68 /// `auth.session_key`).
69 #[must_use]
70 pub fn new() -> Self {
71 Self {
72 registry: AdminRegistry::new(),
73 prefix: "/admin".to_owned(),
74 actuator_prefix: "/actuator".to_owned(),
75 auth_session_key: "user_id".to_owned(),
76 require_role: Some("admin".to_owned()),
77 }
78 }
79
80 /// Override the URL prefix (default: `/admin`).
81 #[must_use]
82 pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
83 self.prefix = prefix.into();
84 self
85 }
86
87 /// Override the actuator mount prefix that dashboard links/polling target
88 /// (default: `/actuator`). Must match `config.actuator.prefix` from your
89 /// autumn config — the plugin cannot read it automatically because config
90 /// is loaded after `Plugin::build` runs.
91 #[must_use]
92 pub fn actuator_prefix(mut self, prefix: impl Into<String>) -> Self {
93 self.actuator_prefix = prefix.into();
94 self
95 }
96
97 /// Override the session key the role middleware reads to detect an
98 /// authenticated user. Default: `"user_id"`, matching Autumn's default
99 /// `auth.session_key`. Must match whatever your application populates
100 /// after login — e.g. set this to `"uid"` if you configured
101 /// `auth.session_key = "uid"`.
102 ///
103 /// The plugin can't read `config.auth.session_key` automatically
104 /// because config is loaded after `Plugin::build` runs.
105 #[must_use]
106 pub fn auth_session_key(mut self, key: impl Into<String>) -> Self {
107 self.auth_session_key = key.into();
108 self
109 }
110
111 /// Set the required session role for accessing the admin panel.
112 ///
113 /// Pass `None` to disable role checks entirely. Authentication
114 /// (a populated `user_id` session key) is always required when a role
115 /// is set.
116 #[must_use]
117 pub fn require_role(mut self, role: impl Into<Option<String>>) -> Self {
118 self.require_role = role.into();
119 self
120 }
121
122 /// Register a model for admin management.
123 ///
124 /// The model must implement [`AdminModel`], which provides field metadata,
125 /// CRUD operations, and display configuration.
126 #[must_use]
127 pub fn register<M: AdminModel>(mut self, model: M) -> Self {
128 self.registry.register(model);
129 self
130 }
131}
132
133impl Default for AdminPlugin {
134 fn default() -> Self {
135 Self::new()
136 }
137}
138
139impl Plugin for AdminPlugin {
140 fn name(&self) -> Cow<'static, str> {
141 Cow::Borrowed("autumn-admin-plugin")
142 }
143
144 fn build(self, app: AppBuilder) -> AppBuilder {
145 let Self {
146 registry,
147 prefix,
148 actuator_prefix,
149 auth_session_key,
150 require_role,
151 } = self;
152 let registry = Arc::new(registry);
153 let router = routes::admin_router(
154 Arc::clone(®istry),
155 &prefix,
156 actuator_prefix.clone(),
157 auth_session_key.clone(),
158 require_role.clone(),
159 );
160
161 tracing::info!(
162 prefix = %prefix,
163 actuator_prefix = %actuator_prefix,
164 auth_session_key = %auth_session_key,
165 models = registry.model_count(),
166 role = require_role.as_deref().unwrap_or("<none>"),
167 "🍂 Autumn Admin mounted"
168 );
169
170 app.nest(&prefix, router)
171 }
172}