chore: checkpoint current workspace changes
This commit is contained in:
parent
ac27184ae2
commit
b82f294331
10 changed files with 462 additions and 41 deletions
|
|
@ -17,12 +17,12 @@ pub fn router() -> Router<PgPool> {
|
|||
Router::new()
|
||||
.route("/profile/me", get(get_profile).patch(update_profile))
|
||||
.route("/jobs", get(list_jobs).post(create_job))
|
||||
.route("/jobs/:id", get(get_job).patch(update_job))
|
||||
.route("/jobs/:id/submit", post(submit_job))
|
||||
.route("/jobs/:id/close", post(close_job))
|
||||
.route("/jobs/:id/applications", get(list_applications))
|
||||
.route("/applications/:id/status", patch(update_application_status))
|
||||
.route("/applications/:id/contact", get(view_contact))
|
||||
.route("/jobs/{id}", get(get_job).patch(update_job))
|
||||
.route("/jobs/{id}/submit", post(submit_job))
|
||||
.route("/jobs/{id}/close", post(close_job))
|
||||
.route("/jobs/{id}/applications", get(list_applications))
|
||||
.route("/applications/{id}/status", patch(update_application_status))
|
||||
.route("/applications/{id}/contact", get(view_contact))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ pub fn router() -> Router<PgPool> {
|
|||
Router::new()
|
||||
.route("/profile/me", get(get_profile).patch(update_profile))
|
||||
.route("/requirements", get(list_requirements).post(create_requirement))
|
||||
.route("/requirements/:id", get(get_requirement).patch(update_requirement))
|
||||
.route("/requirements/:id/submit", post(submit_requirement))
|
||||
.route("/requirements/:id/requests", get(list_requests))
|
||||
.route("/requirements/:id/requests/:lead_id/approve", post(approve_request))
|
||||
.route("/requirements/:id/requests/:lead_id/reject", post(reject_request))
|
||||
.route("/requirements/{id}", get(get_requirement).patch(update_requirement))
|
||||
.route("/requirements/{id}/submit", post(submit_requirement))
|
||||
.route("/requirements/{id}/requests", get(list_requests))
|
||||
.route("/requirements/{id}/requests/{lead_id}/approve", post(approve_request))
|
||||
.route("/requirements/{id}/requests/{lead_id}/reject", post(reject_request))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ impl Services {
|
|||
|| path.starts_with("/api/runtime-config")
|
||||
|| path.starts_with("/api/config")
|
||||
|| path.starts_with("/api/admin/roles")
|
||||
|| path.starts_with("/api/admin/permissions")
|
||||
|| path.starts_with("/api/admin/onboarding-config")
|
||||
|| path.starts_with("/api/admin/dashboard-config")
|
||||
|| path.starts_with("/api/admin/users")
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@ pub mod auth;
|
|||
pub mod config;
|
||||
pub mod notifications;
|
||||
pub mod onboarding;
|
||||
pub mod permissions;
|
||||
pub mod roles;
|
||||
pub mod user_roles;
|
||||
|
|
|
|||
79
apps/users/src/handlers/permissions.rs
Normal file
79
apps/users/src/handlers/permissions.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
use crate::AppState;
|
||||
use axum::{http::StatusCode, response::IntoResponse, routing::get, Json, Router};
|
||||
use serde::Serialize;
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new().route("/", get(list_permissions))
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct PermissionEntry {
|
||||
key: String,
|
||||
module: String,
|
||||
action: String,
|
||||
}
|
||||
|
||||
const MODULES: &[&str] = &[
|
||||
"Department Management",
|
||||
"Designation Management",
|
||||
"Internal Role Management",
|
||||
"Employee Management",
|
||||
"External Role Management",
|
||||
"External Onboarding Management",
|
||||
"Internal Dashboard Management",
|
||||
"External Dashboard Management",
|
||||
"Verification Management",
|
||||
"Approval Management",
|
||||
"Users Management",
|
||||
"Company Management",
|
||||
"Candidate Management",
|
||||
"Customer Management",
|
||||
"Photographer Management",
|
||||
"Makeup Artist Management",
|
||||
"Tutor Management",
|
||||
"Developer Management",
|
||||
"Fitness Trainer Management",
|
||||
"Graphic Designer Management",
|
||||
"Social Media Management",
|
||||
"Video Editor Management",
|
||||
"Catering Services Management",
|
||||
"Jobs Management",
|
||||
"Leads Management",
|
||||
"Applications Management",
|
||||
"Responses Management",
|
||||
"Review Management",
|
||||
"Pricing Management",
|
||||
"Credit Management",
|
||||
"Coupon Management",
|
||||
"Discount Management",
|
||||
"Tax Management",
|
||||
"Order Management",
|
||||
"Invoice Management",
|
||||
"Ledger Management",
|
||||
"Knowledge Base Management",
|
||||
"Support Management",
|
||||
"Report Management",
|
||||
"Notifications",
|
||||
];
|
||||
|
||||
const ACTIONS: &[&str] = &["View", "Create", "Update", "Delete"];
|
||||
|
||||
async fn list_permissions(
|
||||
_: axum::extract::State<AppState>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
let permissions: Vec<PermissionEntry> = MODULES
|
||||
.iter()
|
||||
.flat_map(|module| {
|
||||
ACTIONS.iter().map(|action| {
|
||||
let key = format!("{}:{}", module.replace(' ', "_").to_lowercase(), action.to_lowercase());
|
||||
PermissionEntry {
|
||||
key,
|
||||
module: module.to_string(),
|
||||
action: action.to_string(),
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(permissions))
|
||||
}
|
||||
|
|
@ -1,55 +1,376 @@
|
|||
use crate::AppState;
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
extract::{Path, Query, State},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
routing::{get, post},
|
||||
routing::{delete, get, patch, post},
|
||||
Json, Router,
|
||||
};
|
||||
use db::models::role::{CreateRolePayload, RoleRepository};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(list_roles).post(create_role))
|
||||
.route("/{key}", get(get_role_by_key))
|
||||
.route("/:id", get(get_role).patch(update_role).delete(delete_role))
|
||||
}
|
||||
|
||||
// ── 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>>,
|
||||
}
|
||||
|
||||
// ── 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();
|
||||
let audience = params.audience.as_deref().unwrap_or("");
|
||||
|
||||
let rows = sqlx::query!(
|
||||
r#"
|
||||
SELECT
|
||||
r.id,
|
||||
r.key,
|
||||
r.name,
|
||||
r.audience,
|
||||
r.description,
|
||||
r.department_id,
|
||||
d.name AS department_name,
|
||||
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
|
||||
LEFT JOIN employees e ON e.role_id = r.id
|
||||
LEFT JOIN role_permissions rp ON rp.role_id = r.id
|
||||
WHERE ($1 = '' OR r.audience = $1)
|
||||
AND ($2 = '' OR LOWER(r.name) LIKE '%' || $2 || '%' OR LOWER(r.key) LIKE '%' || $2 || '%')
|
||||
GROUP BY r.id, d.name
|
||||
ORDER BY r.created_at DESC
|
||||
LIMIT $3 OFFSET $4
|
||||
"#,
|
||||
audience,
|
||||
search,
|
||||
per_page,
|
||||
offset
|
||||
)
|
||||
.fetch_all(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
|
||||
let total: i64 = sqlx::query_scalar!(
|
||||
r#"
|
||||
SELECT COUNT(*) FROM roles r
|
||||
WHERE ($1 = '' OR r.audience = $1)
|
||||
AND ($2 = '' OR LOWER(r.name) LIKE '%' || $2 || '%' OR LOWER(r.key) LIKE '%' || $2 || '%')
|
||||
"#,
|
||||
audience,
|
||||
search
|
||||
)
|
||||
.fetch_one(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?
|
||||
.unwrap_or(0);
|
||||
|
||||
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)> {
|
||||
let row = sqlx::query!(
|
||||
r#"
|
||||
SELECT
|
||||
r.id, r.key, r.name, r.audience, r.description,
|
||||
r.department_id, d.name AS department_name,
|
||||
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
|
||||
"#,
|
||||
id
|
||||
)
|
||||
.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 permission_keys: Vec<String> = sqlx::query_scalar!(
|
||||
"SELECT permission_key FROM role_permissions WHERE role_id = $1 ORDER BY permission_key",
|
||||
id
|
||||
)
|
||||
.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,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn create_role(
|
||||
State(state): State<AppState>,
|
||||
Json(payload): Json<CreateRolePayload>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
match RoleRepository::create(&state.pool, payload).await {
|
||||
Ok(role) => Ok((StatusCode::CREATED, Json(role))),
|
||||
Err(e) => Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Database error: {}", e),
|
||||
)),
|
||||
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);
|
||||
|
||||
let role = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO roles (key, name, audience, description, department_id, is_active, can_approve_requests, can_manage_system_settings)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING id, key, name, audience, description, department_id, is_active, can_approve_requests, can_manage_system_settings, created_at
|
||||
"#,
|
||||
payload.key,
|
||||
payload.name,
|
||||
payload.audience,
|
||||
payload.description,
|
||||
payload.department_id,
|
||||
is_active,
|
||||
can_approve,
|
||||
can_manage,
|
||||
)
|
||||
.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 {
|
||||
sqlx::query!(
|
||||
"INSERT INTO role_permissions (role_id, permission_key) VALUES ($1, $2) ON CONFLICT DO NOTHING",
|
||||
role.id,
|
||||
key
|
||||
)
|
||||
.execute(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
}
|
||||
}
|
||||
|
||||
let permission_keys: Vec<String> = sqlx::query_scalar!(
|
||||
"SELECT permission_key FROM role_permissions WHERE role_id = $1 ORDER BY permission_key",
|
||||
role.id
|
||||
)
|
||||
.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,
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
async fn list_roles(
|
||||
async fn update_role(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
Json(payload): Json<UpdateRolePayload>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
match RoleRepository::get_all(&state.pool).await {
|
||||
Ok(roles) => Ok((StatusCode::OK, Json(roles))),
|
||||
Err(e) => Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Database error: {}", e),
|
||||
)),
|
||||
// Fetch current values first
|
||||
let current = sqlx::query!(
|
||||
"SELECT name, description, department_id, is_active, can_approve_requests, can_manage_system_settings FROM roles WHERE id = $1",
|
||||
id
|
||||
)
|
||||
.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);
|
||||
|
||||
sqlx::query!(
|
||||
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
|
||||
"#,
|
||||
name,
|
||||
description,
|
||||
department_id,
|
||||
is_active,
|
||||
can_approve,
|
||||
can_manage,
|
||||
id
|
||||
)
|
||||
.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 {
|
||||
sqlx::query!("DELETE FROM role_permissions WHERE role_id = $1", id)
|
||||
.execute(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
|
||||
for key in keys {
|
||||
sqlx::query!(
|
||||
"INSERT INTO role_permissions (role_id, permission_key) VALUES ($1, $2) ON CONFLICT DO NOTHING",
|
||||
id,
|
||||
key
|
||||
)
|
||||
.execute(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
}
|
||||
}
|
||||
|
||||
// Return updated role
|
||||
get_role(State(state), Path(id)).await
|
||||
}
|
||||
|
||||
async fn get_role_by_key(
|
||||
async fn delete_role(
|
||||
State(state): State<AppState>,
|
||||
Path(key): Path<String>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
match RoleRepository::get_by_key(&state.pool, &key).await {
|
||||
Ok(role) => Ok((StatusCode::OK, Json(role))),
|
||||
Err(sqlx::Error::RowNotFound) => Err((StatusCode::NOT_FOUND, "Role not found".to_string())),
|
||||
Err(e) => Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Database error: {}", e),
|
||||
)),
|
||||
}
|
||||
}
|
||||
let result = sqlx::query!("DELETE FROM roles WHERE id = $1", id)
|
||||
.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()));
|
||||
}
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ async fn main() {
|
|||
.nest("/api/auth", handlers::auth::router())
|
||||
// ── Roles & User Self-Service ─────────────────────────────────────
|
||||
.nest("/api/admin/roles", handlers::roles::router())
|
||||
.nest("/api/admin/permissions", handlers::permissions::router())
|
||||
.nest("/api/me/roles", handlers::user_roles::router())
|
||||
// ── Notifications ─────────────────────────────────────────────────
|
||||
.nest("/api/me/notifications", handlers::notifications::router())
|
||||
|
|
|
|||
|
|
@ -134,9 +134,16 @@ pub fn require_role(auth: &AuthUser, expected_role: &str) -> Result<(), AuthErro
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns Ok if the user has the ADMIN role.
|
||||
/// Returns Ok if the user has an internal admin role (ADMIN or SUPER_ADMIN).
|
||||
pub fn require_admin(auth: &AuthUser) -> Result<(), AuthError> {
|
||||
if auth.claims.roles.contains(&"ADMIN".to_string()) {
|
||||
let active = auth.claims.active_role.as_str();
|
||||
let has_internal_admin =
|
||||
active == "ADMIN"
|
||||
|| active == "SUPER_ADMIN"
|
||||
|| auth.claims.roles.contains(&"ADMIN".to_string())
|
||||
|| auth.claims.roles.contains(&"SUPER_ADMIN".to_string());
|
||||
|
||||
if has_internal_admin {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(AuthError::InsufficientPermissions)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
ALTER TABLE roles
|
||||
DROP COLUMN IF EXISTS description,
|
||||
DROP COLUMN IF EXISTS department_id,
|
||||
DROP COLUMN IF EXISTS can_approve_requests,
|
||||
DROP COLUMN IF EXISTS can_manage_system_settings;
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
-- Extend roles table for internal role management
|
||||
ALTER TABLE roles
|
||||
ADD COLUMN IF NOT EXISTS description TEXT,
|
||||
ADD COLUMN IF NOT EXISTS department_id UUID REFERENCES departments(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS can_approve_requests BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS can_manage_system_settings BOOLEAN NOT NULL DEFAULT false;
|
||||
Loading…
Add table
Reference in a new issue