Skip to main content

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(&registry),
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}