quokka-admin 0.1.0

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

use axum::{extract::Path, http::Method};
use quokka::{
    handler::html::{FormDataStore, FormResponse, TemplateDataLoader},
    state::{FromState, ProvideState},
};

use crate::{
    data::Severity,
    service::{AdminUpdateForm, FormBuilder, FormProperties},
};

use super::{AdminPageData, AdminPageLoader};

pub struct AdminUpdateFormPageLoader<S, F> {
    form_builder: FormBuilder<S>,
    admin_page_loader: AdminPageLoader,
    state: S,
    _form: PhantomData<F>,
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct AdminFormUpdatePageData<F> {
    form: FormProperties,
    data: F,
}

impl<S, F> TemplateDataLoader<S> for AdminUpdateFormPageLoader<S, F>
where
    S: ProvideState<AdminPageLoader>,
    S: Send + Sync + Clone + 'static,
    F: AdminUpdateForm<S>
        + Send
        + Sync
        + std::fmt::Debug
        + serde::de::DeserializeOwned
        + serde::Serialize
        + 'static,
    F::PrimaryKeys: Send + Sync + serde::de::DeserializeOwned + 'static,
{
    type Args = (
        Path<F::PrimaryKeys>,
        Method,
        <AdminPageLoader as TemplateDataLoader<S>>::Args,
    );

    type Data = AdminPageData<AdminFormUpdatePageData<F>>;

    #[tracing::instrument(
        skip_all,
        target = "quokka_admin::service::page_loader::admin_update_form_page_loader::AdminUpdateFormPageLoader"
    )]
    async fn load_data(
        &self,
        (path, method, base_params): Self::Args,
    ) -> quokka::Result<Self::Data> {
        let mut base = <AdminPageLoader as TemplateDataLoader<S>>::load_data(
            &self.admin_page_loader,
            base_params,
        )
        .await?;

        if method == Method::POST {
            tracing::debug!(
                update_form = std::any::type_name::<F>(),
                "Received POST method in AdminUpdateFormPageLoader::load_data, this indicates a success for us",
            );

            base = base.message(
                Severity::Success.message("Entity Updated", "Entity updated successfully"),
            );
        }

        let form = self
            .form_builder
            .construct_form_data(F::get_form())
            .await
            .inspect_err(|error| {
                tracing::error!(
                    ?error,
                    update_form = std::any::type_name::<F>(),
                    "Unable to construct form data in update form",
                );
            })?;

        Ok(base.page(AdminFormUpdatePageData {
            form,
            data: F::get_query(&self.state, path.0)
                .await
                .inspect_err(|error| {
                    tracing::error!(
                        ?error,
                        update_form = std::any::type_name::<F>(),
                        "Unable to get entity data for update form",
                    );
                })?,
        }))
    }

    #[tracing::instrument(
        skip_all,
        target = "quokka_admin::service::page_loader::admin_update_form_page_loader::AdminUpdateFormPageLoader"
    )]
    async fn render_error(&self, error: quokka::Error) -> impl axum::response::IntoResponse {
        <AdminPageLoader as TemplateDataLoader<S>>::render_error(&self.admin_page_loader, error)
            .await
    }
}

impl<S, F> FormDataStore<S> for AdminUpdateFormPageLoader<S, F>
where
    S: ProvideState<AdminPageLoader>,
    S: Send + Sync + Clone + 'static,
    F: AdminUpdateForm<S>
        + Send
        + Sync
        + std::fmt::Debug
        + serde::de::DeserializeOwned
        + serde::Serialize
        + 'static,
    F::PrimaryKeys: Send + Sync + serde::de::DeserializeOwned + 'static,
{
    type StoreArgs = ();

    type AdditionalResponse = ();

    type Body = axum::Form<F>;

    #[tracing::instrument(
        skip_all,
        target = "quokka_admin::service::page_loader::admin_update_form_page_loader::AdminUpdateFormPageLoader"
    )]
    async fn store_data(
        &self,
        _: Self::StoreArgs,
        body: Self::Body,
    ) -> quokka::Result<FormResponse<Self::AdditionalResponse>> {
        body.0
            .update_query(&self.state)
            .await
            .inspect_err(|error| {
                tracing::error!(
                    ?error,
                    update_form = std::any::type_name::<F>(),
                    "Unable to update entity data in update form",
                );
            })?;

        Ok(FormResponse::Empty)
    }
}

impl<S, F> FromState<S> for AdminUpdateFormPageLoader<S, F>
where
    S: ProvideState<FormBuilder<S>>,
    S: ProvideState<AdminPageLoader>,
    S: Clone,
{
    fn from_state(state: &S) -> Self {
        Self {
            form_builder: state.provide(),
            admin_page_loader: state.provide(),
            state: state.clone(),
            _form: PhantomData,
        }
    }
}

impl<S: Clone, F> Clone for AdminUpdateFormPageLoader<S, F> {
    fn clone(&self) -> Self {
        Self {
            form_builder: self.form_builder.clone(),
            admin_page_loader: self.admin_page_loader.clone(),
            state: self.state.clone(),
            _form: PhantomData,
        }
    }
}