1use std::sync::Arc;
2
3use axum::{
4 Router, middleware,
5 routing::{get, post},
6};
7
8use crate::pages::script;
9use crate::pages::template;
10use crate::{
11 AppState,
12 files::download_file,
13 handler::{
14 change_password_handler, get_home_link, get_logout_link, get_me_handler, get_version,
15 login_form_handler, login_user_handler, logout_handler, refresh_access_token_handler,
16 register_user_handler,
17 },
18 jwt_auth::{admin, auth},
19 pages::{account, commodity, file_table, report, tag},
20 rate_limit::{AuthLimiter, throttle_auth},
21 redirect_middleware::redirect_on_auth_error,
22};
23
24pub fn create_admin_pages_router(app_state: Arc<AppState>) -> Router<Arc<AppState>> {
35 Router::new()
36 .route("/register", get(crate::pages::register))
37 .route_layer(middleware::from_fn_with_state(app_state.clone(), admin))
38 .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
39 .route_layer(middleware::from_fn(redirect_on_auth_error))
40 .with_state(app_state.clone())
41}
42
43pub fn create_pages_router(app_state: Arc<AppState>) -> Router<Arc<AppState>> {
44 Router::new()
45 .route(
46 "/commodity/create",
47 get(crate::pages::commodity::create::submit::commodity_create_page),
48 )
49 .route(
50 "/commodity/convert",
51 get(crate::pages::commodity::convert::submit::commodity_convert_page),
52 )
53 .route(
54 "/commodity/list",
55 get(crate::pages::commodity::list::commodity_list_page),
56 )
57 .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
58 .route_layer(middleware::from_fn(redirect_on_auth_error))
59 .with_state(app_state.clone())
60}
61
62pub fn create_accounts_router(app_state: Arc<AppState>) -> Router<Arc<AppState>> {
63 Router::new()
64 .route(
65 "/account/create",
66 get(crate::pages::account::create::submit::account_create_page),
67 )
68 .route(
69 "/account/list",
70 get(crate::pages::account::list::account_list_page),
71 )
72 .route(
73 "/account/manage",
74 get(crate::pages::account::manage::account_manage_page),
75 )
76 .route(
77 "/account/ssh-key",
78 get(crate::pages::account::ssh_key::ssh_key_page),
79 )
80 .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
81 .route_layer(middleware::from_fn(redirect_on_auth_error))
82 .with_state(app_state.clone())
83}
84
85pub fn create_transactions_router(app_state: Arc<AppState>) -> Router<Arc<AppState>> {
86 Router::new()
87 .route(
88 "/transaction/create",
89 get(crate::pages::transaction::create::submit::transaction_create_page),
90 )
91 .route(
92 "/transaction/list",
93 get(crate::pages::transaction::list::transaction_list_page),
94 )
95 .route(
96 "/transaction/edit/{id}",
97 get(crate::pages::transaction::edit::submit::transaction_edit_page),
98 )
99 .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
100 .route_layer(middleware::from_fn(redirect_on_auth_error))
101 .with_state(app_state.clone())
102}
103
104pub fn create_tags_router(app_state: Arc<AppState>) -> Router<Arc<AppState>> {
105 Router::new()
106 .route(
107 "/tag/create",
108 get(crate::pages::tag::create::tag_create_page),
109 )
110 .route("/tag/list", get(crate::pages::tag::list::tag_list_page))
111 .route(
112 "/tag/edit/{id}",
113 get(crate::pages::tag::edit::tag_edit_page),
114 )
115 .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
116 .route_layer(middleware::from_fn(redirect_on_auth_error))
117 .with_state(app_state.clone())
118}
119
120pub fn create_scripts_router(app_state: Arc<AppState>) -> Router<Arc<AppState>> {
121 Router::new()
122 .route("/script/create", get(script::edit::script_create_page))
123 .route("/script/list", get(script::list::script_list_page))
124 .route("/script/edit/{id}", get(script::edit::script_edit_page))
125 .route(
126 "/template/create",
127 get(template::edit::template_create_page),
128 )
129 .route("/template/list", get(template::list::template_list_page))
130 .route(
131 "/template/edit/{id}",
132 get(template::edit::template_edit_page),
133 )
134 .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
135 .route_layer(middleware::from_fn(redirect_on_auth_error))
136 .with_state(app_state.clone())
137}
138
139pub fn create_reports_router(app_state: Arc<AppState>) -> Router<Arc<AppState>> {
140 Router::new()
141 .route("/report/balance", get(report::balance::balance_report_page))
142 .route(
143 "/report/activity",
144 get(report::activity::activity_report_page),
145 )
146 .route(
147 "/report/category-breakdown",
148 get(report::category_breakdown::category_breakdown_report_page),
149 )
150 .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
151 .route_layer(middleware::from_fn(redirect_on_auth_error))
152 .with_state(app_state.clone())
153}
154
155pub fn create_api_router(
156 app_state: Arc<AppState>,
157 limiter: Arc<AuthLimiter>,
158) -> Router<Arc<AppState>> {
159 let login_router = Router::new()
164 .route("/auth/login", post(login_form_handler))
165 .route("/auth/login/json", post(login_user_handler))
166 .route_layer(middleware::from_fn_with_state(
167 limiter.clone(),
168 throttle_auth,
169 ));
170
171 let public_router = login_router.route("/version", get(get_version));
172
173 let password_router = Router::new()
176 .route("/auth/password", post(change_password_handler))
177 .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
178 .route_layer(middleware::from_fn_with_state(limiter, throttle_auth))
179 .with_state(app_state.clone());
180
181 let auth_router = Router::new()
182 .route("/auth/refresh", get(refresh_access_token_handler))
183 .route("/files/list", get(file_table))
184 .route("/files/download/{file_name}", get(download_file))
185 .route("/auth/logout", get(logout_handler))
186 .route("/users/me", get(get_me_handler))
187 .route("/htmx/logoutlink", get(get_logout_link))
188 .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
189 .with_state(app_state.clone());
190
191 let account_router = Router::new()
192 .route("/account/list", get(account::list::account_table))
193 .route("/account/info", get(account::list::account_info))
194 .route("/account/tree", get(account::manage::account_tree))
195 .route(
196 "/account/manage/view",
197 get(account::manage::account_manage_view),
198 )
199 .route(
200 "/account/manage/details",
201 get(account::manage::account_manage_details),
202 )
203 .route(
204 "/account/manage/tag",
205 post(account::manage::account_manage_set_tag),
206 )
207 .route(
208 "/account/manage/tag/submit",
209 post(account::manage::account_manage_set_tag_submit),
210 )
211 .route(
212 "/account/manage/tag/delete/{tag_id}",
213 post(account::manage::account_manage_delete_tag_submit),
214 )
215 .route("/account/search", post(account::search::search_accounts))
216 .route(
217 "/account/create/submit",
218 post(account::create::submit::create_account),
219 )
220 .route(
221 "/account/create/validate/name",
222 post(account::create::validate::validate_name),
223 )
224 .route("/account/rename", post(account::edit::rename_account))
225 .route(
226 "/account/{id}/tags/submit",
227 post(account::edit::account_tags_submit),
228 )
229 .route("/account/ssh-key/add", post(account::ssh_key::ssh_key_add))
230 .route(
231 "/account/ssh-key/remove",
232 post(account::ssh_key::ssh_key_remove),
233 )
234 .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
235 .with_state(app_state.clone());
236
237 let nav_router = Router::new()
238 .route("/nav/home", get(get_home_link))
239 .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
240 .with_state(app_state.clone());
241
242 let commodity_router = Router::new()
243 .route("/commodity/list", get(commodity::list::commodity_table))
244 .route(
245 "/commodity/search",
246 post(commodity::search::search_commodities),
247 )
248 .route(
249 "/commodity/create/submit",
250 post(commodity::create::submit::commodity_submit),
251 )
252 .route(
253 "/commodity/create/validate/symbol",
254 post(commodity::create::validate::validate_symbol),
255 )
256 .route(
257 "/commodity/create/validate/name",
258 post(commodity::create::validate::validate_name),
259 )
260 .route(
261 "/commodity/convert/submit",
262 post(commodity::convert::submit::commodity_convert_submit),
263 )
264 .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
265 .with_state(app_state.clone());
266
267 let transaction_router = Router::new()
268 .route(
269 "/transaction/list",
270 get(crate::pages::transaction::list::transaction_table),
271 )
272 .route(
273 "/transaction/split/create",
274 get(crate::pages::transaction::create::split::split_create_handler),
275 )
276 .route(
277 "/transaction/create/submit",
278 post(crate::pages::transaction::create::submit::transaction_submit),
279 )
280 .route(
281 "/transaction/create/validate/amount",
282 post(crate::pages::transaction::validate::validate_amount_html),
283 )
284 .route(
285 "/transaction/create/validate/from_account",
286 post(crate::pages::transaction::validate::validate_from_account_html),
287 )
288 .route(
289 "/transaction/create/validate/to_account",
290 post(crate::pages::transaction::validate::validate_to_account_html),
291 )
292 .route(
293 "/transaction/create/validate/note",
294 post(crate::pages::transaction::validate::validate_note_html),
295 )
296 .route(
297 "/transaction/edit/submit",
298 post(crate::pages::transaction::edit::submit::transaction_edit_submit),
299 )
300 .route(
301 "/transaction/edit/validate/amount",
302 post(crate::pages::transaction::validate::validate_amount_json),
303 )
304 .route(
305 "/transaction/edit/validate/from_account",
306 post(crate::pages::transaction::validate::validate_from_account_json),
307 )
308 .route(
309 "/transaction/edit/validate/to_account",
310 post(crate::pages::transaction::validate::validate_to_account_json),
311 )
312 .route(
313 "/transaction/edit/validate/note",
314 post(crate::pages::transaction::validate::validate_note_json),
315 )
316 .route(
317 "/transaction/delete/{id}",
318 post(crate::pages::transaction::delete::submit::transaction_delete_submit),
319 )
320 .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
321 .with_state(app_state.clone());
322
323 let tag_router = Router::new()
324 .route("/tag/list", get(tag::list::tag_table))
325 .route("/tag/create/submit", post(tag::create::create_tag))
326 .route(
327 "/transaction/{id}/tag/create/submit",
328 post(tag::create::create_transaction_tag),
329 )
330 .route(
331 "/split/{id}/tag/create/submit",
332 post(tag::create::create_split_tag),
333 )
334 .route(
335 "/account/{id}/tag/create/submit",
336 post(tag::create::create_account_tag),
337 )
338 .route("/tag/edit/submit", post(tag::edit::edit_tag))
339 .route("/tag/delete/{id}", post(tag::edit::delete_tag))
340 .route(
341 "/tags/transaction/names",
342 get(tag::autocomplete::transaction_tag_names),
343 )
344 .route(
345 "/tags/transaction/values",
346 get(tag::autocomplete::transaction_tag_values),
347 )
348 .route("/tags/split/names", get(tag::autocomplete::split_tag_names))
349 .route(
350 "/tags/split/values",
351 get(tag::autocomplete::split_tag_values),
352 )
353 .route(
354 "/tags/account/names",
355 get(tag::autocomplete::account_tag_names),
356 )
357 .route(
358 "/tags/account/values",
359 get(tag::autocomplete::account_tag_values),
360 )
361 .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
362 .with_state(app_state.clone());
363
364 let script_router = Router::new()
365 .route("/script/list", get(script::list::script_table))
366 .route("/script/create/submit", post(script::edit::create_script))
367 .route("/script/edit/submit", post(script::edit::edit_script))
368 .route(
369 "/script/bytecode/{id}",
370 post(script::edit::upload_script_bytecode),
371 )
372 .route("/script/delete/{id}", post(script::edit::delete_script))
373 .route(
374 "/account/{account_id}/script/run/{script_id}",
375 post(account::edit::run_account_script),
376 )
377 .route("/template/list", get(template::list::template_table))
378 .route(
379 "/template/create/submit",
380 post(template::edit::create_template),
381 )
382 .route("/template/test", post(template::edit::test_template))
383 .route("/template/edit/submit", post(template::edit::edit_template))
384 .route(
385 "/template/delete/{id}",
386 post(template::edit::delete_template),
387 )
388 .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
389 .with_state(app_state.clone());
390
391 let report_router = Router::new()
392 .route(
393 "/report/balance",
394 get(report::balance::balance_report_table),
395 )
396 .route(
397 "/report/balance/chart.svg",
398 get(report::balance::balance_report_chart_svg),
399 )
400 .route(
401 "/report/balance/chart.json",
402 get(report::balance::balance_report_chart_json),
403 )
404 .route(
405 "/report/activity",
406 get(report::activity::activity_report_table),
407 )
408 .route(
409 "/report/activity/chart.svg",
410 get(report::activity::activity_report_chart_svg),
411 )
412 .route(
413 "/report/activity/chart.json",
414 get(report::activity::activity_report_chart_json),
415 )
416 .route(
417 "/report/category-breakdown",
418 get(report::category_breakdown::category_breakdown_report_table),
419 )
420 .route(
421 "/report/category-breakdown/chart.svg",
422 get(report::category_breakdown::category_breakdown_report_chart_svg),
423 )
424 .route(
425 "/report/category-breakdown/chart.json",
426 get(report::category_breakdown::category_breakdown_report_chart_json),
427 )
428 .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
429 .with_state(app_state.clone());
430
431 let admin_router = Router::new()
432 .route("/auth/register", post(register_user_handler))
433 .route_layer(middleware::from_fn_with_state(app_state.clone(), admin))
434 .route_layer(middleware::from_fn_with_state(app_state, auth));
435
436 let router = Router::new()
437 .merge(public_router)
438 .merge(password_router)
439 .merge(nav_router)
440 .merge(auth_router)
441 .merge(account_router)
442 .merge(commodity_router)
443 .merge(transaction_router)
444 .merge(tag_router)
445 .merge(report_router)
446 .merge(admin_router);
447
448 router.merge(script_router)
449}