Skip to main content

quokka_admin/state/
mod.rs

1use std::collections::HashMap;
2
3use quokka::{
4    config::TryFromModule,
5    state::{Database, ProvideState, Templating},
6};
7
8use crate::{
9    data::{AdminDashboardWidget, AdminNavigationGroup, AdminSidebarWidget},
10    handler::{EntityHandler, TypeErasedEntityHandler},
11    middleware::{
12        AdminAuthProvider, AdminLoginProvider, AuthProviders, InnerAuthProvider,
13        InnerLoginProvider, LoginProviders,
14    },
15    service::{
16        page_loader::{AdminCreateFormPageLoader, AdminPageLoader, AdminUpdateFormPageLoader},
17        AdminCreateForm, AdminListing, AdminUpdateForm, FormBuilder,
18    },
19};
20
21#[derive(Clone)]
22pub struct AdminState<S> {
23    pub title: String,
24    pub navigation_groups: HashMap<String, AdminNavigationGroup>,
25    pub sidebar_widgets: Vec<AdminSidebarWidget>,
26    pub dashboard_widgets: Vec<AdminDashboardWidget>,
27    pub entities: Vec<std::sync::Arc<dyn TypeErasedEntityHandler<S> + Send + Sync>>,
28    pub auth_providers: AuthProviders<S>,
29    pub login_url: String,
30    pub super_admin_group: Option<String>,
31    pub login_providers: LoginProviders,
32}
33
34#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
35pub struct AdminStateConfig {
36    pub super_admin_group: Option<String>,
37}
38
39impl<S> AdminState<S>
40where
41    S: quokka::state::State + 'static,
42{
43    ///
44    /// Adds a navigation section for your bundle
45    ///
46    pub fn add_navigation(&mut self, group: impl Into<AdminNavigationGroup>) {
47        let group: AdminNavigationGroup = group.into();
48        self.navigation_groups
49            .entry(group.title.clone())
50            .and_modify(|entry| {
51                entry.items.extend(group.items.clone());
52            })
53            .or_insert(group);
54    }
55
56    ///
57    /// Gets an ordered version of the navigation
58    ///
59    pub fn get_navigation(&self) -> Vec<AdminNavigationGroup> {
60        let mut final_navigation_groups: Vec<AdminNavigationGroup> =
61            self.navigation_groups.values().cloned().collect();
62
63        final_navigation_groups.sort_by_key(|item| item.order);
64
65        final_navigation_groups
66    }
67
68    ///
69    /// Adds a sidebar widget, check [AdminSidebarWidget] for details
70    ///
71    pub fn add_sidebar_widget(&mut self, widget: impl Into<AdminSidebarWidget>) {
72        self.sidebar_widgets.push(widget.into())
73    }
74
75    ///
76    /// Adds a dashboard widget, check [AdminDashboardWidget] for details
77    ///
78    pub fn add_dashboard_widget(&mut self, widget: impl Into<AdminDashboardWidget>) {
79        self.dashboard_widgets.push(widget.into())
80    }
81
82    ///
83    /// Registers your custom entity to be managable in the admin-ui. Check the [EntityHandler] for more details.
84    ///
85    pub fn register_entity_handler<C, U, L>(&mut self, handler: EntityHandler<S, C, U, L>)
86    where
87        S: ProvideState<Database>,
88        S: ProvideState<Templating>,
89        S: ProvideState<FormBuilder<S>>,
90        S: ProvideState<AdminCreateFormPageLoader<S, C>>,
91        S: ProvideState<AdminUpdateFormPageLoader<S, U>>,
92        S: ProvideState<AdminPageLoader>,
93        C: AdminCreateForm<S>
94            + serde::de::DeserializeOwned
95            + serde::Serialize
96            + std::fmt::Debug
97            + Send
98            + Sync
99            + 'static,
100        U: AdminUpdateForm<S>
101            + serde::de::DeserializeOwned
102            + serde::Serialize
103            + std::fmt::Debug
104            + Send
105            + Sync
106            + 'static,
107        U::PrimaryKeys: serde::de::DeserializeOwned + Send + Sync + 'static,
108        L: AdminListing<S> + Clone + Send + Sync + serde::Serialize + 'static,
109        L::PrimaryKeys: serde::de::DeserializeOwned + Send + Sync + 'static,
110        L::Entity: serde::Serialize
111            + serde::de::DeserializeOwned
112            + Clone
113            + std::fmt::Debug
114            + Send
115            + 'static,
116    {
117        self.add_navigation(handler.get_navigation());
118
119        self.entities.push(std::sync::Arc::new(handler));
120    }
121
122    ///
123    /// Registers the route for the [AdminBundle]
124    ///
125    pub fn register_routes(&self, mut router: axum::Router<S>) -> axum::Router<S> {
126        for entity in &self.entities {
127            router = router.merge(entity.get_router());
128        }
129
130        router
131    }
132
133    ///
134    /// Adds a custom auth provider. Check the [AdminAuthProvider] for more details.
135    ///
136    pub fn add_auth_provider<P: AdminAuthProvider<S> + InnerAuthProvider<S> + 'static>(
137        &mut self,
138        provider: P,
139    ) {
140        self.auth_providers
141            .providers
142            .push(std::sync::Arc::new(provider));
143    }
144
145    ///
146    /// Adds a custom login provider. Check the [AdminLoginProvider] for details.
147    ///
148    pub fn add_login_provider<
149        P: AdminLoginProvider + InnerLoginProvider + Send + Sync + 'static,
150    >(
151        &mut self,
152        provider: P,
153    ) {
154        self.login_providers
155            .providers
156            .push(std::sync::Arc::new(provider));
157    }
158}
159
160impl<S> TryFromModule for AdminState<S> {
161    async fn try_from_module(module: &quokka::config::Module) -> quokka::Result<Option<Self>>
162    where
163        Self: Sized,
164    {
165        if module.module.ne("quokka-admin") {
166            return Ok(None);
167        }
168
169        let config = module.build_config::<AdminStateConfig>()?;
170
171        Ok(Some(Self {
172            title: "Quokka Admin".to_string(),
173            login_url: "/admin/login".to_string(),
174            super_admin_group: config.super_admin_group,
175            navigation_groups: Default::default(),
176            sidebar_widgets: Default::default(),
177            dashboard_widgets: Default::default(),
178            entities: Default::default(), // The default derive fails here because of the State
179            auth_providers: Default::default(),
180            login_providers: Default::default(),
181        }))
182    }
183}