use crate::AppState; use db::models::company::CompanyProfile; use db::models::job::Job; use db::models::application::Application; use axum::{ extract::{Path, Query, State}, http::StatusCode, response::IntoResponse, routing::{get, patch}, Json, Router, }; use chrono::{DateTime, Utc}; use contracts::auth_middleware::AuthUser; use serde::{Deserialize, Serialize}; use uuid::Uuid; pub fn router() -> Router { Router::new() .route("/", get(list_companies)) .route("/{id}", get(get_company)) .route("/{id}/approve", patch(approve_company)) .route("/{id}/reject", patch(reject_company)) .route("/{id}/suspend", patch(suspend_company)) .route("/jobs", get(list_jobs)) .route("/applications", get(list_applications)) } #[derive(Deserialize)] #[allow(dead_code)] pub struct ListQuery { pub q: Option, } #[derive(Serialize)] pub struct AdminCompanyRow { pub id: Uuid, pub user_id: Uuid, pub company_name: String, pub registration_number: Option, pub industry: Option, pub status: String, pub created_at: DateTime, pub updated_at: DateTime, } impl From for AdminCompanyRow { fn from(c: CompanyProfile) -> Self { Self { id: c.id, user_id: c.user_id, company_name: c.company_name, registration_number: c.registration_number, industry: c.industry, status: c.status, created_at: c.created_at, updated_at: c.updated_at, } } } #[derive(Serialize)] pub struct AdminJobRow { pub id: Uuid, pub title: String, pub description: Option, pub company_id: Uuid, pub company_name: String, pub location: Option, pub job_type: Option, pub salary_min: Option, pub salary_max: Option, pub status: String, pub applications_count: i64, pub created_at: DateTime, pub updated_at: DateTime, } impl From for AdminJobRow { fn from(j: Job) -> Self { Self { id: j.id, title: j.title, description: Some(j.description), company_id: j.company_id, company_name: String::new(), location: Some(j.location), job_type: Some(j.job_type), salary_min: j.salary_min, salary_max: j.salary_max, status: j.status, applications_count: 0, created_at: j.created_at, updated_at: j.updated_at, } } } #[derive(Serialize)] pub struct AdminApplicationRow { pub id: Uuid, pub job_id: Uuid, pub job_title: String, pub company_id: Uuid, pub company_name: String, pub applicant_id: Uuid, pub applicant_name: String, pub applicant_email: String, pub status: String, pub cover_letter: Option, pub resume_url: Option, pub applied_at: DateTime, pub created_at: DateTime, } impl From for AdminApplicationRow { fn from(a: Application) -> Self { Self { id: a.id, job_id: a.job_id, job_title: String::new(), company_id: Uuid::nil(), company_name: String::new(), applicant_id: a.job_seeker_id, applicant_name: String::new(), applicant_email: String::new(), status: a.status, cover_letter: a.cover_letter, resume_url: a.resume_url, applied_at: a.applied_at, created_at: a.updated_at, } } } async fn list_companies( _auth: AuthUser, State(state): State, Query(_q): Query, ) -> Result { let companies = sqlx::query_as!( CompanyProfile, r#" SELECT id, user_id, company_name, registration_number, industry, website_url, employee_count, business_type, gst_number, contact_name, contact_email, contact_phone, address_line1, city, state, country, postal_code, status, free_job_slots, purchased_job_slots, free_contact_views, purchased_contact_views, created_at, updated_at FROM company_profiles ORDER BY created_at DESC LIMIT 100 "# ) .fetch_all(&state.pool) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?; let list: Vec = companies.into_iter().map(|c| c.into()).collect(); Ok(Json(list)) } async fn get_company( _auth: AuthUser, State(state): State, Path(id): Path, ) -> Result { let company = sqlx::query_as!( CompanyProfile, r#" SELECT id, user_id, company_name, registration_number, industry, website_url, employee_count, business_type, gst_number, contact_name, contact_email, contact_phone, address_line1, city, state, country, postal_code, status, free_job_slots, purchased_job_slots, free_contact_views, purchased_contact_views, created_at, updated_at FROM company_profiles WHERE id = $1 "#, id ) .fetch_optional(&state.pool) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?; match company { Some(c) => Ok(Json(AdminCompanyRow::from(c))), None => Err((StatusCode::NOT_FOUND, "Company not found".to_string())), } } async fn approve_company( _auth: AuthUser, State(state): State, Path(id): Path, ) -> Result { sqlx::query!("UPDATE company_profiles SET status = 'ACTIVE', updated_at = NOW() WHERE id = $1", id) .execute(&state.pool) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?; Ok(Json(serde_json::json!({ "status": "ACTIVE" }))) } async fn reject_company( _auth: AuthUser, State(state): State, Path(id): Path, ) -> Result { sqlx::query!("UPDATE company_profiles SET status = 'REJECTED', updated_at = NOW() WHERE id = $1", id) .execute(&state.pool) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?; Ok(Json(serde_json::json!({ "status": "REJECTED" }))) } async fn suspend_company( _auth: AuthUser, State(state): State, Path(id): Path, ) -> Result { sqlx::query!("UPDATE company_profiles SET status = 'SUSPENDED', updated_at = NOW() WHERE id = $1", id) .execute(&state.pool) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?; Ok(Json(serde_json::json!({ "status": "SUSPENDED" }))) } async fn list_jobs( _auth: AuthUser, State(state): State, Query(_q): Query, ) -> Result { let jobs = sqlx::query_as!( Job, r#" SELECT id, company_id, title, category, description, location, job_type, salary_min, salary_max, experience_years, skills, status, rejection_reason, expires_at, approved_at, approved_by, created_at, updated_at FROM jobs ORDER BY created_at DESC LIMIT 100 "# ) .fetch_all(&state.pool) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?; let list: Vec = jobs.into_iter().map(|j| j.into()).collect(); Ok(Json(list)) } async fn list_applications( _auth: AuthUser, State(state): State, Query(_q): Query, ) -> Result { let applications = sqlx::query_as!( Application, r#" SELECT id, job_id, job_seeker_id, cover_letter, resume_url, status, applied_at, updated_at, contact_viewed FROM applications ORDER BY applied_at DESC LIMIT 100 "# ) .fetch_all(&state.pool) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?; let list: Vec = applications.into_iter().map(|a| a.into()).collect(); Ok(Json(list)) }