2026-04-02 13:09:43 +02:00
|
|
|
use crate::AppState;
|
|
|
|
|
use axum::{
|
|
|
|
|
extract::{Path, Query, State},
|
|
|
|
|
http::StatusCode,
|
|
|
|
|
response::IntoResponse,
|
2026-04-06 06:19:10 +02:00
|
|
|
routing::{get, patch},
|
2026-04-02 13:09:43 +02:00
|
|
|
Json, Router,
|
|
|
|
|
};
|
|
|
|
|
use contracts::auth_middleware::AuthUser;
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
|
|
pub fn router() -> Router<AppState> {
|
|
|
|
|
Router::new()
|
|
|
|
|
.route("/", get(list_companies))
|
2026-04-06 06:19:10 +02:00
|
|
|
.route("/{id}", get(get_company))
|
|
|
|
|
.route("/{id}/approve", patch(approve_company))
|
|
|
|
|
.route("/{id}/reject", patch(reject_company))
|
|
|
|
|
.route("/{id}/suspend", patch(suspend_company))
|
2026-04-02 13:09:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
|
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: chrono::DateTime<chrono::Utc>,
|
|
|
|
|
pub updated_at: chrono::DateTime<chrono::Utc>,
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 06:19:10 +02:00
|
|
|
#[derive(Serialize)]
|
|
|
|
|
pub struct AdminCompanyDetail {
|
|
|
|
|
pub id: Uuid,
|
|
|
|
|
pub user_id: Uuid,
|
|
|
|
|
pub company_name: String,
|
|
|
|
|
pub registration_number: Option<String>,
|
|
|
|
|
pub industry: Option<String>,
|
|
|
|
|
pub website_url: Option<String>,
|
|
|
|
|
pub employee_count: Option<i32>,
|
|
|
|
|
pub business_type: Option<String>,
|
|
|
|
|
pub gst_number: Option<String>,
|
|
|
|
|
pub contact_name: Option<String>,
|
|
|
|
|
pub contact_email: Option<String>,
|
|
|
|
|
pub contact_phone: Option<String>,
|
|
|
|
|
pub address_line1: Option<String>,
|
|
|
|
|
pub city: Option<String>,
|
|
|
|
|
pub state: Option<String>,
|
|
|
|
|
pub country: String,
|
|
|
|
|
pub postal_code: Option<String>,
|
|
|
|
|
pub status: String,
|
|
|
|
|
pub free_job_slots: i32,
|
|
|
|
|
pub purchased_job_slots: i32,
|
|
|
|
|
pub free_contact_views: i32,
|
|
|
|
|
pub purchased_contact_views: i32,
|
|
|
|
|
pub created_at: chrono::DateTime<chrono::Utc>,
|
|
|
|
|
pub updated_at: chrono::DateTime<chrono::Utc>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
|
pub struct ApproveRejectRequest {
|
|
|
|
|
pub reason: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 13:09:43 +02:00
|
|
|
async fn list_companies(
|
|
|
|
|
_auth: AuthUser,
|
|
|
|
|
State(state): State<AppState>,
|
|
|
|
|
Query(q): Query<ListQuery>,
|
|
|
|
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
|
|
|
|
let search = q.q.as_deref().unwrap_or_default().to_lowercase();
|
|
|
|
|
|
|
|
|
|
let companies = sqlx::query_as!(
|
|
|
|
|
AdminCompanyRow,
|
|
|
|
|
r#"
|
|
|
|
|
SELECT
|
|
|
|
|
id, user_id, company_name, registration_number, industry,
|
|
|
|
|
status, created_at, updated_at
|
|
|
|
|
FROM company_profiles
|
|
|
|
|
WHERE ($1 = '' OR LOWER(company_name) LIKE '%' || $1 || '%')
|
|
|
|
|
ORDER BY created_at DESC
|
|
|
|
|
LIMIT 100
|
|
|
|
|
"#,
|
|
|
|
|
search
|
|
|
|
|
)
|
|
|
|
|
.fetch_all(&state.pool)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
|
|
|
|
|
|
|
|
|
Ok(Json(companies))
|
|
|
|
|
}
|
2026-04-06 06:19:10 +02:00
|
|
|
|
|
|
|
|
async fn get_company(
|
|
|
|
|
_auth: AuthUser,
|
|
|
|
|
State(state): State<AppState>,
|
|
|
|
|
Path(id): Path<Uuid>,
|
|
|
|
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
|
|
|
|
let company = sqlx::query_as!(
|
|
|
|
|
AdminCompanyDetail,
|
|
|
|
|
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(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>,
|
|
|
|
|
Json(_payload): Json<ApproveRejectRequest>,
|
|
|
|
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
|
|
|
|
let company = sqlx::query!(
|
|
|
|
|
"SELECT id, user_id, status FROM company_profiles WHERE id = $1",
|
|
|
|
|
id
|
|
|
|
|
)
|
|
|
|
|
.fetch_optional(&state.pool)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
|
|
|
|
|
|
|
|
|
let company = match company {
|
|
|
|
|
Some(c) => c,
|
|
|
|
|
None => return Err((StatusCode::NOT_FOUND, "Company not found".to_string())),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if company.status == "APPROVED" {
|
|
|
|
|
return Err((StatusCode::BAD_REQUEST, "Company is already approved".to_string()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sqlx::query!(
|
|
|
|
|
"UPDATE company_profiles SET status = 'APPROVED', 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!({
|
|
|
|
|
"id": id,
|
|
|
|
|
"status": "APPROVED",
|
|
|
|
|
"message": "Company approved successfully"
|
|
|
|
|
})))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn reject_company(
|
|
|
|
|
_auth: AuthUser,
|
|
|
|
|
State(state): State<AppState>,
|
|
|
|
|
Path(id): Path<Uuid>,
|
|
|
|
|
Json(payload): Json<ApproveRejectRequest>,
|
|
|
|
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
|
|
|
|
let reason = payload.reason.as_deref().unwrap_or("No reason provided");
|
|
|
|
|
|
|
|
|
|
let company = sqlx::query!(
|
|
|
|
|
"SELECT id, user_id, status FROM company_profiles WHERE id = $1",
|
|
|
|
|
id
|
|
|
|
|
)
|
|
|
|
|
.fetch_optional(&state.pool)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
|
|
|
|
|
|
|
|
|
let company = match company {
|
|
|
|
|
Some(c) => c,
|
|
|
|
|
None => return Err((StatusCode::NOT_FOUND, "Company not found".to_string())),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if company.status == "REJECTED" {
|
|
|
|
|
return Err((StatusCode::BAD_REQUEST, "Company is already rejected".to_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!({
|
|
|
|
|
"id": id,
|
|
|
|
|
"status": "REJECTED",
|
|
|
|
|
"reason": reason,
|
|
|
|
|
"message": "Company rejected"
|
|
|
|
|
})))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn suspend_company(
|
|
|
|
|
_auth: AuthUser,
|
|
|
|
|
State(state): State<AppState>,
|
|
|
|
|
Path(id): Path<Uuid>,
|
|
|
|
|
Json(payload): Json<ApproveRejectRequest>,
|
|
|
|
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
|
|
|
|
let reason = payload.reason.as_deref().unwrap_or("No reason provided");
|
|
|
|
|
|
|
|
|
|
let company = sqlx::query!(
|
|
|
|
|
"SELECT id, user_id, status FROM company_profiles WHERE id = $1",
|
|
|
|
|
id
|
|
|
|
|
)
|
|
|
|
|
.fetch_optional(&state.pool)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
|
|
|
|
|
|
|
|
|
let company = match company {
|
|
|
|
|
Some(c) => c,
|
|
|
|
|
None => return Err((StatusCode::NOT_FOUND, "Company not found".to_string())),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if company.status == "SUSPENDED" {
|
|
|
|
|
return Err((StatusCode::BAD_REQUEST, "Company is already suspended".to_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!({
|
|
|
|
|
"id": id,
|
|
|
|
|
"status": "SUSPENDED",
|
|
|
|
|
"reason": reason,
|
|
|
|
|
"message": "Company suspended"
|
|
|
|
|
})))
|
|
|
|
|
}
|