260 lines
8.2 KiB
Rust
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(),
|
|
}
|
|
}
|