- Remove duplicate departments/designations/employees handlers from users service (already in employees service) - Fix all 9 profession admin handlers to use correct DB schema (display_name, bio, location, custom_data) - Fix companies admin handler to match CompanyProfile DB model with all fields - Fix customers admin handler to match Requirement model with preferred_date - Fix missing serde_json imports and type annotations in admin handlers - Add #[allow(dead_code)] for intentionally unused structs/fields - Add test infrastructure: auth crypto tests (2 passing), test directory structure - Zero compilation warnings across all services
269 lines
8.4 KiB
Rust
269 lines
8.4 KiB
Rust
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<AppState> {
|
|
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<String>,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub struct AdminCompanyRow {
|
|
pub id: Uuid,
|
|
pub user_id: Uuid,
|
|
pub company_name: String,
|
|
pub registration_number: Option<String>,
|
|
pub industry: Option<String>,
|
|
pub status: String,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
}
|
|
|
|
impl From<CompanyProfile> 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<String>,
|
|
pub company_id: Uuid,
|
|
pub company_name: String,
|
|
pub location: Option<String>,
|
|
pub job_type: Option<String>,
|
|
pub salary_min: Option<i32>,
|
|
pub salary_max: Option<i32>,
|
|
pub status: String,
|
|
pub applications_count: i64,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
}
|
|
|
|
impl From<Job> 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<String>,
|
|
pub resume_url: Option<String>,
|
|
pub applied_at: DateTime<Utc>,
|
|
pub created_at: DateTime<Utc>,
|
|
}
|
|
|
|
impl From<Application> 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<AppState>,
|
|
Query(_q): Query<ListQuery>,
|
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
|
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<AdminCompanyRow> = companies.into_iter().map(|c| c.into()).collect();
|
|
Ok(Json(list))
|
|
}
|
|
|
|
async fn get_company(
|
|
_auth: AuthUser,
|
|
State(state): State<AppState>,
|
|
Path(id): Path<Uuid>,
|
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
|
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<AppState>,
|
|
Path(id): Path<Uuid>,
|
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
|
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<AppState>,
|
|
Path(id): Path<Uuid>,
|
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
|
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<AppState>,
|
|
Path(id): Path<Uuid>,
|
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
|
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<AppState>,
|
|
Query(_q): Query<ListQuery>,
|
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
|
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<AdminJobRow> = jobs.into_iter().map(|j| j.into()).collect();
|
|
Ok(Json(list))
|
|
}
|
|
|
|
async fn list_applications(
|
|
_auth: AuthUser,
|
|
State(state): State<AppState>,
|
|
Query(_q): Query<ListQuery>,
|
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
|
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<AdminApplicationRow> = applications.into_iter().map(|a| a.into()).collect();
|
|
Ok(Json(list))
|
|
}
|