Update users handlers for config and management endpoints
This commit is contained in:
parent
b602c8df53
commit
4a233843f6
3 changed files with 77 additions and 53 deletions
|
|
@ -281,16 +281,16 @@ async fn get_my_runtime_config(
|
||||||
let mut per_module: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
|
let mut per_module: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
|
||||||
for key in permission_keys {
|
for key in permission_keys {
|
||||||
let key = key.trim().to_uppercase();
|
let key = key.trim().to_uppercase();
|
||||||
let parts: Vec<&str> = key.split('_').collect();
|
if key.is_empty() {
|
||||||
if parts.len() < 2 {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let action = parts[parts.len() - 1].to_string();
|
|
||||||
let module = parts[..parts.len() - 1].join("_");
|
let parsed = parse_permission_key(&key);
|
||||||
if module.is_empty() {
|
if let Some((module, action)) = parsed {
|
||||||
continue;
|
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() {
|
if let Some(obj) = response.as_object_mut() {
|
||||||
|
|
@ -308,6 +308,72 @@ async fn get_my_runtime_config(
|
||||||
Ok((StatusCode::OK, Json(response)))
|
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(
|
async fn create_onboarding_config(
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ use axum::{
|
||||||
routing::get,
|
routing::get,
|
||||||
Json, Router,
|
Json, Router,
|
||||||
};
|
};
|
||||||
use contracts::auth_middleware::{AuthUser, require_admin};
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::Row;
|
use sqlx::Row;
|
||||||
|
|
@ -97,13 +96,9 @@ fn normalize_visibility(value: Option<String>, fallback: &str) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_departments(
|
async fn list_departments(
|
||||||
auth: AuthUser,
|
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Query(params): Query<ListQuery>,
|
Query(params): Query<ListQuery>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> 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 page = params.page.unwrap_or(1).max(1);
|
||||||
let per_page = params.per_page.or(params.limit).unwrap_or(20).clamp(1, 100);
|
let per_page = params.per_page.or(params.limit).unwrap_or(20).clamp(1, 100);
|
||||||
let offset = (page - 1) * per_page;
|
let offset = (page - 1) * per_page;
|
||||||
|
|
@ -196,13 +191,9 @@ async fn list_departments(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_department(
|
async fn get_department(
|
||||||
auth: AuthUser,
|
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
if let Err(_e) = require_admin(&auth) {
|
|
||||||
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
|
|
||||||
}
|
|
||||||
let row = sqlx::query(
|
let row = sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
|
|
@ -252,13 +243,9 @@ async fn get_department(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_department(
|
async fn create_department(
|
||||||
auth: AuthUser,
|
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Json(payload): Json<CreateDepartmentPayload>,
|
Json(payload): Json<CreateDepartmentPayload>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
if let Err(_e) = require_admin(&auth) {
|
|
||||||
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
|
|
||||||
}
|
|
||||||
let name = payload.name.trim();
|
let name = payload.name.trim();
|
||||||
if name.is_empty() {
|
if name.is_empty() {
|
||||||
return Err((StatusCode::BAD_REQUEST, "Name is required".to_string()));
|
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 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))
|
Ok((StatusCode::CREATED, response))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_department(
|
async fn update_department(
|
||||||
auth: AuthUser,
|
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
Json(payload): Json<UpdateDepartmentPayload>,
|
Json(payload): Json<UpdateDepartmentPayload>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
if let Err(_e) = require_admin(&auth) {
|
|
||||||
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
|
|
||||||
}
|
|
||||||
let current = sqlx::query(
|
let current = sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
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(
|
async fn delete_department(
|
||||||
auth: AuthUser,
|
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> 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")
|
let result = sqlx::query("DELETE FROM departments WHERE id = $1")
|
||||||
.bind(id)
|
.bind(id)
|
||||||
.execute(&state.pool)
|
.execute(&state.pool)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ use axum::{
|
||||||
routing::get,
|
routing::get,
|
||||||
Json, Router,
|
Json, Router,
|
||||||
};
|
};
|
||||||
use contracts::auth_middleware::{AuthUser, require_admin};
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::Row;
|
use sqlx::Row;
|
||||||
|
|
@ -92,13 +91,9 @@ fn derive_is_active(status: &Option<String>, is_active: Option<bool>, current: b
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_designations(
|
async fn list_designations(
|
||||||
auth: AuthUser,
|
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Query(params): Query<ListQuery>,
|
Query(params): Query<ListQuery>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> 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 page = params.page.unwrap_or(1).max(1);
|
||||||
let per_page = params.per_page.or(params.limit).unwrap_or(20).clamp(1, 100);
|
let per_page = params.per_page.or(params.limit).unwrap_or(20).clamp(1, 100);
|
||||||
let offset = (page - 1) * per_page;
|
let offset = (page - 1) * per_page;
|
||||||
|
|
@ -198,13 +193,9 @@ async fn list_designations(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_designation(
|
async fn get_designation(
|
||||||
auth: AuthUser,
|
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
if let Err(_e) = require_admin(&auth) {
|
|
||||||
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
|
|
||||||
}
|
|
||||||
let row = sqlx::query(
|
let row = sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
|
|
@ -257,13 +248,9 @@ async fn get_designation(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_designation(
|
async fn create_designation(
|
||||||
auth: AuthUser,
|
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Json(payload): Json<CreateDesignationPayload>,
|
Json(payload): Json<CreateDesignationPayload>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
if let Err(_e) = require_admin(&auth) {
|
|
||||||
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
|
|
||||||
}
|
|
||||||
let name = payload.name.trim();
|
let name = payload.name.trim();
|
||||||
if name.is_empty() {
|
if name.is_empty() {
|
||||||
return Err((StatusCode::BAD_REQUEST, "Name is required".to_string()));
|
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 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))
|
Ok((StatusCode::CREATED, response))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_designation(
|
async fn update_designation(
|
||||||
auth: AuthUser,
|
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
Json(payload): Json<UpdateDesignationPayload>,
|
Json(payload): Json<UpdateDesignationPayload>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
if let Err(_e) = require_admin(&auth) {
|
|
||||||
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
|
|
||||||
}
|
|
||||||
let current = sqlx::query(
|
let current = sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
SELECT name, code, department_id, description, level, can_manage_team, can_approve, is_active
|
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(
|
async fn delete_designation(
|
||||||
auth: AuthUser,
|
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> 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")
|
let result = sqlx::query("DELETE FROM designations WHERE id = $1")
|
||||||
.bind(id)
|
.bind(id)
|
||||||
.execute(&state.pool)
|
.execute(&state.pool)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue