quokka-admin 0.1.0

An admin panel for quokka
Documentation
mod command;
mod controller;
pub mod data;
pub mod entity;
pub mod handler;
pub mod helper;
pub mod middleware;
pub mod service;
pub mod state;

use axum::routing::get;
use command::AdminCreateCommand;
use controller::logout::get_logout;
use middleware::{toast_middleware, AdminAuthMiddleware};
use quokka::{
    handler::html::{DataTemplate, FormTemplate},
    state::{Database, FromState, ProvideState, ProvideStateRef, State, Templating},
};
use rust_embed::Embed as RustEmbed;
use service::{
    page_loader::{
        AdminDashboardPageLoader, AdminLoginPageLoader, AdminNavigationGroup, AdminPageLoader,
        AdminUiElementsPageLoader, NavigationItem,
    },
    DbAuthProvider, DbLoginProvider,
};
use state::AdminState;

#[derive(RustEmbed)]
#[folder = "web/templates"]
#[include = "*.hbs"]
struct Templates;

#[derive(RustEmbed)]
#[folder = "web/templates"]
#[include = "*.scss"]
struct Styles;

#[derive(RustEmbed)]
#[folder = "web/templates"]
#[include = "*.js"]
struct JavaScript;

#[derive(RustEmbed)]
#[folder = "web/assets"]
struct Resources;

pub struct AdminBundle {}

/// This is provides the minimum to get a Bundle started.
///
/// See the [quokka::bundle::Pouch] for available setup functions.
impl<S: State + 'static> quokka::bundle::Pouch<S> for AdminBundle
where
    S: ProvideState<Templating>,
    S: ProvideState<Database>,
    S: ProvideStateRef<AdminState<S>>,
{
    /// Find your config module from here
    fn from_config(_: &quokka::config::Config) -> quokka::Result<Self>
    where
        Self: Sized,
    {
        Ok(AdminBundle {})
    }

    fn configure_commands(&self, commands: &mut quokka::state::Commands<S>) -> quokka::Result<()> {
        commands.register_command::<AdminCreateCommand>();

        Ok(())
    }

    fn configure_state(&mut self, state: &mut S) -> quokka::Result<()> {
        let login_provider = DbLoginProvider::from_state(state);
        let auth_provider = DbAuthProvider::from_state(state);
        let admin: &mut AdminState<S> = state.provide_mut();

        admin.add_login_provider(login_provider);
        admin.add_auth_provider(auth_provider);

        admin.add_navigation(
            AdminNavigationGroup::new("Quokka Admin")
                .order(-100)
                .add(NavigationItem::new("dashboard", "Dashboard").link("/admin")),
        );

        #[cfg(debug_assertions)]
        admin.add_navigation(
            AdminNavigationGroup::new("Quokka Admin").add(
                NavigationItem::new("", "Debugging")
                    .nest(NavigationItem::new("colors", "Color Overview").link("/admin/colors"))
                    .nest(
                        NavigationItem::new("ui_elements", "UI Elements Overview")
                            .link("/admin/ui-elements"),
                    ),
            ),
        );

        Ok(())
    }

    fn configure_styles(&mut self, styles: &mut quokka::state::Styling) -> quokka::Result<()> {
        styles.register_embedded_styles::<Styles>();
        styles.add_merged_style_group("quokka-admin", "quokka-admin/style.scss");

        Ok(())
    }

    fn configure_scripts(&mut self, scripts: &mut quokka::state::Scripting) -> quokka::Result<()> {
        scripts.register_embedded_scripts::<JavaScript>();
        scripts.add_merged_script_group(
            "quokka-admin",
            "/resources/quokka-admin/scripts/htmx.min.js",
        );
        scripts.add_merged_script_group("quokka-admin", "/scripting/quokka-admin/script.js");

        Ok(())
    }

    fn configure_templates(
        &mut self,
        templates: &mut quokka::state::Templating,
    ) -> quokka::Result<()> {
        templates.register_embedded_templates_aliased_partials::<Templates>()
    }

    fn configure_resources(
        &mut self,
        resources: &mut quokka::state::Resources,
    ) -> quokka::Result<()> {
        resources.register_embedded_resources::<Resources>();

        Ok(())
    }

    fn get_router(&mut self, state: &S) -> axum::Router<S> {
        let admin: AdminState<S> = state.provide();
        let mut router = admin.register_routes(axum::Router::new());

        router = router
            .route(
                "/admin",
                DataTemplate::<AdminDashboardPageLoader, S>::new(
                    "quokka-admin/page/dashboard/dashboard.html.hbs",
                )
                .into(),
            )
            .layer(AdminAuthMiddleware::from_state(state))
            .route(
                "/admin/login",
                FormTemplate::<AdminLoginPageLoader, S>::new(
                    "quokka-admin/page/login/login.html.hbs",
                )
                .into(),
            )
            .route("/admin/logout", get(get_logout));

        #[cfg(debug_assertions)]
        {
            router = router
                .route(
                    "/admin/colors",
                    DataTemplate::<AdminPageLoader, S>::new(
                        "quokka-admin/page/colors/colors.html.hbs",
                    )
                    .into(),
                )
                .route(
                    "/admin/ui-elements",
                    DataTemplate::<AdminUiElementsPageLoader, S>::new(
                        "quokka-admin/page/ui_elements/ui_elements.html.hbs",
                    )
                    .into(),
                )
                .route(
                    "/admin/error",
                    DataTemplate::<AdminPageLoader, S>::new("/dev/null").into(),
                );
        }

        router.layer(axum::middleware::from_fn(toast_middleware))
    }

    fn get_migrations(&self) -> Option<sqlx::migrate::Migrator> {
        Some(sqlx::migrate!())
    }
}