2026-03-17 20:42:51 +01:00
|
|
|
use crate::AppState;
|
|
|
|
|
use axum::{
|
2026-03-25 22:15:07 +01:00
|
|
|
extract::{Path, Query, State},
|
2026-03-17 20:42:51 +01:00
|
|
|
http::StatusCode,
|
|
|
|
|
response::IntoResponse,
|
2026-03-27 21:34:28 +01:00
|
|
|
routing::get,
|
2026-03-17 20:42:51 +01:00
|
|
|
Json, Router,
|
|
|
|
|
};
|
2026-03-25 22:15:07 +01:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
use uuid::Uuid;
|
2026-03-17 20:42:51 +01:00
|
|
|
|
|
|
|
|
pub fn router() -> Router<AppState> {
|
|
|
|
|
Router::new()
|
|
|
|
|
.route("/", get(list_roles).post(create_role))
|
2026-03-25 23:03:12 +01:00
|
|
|
.route("/{id}", get(get_role).patch(update_role).delete(delete_role))
|
2026-03-25 22:15:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Query params ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
|
struct ListQuery {
|
|
|
|
|
audience: Option<String>,
|
|
|
|
|
q: Option<String>,
|
|
|
|
|
page: Option<i64>,
|
|
|
|
|
per_page: Option<i64>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Response types ───────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize)]
|
|
|
|
|
struct RoleRow {
|
|
|
|
|
id: Uuid,
|
|
|
|
|
key: String,
|
|
|
|
|
name: String,
|
|
|
|
|
audience: String,
|
|
|
|
|
description: Option<String>,
|
|
|
|
|
department_id: Option<Uuid>,
|
|
|
|
|
department_name: Option<String>,
|
|
|
|
|
is_active: bool,
|
|
|
|
|
can_approve_requests: bool,
|
|
|
|
|
can_manage_system_settings: bool,
|
|
|
|
|
users_assigned: i64,
|
|
|
|
|
permissions_count: i64,
|
|
|
|
|
created_at: chrono::DateTime<chrono::Utc>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize)]
|
|
|
|
|
struct ListResponse {
|
|
|
|
|
roles: Vec<RoleRow>,
|
|
|
|
|
total: i64,
|
|
|
|
|
page: i64,
|
|
|
|
|
per_page: i64,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize)]
|
|
|
|
|
struct RoleDetail {
|
|
|
|
|
id: Uuid,
|
|
|
|
|
key: String,
|
|
|
|
|
name: String,
|
|
|
|
|
audience: String,
|
|
|
|
|
description: Option<String>,
|
|
|
|
|
department_id: Option<Uuid>,
|
|
|
|
|
department_name: Option<String>,
|
|
|
|
|
is_active: bool,
|
|
|
|
|
can_approve_requests: bool,
|
|
|
|
|
can_manage_system_settings: bool,
|
|
|
|
|
permission_keys: Vec<String>,
|
|
|
|
|
created_at: chrono::DateTime<chrono::Utc>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Request types ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
|
struct CreateRolePayload {
|
|
|
|
|
key: String,
|
|
|
|
|
name: String,
|
|
|
|
|
audience: String,
|
|
|
|
|
description: Option<String>,
|
|
|
|
|
department_id: Option<Uuid>,
|
|
|
|
|
is_active: Option<bool>,
|
|
|
|
|
can_approve_requests: Option<bool>,
|
|
|
|
|
can_manage_system_settings: Option<bool>,
|
|
|
|
|
permission_keys: Option<Vec<String>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
|
struct UpdateRolePayload {
|
|
|
|
|
name: Option<String>,
|
|
|
|
|
description: Option<String>,
|
|
|
|
|
department_id: Option<Uuid>,
|
|
|
|
|
is_active: Option<bool>,
|
|
|
|
|
can_approve_requests: Option<bool>,
|
|
|
|
|
can_manage_system_settings: Option<bool>,
|
|
|
|
|
permission_keys: Option<Vec<String>>,
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 08:11:26 +02:00
|
|
|
// ── FromRow structs ──────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
#[derive(sqlx::FromRow)]
|
|
|
|
|
struct RoleListRow {
|
|
|
|
|
id: Uuid,
|
|
|
|
|
key: String,
|
|
|
|
|
name: String,
|
|
|
|
|
audience: String,
|
|
|
|
|
description: Option<String>,
|
|
|
|
|
department_id: Option<Uuid>,
|
|
|
|
|
department_name: Option<String>,
|
|
|
|
|
is_active: bool,
|
|
|
|
|
can_approve_requests: bool,
|
|
|
|
|
can_manage_system_settings: bool,
|
|
|
|
|
created_at: chrono::DateTime<chrono::Utc>,
|
|
|
|
|
users_assigned: Option<i64>,
|
|
|
|
|
permissions_count: Option<i64>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(sqlx::FromRow)]
|
|
|
|
|
struct RoleDetailRow {
|
|
|
|
|
id: Uuid,
|
|
|
|
|
key: String,
|
|
|
|
|
name: String,
|
|
|
|
|
audience: String,
|
|
|
|
|
description: Option<String>,
|
|
|
|
|
department_id: Option<Uuid>,
|
|
|
|
|
department_name: Option<String>,
|
|
|
|
|
is_active: bool,
|
|
|
|
|
can_approve_requests: bool,
|
|
|
|
|
can_manage_system_settings: bool,
|
|
|
|
|
created_at: chrono::DateTime<chrono::Utc>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(sqlx::FromRow)]
|
|
|
|
|
struct InsertedRoleRow {
|
|
|
|
|
id: Uuid,
|
|
|
|
|
key: String,
|
|
|
|
|
name: String,
|
|
|
|
|
audience: String,
|
|
|
|
|
description: Option<String>,
|
|
|
|
|
department_id: Option<Uuid>,
|
|
|
|
|
is_active: bool,
|
|
|
|
|
can_approve_requests: bool,
|
|
|
|
|
can_manage_system_settings: bool,
|
|
|
|
|
created_at: chrono::DateTime<chrono::Utc>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(sqlx::FromRow)]
|
|
|
|
|
struct CurrentRoleRow {
|
|
|
|
|
name: String,
|
|
|
|
|
description: Option<String>,
|
|
|
|
|
department_id: Option<Uuid>,
|
|
|
|
|
is_active: bool,
|
|
|
|
|
can_approve_requests: bool,
|
|
|
|
|
can_manage_system_settings: bool,
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 22:15:07 +01:00
|
|
|
// ── Handlers ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
async fn list_roles(
|
|
|
|
|
State(state): State<AppState>,
|
|
|
|
|
Query(params): Query<ListQuery>,
|
|
|
|
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
|
|
|
|
let page = params.page.unwrap_or(1).max(1);
|
|
|
|
|
let per_page = params.per_page.unwrap_or(20).min(100);
|
|
|
|
|
let offset = (page - 1) * per_page;
|
|
|
|
|
let search = params.q.as_deref().unwrap_or("").to_lowercase();
|
2026-04-09 08:11:26 +02:00
|
|
|
let audience = params.audience.as_deref().unwrap_or("").to_string();
|
2026-03-25 22:15:07 +01:00
|
|
|
|
2026-04-09 08:11:26 +02:00
|
|
|
let rows = sqlx::query_as::<_, RoleListRow>(
|
2026-03-25 22:15:07 +01:00
|
|
|
r#"
|
|
|
|
|
SELECT
|
|
|
|
|
r.id,
|
2026-04-15 00:16:25 +02:00
|
|
|
r.code AS key,
|
2026-03-25 22:15:07 +01:00
|
|
|
r.name,
|
|
|
|
|
r.audience,
|
|
|
|
|
r.description,
|
|
|
|
|
r.department_id,
|
2026-04-09 08:11:26 +02:00
|
|
|
d.name AS department_name,
|
2026-03-25 22:15:07 +01:00
|
|
|
r.is_active,
|
|
|
|
|
r.can_approve_requests,
|
|
|
|
|
r.can_manage_system_settings,
|
|
|
|
|
r.created_at,
|
|
|
|
|
COUNT(DISTINCT e.id) AS users_assigned,
|
|
|
|
|
COUNT(DISTINCT rp.id) AS permissions_count
|
|
|
|
|
FROM roles r
|
|
|
|
|
LEFT JOIN departments d ON d.id = r.department_id
|
2026-04-15 00:16:25 +02:00
|
|
|
LEFT JOIN employees e ON e.role_code = r.code
|
2026-03-25 22:15:07 +01:00
|
|
|
LEFT JOIN role_permissions rp ON rp.role_id = r.id
|
|
|
|
|
WHERE ($1 = '' OR r.audience = $1)
|
2026-04-15 00:16:25 +02:00
|
|
|
AND ($2 = '' OR LOWER(r.name) LIKE '%' || $2 || '%' OR LOWER(r.code) LIKE '%' || $2 || '%')
|
2026-03-25 22:15:07 +01:00
|
|
|
GROUP BY r.id, d.name
|
|
|
|
|
ORDER BY r.created_at DESC
|
|
|
|
|
LIMIT $3 OFFSET $4
|
|
|
|
|
"#,
|
|
|
|
|
)
|
2026-04-09 08:11:26 +02:00
|
|
|
.bind(&audience)
|
|
|
|
|
.bind(&search)
|
|
|
|
|
.bind(per_page)
|
|
|
|
|
.bind(offset)
|
2026-03-25 22:15:07 +01:00
|
|
|
.fetch_all(&state.pool)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
|
|
|
|
|
2026-04-09 08:11:26 +02:00
|
|
|
let total: i64 = sqlx::query_scalar::<_, i64>(
|
2026-03-25 22:15:07 +01:00
|
|
|
r#"
|
|
|
|
|
SELECT COUNT(*) FROM roles r
|
|
|
|
|
WHERE ($1 = '' OR r.audience = $1)
|
2026-04-15 00:16:25 +02:00
|
|
|
AND ($2 = '' OR LOWER(r.name) LIKE '%' || $2 || '%' OR LOWER(r.code) LIKE '%' || $2 || '%')
|
2026-03-25 22:15:07 +01:00
|
|
|
"#,
|
|
|
|
|
)
|
2026-04-09 08:11:26 +02:00
|
|
|
.bind(&audience)
|
|
|
|
|
.bind(&search)
|
2026-03-25 22:15:07 +01:00
|
|
|
.fetch_one(&state.pool)
|
|
|
|
|
.await
|
2026-04-09 08:11:26 +02:00
|
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
2026-03-25 22:15:07 +01:00
|
|
|
|
|
|
|
|
let roles = rows
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|r| RoleRow {
|
|
|
|
|
id: r.id,
|
|
|
|
|
key: r.key,
|
|
|
|
|
name: r.name,
|
|
|
|
|
audience: r.audience,
|
|
|
|
|
description: r.description,
|
|
|
|
|
department_id: r.department_id,
|
|
|
|
|
department_name: r.department_name,
|
|
|
|
|
is_active: r.is_active,
|
|
|
|
|
can_approve_requests: r.can_approve_requests,
|
|
|
|
|
can_manage_system_settings: r.can_manage_system_settings,
|
|
|
|
|
users_assigned: r.users_assigned.unwrap_or(0),
|
|
|
|
|
permissions_count: r.permissions_count.unwrap_or(0),
|
|
|
|
|
created_at: r.created_at,
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
Ok(Json(ListResponse { roles, total, page, per_page }))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn get_role(
|
|
|
|
|
State(state): State<AppState>,
|
|
|
|
|
Path(id): Path<Uuid>,
|
|
|
|
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
2026-04-09 08:11:26 +02:00
|
|
|
let row = sqlx::query_as::<_, RoleDetailRow>(
|
2026-03-25 22:15:07 +01:00
|
|
|
r#"
|
|
|
|
|
SELECT
|
2026-04-15 00:16:25 +02:00
|
|
|
r.id, r.code AS key, r.name, r.audience, r.description,
|
2026-04-09 08:11:26 +02:00
|
|
|
r.department_id, d.name AS department_name,
|
2026-03-25 22:15:07 +01:00
|
|
|
r.is_active, r.can_approve_requests, r.can_manage_system_settings,
|
|
|
|
|
r.created_at
|
|
|
|
|
FROM roles r
|
|
|
|
|
LEFT JOIN departments d ON d.id = r.department_id
|
|
|
|
|
WHERE r.id = $1
|
|
|
|
|
"#,
|
|
|
|
|
)
|
2026-04-09 08:11:26 +02:00
|
|
|
.bind(id)
|
2026-03-25 22:15:07 +01:00
|
|
|
.fetch_optional(&state.pool)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?
|
|
|
|
|
.ok_or((StatusCode::NOT_FOUND, "Role not found".to_string()))?;
|
|
|
|
|
|
2026-04-09 08:11:26 +02:00
|
|
|
let permission_keys: Vec<String> = sqlx::query_scalar::<_, String>(
|
2026-03-25 22:15:07 +01:00
|
|
|
"SELECT permission_key FROM role_permissions WHERE role_id = $1 ORDER BY permission_key",
|
|
|
|
|
)
|
2026-04-09 08:11:26 +02:00
|
|
|
.bind(id)
|
2026-03-25 22:15:07 +01:00
|
|
|
.fetch_all(&state.pool)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
|
|
|
|
|
|
|
|
|
Ok(Json(RoleDetail {
|
|
|
|
|
id: row.id,
|
|
|
|
|
key: row.key,
|
|
|
|
|
name: row.name,
|
|
|
|
|
audience: row.audience,
|
|
|
|
|
description: row.description,
|
|
|
|
|
department_id: row.department_id,
|
|
|
|
|
department_name: row.department_name,
|
|
|
|
|
is_active: row.is_active,
|
|
|
|
|
can_approve_requests: row.can_approve_requests,
|
|
|
|
|
can_manage_system_settings: row.can_manage_system_settings,
|
|
|
|
|
permission_keys,
|
|
|
|
|
created_at: row.created_at,
|
|
|
|
|
}))
|
2026-03-17 20:42:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn create_role(
|
|
|
|
|
State(state): State<AppState>,
|
|
|
|
|
Json(payload): Json<CreateRolePayload>,
|
|
|
|
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
2026-03-25 22:15:07 +01:00
|
|
|
let is_active = payload.is_active.unwrap_or(true);
|
|
|
|
|
let can_approve = payload.can_approve_requests.unwrap_or(false);
|
|
|
|
|
let can_manage = payload.can_manage_system_settings.unwrap_or(false);
|
|
|
|
|
|
2026-04-09 08:11:26 +02:00
|
|
|
let role = sqlx::query_as::<_, InsertedRoleRow>(
|
2026-03-25 22:15:07 +01:00
|
|
|
r#"
|
2026-04-15 00:16:25 +02:00
|
|
|
INSERT INTO roles (code, name, audience, description, department_id, is_active, can_approve_requests, can_manage_system_settings)
|
2026-03-25 22:15:07 +01:00
|
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
2026-04-15 00:16:25 +02:00
|
|
|
RETURNING id, code AS key, name, audience, description, department_id, is_active, can_approve_requests, can_manage_system_settings, created_at
|
2026-03-25 22:15:07 +01:00
|
|
|
"#,
|
|
|
|
|
)
|
2026-04-09 08:11:26 +02:00
|
|
|
.bind(&payload.key)
|
|
|
|
|
.bind(&payload.name)
|
|
|
|
|
.bind(&payload.audience)
|
|
|
|
|
.bind(&payload.description)
|
|
|
|
|
.bind(payload.department_id)
|
|
|
|
|
.bind(is_active)
|
|
|
|
|
.bind(can_approve)
|
|
|
|
|
.bind(can_manage)
|
2026-03-25 22:15:07 +01:00
|
|
|
.fetch_one(&state.pool)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
|
|
|
|
|
|
|
|
|
// Insert permission keys
|
|
|
|
|
if let Some(keys) = &payload.permission_keys {
|
|
|
|
|
for key in keys {
|
2026-04-09 08:11:26 +02:00
|
|
|
sqlx::query(
|
2026-03-25 22:15:07 +01:00
|
|
|
"INSERT INTO role_permissions (role_id, permission_key) VALUES ($1, $2) ON CONFLICT DO NOTHING",
|
|
|
|
|
)
|
2026-04-09 08:11:26 +02:00
|
|
|
.bind(role.id)
|
|
|
|
|
.bind(key)
|
2026-03-25 22:15:07 +01:00
|
|
|
.execute(&state.pool)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
|
|
|
|
}
|
2026-03-17 20:42:51 +01:00
|
|
|
}
|
2026-03-25 22:15:07 +01:00
|
|
|
|
2026-04-09 08:11:26 +02:00
|
|
|
let permission_keys: Vec<String> = sqlx::query_scalar::<_, String>(
|
2026-03-25 22:15:07 +01:00
|
|
|
"SELECT permission_key FROM role_permissions WHERE role_id = $1 ORDER BY permission_key",
|
|
|
|
|
)
|
2026-04-09 08:11:26 +02:00
|
|
|
.bind(role.id)
|
2026-03-25 22:15:07 +01:00
|
|
|
.fetch_all(&state.pool)
|
|
|
|
|
.await
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
|
StatusCode::CREATED,
|
|
|
|
|
Json(RoleDetail {
|
|
|
|
|
id: role.id,
|
|
|
|
|
key: role.key,
|
|
|
|
|
name: role.name,
|
|
|
|
|
audience: role.audience,
|
|
|
|
|
description: role.description,
|
|
|
|
|
department_id: role.department_id,
|
|
|
|
|
department_name: None,
|
|
|
|
|
is_active: role.is_active,
|
|
|
|
|
can_approve_requests: role.can_approve_requests,
|
|
|
|
|
can_manage_system_settings: role.can_manage_system_settings,
|
|
|
|
|
permission_keys,
|
|
|
|
|
created_at: role.created_at,
|
|
|
|
|
}),
|
|
|
|
|
))
|
2026-03-17 20:42:51 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-25 22:15:07 +01:00
|
|
|
async fn update_role(
|
2026-03-17 20:42:51 +01:00
|
|
|
State(state): State<AppState>,
|
2026-03-25 22:15:07 +01:00
|
|
|
Path(id): Path<Uuid>,
|
|
|
|
|
Json(payload): Json<UpdateRolePayload>,
|
2026-03-17 20:42:51 +01:00
|
|
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
2026-03-25 22:15:07 +01:00
|
|
|
// Fetch current values first
|
2026-04-09 08:11:26 +02:00
|
|
|
let current = sqlx::query_as::<_, CurrentRoleRow>(
|
2026-03-25 22:15:07 +01:00
|
|
|
"SELECT name, description, department_id, is_active, can_approve_requests, can_manage_system_settings FROM roles WHERE id = $1",
|
|
|
|
|
)
|
2026-04-09 08:11:26 +02:00
|
|
|
.bind(id)
|
2026-03-25 22:15:07 +01:00
|
|
|
.fetch_optional(&state.pool)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?
|
|
|
|
|
.ok_or((StatusCode::NOT_FOUND, "Role not found".to_string()))?;
|
|
|
|
|
|
|
|
|
|
let name = payload.name.unwrap_or(current.name);
|
|
|
|
|
let description = payload.description.or(current.description);
|
|
|
|
|
let department_id = payload.department_id.or(current.department_id);
|
|
|
|
|
let is_active = payload.is_active.unwrap_or(current.is_active);
|
|
|
|
|
let can_approve = payload.can_approve_requests.unwrap_or(current.can_approve_requests);
|
|
|
|
|
let can_manage = payload.can_manage_system_settings.unwrap_or(current.can_manage_system_settings);
|
|
|
|
|
|
2026-04-09 08:11:26 +02:00
|
|
|
sqlx::query(
|
2026-03-25 22:15:07 +01:00
|
|
|
r#"
|
|
|
|
|
UPDATE roles SET
|
|
|
|
|
name = $1,
|
|
|
|
|
description = $2,
|
|
|
|
|
department_id = $3,
|
|
|
|
|
is_active = $4,
|
|
|
|
|
can_approve_requests = $5,
|
|
|
|
|
can_manage_system_settings = $6
|
|
|
|
|
WHERE id = $7
|
|
|
|
|
"#,
|
|
|
|
|
)
|
2026-04-09 08:11:26 +02:00
|
|
|
.bind(name)
|
|
|
|
|
.bind(description)
|
|
|
|
|
.bind(department_id)
|
|
|
|
|
.bind(is_active)
|
|
|
|
|
.bind(can_approve)
|
|
|
|
|
.bind(can_manage)
|
|
|
|
|
.bind(id)
|
2026-03-25 22:15:07 +01:00
|
|
|
.execute(&state.pool)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
|
|
|
|
|
|
|
|
|
// Replace permissions if provided
|
|
|
|
|
if let Some(keys) = &payload.permission_keys {
|
2026-04-09 08:11:26 +02:00
|
|
|
sqlx::query("DELETE FROM role_permissions WHERE role_id = $1")
|
|
|
|
|
.bind(id)
|
2026-03-25 22:15:07 +01:00
|
|
|
.execute(&state.pool)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
|
|
|
|
|
|
|
|
|
for key in keys {
|
2026-04-09 08:11:26 +02:00
|
|
|
sqlx::query(
|
2026-03-25 22:15:07 +01:00
|
|
|
"INSERT INTO role_permissions (role_id, permission_key) VALUES ($1, $2) ON CONFLICT DO NOTHING",
|
|
|
|
|
)
|
2026-04-09 08:11:26 +02:00
|
|
|
.bind(id)
|
|
|
|
|
.bind(key)
|
2026-03-25 22:15:07 +01:00
|
|
|
.execute(&state.pool)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
|
|
|
|
}
|
2026-03-17 20:42:51 +01:00
|
|
|
}
|
2026-03-25 22:15:07 +01:00
|
|
|
|
|
|
|
|
// Return updated role
|
|
|
|
|
get_role(State(state), Path(id)).await
|
2026-03-17 20:42:51 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-25 22:15:07 +01:00
|
|
|
async fn delete_role(
|
2026-03-17 20:42:51 +01:00
|
|
|
State(state): State<AppState>,
|
2026-03-25 22:15:07 +01:00
|
|
|
Path(id): Path<Uuid>,
|
2026-03-17 20:42:51 +01:00
|
|
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
2026-04-09 08:11:26 +02:00
|
|
|
let result = sqlx::query("DELETE FROM roles WHERE id = $1")
|
|
|
|
|
.bind(id)
|
2026-03-25 22:15:07 +01:00
|
|
|
.execute(&state.pool)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
|
|
|
|
|
|
|
|
|
if result.rows_affected() == 0 {
|
|
|
|
|
return Err((StatusCode::NOT_FOUND, "Role not found".to_string()));
|
2026-03-17 20:42:51 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-25 22:15:07 +01:00
|
|
|
Ok(StatusCode::NO_CONTENT)
|
|
|
|
|
}
|