aboutsummaryrefslogtreecommitdiff
path: root/server/src/ui/admin/users.rs
blob: 654a6b96c3499bc23cbead398729d6ad4e388eb4 (plain)
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
/*
    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 base64::{Engine, prelude::BASE64_URL_SAFE};
use jellycommon::{
    jellyobject::{OBB, ObjectBufferBuilder, Path},
    routes::u_admin_users,
    *,
};
use jellydb::{Filter, Query, Sort};
use jellyui::tr;
use rand::random;
use rocket::{
    FromForm,
    form::Form,
    get, post,
    response::{Flash, Redirect},
};

use crate::{
    auth::hash_password, request_info::RequestInfo, ui::error::MyResult, ui_responder::UiResponse,
};

#[get("/admin/users")]
pub fn r_admin_users(ri: RequestInfo) -> MyResult<UiResponse> {
    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(())
    })?;

    let mut list = ObjectBufferBuilder::default();
    for u in users {
        list.push(ADMIN_USER_LIST_ITEM, u.as_object());
    }

    let mut page = ObjectBufferBuilder::default();
    page.push(VIEW_TITLE, &*tr(ri.lang, "admin.users"));
    page.push(VIEW_ADMIN_USER_LIST, list.finish().as_object());
    Ok(ri.respond_ui(page))
}

#[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::new(
        Redirect::to(u_admin_users()),
        "success",
        format!("User created; password: {password}"),
    ))
}

#[get("/admin/user/<name>")]
pub fn r_admin_user(ri: RequestInfo<'_>, name: &str) -> MyResult<UiResponse> {
    ri.require_admin()?;
    let mut page = OBB::new();
    ri.state.database.transaction(&mut |txn| {
        if let Some(row) = txn.query_single(Query {
            sort: Sort::None,
            filter: Filter::Match(Path(vec![USER_LOGIN.0]), name.into()),
        })? {
            let user = txn.get(row)?.unwrap();
            page = OBB::new();
            page.push(VIEW_ADMIN_USER, user.as_object());
        }
        Ok(())
    })?;

    Ok(ri.respond_ui(page))
}

#[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 {
            sort: Sort::None,
            filter: Filter::Match(Path(vec![USER_LOGIN.0]), name.into()),
        })? {
            txn.remove(row)?;
        }
        Ok(())
    })?;
    Ok(Flash::success(
        Redirect::to(u_admin_users()),
        tr(ri.lang, "admin.users.remove_success"),
    ))
}