//! HTTP handlers for roles. Business rules live in `RoleService`.
use axum::Json;
use axum::extract::{Path, State};
use crate::base::auth::{AdminUser, AuthUser};
use crate::base::error::{AppError, AppResult};
use crate::modules::role::dto::role::{AssignRoleRequest, RoleResponse};
use crate::modules::role::models::role::Role;
use crate::modules::role::services::role_service::RoleService;
/// `GET /roles` (admin only).
pub async fn index(
_admin: AdminUser,
State(service): State<RoleService>,
) -> AppResult<Json<Vec<RoleResponse>>> {
Ok(respond(service.list().await?))
}
/// `GET /users/{id}/roles`: admins can view anyone's roles, users their own.
pub async fn user_roles(
user: AuthUser,
State(service): State<RoleService>,
Path(id): Path<i64>,
) -> AppResult<Json<Vec<RoleResponse>>> {
if user.id != id && !user.is_admin() {
return Err(AppError::Forbidden);
}
Ok(respond(service.roles_of(id).await?))
}
/// `POST /users/{id}/roles` with `{"role": "admin"}` (admin only).
pub async fn assign(
_admin: AdminUser,
State(service): State<RoleService>,
Path(id): Path<i64>,
Json(input): Json<AssignRoleRequest>,
) -> AppResult<Json<Vec<RoleResponse>>> {
Ok(respond(service.assign(id, input.role.trim()).await?))
}
/// `DELETE /users/{id}/roles/{role}` (admin only).
pub async fn revoke(
_admin: AdminUser,
State(service): State<RoleService>,
Path((id, role)): Path<(i64, String)>,
) -> AppResult<Json<Vec<RoleResponse>>> {
Ok(respond(service.revoke(id, &role).await?))
}
fn respond(roles: Vec<Role>) -> Json<Vec<RoleResponse>> {
Json(roles.into_iter().map(RoleResponse::from).collect())
}