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 { 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, pub limit: Option, } #[derive(Deserialize)] pub struct RejectPayload { pub reason: Option, } async fn list_pending( auth: AuthUser, State(state): State, Query(q): Query, ) -> 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, Path(user_id): Path, ) -> 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, Path(user_id): Path, ) -> 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, Path(user_id): Path, ) -> 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, Path(user_id): Path, ) -> 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, 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, Path((role_key, user_id)): Path<(String, Uuid)>, Json(payload): Json, ) -> 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, 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, 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, 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, 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, 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, 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, Path(id): Path, Json(payload): Json, ) -> 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(), } }