chore: cleanup — remove stub professionals app, empty crates, unused import

- Delete apps/professionals (all stub handlers returning mock data, shadowed
  by individual profession services on ports 8085-8093)
- Delete empty crate dirs: crates/config, crates/errors, crates/observability
  (already removed from Cargo.toml workspace members, stale directories)
- Delete empty apps/jobseekers dir (duplicate of apps/job_seekers)
- Remove unused Uri import from gateway main.rs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ashwin Kumar 2026-03-25 23:35:30 +01:00
parent e1ea3f5ffe
commit d49a29b965
3 changed files with 1 additions and 253 deletions

View file

@ -1,7 +1,7 @@
use axum::{ use axum::{
body::Body, body::Body,
extract::{Request, State}, extract::{Request, State},
http::{HeaderValue, Method, StatusCode, Uri}, http::{HeaderValue, Method, StatusCode},
response::IntoResponse, response::IntoResponse,
routing::any, routing::any,
Router, Router,

View file

@ -1,202 +0,0 @@
use axum::{
extract::{Path, Query, State},
http::StatusCode,
response::IntoResponse,
routing::{delete, get, patch, post},
Json, Router,
};
use serde::Deserialize;
use sqlx::PgPool;
use uuid::Uuid;
/// Shared professional router — all 9 profession apps mount this.
/// Each profession app wraps this under its own /api/<profession_key> path.
pub fn router() -> Router<PgPool> {
Router::new()
// Professional profile
.route("/profile/me", get(get_profile).patch(update_profile))
// Marketplace (requirements feed)
.route("/marketplace", get(browse_marketplace))
.route("/marketplace/{id}", get(get_requirement_detail))
// Lead requests
.route("/leads/request", post(send_lead_request))
.route("/leads/requests/me", get(my_requests))
.route("/leads/requests/{id}", delete(cancel_request))
.route("/leads/accepted/me", get(accepted_leads))
.route("/leads/accepted/{id}", get(accepted_lead_detail))
// Portfolio
.route("/portfolio/me", get(list_portfolio))
.route("/portfolio", post(create_portfolio_item))
.route("/portfolio/{id}", patch(update_portfolio_item).delete(delete_portfolio_item))
// Services
.route("/services/me", get(list_services))
.route("/services", post(create_service))
.route("/services/{id}", patch(update_service).delete(delete_service))
// Wallet
.route("/wallet/balance", get(wallet_balance))
.route("/wallet/ledger", get(wallet_ledger))
.route("/wallet/invoices", get(wallet_invoices))
.route("/wallet/invoices/{id}", get(wallet_invoice_detail))
}
#[derive(Deserialize)]
pub struct PaginationQuery {
pub page: Option<i64>,
pub limit: Option<i64>,
pub profession_key: Option<String>,
}
#[derive(Deserialize)]
pub struct LeadRequestPayload {
pub requirement_id: String,
}
#[derive(Deserialize)]
pub struct CreatePortfolioPayload {
pub title: String,
pub description: Option<String>,
pub tags: Option<Vec<String>>,
}
#[derive(Deserialize)]
pub struct CreateServicePayload {
pub name: String,
pub description: Option<String>,
pub price: i32, // in paise
pub duration_minutes: Option<i32>,
}
async fn get_profile(State(pool): State<PgPool>) -> impl IntoResponse {
let _ = pool;
(StatusCode::OK, Json(serde_json::json!({ "id": null, "display_name": null, "status": "ACTIVE" })))
}
async fn update_profile(State(pool): State<PgPool>, Json(_p): Json<serde_json::Value>) -> impl IntoResponse {
let _ = pool;
(StatusCode::OK, Json(serde_json::json!({ "message": "Profile updated" })))
}
async fn browse_marketplace(
State(pool): State<PgPool>,
Query(q): Query<PaginationQuery>,
) -> impl IntoResponse {
let _ = pool;
// Returns OPEN + non-expired requirements for this profession_key
(StatusCode::OK, Json(serde_json::json!({
"data": [],
"pagination": { "page": q.page.unwrap_or(1), "limit": q.limit.unwrap_or(20), "total": 0 }
})))
}
async fn get_requirement_detail(
State(pool): State<PgPool>,
Path(id): Path<Uuid>,
) -> impl IntoResponse {
let _ = pool;
// Customer contact details NOT included — only revealed after acceptance
(StatusCode::OK, Json(serde_json::json!({ "id": id.to_string(), "status": "OPEN" })))
}
async fn send_lead_request(
State(pool): State<PgPool>,
Json(payload): Json<LeadRequestPayload>,
) -> impl IntoResponse {
let _ = pool;
// Server enforces: 25 Tracecoins reserved, requirement OPEN, max 20 requests, no duplicate
let req_id = payload.requirement_id.clone();
(StatusCode::CREATED, Json(serde_json::json!({
"id": Uuid::new_v4().to_string(),
"requirement_id": req_id,
"status": "PENDING",
"tracecoins_reserved": 25,
"expires_at": (chrono::Utc::now() + chrono::Duration::days(1)).to_rfc3339()
})))
}
async fn my_requests(State(pool): State<PgPool>, Query(q): Query<PaginationQuery>) -> impl IntoResponse {
let _ = pool;
(StatusCode::OK, Json(serde_json::json!({ "data": [], "pagination": { "page": q.page.unwrap_or(1), "limit": 20 } })))
}
async fn cancel_request(State(pool): State<PgPool>, Path(id): Path<Uuid>) -> impl IntoResponse {
let _ = pool;
(StatusCode::OK, Json(serde_json::json!({ "id": id.to_string(), "message": "Request cancelled" })))
}
async fn accepted_leads(State(pool): State<PgPool>, Query(q): Query<PaginationQuery>) -> impl IntoResponse {
let _ = pool;
// Returns leads where status = ACCEPTED — includes customer contact info
(StatusCode::OK, Json(serde_json::json!({ "data": [], "pagination": { "page": q.page.unwrap_or(1), "limit": 20 } })))
}
async fn accepted_lead_detail(State(pool): State<PgPool>, Path(id): Path<Uuid>) -> impl IntoResponse {
let _ = pool;
(StatusCode::OK, Json(serde_json::json!({ "id": id.to_string() })))
}
async fn list_portfolio(State(pool): State<PgPool>) -> impl IntoResponse {
let _ = pool;
(StatusCode::OK, Json(serde_json::json!({ "data": [] })))
}
async fn create_portfolio_item(
State(pool): State<PgPool>,
Json(p): Json<CreatePortfolioPayload>,
) -> impl IntoResponse {
let _ = pool;
(StatusCode::CREATED, Json(serde_json::json!({ "id": Uuid::new_v4().to_string(), "title": p.title })))
}
async fn update_portfolio_item(State(pool): State<PgPool>, Path(id): Path<Uuid>, Json(_p): Json<serde_json::Value>) -> impl IntoResponse {
let _ = pool;
(StatusCode::OK, Json(serde_json::json!({ "id": id.to_string(), "message": "Updated" })))
}
async fn delete_portfolio_item(State(pool): State<PgPool>, Path(id): Path<Uuid>) -> impl IntoResponse {
let _ = pool;
(StatusCode::OK, Json(serde_json::json!({ "id": id.to_string(), "message": "Deleted" })))
}
async fn list_services(State(pool): State<PgPool>) -> impl IntoResponse {
let _ = pool;
(StatusCode::OK, Json(serde_json::json!({ "data": [] })))
}
async fn create_service(
State(pool): State<PgPool>,
Json(p): Json<CreateServicePayload>,
) -> impl IntoResponse {
let _ = pool;
(StatusCode::CREATED, Json(serde_json::json!({ "id": Uuid::new_v4().to_string(), "name": p.name })))
}
async fn update_service(State(pool): State<PgPool>, Path(id): Path<Uuid>, Json(_p): Json<serde_json::Value>) -> impl IntoResponse {
let _ = pool;
(StatusCode::OK, Json(serde_json::json!({ "id": id.to_string(), "message": "Updated" })))
}
async fn delete_service(State(pool): State<PgPool>, Path(id): Path<Uuid>) -> impl IntoResponse {
let _ = pool;
(StatusCode::OK, Json(serde_json::json!({ "id": id.to_string(), "message": "Deleted" })))
}
async fn wallet_balance(State(pool): State<PgPool>) -> impl IntoResponse {
let _ = pool;
// Read-only — no write on client
(StatusCode::OK, Json(serde_json::json!({ "balance": 0, "reserved": 0, "available": 0 })))
}
async fn wallet_ledger(State(pool): State<PgPool>, Query(q): Query<PaginationQuery>) -> impl IntoResponse {
let _ = pool;
(StatusCode::OK, Json(serde_json::json!({ "data": [], "pagination": { "page": q.page.unwrap_or(1), "limit": 20 } })))
}
async fn wallet_invoices(State(pool): State<PgPool>, Query(q): Query<PaginationQuery>) -> impl IntoResponse {
let _ = pool;
(StatusCode::OK, Json(serde_json::json!({ "data": [], "pagination": { "page": q.page.unwrap_or(1), "limit": 20 } })))
}
async fn wallet_invoice_detail(State(pool): State<PgPool>, Path(id): Path<Uuid>) -> impl IntoResponse {
let _ = pool;
(StatusCode::OK, Json(serde_json::json!({ "id": id.to_string() })))
}

View file

@ -1,50 +0,0 @@
mod handlers;
use axum::{Router, http::Method};
use std::net::SocketAddr;
use tower_http::cors::{Any, CorsLayer};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
#[tokio::main]
async fn main() {
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::new(
std::env::var("RUST_LOG").unwrap_or_else(|_| "info".into()),
))
.with(tracing_subscriber::fmt::layer())
.init();
let database_url =
std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let pool = sqlx::postgres::PgPoolOptions::new()
.max_connections(5)
.connect(&database_url)
.await
.expect("Failed to connect to postgres");
tracing::info!("Professionals service — connected to database");
let cors = CorsLayer::new()
.allow_methods([Method::GET, Method::POST, Method::PATCH, Method::DELETE, Method::OPTIONS])
.allow_origin(Any)
.allow_headers(Any);
let app = Router::new()
.route("/health", axum::routing::get(|| async { "Professionals Service OK" }))
// All 9 profession types share the same router under /api/professionals
.nest("/api/professionals", handlers::router())
.layer(cors)
.with_state(pool);
let port: u16 = std::env::var("PORT")
.unwrap_or_else(|_| "8084".to_string())
.parse()
.expect("PORT must be a number");
let addr = SocketAddr::from(([0, 0, 0, 0], port));
tracing::info!("Professionals service listening on {}", addr);
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
axum::serve(listener, app).await.unwrap();
}