quokka-admin 0.1.0

An admin panel for quokka
Documentation
use std::marker::PhantomData;

use axum::routing::get;
use quokka::{
    handler::html::{DataTemplate, FormTemplate},
    state::{ProvideState, Templating},
};

use crate::{
    controller::{entity_create::post_create_entity, entity_delete::post_delete_entity},
    data::{AdminNavigationGroup, NavigationItem},
    service::{
        page_loader::{
            AdminCreateFormPageLoader, AdminEntityDeleteConfirmPageLoader,
            AdminEntityListingPageLoader, AdminPageLoader, AdminUpdateFormPageLoader,
        },
        AdminCreateForm, AdminListing, AdminUpdateForm, FormBuilder,
    },
};
pub const ENTITY_LISTING_TEMPLATE: &str = "quokka-admin/page/listing/listing.html.hbs";
pub const FORM_CREATE_TEMPLATE: &str = "quokka-admin/page/form/create.html.hbs";
pub const FORM_UPDATE_TEMPLATE: &str = "quokka-admin/page/form/update.html.hbs";
pub const DELETE_ENTITY_CONFIRM_PAGE: &str =
    "quokka-admin/page/listing/delete_entity_confirm.html.hbs";

///
/// This is the main item for presenting your custom entity to the admin-ui. It requires a [AdminCreateForm], [AdminUpdateForm] and
/// [AdminListing].
///
#[derive(Clone, Debug)]
pub struct EntityHandler<State, CreateForm, UpdateForm, Listing> {
    _types: PhantomData<(State, CreateForm, UpdateForm, Listing)>,
    pub navigation_group: String,
    pub navigation_title: String,
}

///
/// In internal, type-erased version of the [EntityHandler]
///
#[doc(hidden)]
pub trait TypeErasedEntityHandler<S> {
    fn get_router(&self) -> axum::Router<S>;

    fn get_navigation(&self) -> AdminNavigationGroup;
}

impl<S, C, U, L: AdminListing<S>> EntityHandler<S, C, U, L> {
    pub fn new(navigation_group: impl ToString) -> Self {
        Self {
            _types: PhantomData,
            navigation_group: navigation_group.to_string(),
            navigation_title: format!("List {}", L::entity_name()),
        }
    }
}

impl<S, C, U, L> TypeErasedEntityHandler<S> for EntityHandler<S, C, U, L>
where
    S: ProvideState<Templating>,
    S: ProvideState<FormBuilder<S>>,
    S: ProvideState<AdminCreateFormPageLoader<S, C>>,
    S: ProvideState<AdminUpdateFormPageLoader<S, U>>,
    S: ProvideState<AdminPageLoader>,
    S: quokka::state::State + 'static,
    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,
{
    fn get_router(&self) -> axum::Router<S> {
        axum::Router::new()
            .route(
                &format!("/admin/entity/{}/create", C::entity_name()),
                get(DataTemplate::<AdminCreateFormPageLoader<S, C>, S>::new(
                    FORM_CREATE_TEMPLATE,
                ))
                .post(post_create_entity::<S, C>),
            )
            .route(
                &format!("/admin/entity/{}/update/{{*pks}}", U::entity_name()),
                FormTemplate::<AdminUpdateFormPageLoader<S, U>, S>::new(FORM_UPDATE_TEMPLATE)
                    .into(),
            )
            .route(
                &format!("/admin/entity/{}", L::entity_name()),
                DataTemplate::<AdminEntityListingPageLoader<S, L>, S>::new(ENTITY_LISTING_TEMPLATE)
                    .into(),
            )
            .route(
                &format!("/admin/entity/{}/delete/{{*pks}}", L::entity_name()),
                get(
                    DataTemplate::<AdminEntityDeleteConfirmPageLoader<S, L>, S>::new(
                        DELETE_ENTITY_CONFIRM_PAGE,
                    ),
                )
                .post(post_delete_entity::<S, L>),
            )
    }

    fn get_navigation(&self) -> AdminNavigationGroup {
        AdminNavigationGroup::new(&self.navigation_group).add(
            NavigationItem::new(
                format!("entity_{}", C::entity_name()),
                &self.navigation_title,
            )
            .link(format!("/admin/entity/{}", C::entity_name())),
        )
    }
}