1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
/*
This file is part of jellything (https://codeberg.org/metamuffin/jellything)
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
use std::str::FromStr;
use crate::{
auth::hash_password, request_info::RequestInfo, responders::UiPage, routes::error::MyResult,
};
use anyhow::anyhow;
use base64::{Engine, prelude::BASE64_URL_SAFE};
use jellycommon::{
jellyobject::{ObjectBufferBuilder, Path},
routes::u_admin_users,
*,
};
use jellydb::{Filter, Query};
use jellyui::{
components::admin::{AdminUser, AdminUserList},
tr,
};
use rand::random;
use rocket::{
FromForm,
form::Form,
get, post,
response::{Flash, Redirect},
};
#[get("/admin/users")]
pub fn r_admin_users(ri: RequestInfo) -> MyResult<UiPage> {
ri.require_admin()?;
let mut users = Vec::new();
ri.state.database.transaction(&mut |txn| {
users.clear();
let rows = txn
.query(Query::from_str("FILTER Ulgn")?)?
.collect::<Vec<_>>();
for row in rows {
let (row, _) = row?;
users.push(txn.get(row)?.unwrap());
}
Ok(())
})?;
Ok(ri.respond_ui(&AdminUserList {
ri: &ri.render_info(),
users: &users.iter().map(|u| &**u).collect::<Vec<_>>(),
}))
}
#[derive(FromForm)]
pub struct NewUser {
login: String,
}
#[post("/admin/new_user", data = "<form>")]
pub fn r_admin_new_user(ri: RequestInfo, form: Form<NewUser>) -> MyResult<Flash<Redirect>> {
ri.require_admin()?;
let password = BASE64_URL_SAFE.encode([(); 12].map(|()| random()));
let password_hashed = hash_password(&form.login, &password);
ri.state.database.transaction(&mut |txn| {
let mut user = ObjectBufferBuilder::default();
user.push(USER_LOGIN, &form.login);
user.push(USER_PASSWORD, &password_hashed);
user.push(USER_PASSWORD_REQUIRE_CHANGE, ());
txn.insert(user.finish())?;
Ok(())
})?;
Ok(Flash::success(
Redirect::to(u_admin_users()),
format!("User created; password: {password}"),
))
}
#[get("/admin/user/<name>")]
pub fn r_admin_user(ri: RequestInfo<'_>, name: &str) -> MyResult<UiPage> {
ri.require_admin()?;
let mut user = None;
ri.state.database.transaction(&mut |txn| {
if let Some(row) = txn.query_single(Query {
filter: Filter::Match(Path(vec![USER_LOGIN.0]), name.into()),
..Default::default()
})? {
user = Some(txn.get(row)?.unwrap());
}
Ok(())
})?;
let Some(user) = user else {
Err(anyhow!("no such user"))?
};
Ok(ri.respond_ui(&AdminUser {
ri: &ri.render_info(),
user: &user,
}))
}
#[post("/admin/user/<name>/remove")]
pub fn r_admin_user_remove(ri: RequestInfo<'_>, name: &str) -> MyResult<Flash<Redirect>> {
ri.require_admin()?;
ri.state.database.transaction(&mut |txn| {
if let Some(row) = txn.query_single(Query {
filter: Filter::Match(Path(vec![USER_LOGIN.0]), name.into()),
..Default::default()
})? {
txn.remove(row)?;
}
Ok(())
})?;
Ok(Flash::success(
Redirect::to(u_admin_users()),
tr(ri.lang, "admin.users.remove_success"),
))
}
|