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

575 lines
18 KiB
Rust
Raw Normal View History

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::job::JobRepository;
use db::models::requirement::RequirementRepository;
use serde::Deserialize;
use uuid::Uuid;
pub fn router() -> Router<AppState> {
Router::new()
.route("/", get(list_pending))
.route("/profiles/company/:user_id/approve", post(approve_company_profile))
.route("/profiles/company/:user_id/reject", post(reject_company_profile))
.route("/profiles/customer/:user_id/approve", post(approve_customer_profile))
.route("/profiles/customer/:user_id/reject", post(reject_customer_profile))
.route("/profiles/professional/:role_key/:user_id/approve", post(approve_professional_profile))
.route("/profiles/professional/:role_key/:user_id/reject", post(reject_professional_profile))
.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 ListQuery {
pub page: Option<i64>,
pub limit: Option<i64>,
}
#[derive(Deserialize)]
pub struct RejectPayload {
pub reason: Option<String>,
}
async fn list_pending(
auth: AuthUser,
State(state): State<AppState>,
Query(q): Query<ListQuery>,
) -> impl IntoResponse {
if let Err(e) = require_admin(&auth) {
return e.into_response();
}
let page = q.page.unwrap_or(1);
let limit = q.limit.unwrap_or(20);
let offset = (page - 1) * limit;
let jobs = sqlx::query_as!(
db::models::job::Job,
r#"
SELECT *
FROM jobs
WHERE status = 'PENDING_APPROVAL'
ORDER BY created_at ASC
LIMIT $1 OFFSET $2
"#,
limit,
offset
)
.fetch_all(&state.pool)
.await;
let requirements = sqlx::query_as!(
db::models::requirement::Requirement,
r#"
SELECT *
FROM requirements
WHERE status = 'PENDING_APPROVAL'
ORDER BY created_at ASC
LIMIT $1 OFFSET $2
"#,
limit,
offset
)
.fetch_all(&state.pool)
.await;
let company_profiles = sqlx::query!(
r#"
SELECT user_id, status, updated_at
FROM company_profiles
WHERE status = 'PENDING'
ORDER BY updated_at ASC
LIMIT $1 OFFSET $2
"#,
limit,
offset
)
.fetch_all(&state.pool)
.await;
let customer_profiles = sqlx::query!(
r#"
SELECT user_id, status, updated_at
FROM customer_profiles
WHERE status = 'PENDING'
ORDER BY updated_at ASC
LIMIT $1 OFFSET $2
"#,
limit,
offset
)
.fetch_all(&state.pool)
.await;
let photographer_profiles = sqlx::query!(
r#"
SELECT user_id, status, updated_at
FROM photographer_profiles
WHERE status = 'PENDING'
ORDER BY updated_at ASC
LIMIT $1 OFFSET $2
"#,
limit,
offset
)
.fetch_all(&state.pool)
.await;
let makeup_profiles = sqlx::query!(
r#"
SELECT user_id, status, updated_at
FROM makeup_artist_profiles
WHERE status = 'PENDING'
ORDER BY updated_at ASC
LIMIT $1 OFFSET $2
"#,
limit,
offset
)
.fetch_all(&state.pool)
.await;
let tutor_profiles = sqlx::query!(
r#"
SELECT user_id, status, updated_at
FROM tutor_profiles
WHERE status = 'PENDING'
ORDER BY updated_at ASC
LIMIT $1 OFFSET $2
"#,
limit,
offset
)
.fetch_all(&state.pool)
.await;
let developer_profiles = sqlx::query!(
r#"
SELECT user_id, status, updated_at
FROM developer_profiles
WHERE status = 'PENDING'
ORDER BY updated_at ASC
LIMIT $1 OFFSET $2
"#,
limit,
offset
)
.fetch_all(&state.pool)
.await;
let video_editor_profiles = sqlx::query!(
r#"
SELECT user_id, status, updated_at
FROM video_editor_profiles
WHERE status = 'PENDING'
ORDER BY updated_at ASC
LIMIT $1 OFFSET $2
"#,
limit,
offset
)
.fetch_all(&state.pool)
.await;
let graphic_designer_profiles = sqlx::query!(
r#"
SELECT user_id, status, updated_at
FROM graphic_designer_profiles
WHERE status = 'PENDING'
ORDER BY updated_at ASC
LIMIT $1 OFFSET $2
"#,
limit,
offset
)
.fetch_all(&state.pool)
.await;
let social_media_manager_profiles = sqlx::query!(
r#"
SELECT user_id, status, updated_at
FROM social_media_manager_profiles
WHERE status = 'PENDING'
ORDER BY updated_at ASC
LIMIT $1 OFFSET $2
"#,
limit,
offset
)
.fetch_all(&state.pool)
.await;
let fitness_trainer_profiles = sqlx::query!(
r#"
SELECT user_id, status, updated_at
FROM fitness_trainer_profiles
WHERE status = 'PENDING'
ORDER BY updated_at ASC
LIMIT $1 OFFSET $2
"#,
limit,
offset
)
.fetch_all(&state.pool)
.await;
let catering_profiles = sqlx::query!(
r#"
SELECT user_id, status, updated_at
FROM catering_service_profiles
WHERE status = 'PENDING'
ORDER BY updated_at ASC
LIMIT $1 OFFSET $2
"#,
limit,
offset
)
.fetch_all(&state.pool)
.await;
match (
jobs,
requirements,
company_profiles,
customer_profiles,
photographer_profiles,
makeup_profiles,
tutor_profiles,
developer_profiles,
video_editor_profiles,
graphic_designer_profiles,
social_media_manager_profiles,
fitness_trainer_profiles,
catering_profiles,
) {
(
Ok(jobs),
Ok(requirements),
Ok(company_profiles),
Ok(customer_profiles),
Ok(photographer_profiles),
Ok(makeup_profiles),
Ok(tutor_profiles),
Ok(developer_profiles),
Ok(video_editor_profiles),
Ok(graphic_designer_profiles),
Ok(social_media_manager_profiles),
Ok(fitness_trainer_profiles),
Ok(catering_profiles),
) => (
StatusCode::OK,
Json(serde_json::json!({
"jobs": jobs,
"requirements": requirements,
"profiles": {
"company": company_profiles,
"customer": customer_profiles,
"photographer": photographer_profiles,
"makeup_artist": makeup_profiles,
"tutor": tutor_profiles,
"developer": developer_profiles,
"video_editor": video_editor_profiles,
"graphic_designer": graphic_designer_profiles,
"social_media_manager": social_media_manager_profiles,
"fitness_trainer": fitness_trainer_profiles,
"catering_services": catering_profiles
},
"pagination": { "page": page, "limit": limit }
})),
)
.into_response(),
(Err(e), _, _, _, _, _, _, _, _, _, _, _, _)
| (_, Err(e), _, _, _, _, _, _, _, _, _, _, _)
| (_, _, Err(e), _, _, _, _, _, _, _, _, _, _)
| (_, _, _, Err(e), _, _, _, _, _, _, _, _, _)
| (_, _, _, _, Err(e), _, _, _, _, _, _, _, _)
| (_, _, _, _, _, Err(e), _, _, _, _, _, _, _)
| (_, _, _, _, _, _, Err(e), _, _, _, _, _, _)
| (_, _, _, _, _, _, _, Err(e), _, _, _, _, _)
| (_, _, _, _, _, _, _, _, Err(e), _, _, _, _)
| (_, _, _, _, _, _, _, _, _, Err(e), _, _, _)
| (_, _, _, _, _, _, _, _, _, _, Err(e), _, _)
| (_, _, _, _, _, _, _, _, _, _, _, Err(e), _)
| (_, _, _, _, _, _, _, _, _, _, _, _, Err(e)) => {
(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response()
}
}
}
async fn approve_company_profile(
auth: AuthUser,
State(state): State<AppState>,
Path(user_id): Path<Uuid>,
) -> impl IntoResponse {
if let Err(e) = require_admin(&auth) {
return e.into_response();
}
match sqlx::query!(
"UPDATE company_profiles SET status = 'APPROVED', updated_at = NOW() WHERE user_id = $1",
user_id
)
.execute(&state.pool)
.await
{
Ok(result) if result.rows_affected() > 0 => {
(StatusCode::OK, Json(serde_json::json!({ "user_id": user_id, "status": "APPROVED" }))).into_response()
}
Ok(_) => (StatusCode::NOT_FOUND, "Company profile not found").into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
}
async fn reject_company_profile(
auth: AuthUser,
State(state): State<AppState>,
Path(user_id): Path<Uuid>,
) -> impl IntoResponse {
if let Err(e) = require_admin(&auth) {
return e.into_response();
}
match sqlx::query!(
"UPDATE company_profiles SET status = 'REJECTED', updated_at = NOW() WHERE user_id = $1",
user_id
)
.execute(&state.pool)
.await
{
Ok(result) if result.rows_affected() > 0 => {
(StatusCode::OK, Json(serde_json::json!({ "user_id": user_id, "status": "REJECTED" }))).into_response()
}
Ok(_) => (StatusCode::NOT_FOUND, "Company profile not found").into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
}
async fn approve_customer_profile(
auth: AuthUser,
State(state): State<AppState>,
Path(user_id): Path<Uuid>,
) -> impl IntoResponse {
if let Err(e) = require_admin(&auth) {
return e.into_response();
}
match sqlx::query!(
"UPDATE customer_profiles SET status = 'APPROVED', updated_at = NOW() WHERE user_id = $1",
user_id
)
.execute(&state.pool)
.await
{
Ok(result) if result.rows_affected() > 0 => {
(StatusCode::OK, Json(serde_json::json!({ "user_id": user_id, "status": "APPROVED" }))).into_response()
}
Ok(_) => (StatusCode::NOT_FOUND, "Customer profile not found").into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
}
async fn reject_customer_profile(
auth: AuthUser,
State(state): State<AppState>,
Path(user_id): Path<Uuid>,
) -> impl IntoResponse {
if let Err(e) = require_admin(&auth) {
return e.into_response();
}
match sqlx::query!(
"UPDATE customer_profiles SET status = 'REJECTED', updated_at = NOW() WHERE user_id = $1",
user_id
)
.execute(&state.pool)
.await
{
Ok(result) if result.rows_affected() > 0 => {
(StatusCode::OK, Json(serde_json::json!({ "user_id": user_id, "status": "REJECTED" }))).into_response()
}
Ok(_) => (StatusCode::NOT_FOUND, "Customer profile not found").into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
}
fn professional_profile_table(role_key: &str) -> Option<&'static str> {
match role_key {
"PHOTOGRAPHER" => Some("photographer_profiles"),
"MAKEUP_ARTIST" => Some("makeup_artist_profiles"),
"TUTOR" => Some("tutor_profiles"),
"DEVELOPER" => Some("developer_profiles"),
"VIDEO_EDITOR" => Some("video_editor_profiles"),
"GRAPHIC_DESIGNER" => Some("graphic_designer_profiles"),
"SOCIAL_MEDIA_MANAGER" => Some("social_media_manager_profiles"),
"FITNESS_TRAINER" => Some("fitness_trainer_profiles"),
"CATERING_SERVICES" => Some("catering_service_profiles"),
_ => None,
}
}
async fn approve_professional_profile(
auth: AuthUser,
State(state): State<AppState>,
Path((role_key, user_id)): Path<(String, Uuid)>,
) -> impl IntoResponse {
if let Err(e) = require_admin(&auth) {
return e.into_response();
}
let role_key = role_key.to_uppercase();
let Some(table) = professional_profile_table(&role_key) else {
return (StatusCode::BAD_REQUEST, "Unsupported professional role_key").into_response();
};
let query = format!(
"UPDATE {} SET status = 'APPROVED', rejection_reason = NULL, approved_at = NOW(), updated_at = NOW() WHERE user_id = $1",
table
);
match sqlx::query(&query).bind(user_id).execute(&state.pool).await {
Ok(result) if result.rows_affected() > 0 => (
StatusCode::OK,
Json(serde_json::json!({ "user_id": user_id, "role_key": role_key, "status": "APPROVED" })),
)
.into_response(),
Ok(_) => (StatusCode::NOT_FOUND, "Professional profile not found").into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
}
async fn reject_professional_profile(
auth: AuthUser,
State(state): State<AppState>,
Path((role_key, user_id)): Path<(String, Uuid)>,
Json(payload): Json<RejectPayload>,
) -> impl IntoResponse {
if let Err(e) = require_admin(&auth) {
return e.into_response();
}
let role_key = role_key.to_uppercase();
let Some(table) = professional_profile_table(&role_key) else {
return (StatusCode::BAD_REQUEST, "Unsupported professional role_key").into_response();
};
let query = format!(
"UPDATE {} SET status = 'REJECTED', rejection_reason = $2, updated_at = NOW() WHERE user_id = $1",
table
);
match sqlx::query(&query)
.bind(user_id)
.bind(payload.reason.unwrap_or_else(|| "Profile rejected".to_string()))
.execute(&state.pool)
.await
{
Ok(result) if result.rows_affected() > 0 => (
StatusCode::OK,
Json(serde_json::json!({ "user_id": user_id, "role_key": role_key, "status": "REJECTED" })),
)
.into_response(),
Ok(_) => (StatusCode::NOT_FOUND, "Professional profile not found").into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).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, e.to_string()).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) => (StatusCode::OK, Json(job)).into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).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, e.to_string()).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).await {
Ok(job) => (StatusCode::OK, Json(job)).into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).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, e.to_string()).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) => (StatusCode::OK, Json(req)).into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).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();
}
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, e.to_string()).into_response(),
};
if existing.status != "PENDING_APPROVAL" {
return (StatusCode::BAD_REQUEST, "Requirement is not pending approval").into_response();
}
match RequirementRepository::reject(&state.pool, id, payload.reason).await {
Ok(req) => (StatusCode::OK, Json(req)).into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
}