koprogo_api/infrastructure/web/handlers/
individual_member_handlers.rs

1use actix_web::{delete, post, put, web, HttpResponse};
2use uuid::Uuid;
3
4use crate::application::dto::{
5    GrantConsentDto, IndividualMemberResponseDto, JoinCampaignAsIndividualDto,
6    UnsubscribeConfirmationDto, UpdateConsumptionDto,
7};
8use crate::domain::entities::IndividualMember;
9use crate::infrastructure::web::AppState;
10
11/// POST /api/v1/energy-campaigns/{campaign_id}/join-as-individual
12/// Join energy campaign as individual member (no authentication required)
13/// Issue #280: Energy group buying extensions
14#[post("/energy-campaigns/{campaign_id}/join-as-individual")]
15pub async fn join_campaign_as_individual(
16    _state: web::Data<AppState>,
17    campaign_id: web::Path<Uuid>,
18    request: web::Json<JoinCampaignAsIndividualDto>,
19) -> Result<HttpResponse, actix_web::Error> {
20    let campaign_id = campaign_id.into_inner();
21
22    // TODO: Verify campaign exists and is open to individuals (audience_type != 'CoProprietiesOnly')
23
24    // Create individual member
25    let member = IndividualMember::new(
26        campaign_id,
27        request.email.clone(),
28        request.postal_code.clone(),
29    )
30    .map_err(actix_web::error::ErrorBadRequest)?;
31
32    // TODO: Save to database using repository
33    // TODO: Check for duplicate email in campaign (UNIQUE constraint will handle)
34    // TODO: Send confirmation email with consent link
35
36    let response = IndividualMemberResponseDto::from(member);
37    Ok(HttpResponse::Created().json(response))
38}
39
40/// POST /api/v1/energy-campaigns/{campaign_id}/members/{member_id}/consent
41/// Grant GDPR consent for campaign participation
42#[post("/energy-campaigns/{campaign_id}/members/{member_id}/consent")]
43pub async fn grant_consent(
44    _state: web::Data<AppState>,
45    path: web::Path<(Uuid, Uuid)>,
46    _request: web::Json<GrantConsentDto>,
47) -> Result<HttpResponse, actix_web::Error> {
48    let (_campaign_id, _member_id) = path.into_inner();
49
50    // TODO: Fetch member from database
51    // TODO: Verify campaign_id matches
52    // TODO: Update has_gdpr_consent = true
53    // TODO: Log consent in gdpr_art30_register for audit trail
54
55    Ok(HttpResponse::Ok().json(serde_json::json!({
56        "success": true,
57        "message": "Consent granted successfully"
58    })))
59}
60
61/// PUT /api/v1/energy-campaigns/{campaign_id}/members/{member_id}/consumption
62/// Update consumption data for member
63#[put("/energy-campaigns/{campaign_id}/members/{member_id}/consumption")]
64pub async fn update_consumption(
65    _state: web::Data<AppState>,
66    path: web::Path<(Uuid, Uuid)>,
67    _request: web::Json<UpdateConsumptionDto>,
68) -> Result<HttpResponse, actix_web::Error> {
69    let (_campaign_id, _member_id) = path.into_inner();
70
71    // TODO: Fetch member from database
72    // TODO: Verify campaign_id matches
73    // TODO: Update consumption data
74    // TODO: Validate kwh >= 0
75
76    Ok(HttpResponse::Ok().json(serde_json::json!({
77        "success": true,
78        "message": "Consumption data updated"
79    })))
80}
81
82/// DELETE /api/v1/energy-campaigns/{campaign_id}/members/{member_id}/withdraw
83/// Withdraw from campaign (GDPR right to erasure preparation)
84/// TODO: Requires email token or authenticated user
85#[delete("/energy-campaigns/{campaign_id}/members/{member_id}/withdraw")]
86pub async fn withdraw_from_campaign(
87    _state: web::Data<AppState>,
88    path: web::Path<(Uuid, Uuid)>,
89) -> Result<HttpResponse, actix_web::Error> {
90    let (_campaign_id, _member_id) = path.into_inner();
91
92    // TODO: Fetch member from database
93    // TODO: Verify campaign_id matches
94    // TODO: Mark unsubscribed_at = NOW()
95    // TODO: Schedule data anonymization/deletion (GDPR Article 17)
96
97    let response = UnsubscribeConfirmationDto {
98        success: true,
99        message: "Successfully withdrawn from campaign".to_string(),
100        email: "user@example.com".to_string(), // TODO: Get from member
101    };
102
103    Ok(HttpResponse::Ok().json(response))
104}
105
106#[cfg(test)]
107mod tests {
108    #[test]
109    fn test_individual_member_handlers_compile() {
110        // Placeholder test to verify handler structure
111    }
112}