Skip to main content

kellnr_web_ui/
group.rs

1use axum::Json;
2use axum::extract::{Path, State};
3use axum::http::StatusCode;
4use kellnr_appstate::DbState;
5use kellnr_db::{self, Group};
6use serde::{Deserialize, Serialize};
7use utoipa::ToSchema;
8
9use crate::error::RouteError;
10use crate::session::AdminUser;
11
12#[derive(Serialize, ToSchema)]
13pub struct NewTokenResponse {
14    name: String,
15    token: String,
16}
17
18/// List all groups (admin only)
19#[utoipa::path(
20    get,
21    path = "/",
22    tag = "groups",
23    responses(
24        (status = 200, description = "List of all groups", body = Vec<Group>),
25        (status = 403, description = "Admin access required")
26    ),
27    security(("session_cookie" = []))
28)]
29pub async fn list_groups(
30    _user: AdminUser,
31    State(db): DbState,
32) -> Result<Json<Vec<Group>>, RouteError> {
33    Ok(Json(db.get_groups().await?))
34}
35
36/// Delete a group (admin only)
37#[utoipa::path(
38    delete,
39    path = "/{name}",
40    tag = "groups",
41    params(
42        ("name" = String, Path, description = "Group name to delete")
43    ),
44    responses(
45        (status = 200, description = "Group deleted successfully"),
46        (status = 403, description = "Admin access required")
47    ),
48    security(("session_cookie" = []))
49)]
50pub async fn delete(
51    _user: AdminUser,
52    Path(name): Path<String>,
53    State(db): DbState,
54) -> Result<(), RouteError> {
55    Ok(db.delete_group(&name).await?)
56}
57
58#[derive(Deserialize, ToSchema)]
59pub struct NewGroup {
60    pub name: String,
61}
62
63impl NewGroup {
64    pub fn validate(&self) -> Result<(), RouteError> {
65        if self.name.is_empty() {
66            return Err(RouteError::Status(StatusCode::BAD_REQUEST));
67        }
68        Ok(())
69    }
70}
71
72/// Create a new group (admin only)
73#[utoipa::path(
74    post,
75    path = "/",
76    tag = "groups",
77    request_body = NewGroup,
78    responses(
79        (status = 200, description = "Group created successfully"),
80        (status = 400, description = "Validation failed"),
81        (status = 403, description = "Admin access required")
82    ),
83    security(("session_cookie" = []))
84)]
85pub async fn add(
86    _user: AdminUser,
87    State(db): DbState,
88    Json(new_group): Json<NewGroup>,
89) -> Result<(), RouteError> {
90    new_group.validate()?;
91
92    Ok(db.add_group(&new_group.name).await?)
93}
94
95#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
96pub struct GroupUser {
97    pub id: i32,
98    pub name: String,
99}
100
101#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
102pub struct GroupUserList {
103    pub users: Vec<GroupUser>,
104}
105
106impl From<Vec<GroupUser>> for GroupUserList {
107    fn from(users: Vec<GroupUser>) -> Self {
108        Self { users }
109    }
110}
111
112/// List members of a group (admin only)
113#[utoipa::path(
114    get,
115    path = "/{group_name}/members",
116    tag = "groups",
117    params(
118        ("group_name" = String, Path, description = "Group name")
119    ),
120    responses(
121        (status = 200, description = "List of group members", body = GroupUserList),
122        (status = 403, description = "Admin access required")
123    ),
124    security(("session_cookie" = []))
125)]
126pub async fn list_users(
127    _user: AdminUser,
128    Path(group_name): Path<String>,
129    State(db): DbState,
130) -> Result<Json<GroupUserList>, RouteError> {
131    let users: Vec<GroupUser> = db
132        .get_group_users(&group_name)
133        .await?
134        .iter()
135        .map(|u| GroupUser {
136            id: u.id,
137            name: u.name.clone(),
138        })
139        .collect();
140
141    Ok(Json(GroupUserList::from(users)))
142}
143
144/// Add a user to a group (admin only)
145#[utoipa::path(
146    put,
147    path = "/{group_name}/members/{name}",
148    tag = "groups",
149    params(
150        ("group_name" = String, Path, description = "Group name"),
151        ("name" = String, Path, description = "Username to add")
152    ),
153    responses(
154        (status = 200, description = "User added to group successfully"),
155        (status = 403, description = "Admin access required")
156    ),
157    security(("session_cookie" = []))
158)]
159pub async fn add_user(
160    _user: AdminUser,
161    Path((group_name, name)): Path<(String, String)>,
162    State(db): DbState,
163) -> Result<(), RouteError> {
164    if !db.is_group_user(&group_name, &name).await? {
165        db.add_group_user(&group_name, &name).await?;
166    }
167
168    Ok(())
169}
170
171/// Remove a user from a group (admin only)
172#[utoipa::path(
173    delete,
174    path = "/{group_name}/members/{name}",
175    tag = "groups",
176    params(
177        ("group_name" = String, Path, description = "Group name"),
178        ("name" = String, Path, description = "Username to remove")
179    ),
180    responses(
181        (status = 200, description = "User removed from group successfully"),
182        (status = 403, description = "Admin access required")
183    ),
184    security(("session_cookie" = []))
185)]
186pub async fn delete_user(
187    _user: AdminUser,
188    Path((group_name, name)): Path<(String, String)>,
189    State(db): DbState,
190) -> Result<(), RouteError> {
191    Ok(db.delete_group_user(&group_name, &name).await?)
192}