feat(sprint-15 backend 2026-04-25): Live Activity Feed — endpoint /api/audit-trail/recent + tests
Some checks failed
CI/CD Pipeline / pipeline (push) Failing after 3m50s
Some checks failed
CI/CD Pipeline / pipeline (push) Failing after 3m50s
Transparency opérationnelle UnionFlow — opérations sensibles consultables en temps réel selon scope. Repository - findRecent(limit) : N opérations les plus récentes, toutes orgs - findRecentByOrganisation(orgId, limit) : pour scope ORG - findRecentByUser(userId, limit) : pour scope SELF - Tous clamps limit à [1, 500] (sécurité DoS) Service AuditTrailQueryService - listerRecentes(scope, orgId, userId, limit) : dispatcher SELF/ORG/ALL avec fallback liste vide si UUID requis manquant ; insensible à la casse Resource REST AuditTrailOperationResource - GET /api/audit-trail/recent?scope=SELF&orgId=...&userId=...&limit=50 - Default scope=SELF, limit=50 - @RolesAllowed étendu (MEMBRE inclus pour scope SELF) — un membre peut voir SES propres opérations Tests (7 nouveaux, 11/11 cumulé service) - recentes_All / Null defaults to ALL / Org / OrgWithoutId / Self / SelfWithoutUser / CaseInsensitive
This commit is contained in:
@@ -47,4 +47,21 @@ public class AuditTrailOperationRepository
|
||||
+ "ORDER BY operationAt DESC",
|
||||
organisationId, from, to);
|
||||
}
|
||||
|
||||
/** N opérations les plus récentes — toutes organisations confondues (Live Feed). */
|
||||
public List<AuditTrailOperation> findRecent(int limit) {
|
||||
return find("ORDER BY operationAt DESC").page(0, Math.max(1, Math.min(limit, 500))).list();
|
||||
}
|
||||
|
||||
/** N opérations les plus récentes pour une organisation. */
|
||||
public List<AuditTrailOperation> findRecentByOrganisation(UUID organisationId, int limit) {
|
||||
return find("organisationActiveId = ?1 ORDER BY operationAt DESC", organisationId)
|
||||
.page(0, Math.max(1, Math.min(limit, 500))).list();
|
||||
}
|
||||
|
||||
/** N opérations les plus récentes d'un utilisateur. */
|
||||
public List<AuditTrailOperation> findRecentByUser(UUID userId, int limit) {
|
||||
return find("userId = ?1 ORDER BY operationAt DESC", userId)
|
||||
.page(0, Math.max(1, Math.min(limit, 500))).list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,30 @@ public class AuditTrailOperationResource {
|
||||
return queryService.operationsFinancieres(orgId, fromDt, toDt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Live Activity Feed (Sprint 15) — N opérations les plus récentes (transparency).
|
||||
*
|
||||
* <p>Scopes :
|
||||
* <ul>
|
||||
* <li>ALL — toutes orgs (restreint compliance/contrôleurs/super-admin)</li>
|
||||
* <li>ORG — organisation indiquée (admin org / président / officers)</li>
|
||||
* <li>SELF (défaut) — opérations de l'utilisateur indiqué (n'importe quel rôle peut voir les siennes)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Limit clampé à [1, 500] côté repository (sécurité contre DoS).
|
||||
*/
|
||||
@GET
|
||||
@Path("/recent")
|
||||
@RolesAllowed({"COMPLIANCE_OFFICER", "CONTROLEUR_INTERNE", "ADMIN_ORGANISATION",
|
||||
"PRESIDENT", "TRESORIER", "MEMBRE", "SUPER_ADMIN"})
|
||||
public List<AuditTrailOperationResponse> recent(
|
||||
@QueryParam("scope") @jakarta.ws.rs.DefaultValue("SELF") String scope,
|
||||
@QueryParam("orgId") UUID orgId,
|
||||
@QueryParam("userId") UUID userId,
|
||||
@QueryParam("limit") @jakarta.ws.rs.DefaultValue("50") int limit) {
|
||||
return queryService.listerRecentes(scope, orgId, userId, limit);
|
||||
}
|
||||
|
||||
private LocalDateTime parseDateTime(String input, LocalDateTime fallback) {
|
||||
if (input == null || input.isBlank()) return fallback;
|
||||
try {
|
||||
|
||||
@@ -52,6 +52,32 @@ public class AuditTrailQueryService {
|
||||
.map(this::toResponse).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Live Feed (Sprint 15) — N opérations les plus récentes selon le scope demandé.
|
||||
*
|
||||
* @param scope ALL (toutes orgs — restreint COMPLIANCE_OFFICER/CONTROLEUR_INTERNE/SUPER_ADMIN),
|
||||
* ORG (organisation passée), SELF (utilisateur passé)
|
||||
* @param organisationId requis pour scope=ORG
|
||||
* @param userId requis pour scope=SELF
|
||||
* @param limit clampé à [1, 500]
|
||||
*/
|
||||
public List<AuditTrailOperationResponse> listerRecentes(
|
||||
String scope, UUID organisationId, UUID userId, int limit) {
|
||||
String s = scope == null ? "ALL" : scope.toUpperCase();
|
||||
return switch (s) {
|
||||
case "ORG" -> organisationId == null
|
||||
? java.util.List.of()
|
||||
: repository.findRecentByOrganisation(organisationId, limit)
|
||||
.stream().map(this::toResponse).toList();
|
||||
case "SELF" -> userId == null
|
||||
? java.util.List.of()
|
||||
: repository.findRecentByUser(userId, limit)
|
||||
.stream().map(this::toResponse).toList();
|
||||
default -> repository.findRecent(limit)
|
||||
.stream().map(this::toResponse).toList();
|
||||
};
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// Mapping Entity ↔ DTO
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user