1use axum::{
13 Json,
14 extract::{Path, Query},
15 http::StatusCode,
16 response::IntoResponse,
17};
18
19use super::{ErrorResponse, RequestCtx, SiteHeader, to_handler_error};
20use crate::service;
21
22pub use manta_shared::types::api::queries::{DeleteGroupQuery, GroupQuery};
27
28#[utoipa::path(get, path = "/groups/available", tag = "groups",
30 params(SiteHeader),
31 security(("bearerAuth" = [])),
32 responses(
33 (status = 200, description = "List of accessible group names", body = Vec<String>),
34 (status = 401, description = "Unauthorized", body = ErrorResponse),
35 (status = 500, description = "Internal error", body = ErrorResponse),
36 )
37)]
38#[tracing::instrument(skip_all)]
39pub async fn get_available_groups(
40 ctx: RequestCtx,
41) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
42 let infra = ctx.infra();
43 let names = service::group::get_available_groups(&infra, &ctx.token)
44 .await
45 .map_err(to_handler_error)?;
46 Ok(Json(names))
47}
48
49#[utoipa::path(get, path = "/groups", tag = "groups",
51 params(GroupQuery, SiteHeader),
52 security(("bearerAuth" = [])),
53 responses(
54 (status = 200, description = "List of groups", body = Vec<manta_backend_dispatcher::types::Group>),
55 (status = 401, description = "Unauthorized", body = ErrorResponse),
56 (status = 500, description = "Internal error", body = ErrorResponse),
57 )
58)]
59#[tracing::instrument(skip_all)]
60pub async fn get_groups(
61 ctx: RequestCtx,
62 Query(q): Query<GroupQuery>,
63) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
64 let infra = ctx.infra();
65
66 let params = service::group::GetGroupParams {
67 group_name: q.name,
68 settings_group_name: None,
69 };
70
71 let groups = service::group::get_groups(&infra, &ctx.token, ¶ms)
72 .await
73 .map_err(to_handler_error)?;
74
75 Ok(Json(groups))
76}
77
78#[utoipa::path(delete, path = "/groups/{label}", tag = "groups",
84 params(("label" = String, Path, description = "Group label"), DeleteGroupQuery, SiteHeader),
85 security(("bearerAuth" = [])),
86 responses(
87 (status = 204, description = "Group removed"),
88 (status = 401, description = "Unauthorized", body = ErrorResponse),
89 (status = 404, description = "Not found", body = ErrorResponse),
90 (status = 500, description = "Internal error", body = ErrorResponse),
91 )
92)]
93#[tracing::instrument(skip_all)]
94pub async fn delete_group(
95 ctx: RequestCtx,
96 Path(label): Path<String>,
97 Query(q): Query<DeleteGroupQuery>,
98) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
99 tracing::info!("delete_group label={} force={}", label, q.force);
100 let infra = ctx.infra();
101
102 service::authorization::validate_user_group_access(
104 &infra, &ctx.token, &label,
105 )
106 .await
107 .map_err(to_handler_error)?;
108
109 service::group::delete_group(&infra, &ctx.token, &label, q.force)
110 .await
111 .map_err(to_handler_error)?;
112
113 Ok(StatusCode::NO_CONTENT)
114}
115
116#[utoipa::path(post, path = "/groups", tag = "groups",
122 params(SiteHeader),
123 request_body = manta_backend_dispatcher::types::Group,
124 security(("bearerAuth" = [])),
125 responses(
126 (status = 201, description = "Group created", body = manta_shared::types::api::responses::CreatedResponse),
127 (status = 401, description = "Unauthorized", body = ErrorResponse),
128 (status = 409, description = "Conflict", body = ErrorResponse),
129 (status = 500, description = "Internal error", body = ErrorResponse),
130 )
131)]
132#[tracing::instrument(skip_all)]
133pub async fn create_group(
134 ctx: RequestCtx,
135 Json(group): Json<::manta_backend_dispatcher::types::Group>,
136) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
137 tracing::info!("create_group");
138 let infra = ctx.infra();
139
140 service::authorization::require_admin(&ctx.token)
145 .map_err(to_handler_error)?;
146
147 service::group::create_group(&infra, &ctx.token, group)
148 .await
149 .map_err(to_handler_error)?;
150
151 Ok((
152 StatusCode::CREATED,
153 Json(serde_json::json!({ "created": true })),
154 ))
155}
156
157pub use manta_shared::types::api::group::{
162 AddNodesToGroupRequest, AddNodesToGroupResponse, DeleteGroupMembersRequest,
163};
164
165#[utoipa::path(post, path = "/groups/{name}/members", tag = "groups",
167 params(("name" = String, Path, description = "Group name"), SiteHeader),
168 request_body = AddNodesToGroupRequest,
169 security(("bearerAuth" = [])),
170 responses(
171 (status = 200, description = "Members updated", body = AddNodesToGroupResponse),
172 (status = 400, description = "Bad request", body = ErrorResponse),
173 (status = 401, description = "Unauthorized", body = ErrorResponse),
174 (status = 500, description = "Internal error", body = ErrorResponse),
175 )
176)]
177#[tracing::instrument(skip_all)]
178pub async fn add_nodes_to_group(
179 ctx: RequestCtx,
180 Path(name): Path<String>,
181 Json(body): Json<AddNodesToGroupRequest>,
182) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
183 tracing::info!(
184 "add_nodes_to_group group={} hosts={}",
185 name,
186 body.hosts_expression
187 );
188 let infra = ctx.infra();
189
190 service::authorization::validate_user_group_access(&infra, &ctx.token, &name)
192 .await
193 .map_err(to_handler_error)?;
194
195 let (added, removed) = service::group::add_nodes_to_group(
196 &infra,
197 &ctx.token,
198 &name,
199 &body.hosts_expression,
200 )
201 .await
202 .map_err(to_handler_error)?;
203
204 Ok(Json(AddNodesToGroupResponse {
208 added,
209 final_members: removed.clone(),
210 removed,
211 }))
212}
213
214#[utoipa::path(delete, path = "/groups/{name}/members", tag = "groups",
220 params(("name" = String, Path, description = "Group name"), SiteHeader),
221 request_body = DeleteGroupMembersRequest,
222 security(("bearerAuth" = [])),
223 responses(
224 (status = 204, description = "Members removed"),
225 (status = 400, description = "Bad request", body = ErrorResponse),
226 (status = 401, description = "Unauthorized", body = ErrorResponse),
227 (status = 500, description = "Internal error", body = ErrorResponse),
228 )
229)]
230#[tracing::instrument(skip_all)]
231pub async fn delete_group_members(
232 ctx: RequestCtx,
233 Path(name): Path<String>,
234 Json(body): Json<DeleteGroupMembersRequest>,
235) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
236 tracing::info!(
237 "delete_group_members group={} xnames_expression={} dry_run={}",
238 name,
239 body.xnames_expression,
240 body.dry_run
241 );
242 let infra = ctx.infra();
243
244 service::authorization::validate_user_group_access(&infra, &ctx.token, &name)
246 .await
247 .map_err(to_handler_error)?;
248
249 service::group::delete_group_members(
250 &infra,
251 &ctx.token,
252 &name,
253 &body.xnames_expression,
254 body.dry_run,
255 )
256 .await
257 .map_err(to_handler_error)?;
258
259 Ok(StatusCode::NO_CONTENT)
260}