nxtgauge-backend-rust/apps/users/src/handlers/approvals.rs

260 lines
8.2 KiB
Rust

use crate::AppState;
use axum::{
extract::{Path, Query, State},
http::StatusCode,
response::IntoResponse,
routing::{get, post},
Json, Router,
};
use contracts::auth_middleware::{require_admin, AuthUser};
use db::models::activity_log::ActivityLogRepository;
use db::models::job::JobRepository;
use db::models::requirement::RequirementRepository;
use db::models::user::UserRepository;
use serde::Deserialize;
use uuid::Uuid;
pub fn router() -> Router<AppState> {
Router::new()
.route("/", get(list_pending))
.route("/submission/{user_id}", get(get_submission))
.route("/jobs/{id}/approve", post(approve_job))
.route("/jobs/{id}/reject", post(reject_job))
.route("/requirements/{id}/approve", post(approve_requirement))
.route("/requirements/{id}/reject", post(reject_requirement))
}
#[derive(Deserialize)]
pub struct RoleKeyQuery {
#[serde(rename = "roleKey", alias = "role_key")]
pub role_key: Option<String>,
}
/// GET /api/admin/approvals/submission/{user_id}?roleKey=PHOTOGRAPHER
async fn get_submission(
auth: AuthUser,
State(state): State<AppState>,
Path(user_id): Path<Uuid>,
Query(q): Query<RoleKeyQuery>,
) -> impl IntoResponse {
if let Err(e) = require_admin(&auth) {
return e.into_response();
}
let user = match UserRepository::get_by_id(&state.pool, user_id).await {
Ok(u) => u,
Err(_) => return (StatusCode::NOT_FOUND, "User not found").into_response(),
};
(
StatusCode::OK,
Json(serde_json::json!({
"user": {
"id": user.id,
"name": user.full_name,
"email": user.email,
"phone": user.phone,
"status": user.status,
"email_verified": user.email_verified,
"created_at": user.created_at,
},
"role_key": q.role_key,
"message": "Detailed submission data is now managed via the Verifications system.",
})),
)
.into_response()
}
#[derive(Deserialize)]
pub struct ListQuery {
pub page: Option<i64>,
pub limit: Option<i64>,
}
#[derive(Deserialize)]
pub struct RejectPayload {
pub reason: Option<String>,
}
/// Deprecated: Use /api/admin/verifications instead.
async fn list_pending(
auth: AuthUser,
) -> impl IntoResponse {
if let Err(e) = require_admin(&auth) {
return e.into_response();
}
(
StatusCode::OK,
Json(serde_json::json!({
"message": "This endpoint is deprecated. Please use /api/admin/verifications for profile and job approvals.",
"jobs": [],
"requirements": [],
"profiles_summary": {}
})),
)
.into_response()
}
async fn approve_job(
auth: AuthUser,
State(state): State<AppState>,
Path(id): Path<Uuid>,
) -> impl IntoResponse {
if let Err(e) = require_admin(&auth) {
return e.into_response();
}
let existing = match JobRepository::get_by_id(&state.pool, id).await {
Ok(Some(job)) => job,
Ok(None) => return (StatusCode::NOT_FOUND, "Job not found").into_response(),
Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("{}", e)).into_response(),
};
if existing.status != "PENDING_APPROVAL" {
return (StatusCode::BAD_REQUEST, "Job is not pending approval").into_response();
}
match JobRepository::approve(&state.pool, id, auth.user_id).await {
Ok(job) => {
let _ = ActivityLogRepository::create(
&state.pool,
auth.user_id,
"EMPLOYEE",
id,
"JOB",
"APPROVE",
None,
)
.await;
let company_info = sqlx::query_as::<_, (String, String)>(
"SELECT u.full_name, u.email FROM companies c JOIN users u ON u.id = c.user_id WHERE c.id = $1",
)
.bind(existing.company_id)
.fetch_optional(&state.pool)
.await;
if let Ok(Some((name, email))) = company_info {
let _ = state.mail.send_job_approved_email(&email, &name, &existing.title).await;
}
(StatusCode::OK, Json(job)).into_response()
}
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{}", e)).into_response(),
}
}
async fn reject_job(
auth: AuthUser,
State(state): State<AppState>,
Path(id): Path<Uuid>,
Json(payload): Json<RejectPayload>,
) -> impl IntoResponse {
if let Err(e) = require_admin(&auth) {
return e.into_response();
}
let existing = match JobRepository::get_by_id(&state.pool, id).await {
Ok(Some(job)) => job,
Ok(None) => return (StatusCode::NOT_FOUND, "Job not found").into_response(),
Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("{}", e)).into_response(),
};
if existing.status != "PENDING_APPROVAL" {
return (StatusCode::BAD_REQUEST, "Job is not pending approval").into_response();
}
match JobRepository::reject(&state.pool, id, payload.reason.clone()).await {
Ok(job) => {
let _ = ActivityLogRepository::create(
&state.pool,
auth.user_id,
"EMPLOYEE",
id,
"JOB",
"REJECT",
Some(serde_json::json!({ "reason": payload.reason })),
)
.await;
let company_info = sqlx::query_as::<_, (String, String)>(
"SELECT u.full_name, u.email FROM companies c JOIN users u ON u.id = c.user_id WHERE c.id = $1",
)
.bind(existing.company_id)
.fetch_optional(&state.pool)
.await;
if let Ok(Some((name, email))) = company_info {
let r = payload.reason.as_deref().unwrap_or("Rejected by admin");
let _ = state.mail.send_job_rejected_email(&email, &name, &existing.title, r).await;
}
(StatusCode::OK, Json(job)).into_response()
}
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{}", e)).into_response(),
}
}
async fn approve_requirement(
auth: AuthUser,
State(state): State<AppState>,
Path(id): Path<Uuid>,
) -> impl IntoResponse {
if let Err(e) = require_admin(&auth) {
return e.into_response();
}
let existing = match RequirementRepository::get_by_id(&state.pool, id).await {
Ok(Some(req)) => req,
Ok(None) => return (StatusCode::NOT_FOUND, "Requirement not found").into_response(),
Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("{}", e)).into_response(),
};
if existing.status != "PENDING_APPROVAL" {
return (StatusCode::BAD_REQUEST, "Requirement is not pending approval").into_response();
}
match RequirementRepository::approve(&state.pool, id, auth.user_id).await {
Ok(req) => {
let _ = ActivityLogRepository::create(
&state.pool,
auth.user_id,
"EMPLOYEE",
id,
"REQUIREMENT",
"APPROVE",
None,
)
.await;
(StatusCode::OK, Json(req)).into_response()
}
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{}", e)).into_response(),
}
}
async fn reject_requirement(
auth: AuthUser,
State(state): State<AppState>,
Path(id): Path<Uuid>,
Json(payload): Json<RejectPayload>,
) -> impl IntoResponse {
if let Err(e) = require_admin(&auth) {
return e.into_response();
}
match RequirementRepository::reject(&state.pool, id, payload.reason.clone()).await {
Ok(req) => {
let _ = ActivityLogRepository::create(
&state.pool,
auth.user_id,
"EMPLOYEE",
id,
"REQUIREMENT",
"REJECT",
Some(serde_json::json!({ "reason": payload.reason })),
)
.await;
(StatusCode::OK, Json(req)).into_response()
}
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{}", e)).into_response(),
}
}