quokka_admin/state/
mod.rs1use 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 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 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 pub fn add_sidebar_widget(&mut self, widget: impl Into<AdminSidebarWidget>) {
72 self.sidebar_widgets.push(widget.into())
73 }
74
75 pub fn add_dashboard_widget(&mut self, widget: impl Into<AdminDashboardWidget>) {
79 self.dashboard_widgets.push(widget.into())
80 }
81
82 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 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 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 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(), auth_providers: Default::default(),
180 login_providers: Default::default(),
181 }))
182 }
183}