nxtgauge-backend-rust/crates/contracts/src/profession_shared.rs

180 lines
8.1 KiB
Rust
Raw Normal View History

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;
use db::models::professional::ProfessionalRepository;
use db::models::requirement::RequirementRepository;
use db::models::lead_request::{LeadRequestRepository, CreateLeadRequestPayload};
use crate::auth_middleware::AuthUser;
#[derive(Deserialize)]
pub struct PaginationQuery {
pub page: Option<i64>,
pub limit: Option<i64>,
}
#[derive(Deserialize)]
pub struct LeadRequestPayload {
pub requirement_id: Uuid,
}
pub fn shared_routes(profession_key: &'static str) -> Router<PgPool> {
Router::new()
.route(
"/marketplace",
get(move |state, query| browse_marketplace(state, query, profession_key)),
)
.route("/marketplace/:id", get(get_requirement))
.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))
.route("/portfolio/me", get(list_portfolio))
// ... (other routes remain same for now)
}
async fn browse_marketplace(
State(pool): State<PgPool>,
Query(q): Query<PaginationQuery>,
profession_key: &str,
) -> impl IntoResponse {
let page = q.page.unwrap_or(1);
let limit = q.limit.unwrap_or(20);
match ProfessionalRepository::get_marketplace(&pool, profession_key, page, limit).await {
Ok(items) => (StatusCode::OK, Json(serde_json::json!({
"data": items,
"pagination": { "page": page, "limit": limit }
}))).into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
}
async fn get_requirement(
State(pool): State<PgPool>,
_auth: AuthUser,
Path(id): Path<Uuid>,
) -> impl IntoResponse {
match RequirementRepository::get_by_id(&pool, id).await {
Ok(Some(req)) if req.status == "OPEN" => (StatusCode::OK, Json(req)).into_response(),
Ok(Some(_)) => (StatusCode::FORBIDDEN, "Requirement is not open").into_response(),
Ok(None) => (StatusCode::NOT_FOUND, "Requirement not found").into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
}
async fn send_lead_request(
State(pool): State<PgPool>,
auth: AuthUser,
Json(payload): Json<LeadRequestPayload>,
) -> impl IntoResponse {
let prof = match ProfessionalRepository::get_by_user_id(&pool, auth.user_id).await {
Ok(p) => p,
Err(_) => return (StatusCode::NOT_FOUND, "Professional profile not found").into_response(),
};
let req = match RequirementRepository::get_by_id(&pool, payload.requirement_id).await {
Ok(Some(r)) if r.status == "OPEN" => r,
Ok(Some(_)) => return (StatusCode::BAD_REQUEST, "Requirement is not open").into_response(),
_ => return (StatusCode::NOT_FOUND, "Requirement not found").into_response(),
};
if req.request_count >= 20 {
return (StatusCode::CONFLICT, "Requirement reached max requests").into_response();
}
// Check wallet balance
let wallet = match ProfessionalRepository::get_wallet(&pool, auth.user_id).await {
Ok(w) => w,
_ => return (StatusCode::BAD_REQUEST, "Wallet not found").into_response(),
};
if wallet.balance < 25 {
return (StatusCode::PAYMENT_REQUIRED, "Insufficient Tracecoin balance").into_response();
}
let db_payload = CreateLeadRequestPayload {
requirement_id: req.id,
professional_id: prof.id,
expires_at: Utc::now() + chrono::Duration::hours(24),
};
match LeadRequestRepository::create(&pool, db_payload).await {
Ok(lead) => {
let _ = RequirementRepository::increment_request_count(&pool, req.id).await;
// TODO: Debit/Reserve Tracecoins in wallet ledger
(StatusCode::CREATED, Json(lead)).into_response()
},
Err(e) => {
if e.to_string().contains("unique") {
(StatusCode::CONFLICT, "Already requested this lead").into_response()
} else {
(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response()
}
}
}
}
async fn list_portfolio(
State(pool): State<PgPool>,
auth: AuthUser,
) -> impl IntoResponse {
match ProfessionalRepository::get_by_user_id(&pool, auth.user_id).await {
Ok(prof) => {
match ProfessionalRepository::get_portfolio(&pool, prof.id).await {
Ok(items) => (StatusCode::OK, Json(serde_json::json!({ "data": items }))).into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
},
Err(_) => (StatusCode::NOT_FOUND, "Professional profile not found").into_response(),
}
}
async fn list_services(
State(pool): State<PgPool>,
auth: AuthUser,
) -> impl IntoResponse {
match ProfessionalRepository::get_by_user_id(&pool, auth.user_id).await {
Ok(prof) => {
match ProfessionalRepository::get_services(&pool, prof.id).await {
Ok(items) => (StatusCode::OK, Json(serde_json::json!({ "data": items }))).into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
},
Err(_) => (StatusCode::NOT_FOUND, "Professional profile not found").into_response(),
}
}
async fn wallet_balance(
State(pool): State<PgPool>,
auth: AuthUser,
) -> impl IntoResponse {
match ProfessionalRepository::get_wallet(&pool, auth.user_id).await {
Ok(w) => (StatusCode::OK, Json(w)).into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
}
// Stubs for remaining routes (ledger, invoices, etc.)
async fn my_requests(_s: State<PgPool>, _a: AuthUser, _q: Query<PaginationQuery>) -> impl IntoResponse { (StatusCode::OK, Json(serde_json::json!({"data":[]}))) }
async fn cancel_request(_s: State<PgPool>, _a: AuthUser, _p: Path<Uuid>) -> impl IntoResponse { (StatusCode::OK, Json(serde_json::json!({"message":"Done"}))) }
async fn accepted_leads(_s: State<PgPool>, _a: AuthUser, _q: Query<PaginationQuery>) -> impl IntoResponse { (StatusCode::OK, Json(serde_json::json!({"data":[]}))) }
async fn accepted_lead_detail(_s: State<PgPool>, _a: AuthUser, _p: Path<Uuid>) -> impl IntoResponse { (StatusCode::OK, Json(serde_json::json!({"id":_p.to_string()}))) }
async fn create_portfolio_item(_s: State<PgPool>, _a: AuthUser, _p: Json<serde_json::Value>) -> impl IntoResponse { (StatusCode::CREATED, Json(serde_json::json!({"id":Uuid::new_v4().to_string()}))) }
async fn update_portfolio_item(_s: State<PgPool>, _a: AuthUser, _p: Path<Uuid>, _v: Json<serde_json::Value>) -> impl IntoResponse { (StatusCode::OK, Json(serde_json::json!({"message":"Updated"}))) }
async fn delete_portfolio_item(_s: State<PgPool>, _a: AuthUser, _p: Path<Uuid>) -> impl IntoResponse { (StatusCode::OK, Json(serde_json::json!({"message":"Deleted"}))) }
async fn create_service(_s: State<PgPool>, _a: AuthUser, _p: Json<serde_json::Value>) -> impl IntoResponse { (StatusCode::CREATED, Json(serde_json::json!({"id":Uuid::new_v4().to_string()}))) }
async fn update_service(_s: State<PgPool>, _a: AuthUser, _p: Path<Uuid>, _v: Json<serde_json::Value>) -> impl IntoResponse { (StatusCode::OK, Json(serde_json::json!({"message":"Updated"}))) }
async fn delete_service(_s: State<PgPool>, _a: AuthUser, _p: Path<Uuid>) -> impl IntoResponse { (StatusCode::OK, Json(serde_json::json!({"message":"Deleted"}))) }
async fn wallet_ledger(_s: State<PgPool>, _a: AuthUser, _q: Query<PaginationQuery>) -> impl IntoResponse { (StatusCode::OK, Json(serde_json::json!({"data":[]}))) }
async fn wallet_invoices(_s: State<PgPool>, _a: AuthUser, _q: Query<PaginationQuery>) -> impl IntoResponse { (StatusCode::OK, Json(serde_json::json!({"data":[]}))) }
async fn wallet_invoice_detail(_s: State<PgPool>, _a: AuthUser, _p: Path<Uuid>) -> impl IntoResponse { (StatusCode::OK, Json(serde_json::json!({"id":_p.to_string()}))) }