koprogo_api/infrastructure/web/handlers/
marketplace_handlers.rs

1use actix_web::{get, post, web, HttpResponse};
2use chrono::Datelike;
3use uuid::Uuid;
4
5use crate::application::dto::{
6    ContractEvaluationsAnnualReportDto, CreateServiceProviderDto, SearchServiceProvidersQuery,
7};
8use crate::infrastructure::web::middleware::AuthenticatedUser;
9use crate::infrastructure::web::AppState;
10
11/// GET /api/v1/marketplace/providers
12/// Search for service providers (public - no authentication required)
13#[get("/marketplace/providers")]
14pub async fn search_service_providers(
15    state: web::Data<AppState>,
16    query: web::Query<SearchServiceProvidersQuery>,
17) -> Result<HttpResponse, actix_web::Error> {
18    let results = state
19        .service_provider_use_cases
20        .search(&query.into_inner())
21        .await
22        .map_err(actix_web::error::ErrorInternalServerError)?;
23    Ok(HttpResponse::Ok().json(results))
24}
25
26/// GET /api/v1/marketplace/providers/{slug}
27/// Get public service provider profile (no authentication required)
28#[get("/marketplace/providers/{slug}")]
29pub async fn get_provider_by_slug(
30    state: web::Data<AppState>,
31    slug: web::Path<String>,
32) -> Result<HttpResponse, actix_web::Error> {
33    let slug_str = slug.into_inner();
34    match state
35        .service_provider_use_cases
36        .find_by_slug(&slug_str)
37        .await
38        .map_err(actix_web::error::ErrorInternalServerError)?
39    {
40        Some(provider) => Ok(HttpResponse::Ok().json(provider)),
41        None => Ok(HttpResponse::NotFound().json(serde_json::json!({
42            "error": format!("Provider not found: {}", slug_str)
43        }))),
44    }
45}
46
47/// POST /api/v1/service-providers
48/// Create a new service provider (authenticated - syndic/admin only)
49#[post("/service-providers")]
50pub async fn create_service_provider(
51    state: web::Data<AppState>,
52    request: web::Json<CreateServiceProviderDto>,
53    user: AuthenticatedUser,
54) -> Result<HttpResponse, actix_web::Error> {
55    let org_id = user
56        .organization_id
57        .ok_or_else(|| actix_web::error::ErrorBadRequest("Organization ID required"))?;
58
59    let response = state
60        .service_provider_use_cases
61        .create(org_id, request.into_inner())
62        .await
63        .map_err(actix_web::error::ErrorBadRequest)?;
64
65    Ok(HttpResponse::Created().json(response))
66}
67
68/// GET /api/v1/buildings/{building_id}/reports/contract-evaluations/annual
69/// Get annual contract evaluations report (L13 legal report)
70#[get("/buildings/{building_id}/reports/contract-evaluations/annual")]
71pub async fn get_contract_evaluations_annual(
72    _state: web::Data<AppState>,
73    building_id: web::Path<Uuid>,
74    web::Query(params): web::Query<std::collections::HashMap<String, String>>,
75    _user: AuthenticatedUser,
76) -> Result<HttpResponse, actix_web::Error> {
77    let building_id = building_id.into_inner();
78    let year = params
79        .get("year")
80        .and_then(|y| y.parse::<i32>().ok())
81        .unwrap_or_else(|| chrono::Local::now().year());
82
83    // TODO: Implement ContractEvaluationRepository for real data
84    // For now, return empty report (evaluations are a separate entity not yet persisted)
85    let report = ContractEvaluationsAnnualReportDto {
86        building_id: building_id.to_string(),
87        report_year: year,
88        total_evaluations: 0,
89        total_providers_evaluated: 0,
90        average_global_score: 0.0,
91        recommendation_rate: 0.0,
92        evaluations: vec![],
93    };
94
95    Ok(HttpResponse::Ok().json(report))
96}