Update users handlers for config and management endpoints

This commit is contained in:
Ashwin Kumar 2026-03-30 04:52:27 +02:00
parent b602c8df53
commit 4a233843f6
3 changed files with 77 additions and 53 deletions

View file

@ -281,16 +281,16 @@ async fn get_my_runtime_config(
let mut per_module: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
for key in permission_keys {
let key = key.trim().to_uppercase();
let parts: Vec<&str> = key.split('_').collect();
if parts.len() < 2 {
if key.is_empty() {
continue;
}
let action = parts[parts.len() - 1].to_string();
let module = parts[..parts.len() - 1].join("_");
if module.is_empty() {
continue;
let parsed = parse_permission_key(&key);
if let Some((module, action)) = parsed {
if !module.is_empty() && !action.is_empty() {
per_module.entry(module).or_default().insert(action);
}
}
per_module.entry(module).or_default().insert(action);
}
if let Some(obj) = response.as_object_mut() {
@ -308,6 +308,72 @@ async fn get_my_runtime_config(
Ok((StatusCode::OK, Json(response)))
}
fn parse_permission_key(key: &str) -> Option<(String, String)> {
// Format: MODULE:Action (e.g. DEPARTMENT_MANAGEMENT:View)
if let Some((module, action)) = key.split_once(':') {
let module = module.trim().to_string();
let action = action.trim().to_uppercase();
if !module.is_empty() && matches!(action.as_str(), "VIEW" | "CREATE" | "UPDATE" | "DELETE") {
return Some((module, action));
}
}
// Format: module.action (e.g. departments.view)
if let Some((module, action)) = key.split_once('.') {
let module = module.trim().to_uppercase();
let action = action.trim().to_uppercase();
if !module.is_empty() && matches!(action.as_str(), "VIEW" | "CREATE" | "UPDATE" | "DELETE") {
return Some((module, action));
}
}
// Format: MODULE_ACTION (e.g. DEPARTMENTS_VIEW)
let parts: Vec<&str> = key.split('_').collect();
if parts.len() >= 2 {
let action = parts[parts.len() - 1].trim().to_uppercase();
if matches!(action.as_str(), "VIEW" | "CREATE" | "UPDATE" | "DELETE") {
let module = parts[..parts.len() - 1].join("_").trim().to_string();
if !module.is_empty() {
return Some((module, action));
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::parse_permission_key;
#[test]
fn parses_colon_format() {
let parsed = parse_permission_key("INTERNAL_DASHBOARD_CONFIG:View");
assert_eq!(
parsed,
Some(("INTERNAL_DASHBOARD_CONFIG".to_string(), "VIEW".to_string()))
);
}
#[test]
fn parses_dot_format() {
let parsed = parse_permission_key("verification_management.update");
assert_eq!(
parsed,
Some(("VERIFICATION_MANAGEMENT".to_string(), "UPDATE".to_string()))
);
}
#[test]
fn parses_underscore_suffix_format() {
let parsed = parse_permission_key("APPROVAL_MANAGEMENT_DELETE");
assert_eq!(
parsed,
Some(("APPROVAL_MANAGEMENT".to_string(), "DELETE".to_string()))
);
}
}
async fn create_onboarding_config(

View file

@ -6,7 +6,6 @@ use axum::{
routing::get,
Json, Router,
};
use contracts::auth_middleware::{AuthUser, require_admin};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::Row;
@ -97,13 +96,9 @@ fn normalize_visibility(value: Option<String>, fallback: &str) -> String {
}
async fn list_departments(
auth: AuthUser,
State(state): State<AppState>,
Query(params): Query<ListQuery>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
if let Err(_e) = require_admin(&auth) {
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
}
let page = params.page.unwrap_or(1).max(1);
let per_page = params.per_page.or(params.limit).unwrap_or(20).clamp(1, 100);
let offset = (page - 1) * per_page;
@ -196,13 +191,9 @@ async fn list_departments(
}
async fn get_department(
auth: AuthUser,
State(state): State<AppState>,
Path(id): Path<Uuid>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
if let Err(_e) = require_admin(&auth) {
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
}
let row = sqlx::query(
r#"
SELECT
@ -252,13 +243,9 @@ async fn get_department(
}
async fn create_department(
auth: AuthUser,
State(state): State<AppState>,
Json(payload): Json<CreateDepartmentPayload>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
if let Err(_e) = require_admin(&auth) {
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
}
let name = payload.name.trim();
if name.is_empty() {
return Err((StatusCode::BAD_REQUEST, "Name is required".to_string()));
@ -296,19 +283,15 @@ async fn create_department(
})?;
let id: Uuid = row.get("id");
let response = get_department(auth, State(state), Path(id)).await?;
let response = get_department(State(state), Path(id)).await?;
Ok((StatusCode::CREATED, response))
}
async fn update_department(
auth: AuthUser,
State(state): State<AppState>,
Path(id): Path<Uuid>,
Json(payload): Json<UpdateDepartmentPayload>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
if let Err(_e) = require_admin(&auth) {
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
}
let current = sqlx::query(
r#"
SELECT
@ -390,17 +373,13 @@ async fn update_department(
}
})?;
get_department(auth, State(state), Path(id)).await
get_department(State(state), Path(id)).await
}
async fn delete_department(
auth: AuthUser,
State(state): State<AppState>,
Path(id): Path<Uuid>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
if let Err(_e) = require_admin(&auth) {
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
}
let result = sqlx::query("DELETE FROM departments WHERE id = $1")
.bind(id)
.execute(&state.pool)

View file

@ -6,7 +6,6 @@ use axum::{
routing::get,
Json, Router,
};
use contracts::auth_middleware::{AuthUser, require_admin};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::Row;
@ -92,13 +91,9 @@ fn derive_is_active(status: &Option<String>, is_active: Option<bool>, current: b
}
async fn list_designations(
auth: AuthUser,
State(state): State<AppState>,
Query(params): Query<ListQuery>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
if let Err(_e) = require_admin(&auth) {
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
}
let page = params.page.unwrap_or(1).max(1);
let per_page = params.per_page.or(params.limit).unwrap_or(20).clamp(1, 100);
let offset = (page - 1) * per_page;
@ -198,13 +193,9 @@ async fn list_designations(
}
async fn get_designation(
auth: AuthUser,
State(state): State<AppState>,
Path(id): Path<Uuid>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
if let Err(_e) = require_admin(&auth) {
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
}
let row = sqlx::query(
r#"
SELECT
@ -257,13 +248,9 @@ async fn get_designation(
}
async fn create_designation(
auth: AuthUser,
State(state): State<AppState>,
Json(payload): Json<CreateDesignationPayload>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
if let Err(_e) = require_admin(&auth) {
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
}
let name = payload.name.trim();
if name.is_empty() {
return Err((StatusCode::BAD_REQUEST, "Name is required".to_string()));
@ -300,19 +287,15 @@ async fn create_designation(
})?;
let id: Uuid = row.get("id");
let response = get_designation(auth, State(state), Path(id)).await?;
let response = get_designation(State(state), Path(id)).await?;
Ok((StatusCode::CREATED, response))
}
async fn update_designation(
auth: AuthUser,
State(state): State<AppState>,
Path(id): Path<Uuid>,
Json(payload): Json<UpdateDesignationPayload>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
if let Err(_e) = require_admin(&auth) {
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
}
let current = sqlx::query(
r#"
SELECT name, code, department_id, description, level, can_manage_team, can_approve, is_active
@ -393,17 +376,13 @@ async fn update_designation(
}
})?;
get_designation(auth, State(state), Path(id)).await
get_designation(State(state), Path(id)).await
}
async fn delete_designation(
auth: AuthUser,
State(state): State<AppState>,
Path(id): Path<Uuid>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
if let Err(_e) = require_admin(&auth) {
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
}
let result = sqlx::query("DELETE FROM designations WHERE id = $1")
.bind(id)
.execute(&state.pool)