1mod command;
2mod controller;
3pub mod data;
4pub mod entity;
5pub mod handler;
6pub mod helper;
7pub mod middleware;
8pub mod service;
9pub mod state;
10
11use axum::routing::get;
12use command::AdminCreateCommand;
13use controller::logout::get_logout;
14use middleware::{toast_middleware, AdminAuthMiddleware};
15use quokka::{
16 handler::html::{DataTemplate, FormTemplate},
17 state::{Database, FromState, ProvideState, ProvideStateRef, State, Templating},
18};
19use rust_embed::Embed as RustEmbed;
20use service::{
21 page_loader::{
22 AdminDashboardPageLoader, AdminLoginPageLoader, AdminNavigationGroup, AdminPageLoader,
23 AdminUiElementsPageLoader, NavigationItem,
24 },
25 DbAuthProvider, DbLoginProvider,
26};
27use state::AdminState;
28
29#[derive(RustEmbed)]
30#[folder = "web/templates"]
31#[include = "*.hbs"]
32struct Templates;
33
34#[derive(RustEmbed)]
35#[folder = "web/templates"]
36#[include = "*.scss"]
37struct Styles;
38
39#[derive(RustEmbed)]
40#[folder = "web/templates"]
41#[include = "*.js"]
42struct JavaScript;
43
44#[derive(RustEmbed)]
45#[folder = "web/assets"]
46struct Resources;
47
48pub struct AdminBundle {}
49
50impl<S: State + 'static> quokka::bundle::Pouch<S> for AdminBundle
54where
55 S: ProvideState<Templating>,
56 S: ProvideState<Database>,
57 S: ProvideStateRef<AdminState<S>>,
58{
59 fn from_config(_: &quokka::config::Config) -> quokka::Result<Self>
61 where
62 Self: Sized,
63 {
64 Ok(AdminBundle {})
65 }
66
67 fn configure_commands(&self, commands: &mut quokka::state::Commands<S>) -> quokka::Result<()> {
68 commands.register_command::<AdminCreateCommand>();
69
70 Ok(())
71 }
72
73 fn configure_state(&mut self, state: &mut S) -> quokka::Result<()> {
74 let login_provider = DbLoginProvider::from_state(state);
75 let auth_provider = DbAuthProvider::from_state(state);
76 let admin: &mut AdminState<S> = state.provide_mut();
77
78 admin.add_login_provider(login_provider);
79 admin.add_auth_provider(auth_provider);
80
81 admin.add_navigation(
82 AdminNavigationGroup::new("Quokka Admin")
83 .order(-100)
84 .add(NavigationItem::new("dashboard", "Dashboard").link("/admin")),
85 );
86
87 #[cfg(debug_assertions)]
88 admin.add_navigation(
89 AdminNavigationGroup::new("Quokka Admin").add(
90 NavigationItem::new("", "Debugging")
91 .nest(NavigationItem::new("colors", "Color Overview").link("/admin/colors"))
92 .nest(
93 NavigationItem::new("ui_elements", "UI Elements Overview")
94 .link("/admin/ui-elements"),
95 ),
96 ),
97 );
98
99 Ok(())
100 }
101
102 fn configure_styles(&mut self, styles: &mut quokka::state::Styling) -> quokka::Result<()> {
103 styles.register_embedded_styles::<Styles>();
104 styles.add_merged_style_group("quokka-admin", "quokka-admin/style.scss");
105
106 Ok(())
107 }
108
109 fn configure_scripts(&mut self, scripts: &mut quokka::state::Scripting) -> quokka::Result<()> {
110 scripts.register_embedded_scripts::<JavaScript>();
111 scripts.add_merged_script_group(
112 "quokka-admin",
113 "/resources/quokka-admin/scripts/htmx.min.js",
114 );
115 scripts.add_merged_script_group("quokka-admin", "/scripting/quokka-admin/script.js");
116
117 Ok(())
118 }
119
120 fn configure_templates(
121 &mut self,
122 templates: &mut quokka::state::Templating,
123 ) -> quokka::Result<()> {
124 templates.register_embedded_templates_aliased_partials::<Templates>()
125 }
126
127 fn configure_resources(
128 &mut self,
129 resources: &mut quokka::state::Resources,
130 ) -> quokka::Result<()> {
131 resources.register_embedded_resources::<Resources>();
132
133 Ok(())
134 }
135
136 fn get_router(&mut self, state: &S) -> axum::Router<S> {
137 let admin: AdminState<S> = state.provide();
138 let mut router = admin.register_routes(axum::Router::new());
139
140 router = router
141 .route(
142 "/admin",
143 DataTemplate::<AdminDashboardPageLoader, S>::new(
144 "quokka-admin/page/dashboard/dashboard.html.hbs",
145 )
146 .into(),
147 )
148 .layer(AdminAuthMiddleware::from_state(state))
149 .route(
150 "/admin/login",
151 FormTemplate::<AdminLoginPageLoader, S>::new(
152 "quokka-admin/page/login/login.html.hbs",
153 )
154 .into(),
155 )
156 .route("/admin/logout", get(get_logout));
157
158 #[cfg(debug_assertions)]
159 {
160 router = router
161 .route(
162 "/admin/colors",
163 DataTemplate::<AdminPageLoader, S>::new(
164 "quokka-admin/page/colors/colors.html.hbs",
165 )
166 .into(),
167 )
168 .route(
169 "/admin/ui-elements",
170 DataTemplate::<AdminUiElementsPageLoader, S>::new(
171 "quokka-admin/page/ui_elements/ui_elements.html.hbs",
172 )
173 .into(),
174 )
175 .route(
176 "/admin/error",
177 DataTemplate::<AdminPageLoader, S>::new("/dev/null").into(),
178 );
179 }
180
181 router.layer(axum::middleware::from_fn(toast_middleware))
182 }
183
184 fn get_migrations(&self) -> Option<sqlx::migrate::Migrator> {
185 Some(sqlx::migrate!())
186 }
187}