1use axum::Json;
2use axum::extract::{Path, State};
3use kellnr_appstate::DbState;
4use kellnr_common::original_name::OriginalName;
5use kellnr_registry::crate_group::{CrateGroup, CrateGroupList};
6use kellnr_registry::crate_user::{CrateUser, CrateUserList};
7use serde::{Deserialize, Serialize};
8use utoipa::ToSchema;
9
10use crate::error::RouteError;
11use crate::session::AdminUser;
12
13#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
14pub struct AccessData {
15 pub download_restricted: bool,
16}
17
18#[utoipa::path(
20 get,
21 path = "/{crate_name}/users",
22 tag = "acl",
23 params(
24 ("crate_name" = OriginalName, Path, description = "Crate name")
25 ),
26 responses(
27 (status = 200, description = "List of users with access", body = CrateUserList),
28 (status = 403, description = "Admin access required")
29 ),
30 security(("session_cookie" = []))
31)]
32pub async fn list_users(
33 _user: AdminUser,
34 Path(crate_name): Path<OriginalName>,
35 State(db): DbState,
36) -> Result<Json<CrateUserList>, RouteError> {
37 let crate_name = crate_name.to_normalized();
38 let users: Vec<CrateUser> = db
39 .get_crate_users(&crate_name)
40 .await?
41 .iter()
42 .map(|u| CrateUser {
43 id: u.id,
44 login: u.name.clone(),
45 name: None,
46 })
47 .collect();
48
49 Ok(Json(CrateUserList::from(users)))
50}
51
52#[utoipa::path(
54 put,
55 path = "/{crate_name}/users/{name}",
56 tag = "acl",
57 params(
58 ("crate_name" = OriginalName, Path, description = "Crate name"),
59 ("name" = String, Path, description = "Username to add")
60 ),
61 responses(
62 (status = 200, description = "User added successfully"),
63 (status = 404, description = "User not found"),
64 (status = 403, description = "Admin access required")
65 ),
66 security(("session_cookie" = []))
67)]
68pub async fn add_user(
69 _user: AdminUser,
70 Path((crate_name, name)): Path<(OriginalName, String)>,
71 State(db): DbState,
72) -> Result<(), RouteError> {
73 let crate_name = crate_name.to_normalized();
74
75 if db.get_user(&name).await.is_err() {
76 return Err(RouteError::UserNotFound(name));
77 }
78
79 if !db.is_crate_user(&crate_name, &name).await? {
80 db.add_crate_user(&crate_name, &name).await?;
81 }
82
83 Ok(())
84}
85
86#[utoipa::path(
88 delete,
89 path = "/{crate_name}/users/{name}",
90 tag = "acl",
91 params(
92 ("crate_name" = OriginalName, Path, description = "Crate name"),
93 ("name" = String, Path, description = "Username to remove")
94 ),
95 responses(
96 (status = 200, description = "User removed successfully"),
97 (status = 403, description = "Admin access required")
98 ),
99 security(("session_cookie" = []))
100)]
101pub async fn delete_user(
102 _user: AdminUser,
103 Path((crate_name, name)): Path<(OriginalName, String)>,
104 State(db): DbState,
105) -> Result<(), RouteError> {
106 let crate_name = crate_name.to_normalized();
107 Ok(db.delete_crate_user(&crate_name, &name).await?)
108}
109
110#[utoipa::path(
112 get,
113 path = "/{crate_name}/groups",
114 tag = "acl",
115 params(
116 ("crate_name" = OriginalName, Path, description = "Crate name")
117 ),
118 responses(
119 (status = 200, description = "List of groups with access", body = CrateGroupList),
120 (status = 403, description = "Admin access required")
121 ),
122 security(("session_cookie" = []))
123)]
124pub async fn list_groups(
125 _user: AdminUser,
126 Path(crate_name): Path<OriginalName>,
127 State(db): DbState,
128) -> Result<Json<CrateGroupList>, RouteError> {
129 let crate_name = crate_name.to_normalized();
130 let groups: Vec<CrateGroup> = db
131 .get_crate_groups(&crate_name)
132 .await?
133 .iter()
134 .map(|u| CrateGroup {
135 id: u.id,
136 name: u.name.clone(),
137 })
138 .collect();
139
140 Ok(Json(CrateGroupList::from(groups)))
141}
142
143#[utoipa::path(
145 put,
146 path = "/{crate_name}/groups/{name}",
147 tag = "acl",
148 params(
149 ("crate_name" = OriginalName, Path, description = "Crate name"),
150 ("name" = String, Path, description = "Group name to add")
151 ),
152 responses(
153 (status = 200, description = "Group added successfully"),
154 (status = 403, description = "Admin access required")
155 ),
156 security(("session_cookie" = []))
157)]
158pub async fn add_group(
159 _user: AdminUser,
160 Path((crate_name, name)): Path<(OriginalName, String)>,
161 State(db): DbState,
162) -> Result<(), RouteError> {
163 let crate_name = crate_name.to_normalized();
164 if !db.is_crate_group(&crate_name, &name).await? {
165 db.add_crate_group(&crate_name, &name).await?;
166 }
167
168 Ok(())
169}
170
171#[utoipa::path(
173 delete,
174 path = "/{crate_name}/groups/{name}",
175 tag = "acl",
176 params(
177 ("crate_name" = OriginalName, Path, description = "Crate name"),
178 ("name" = String, Path, description = "Group name to remove")
179 ),
180 responses(
181 (status = 200, description = "Group removed successfully"),
182 (status = 403, description = "Admin access required")
183 ),
184 security(("session_cookie" = []))
185)]
186pub async fn delete_group(
187 _user: AdminUser,
188 Path((crate_name, name)): Path<(OriginalName, String)>,
189 State(db): DbState,
190) -> Result<(), RouteError> {
191 let crate_name = crate_name.to_normalized();
192 Ok(db.delete_crate_group(&crate_name, &name).await?)
193}
194
195#[utoipa::path(
197 get,
198 path = "/{crate_name}",
199 tag = "acl",
200 params(
201 ("crate_name" = OriginalName, Path, description = "Crate name")
202 ),
203 responses(
204 (status = 200, description = "Access settings", body = AccessData),
205 (status = 403, description = "Admin access required")
206 ),
207 security(("session_cookie" = []))
208)]
209pub async fn get_access_data(
210 _user: AdminUser,
211 Path(crate_name): Path<OriginalName>,
212 State(db): DbState,
213) -> Result<Json<AccessData>, RouteError> {
214 let crate_name = crate_name.to_normalized();
215 Ok(Json(AccessData {
216 download_restricted: db.is_download_restricted(&crate_name).await?,
217 }))
218}
219
220#[utoipa::path(
222 put,
223 path = "/{crate_name}",
224 tag = "acl",
225 params(
226 ("crate_name" = OriginalName, Path, description = "Crate name")
227 ),
228 request_body = AccessData,
229 responses(
230 (status = 200, description = "Access settings updated", body = AccessData),
231 (status = 403, description = "Admin access required")
232 ),
233 security(("session_cookie" = []))
234)]
235pub async fn set_access_data(
236 _user: AdminUser,
237 State(db): DbState,
238 Path(crate_name): Path<OriginalName>,
239 Json(input): Json<AccessData>,
240) -> Result<Json<AccessData>, RouteError> {
241 let crate_name = crate_name.to_normalized();
242 db.change_download_restricted(&crate_name, input.download_restricted)
243 .await?;
244
245 Ok(Json(AccessData {
246 download_restricted: db.is_download_restricted(&crate_name).await?,
247 }))
248}