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#[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#[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#[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#[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#[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#[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}