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 { 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, } /// GET /api/admin/approvals/submission/{user_id}?roleKey=PHOTOGRAPHER async fn get_submission( auth: AuthUser, State(state): State, Path(user_id): Path, Query(q): Query, ) -> 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, pub limit: Option, } #[derive(Deserialize)] pub struct RejectPayload { pub reason: Option, } /// 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, Path(id): Path, ) -> 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, Path(id): Path, Json(payload): Json, ) -> 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, Path(id): Path, ) -> 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, Path(id): Path, Json(payload): Json, ) -> 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(), } }