feat(sprint-16 backend 2026-04-25): transparency operations — Métriques Prometheus + Export multi-format + Notifications auto
Some checks failed
CI/CD Pipeline / pipeline (push) Failing after 3m35s
Some checks failed
CI/CD Pipeline / pipeline (push) Failing after 3m35s
Trois piliers transparency opérationnelle UnionFlow livrés en série :
S16.A — Métriques Prometheus métier (transparency observabilité)
- AuditTrailService.log() incrémente :
- unionflow.audit.operations{action, entity}
- unionflow.audit.sod_violations{entity}
- application.properties : quarkus.micrometer.export.prometheus.enabled=true,
http-server + jvm binders activés
- Endpoint /q/metrics exposé pour scraping Prometheus K8s
S16.B — Export massif audit-trail (transparency réglementaire BCEAO/ARTCI/CENTIF)
- AuditTrailExportService :
- exportCsv : Apache Commons CSV avec BOM UTF-8 (compat Excel)
- exportXlsx : POI XSSFWorkbook avec header coloré et auto-size
- exportPdf : OpenPDF A4 paysage avec table 7 colonnes
- Endpoint GET /api/audit-trail/export?format=csv|xlsx|pdf&scope=...&limit=500
- @RolesAllowed COMPLIANCE_OFFICER, CONTROLEUR_INTERNE, SUPER_ADMIN
- Auto-log de l'export lui-même dans audit (méta-traçabilité)
S16.C — Notifications transparency (pattern CDI Event Observer)
- AuditOperationLoggedEvent record + helper estSensible() (DELETE / PAYMENT_* / BUDGET_APPROVED / AID_REQUEST_APPROVED / EXPORT / VALIDATE / SoD-violation)
- AuditTrailService.log() fire le CDI event après persist
- AuditNotificationService observe @Observes(AFTER_SUCCESS) — notifie via NotificationService existant (DRY, pas de nouveau client mail)
- Sujets/corps localisés français + priorité automatique (HAUTE pour DELETE/PAYMENT_FAILED/SoD, NORMALE pour confirmations, BASSE pour exports)
- Fail-soft : exception notification ne bloque jamais l'audit principal
Tests (10 nouveaux S16.C, 21/21 cumulé audit)
- onAuditOperation × 5 (sensible/non-sensible/SoD/null/userId-null)
- mapping helpers × 4 (sujet × 8 actions, sodPriority, priorité × 6 cas, corps × 2)
This commit is contained in:
@@ -30,6 +30,7 @@ import java.util.UUID;
|
||||
public class AuditTrailOperationResource {
|
||||
|
||||
@Inject AuditTrailQueryService queryService;
|
||||
@Inject dev.lions.unionflow.server.service.audit.AuditTrailExportService exportService;
|
||||
|
||||
@GET
|
||||
@Path("/by-user/{userId}")
|
||||
@@ -102,6 +103,47 @@ public class AuditTrailOperationResource {
|
||||
return queryService.listerRecentes(scope, orgId, userId, limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export massif audit (Sprint 16.B) — formats CSV / XLSX / PDF pour BCEAO/ARTCI/CENTIF.
|
||||
*/
|
||||
@GET
|
||||
@Path("/export")
|
||||
@jakarta.ws.rs.Produces("application/octet-stream")
|
||||
@RolesAllowed({"COMPLIANCE_OFFICER", "CONTROLEUR_INTERNE", "SUPER_ADMIN"})
|
||||
public jakarta.ws.rs.core.Response export(
|
||||
@QueryParam("format") @jakarta.ws.rs.DefaultValue("csv") String format,
|
||||
@QueryParam("scope") @jakarta.ws.rs.DefaultValue("ALL") String scope,
|
||||
@QueryParam("orgId") UUID orgId,
|
||||
@QueryParam("userId") UUID userId,
|
||||
@QueryParam("limit") @jakarta.ws.rs.DefaultValue("500") int limit) {
|
||||
String fmt = format == null ? "csv" : format.toLowerCase();
|
||||
byte[] payload;
|
||||
String mediaType;
|
||||
String extension;
|
||||
switch (fmt) {
|
||||
case "xlsx" -> {
|
||||
payload = exportService.exportXlsx(scope, orgId, userId, limit);
|
||||
mediaType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||
extension = "xlsx";
|
||||
}
|
||||
case "pdf" -> {
|
||||
payload = exportService.exportPdf(scope, orgId, userId, limit);
|
||||
mediaType = "application/pdf";
|
||||
extension = "pdf";
|
||||
}
|
||||
default -> {
|
||||
payload = exportService.exportCsv(scope, orgId, userId, limit);
|
||||
mediaType = "text/csv";
|
||||
extension = "csv";
|
||||
}
|
||||
}
|
||||
String filename = String.format("audit-trail-%s-%s.%s",
|
||||
scope.toLowerCase(), java.time.LocalDate.now(), extension);
|
||||
return jakarta.ws.rs.core.Response.ok(payload, mediaType)
|
||||
.header("Content-Disposition", "attachment; filename=\"" + filename + "\"")
|
||||
.build();
|
||||
}
|
||||
|
||||
private LocalDateTime parseDateTime(String input, LocalDateTime fallback) {
|
||||
if (input == null || input.isBlank()) return fallback;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user