1
use std::sync::Arc;
2

            
3
use axum::{
4
    Router, middleware,
5
    routing::{get, post},
6
};
7

            
8
use crate::pages::script;
9
use crate::pages::template;
10
use crate::{
11
    AppState,
12
    files::download_file,
13
    handler::{
14
        get_home_link, get_logout_link, get_me_handler, get_version, login_form_handler,
15
        login_user_handler, logout_handler, refresh_access_token_handler, register_user_handler,
16
    },
17
    jwt_auth::{admin, auth},
18
    pages::{account, commodity, file_table, report, tag},
19
    redirect_middleware::redirect_on_auth_error,
20
};
21

            
22
/// Pages only an admin may see.
23
///
24
/// Registration is invite-only: `POST /api/auth/register` has always been
25
/// admin-gated, but the page carrying its form was public, so an anonymous
26
/// visitor was handed a signup form whose every submission is refused. The
27
/// page now sits behind the same gate as the endpoint it posts to.
28
///
29
/// Layer order matters: `auth` must run before `admin`, which reads the
30
/// extension `auth` inserts, and `redirect_on_auth_error` wraps both so an
31
/// HTML visitor is redirected rather than shown a JSON error.
32
2
pub fn create_admin_pages_router(app_state: Arc<AppState>) -> Router<Arc<AppState>> {
33
2
    Router::new()
34
2
        .route("/register", get(crate::pages::register))
35
2
        .route_layer(middleware::from_fn_with_state(app_state.clone(), admin))
36
2
        .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
37
2
        .route_layer(middleware::from_fn(redirect_on_auth_error))
38
2
        .with_state(app_state.clone())
39
2
}
40

            
41
1
pub fn create_pages_router(app_state: Arc<AppState>) -> Router<Arc<AppState>> {
42
1
    Router::new()
43
1
        .route(
44
1
            "/commodity/create",
45
1
            get(crate::pages::commodity::create::submit::commodity_create_page),
46
        )
47
1
        .route(
48
1
            "/commodity/convert",
49
1
            get(crate::pages::commodity::convert::submit::commodity_convert_page),
50
        )
51
1
        .route(
52
1
            "/commodity/list",
53
1
            get(crate::pages::commodity::list::commodity_list_page),
54
        )
55
1
        .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
56
1
        .route_layer(middleware::from_fn(redirect_on_auth_error))
57
1
        .with_state(app_state.clone())
58
1
}
59

            
60
1
pub fn create_accounts_router(app_state: Arc<AppState>) -> Router<Arc<AppState>> {
61
1
    Router::new()
62
1
        .route(
63
1
            "/account/create",
64
1
            get(crate::pages::account::create::submit::account_create_page),
65
        )
66
1
        .route(
67
1
            "/account/list",
68
1
            get(crate::pages::account::list::account_list_page),
69
        )
70
1
        .route(
71
1
            "/account/manage",
72
1
            get(crate::pages::account::manage::account_manage_page),
73
        )
74
1
        .route(
75
1
            "/account/ssh-key",
76
1
            get(crate::pages::account::ssh_key::ssh_key_page),
77
        )
78
1
        .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
79
1
        .route_layer(middleware::from_fn(redirect_on_auth_error))
80
1
        .with_state(app_state.clone())
81
1
}
82

            
83
1
pub fn create_transactions_router(app_state: Arc<AppState>) -> Router<Arc<AppState>> {
84
1
    Router::new()
85
1
        .route(
86
1
            "/transaction/create",
87
1
            get(crate::pages::transaction::create::submit::transaction_create_page),
88
        )
89
1
        .route(
90
1
            "/transaction/list",
91
1
            get(crate::pages::transaction::list::transaction_list_page),
92
        )
93
1
        .route(
94
1
            "/transaction/edit/{id}",
95
1
            get(crate::pages::transaction::edit::submit::transaction_edit_page),
96
        )
97
1
        .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
98
1
        .route_layer(middleware::from_fn(redirect_on_auth_error))
99
1
        .with_state(app_state.clone())
100
1
}
101

            
102
pub fn create_tags_router(app_state: Arc<AppState>) -> Router<Arc<AppState>> {
103
    Router::new()
104
        .route(
105
            "/tag/create",
106
            get(crate::pages::tag::create::tag_create_page),
107
        )
108
        .route("/tag/list", get(crate::pages::tag::list::tag_list_page))
109
        .route(
110
            "/tag/edit/{id}",
111
            get(crate::pages::tag::edit::tag_edit_page),
112
        )
113
        .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
114
        .route_layer(middleware::from_fn(redirect_on_auth_error))
115
        .with_state(app_state.clone())
116
}
117

            
118
pub fn create_scripts_router(app_state: Arc<AppState>) -> Router<Arc<AppState>> {
119
    Router::new()
120
        .route("/script/create", get(script::edit::script_create_page))
121
        .route("/script/list", get(script::list::script_list_page))
122
        .route("/script/edit/{id}", get(script::edit::script_edit_page))
123
        .route(
124
            "/template/create",
125
            get(template::edit::template_create_page),
126
        )
127
        .route("/template/list", get(template::list::template_list_page))
128
        .route(
129
            "/template/edit/{id}",
130
            get(template::edit::template_edit_page),
131
        )
132
        .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
133
        .route_layer(middleware::from_fn(redirect_on_auth_error))
134
        .with_state(app_state.clone())
135
}
136

            
137
pub fn create_reports_router(app_state: Arc<AppState>) -> Router<Arc<AppState>> {
138
    Router::new()
139
        .route("/report/balance", get(report::balance::balance_report_page))
140
        .route(
141
            "/report/activity",
142
            get(report::activity::activity_report_page),
143
        )
144
        .route(
145
            "/report/category-breakdown",
146
            get(report::category_breakdown::category_breakdown_report_page),
147
        )
148
        .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
149
        .route_layer(middleware::from_fn(redirect_on_auth_error))
150
        .with_state(app_state.clone())
151
}
152

            
153
3
pub fn create_api_router(app_state: Arc<AppState>) -> Router<Arc<AppState>> {
154
3
    let public_router = Router::new()
155
3
        .route("/auth/login", post(login_form_handler))
156
3
        .route("/auth/login/json", post(login_user_handler))
157
3
        .route("/version", get(get_version));
158

            
159
3
    let auth_router = Router::new()
160
3
        .route("/auth/refresh", get(refresh_access_token_handler))
161
3
        .route("/files/list", get(file_table))
162
3
        .route("/files/download/{file_name}", get(download_file))
163
3
        .route("/auth/logout", get(logout_handler))
164
3
        .route("/users/me", get(get_me_handler))
165
3
        .route("/htmx/logoutlink", get(get_logout_link))
166
3
        .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
167
3
        .with_state(app_state.clone());
168

            
169
3
    let account_router = Router::new()
170
3
        .route("/account/list", get(account::list::account_table))
171
3
        .route("/account/info", get(account::list::account_info))
172
3
        .route("/account/tree", get(account::manage::account_tree))
173
3
        .route(
174
3
            "/account/manage/view",
175
3
            get(account::manage::account_manage_view),
176
        )
177
3
        .route(
178
3
            "/account/manage/details",
179
3
            get(account::manage::account_manage_details),
180
        )
181
3
        .route(
182
3
            "/account/manage/tag",
183
3
            post(account::manage::account_manage_set_tag),
184
        )
185
3
        .route(
186
3
            "/account/manage/tag/submit",
187
3
            post(account::manage::account_manage_set_tag_submit),
188
        )
189
3
        .route(
190
3
            "/account/manage/tag/delete/{tag_id}",
191
3
            post(account::manage::account_manage_delete_tag_submit),
192
        )
193
3
        .route("/account/search", post(account::search::search_accounts))
194
3
        .route(
195
3
            "/account/create/submit",
196
3
            post(account::create::submit::create_account),
197
        )
198
3
        .route(
199
3
            "/account/create/validate/name",
200
3
            post(account::create::validate::validate_name),
201
        )
202
3
        .route("/account/rename", post(account::edit::rename_account))
203
3
        .route(
204
3
            "/account/{id}/tags/submit",
205
3
            post(account::edit::account_tags_submit),
206
        )
207
3
        .route("/account/ssh-key/add", post(account::ssh_key::ssh_key_add))
208
3
        .route(
209
3
            "/account/ssh-key/remove",
210
3
            post(account::ssh_key::ssh_key_remove),
211
        )
212
3
        .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
213
3
        .with_state(app_state.clone());
214

            
215
3
    let nav_router = Router::new()
216
3
        .route("/nav/home", get(get_home_link))
217
3
        .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
218
3
        .with_state(app_state.clone());
219

            
220
3
    let commodity_router = Router::new()
221
3
        .route("/commodity/list", get(commodity::list::commodity_table))
222
3
        .route(
223
3
            "/commodity/search",
224
3
            post(commodity::search::search_commodities),
225
        )
226
3
        .route(
227
3
            "/commodity/create/submit",
228
3
            post(commodity::create::submit::commodity_submit),
229
        )
230
3
        .route(
231
3
            "/commodity/create/validate/symbol",
232
3
            post(commodity::create::validate::validate_symbol),
233
        )
234
3
        .route(
235
3
            "/commodity/create/validate/name",
236
3
            post(commodity::create::validate::validate_name),
237
        )
238
3
        .route(
239
3
            "/commodity/convert/submit",
240
3
            post(commodity::convert::submit::commodity_convert_submit),
241
        )
242
3
        .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
243
3
        .with_state(app_state.clone());
244

            
245
3
    let transaction_router = Router::new()
246
3
        .route(
247
3
            "/transaction/list",
248
3
            get(crate::pages::transaction::list::transaction_table),
249
        )
250
3
        .route(
251
3
            "/transaction/split/create",
252
3
            get(crate::pages::transaction::create::split::split_create_handler),
253
        )
254
3
        .route(
255
3
            "/transaction/create/submit",
256
3
            post(crate::pages::transaction::create::submit::transaction_submit),
257
        )
258
3
        .route(
259
3
            "/transaction/create/validate/amount",
260
3
            post(crate::pages::transaction::validate::validate_amount_html),
261
        )
262
3
        .route(
263
3
            "/transaction/create/validate/from_account",
264
3
            post(crate::pages::transaction::validate::validate_from_account_html),
265
        )
266
3
        .route(
267
3
            "/transaction/create/validate/to_account",
268
3
            post(crate::pages::transaction::validate::validate_to_account_html),
269
        )
270
3
        .route(
271
3
            "/transaction/create/validate/note",
272
3
            post(crate::pages::transaction::validate::validate_note_html),
273
        )
274
3
        .route(
275
3
            "/transaction/edit/submit",
276
3
            post(crate::pages::transaction::edit::submit::transaction_edit_submit),
277
        )
278
3
        .route(
279
3
            "/transaction/edit/validate/amount",
280
3
            post(crate::pages::transaction::validate::validate_amount_json),
281
        )
282
3
        .route(
283
3
            "/transaction/edit/validate/from_account",
284
3
            post(crate::pages::transaction::validate::validate_from_account_json),
285
        )
286
3
        .route(
287
3
            "/transaction/edit/validate/to_account",
288
3
            post(crate::pages::transaction::validate::validate_to_account_json),
289
        )
290
3
        .route(
291
3
            "/transaction/edit/validate/note",
292
3
            post(crate::pages::transaction::validate::validate_note_json),
293
        )
294
3
        .route(
295
3
            "/transaction/delete/{id}",
296
3
            post(crate::pages::transaction::delete::submit::transaction_delete_submit),
297
        )
298
3
        .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
299
3
        .with_state(app_state.clone());
300

            
301
3
    let tag_router = Router::new()
302
3
        .route("/tag/list", get(tag::list::tag_table))
303
3
        .route("/tag/create/submit", post(tag::create::create_tag))
304
3
        .route(
305
3
            "/transaction/{id}/tag/create/submit",
306
3
            post(tag::create::create_transaction_tag),
307
        )
308
3
        .route(
309
3
            "/split/{id}/tag/create/submit",
310
3
            post(tag::create::create_split_tag),
311
        )
312
3
        .route(
313
3
            "/account/{id}/tag/create/submit",
314
3
            post(tag::create::create_account_tag),
315
        )
316
3
        .route("/tag/edit/submit", post(tag::edit::edit_tag))
317
3
        .route("/tag/delete/{id}", post(tag::edit::delete_tag))
318
3
        .route(
319
3
            "/tags/transaction/names",
320
3
            get(tag::autocomplete::transaction_tag_names),
321
        )
322
3
        .route(
323
3
            "/tags/transaction/values",
324
3
            get(tag::autocomplete::transaction_tag_values),
325
        )
326
3
        .route("/tags/split/names", get(tag::autocomplete::split_tag_names))
327
3
        .route(
328
3
            "/tags/split/values",
329
3
            get(tag::autocomplete::split_tag_values),
330
        )
331
3
        .route(
332
3
            "/tags/account/names",
333
3
            get(tag::autocomplete::account_tag_names),
334
        )
335
3
        .route(
336
3
            "/tags/account/values",
337
3
            get(tag::autocomplete::account_tag_values),
338
        )
339
3
        .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
340
3
        .with_state(app_state.clone());
341

            
342
3
    let script_router = Router::new()
343
3
        .route("/script/list", get(script::list::script_table))
344
3
        .route("/script/create/submit", post(script::edit::create_script))
345
3
        .route("/script/edit/submit", post(script::edit::edit_script))
346
3
        .route(
347
3
            "/script/bytecode/{id}",
348
3
            post(script::edit::upload_script_bytecode),
349
        )
350
3
        .route("/script/delete/{id}", post(script::edit::delete_script))
351
3
        .route(
352
3
            "/account/{account_id}/script/run/{script_id}",
353
3
            post(account::edit::run_account_script),
354
        )
355
3
        .route("/template/list", get(template::list::template_table))
356
3
        .route(
357
3
            "/template/create/submit",
358
3
            post(template::edit::create_template),
359
        )
360
3
        .route("/template/test", post(template::edit::test_template))
361
3
        .route("/template/edit/submit", post(template::edit::edit_template))
362
3
        .route(
363
3
            "/template/delete/{id}",
364
3
            post(template::edit::delete_template),
365
        )
366
3
        .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
367
3
        .with_state(app_state.clone());
368

            
369
3
    let report_router = Router::new()
370
3
        .route(
371
3
            "/report/balance",
372
3
            get(report::balance::balance_report_table),
373
        )
374
3
        .route(
375
3
            "/report/balance/chart.svg",
376
3
            get(report::balance::balance_report_chart_svg),
377
        )
378
3
        .route(
379
3
            "/report/balance/chart.json",
380
3
            get(report::balance::balance_report_chart_json),
381
        )
382
3
        .route(
383
3
            "/report/activity",
384
3
            get(report::activity::activity_report_table),
385
        )
386
3
        .route(
387
3
            "/report/activity/chart.svg",
388
3
            get(report::activity::activity_report_chart_svg),
389
        )
390
3
        .route(
391
3
            "/report/activity/chart.json",
392
3
            get(report::activity::activity_report_chart_json),
393
        )
394
3
        .route(
395
3
            "/report/category-breakdown",
396
3
            get(report::category_breakdown::category_breakdown_report_table),
397
        )
398
3
        .route(
399
3
            "/report/category-breakdown/chart.svg",
400
3
            get(report::category_breakdown::category_breakdown_report_chart_svg),
401
        )
402
3
        .route(
403
3
            "/report/category-breakdown/chart.json",
404
3
            get(report::category_breakdown::category_breakdown_report_chart_json),
405
        )
406
3
        .route_layer(middleware::from_fn_with_state(app_state.clone(), auth))
407
3
        .with_state(app_state.clone());
408

            
409
3
    let admin_router = Router::new()
410
3
        .route("/auth/register", post(register_user_handler))
411
3
        .route_layer(middleware::from_fn_with_state(app_state.clone(), admin))
412
3
        .route_layer(middleware::from_fn_with_state(app_state, auth));
413

            
414
3
    let router = Router::new()
415
3
        .merge(public_router)
416
3
        .merge(nav_router)
417
3
        .merge(auth_router)
418
3
        .merge(account_router)
419
3
        .merge(commodity_router)
420
3
        .merge(transaction_router)
421
3
        .merge(tag_router)
422
3
        .merge(report_router)
423
3
        .merge(admin_router);
424

            
425
3
    router.merge(script_router)
426
3
}