koprogo_api/infrastructure/web/handlers/
acp_module_handlers.rs1use actix_web::{get, put, web, HttpResponse, Responder, ResponseError};
17use uuid::Uuid;
18
19use crate::application::dto::EnabledModulesResponseDto;
20use crate::application::use_cases::module_registry_use_cases::ModuleRegistryUseCases;
21use crate::infrastructure::audit::{AuditEventType, AuditLogEntry};
22use crate::infrastructure::web::handlers::acp_handlers::caller_from_user;
23use crate::infrastructure::web::{AppState, AuthenticatedUser};
24
25#[utoipa::path(
26 get,
27 path = "/acps/{id}/modules",
28 tag = "Acps",
29 summary = "Modules actifs d'une ACP (portée ACP)",
30 params(("id" = Uuid, Path, description = "ACP UUID")),
31 responses(
32 (status = 200, description = "Modules actifs", body = crate::application::dto::EnabledModulesResponseDto),
33 (status = 403, description = "Hors portée"),
34 ),
35 security(("bearer_auth" = []))
36)]
37#[get("/acps/{id}/modules")]
38pub async fn list_acp_modules(
39 state: web::Data<AppState>,
40 user: AuthenticatedUser,
41 id: web::Path<Uuid>,
42) -> impl Responder {
43 let acp_id = *id;
44 let caller = caller_from_user(&user);
45 match state
46 .module_registry_use_cases
47 .list_enabled(&caller, acp_id)
48 .await
49 {
50 Ok(modules) => HttpResponse::Ok().json(EnabledModulesResponseDto {
51 acp_id: acp_id.to_string(),
52 modules,
53 }),
54 Err(err) => err.error_response(),
55 }
56}
57
58#[utoipa::path(
59 put,
60 path = "/acps/{id}/modules/{module}/enable",
61 tag = "Acps",
62 summary = "Allumer un module pour une ACP (admin + portée)",
63 params(
64 ("id" = Uuid, Path, description = "ACP UUID"),
65 ("module" = String, Path, description = "Nom du module (ADR-0015)"),
66 ),
67 responses(
68 (status = 204, description = "Module actif"),
69 (status = 403, description = "Hors portée ou droits insuffisants"),
70 (status = 422, description = "Nom de module inconnu"),
71 ),
72 security(("bearer_auth" = []))
73)]
74#[put("/acps/{id}/modules/{module}/enable")]
75pub async fn enable_acp_module(
76 state: web::Data<AppState>,
77 user: AuthenticatedUser,
78 chemin: web::Path<(Uuid, String)>,
79) -> impl Responder {
80 let (acp_id, nom) = chemin.into_inner();
81 let module = match ModuleRegistryUseCases::reconnaitre(&nom) {
82 Ok(m) => m,
83 Err(err) => return err.error_response(),
84 };
85 let caller = caller_from_user(&user);
86 let issue = state
87 .module_registry_use_cases
88 .enable(&caller, acp_id, module)
89 .await;
90 journaliser(AuditEventType::AcpModuleEnabled, &user, acp_id, &issue);
91 match issue {
92 Ok(()) => HttpResponse::NoContent().finish(),
95 Err(err) => err.error_response(),
96 }
97}
98
99#[utoipa::path(
100 put,
101 path = "/acps/{id}/modules/{module}/disable",
102 tag = "Acps",
103 summary = "Éteindre un module pour une ACP (admin + portée)",
104 params(
105 ("id" = Uuid, Path, description = "ACP UUID"),
106 ("module" = String, Path, description = "Nom du module (ADR-0015)"),
107 ),
108 responses(
109 (status = 204, description = "Module éteint"),
110 (status = 403, description = "Hors portée, droits insuffisants, ou module toujours actif"),
111 (status = 422, description = "Nom de module inconnu"),
112 ),
113 security(("bearer_auth" = []))
114)]
115#[put("/acps/{id}/modules/{module}/disable")]
116pub async fn disable_acp_module(
117 state: web::Data<AppState>,
118 user: AuthenticatedUser,
119 chemin: web::Path<(Uuid, String)>,
120) -> impl Responder {
121 let (acp_id, nom) = chemin.into_inner();
122 let module = match ModuleRegistryUseCases::reconnaitre(&nom) {
123 Ok(m) => m,
124 Err(err) => return err.error_response(),
125 };
126 let caller = caller_from_user(&user);
127 let issue = state
128 .module_registry_use_cases
129 .disable(&caller, acp_id, module)
130 .await;
131 journaliser(AuditEventType::AcpModuleDisabled, &user, acp_id, &issue);
132 match issue {
133 Ok(()) => HttpResponse::NoContent().finish(),
134 Err(err) => err.error_response(),
135 }
136}
137
138fn journaliser(
144 evenement: AuditEventType,
145 user: &AuthenticatedUser,
146 acp_id: Uuid,
147 issue: &Result<(), crate::application::error::AppError>,
148) {
149 let entree = AuditLogEntry::new(evenement, Some(user.user_id), user.organization_id)
150 .with_resource("Acp", acp_id);
151 match issue {
152 Ok(()) => entree.log(),
153 Err(err) => entree.with_error(err.to_string()).log(),
154 }
155}