Skip to main content

full_admin/
full-admin.rs

1use axum::routing::get;
2use full::{
3    auth::{TestAuthProvider, TestLoginProvider},
4    ui::test::{TestEntityCreateForm, TestEntityListing, TestEntityUpdateForm},
5};
6use quokka::{
7    bundle::Pouch,
8    state::{
9        Database, ProvideState, ProvideStateRef, Resources, Scripting, State, Styling, Templating,
10    },
11};
12use quokka_admin::{
13    data::AdminDashboardWidget,
14    handler::EntityHandler,
15    service::page_loader::{AdminNavigationGroup, AdminSidebarWidget, NavigationItem},
16    state::AdminState,
17    AdminBundle,
18};
19
20mod full;
21
22pub const EXAMPLE_SUPER_USER_GROUP: &str = "super_example_user";
23
24#[derive(Clone, State, ProvideState)]
25pub struct ExampleState {
26    templating: Templating,
27    database: Database,
28    styling: Styling,
29    scripting: Scripting,
30    resources: Resources,
31    admin_state: AdminState<Self>,
32}
33
34struct ExampleBundle;
35
36async fn test_widget() -> String {
37    String::from("Example widget")
38}
39
40async fn time_widget() -> String {
41    format!(
42        "Current Server Time: {}",
43        time::OffsetDateTime::now_local().unwrap_or_else(|_| time::OffsetDateTime::now_utc())
44    )
45}
46
47impl<S> Pouch<S> for ExampleBundle
48where
49    S: ProvideState<Database>,
50    S: ProvideState<Templating>,
51    S: ProvideStateRef<AdminState<S>>,
52    S: State + 'static,
53{
54    fn from_config(_: &quokka::config::Config) -> quokka::Result<Self>
55    where
56        Self: Sized,
57    {
58        Ok(Self)
59    }
60
61    fn get_router(&mut self, _state: &S) -> axum::Router<S> {
62        axum::Router::new()
63            .route("/example/admin/test_widget", get(test_widget))
64            .route("/example/admin/time_widget", get(time_widget))
65    }
66
67    fn configure_state(&mut self, state: &mut S) -> quokka::Result<()> {
68        let admin: &mut AdminState<_> = state.provide_mut();
69
70        admin.super_admin_group = EXAMPLE_SUPER_USER_GROUP.to_string().into(); // This can be and usually should be set via the config
71        admin.add_auth_provider(TestAuthProvider);
72        admin.add_login_provider(TestLoginProvider);
73
74        admin.add_navigation(
75            AdminNavigationGroup::new("Example").add(
76                NavigationItem::new("example_page", "Example Page").link("/admin/example/page"),
77            ),
78        );
79
80        admin.add_sidebar_widget(AdminSidebarWidget::htmx("/example/admin/test_widget"));
81        admin.add_sidebar_widget(AdminSidebarWidget::htmx("/example/admin/time_widget")); // .hx_trigger("load, every 1s") // This widget should be refreshed, but as it spams the console I leave this comment here so you know that it is possible
82
83        admin.add_dashboard_widget(AdminDashboardWidget::htmx("/example/admin/test_widget"));
84        admin.add_dashboard_widget(AdminDashboardWidget::htmx("/example/admin/test_widget"));
85        admin.add_dashboard_widget(AdminDashboardWidget::htmx("/example/admin/test_widget"));
86        admin.add_dashboard_widget(AdminDashboardWidget::htmx("/example/admin/test_widget"));
87
88        admin.register_entity_handler(EntityHandler::<
89            S,
90            TestEntityCreateForm,
91            TestEntityUpdateForm,
92            TestEntityListing,
93        >::new("Example"));
94
95        Ok(())
96    }
97
98    fn get_migrations(&self) -> Option<sqlx::migrate::Migrator> {
99        Some(sqlx::migrate::Migrator {
100            migrations: vec![sqlx::migrate::Migration {
101                version: 1,
102                description: "Test migration".into(),
103                migration_type: sqlx::migrate::MigrationType::Simple,
104                sql: r#"CREATE TABLE IF NOT EXISTS "test" (
105                        id SERIAL NOT NULL PRIMARY KEY,
106                        name VARCHAR(255) NOT NULL,
107                        age INTEGER NOT NULL,
108                        created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
109                        updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW())"#
110                    .into(),
111                checksum: vec![1, 2, 3, 4, 5, 6].into(),
112                no_tx: true,
113            }]
114            .into(),
115            ignore_missing: true,
116            locking: true,
117            no_tx: true,
118        })
119    }
120}
121
122#[tokio::main]
123async fn main() -> quokka::Result<()> {
124    quokka::Quokka::<ExampleState>::try_default()?
125        .load::<ExampleBundle>()?
126        .load::<AdminBundle>()?
127        .serve()
128        .await
129}