Private
Public Access
1
0

style: Apply rustfmt with stable-only config
Some checks failed
CI Pipeline / Clippy Lints (push) Failing after 0s
CI Pipeline / Rust Unit Tests (push) Failing after 0s
CI Pipeline / Rust Format Check (push) Successful in 4s
CI Pipeline / Frontend Lint & Type Check (push) Failing after 0s
CI Pipeline / Security Audit (push) Failing after 3s
CI Pipeline / Build .deb & Release (push) Has been skipped

- Fixed rustfmt.toml to only use stable options (removed nightly-only)
- Applied cargo fmt --all to fix formatting violations
- Stable options: edition=2021, max_width=100, reorder_imports/modules, match_block_trailing_comma
This commit is contained in:
2026-04-24 15:32:50 +00:00
parent f0fe5f5fd1
commit 5a4d4d583e
44 changed files with 1498 additions and 1040 deletions

View File

@ -15,11 +15,11 @@ use axum::{
routing::{delete, get, post, put},
Router,
};
use pm_auth::rbac::AuthUser;
use pm_core::{
audit::{log_event, AuditAction},
models::{Group, CreateGroupRequest, UpdateGroupRequest},
models::{CreateGroupRequest, Group, UpdateGroupRequest},
};
use pm_auth::rbac::AuthUser;
use serde_json::{json, Value};
use uuid::Uuid;
@ -28,8 +28,14 @@ use crate::AppState;
pub fn router() -> Router<AppState> {
Router::new()
.route("/", get(list_groups).post(create_group))
.route("/:id", get(get_group).put(update_group).delete(delete_group))
.route("/:id/users/:user_id", post(add_user_to_group).delete(remove_user_from_group))
.route(
"/:id",
get(get_group).put(update_group).delete(delete_group),
)
.route(
"/:id/users/:user_id",
post(add_user_to_group).delete(remove_user_from_group),
)
}
async fn list_groups(
@ -37,14 +43,17 @@ async fn list_groups(
_auth: AuthUser,
) -> Result<Json<Vec<Group>>, (StatusCode, Json<Value>)> {
sqlx::query_as::<_, Group>(
"SELECT id, name, description, created_at, updated_at FROM groups ORDER BY name"
"SELECT id, name, description, created_at, updated_at FROM groups ORDER BY name",
)
.fetch_all(&state.db)
.await
.map(Json)
.map_err(|e| {
tracing::error!(error = %e, "Failed to list groups");
(StatusCode::INTERNAL_SERVER_ERROR, Json(json!({ "error": { "code": "internal_error", "message": "Database error" } })))
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({ "error": { "code": "internal_error", "message": "Database error" } })),
)
})
}
@ -54,23 +63,42 @@ async fn create_group(
Json(req): Json<CreateGroupRequest>,
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
if !auth.role.is_admin() {
return Err((StatusCode::FORBIDDEN, Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } }))));
return Err((
StatusCode::FORBIDDEN,
Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } })),
));
}
let id: Uuid = sqlx::query_scalar(
"INSERT INTO groups (name, description) VALUES ($1, $2) RETURNING id"
)
.bind(&req.name)
.bind(req.description.as_deref().unwrap_or(""))
.fetch_one(&state.db)
.await
.map_err(|e| {
let msg = if e.to_string().contains("unique") { "Group name already exists".to_string() } else { "Database error".to_string() };
(StatusCode::CONFLICT, Json(json!({ "error": { "code": "conflict", "message": msg } })))
})?;
let id: Uuid =
sqlx::query_scalar("INSERT INTO groups (name, description) VALUES ($1, $2) RETURNING id")
.bind(&req.name)
.bind(req.description.as_deref().unwrap_or(""))
.fetch_one(&state.db)
.await
.map_err(|e| {
let msg = if e.to_string().contains("unique") {
"Group name already exists".to_string()
} else {
"Database error".to_string()
};
(
StatusCode::CONFLICT,
Json(json!({ "error": { "code": "conflict", "message": msg } })),
)
})?;
log_event(&state.db, AuditAction::GroupCreated, Some(auth.user_id), Some(&auth.username),
Some("group"), Some(&id.to_string()), json!({ "name": req.name }), None, None).await;
log_event(
&state.db,
AuditAction::GroupCreated,
Some(auth.user_id),
Some(&auth.username),
Some("group"),
Some(&id.to_string()),
json!({ "name": req.name }),
None,
None,
)
.await;
Ok(Json(json!({ "id": id, "message": "Group created" })))
}
@ -81,24 +109,43 @@ async fn get_group(
Path(id): Path<Uuid>,
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
let group: Option<Group> = sqlx::query_as(
"SELECT id, name, description, created_at, updated_at FROM groups WHERE id = $1"
"SELECT id, name, description, created_at, updated_at FROM groups WHERE id = $1",
)
.bind(id)
.fetch_optional(&state.db)
.await
.map_err(|e| {
tracing::error!(error = %e); (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({ "error": { "code": "internal_error", "message": "Database error" } })))
tracing::error!(error = %e);
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({ "error": { "code": "internal_error", "message": "Database error" } })),
)
})?;
let group = group.ok_or_else(|| (StatusCode::NOT_FOUND, Json(json!({ "error": { "code": "not_found", "message": "Group not found" } }))))?;
let group = group.ok_or_else(|| {
(
StatusCode::NOT_FOUND,
Json(json!({ "error": { "code": "not_found", "message": "Group not found" } })),
)
})?;
// Fetch member counts
let host_count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM host_groups WHERE group_id = $1")
.bind(id).fetch_one(&state.db).await.unwrap_or(0);
let user_count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM user_groups WHERE group_id = $1")
.bind(id).fetch_one(&state.db).await.unwrap_or(0);
let host_count: i64 =
sqlx::query_scalar("SELECT COUNT(*) FROM host_groups WHERE group_id = $1")
.bind(id)
.fetch_one(&state.db)
.await
.unwrap_or(0);
let user_count: i64 =
sqlx::query_scalar("SELECT COUNT(*) FROM user_groups WHERE group_id = $1")
.bind(id)
.fetch_one(&state.db)
.await
.unwrap_or(0);
Ok(Json(json!({ "group": group, "host_count": host_count, "user_count": user_count })))
Ok(Json(
json!({ "group": group, "host_count": host_count, "user_count": user_count }),
))
}
async fn update_group(
@ -108,7 +155,10 @@ async fn update_group(
Json(req): Json<UpdateGroupRequest>,
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
if !auth.role.is_admin() {
return Err((StatusCode::FORBIDDEN, Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } }))));
return Err((
StatusCode::FORBIDDEN,
Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } })),
));
}
let rows = sqlx::query(
@ -123,7 +173,10 @@ async fn update_group(
.rows_affected();
if rows == 0 {
return Err((StatusCode::NOT_FOUND, Json(json!({ "error": { "code": "not_found", "message": "Group not found" } }))));
return Err((
StatusCode::NOT_FOUND,
Json(json!({ "error": { "code": "not_found", "message": "Group not found" } })),
));
}
Ok(Json(json!({ "message": "Group updated" })))
@ -135,20 +188,43 @@ async fn delete_group(
Path(id): Path<Uuid>,
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
if !auth.role.is_admin() {
return Err((StatusCode::FORBIDDEN, Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } }))));
return Err((
StatusCode::FORBIDDEN,
Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } })),
));
}
let rows = sqlx::query("DELETE FROM groups WHERE id = $1")
.bind(id).execute(&state.db).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({ "error": { "code": "internal_error", "message": e.to_string() } }))))?
.bind(id)
.execute(&state.db)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({ "error": { "code": "internal_error", "message": e.to_string() } })),
)
})?
.rows_affected();
if rows == 0 {
return Err((StatusCode::NOT_FOUND, Json(json!({ "error": { "code": "not_found", "message": "Group not found" } }))));
return Err((
StatusCode::NOT_FOUND,
Json(json!({ "error": { "code": "not_found", "message": "Group not found" } })),
));
}
log_event(&state.db, AuditAction::GroupDeleted, Some(auth.user_id), Some(&auth.username),
Some("group"), Some(&id.to_string()), json!({}), None, None).await;
log_event(
&state.db,
AuditAction::GroupDeleted,
Some(auth.user_id),
Some(&auth.username),
Some("group"),
Some(&id.to_string()),
json!({}),
None,
None,
)
.await;
Ok(Json(json!({ "message": "Group deleted" })))
}
@ -159,16 +235,38 @@ async fn add_user_to_group(
Path((id, user_id)): Path<(Uuid, Uuid)>,
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
if !auth.role.is_admin() {
return Err((StatusCode::FORBIDDEN, Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } }))));
return Err((
StatusCode::FORBIDDEN,
Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } })),
));
}
sqlx::query("INSERT INTO user_groups (user_id, group_id) VALUES ($1, $2) ON CONFLICT DO NOTHING")
.bind(user_id).bind(id)
.execute(&state.db).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({ "error": { "code": "internal_error", "message": e.to_string() } }))))?;
sqlx::query(
"INSERT INTO user_groups (user_id, group_id) VALUES ($1, $2) ON CONFLICT DO NOTHING",
)
.bind(user_id)
.bind(id)
.execute(&state.db)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({ "error": { "code": "internal_error", "message": e.to_string() } })),
)
})?;
log_event(&state.db, AuditAction::GroupMembershipChanged, Some(auth.user_id), Some(&auth.username),
Some("user_group"), Some(&id.to_string()), json!({ "user_id": user_id, "action": "added" }), None, None).await;
log_event(
&state.db,
AuditAction::GroupMembershipChanged,
Some(auth.user_id),
Some(&auth.username),
Some("user_group"),
Some(&id.to_string()),
json!({ "user_id": user_id, "action": "added" }),
None,
None,
)
.await;
Ok(Json(json!({ "message": "User added to group" })))
}
@ -179,16 +277,36 @@ async fn remove_user_from_group(
Path((id, user_id)): Path<(Uuid, Uuid)>,
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
if !auth.role.is_admin() {
return Err((StatusCode::FORBIDDEN, Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } }))));
return Err((
StatusCode::FORBIDDEN,
Json(json!({ "error": { "code": "forbidden", "message": "Admin role required" } })),
));
}
sqlx::query("DELETE FROM user_groups WHERE user_id = $1 AND group_id = $2")
.bind(user_id).bind(id)
.execute(&state.db).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({ "error": { "code": "internal_error", "message": e.to_string() } }))))?;
.bind(user_id)
.bind(id)
.execute(&state.db)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({ "error": { "code": "internal_error", "message": e.to_string() } })),
)
})?;
log_event(&state.db, AuditAction::GroupMembershipChanged, Some(auth.user_id), Some(&auth.username),
Some("user_group"), Some(&id.to_string()), json!({ "user_id": user_id, "action": "removed" }), None, None).await;
log_event(
&state.db,
AuditAction::GroupMembershipChanged,
Some(auth.user_id),
Some(&auth.username),
Some("user_group"),
Some(&id.to_string()),
json!({ "user_id": user_id, "action": "removed" }),
None,
None,
)
.await;
Ok(Json(json!({ "message": "User removed from group" })))
}