use crate::AppState; use axum::{ extract::{Path, Query, State}, http::StatusCode, response::IntoResponse, routing::get, Json, Router, }; use contracts::auth_middleware::{AuthUser, require_admin}; use serde::{Deserialize, Serialize}; use uuid::Uuid; use sqlx::{FromRow, Row}; pub fn router() -> Router { Router::new() .route("/users", get(list_users)) .route("/customers", get(list_customers)) .route("/candidates", get(list_candidates)) .route("/users/{id}/status", axum::routing::patch(update_user_status)) } #[derive(Deserialize)] pub struct ListQuery { pub q: Option, pub status: Option, pub role: Option, } #[derive(Serialize, Deserialize, FromRow)] pub struct AdminUserRow { pub id: Uuid, pub email: String, pub full_name: Option, pub status: String, pub created_at: chrono::DateTime, pub roles: Vec, } async fn list_users( _auth: AuthUser, State(state): State, Query(q): Query, ) -> Result { let search = q.q.as_deref().unwrap_or_default().to_lowercase(); let role_filter = q.role.as_deref().unwrap_or_default().to_uppercase(); let sql = if role_filter.is_empty() { // Generic list: users + their approved roles r#" SELECT u.id, u.email, u.full_name, u.status, u.created_at, COALESCE(array_agg(r.key) FILTER (WHERE r.key IS NOT NULL), '{}') as roles FROM users u LEFT JOIN user_roles ur ON ur.user_id = u.id AND ur.status = 'APPROVED' LEFT JOIN roles r ON r.id = ur.role_id WHERE ($1 = '' OR LOWER(u.full_name) LIKE '%' || $1 || '%' OR LOWER(u.email) LIKE '%' || $1 || '%') GROUP BY u.id ORDER BY u.created_at DESC LIMIT 100 "#.to_string() } else { // Role-specific list: joins with the specific profile table to get THAT role's status let table = match role_filter.as_str() { "PHOTOGRAPHER" => "photographer_profiles", "MAKEUP_ARTIST" => "makeup_artist_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_service_profiles", "CUSTOMER" => "customer_profiles", "COMPANY" => "company_profiles", "JOB_SEEKER" => "job_seeker_profiles", _ => "user_roles", // fallback }; format!( r#" SELECT u.id, u.email, u.full_name, p.status, u.created_at, ARRAY['{}']::text[] as roles FROM users u JOIN {} p ON p.user_id = u.id WHERE ($1 = '' OR LOWER(u.full_name) LIKE '%' || $1 || '%' OR LOWER(u.email) LIKE '%' || $1 || '%') ORDER BY u.created_at DESC LIMIT 100 "#, role_filter, table ) }; let rows = sqlx::query_as::<_, AdminUserRow>(&sql) .bind(search) .fetch_all(&state.pool) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?; Ok(Json(rows)) } async fn list_customers( _auth: AuthUser, State(state): State, Query(q): Query, ) -> Result { let search = q.q.unwrap_or_default().to_lowercase(); let sql = r#" SELECT u.id, u.email, u.full_name, u.status, u.created_at, ARRAY['CUSTOMER']::text[] as roles FROM users u JOIN user_roles ur ON ur.user_id = u.id AND ur.status = 'APPROVED' JOIN roles r ON r.id = ur.role_id AND r.key = 'CUSTOMER' WHERE ($1 = '' OR LOWER(u.full_name) LIKE '%' || $1 || '%' OR LOWER(u.email) LIKE '%' || $1 || '%') ORDER BY u.created_at DESC LIMIT 50 "#; let rows = sqlx::query_as::<_, AdminUserRow>(sql) .bind(search) .fetch_all(&state.pool) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?; Ok(Json(rows)) } async fn list_candidates( _auth: AuthUser, State(state): State, Query(q): Query, ) -> Result { let search = q.q.unwrap_or_default().to_lowercase(); let sql = r#" SELECT u.id, u.email, u.full_name, u.status, u.created_at, ARRAY['JOB_SEEKER']::text[] as roles FROM users u JOIN user_roles ur ON ur.user_id = u.id AND ur.status = 'APPROVED' JOIN roles r ON r.id = ur.role_id AND r.key = 'JOB_SEEKER' WHERE ($1 = '' OR LOWER(u.full_name) LIKE '%' || $1 || '%' OR LOWER(u.email) LIKE '%' || $1 || '%') ORDER BY u.created_at DESC LIMIT 50 "#; let rows = sqlx::query_as::<_, AdminUserRow>(sql) .bind(search) .fetch_all(&state.pool) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?; Ok(Json(rows)) } #[derive(Deserialize)] pub struct StatusPayload { pub status: String, } async fn update_user_status( auth: AuthUser, State(state): State, Path(id): Path, Json(payload): Json, ) -> Result { sqlx::query!( "UPDATE users SET status = $1, updated_at = NOW() WHERE id = $2", payload.status, id ) .execute(&state.pool) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?; Ok(StatusCode::OK) }