use std::collections::HashMap;
use quokka::{
config::TryFromModule,
state::{Database, ProvideState, Templating},
};
use crate::{
data::{AdminDashboardWidget, AdminNavigationGroup, AdminSidebarWidget},
handler::{EntityHandler, TypeErasedEntityHandler},
middleware::{
AdminAuthProvider, AdminLoginProvider, AuthProviders, InnerAuthProvider,
InnerLoginProvider, LoginProviders,
},
service::{
page_loader::{AdminCreateFormPageLoader, AdminPageLoader, AdminUpdateFormPageLoader},
AdminCreateForm, AdminListing, AdminUpdateForm, FormBuilder,
},
};
#[derive(Clone)]
pub struct AdminState<S> {
pub title: String,
pub navigation_groups: HashMap<String, AdminNavigationGroup>,
pub sidebar_widgets: Vec<AdminSidebarWidget>,
pub dashboard_widgets: Vec<AdminDashboardWidget>,
pub entities: Vec<std::sync::Arc<dyn TypeErasedEntityHandler<S> + Send + Sync>>,
pub auth_providers: AuthProviders<S>,
pub login_url: String,
pub super_admin_group: Option<String>,
pub login_providers: LoginProviders,
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct AdminStateConfig {
pub super_admin_group: Option<String>,
}
impl<S> AdminState<S>
where
S: quokka::state::State + 'static,
{
pub fn add_navigation(&mut self, group: impl Into<AdminNavigationGroup>) {
let group: AdminNavigationGroup = group.into();
self.navigation_groups
.entry(group.title.clone())
.and_modify(|entry| {
entry.items.extend(group.items.clone());
})
.or_insert(group);
}
pub fn get_navigation(&self) -> Vec<AdminNavigationGroup> {
let mut final_navigation_groups: Vec<AdminNavigationGroup> =
self.navigation_groups.values().cloned().collect();
final_navigation_groups.sort_by_key(|item| item.order);
final_navigation_groups
}
pub fn add_sidebar_widget(&mut self, widget: impl Into<AdminSidebarWidget>) {
self.sidebar_widgets.push(widget.into())
}
pub fn add_dashboard_widget(&mut self, widget: impl Into<AdminDashboardWidget>) {
self.dashboard_widgets.push(widget.into())
}
pub fn register_entity_handler<C, U, L>(&mut self, handler: EntityHandler<S, C, U, L>)
where
S: ProvideState<Database>,
S: ProvideState<Templating>,
S: ProvideState<FormBuilder<S>>,
S: ProvideState<AdminCreateFormPageLoader<S, C>>,
S: ProvideState<AdminUpdateFormPageLoader<S, U>>,
S: ProvideState<AdminPageLoader>,
C: AdminCreateForm<S>
+ serde::de::DeserializeOwned
+ serde::Serialize
+ std::fmt::Debug
+ Send
+ Sync
+ 'static,
U: AdminUpdateForm<S>
+ serde::de::DeserializeOwned
+ serde::Serialize
+ std::fmt::Debug
+ Send
+ Sync
+ 'static,
U::PrimaryKeys: serde::de::DeserializeOwned + Send + Sync + 'static,
L: AdminListing<S> + Clone + Send + Sync + serde::Serialize + 'static,
L::PrimaryKeys: serde::de::DeserializeOwned + Send + Sync + 'static,
L::Entity: serde::Serialize
+ serde::de::DeserializeOwned
+ Clone
+ std::fmt::Debug
+ Send
+ 'static,
{
self.add_navigation(handler.get_navigation());
self.entities.push(std::sync::Arc::new(handler));
}
pub fn register_routes(&self, mut router: axum::Router<S>) -> axum::Router<S> {
for entity in &self.entities {
router = router.merge(entity.get_router());
}
router
}
pub fn add_auth_provider<P: AdminAuthProvider<S> + InnerAuthProvider<S> + 'static>(
&mut self,
provider: P,
) {
self.auth_providers
.providers
.push(std::sync::Arc::new(provider));
}
pub fn add_login_provider<
P: AdminLoginProvider + InnerLoginProvider + Send + Sync + 'static,
>(
&mut self,
provider: P,
) {
self.login_providers
.providers
.push(std::sync::Arc::new(provider));
}
}
impl<S> TryFromModule for AdminState<S> {
async fn try_from_module(module: &quokka::config::Module) -> quokka::Result<Option<Self>>
where
Self: Sized,
{
if module.module.ne("quokka-admin") {
return Ok(None);
}
let config = module.build_config::<AdminStateConfig>()?;
Ok(Some(Self {
title: "Quokka Admin".to_string(),
login_url: "/admin/login".to_string(),
super_admin_group: config.super_admin_group,
navigation_groups: Default::default(),
sidebar_widgets: Default::default(),
dashboard_widgets: Default::default(),
entities: Default::default(), auth_providers: Default::default(),
login_providers: Default::default(),
}))
}
}