fix(disaster-recovery 2/2): restaurer 242 fichiers Java modifiés par a72ab54
Some checks failed
CI/CD Pipeline / pipeline (push) Failing after 3m22s

Suite à la récupération précédente (044ca4b) qui n'avait restauré que les
fichiers SUPPRIMÉS, ce commit restaure les MODIFICATIONS d'entités/services
qui étaient nécessaires pour que les fichiers restaurés compilent.

Restaurés depuis a72ab54^ (= 31330d9 + corrections) :
- Entities : Organisation, FormuleAbonnement, AuditService, MembreOrganisation, SouscriptionOrganisation, etc.
- Services : MigrerOrganisationsVersKeycloakService, ComptabilitePdfService, KycAmlService, AuditService.logKycRisqueEleve, etc.
- Resources : PaiementUnifieResource, etc.

Backend compile désormais (BUILD SUCCESS).
This commit is contained in:
2026-04-25 01:05:08 +00:00
parent 044ca4bd7e
commit 6e9841b3bb
242 changed files with 38000 additions and 37312 deletions

View File

@@ -1,88 +1,88 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.service.OrganisationService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
import java.util.Map;
import java.util.UUID;
/**
* API réservée au SUPER_ADMIN pour associer un utilisateur (par email) à une organisation.
* Permet à un admin d'organisation de voir « Mes organisations » après connexion.
*/
@Path("/api/admin/associer-organisation")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Admin - Association", description = "Associer un utilisateur à une organisation (SUPER_ADMIN)")
@RolesAllowed("SUPER_ADMIN")
public class AdminAssocierOrganisationResource {
private static final Logger LOG = Logger.getLogger(AdminAssocierOrganisationResource.class);
@Inject
OrganisationService organisationService;
/**
* Associe l'utilisateur ayant l'email donné à l'organisation indiquée.
* Crée un Membre minimal si nécessaire, puis le lien MembreOrganisation (idempotent).
*/
@POST
@Operation(
summary = "Associer un compte à une organisation",
description = "En tant que super admin, associe l'utilisateur (email) à une organisation. "
+ "Si aucun Membre n'existe pour cet email, une fiche minimale est créée. "
+ "L'utilisateur pourra alors voir cette organisation dans « Mes organisations »."
)
public Response associerOrganisation(AssocierOrganisationRequest request) {
if (request == null || request.email() == null || request.email().isBlank()) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "L'email est obligatoire"))
.build();
}
if (request.organisationId() == null) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "L'organisation (organisationId) est obligatoire"))
.build();
}
try {
organisationService.associerUtilisateurAOrganisation(request.email().trim(), request.organisationId());
LOG.infof("Association réussie: %s -> organisation %s", request.email(), request.organisationId());
return Response.ok(Map.of(
"success", true,
"message", "Utilisateur associé à l'organisation avec succès.",
"email", request.email(),
"organisationId", request.organisationId().toString()
)).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", "Non trouvé", "message", e.getMessage()))
.build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "Requête invalide", "message", e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur association organisation: %s", e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur serveur", "message", e.getMessage()))
.build();
}
}
/**
* Corps de la requête pour associer un utilisateur à une organisation.
*/
public record AssocierOrganisationRequest(
@NotBlank(message = "L'email est obligatoire") String email,
@NotNull(message = "L'organisation est obligatoire") UUID organisationId
) {}
}
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.service.OrganisationService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
import java.util.Map;
import java.util.UUID;
/**
* API réservée au SUPER_ADMIN pour associer un utilisateur (par email) à une organisation.
* Permet à un admin d'organisation de voir « Mes organisations » après connexion.
*/
@Path("/api/admin/associer-organisation")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Admin - Association", description = "Associer un utilisateur à une organisation (SUPER_ADMIN)")
@RolesAllowed("SUPER_ADMIN")
public class AdminAssocierOrganisationResource {
private static final Logger LOG = Logger.getLogger(AdminAssocierOrganisationResource.class);
@Inject
OrganisationService organisationService;
/**
* Associe l'utilisateur ayant l'email donné à l'organisation indiquée.
* Crée un Membre minimal si nécessaire, puis le lien MembreOrganisation (idempotent).
*/
@POST
@Operation(
summary = "Associer un compte à une organisation",
description = "En tant que super admin, associe l'utilisateur (email) à une organisation. "
+ "Si aucun Membre n'existe pour cet email, une fiche minimale est créée. "
+ "L'utilisateur pourra alors voir cette organisation dans « Mes organisations »."
)
public Response associerOrganisation(AssocierOrganisationRequest request) {
if (request == null || request.email() == null || request.email().isBlank()) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "L'email est obligatoire"))
.build();
}
if (request.organisationId() == null) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "L'organisation (organisationId) est obligatoire"))
.build();
}
try {
organisationService.associerUtilisateurAOrganisation(request.email().trim(), request.organisationId());
LOG.infof("Association réussie: %s -> organisation %s", request.email(), request.organisationId());
return Response.ok(Map.of(
"success", true,
"message", "Utilisateur associé à l'organisation avec succès.",
"email", request.email(),
"organisationId", request.organisationId().toString()
)).build();
} catch (jakarta.ws.rs.NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", "Non trouvé", "message", e.getMessage()))
.build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "Requête invalide", "message", e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur association organisation: %s", e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur serveur", "message", e.getMessage()))
.build();
}
}
/**
* Corps de la requête pour associer un utilisateur à une organisation.
*/
public record AssocierOrganisationRequest(
@NotBlank(message = "L'email est obligatoire") String email,
@NotNull(message = "L'organisation est obligatoire") UUID organisationId
) {}
}

View File

@@ -1,162 +1,162 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.service.AdminUserService;
import dev.lions.user.manager.dto.role.RoleDTO;
import dev.lions.user.manager.dto.user.UserDTO;
import dev.lions.user.manager.dto.user.UserSearchResultDTO;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
import java.util.List;
import java.util.Map;
/**
* API admin pour la gestion des utilisateurs Keycloak (proxy vers lions-user-manager).
* Réservé au rôle SUPER_ADMIN — la vérification est faite par @RolesAllowed au niveau classe.
*/
@Path("/api/admin/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Admin - Utilisateurs", description = "Gestion des utilisateurs Keycloak (SUPER_ADMIN)")
@RolesAllowed("SUPER_ADMIN")
public class AdminUserResource {
private static final Logger LOG = Logger.getLogger(AdminUserResource.class);
@Inject
AdminUserService adminUserService;
@GET
@Operation(summary = "Lister les utilisateurs", description = "Liste paginée des utilisateurs du realm")
public Response list(
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("20") int size,
@QueryParam("search") String search
) {
try {
UserSearchResultDTO result = adminUserService.searchUsers(page, size, search);
return Response.ok(result).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur liste utilisateurs: %s", e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur serveur", "message", e.getMessage()))
.build();
}
}
@GET
@Path("/{id}")
@Operation(summary = "Détail utilisateur")
public Response getById(@PathParam("id") String id) {
try {
UserDTO user = adminUserService.getUserById(id);
if (user == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return Response.ok(user).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur détail utilisateur %s: %s", id, e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur serveur", "message", e.getMessage()))
.build();
}
}
@GET
@Path("/roles")
@Operation(summary = "Liste des rôles realm")
public Response listRoles() {
try {
List<RoleDTO> roles = adminUserService.getRealmRoles();
return Response.ok(roles).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur liste rôles: %s", e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur serveur", "message", e.getMessage()))
.build();
}
}
@GET
@Path("/{id}/roles")
@Operation(summary = "Rôles d'un utilisateur")
public Response getUserRoles(@PathParam("id") String id) {
try {
List<RoleDTO> roles = adminUserService.getUserRoles(id);
return Response.ok(roles).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur rôles utilisateur %s: %s", id, e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur serveur", "message", e.getMessage()))
.build();
}
}
@PUT
@Path("/{id}/roles")
@Operation(summary = "Mettre à jour les rôles d'un utilisateur")
public Response setUserRoles(@PathParam("id") String id, List<String> roleNames) {
try {
adminUserService.setUserRoles(id, roleNames);
return Response.ok(Map.of("success", true, "userId", id)).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur mise à jour rôles %s: %s", id, e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur serveur", "message", e.getMessage()))
.build();
}
}
@POST
@Operation(summary = "Créer un utilisateur", description = "Crée un nouvel utilisateur Keycloak (proxy lions-user-manager)")
public Response createUser(UserDTO user) {
try {
UserDTO created = adminUserService.createUser(user);
return Response.status(Response.Status.CREATED).entity(created).build();
} catch (IllegalArgumentException e) {
LOG.warnf("Création utilisateur refusée: %s", e.getMessage());
return Response.status(Response.Status.CONFLICT)
.entity(Map.of("error", "Conflit", "message", e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur création utilisateur: %s", e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur serveur", "message", e.getMessage()))
.build();
}
}
@PUT
@Path("/{id}")
@Operation(summary = "Mettre à jour un utilisateur", description = "Met à jour un utilisateur (au minimum enabled)")
public Response updateUser(@PathParam("id") String id, UserDTO user) {
try {
if (user.getEnabled() != null) {
UserDTO updated = adminUserService.updateUserEnabled(id, user.getEnabled());
return Response.ok(updated).build();
}
UserDTO updated = adminUserService.updateUser(id, user);
return Response.ok(updated).build();
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().contains("non trouvé")) {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", "Non trouvé", "message", e.getMessage()))
.build();
}
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "Requête invalide", "message", e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur mise à jour utilisateur %s: %s", id, e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur serveur", "message", e.getMessage()))
.build();
}
}
}
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.service.AdminUserService;
import dev.lions.user.manager.dto.role.RoleDTO;
import dev.lions.user.manager.dto.user.UserDTO;
import dev.lions.user.manager.dto.user.UserSearchResultDTO;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
import java.util.List;
import java.util.Map;
/**
* API admin pour la gestion des utilisateurs Keycloak (proxy vers lions-user-manager).
* Réservé au rôle SUPER_ADMIN — la vérification est faite par @RolesAllowed au niveau classe.
*/
@Path("/api/admin/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Admin - Utilisateurs", description = "Gestion des utilisateurs Keycloak (SUPER_ADMIN)")
@RolesAllowed("SUPER_ADMIN")
public class AdminUserResource {
private static final Logger LOG = Logger.getLogger(AdminUserResource.class);
@Inject
AdminUserService adminUserService;
@GET
@Operation(summary = "Lister les utilisateurs", description = "Liste paginée des utilisateurs du realm")
public Response list(
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("20") int size,
@QueryParam("search") String search
) {
try {
UserSearchResultDTO result = adminUserService.searchUsers(page, size, search);
return Response.ok(result).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur liste utilisateurs: %s", e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur serveur", "message", e.getMessage()))
.build();
}
}
@GET
@Path("/{id}")
@Operation(summary = "Détail utilisateur")
public Response getById(@PathParam("id") String id) {
try {
UserDTO user = adminUserService.getUserById(id);
if (user == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return Response.ok(user).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur détail utilisateur %s: %s", id, e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur serveur", "message", e.getMessage()))
.build();
}
}
@GET
@Path("/roles")
@Operation(summary = "Liste des rôles realm")
public Response listRoles() {
try {
List<RoleDTO> roles = adminUserService.getRealmRoles();
return Response.ok(roles).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur liste rôles: %s", e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur serveur", "message", e.getMessage()))
.build();
}
}
@GET
@Path("/{id}/roles")
@Operation(summary = "Rôles d'un utilisateur")
public Response getUserRoles(@PathParam("id") String id) {
try {
List<RoleDTO> roles = adminUserService.getUserRoles(id);
return Response.ok(roles).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur rôles utilisateur %s: %s", id, e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur serveur", "message", e.getMessage()))
.build();
}
}
@PUT
@Path("/{id}/roles")
@Operation(summary = "Mettre à jour les rôles d'un utilisateur")
public Response setUserRoles(@PathParam("id") String id, List<String> roleNames) {
try {
adminUserService.setUserRoles(id, roleNames);
return Response.ok(Map.of("success", true, "userId", id)).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur mise à jour rôles %s: %s", id, e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur serveur", "message", e.getMessage()))
.build();
}
}
@POST
@Operation(summary = "Créer un utilisateur", description = "Crée un nouvel utilisateur Keycloak (proxy lions-user-manager)")
public Response createUser(UserDTO user) {
try {
UserDTO created = adminUserService.createUser(user);
return Response.status(Response.Status.CREATED).entity(created).build();
} catch (IllegalArgumentException e) {
LOG.warnf("Création utilisateur refusée: %s", e.getMessage());
return Response.status(Response.Status.CONFLICT)
.entity(Map.of("error", "Conflit", "message", e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur création utilisateur: %s", e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur serveur", "message", e.getMessage()))
.build();
}
}
@PUT
@Path("/{id}")
@Operation(summary = "Mettre à jour un utilisateur", description = "Met à jour un utilisateur (au minimum enabled)")
public Response updateUser(@PathParam("id") String id, UserDTO user) {
try {
if (user.getEnabled() != null) {
UserDTO updated = adminUserService.updateUserEnabled(id, user.getEnabled());
return Response.ok(updated).build();
}
UserDTO updated = adminUserService.updateUser(id, user);
return Response.ok(updated).build();
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().contains("non trouvé")) {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", "Non trouvé", "message", e.getMessage()))
.build();
}
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "Requête invalide", "message", e.getMessage()))
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur mise à jour utilisateur %s: %s", id, e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur serveur", "message", e.getMessage()))
.build();
}
}
}

View File

@@ -1,168 +1,168 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.lcbft.AlerteLcbFtResponse;
import dev.lions.unionflow.server.entity.AlerteLcbFt;
import dev.lions.unionflow.server.repository.AlerteLcbFtRepository;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* API REST pour la gestion des alertes LCB-FT.
*
* @author UnionFlow Team
* @version 1.0
* @since 2026-03-15
*/
@Path("/api/alertes-lcb-ft")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Alertes LCB-FT", description = "Gestion des alertes Lutte Contre le Blanchiment")
public class AlerteLcbFtResource {
@Inject
AlerteLcbFtRepository alerteLcbFtRepository;
/**
* Récupère les alertes LCB-FT avec filtres et pagination.
*/
@GET
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Liste des alertes LCB-FT", description = "Récupère les alertes avec filtrage et pagination")
public Response getAlertes(
@QueryParam("organisationId") String organisationId,
@QueryParam("typeAlerte") String typeAlerte,
@QueryParam("traitee") Boolean traitee,
@QueryParam("dateDebut") String dateDebut,
@QueryParam("dateFin") String dateFin,
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("20") int size
) {
UUID orgId = organisationId != null && !organisationId.isBlank() ? UUID.fromString(organisationId) : null;
LocalDateTime debut = dateDebut != null && !dateDebut.isBlank() ? LocalDateTime.parse(dateDebut) : null;
LocalDateTime fin = dateFin != null && !dateFin.isBlank() ? LocalDateTime.parse(dateFin) : null;
List<AlerteLcbFt> alertes = alerteLcbFtRepository.search(
orgId,
typeAlerte,
traitee,
debut,
fin,
page,
size
);
long total = alerteLcbFtRepository.count(orgId, typeAlerte, traitee, debut, fin);
List<AlerteLcbFtResponse> responses = alertes.stream()
.map(this::mapToResponse)
.collect(Collectors.toList());
Map<String, Object> result = new HashMap<>();
result.put("content", responses);
result.put("totalElements", total);
result.put("totalPages", (int) Math.ceil((double) total / size));
result.put("currentPage", page);
result.put("pageSize", size);
return Response.ok(result).build();
}
/**
* Récupère une alerte par son ID.
*/
@GET
@Path("/{id}")
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Détails d'une alerte", description = "Récupère une alerte par son ID")
public Response getAlerteById(@PathParam("id") String id) {
AlerteLcbFt alerte = alerteLcbFtRepository.findById(UUID.fromString(id));
if (alerte == null) {
throw new NotFoundException("Alerte non trouvée");
}
return Response.ok(mapToResponse(alerte)).build();
}
/**
* Marque une alerte comme traitée.
*/
@POST
@Path("/{id}/traiter")
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Traiter une alerte", description = "Marque une alerte comme traitée avec un commentaire")
public Response traiterAlerte(
@PathParam("id") String id,
Map<String, String> body
) {
AlerteLcbFt alerte = alerteLcbFtRepository.findById(UUID.fromString(id));
if (alerte == null) {
throw new NotFoundException("Alerte non trouvée");
}
alerte.setTraitee(true);
alerte.setDateTraitement(LocalDateTime.now());
String traiteParStr = body.get("traitePar");
if (traiteParStr != null && !traiteParStr.isBlank()) {
try {
alerte.setTraitePar(UUID.fromString(traiteParStr));
} catch (IllegalArgumentException e) {
throw new BadRequestException("traitePar doit être un UUID valide");
}
}
alerte.setCommentaireTraitement(body.get("commentaire"));
alerteLcbFtRepository.persist(alerte);
return Response.ok(mapToResponse(alerte)).build();
}
/**
* Compte les alertes non traitées.
*/
@GET
@Path("/stats/non-traitees")
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Statistiques alertes", description = "Nombre d'alertes non traitées")
public Response getStatsNonTraitees(@QueryParam("organisationId") String organisationId) {
UUID orgId = organisationId != null && !organisationId.isBlank() ? UUID.fromString(organisationId) : null;
long count = alerteLcbFtRepository.countNonTraitees(orgId);
return Response.ok(Map.of("count", count)).build();
}
private AlerteLcbFtResponse mapToResponse(AlerteLcbFt alerte) {
return AlerteLcbFtResponse.builder()
.id(alerte.getId().toString())
.organisationId(alerte.getOrganisation() != null ? alerte.getOrganisation().getId().toString() : null)
.organisationNom(alerte.getOrganisation() != null ? alerte.getOrganisation().getNom() : null)
.membreId(alerte.getMembre() != null ? alerte.getMembre().getId().toString() : null)
.membreNomComplet(alerte.getMembre() != null ?
alerte.getMembre().getPrenom() + " " + alerte.getMembre().getNom() : null)
.typeAlerte(alerte.getTypeAlerte())
.dateAlerte(alerte.getDateAlerte())
.description(alerte.getDescription())
.details(alerte.getDetails())
.montant(alerte.getMontant())
.seuil(alerte.getSeuil())
.typeOperation(alerte.getTypeOperation())
.transactionRef(alerte.getTransactionRef())
.severite(alerte.getSeverite())
.traitee(alerte.getTraitee())
.dateTraitement(alerte.getDateTraitement())
.traitePar(alerte.getTraitePar() != null ? alerte.getTraitePar().toString() : null)
.commentaireTraitement(alerte.getCommentaireTraitement())
.build();
}
}
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.lcbft.AlerteLcbFtResponse;
import dev.lions.unionflow.server.entity.AlerteLcbFt;
import dev.lions.unionflow.server.repository.AlerteLcbFtRepository;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* API REST pour la gestion des alertes LCB-FT.
*
* @author UnionFlow Team
* @version 1.0
* @since 2026-03-15
*/
@Path("/api/alertes-lcb-ft")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Alertes LCB-FT", description = "Gestion des alertes Lutte Contre le Blanchiment")
public class AlerteLcbFtResource {
@Inject
AlerteLcbFtRepository alerteLcbFtRepository;
/**
* Récupère les alertes LCB-FT avec filtres et pagination.
*/
@GET
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Liste des alertes LCB-FT", description = "Récupère les alertes avec filtrage et pagination")
public Response getAlertes(
@QueryParam("organisationId") String organisationId,
@QueryParam("typeAlerte") String typeAlerte,
@QueryParam("traitee") Boolean traitee,
@QueryParam("dateDebut") String dateDebut,
@QueryParam("dateFin") String dateFin,
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("20") int size
) {
UUID orgId = organisationId != null && !organisationId.isBlank() ? UUID.fromString(organisationId) : null;
LocalDateTime debut = dateDebut != null && !dateDebut.isBlank() ? LocalDateTime.parse(dateDebut) : null;
LocalDateTime fin = dateFin != null && !dateFin.isBlank() ? LocalDateTime.parse(dateFin) : null;
List<AlerteLcbFt> alertes = alerteLcbFtRepository.search(
orgId,
typeAlerte,
traitee,
debut,
fin,
page,
size
);
long total = alerteLcbFtRepository.count(orgId, typeAlerte, traitee, debut, fin);
List<AlerteLcbFtResponse> responses = alertes.stream()
.map(this::mapToResponse)
.collect(Collectors.toList());
Map<String, Object> result = new HashMap<>();
result.put("content", responses);
result.put("totalElements", total);
result.put("totalPages", (int) Math.ceil((double) total / size));
result.put("currentPage", page);
result.put("pageSize", size);
return Response.ok(result).build();
}
/**
* Récupère une alerte par son ID.
*/
@GET
@Path("/{id}")
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Détails d'une alerte", description = "Récupère une alerte par son ID")
public Response getAlerteById(@PathParam("id") String id) {
AlerteLcbFt alerte = alerteLcbFtRepository.findById(UUID.fromString(id));
if (alerte == null) {
throw new NotFoundException("Alerte non trouvée");
}
return Response.ok(mapToResponse(alerte)).build();
}
/**
* Marque une alerte comme traitée.
*/
@POST
@Path("/{id}/traiter")
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Traiter une alerte", description = "Marque une alerte comme traitée avec un commentaire")
public Response traiterAlerte(
@PathParam("id") String id,
Map<String, String> body
) {
AlerteLcbFt alerte = alerteLcbFtRepository.findById(UUID.fromString(id));
if (alerte == null) {
throw new NotFoundException("Alerte non trouvée");
}
alerte.setTraitee(true);
alerte.setDateTraitement(LocalDateTime.now());
String traiteParStr = body.get("traitePar");
if (traiteParStr != null && !traiteParStr.isBlank()) {
try {
alerte.setTraitePar(UUID.fromString(traiteParStr));
} catch (IllegalArgumentException e) {
throw new BadRequestException("traitePar doit être un UUID valide");
}
}
alerte.setCommentaireTraitement(body.get("commentaire"));
alerteLcbFtRepository.persist(alerte);
return Response.ok(mapToResponse(alerte)).build();
}
/**
* Compte les alertes non traitées.
*/
@GET
@Path("/stats/non-traitees")
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Statistiques alertes", description = "Nombre d'alertes non traitées")
public Response getStatsNonTraitees(@QueryParam("organisationId") String organisationId) {
UUID orgId = organisationId != null && !organisationId.isBlank() ? UUID.fromString(organisationId) : null;
long count = alerteLcbFtRepository.countNonTraitees(orgId);
return Response.ok(Map.of("count", count)).build();
}
private AlerteLcbFtResponse mapToResponse(AlerteLcbFt alerte) {
return AlerteLcbFtResponse.builder()
.id(alerte.getId().toString())
.organisationId(alerte.getOrganisation() != null ? alerte.getOrganisation().getId().toString() : null)
.organisationNom(alerte.getOrganisation() != null ? alerte.getOrganisation().getNom() : null)
.membreId(alerte.getMembre() != null ? alerte.getMembre().getId().toString() : null)
.membreNomComplet(alerte.getMembre() != null ?
alerte.getMembre().getPrenom() + " " + alerte.getMembre().getNom() : null)
.typeAlerte(alerte.getTypeAlerte())
.dateAlerte(alerte.getDateAlerte())
.description(alerte.getDescription())
.details(alerte.getDetails())
.montant(alerte.getMontant())
.seuil(alerte.getSeuil())
.typeOperation(alerte.getTypeOperation())
.transactionRef(alerte.getTransactionRef())
.severite(alerte.getSeverite())
.traitee(alerte.getTraitee())
.dateTraitement(alerte.getDateTraitement())
.traitePar(alerte.getTraitePar() != null ? alerte.getTraitePar().toString() : null)
.commentaireTraitement(alerte.getCommentaireTraitement())
.build();
}
}

View File

@@ -1,314 +1,314 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.analytics.AnalyticsDataResponse;
import dev.lions.unionflow.server.api.dto.analytics.DashboardWidgetResponse;
import dev.lions.unionflow.server.api.dto.analytics.KPITrendResponse;
import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse;
import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
import dev.lions.unionflow.server.service.AnalyticsService;
import dev.lions.unionflow.server.service.KPICalculatorService;
import io.quarkus.security.Authenticated;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
/**
* Ressource REST pour les analytics et métriques UnionFlow
*
* <p>Cette ressource expose les APIs pour accéder aux données analytics, KPI, tendances et widgets
* de tableau de bord.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@Path("/api/v1/analytics")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Authenticated
@Tag(name = "Analytics", description = "APIs pour les analytics et métriques")
public class AnalyticsResource {
private static final Logger log = Logger.getLogger(AnalyticsResource.class);
@Inject AnalyticsService analyticsService;
@Inject KPICalculatorService kpiCalculatorService;
/** Calcule une métrique analytics pour une période donnée */
@GET
@Path("/metriques/{typeMetrique}")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Calculer une métrique analytics",
description = "Calcule une métrique spécifique pour une période et organisation données")
@APIResponse(responseCode = "200", description = "Métrique calculée avec succès")
@APIResponse(responseCode = "400", description = "Paramètres invalides")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response calculerMetrique(
@Parameter(description = "Type de métrique à calculer", required = true)
@PathParam("typeMetrique")
TypeMetrique typeMetrique,
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
PeriodeAnalyse periodeAnalyse,
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
UUID organisationId) {
try {
log.infof(
"Calcul de la métrique %s pour la période %s et l'organisation %s",
typeMetrique, periodeAnalyse, organisationId);
AnalyticsDataResponse result =
analyticsService.calculerMetrique(typeMetrique, periodeAnalyse, organisationId);
return Response.ok(result).build();
} catch (Exception e) {
log.errorf(e, "Erreur lors du calcul de la métrique %s: %s", typeMetrique, e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of("error", "Erreur lors du calcul de la métrique", "message", e.getMessage()))
.build();
}
}
/** Calcule les tendances d'un KPI sur une période */
@GET
@Path("/tendances/{typeMetrique}")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Calculer la tendance d'un KPI",
description = "Calcule l'évolution et les tendances d'un KPI sur une période donnée")
@APIResponse(responseCode = "200", description = "Tendance calculée avec succès")
@APIResponse(responseCode = "400", description = "Paramètres invalides")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response calculerTendanceKPI(
@Parameter(description = "Type de métrique pour la tendance", required = true)
@PathParam("typeMetrique")
TypeMetrique typeMetrique,
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
PeriodeAnalyse periodeAnalyse,
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
UUID organisationId) {
try {
log.infof(
"Calcul de la tendance KPI %s pour la période %s et l'organisation %s",
typeMetrique, periodeAnalyse, organisationId);
KPITrendResponse result =
analyticsService.calculerTendanceKPI(typeMetrique, periodeAnalyse, organisationId);
return Response.ok(result).build();
} catch (Exception e) {
log.errorf(
e, "Erreur lors du calcul de la tendance KPI %s: %s", typeMetrique, e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of("error", "Erreur lors du calcul de la tendance", "message", e.getMessage()))
.build();
}
}
/** Obtient tous les KPI pour une organisation */
@GET
@Path("/kpis")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir tous les KPI",
description = "Récupère tous les KPI calculés pour une organisation et période données")
@APIResponse(responseCode = "200", description = "KPI récupérés avec succès")
@APIResponse(responseCode = "400", description = "Paramètres invalides")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response obtenirTousLesKPI(
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
PeriodeAnalyse periodeAnalyse,
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
UUID organisationId) {
try {
log.infof(
"Récupération de tous les KPI pour la période %s et l'organisation %s",
periodeAnalyse, organisationId);
Map<TypeMetrique, BigDecimal> kpis =
kpiCalculatorService.calculerTousLesKPI(
organisationId, periodeAnalyse.getDateDebut(), periodeAnalyse.getDateFin());
return Response.ok(kpis).build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des KPI: {}", e.getMessage(), e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of("error", "Erreur lors de la récupération des KPI", "message", e.getMessage()))
.build();
}
}
/** Calcule le KPI de performance globale */
@GET
@Path("/performance-globale")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(
summary = "Calculer la performance globale",
description = "Calcule le score de performance globale de l'organisation")
@APIResponse(responseCode = "200", description = "Performance globale calculée avec succès")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response calculerPerformanceGlobale(
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
PeriodeAnalyse periodeAnalyse,
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
UUID organisationId) {
try {
log.infof(
"Calcul de la performance globale pour la période %s et l'organisation %s",
periodeAnalyse, organisationId);
BigDecimal performanceGlobale =
kpiCalculatorService.calculerKPIPerformanceGlobale(
organisationId, periodeAnalyse.getDateDebut(), periodeAnalyse.getDateFin());
return Response.ok(
Map.of(
"performanceGlobale", performanceGlobale,
"periode", periodeAnalyse,
"organisationId", organisationId,
"dateCalcul", java.time.LocalDateTime.now()))
.build();
} catch (Exception e) {
log.error("Erreur lors du calcul de la performance globale: {}", e.getMessage(), e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error",
"Erreur lors du calcul de la performance globale",
"message",
e.getMessage()))
.build();
}
}
/** Obtient les évolutions des KPI par rapport à la période précédente */
@GET
@Path("/evolutions")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir les évolutions des KPI",
description = "Récupère les évolutions des KPI par rapport à la période précédente")
@APIResponse(responseCode = "200", description = "Évolutions récupérées avec succès")
@APIResponse(responseCode = "400", description = "Paramètres invalides")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response obtenirEvolutionsKPI(
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
PeriodeAnalyse periodeAnalyse,
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
UUID organisationId) {
try {
log.infof(
"Récupération des évolutions KPI pour la période %s et l'organisation %s",
periodeAnalyse, organisationId);
Map<TypeMetrique, BigDecimal> evolutions =
kpiCalculatorService.calculerEvolutionsKPI(
organisationId, periodeAnalyse.getDateDebut(), periodeAnalyse.getDateFin());
return Response.ok(evolutions).build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des évolutions KPI: {}", e.getMessage(), e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error",
"Erreur lors de la récupération des évolutions",
"message",
e.getMessage()))
.build();
}
}
/** Obtient les widgets du tableau de bord pour un utilisateur */
@GET
@Path("/dashboard/widgets")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir les widgets du tableau de bord",
description = "Récupère tous les widgets configurés pour le tableau de bord de l'utilisateur")
@APIResponse(responseCode = "200", description = "Widgets récupérés avec succès")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response obtenirWidgetsTableauBord(
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
UUID organisationId,
@Parameter(description = "ID de l'utilisateur", required = true)
@QueryParam("utilisateurId")
@NotNull
UUID utilisateurId) {
try {
log.infof(
"Récupération des widgets du tableau de bord pour l'organisation %s et l'utilisateur %s",
organisationId, utilisateurId);
List<DashboardWidgetResponse> widgets =
analyticsService.obtenirMetriquesTableauBord(organisationId, utilisateurId);
return Response.ok(widgets).build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des widgets: {}", e.getMessage(), e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error", "Erreur lors de la récupération des widgets", "message", e.getMessage()))
.build();
}
}
/** Obtient les types de métriques disponibles */
@GET
@Path("/types-metriques")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir les types de métriques disponibles",
description = "Récupère la liste de tous les types de métriques disponibles")
@APIResponse(responseCode = "200", description = "Types de métriques récupérés avec succès")
public Response obtenirTypesMetriques() {
log.info("Récupération des types de métriques disponibles");
TypeMetrique[] typesMetriques = TypeMetrique.values();
return Response.ok(Map.of("typesMetriques", typesMetriques, "total", typesMetriques.length))
.build();
}
/** Obtient les périodes d'analyse disponibles */
@GET
@Path("/periodes-analyse")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir les périodes d'analyse disponibles",
description = "Récupère la liste de toutes les périodes d'analyse disponibles")
@APIResponse(responseCode = "200", description = "Périodes d'analyse récupérées avec succès")
public Response obtenirPeriodesAnalyse() {
log.info("Récupération des périodes d'analyse disponibles");
PeriodeAnalyse[] periodesAnalyse = PeriodeAnalyse.values();
return Response.ok(Map.of("periodesAnalyse", periodesAnalyse, "total", periodesAnalyse.length))
.build();
}
}
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.analytics.AnalyticsDataResponse;
import dev.lions.unionflow.server.api.dto.analytics.DashboardWidgetResponse;
import dev.lions.unionflow.server.api.dto.analytics.KPITrendResponse;
import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse;
import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
import dev.lions.unionflow.server.service.AnalyticsService;
import dev.lions.unionflow.server.service.KPICalculatorService;
import io.quarkus.security.Authenticated;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
/**
* Ressource REST pour les analytics et métriques UnionFlow
*
* <p>Cette ressource expose les APIs pour accéder aux données analytics, KPI, tendances et widgets
* de tableau de bord.
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
*/
@Path("/api/v1/analytics")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Authenticated
@Tag(name = "Analytics", description = "APIs pour les analytics et métriques")
public class AnalyticsResource {
private static final Logger log = Logger.getLogger(AnalyticsResource.class);
@Inject AnalyticsService analyticsService;
@Inject KPICalculatorService kpiCalculatorService;
/** Calcule une métrique analytics pour une période donnée */
@GET
@Path("/metriques/{typeMetrique}")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Calculer une métrique analytics",
description = "Calcule une métrique spécifique pour une période et organisation données")
@APIResponse(responseCode = "200", description = "Métrique calculée avec succès")
@APIResponse(responseCode = "400", description = "Paramètres invalides")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response calculerMetrique(
@Parameter(description = "Type de métrique à calculer", required = true)
@PathParam("typeMetrique")
TypeMetrique typeMetrique,
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
PeriodeAnalyse periodeAnalyse,
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
UUID organisationId) {
try {
log.infof(
"Calcul de la métrique %s pour la période %s et l'organisation %s",
typeMetrique, periodeAnalyse, organisationId);
AnalyticsDataResponse result =
analyticsService.calculerMetrique(typeMetrique, periodeAnalyse, organisationId);
return Response.ok(result).build();
} catch (Exception e) {
log.errorf(e, "Erreur lors du calcul de la métrique %s: %s", typeMetrique, e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of("error", "Erreur lors du calcul de la métrique", "message", e.getMessage()))
.build();
}
}
/** Calcule les tendances d'un KPI sur une période */
@GET
@Path("/tendances/{typeMetrique}")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Calculer la tendance d'un KPI",
description = "Calcule l'évolution et les tendances d'un KPI sur une période donnée")
@APIResponse(responseCode = "200", description = "Tendance calculée avec succès")
@APIResponse(responseCode = "400", description = "Paramètres invalides")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response calculerTendanceKPI(
@Parameter(description = "Type de métrique pour la tendance", required = true)
@PathParam("typeMetrique")
TypeMetrique typeMetrique,
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
PeriodeAnalyse periodeAnalyse,
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
UUID organisationId) {
try {
log.infof(
"Calcul de la tendance KPI %s pour la période %s et l'organisation %s",
typeMetrique, periodeAnalyse, organisationId);
KPITrendResponse result =
analyticsService.calculerTendanceKPI(typeMetrique, periodeAnalyse, organisationId);
return Response.ok(result).build();
} catch (Exception e) {
log.errorf(
e, "Erreur lors du calcul de la tendance KPI %s: %s", typeMetrique, e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of("error", "Erreur lors du calcul de la tendance", "message", e.getMessage()))
.build();
}
}
/** Obtient tous les KPI pour une organisation */
@GET
@Path("/kpis")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir tous les KPI",
description = "Récupère tous les KPI calculés pour une organisation et période données")
@APIResponse(responseCode = "200", description = "KPI récupérés avec succès")
@APIResponse(responseCode = "400", description = "Paramètres invalides")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response obtenirTousLesKPI(
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
PeriodeAnalyse periodeAnalyse,
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
UUID organisationId) {
try {
log.infof(
"Récupération de tous les KPI pour la période %s et l'organisation %s",
periodeAnalyse, organisationId);
Map<TypeMetrique, BigDecimal> kpis =
kpiCalculatorService.calculerTousLesKPI(
organisationId, periodeAnalyse.getDateDebut(), periodeAnalyse.getDateFin());
return Response.ok(kpis).build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des KPI: {}", e.getMessage(), e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of("error", "Erreur lors de la récupération des KPI", "message", e.getMessage()))
.build();
}
}
/** Calcule le KPI de performance globale */
@GET
@Path("/performance-globale")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(
summary = "Calculer la performance globale",
description = "Calcule le score de performance globale de l'organisation")
@APIResponse(responseCode = "200", description = "Performance globale calculée avec succès")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response calculerPerformanceGlobale(
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
PeriodeAnalyse periodeAnalyse,
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
UUID organisationId) {
try {
log.infof(
"Calcul de la performance globale pour la période %s et l'organisation %s",
periodeAnalyse, organisationId);
BigDecimal performanceGlobale =
kpiCalculatorService.calculerKPIPerformanceGlobale(
organisationId, periodeAnalyse.getDateDebut(), periodeAnalyse.getDateFin());
return Response.ok(
Map.of(
"performanceGlobale", performanceGlobale,
"periode", periodeAnalyse,
"organisationId", organisationId,
"dateCalcul", java.time.LocalDateTime.now()))
.build();
} catch (Exception e) {
log.error("Erreur lors du calcul de la performance globale: {}", e.getMessage(), e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error",
"Erreur lors du calcul de la performance globale",
"message",
e.getMessage()))
.build();
}
}
/** Obtient les évolutions des KPI par rapport à la période précédente */
@GET
@Path("/evolutions")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir les évolutions des KPI",
description = "Récupère les évolutions des KPI par rapport à la période précédente")
@APIResponse(responseCode = "200", description = "Évolutions récupérées avec succès")
@APIResponse(responseCode = "400", description = "Paramètres invalides")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response obtenirEvolutionsKPI(
@Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull
PeriodeAnalyse periodeAnalyse,
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
UUID organisationId) {
try {
log.infof(
"Récupération des évolutions KPI pour la période %s et l'organisation %s",
periodeAnalyse, organisationId);
Map<TypeMetrique, BigDecimal> evolutions =
kpiCalculatorService.calculerEvolutionsKPI(
organisationId, periodeAnalyse.getDateDebut(), periodeAnalyse.getDateFin());
return Response.ok(evolutions).build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des évolutions KPI: {}", e.getMessage(), e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error",
"Erreur lors de la récupération des évolutions",
"message",
e.getMessage()))
.build();
}
}
/** Obtient les widgets du tableau de bord pour un utilisateur */
@GET
@Path("/dashboard/widgets")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir les widgets du tableau de bord",
description = "Récupère tous les widgets configurés pour le tableau de bord de l'utilisateur")
@APIResponse(responseCode = "200", description = "Widgets récupérés avec succès")
@APIResponse(responseCode = "403", description = "Accès non autorisé")
public Response obtenirWidgetsTableauBord(
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId")
UUID organisationId,
@Parameter(description = "ID de l'utilisateur", required = true)
@QueryParam("utilisateurId")
@NotNull
UUID utilisateurId) {
try {
log.infof(
"Récupération des widgets du tableau de bord pour l'organisation %s et l'utilisateur %s",
organisationId, utilisateurId);
List<DashboardWidgetResponse> widgets =
analyticsService.obtenirMetriquesTableauBord(organisationId, utilisateurId);
return Response.ok(widgets).build();
} catch (Exception e) {
log.error("Erreur lors de la récupération des widgets: {}", e.getMessage(), e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
Map.of(
"error", "Erreur lors de la récupération des widgets", "message", e.getMessage()))
.build();
}
}
/** Obtient les types de métriques disponibles */
@GET
@Path("/types-metriques")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir les types de métriques disponibles",
description = "Récupère la liste de tous les types de métriques disponibles")
@APIResponse(responseCode = "200", description = "Types de métriques récupérés avec succès")
public Response obtenirTypesMetriques() {
log.info("Récupération des types de métriques disponibles");
TypeMetrique[] typesMetriques = TypeMetrique.values();
return Response.ok(Map.of("typesMetriques", typesMetriques, "total", typesMetriques.length))
.build();
}
/** Obtient les périodes d'analyse disponibles */
@GET
@Path("/periodes-analyse")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
@Operation(
summary = "Obtenir les périodes d'analyse disponibles",
description = "Récupère la liste de toutes les périodes d'analyse disponibles")
@APIResponse(responseCode = "200", description = "Périodes d'analyse récupérées avec succès")
public Response obtenirPeriodesAnalyse() {
log.info("Récupération des périodes d'analyse disponibles");
PeriodeAnalyse[] periodesAnalyse = PeriodeAnalyse.values();
return Response.ok(Map.of("periodesAnalyse", periodesAnalyse, "total", periodesAnalyse.length))
.build();
}
}

View File

@@ -1,132 +1,132 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.backup.request.CreateBackupRequest;
import dev.lions.unionflow.server.api.dto.backup.request.RestoreBackupRequest;
import dev.lions.unionflow.server.api.dto.backup.request.UpdateBackupConfigRequest;
import dev.lions.unionflow.server.api.dto.backup.response.BackupConfigResponse;
import dev.lions.unionflow.server.api.dto.backup.response.BackupResponse;
import dev.lions.unionflow.server.service.BackupService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.List;
import java.util.UUID;
/**
* REST Resource pour la gestion des sauvegardes système
*/
@Slf4j
@Path("/api/backups")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Sauvegardes", description = "Gestion des sauvegardes et restaurations")
public class BackupResource {
@Inject
BackupService backupService;
/**
* Lister toutes les sauvegardes
*/
@GET
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Lister toutes les sauvegardes disponibles")
public List<BackupResponse> getAllBackups() {
log.info("GET /api/backups");
return backupService.getAllBackups();
}
/**
* Récupérer une sauvegarde par ID
*/
@GET
@Path("/{id}")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Récupérer une sauvegarde par ID")
public BackupResponse getBackupById(@PathParam("id") UUID id) {
log.info("GET /api/backups/{}", id);
return backupService.getBackupById(id);
}
/**
* Créer une nouvelle sauvegarde
*/
@POST
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Créer une nouvelle sauvegarde")
public Response createBackup(@Valid CreateBackupRequest request) {
log.info("POST /api/backups - {}", request.getName());
BackupResponse backup = backupService.createBackup(request);
return Response.status(Response.Status.CREATED).entity(backup).build();
}
/**
* Restaurer une sauvegarde
*/
@POST
@Path("/restore")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Restaurer une sauvegarde")
public Response restoreBackup(@Valid RestoreBackupRequest request) {
log.info("POST /api/backups/restore - backupId={}", request.getBackupId());
backupService.restoreBackup(request);
return Response.ok().entity(java.util.Map.of("message", "Restauration en cours")).build();
}
/**
* Supprimer une sauvegarde
*/
@DELETE
@Path("/{id}")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Supprimer une sauvegarde")
public Response deleteBackup(@PathParam("id") UUID id) {
log.info("DELETE /api/backups/{}", id);
backupService.deleteBackup(id);
return Response.ok().entity(java.util.Map.of("message", "Sauvegarde supprimée avec succès")).build();
}
/**
* Récupérer la configuration des sauvegardes automatiques
*/
@GET
@Path("/config")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Récupérer la configuration des sauvegardes automatiques")
public BackupConfigResponse getBackupConfig() {
log.info("GET /api/backups/config");
return backupService.getBackupConfig();
}
/**
* Mettre à jour la configuration des sauvegardes automatiques
*/
@PUT
@Path("/config")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Mettre à jour la configuration des sauvegardes automatiques")
public BackupConfigResponse updateBackupConfig(@Valid UpdateBackupConfigRequest request) {
log.info("PUT /api/backups/config");
return backupService.updateBackupConfig(request);
}
/**
* Créer un point de restauration
*/
@POST
@Path("/restore-point")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Créer un point de restauration")
public Response createRestorePoint() {
log.info("POST /api/backups/restore-point");
BackupResponse backup = backupService.createRestorePoint();
return Response.status(Response.Status.CREATED).entity(backup).build();
}
}
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.backup.request.CreateBackupRequest;
import dev.lions.unionflow.server.api.dto.backup.request.RestoreBackupRequest;
import dev.lions.unionflow.server.api.dto.backup.request.UpdateBackupConfigRequest;
import dev.lions.unionflow.server.api.dto.backup.response.BackupConfigResponse;
import dev.lions.unionflow.server.api.dto.backup.response.BackupResponse;
import dev.lions.unionflow.server.service.BackupService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.List;
import java.util.UUID;
/**
* REST Resource pour la gestion des sauvegardes système
*/
@Slf4j
@Path("/api/backups")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Sauvegardes", description = "Gestion des sauvegardes et restaurations")
public class BackupResource {
@Inject
BackupService backupService;
/**
* Lister toutes les sauvegardes
*/
@GET
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Lister toutes les sauvegardes disponibles")
public List<BackupResponse> getAllBackups() {
log.info("GET /api/backups");
return backupService.getAllBackups();
}
/**
* Récupérer une sauvegarde par ID
*/
@GET
@Path("/{id}")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Récupérer une sauvegarde par ID")
public BackupResponse getBackupById(@PathParam("id") UUID id) {
log.info("GET /api/backups/{}", id);
return backupService.getBackupById(id);
}
/**
* Créer une nouvelle sauvegarde
*/
@POST
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Créer une nouvelle sauvegarde")
public Response createBackup(@Valid CreateBackupRequest request) {
log.info("POST /api/backups - {}", request.getName());
BackupResponse backup = backupService.createBackup(request);
return Response.status(Response.Status.CREATED).entity(backup).build();
}
/**
* Restaurer une sauvegarde
*/
@POST
@Path("/restore")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Restaurer une sauvegarde")
public Response restoreBackup(@Valid RestoreBackupRequest request) {
log.info("POST /api/backups/restore - backupId={}", request.getBackupId());
backupService.restoreBackup(request);
return Response.ok().entity(java.util.Map.of("message", "Restauration en cours")).build();
}
/**
* Supprimer une sauvegarde
*/
@DELETE
@Path("/{id}")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Supprimer une sauvegarde")
public Response deleteBackup(@PathParam("id") UUID id) {
log.info("DELETE /api/backups/{}", id);
backupService.deleteBackup(id);
return Response.ok().entity(java.util.Map.of("message", "Sauvegarde supprimée avec succès")).build();
}
/**
* Récupérer la configuration des sauvegardes automatiques
*/
@GET
@Path("/config")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Récupérer la configuration des sauvegardes automatiques")
public BackupConfigResponse getBackupConfig() {
log.info("GET /api/backups/config");
return backupService.getBackupConfig();
}
/**
* Mettre à jour la configuration des sauvegardes automatiques
*/
@PUT
@Path("/config")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Mettre à jour la configuration des sauvegardes automatiques")
public BackupConfigResponse updateBackupConfig(@Valid UpdateBackupConfigRequest request) {
log.info("PUT /api/backups/config");
return backupService.updateBackupConfig(request);
}
/**
* Créer un point de restauration
*/
@POST
@Path("/restore-point")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Créer un point de restauration")
public Response createRestorePoint() {
log.info("POST /api/backups/restore-point");
BackupResponse backup = backupService.createRestorePoint();
return Response.status(Response.Status.CREATED).entity(backup).build();
}
}

View File

@@ -1,6 +1,7 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.membre.CompteAdherentResponse;
import dev.lions.unionflow.server.service.FirebasePushService;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.MembreOrganisation;
import dev.lions.unionflow.server.entity.SouscriptionOrganisation;
@@ -67,6 +68,59 @@ public class CompteAdherentResource {
@Inject
MembreService membreService;
@Inject
FirebasePushService firebasePushService;
/**
* Enregistre ou met à jour le token FCM du membre connecté pour les notifications push.
* Appelé par l'application mobile au démarrage ou quand Firebase renouvelle le token.
*/
@PUT
@Path("/mon-compte/fcm-token")
@Authenticated
@Operation(summary = "Enregistrer le token FCM pour les notifications push")
@jakarta.transaction.Transactional
public Response enregistrerFcmToken(Map<String, String> body) {
String email = securiteHelper.resolveEmail();
if (email == null) return Response.status(Response.Status.UNAUTHORIZED).build();
String token = body != null ? body.get("token") : null;
if (token == null || token.isBlank()) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("message", "Le champ 'token' est requis.")).build();
}
return membreRepository.findByEmail(email)
.map(membre -> {
membre.setFcmToken(token.trim());
membreRepository.persist(membre);
return Response.ok(Map.of("message", "Token FCM enregistré.")).build();
})
.orElse(Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("message", "Membre introuvable.")).build());
}
/**
* Supprime le token FCM (désabonnement des notifications push).
*/
@DELETE
@Path("/mon-compte/fcm-token")
@Authenticated
@Operation(summary = "Désactiver les notifications push")
@jakarta.transaction.Transactional
public Response supprimerFcmToken() {
String email = securiteHelper.resolveEmail();
if (email == null) return Response.status(Response.Status.UNAUTHORIZED).build();
return membreRepository.findByEmail(email)
.map(membre -> {
membre.setFcmToken(null);
membreRepository.persist(membre);
return Response.ok(Map.of("message", "Notifications push désactivées.")).build();
})
.orElse(Response.status(Response.Status.NOT_FOUND).build());
}
/**
* Retourne le compte adhérent complet du membre connecté :
* numéro de membre, soldes (cotisations + épargne), capacité d'emprunt, taux d'engagement.
@@ -138,15 +192,17 @@ public class CompteAdherentResource {
}
}
// Fallback : auto-activer si EN_ATTENTE_VALIDATION et org a souscription active
// (membres sans premiereConnexion=true ou créés avant cette logique)
// Fallback : auto-activer si EN_ATTENTE_VALIDATION et org a reçu un paiement.
// Couvre le cas PAIEMENT_CONFIRME (admin a payé mais super admin n'a pas encore validé)
// et ACTIVE/VALIDEE (chemin nominal). L'admin ne doit pas bloquer sur l'AwaitingValidationPage
// dès lors que le paiement est confirmé côté Wave.
if ("EN_ATTENTE_VALIDATION".equals(statutCompte) && membreOpt.isPresent()) {
Membre m = membreOpt.get();
UUID orgId = membreOrganisationRepo.findFirstByMembreId(m.getId())
.map(mo -> mo.getOrganisation().getId())
.orElse(null);
if (membreService.orgHasActiveSubscription(orgId)) {
LOG.infof("Auto-activation au login de %s (org %s a souscription active)", m.getEmail(), orgId);
if (membreService.orgHasPaidSubscription(orgId)) {
LOG.infof("Auto-activation au login de %s (org %s a souscription payée)", m.getEmail(), orgId);
membreService.activerMembre(m.getId());
try {
membreKeycloakSyncService.activerMembreDansKeycloak(m.getId());

View File

@@ -1,64 +1,64 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.config.request.UpdateConfigurationRequest;
import dev.lions.unionflow.server.api.dto.config.response.ConfigurationResponse;
import dev.lions.unionflow.server.service.ConfigurationService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.List;
/**
* Resource REST pour la gestion de la configuration système
*
* @author UnionFlow Team
* @version 1.0
*/
@Path("/api/configuration")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Configuration", description = "Gestion de la configuration système")
@Slf4j
@RolesAllowed({ "ADMIN", "SUPER_ADMIN" })
public class ConfigurationResource {
@Inject
ConfigurationService configurationService;
@GET
@Operation(summary = "Lister toutes les configurations")
@APIResponse(responseCode = "200", description = "Liste des configurations récupérée avec succès")
public Response listerConfigurations() {
log.info("GET /api/configuration");
List<ConfigurationResponse> configurations = configurationService.listerConfigurations();
return Response.ok(configurations).build();
}
@GET
@Path("/{cle}")
@Operation(summary = "Récupérer une configuration par clé")
@APIResponse(responseCode = "200", description = "Configuration trouvée")
public Response obtenirConfiguration(@PathParam("cle") String cle) {
log.info("GET /api/configuration/{}", cle);
ConfigurationResponse config = configurationService.obtenirConfiguration(cle);
return Response.ok(config).build();
}
@PUT
@Path("/{cle}")
@Operation(summary = "Mettre à jour une configuration")
@APIResponse(responseCode = "200", description = "Configuration mise à jour avec succès")
public Response mettreAJourConfiguration(@PathParam("cle") String cle, @Valid UpdateConfigurationRequest request) {
log.info("PUT /api/configuration/{}", cle);
ConfigurationResponse updated = configurationService.mettreAJourConfiguration(cle, request);
return Response.ok(updated).build();
}
}
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.config.request.UpdateConfigurationRequest;
import dev.lions.unionflow.server.api.dto.config.response.ConfigurationResponse;
import dev.lions.unionflow.server.service.ConfigurationService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.List;
/**
* Resource REST pour la gestion de la configuration système
*
* @author UnionFlow Team
* @version 1.0
*/
@Path("/api/configuration")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Configuration", description = "Gestion de la configuration système")
@Slf4j
@RolesAllowed({ "ADMIN", "SUPER_ADMIN" })
public class ConfigurationResource {
@Inject
ConfigurationService configurationService;
@GET
@Operation(summary = "Lister toutes les configurations")
@APIResponse(responseCode = "200", description = "Liste des configurations récupérée avec succès")
public Response listerConfigurations() {
log.info("GET /api/configuration");
List<ConfigurationResponse> configurations = configurationService.listerConfigurations();
return Response.ok(configurations).build();
}
@GET
@Path("/{cle}")
@Operation(summary = "Récupérer une configuration par clé")
@APIResponse(responseCode = "200", description = "Configuration trouvée")
public Response obtenirConfiguration(@PathParam("cle") String cle) {
log.info("GET /api/configuration/{}", cle);
ConfigurationResponse config = configurationService.obtenirConfiguration(cle);
return Response.ok(config).build();
}
@PUT
@Path("/{cle}")
@Operation(summary = "Mettre à jour une configuration")
@APIResponse(responseCode = "200", description = "Configuration mise à jour avec succès")
public Response mettreAJourConfiguration(@PathParam("cle") String cle, @Valid UpdateConfigurationRequest request) {
log.info("PUT /api/configuration/{}", cle);
ConfigurationResponse updated = configurationService.mettreAJourConfiguration(cle, request);
return Response.ok(updated).build();
}
}

View File

@@ -1,140 +1,140 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.solidarite.request.CreateDemandeAideRequest;
import dev.lions.unionflow.server.api.dto.solidarite.request.UpdateDemandeAideRequest;
import dev.lions.unionflow.server.api.dto.solidarite.response.DemandeAideResponse;
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.service.DemandeAideService;
import dev.lions.unionflow.server.repository.MembreRepository;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.Response;
import java.util.UUID;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import io.quarkus.security.identity.SecurityIdentity;
import java.util.Collections;
import java.util.List;
/**
* Resource REST pour les demandes d'aide.
* Expose l'API attendue par le client (DemandeAideService REST client).
*/
@Path("/api/demandes-aide")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Demandes d'aide", description = "Gestion des demandes d'aide solidarité")
@RolesAllowed({ "USER", "MEMBRE", "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION" })
public class DemandeAideResource {
@Inject
DemandeAideService demandeAideService;
@Inject
MembreRepository membreRepository;
@Inject
SecurityIdentity securityIdentity;
@GET
@Path("/mes")
@Operation(summary = "Mes demandes d'aide", description = "Liste les demandes du membre connecté")
public List<DemandeAideResponse> mesDemandes(
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("50") int size) {
String email = securityIdentity.getPrincipal().getName();
Membre membre = membreRepository.findByEmail(email).orElse(null);
if (membre == null) {
return List.of();
}
List<DemandeAideResponse> all = demandeAideService.rechercherAvecFiltres(
java.util.Map.of("demandeurId", membre.getId()));
int from = Math.min(page * size, all.size());
int to = Math.min(from + size, all.size());
return from < to ? all.subList(from, to) : List.of();
}
@GET
@Operation(summary = "Liste les demandes d'aide avec pagination")
public List<DemandeAideResponse> listerToutes(
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("20") int size) {
List<DemandeAideResponse> all = demandeAideService.rechercherAvecFiltres(Collections.emptyMap());
int from = Math.min(page * size, all.size());
int to = Math.min(from + size, all.size());
return from < to ? all.subList(from, to) : List.of();
}
@GET
@Path("/search")
@Operation(summary = "Recherche les demandes d'aide avec filtres (statut, type, urgence)")
public List<DemandeAideResponse> rechercher(
@QueryParam("statut") String statut,
@QueryParam("type") String type,
@QueryParam("urgence") String urgence,
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("20") int size) {
java.util.Map<String, Object> filtres = new java.util.HashMap<>();
if (statut != null && !statut.isEmpty()) {
try { filtres.put("statut", StatutAide.valueOf(statut)); } catch (IllegalArgumentException e) {}
}
if (type != null && !type.isEmpty()) {
try { filtres.put("typeAide", TypeAide.valueOf(type)); } catch (IllegalArgumentException e) {}
}
if (urgence != null && !urgence.isEmpty()) {
try { filtres.put("priorite", PrioriteAide.valueOf(urgence)); } catch (IllegalArgumentException e) {}
}
List<DemandeAideResponse> all = demandeAideService.rechercherAvecFiltres(filtres);
int from = Math.min(page * size, all.size());
int to = Math.min(from + size, all.size());
return from < to ? all.subList(from, to) : List.of();
}
@GET
@Path("/{id}")
@Operation(summary = "Récupère une demande d'aide par son ID")
public DemandeAideResponse obtenirParId(@PathParam("id") UUID id) {
DemandeAideResponse response = demandeAideService.obtenirParId(id);
if (response == null) {
throw new NotFoundException("Demande d'aide non trouvée : " + id);
}
return response;
}
@POST
@Operation(summary = "Crée une nouvelle demande d'aide")
public Response creer(@Valid CreateDemandeAideRequest request) {
DemandeAideResponse response = demandeAideService.creerDemande(request);
return Response.status(Response.Status.CREATED).entity(response).build();
}
@PUT
@Path("/{id}")
@Operation(summary = "Met à jour une demande d'aide")
public DemandeAideResponse mettreAJour(@PathParam("id") UUID id, @Valid UpdateDemandeAideRequest request) {
return demandeAideService.mettreAJour(id, request);
}
@PUT
@Path("/{id}/approuver")
@Operation(summary = "Approuver une demande d'aide")
public DemandeAideResponse approuver(@PathParam("id") UUID id, @QueryParam("motif") String motif) {
return demandeAideService.changerStatut(id, StatutAide.APPROUVEE, motif);
}
@PUT
@Path("/{id}/rejeter")
@Operation(summary = "Rejeter une demande d'aide")
public DemandeAideResponse rejeter(@PathParam("id") UUID id, @QueryParam("motif") String motif) {
return demandeAideService.changerStatut(id, StatutAide.REJETEE, motif);
}
}
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.solidarite.request.CreateDemandeAideRequest;
import dev.lions.unionflow.server.api.dto.solidarite.request.UpdateDemandeAideRequest;
import dev.lions.unionflow.server.api.dto.solidarite.response.DemandeAideResponse;
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.service.DemandeAideService;
import dev.lions.unionflow.server.repository.MembreRepository;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.Response;
import java.util.UUID;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import io.quarkus.security.identity.SecurityIdentity;
import java.util.Collections;
import java.util.List;
/**
* Resource REST pour les demandes d'aide.
* Expose l'API attendue par le client (DemandeAideService REST client).
*/
@Path("/api/demandes-aide")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Demandes d'aide", description = "Gestion des demandes d'aide solidarité")
@RolesAllowed({ "USER", "MEMBRE", "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION" })
public class DemandeAideResource {
@Inject
DemandeAideService demandeAideService;
@Inject
MembreRepository membreRepository;
@Inject
SecurityIdentity securityIdentity;
@GET
@Path("/mes")
@Operation(summary = "Mes demandes d'aide", description = "Liste les demandes du membre connecté")
public List<DemandeAideResponse> mesDemandes(
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("50") int size) {
String email = securityIdentity.getPrincipal().getName();
Membre membre = membreRepository.findByEmail(email).orElse(null);
if (membre == null) {
return List.of();
}
List<DemandeAideResponse> all = demandeAideService.rechercherAvecFiltres(
java.util.Map.of("demandeurId", membre.getId()));
int from = Math.min(page * size, all.size());
int to = Math.min(from + size, all.size());
return from < to ? all.subList(from, to) : List.of();
}
@GET
@Operation(summary = "Liste les demandes d'aide avec pagination")
public List<DemandeAideResponse> listerToutes(
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("20") int size) {
List<DemandeAideResponse> all = demandeAideService.rechercherAvecFiltres(Collections.emptyMap());
int from = Math.min(page * size, all.size());
int to = Math.min(from + size, all.size());
return from < to ? all.subList(from, to) : List.of();
}
@GET
@Path("/search")
@Operation(summary = "Recherche les demandes d'aide avec filtres (statut, type, urgence)")
public List<DemandeAideResponse> rechercher(
@QueryParam("statut") String statut,
@QueryParam("type") String type,
@QueryParam("urgence") String urgence,
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("20") int size) {
java.util.Map<String, Object> filtres = new java.util.HashMap<>();
if (statut != null && !statut.isEmpty()) {
try { filtres.put("statut", StatutAide.valueOf(statut)); } catch (IllegalArgumentException e) {}
}
if (type != null && !type.isEmpty()) {
try { filtres.put("typeAide", TypeAide.valueOf(type)); } catch (IllegalArgumentException e) {}
}
if (urgence != null && !urgence.isEmpty()) {
try { filtres.put("priorite", PrioriteAide.valueOf(urgence)); } catch (IllegalArgumentException e) {}
}
List<DemandeAideResponse> all = demandeAideService.rechercherAvecFiltres(filtres);
int from = Math.min(page * size, all.size());
int to = Math.min(from + size, all.size());
return from < to ? all.subList(from, to) : List.of();
}
@GET
@Path("/{id}")
@Operation(summary = "Récupère une demande d'aide par son ID")
public DemandeAideResponse obtenirParId(@PathParam("id") UUID id) {
DemandeAideResponse response = demandeAideService.obtenirParId(id);
if (response == null) {
throw new NotFoundException("Demande d'aide non trouvée : " + id);
}
return response;
}
@POST
@Operation(summary = "Crée une nouvelle demande d'aide")
public Response creer(@Valid CreateDemandeAideRequest request) {
DemandeAideResponse response = demandeAideService.creerDemande(request);
return Response.status(Response.Status.CREATED).entity(response).build();
}
@PUT
@Path("/{id}")
@Operation(summary = "Met à jour une demande d'aide")
public DemandeAideResponse mettreAJour(@PathParam("id") UUID id, @Valid UpdateDemandeAideRequest request) {
return demandeAideService.mettreAJour(id, request);
}
@PUT
@Path("/{id}/approuver")
@Operation(summary = "Approuver une demande d'aide")
public DemandeAideResponse approuver(@PathParam("id") UUID id, @QueryParam("motif") String motif) {
return demandeAideService.changerStatut(id, StatutAide.APPROUVEE, motif);
}
@PUT
@Path("/{id}/rejeter")
@Operation(summary = "Rejeter une demande d'aide")
public DemandeAideResponse rejeter(@PathParam("id") UUID id, @QueryParam("motif") String motif) {
return demandeAideService.changerStatut(id, StatutAide.REJETEE, motif);
}
}

View File

@@ -1,114 +1,114 @@
package dev.lions.unionflow.server.resource;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Resource REST pour les workflows financiers (stats et audits)
*
* @author UnionFlow Team
* @version 1.0
* @since 2026-03-16
*/
@Path("/api/finance")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Finance - Workflow", description = "Statistiques et audits des workflows financiers")
public class FinanceWorkflowResource {
private static final Logger LOG = Logger.getLogger(FinanceWorkflowResource.class);
@GET
@Path("/stats")
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Statistiques du workflow financier",
description = "Retourne les statistiques globales du workflow financier")
public Response getWorkflowStats(
@QueryParam("organizationId") String organizationId,
@QueryParam("startDate") String startDate,
@QueryParam("endDate") String endDate) {
LOG.infof("GET /api/finance/stats?organizationId=%s", organizationId);
Map<String, Object> stats = new HashMap<>();
stats.put("totalApprovals", 0);
stats.put("pendingApprovals", 0);
stats.put("approvedCount", 0);
stats.put("rejectedCount", 0);
stats.put("totalBudgets", 0);
stats.put("activeBudgets", 0);
stats.put("averageApprovalTime", "0 hours");
stats.put("period", Map.of(
"startDate", startDate != null ? startDate : LocalDateTime.now().minusMonths(1).toString(),
"endDate", endDate != null ? endDate : LocalDateTime.now().toString()
));
return Response.ok(stats).build();
}
@GET
@Path("/audit-logs")
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Récupère les logs d'audit financier",
description = "Liste les logs d'audit avec filtres optionnels")
public Response getAuditLogs(
@QueryParam("organizationId") String organizationId,
@QueryParam("startDate") String startDate,
@QueryParam("endDate") String endDate,
@QueryParam("operation") String operation,
@QueryParam("entityType") String entityType,
@QueryParam("severity") String severity,
@QueryParam("limit") @DefaultValue("100") int limit) {
LOG.infof("GET /api/finance/audit-logs?organizationId=%s&limit=%d", organizationId, limit);
// Retourne une liste vide pour l'instant - à implémenter plus tard avec vraie persistence
return Response.ok(new ArrayList<>()).build();
}
@GET
@Path("/audit-logs/anomalies")
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Récupère les anomalies financières détectées",
description = "Liste les anomalies et transactions suspectes")
public Response getAnomalies(
@QueryParam("organizationId") String organizationId,
@QueryParam("startDate") String startDate,
@QueryParam("endDate") String endDate) {
LOG.infof("GET /api/finance/audit-logs/anomalies?organizationId=%s", organizationId);
// Retourne une liste vide pour l'instant - à implémenter plus tard
return Response.ok(new ArrayList<>()).build();
}
@POST
@Path("/audit-logs/export")
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Exporte les logs d'audit",
description = "Génère un export des logs d'audit au format spécifié (CSV/PDF)")
public Response exportAuditLogs(Map<String, Object> request) {
String organizationId = (String) request.get("organizationId");
String format = (String) request.getOrDefault("format", "csv");
LOG.infof("POST /api/finance/audit-logs/export - format: %s", format);
// Pour l'instant, retourne un URL fictif - à implémenter plus tard
String exportUrl = "/api/finance/exports/" + UUID.randomUUID() + "." + format;
Map<String, Object> response = new HashMap<>();
response.put("exportUrl", exportUrl);
response.put("format", format);
response.put("status", "generated");
response.put("expiresAt", LocalDateTime.now().plusHours(24).toString());
return Response.ok(response).build();
}
}
package dev.lions.unionflow.server.resource;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Resource REST pour les workflows financiers (stats et audits)
*
* @author UnionFlow Team
* @version 1.0
* @since 2026-03-16
*/
@Path("/api/finance")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Finance - Workflow", description = "Statistiques et audits des workflows financiers")
public class FinanceWorkflowResource {
private static final Logger LOG = Logger.getLogger(FinanceWorkflowResource.class);
@GET
@Path("/stats")
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Statistiques du workflow financier",
description = "Retourne les statistiques globales du workflow financier")
public Response getWorkflowStats(
@QueryParam("organizationId") String organizationId,
@QueryParam("startDate") String startDate,
@QueryParam("endDate") String endDate) {
LOG.infof("GET /api/finance/stats?organizationId=%s", organizationId);
Map<String, Object> stats = new HashMap<>();
stats.put("totalApprovals", 0);
stats.put("pendingApprovals", 0);
stats.put("approvedCount", 0);
stats.put("rejectedCount", 0);
stats.put("totalBudgets", 0);
stats.put("activeBudgets", 0);
stats.put("averageApprovalTime", "0 hours");
stats.put("period", Map.of(
"startDate", startDate != null ? startDate : LocalDateTime.now().minusMonths(1).toString(),
"endDate", endDate != null ? endDate : LocalDateTime.now().toString()
));
return Response.ok(stats).build();
}
@GET
@Path("/audit-logs")
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Récupère les logs d'audit financier",
description = "Liste les logs d'audit avec filtres optionnels")
public Response getAuditLogs(
@QueryParam("organizationId") String organizationId,
@QueryParam("startDate") String startDate,
@QueryParam("endDate") String endDate,
@QueryParam("operation") String operation,
@QueryParam("entityType") String entityType,
@QueryParam("severity") String severity,
@QueryParam("limit") @DefaultValue("100") int limit) {
LOG.infof("GET /api/finance/audit-logs?organizationId=%s&limit=%d", organizationId, limit);
// Retourne une liste vide pour l'instant - à implémenter plus tard avec vraie persistence
return Response.ok(new ArrayList<>()).build();
}
@GET
@Path("/audit-logs/anomalies")
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Récupère les anomalies financières détectées",
description = "Liste les anomalies et transactions suspectes")
public Response getAnomalies(
@QueryParam("organizationId") String organizationId,
@QueryParam("startDate") String startDate,
@QueryParam("endDate") String endDate) {
LOG.infof("GET /api/finance/audit-logs/anomalies?organizationId=%s", organizationId);
// Retourne une liste vide pour l'instant - à implémenter plus tard
return Response.ok(new ArrayList<>()).build();
}
@POST
@Path("/audit-logs/export")
@RolesAllowed({"ADMIN_ORGANISATION", "SUPER_ADMIN"})
@Operation(summary = "Exporte les logs d'audit",
description = "Génère un export des logs d'audit au format spécifié (CSV/PDF)")
public Response exportAuditLogs(Map<String, Object> request) {
String organizationId = (String) request.get("organizationId");
String format = (String) request.getOrDefault("format", "csv");
LOG.infof("POST /api/finance/audit-logs/export - format: %s", format);
// Pour l'instant, retourne un URL fictif - à implémenter plus tard
String exportUrl = "/api/finance/exports/" + UUID.randomUUID() + "." + format;
Map<String, Object> response = new HashMap<>();
response.put("exportUrl", exportUrl);
response.put("format", format);
response.put("status", "generated");
response.put("expiresAt", LocalDateTime.now().plusHours(24).toString());
return Response.ok(response).build();
}
}

View File

@@ -1,35 +1,35 @@
package dev.lions.unionflow.server.resource;
import jakarta.annotation.security.PermitAll;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.time.LocalDateTime;
import java.util.Map;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
/** Resource de santé pour UnionFlow Server */
@Path("/api/status")
@Produces(MediaType.APPLICATION_JSON)
@ApplicationScoped
@PermitAll
@Tag(name = "Status", description = "API de statut du serveur")
public class HealthResource {
@GET
@Operation(summary = "Vérifier le statut du serveur")
public Response getStatus() {
return Response.ok(
Map.of(
"status", "UP",
"service", "UnionFlow Server",
"version", "1.0.0",
"timestamp", LocalDateTime.now().toString(),
"message", "Serveur opérationnel"))
.build();
}
}
package dev.lions.unionflow.server.resource;
import jakarta.annotation.security.PermitAll;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.time.LocalDateTime;
import java.util.Map;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
/** Resource de santé pour UnionFlow Server */
@Path("/api/status")
@Produces(MediaType.APPLICATION_JSON)
@ApplicationScoped
@PermitAll
@Tag(name = "Status", description = "API de statut du serveur")
public class HealthResource {
@GET
@Operation(summary = "Vérifier le statut du serveur")
public Response getStatus() {
return Response.ok(
Map.of(
"status", "UP",
"service", "UnionFlow Server",
"version", "1.0.0",
"timestamp", LocalDateTime.now().toString(),
"message", "Serveur opérationnel"))
.build();
}
}

View File

@@ -1,148 +1,148 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.logs.request.LogSearchRequest;
import dev.lions.unionflow.server.api.dto.logs.request.UpdateAlertConfigRequest;
import dev.lions.unionflow.server.api.dto.logs.response.AlertConfigResponse;
import dev.lions.unionflow.server.api.dto.logs.response.SystemAlertResponse;
import dev.lions.unionflow.server.api.dto.logs.response.SystemLogResponse;
import dev.lions.unionflow.server.api.dto.logs.response.SystemMetricsResponse;
import dev.lions.unionflow.server.service.LogsMonitoringService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.List;
import java.util.UUID;
/**
* REST Resource pour la gestion des logs et du monitoring système
*/
@Slf4j
@Path("/api")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Logs & Monitoring", description = "Gestion des logs système et monitoring")
public class LogsMonitoringResource {
@Inject
LogsMonitoringService logsMonitoringService;
/**
* Rechercher dans les logs système
*/
@POST
@Path("/logs/search")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Rechercher dans les logs système", description = "Recherche avec filtres (niveau, source, texte, dates)")
public List<SystemLogResponse> searchLogs(@Valid LogSearchRequest request) {
log.info("POST /api/logs/search - level={}, source={}", request.getLevel(), request.getSource());
return logsMonitoringService.searchLogs(request);
}
/**
* Exporter les logs (simplifié)
*/
@GET
@Path("/logs/export")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Produces("text/csv")
@Operation(summary = "Exporter les logs en CSV")
public Response exportLogs(
@QueryParam("level") String level,
@QueryParam("source") String source,
@QueryParam("timeRange") String timeRange
) {
log.info("GET /api/logs/export");
// Dans une vraie implémentation, on générerait un vrai CSV
LogSearchRequest request = LogSearchRequest.builder()
.level(level)
.source(source)
.timeRange(timeRange)
.build();
List<SystemLogResponse> logs = logsMonitoringService.searchLogs(request);
// Génération simplifiée du CSV
StringBuilder csv = new StringBuilder();
csv.append("Timestamp,Level,Source,Message,Details\n");
logs.forEach(log -> csv.append(String.format("%s,%s,%s,\"%s\",\"%s\"\n",
log.getTimestamp(),
log.getLevel(),
log.getSource(),
log.getMessage().replace("\"", "\"\""),
log.getDetails() != null ? log.getDetails().replace("\"", "\"\"") : ""
)));
return Response.ok(csv.toString())
.header("Content-Disposition", "attachment; filename=\"logs-export.csv\"")
.build();
}
/**
* Récupérer les métriques système en temps réel
*/
@GET
@Path("/monitoring/metrics")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Récupérer les métriques système en temps réel")
public SystemMetricsResponse getSystemMetrics() {
log.debug("GET /api/monitoring/metrics");
return logsMonitoringService.getSystemMetrics();
}
/**
* Récupérer toutes les alertes actives
*/
@GET
@Path("/alerts")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Récupérer toutes les alertes actives")
public List<SystemAlertResponse> getActiveAlerts() {
log.info("GET /api/alerts");
return logsMonitoringService.getActiveAlerts();
}
/**
* Acquitter une alerte
*/
@POST
@Path("/alerts/{id}/acknowledge")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Acquitter une alerte")
public Response acknowledgeAlert(@PathParam("id") UUID id) {
log.info("POST /api/alerts/{}/acknowledge", id);
logsMonitoringService.acknowledgeAlert(id);
return Response.ok().entity(java.util.Map.of("message", "Alerte acquittée avec succès")).build();
}
/**
* Récupérer la configuration des alertes
*/
@GET
@Path("/alerts/config")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Récupérer la configuration des alertes système")
public AlertConfigResponse getAlertConfig() {
log.info("GET /api/alerts/config");
return logsMonitoringService.getAlertConfig();
}
/**
* Mettre à jour la configuration des alertes
*/
@PUT
@Path("/alerts/config")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Mettre à jour la configuration des alertes système")
public AlertConfigResponse updateAlertConfig(@Valid UpdateAlertConfigRequest request) {
log.info("PUT /api/alerts/config");
return logsMonitoringService.updateAlertConfig(request);
}
}
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.logs.request.LogSearchRequest;
import dev.lions.unionflow.server.api.dto.logs.request.UpdateAlertConfigRequest;
import dev.lions.unionflow.server.api.dto.logs.response.AlertConfigResponse;
import dev.lions.unionflow.server.api.dto.logs.response.SystemAlertResponse;
import dev.lions.unionflow.server.api.dto.logs.response.SystemLogResponse;
import dev.lions.unionflow.server.api.dto.logs.response.SystemMetricsResponse;
import dev.lions.unionflow.server.service.LogsMonitoringService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.List;
import java.util.UUID;
/**
* REST Resource pour la gestion des logs et du monitoring système
*/
@Slf4j
@Path("/api")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Logs & Monitoring", description = "Gestion des logs système et monitoring")
public class LogsMonitoringResource {
@Inject
LogsMonitoringService logsMonitoringService;
/**
* Rechercher dans les logs système
*/
@POST
@Path("/logs/search")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Rechercher dans les logs système", description = "Recherche avec filtres (niveau, source, texte, dates)")
public List<SystemLogResponse> searchLogs(@Valid LogSearchRequest request) {
log.info("POST /api/logs/search - level={}, source={}", request.getLevel(), request.getSource());
return logsMonitoringService.searchLogs(request);
}
/**
* Exporter les logs (simplifié)
*/
@GET
@Path("/logs/export")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Produces("text/csv")
@Operation(summary = "Exporter les logs en CSV")
public Response exportLogs(
@QueryParam("level") String level,
@QueryParam("source") String source,
@QueryParam("timeRange") String timeRange
) {
log.info("GET /api/logs/export");
// Dans une vraie implémentation, on générerait un vrai CSV
LogSearchRequest request = LogSearchRequest.builder()
.level(level)
.source(source)
.timeRange(timeRange)
.build();
List<SystemLogResponse> logs = logsMonitoringService.searchLogs(request);
// Génération simplifiée du CSV
StringBuilder csv = new StringBuilder();
csv.append("Timestamp,Level,Source,Message,Details\n");
logs.forEach(log -> csv.append(String.format("%s,%s,%s,\"%s\",\"%s\"\n",
log.getTimestamp(),
log.getLevel(),
log.getSource(),
log.getMessage().replace("\"", "\"\""),
log.getDetails() != null ? log.getDetails().replace("\"", "\"\"") : ""
)));
return Response.ok(csv.toString())
.header("Content-Disposition", "attachment; filename=\"logs-export.csv\"")
.build();
}
/**
* Récupérer les métriques système en temps réel
*/
@GET
@Path("/monitoring/metrics")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Récupérer les métriques système en temps réel")
public SystemMetricsResponse getSystemMetrics() {
log.debug("GET /api/monitoring/metrics");
return logsMonitoringService.getSystemMetrics();
}
/**
* Récupérer toutes les alertes actives
*/
@GET
@Path("/alerts")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Récupérer toutes les alertes actives")
public List<SystemAlertResponse> getActiveAlerts() {
log.info("GET /api/alerts");
return logsMonitoringService.getActiveAlerts();
}
/**
* Acquitter une alerte
*/
@POST
@Path("/alerts/{id}/acknowledge")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Acquitter une alerte")
public Response acknowledgeAlert(@PathParam("id") UUID id) {
log.info("POST /api/alerts/{}/acknowledge", id);
logsMonitoringService.acknowledgeAlert(id);
return Response.ok().entity(java.util.Map.of("message", "Alerte acquittée avec succès")).build();
}
/**
* Récupérer la configuration des alertes
*/
@GET
@Path("/alerts/config")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Récupérer la configuration des alertes système")
public AlertConfigResponse getAlertConfig() {
log.info("GET /api/alerts/config");
return logsMonitoringService.getAlertConfig();
}
/**
* Mettre à jour la configuration des alertes
*/
@PUT
@Path("/alerts/config")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Mettre à jour la configuration des alertes système")
public AlertConfigResponse updateAlertConfig(@Valid UpdateAlertConfigRequest request) {
log.info("PUT /api/alerts/config");
return logsMonitoringService.updateAlertConfig(request);
}
}

View File

@@ -11,6 +11,7 @@ import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.entity.MembreOrganisation;
import dev.lions.unionflow.server.repository.MembreOrganisationRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.MembreRoleRepository;
import dev.lions.unionflow.server.service.MemberLifecycleService;
import dev.lions.unionflow.server.service.MembreKeycloakSyncService;
@@ -78,6 +79,9 @@ public class MembreResource {
@Inject
MembreOrganisationRepository membreOrgRepository;
@Inject
MembreRepository membreRepository;
@Inject
MembreRoleRepository membreRoleRepository;
@@ -447,6 +451,40 @@ public class MembreResource {
}
}
/**
* Liste TOUS les membres (y compris EN_ATTENTE_VALIDATION) — réservé SUPER_ADMIN.
* Utile pour les imports de données historiques et la gestion admin.
*/
@GET
@Path("/admin/tous")
@RolesAllowed({ "SUPER_ADMIN" })
@Operation(summary = "Tous les membres (admin)", description = "Liste tous les membres quelque soit leur statut, réservé SUPER_ADMIN")
@APIResponse(responseCode = "200", description = "Liste complète des membres")
public Response getTousMembres(
@Parameter(description = "Numéro de page (0-based)") @QueryParam("page") @DefaultValue("0") int page,
@Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("100") int size) {
try {
LOG.infof("GET /api/membres/admin/tous - page=%d size=%d", page, size);
List<Membre> membres = membreRepository.findAll(
io.quarkus.panache.common.Sort.by("nom").ascending())
.page(io.quarkus.panache.common.Page.of(page, size))
.list();
List<MembreResponse> membresDTO = membreService.convertToResponseList(membres);
long total = membreRepository.count();
return Response.ok(Map.of(
"data", membresDTO,
"totalElements", total,
"page", page,
"size", size,
"totalPages", (int) Math.ceil((double) total / size)
)).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur récupération tous membres");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", e.getMessage())).build();
}
}
/**
* Liste les membres d'une organisation spécifique (statut ACTIF dans l'organisation).
* Utilisé pour la création de campagnes ciblées.
@@ -588,7 +626,7 @@ public class MembreResource {
@APIResponses({
@APIResponse(responseCode = "200", description = "Recherche effectuée avec succès", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = MembreSearchResultDTO.class), examples = @ExampleObject(name = "Exemple de résultats", value = """
{
"membres": [...],
"membres": [],
"totalElements": 247,
"totalPages": 13,
"currentPage": 0,

View File

@@ -1,130 +1,130 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.config.request.ParametresLcbFtRequest;
import dev.lions.unionflow.server.api.dto.config.response.ParametresLcbFtResponse;
import dev.lions.unionflow.server.service.ParametresLcbFtService;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.math.BigDecimal;
import java.util.UUID;
/**
* Resource REST pour les paramètres LCB-FT (seuils anti-blanchiment).
*
* @author lions dev Team
* @version 1.0
* @since 2026-03-13
*/
@Path("/api/parametres-lcb-ft")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Paramètres LCB-FT", description = "Gestion des seuils anti-blanchiment (LCB-FT)")
@Slf4j
public class ParametresLcbFtResource {
@Inject
ParametresLcbFtService parametresService;
/**
* Récupère les paramètres LCB-FT pour une organisation et une devise.
* Endpoint utilisé par le mobile pour valider côté client.
*
* @param organisationId ID de l'organisation (optionnel, null pour paramètres plateforme)
* @param codeDevise Code devise ISO 4217 (XOF par défaut)
* @return Paramètres LCB-FT complets
*/
@GET
@PermitAll
@Operation(
summary = "Récupérer les paramètres LCB-FT",
description = "Retourne les seuils anti-blanchiment pour une organisation ou la plateforme"
)
@APIResponse(responseCode = "200", description = "Paramètres récupérés")
@APIResponse(responseCode = "404", description = "Paramètres non configurés")
public Response getParametres(
@QueryParam("organisationId")
@Parameter(description = "ID de l'organisation (optionnel)")
String organisationId,
@QueryParam("codeDevise")
@DefaultValue("XOF")
@Parameter(description = "Code devise (XOF par défaut)")
String codeDevise) {
log.info("GET /api/parametres-lcb-ft?organisationId={}&codeDevise={}",
organisationId, codeDevise);
UUID orgId = organisationId != null && !organisationId.isBlank() ?
UUID.fromString(organisationId) : null;
ParametresLcbFtResponse params = parametresService.getParametres(orgId, codeDevise);
return Response.ok(params).build();
}
/**
* Récupère uniquement le seuil de justification (endpoint léger pour mobile).
*
* @param organisationId ID de l'organisation
* @param codeDevise Code devise (XOF par défaut)
* @return Montant seuil
*/
@GET
@Path("/seuil-justification")
@PermitAll
@Operation(
summary = "Récupérer le seuil de justification uniquement",
description = "Endpoint léger pour récupérer juste le montant seuil (utilisé par mobile)"
)
@APIResponse(responseCode = "200", description = "Seuil récupéré")
public Response getSeuilJustification(
@QueryParam("organisationId") String organisationId,
@QueryParam("codeDevise") @DefaultValue("XOF") String codeDevise) {
log.debug("GET /api/parametres-lcb-ft/seuil-justification");
UUID orgId = organisationId != null && !organisationId.isBlank() ?
UUID.fromString(organisationId) : null;
BigDecimal seuil = parametresService.getSeuilJustification(orgId, codeDevise);
return Response.ok().entity(new SeuilResponse(seuil, codeDevise)).build();
}
/**
* Crée ou met à jour les paramètres LCB-FT (admin uniquement).
*
* @param request Paramètres à créer/mettre à jour
* @return Paramètres créés/mis à jour
*/
@POST
@RolesAllowed({ "ADMIN", "SUPER_ADMIN" })
@Operation(
summary = "Créer ou mettre à jour les paramètres LCB-FT",
description = "Admin uniquement - Configure les seuils pour une organisation ou la plateforme"
)
@APIResponse(responseCode = "200", description = "Paramètres sauvegardés")
public Response saveOrUpdateParametres(@Valid ParametresLcbFtRequest request) {
log.info("POST /api/parametres-lcb-ft - org={}", request.getOrganisationId());
ParametresLcbFtResponse saved = parametresService.saveOrUpdateParametres(request);
return Response.ok(saved).build();
}
/**
* DTO léger pour retourner uniquement le seuil.
*/
public record SeuilResponse(
BigDecimal montantSeuil,
String codeDevise
) {}
}
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.config.request.ParametresLcbFtRequest;
import dev.lions.unionflow.server.api.dto.config.response.ParametresLcbFtResponse;
import dev.lions.unionflow.server.service.ParametresLcbFtService;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.math.BigDecimal;
import java.util.UUID;
/**
* Resource REST pour les paramètres LCB-FT (seuils anti-blanchiment).
*
* @author lions dev Team
* @version 1.0
* @since 2026-03-13
*/
@Path("/api/parametres-lcb-ft")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Paramètres LCB-FT", description = "Gestion des seuils anti-blanchiment (LCB-FT)")
@Slf4j
public class ParametresLcbFtResource {
@Inject
ParametresLcbFtService parametresService;
/**
* Récupère les paramètres LCB-FT pour une organisation et une devise.
* Endpoint utilisé par le mobile pour valider côté client.
*
* @param organisationId ID de l'organisation (optionnel, null pour paramètres plateforme)
* @param codeDevise Code devise ISO 4217 (XOF par défaut)
* @return Paramètres LCB-FT complets
*/
@GET
@PermitAll
@Operation(
summary = "Récupérer les paramètres LCB-FT",
description = "Retourne les seuils anti-blanchiment pour une organisation ou la plateforme"
)
@APIResponse(responseCode = "200", description = "Paramètres récupérés")
@APIResponse(responseCode = "404", description = "Paramètres non configurés")
public Response getParametres(
@QueryParam("organisationId")
@Parameter(description = "ID de l'organisation (optionnel)")
String organisationId,
@QueryParam("codeDevise")
@DefaultValue("XOF")
@Parameter(description = "Code devise (XOF par défaut)")
String codeDevise) {
log.info("GET /api/parametres-lcb-ft?organisationId={}&codeDevise={}",
organisationId, codeDevise);
UUID orgId = organisationId != null && !organisationId.isBlank() ?
UUID.fromString(organisationId) : null;
ParametresLcbFtResponse params = parametresService.getParametres(orgId, codeDevise);
return Response.ok(params).build();
}
/**
* Récupère uniquement le seuil de justification (endpoint léger pour mobile).
*
* @param organisationId ID de l'organisation
* @param codeDevise Code devise (XOF par défaut)
* @return Montant seuil
*/
@GET
@Path("/seuil-justification")
@PermitAll
@Operation(
summary = "Récupérer le seuil de justification uniquement",
description = "Endpoint léger pour récupérer juste le montant seuil (utilisé par mobile)"
)
@APIResponse(responseCode = "200", description = "Seuil récupéré")
public Response getSeuilJustification(
@QueryParam("organisationId") String organisationId,
@QueryParam("codeDevise") @DefaultValue("XOF") String codeDevise) {
log.debug("GET /api/parametres-lcb-ft/seuil-justification");
UUID orgId = organisationId != null && !organisationId.isBlank() ?
UUID.fromString(organisationId) : null;
BigDecimal seuil = parametresService.getSeuilJustification(orgId, codeDevise);
return Response.ok().entity(new SeuilResponse(seuil, codeDevise)).build();
}
/**
* Crée ou met à jour les paramètres LCB-FT (admin uniquement).
*
* @param request Paramètres à créer/mettre à jour
* @return Paramètres créés/mis à jour
*/
@POST
@RolesAllowed({ "ADMIN", "SUPER_ADMIN" })
@Operation(
summary = "Créer ou mettre à jour les paramètres LCB-FT",
description = "Admin uniquement - Configure les seuils pour une organisation ou la plateforme"
)
@APIResponse(responseCode = "200", description = "Paramètres sauvegardés")
public Response saveOrUpdateParametres(@Valid ParametresLcbFtRequest request) {
log.info("POST /api/parametres-lcb-ft - org={}", request.getOrganisationId());
ParametresLcbFtResponse saved = parametresService.saveOrUpdateParametres(request);
return Response.ok(saved).build();
}
/**
* DTO léger pour retourner uniquement le seuil.
*/
public record SeuilResponse(
BigDecimal montantSeuil,
String codeDevise
) {}
}

View File

@@ -1,75 +1,75 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.service.PreferencesNotificationService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.Map;
import java.util.UUID;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
/** Resource REST pour la gestion des préférences utilisateur */
@Path("/api/preferences")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@ApplicationScoped
@Tag(name = "Préférences", description = "API de gestion des préférences utilisateur")
public class PreferencesResource {
private static final Logger LOG = Logger.getLogger(PreferencesResource.class);
@Inject PreferencesNotificationService preferencesService;
@GET
@Path("/{utilisateurId}")
@RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"})
@Operation(summary = "Obtenir les préférences d'un utilisateur")
@APIResponse(responseCode = "200", description = "Préférences récupérées avec succès")
public Response obtenirPreferences(
@PathParam("utilisateurId") UUID utilisateurId) {
LOG.infof("Récupération des préférences pour l'utilisateur %s", utilisateurId);
Map<String, Boolean> preferences = preferencesService.obtenirPreferences(utilisateurId);
return Response.ok(preferences).build();
}
@PUT
@Path("/{utilisateurId}")
@RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"})
@Operation(summary = "Mettre à jour les préférences d'un utilisateur")
@APIResponse(responseCode = "204", description = "Préférences mises à jour avec succès")
public Response mettreAJourPreferences(
@PathParam("utilisateurId") UUID utilisateurId, Map<String, Boolean> preferences) {
LOG.infof("Mise à jour des préférences pour l'utilisateur %s", utilisateurId);
preferencesService.mettreAJourPreferences(utilisateurId, preferences);
return Response.noContent().build();
}
@POST
@Path("/{utilisateurId}/reinitialiser")
@RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"})
@Operation(summary = "Réinitialiser les préférences d'un utilisateur")
@APIResponse(responseCode = "204", description = "Préférences réinitialisées avec succès")
public Response reinitialiserPreferences(@PathParam("utilisateurId") UUID utilisateurId) {
LOG.infof("Réinitialisation des préférences pour l'utilisateur %s", utilisateurId);
preferencesService.reinitialiserPreferences(utilisateurId);
return Response.noContent().build();
}
@GET
@Path("/{utilisateurId}/export")
@RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"})
@Operation(summary = "Exporter les préférences d'un utilisateur")
@APIResponse(responseCode = "200", description = "Préférences exportées avec succès")
public Response exporterPreferences(@PathParam("utilisateurId") UUID utilisateurId) {
LOG.infof("Export des préférences pour l'utilisateur %s", utilisateurId);
Map<String, Object> export = preferencesService.exporterPreferences(utilisateurId);
return Response.ok(export).build();
}
}
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.service.PreferencesNotificationService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.Map;
import java.util.UUID;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
/** Resource REST pour la gestion des préférences utilisateur */
@Path("/api/preferences")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@ApplicationScoped
@Tag(name = "Préférences", description = "API de gestion des préférences utilisateur")
public class PreferencesResource {
private static final Logger LOG = Logger.getLogger(PreferencesResource.class);
@Inject PreferencesNotificationService preferencesService;
@GET
@Path("/{utilisateurId}")
@RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"})
@Operation(summary = "Obtenir les préférences d'un utilisateur")
@APIResponse(responseCode = "200", description = "Préférences récupérées avec succès")
public Response obtenirPreferences(
@PathParam("utilisateurId") UUID utilisateurId) {
LOG.infof("Récupération des préférences pour l'utilisateur %s", utilisateurId);
Map<String, Boolean> preferences = preferencesService.obtenirPreferences(utilisateurId);
return Response.ok(preferences).build();
}
@PUT
@Path("/{utilisateurId}")
@RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"})
@Operation(summary = "Mettre à jour les préférences d'un utilisateur")
@APIResponse(responseCode = "204", description = "Préférences mises à jour avec succès")
public Response mettreAJourPreferences(
@PathParam("utilisateurId") UUID utilisateurId, Map<String, Boolean> preferences) {
LOG.infof("Mise à jour des préférences pour l'utilisateur %s", utilisateurId);
preferencesService.mettreAJourPreferences(utilisateurId, preferences);
return Response.noContent().build();
}
@POST
@Path("/{utilisateurId}/reinitialiser")
@RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"})
@Operation(summary = "Réinitialiser les préférences d'un utilisateur")
@APIResponse(responseCode = "204", description = "Préférences réinitialisées avec succès")
public Response reinitialiserPreferences(@PathParam("utilisateurId") UUID utilisateurId) {
LOG.infof("Réinitialisation des préférences pour l'utilisateur %s", utilisateurId);
preferencesService.reinitialiserPreferences(utilisateurId);
return Response.noContent().build();
}
@GET
@Path("/{utilisateurId}/export")
@RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"})
@Operation(summary = "Exporter les préférences d'un utilisateur")
@APIResponse(responseCode = "200", description = "Préférences exportées avec succès")
public Response exporterPreferences(@PathParam("utilisateurId") UUID utilisateurId) {
LOG.infof("Export des préférences pour l'utilisateur %s", utilisateurId);
Map<String, Object> export = preferencesService.exporterPreferences(utilisateurId);
return Response.ok(export).build();
}
}

View File

@@ -1,75 +1,75 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.solidarite.request.CreatePropositionAideRequest;
import dev.lions.unionflow.server.api.dto.solidarite.request.UpdatePropositionAideRequest;
import dev.lions.unionflow.server.api.dto.solidarite.response.PropositionAideResponse;
import dev.lions.unionflow.server.service.PropositionAideService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.Collections;
import java.util.List;
/**
* Resource REST pour les propositions d'aide.
*/
@Path("/api/propositions-aide")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RolesAllowed({"ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER"})
@Tag(name = "Propositions d'aide", description = "Gestion des propositions d'aide solidarité")
public class PropositionAideResource {
@Inject
PropositionAideService propositionAideService;
@GET
@Operation(summary = "Liste les propositions d'aide avec pagination")
public List<PropositionAideResponse> listerToutes(
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("20") int size) {
List<PropositionAideResponse> all = propositionAideService.rechercherAvecFiltres(Collections.emptyMap());
int from = Math.min(page * size, all.size());
int to = Math.min(from + size, all.size());
return from < to ? all.subList(from, to) : List.of();
}
@GET
@Path("/{id}")
@Operation(summary = "Récupère une proposition d'aide par son ID")
public PropositionAideResponse obtenirParId(@PathParam("id") String id) {
PropositionAideResponse response = propositionAideService.obtenirParId(id);
if (response == null) {
throw new NotFoundException("Proposition d'aide non trouvée : " + id);
}
return response;
}
@POST
@Operation(summary = "Crée une nouvelle proposition d'aide")
public Response creer(@Valid CreatePropositionAideRequest request) {
PropositionAideResponse response = propositionAideService.creerProposition(request);
return Response.status(Response.Status.CREATED).entity(response).build();
}
@PUT
@Path("/{id}")
@Operation(summary = "Met à jour une proposition d'aide")
public PropositionAideResponse mettreAJour(@PathParam("id") String id,
@Valid UpdatePropositionAideRequest request) {
return propositionAideService.mettreAJour(id, request);
}
@GET
@Path("/meilleures")
@Operation(summary = "Récupère les meilleures propositions")
public List<PropositionAideResponse> obtenirMeilleures(@QueryParam("limite") @DefaultValue("5") int limite) {
return propositionAideService.obtenirMeilleuresPropositions(limite);
}
}
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.solidarite.request.CreatePropositionAideRequest;
import dev.lions.unionflow.server.api.dto.solidarite.request.UpdatePropositionAideRequest;
import dev.lions.unionflow.server.api.dto.solidarite.response.PropositionAideResponse;
import dev.lions.unionflow.server.service.PropositionAideService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.Collections;
import java.util.List;
/**
* Resource REST pour les propositions d'aide.
*/
@Path("/api/propositions-aide")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RolesAllowed({"ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER"})
@Tag(name = "Propositions d'aide", description = "Gestion des propositions d'aide solidarité")
public class PropositionAideResource {
@Inject
PropositionAideService propositionAideService;
@GET
@Operation(summary = "Liste les propositions d'aide avec pagination")
public List<PropositionAideResponse> listerToutes(
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("20") int size) {
List<PropositionAideResponse> all = propositionAideService.rechercherAvecFiltres(Collections.emptyMap());
int from = Math.min(page * size, all.size());
int to = Math.min(from + size, all.size());
return from < to ? all.subList(from, to) : List.of();
}
@GET
@Path("/{id}")
@Operation(summary = "Récupère une proposition d'aide par son ID")
public PropositionAideResponse obtenirParId(@PathParam("id") String id) {
PropositionAideResponse response = propositionAideService.obtenirParId(id);
if (response == null) {
throw new NotFoundException("Proposition d'aide non trouvée : " + id);
}
return response;
}
@POST
@Operation(summary = "Crée une nouvelle proposition d'aide")
public Response creer(@Valid CreatePropositionAideRequest request) {
PropositionAideResponse response = propositionAideService.creerProposition(request);
return Response.status(Response.Status.CREATED).entity(response).build();
}
@PUT
@Path("/{id}")
@Operation(summary = "Met à jour une proposition d'aide")
public PropositionAideResponse mettreAJour(@PathParam("id") String id,
@Valid UpdatePropositionAideRequest request) {
return propositionAideService.mettreAJour(id, request);
}
@GET
@Path("/meilleures")
@Operation(summary = "Récupère les meilleures propositions")
public List<PropositionAideResponse> obtenirMeilleures(@QueryParam("limite") @DefaultValue("5") int limite) {
return propositionAideService.obtenirMeilleuresPropositions(limite);
}
}

View File

@@ -1,55 +1,55 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.role.response.RoleResponse;
import dev.lions.unionflow.server.entity.Role;
import dev.lions.unionflow.server.service.RoleService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.List;
import java.util.stream.Collectors;
/**
* Resource REST pour les rôles.
*/
@Path("/api/roles")
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed({"ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION"})
@Tag(name = "Rôles", description = "Gestion des rôles et permissions")
public class RoleResource {
@Inject
RoleService roleService;
@GET
@Operation(summary = "Liste tous les rôles actifs")
public List<RoleResponse> listerTous() {
return roleService.listerTousActifs().stream()
.map(this::toDTO)
.collect(Collectors.toList());
}
private RoleResponse toDTO(Role entity) {
RoleResponse dto = new RoleResponse();
dto.setId(entity.getId());
dto.setCode(entity.getCode());
dto.setLibelle(entity.getLibelle());
dto.setDescription(entity.getDescription());
dto.setTypeRole(entity.getTypeRole());
dto.setNiveauHierarchique(entity.getNiveauHierarchique());
if (entity.getOrganisation() != null) {
dto.setOrganisationId(entity.getOrganisation().getId());
dto.setNomOrganisation(entity.getOrganisation().getNom());
}
dto.setActif(entity.getActif());
dto.setDateCreation(entity.getDateCreation());
dto.setDateModification(entity.getDateModification());
return dto;
}
}
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.role.response.RoleResponse;
import dev.lions.unionflow.server.entity.Role;
import dev.lions.unionflow.server.service.RoleService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.List;
import java.util.stream.Collectors;
/**
* Resource REST pour les rôles.
*/
@Path("/api/roles")
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed({"ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION"})
@Tag(name = "Rôles", description = "Gestion des rôles et permissions")
public class RoleResource {
@Inject
RoleService roleService;
@GET
@Operation(summary = "Liste tous les rôles actifs")
public List<RoleResponse> listerTous() {
return roleService.listerTousActifs().stream()
.map(this::toDTO)
.collect(Collectors.toList());
}
private RoleResponse toDTO(Role entity) {
RoleResponse dto = new RoleResponse();
dto.setId(entity.getId());
dto.setCode(entity.getCode());
dto.setLibelle(entity.getLibelle());
dto.setDescription(entity.getDescription());
dto.setTypeRole(entity.getTypeRole());
dto.setNiveauHierarchique(entity.getNiveauHierarchique());
if (entity.getOrganisation() != null) {
dto.setOrganisationId(entity.getOrganisation().getId());
dto.setNomOrganisation(entity.getOrganisation().getNom());
}
dto.setActif(entity.getActif());
dto.setDateCreation(entity.getDateCreation());
dto.setDateModification(entity.getDateModification());
return dto;
}
}

View File

@@ -1,293 +1,293 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.system.request.UpdateSystemConfigRequest;
import dev.lions.unionflow.server.api.dto.system.response.CacheStatsResponse;
import dev.lions.unionflow.server.api.dto.system.response.SystemConfigResponse;
import dev.lions.unionflow.server.api.dto.logs.response.SystemMetricsResponse;
import dev.lions.unionflow.server.api.dto.system.response.SystemTestResultResponse;
import dev.lions.unionflow.server.service.SystemConfigService;
import dev.lions.unionflow.server.service.SystemMetricsService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.Map;
/**
* REST Resource pour la gestion de la configuration système
*/
@Slf4j
@Path("/api/system")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Système", description = "Gestion de la configuration système")
public class SystemResource {
@Inject
SystemConfigService systemConfigService;
@Inject
SystemMetricsService systemMetricsService;
/**
* Récupérer la configuration système
*/
@GET
@Path("/config")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Récupérer la configuration système", description = "Retourne la configuration système complète")
public SystemConfigResponse getSystemConfig() {
log.info("GET /api/system/config");
return systemConfigService.getSystemConfig();
}
/**
* Mettre à jour la configuration système
*/
@PUT
@Path("/config")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Mettre à jour la configuration système")
public SystemConfigResponse updateSystemConfig(@Valid UpdateSystemConfigRequest request) {
log.info("PUT /api/system/config");
return systemConfigService.updateSystemConfig(request);
}
/**
* Récupérer les statistiques du cache
*/
@GET
@Path("/cache/stats")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Récupérer les statistiques du cache système")
public CacheStatsResponse getCacheStats() {
log.info("GET /api/system/cache/stats");
return systemConfigService.getCacheStats();
}
/**
* Vider le cache système
*/
@POST
@Path("/cache/clear")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Vider le cache système")
public Response clearCache() {
log.info("POST /api/system/cache/clear");
systemConfigService.clearCache();
return Response.ok().entity(java.util.Map.of("message", "Cache vidé avec succès")).build();
}
/**
* Tester la connexion à la base de données
*/
@POST
@Path("/test/database")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Tester la connexion à la base de données")
public SystemTestResultResponse testDatabaseConnection() {
log.info("POST /api/system/test/database");
return systemConfigService.testDatabaseConnection();
}
/**
* Tester la configuration email
*/
@POST
@Path("/test/email")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Tester la configuration email")
public SystemTestResultResponse testEmailConfiguration() {
log.info("POST /api/system/test/email");
return systemConfigService.testEmailConfiguration();
}
/**
* Récupérer les métriques système en temps réel
*/
@GET
@Path("/metrics")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(
summary = "Récupérer les métriques système en temps réel",
description = "Retourne toutes les métriques système (CPU, RAM, disque, utilisateurs actifs, etc.)"
)
public SystemMetricsResponse getSystemMetrics() {
log.info("GET /api/system/metrics");
return systemMetricsService.getSystemMetrics();
}
/**
* Optimiser la base de données
*/
@POST
@Path("/database/optimize")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Optimiser la base de données (VACUUM ANALYZE)")
public Response optimizeDatabase() {
log.info("POST /api/system/database/optimize");
return Response.ok(systemConfigService.optimizeDatabase()).build();
}
/**
* Forcer la déconnexion globale
*/
@POST
@Path("/auth/logout-all")
@RolesAllowed({"SUPER_ADMIN"})
@Operation(summary = "Forcer la déconnexion de tous les utilisateurs")
public Response forceGlobalLogout() {
log.info("POST /api/system/auth/logout-all");
return Response.ok(systemConfigService.forceGlobalLogout()).build();
}
/**
* Nettoyer les sessions expirées
*/
@POST
@Path("/sessions/cleanup")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Nettoyer les sessions expirées")
public Response cleanupSessions() {
log.info("POST /api/system/sessions/cleanup");
return Response.ok(systemConfigService.cleanupSessions()).build();
}
/**
* Nettoyer les anciens logs
*/
@POST
@Path("/logs/cleanup")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Nettoyer les anciens logs selon la politique de rétention")
public Response cleanOldLogs() {
log.info("POST /api/system/logs/cleanup");
return Response.ok(systemConfigService.cleanOldLogs()).build();
}
/**
* Purger les données expirées
*/
@POST
@Path("/data/purge")
@RolesAllowed({"SUPER_ADMIN"})
@Operation(summary = "Purger les données expirées (RGPD)")
public Response purgeExpiredData() {
log.info("POST /api/system/data/purge");
return Response.ok(systemConfigService.purgeExpiredData()).build();
}
/**
* Analyser les performances de la base de données
*/
@POST
@Path("/performance/analyze")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Analyser les performances du système")
public Response analyzePerformance() {
log.info("POST /api/system/performance/analyze");
return Response.ok(systemConfigService.analyzePerformance()).build();
}
/**
* Créer une sauvegarde
*/
@POST
@Path("/backup/create")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Créer une sauvegarde du système")
public Response createBackup() {
log.info("POST /api/system/backup/create");
return Response.ok(systemConfigService.createBackup()).build();
}
/**
* Planifier une maintenance
*/
@POST
@Path("/maintenance/schedule")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Planifier une maintenance")
public Response scheduleMaintenance(@QueryParam("scheduledAt") String scheduledAt, @QueryParam("reason") String reason) {
log.info("POST /api/system/maintenance/schedule");
return Response.ok(systemConfigService.scheduleMaintenance(scheduledAt, reason)).build();
}
/**
* Activer la maintenance d'urgence
*/
@POST
@Path("/maintenance/emergency")
@RolesAllowed({"SUPER_ADMIN"})
@Operation(summary = "Activer le mode maintenance d'urgence")
public Response emergencyMaintenance() {
log.info("POST /api/system/maintenance/emergency");
return Response.ok(systemConfigService.emergencyMaintenance()).build();
}
/**
* Vérifier les mises à jour
*/
@GET
@Path("/updates/check")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Vérifier les mises à jour disponibles")
public Response checkUpdates() {
log.info("GET /api/system/updates/check");
return Response.ok(systemConfigService.checkUpdates()).build();
}
/**
* Exporter les logs récents
*/
@GET
@Path("/logs/export")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Exporter les logs des dernières 24h")
public Response exportLogs() {
log.info("GET /api/system/logs/export");
return Response.ok(systemConfigService.exportLogs()).build();
}
/**
* Générer un rapport d'utilisation
*/
@GET
@Path("/reports/usage")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Générer un rapport d'utilisation du système")
public Response generateUsageReport() {
log.info("GET /api/system/reports/usage");
return Response.ok(systemConfigService.generateUsageReport()).build();
}
/**
* Générer un rapport d'audit
*/
@GET
@Path("/audit/report")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Générer un rapport d'audit")
public Response generateAuditReport() {
log.info("GET /api/system/audit/report");
return Response.ok(systemConfigService.generateAuditReport()).build();
}
/**
* Export RGPD
*/
@POST
@Path("/gdpr/export")
@RolesAllowed({"SUPER_ADMIN"})
@Operation(summary = "Initier un export RGPD des données utilisateurs")
public Response exportGDPRData() {
log.info("POST /api/system/gdpr/export");
return Response.ok(systemConfigService.exportGDPRData()).build();
}
}
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.system.request.UpdateSystemConfigRequest;
import dev.lions.unionflow.server.api.dto.system.response.CacheStatsResponse;
import dev.lions.unionflow.server.api.dto.system.response.SystemConfigResponse;
import dev.lions.unionflow.server.api.dto.logs.response.SystemMetricsResponse;
import dev.lions.unionflow.server.api.dto.system.response.SystemTestResultResponse;
import dev.lions.unionflow.server.service.SystemConfigService;
import dev.lions.unionflow.server.service.SystemMetricsService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.Map;
/**
* REST Resource pour la gestion de la configuration système
*/
@Slf4j
@Path("/api/system")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Système", description = "Gestion de la configuration système")
public class SystemResource {
@Inject
SystemConfigService systemConfigService;
@Inject
SystemMetricsService systemMetricsService;
/**
* Récupérer la configuration système
*/
@GET
@Path("/config")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Récupérer la configuration système", description = "Retourne la configuration système complète")
public SystemConfigResponse getSystemConfig() {
log.info("GET /api/system/config");
return systemConfigService.getSystemConfig();
}
/**
* Mettre à jour la configuration système
*/
@PUT
@Path("/config")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Mettre à jour la configuration système")
public SystemConfigResponse updateSystemConfig(@Valid UpdateSystemConfigRequest request) {
log.info("PUT /api/system/config");
return systemConfigService.updateSystemConfig(request);
}
/**
* Récupérer les statistiques du cache
*/
@GET
@Path("/cache/stats")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Récupérer les statistiques du cache système")
public CacheStatsResponse getCacheStats() {
log.info("GET /api/system/cache/stats");
return systemConfigService.getCacheStats();
}
/**
* Vider le cache système
*/
@POST
@Path("/cache/clear")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Vider le cache système")
public Response clearCache() {
log.info("POST /api/system/cache/clear");
systemConfigService.clearCache();
return Response.ok().entity(java.util.Map.of("message", "Cache vidé avec succès")).build();
}
/**
* Tester la connexion à la base de données
*/
@POST
@Path("/test/database")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Tester la connexion à la base de données")
public SystemTestResultResponse testDatabaseConnection() {
log.info("POST /api/system/test/database");
return systemConfigService.testDatabaseConnection();
}
/**
* Tester la configuration email
*/
@POST
@Path("/test/email")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Tester la configuration email")
public SystemTestResultResponse testEmailConfiguration() {
log.info("POST /api/system/test/email");
return systemConfigService.testEmailConfiguration();
}
/**
* Récupérer les métriques système en temps réel
*/
@GET
@Path("/metrics")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(
summary = "Récupérer les métriques système en temps réel",
description = "Retourne toutes les métriques système (CPU, RAM, disque, utilisateurs actifs, etc.)"
)
public SystemMetricsResponse getSystemMetrics() {
log.info("GET /api/system/metrics");
return systemMetricsService.getSystemMetrics();
}
/**
* Optimiser la base de données
*/
@POST
@Path("/database/optimize")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Optimiser la base de données (VACUUM ANALYZE)")
public Response optimizeDatabase() {
log.info("POST /api/system/database/optimize");
return Response.ok(systemConfigService.optimizeDatabase()).build();
}
/**
* Forcer la déconnexion globale
*/
@POST
@Path("/auth/logout-all")
@RolesAllowed({"SUPER_ADMIN"})
@Operation(summary = "Forcer la déconnexion de tous les utilisateurs")
public Response forceGlobalLogout() {
log.info("POST /api/system/auth/logout-all");
return Response.ok(systemConfigService.forceGlobalLogout()).build();
}
/**
* Nettoyer les sessions expirées
*/
@POST
@Path("/sessions/cleanup")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Nettoyer les sessions expirées")
public Response cleanupSessions() {
log.info("POST /api/system/sessions/cleanup");
return Response.ok(systemConfigService.cleanupSessions()).build();
}
/**
* Nettoyer les anciens logs
*/
@POST
@Path("/logs/cleanup")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Nettoyer les anciens logs selon la politique de rétention")
public Response cleanOldLogs() {
log.info("POST /api/system/logs/cleanup");
return Response.ok(systemConfigService.cleanOldLogs()).build();
}
/**
* Purger les données expirées
*/
@POST
@Path("/data/purge")
@RolesAllowed({"SUPER_ADMIN"})
@Operation(summary = "Purger les données expirées (RGPD)")
public Response purgeExpiredData() {
log.info("POST /api/system/data/purge");
return Response.ok(systemConfigService.purgeExpiredData()).build();
}
/**
* Analyser les performances de la base de données
*/
@POST
@Path("/performance/analyze")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Analyser les performances du système")
public Response analyzePerformance() {
log.info("POST /api/system/performance/analyze");
return Response.ok(systemConfigService.analyzePerformance()).build();
}
/**
* Créer une sauvegarde
*/
@POST
@Path("/backup/create")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Créer une sauvegarde du système")
public Response createBackup() {
log.info("POST /api/system/backup/create");
return Response.ok(systemConfigService.createBackup()).build();
}
/**
* Planifier une maintenance
*/
@POST
@Path("/maintenance/schedule")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Planifier une maintenance")
public Response scheduleMaintenance(@QueryParam("scheduledAt") String scheduledAt, @QueryParam("reason") String reason) {
log.info("POST /api/system/maintenance/schedule");
return Response.ok(systemConfigService.scheduleMaintenance(scheduledAt, reason)).build();
}
/**
* Activer la maintenance d'urgence
*/
@POST
@Path("/maintenance/emergency")
@RolesAllowed({"SUPER_ADMIN"})
@Operation(summary = "Activer le mode maintenance d'urgence")
public Response emergencyMaintenance() {
log.info("POST /api/system/maintenance/emergency");
return Response.ok(systemConfigService.emergencyMaintenance()).build();
}
/**
* Vérifier les mises à jour
*/
@GET
@Path("/updates/check")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Vérifier les mises à jour disponibles")
public Response checkUpdates() {
log.info("GET /api/system/updates/check");
return Response.ok(systemConfigService.checkUpdates()).build();
}
/**
* Exporter les logs récents
*/
@GET
@Path("/logs/export")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Exporter les logs des dernières 24h")
public Response exportLogs() {
log.info("GET /api/system/logs/export");
return Response.ok(systemConfigService.exportLogs()).build();
}
/**
* Générer un rapport d'utilisation
*/
@GET
@Path("/reports/usage")
@RolesAllowed({"SUPER_ADMIN", "ADMIN"})
@Operation(summary = "Générer un rapport d'utilisation du système")
public Response generateUsageReport() {
log.info("GET /api/system/reports/usage");
return Response.ok(systemConfigService.generateUsageReport()).build();
}
/**
* Générer un rapport d'audit
*/
@GET
@Path("/audit/report")
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
@Operation(summary = "Générer un rapport d'audit")
public Response generateAuditReport() {
log.info("GET /api/system/audit/report");
return Response.ok(systemConfigService.generateAuditReport()).build();
}
/**
* Export RGPD
*/
@POST
@Path("/gdpr/export")
@RolesAllowed({"SUPER_ADMIN"})
@Operation(summary = "Initier un export RGPD des données utilisateurs")
public Response exportGDPRData() {
log.info("POST /api/system/gdpr/export");
return Response.ok(systemConfigService.exportGDPRData()).build();
}
}

View File

@@ -1,276 +1,276 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.reference.request.CreateTypeReferenceRequest;
import dev.lions.unionflow.server.api.dto.reference.request.UpdateTypeReferenceRequest;
import dev.lions.unionflow.server.api.dto.reference.response.TypeReferenceResponse;
import dev.lions.unionflow.server.service.TypeReferenceService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
/**
* Ressource REST pour le CRUD des données
* de référence.
*
* <p>
* Expose les endpoints permettant de gérer
* dynamiquement toutes les valeurs catégorielles
* de l'application (statuts, types, devises,
* priorités, etc.) via la table
* {@code types_reference}.
*
* @author UnionFlow Team
* @version 3.0
* @since 2026-02-21
*/
@Path("/api/v1/types-reference")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Types de référence", description = "Gestion des données de référence"
+ " paramétrables")
@RolesAllowed({
"SUPER_ADMIN",
"ADMIN", "MEMBRE", "USER"
})
public class TypeReferenceResource {
private static final Logger LOG = Logger.getLogger(TypeReferenceResource.class);
@Inject
TypeReferenceService service;
/**
* Liste les références actives d'un domaine.
*
* @param domaine le domaine fonctionnel
* @param organisationId l'UUID de l'organisation
* @return liste triée par ordre d'affichage
*/
@GET
@Operation(summary = "Lister par domaine", description = "Récupère les valeurs actives"
+ " d'un domaine donné")
@APIResponses({
@APIResponse(responseCode = "200", description = "Liste récupérée", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = TypeReferenceResponse.class))),
@APIResponse(responseCode = "401", description = "Non authentifié")
})
public Response listerParDomaine(
@Parameter(description = "Domaine fonctionnel", example = "STATUT_ORGANISATION", required = true) @QueryParam("domaine") String domaine,
@Parameter(description = "UUID de l'organisation", example = "550e8400-e29b-41d4-a716-4466"
+ "55440000") @QueryParam("organisationId") UUID organisationId) {
if (domaine == null || domaine.isBlank()) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity(Map.of(
"error",
"Le paramètre 'domaine' est"
+ " obligatoire"))
.build();
}
List<TypeReferenceResponse> result = service.listerParDomaine(
domaine, organisationId);
return Response.ok(result).build();
}
/**
* Retourne un type de référence par son ID.
*
* @param id l'UUID du type de référence
* @return le détail complet
*/
@GET
@Path("/{id}")
@Operation(summary = "Détail d'une référence", description = "Récupère une référence par"
+ " son identifiant")
@APIResponses({
@APIResponse(responseCode = "200", description = "Référence trouvée"),
@APIResponse(responseCode = "404", description = "Référence non trouvée")
})
public Response trouverParId(
@PathParam("id") UUID id) {
try {
TypeReferenceResponse response = service.trouverParId(id);
return Response.ok(response).build();
} catch (IllegalArgumentException e) {
return Response
.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", e.getMessage()))
.build();
}
}
/**
* Liste les domaines disponibles.
*
* @return noms de domaines distincts
*/
@GET
@Path("/domaines")
@Operation(summary = "Lister les domaines", description = "Récupère la liste des domaines"
+ " disponibles")
public Response listerDomaines() {
List<String> domaines = service.listerDomaines();
return Response.ok(domaines).build();
}
/**
* Retourne la valeur par défaut d'un domaine.
*
* @param domaine le domaine fonctionnel
* @param organisationId l'UUID de l'organisation
* @return la valeur par défaut
*/
@GET
@Path("/defaut")
@Operation(summary = "Valeur par défaut", description = "Récupère la valeur par défaut"
+ " d'un domaine")
public Response trouverDefaut(
@Parameter(description = "Domaine fonctionnel", required = true) @QueryParam("domaine") String domaine,
@Parameter(description = "UUID de l'organisation") @QueryParam("organisationId") UUID organisationId) {
if (domaine == null || domaine.isBlank()) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "Le paramètre 'domaine' est obligatoire"))
.build();
}
try {
TypeReferenceResponse response = service.trouverDefaut(
domaine, organisationId);
return Response.ok(response).build();
} catch (IllegalArgumentException e) {
return Response
.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", e.getMessage()))
.build();
}
}
/**
* Crée une nouvelle donnée de référence.
*
* @param request la requête de création validée
* @return la référence créée (HTTP 201)
*/
@POST
@RolesAllowed({
"SUPER_ADMIN", "ADMIN"
})
@Operation(summary = "Créer une référence", description = "Ajoute une nouvelle valeur dans"
+ " un domaine")
@APIResponses({
@APIResponse(responseCode = "201", description = "Référence créée"),
@APIResponse(responseCode = "400", description = "Données invalides ou"
+ " code dupliqué")
})
public Response creer(
@Valid CreateTypeReferenceRequest request) {
try {
TypeReferenceResponse created = service.creer(request);
return Response
.status(Response.Status.CREATED)
.entity(created)
.build();
} catch (IllegalArgumentException e) {
LOG.warnf(
"Erreur création référence: %s",
e.getMessage());
return Response
.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", e.getMessage()))
.build();
}
}
/**
* Met à jour une donnée de référence.
*
* @param id l'UUID de la référence
* @param request la requête de mise à jour
* @return la référence mise à jour
*/
@PUT
@Path("/{id}")
@RolesAllowed({
"SUPER_ADMIN", "ADMIN"
})
@Operation(summary = "Modifier une référence", description = "Met à jour une valeur"
+ " existante")
@APIResponses({
@APIResponse(responseCode = "200", description = "Référence modifiée"),
@APIResponse(responseCode = "400", description = "Données invalides"),
@APIResponse(responseCode = "404", description = "Référence non trouvée")
})
public Response modifier(
@PathParam("id") UUID id,
@Valid UpdateTypeReferenceRequest request) {
try {
TypeReferenceResponse updated = service.modifier(id, request);
return Response.ok(updated).build();
} catch (IllegalArgumentException e) {
LOG.warnf(
"Erreur modification référence: %s",
e.getMessage());
return Response
.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", e.getMessage()))
.build();
}
}
/**
* Supprime une donnée de référence.
*
* <p>
* Les valeurs système ne peuvent pas être
* supprimées.
*
* @param id l'UUID de la référence
* @return HTTP 204 si succès
*/
@DELETE
@Path("/{id}")
@RolesAllowed({
"SUPER_ADMIN"
})
@Operation(summary = "Supprimer une référence", description = "Supprime une valeur non"
+ " système")
@APIResponses({
@APIResponse(responseCode = "204", description = "Référence supprimée"),
@APIResponse(responseCode = "400", description = "Valeur système non"
+ " supprimable"),
@APIResponse(responseCode = "404", description = "Référence non trouvée")
})
public Response supprimer(
@PathParam("id") UUID id) {
try {
service.supprimer(id);
return Response.noContent().build();
} catch (IllegalArgumentException e) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", e.getMessage()))
.build();
}
}
}
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.reference.request.CreateTypeReferenceRequest;
import dev.lions.unionflow.server.api.dto.reference.request.UpdateTypeReferenceRequest;
import dev.lions.unionflow.server.api.dto.reference.response.TypeReferenceResponse;
import dev.lions.unionflow.server.service.TypeReferenceService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
/**
* Ressource REST pour le CRUD des données
* de référence.
*
* <p>
* Expose les endpoints permettant de gérer
* dynamiquement toutes les valeurs catégorielles
* de l'application (statuts, types, devises,
* priorités, etc.) via la table
* {@code types_reference}.
*
* @author UnionFlow Team
* @version 3.0
* @since 2026-02-21
*/
@Path("/api/v1/types-reference")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Types de référence", description = "Gestion des données de référence"
+ " paramétrables")
@RolesAllowed({
"SUPER_ADMIN",
"ADMIN", "MEMBRE", "USER"
})
public class TypeReferenceResource {
private static final Logger LOG = Logger.getLogger(TypeReferenceResource.class);
@Inject
TypeReferenceService service;
/**
* Liste les références actives d'un domaine.
*
* @param domaine le domaine fonctionnel
* @param organisationId l'UUID de l'organisation
* @return liste triée par ordre d'affichage
*/
@GET
@Operation(summary = "Lister par domaine", description = "Récupère les valeurs actives"
+ " d'un domaine donné")
@APIResponses({
@APIResponse(responseCode = "200", description = "Liste récupérée", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = TypeReferenceResponse.class))),
@APIResponse(responseCode = "401", description = "Non authentifié")
})
public Response listerParDomaine(
@Parameter(description = "Domaine fonctionnel", example = "STATUT_ORGANISATION", required = true) @QueryParam("domaine") String domaine,
@Parameter(description = "UUID de l'organisation", example = "550e8400-e29b-41d4-a716-4466"
+ "55440000") @QueryParam("organisationId") UUID organisationId) {
if (domaine == null || domaine.isBlank()) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity(Map.of(
"error",
"Le paramètre 'domaine' est"
+ " obligatoire"))
.build();
}
List<TypeReferenceResponse> result = service.listerParDomaine(
domaine, organisationId);
return Response.ok(result).build();
}
/**
* Retourne un type de référence par son ID.
*
* @param id l'UUID du type de référence
* @return le détail complet
*/
@GET
@Path("/{id}")
@Operation(summary = "Détail d'une référence", description = "Récupère une référence par"
+ " son identifiant")
@APIResponses({
@APIResponse(responseCode = "200", description = "Référence trouvée"),
@APIResponse(responseCode = "404", description = "Référence non trouvée")
})
public Response trouverParId(
@PathParam("id") UUID id) {
try {
TypeReferenceResponse response = service.trouverParId(id);
return Response.ok(response).build();
} catch (IllegalArgumentException e) {
return Response
.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", e.getMessage()))
.build();
}
}
/**
* Liste les domaines disponibles.
*
* @return noms de domaines distincts
*/
@GET
@Path("/domaines")
@Operation(summary = "Lister les domaines", description = "Récupère la liste des domaines"
+ " disponibles")
public Response listerDomaines() {
List<String> domaines = service.listerDomaines();
return Response.ok(domaines).build();
}
/**
* Retourne la valeur par défaut d'un domaine.
*
* @param domaine le domaine fonctionnel
* @param organisationId l'UUID de l'organisation
* @return la valeur par défaut
*/
@GET
@Path("/defaut")
@Operation(summary = "Valeur par défaut", description = "Récupère la valeur par défaut"
+ " d'un domaine")
public Response trouverDefaut(
@Parameter(description = "Domaine fonctionnel", required = true) @QueryParam("domaine") String domaine,
@Parameter(description = "UUID de l'organisation") @QueryParam("organisationId") UUID organisationId) {
if (domaine == null || domaine.isBlank()) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "Le paramètre 'domaine' est obligatoire"))
.build();
}
try {
TypeReferenceResponse response = service.trouverDefaut(
domaine, organisationId);
return Response.ok(response).build();
} catch (IllegalArgumentException e) {
return Response
.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", e.getMessage()))
.build();
}
}
/**
* Crée une nouvelle donnée de référence.
*
* @param request la requête de création validée
* @return la référence créée (HTTP 201)
*/
@POST
@RolesAllowed({
"SUPER_ADMIN", "ADMIN"
})
@Operation(summary = "Créer une référence", description = "Ajoute une nouvelle valeur dans"
+ " un domaine")
@APIResponses({
@APIResponse(responseCode = "201", description = "Référence créée"),
@APIResponse(responseCode = "400", description = "Données invalides ou"
+ " code dupliqué")
})
public Response creer(
@Valid CreateTypeReferenceRequest request) {
try {
TypeReferenceResponse created = service.creer(request);
return Response
.status(Response.Status.CREATED)
.entity(created)
.build();
} catch (IllegalArgumentException e) {
LOG.warnf(
"Erreur création référence: %s",
e.getMessage());
return Response
.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", e.getMessage()))
.build();
}
}
/**
* Met à jour une donnée de référence.
*
* @param id l'UUID de la référence
* @param request la requête de mise à jour
* @return la référence mise à jour
*/
@PUT
@Path("/{id}")
@RolesAllowed({
"SUPER_ADMIN", "ADMIN"
})
@Operation(summary = "Modifier une référence", description = "Met à jour une valeur"
+ " existante")
@APIResponses({
@APIResponse(responseCode = "200", description = "Référence modifiée"),
@APIResponse(responseCode = "400", description = "Données invalides"),
@APIResponse(responseCode = "404", description = "Référence non trouvée")
})
public Response modifier(
@PathParam("id") UUID id,
@Valid UpdateTypeReferenceRequest request) {
try {
TypeReferenceResponse updated = service.modifier(id, request);
return Response.ok(updated).build();
} catch (IllegalArgumentException e) {
LOG.warnf(
"Erreur modification référence: %s",
e.getMessage());
return Response
.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", e.getMessage()))
.build();
}
}
/**
* Supprime une donnée de référence.
*
* <p>
* Les valeurs système ne peuvent pas être
* supprimées.
*
* @param id l'UUID de la référence
* @return HTTP 204 si succès
*/
@DELETE
@Path("/{id}")
@RolesAllowed({
"SUPER_ADMIN"
})
@Operation(summary = "Supprimer une référence", description = "Supprime une valeur non"
+ " système")
@APIResponses({
@APIResponse(responseCode = "204", description = "Référence supprimée"),
@APIResponse(responseCode = "400", description = "Valeur système non"
+ " supprimable"),
@APIResponse(responseCode = "404", description = "Référence non trouvée")
})
public Response supprimer(
@PathParam("id") UUID id) {
try {
service.supprimer(id);
return Response.noContent().build();
} catch (IllegalArgumentException e) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", e.getMessage()))
.build();
}
}
}

View File

@@ -1,191 +1,191 @@
package dev.lions.unionflow.server.resource;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.lions.unionflow.server.api.dto.mutuelle.epargne.TransactionEpargneRequest;
import dev.lions.unionflow.server.api.enums.mutuelle.epargne.TypeTransactionEpargne;
import dev.lions.unionflow.server.entity.IntentionPaiement;
import dev.lions.unionflow.server.repository.IntentionPaiementRepository;
import dev.lions.unionflow.server.service.VersementService;
import dev.lions.unionflow.server.service.mutuelle.epargne.TransactionEpargneService;
import jakarta.annotation.security.PermitAll;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;
import java.math.BigDecimal;
import java.net.URI;
import java.util.UUID;
/**
* Redirection après paiement Wave (spec Checkout API).
* Wave redirige le client vers success_url ou error_url (https).
* On renvoie une 302 vers le deep link de l'app (unionflow://payment?result=...&ref=...).
* En mode mock : GET /success exécute aussi la validation simulée (intention COMPLETEE, cotisations PAYEE)
* pour que le flux "Ouvrir Wave" → retour app soit entièrement mocké.
*/
@Path("/api/wave-redirect")
@PermitAll
public class WaveRedirectResource {
private static final Logger LOG = Logger.getLogger(WaveRedirectResource.class);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@ConfigProperty(name = "wave.deep.link.scheme", defaultValue = "unionflow")
String deepLinkScheme;
@ConfigProperty(name = "wave.mock.enabled", defaultValue = "false")
boolean mockEnabled;
@Inject
IntentionPaiementRepository intentionPaiementRepository;
@Inject
TransactionEpargneService transactionEpargneService;
@Inject
VersementService versementService;
@GET
@Path("/success")
@Transactional
public Response success(@QueryParam("ref") String ref) {
LOG.infof("Wave redirect success (mobile), ref=%s", ref);
if (ref != null && !ref.isBlank()) {
applyCompletion(ref);
}
String location = buildDeepLink("success", ref);
return Response.seeOther(URI.create(location)).build();
}
/**
* Endpoint de redirection Wave pour le flux web QR code.
* Appelé par Wave sur le téléphone du membre après paiement confirmé.
* Marque la cotisation PAYEE et affiche une page HTML de confirmation.
*/
@GET
@Path("/web-success")
@Produces(MediaType.TEXT_HTML)
@Transactional
public Response webSuccess(@QueryParam("ref") String ref) {
LOG.infof("Wave redirect web-success, ref=%s", ref);
if (ref != null && !ref.isBlank()) {
applyCompletion(ref);
}
String html = """
<!DOCTYPE html>
<html lang="fr">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Paiement confirmé</title>
<style>
body{font-family:sans-serif;display:flex;flex-direction:column;align-items:center;
justify-content:center;min-height:100vh;margin:0;background:#f0fdf4;}
.card{background:#fff;border-radius:16px;padding:2rem 2.5rem;text-align:center;
box-shadow:0 4px 24px #0001;max-width:360px;}
.icon{font-size:3.5rem;margin-bottom:1rem;}
h2{color:#16a34a;margin:.5rem 0;}
p{color:#555;margin:.5rem 0;}
</style>
</head>
<body>
<div class="card">
<div class="icon">✅</div>
<h2>Paiement confirmé !</h2>
<p>Votre cotisation a été enregistrée avec succès.</p>
<p style="font-size:.85rem;color:#888;margin-top:1rem;">
Vous pouvez fermer cette page et revenir sur UnionFlow.
</p>
</div>
</body>
</html>
""";
return Response.ok(html).build();
}
@GET
@Path("/error")
public Response error(@QueryParam("ref") String ref) {
LOG.infof("Wave redirect error, ref=%s", ref);
String location = buildDeepLink("error", ref);
return Response.seeOther(URI.create(location)).build();
}
/**
* Test uniquement (wave.mock.enabled=true) : simule la validation Wave puis redirige.
* Appelle la même logique que /success en mock (applyMockCompletion).
*/
@GET
@Path("/mock-complete")
@Transactional
public Response mockComplete(@QueryParam("ref") String ref) {
if (!mockEnabled) {
LOG.warn("mock-complete ignoré (wave.mock.enabled=false)");
return Response.seeOther(URI.create(buildDeepLink("error", ref))).build();
}
if (ref == null || ref.isBlank()) {
return Response.status(Response.Status.BAD_REQUEST).entity("ref requis").build();
}
applyCompletion(ref);
return Response.seeOther(URI.create(buildDeepLink("success", ref))).build();
}
/**
* Marque l'intention comme complétée et réconcilie les cotisations/dépôts liés.
* Délègue au PaiementService pour les cotisations ; gère les dépôts épargne localement.
*/
private void applyCompletion(String ref) {
try {
UUID intentionId = UUID.fromString(ref.trim());
IntentionPaiement intention = intentionPaiementRepository.findById(intentionId);
if (intention == null) {
LOG.warnf("Intention non trouvée: %s", ref);
return;
}
// Gérer les dépôts épargne (non couverts par PaiementService)
String objetsCibles = intention.getObjetsCibles();
if (objetsCibles != null && !objetsCibles.isBlank()) {
JsonNode arr = OBJECT_MAPPER.readTree(objetsCibles);
if (arr.isArray()) {
for (JsonNode node : arr) {
if ("DEPOT_EPARGNE".equals(node.path("type").asText())
&& node.has("compteId") && node.has("montant")) {
String compteId = node.get("compteId").asText();
BigDecimal montant = new BigDecimal(node.get("montant").asText());
TransactionEpargneRequest req = TransactionEpargneRequest.builder()
.compteId(compteId)
.typeTransaction(TypeTransactionEpargne.DEPOT)
.montant(montant)
.motif("Dépôt via Wave (mobile money)")
.build();
transactionEpargneService.executerTransaction(req);
LOG.infof("Wave: dépôt épargne %s XOF sur compte %s", montant, compteId);
}
}
}
}
// Déléguer la confirmation cotisations au service
versementService.confirmerVersementWave(intention, null);
LOG.infof("Wave: intention %s complétée", ref);
} catch (Exception e) {
LOG.errorf(e, "Wave: erreur applyCompletion ref=%s", ref);
}
}
private String buildDeepLink(String result, String ref) {
StringBuilder sb = new StringBuilder();
sb.append(deepLinkScheme).append("://payment?result=").append(result);
if (ref != null && !ref.isBlank()) {
sb.append("&ref=").append(ref);
}
return sb.toString();
}
}
package dev.lions.unionflow.server.resource;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.lions.unionflow.server.api.dto.mutuelle.epargne.TransactionEpargneRequest;
import dev.lions.unionflow.server.api.enums.mutuelle.epargne.TypeTransactionEpargne;
import dev.lions.unionflow.server.entity.IntentionPaiement;
import dev.lions.unionflow.server.repository.IntentionPaiementRepository;
import dev.lions.unionflow.server.service.VersementService;
import dev.lions.unionflow.server.service.mutuelle.epargne.TransactionEpargneService;
import jakarta.annotation.security.PermitAll;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;
import java.math.BigDecimal;
import java.net.URI;
import java.util.UUID;
/**
* Redirection après paiement Wave (spec Checkout API).
* Wave redirige le client vers success_url ou error_url (https).
* On renvoie une 302 vers le deep link de l'app (unionflow://payment?result=...&ref=...).
* En mode mock : GET /success exécute aussi la validation simulée (intention COMPLETEE, cotisations PAYEE)
* pour que le flux "Ouvrir Wave" → retour app soit entièrement mocké.
*/
@Path("/api/wave-redirect")
@PermitAll
public class WaveRedirectResource {
private static final Logger LOG = Logger.getLogger(WaveRedirectResource.class);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@ConfigProperty(name = "wave.deep.link.scheme", defaultValue = "unionflow")
String deepLinkScheme;
@ConfigProperty(name = "wave.mock.enabled", defaultValue = "false")
boolean mockEnabled;
@Inject
IntentionPaiementRepository intentionPaiementRepository;
@Inject
TransactionEpargneService transactionEpargneService;
@Inject
VersementService versementService;
@GET
@Path("/success")
@Transactional
public Response success(@QueryParam("ref") String ref) {
LOG.infof("Wave redirect success (mobile), ref=%s", ref);
if (ref != null && !ref.isBlank()) {
applyCompletion(ref);
}
String location = buildDeepLink("success", ref);
return Response.seeOther(URI.create(location)).build();
}
/**
* Endpoint de redirection Wave pour le flux web QR code.
* Appelé par Wave sur le téléphone du membre après paiement confirmé.
* Marque la cotisation PAYEE et affiche une page HTML de confirmation.
*/
@GET
@Path("/web-success")
@Produces(MediaType.TEXT_HTML)
@Transactional
public Response webSuccess(@QueryParam("ref") String ref) {
LOG.infof("Wave redirect web-success, ref=%s", ref);
if (ref != null && !ref.isBlank()) {
applyCompletion(ref);
}
String html = """
<!DOCTYPE html>
<html lang="fr">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Paiement confirmé</title>
<style>
body{font-family:sans-serif;display:flex;flex-direction:column;align-items:center;
justify-content:center;min-height:100vh;margin:0;background:#f0fdf4;}
.card{background:#fff;border-radius:16px;padding:2rem 2.5rem;text-align:center;
box-shadow:0 4px 24px #0001;max-width:360px;}
.icon{font-size:3.5rem;margin-bottom:1rem;}
h2{color:#16a34a;margin:.5rem 0;}
p{color:#555;margin:.5rem 0;}
</style>
</head>
<body>
<div class="card">
<div class="icon">✅</div>
<h2>Paiement confirmé !</h2>
<p>Votre cotisation a été enregistrée avec succès.</p>
<p style="font-size:.85rem;color:#888;margin-top:1rem;">
Vous pouvez fermer cette page et revenir sur UnionFlow.
</p>
</div>
</body>
</html>
""";
return Response.ok(html).build();
}
@GET
@Path("/error")
public Response error(@QueryParam("ref") String ref) {
LOG.infof("Wave redirect error, ref=%s", ref);
String location = buildDeepLink("error", ref);
return Response.seeOther(URI.create(location)).build();
}
/**
* Test uniquement (wave.mock.enabled=true) : simule la validation Wave puis redirige.
* Appelle la même logique que /success en mock (applyMockCompletion).
*/
@GET
@Path("/mock-complete")
@Transactional
public Response mockComplete(@QueryParam("ref") String ref) {
if (!mockEnabled) {
LOG.warn("mock-complete ignoré (wave.mock.enabled=false)");
return Response.seeOther(URI.create(buildDeepLink("error", ref))).build();
}
if (ref == null || ref.isBlank()) {
return Response.status(Response.Status.BAD_REQUEST).entity("ref requis").build();
}
applyCompletion(ref);
return Response.seeOther(URI.create(buildDeepLink("success", ref))).build();
}
/**
* Marque l'intention comme complétée et réconcilie les cotisations/dépôts liés.
* Délègue au PaiementService pour les cotisations ; gère les dépôts épargne localement.
*/
private void applyCompletion(String ref) {
try {
UUID intentionId = UUID.fromString(ref.trim());
IntentionPaiement intention = intentionPaiementRepository.findById(intentionId);
if (intention == null) {
LOG.warnf("Intention non trouvée: %s", ref);
return;
}
// Gérer les dépôts épargne (non couverts par PaiementService)
String objetsCibles = intention.getObjetsCibles();
if (objetsCibles != null && !objetsCibles.isBlank()) {
JsonNode arr = OBJECT_MAPPER.readTree(objetsCibles);
if (arr.isArray()) {
for (JsonNode node : arr) {
if ("DEPOT_EPARGNE".equals(node.path("type").asText())
&& node.has("compteId") && node.has("montant")) {
String compteId = node.get("compteId").asText();
BigDecimal montant = new BigDecimal(node.get("montant").asText());
TransactionEpargneRequest req = TransactionEpargneRequest.builder()
.compteId(compteId)
.typeTransaction(TypeTransactionEpargne.DEPOT)
.montant(montant)
.motif("Dépôt via Wave (mobile money)")
.build();
transactionEpargneService.executerTransaction(req);
LOG.infof("Wave: dépôt épargne %s XOF sur compte %s", montant, compteId);
}
}
}
}
// Déléguer la confirmation cotisations au service
versementService.confirmerVersementWave(intention, null);
LOG.infof("Wave: intention %s complétée", ref);
} catch (Exception e) {
LOG.errorf(e, "Wave: erreur applyCompletion ref=%s", ref);
}
}
private String buildDeepLink(String result, String ref) {
StringBuilder sb = new StringBuilder();
sb.append(deepLinkScheme).append("://payment?result=").append(result);
if (ref != null && !ref.isBlank()) {
sb.append("&ref=").append(ref);
}
return sb.toString();
}
}

View File

@@ -11,6 +11,7 @@ import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import io.quarkus.security.identity.SecurityIdentity;
import java.util.List;
import java.util.UUID;
@@ -24,10 +25,16 @@ public class TransactionEpargneResource {
@Inject
TransactionEpargneService transactionEpargneService;
@Inject
SecurityIdentity securityIdentity;
@POST
@RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MUTUELLE_RESP", "MEMBRE", "USER" })
public Response executerTransaction(@Valid TransactionEpargneRequest request) {
TransactionEpargneResponse transaction = transactionEpargneService.executerTransaction(request);
public Response executerTransaction(
@Valid TransactionEpargneRequest request,
@QueryParam("historique") @DefaultValue("false") boolean historique) {
boolean bypassSolde = historique && securityIdentity.hasRole("SUPER_ADMIN");
TransactionEpargneResponse transaction = transactionEpargneService.executerTransaction(request, bypassSolde);
return Response.status(Response.Status.CREATED).entity(transaction).build();
}

View File

@@ -1,61 +1,61 @@
package dev.lions.unionflow.server.resource.tontine;
import dev.lions.unionflow.server.api.dto.tontine.TontineRequest;
import dev.lions.unionflow.server.api.dto.tontine.TontineResponse;
import dev.lions.unionflow.server.api.enums.tontine.StatutTontine;
import dev.lions.unionflow.server.service.tontine.TontineService;
import dev.lions.unionflow.server.security.RequiresModule;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.List;
import java.util.UUID;
@Path("/api/v1/tontines")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RequiresModule("TONTINE")
public class TontineResource {
@Inject
TontineService tontineService;
@POST
@RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "TONTINE_RESP" })
public Response creerTontine(@Valid TontineRequest request) {
TontineResponse response = tontineService.creerTontine(request);
return Response.status(Response.Status.CREATED).entity(response).build();
}
@GET
@Path("/{id}")
@RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "TONTINE_RESP", "MEMBRE", "USER" })
public Response getTontineById(@PathParam("id") UUID id) {
TontineResponse response = tontineService.getTontineById(id);
return Response.ok(response).build();
}
@GET
@Path("/organisation/{organisationId}")
@RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "TONTINE_RESP" })
public Response getTontinesByOrganisation(@PathParam("organisationId") UUID organisationId) {
List<TontineResponse> response = tontineService.getTontinesByOrganisation(organisationId);
return Response.ok(response).build();
}
@PATCH
@Path("/{id}/statut")
@RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "TONTINE_RESP" })
public Response changerStatut(@PathParam("id") UUID id, @QueryParam("statut") StatutTontine statut) {
if (statut == null) {
return Response.status(Response.Status.BAD_REQUEST).entity("Le statut est requis").build();
}
TontineResponse response = tontineService.changerStatut(id, statut);
return Response.ok(response).build();
}
}
package dev.lions.unionflow.server.resource.tontine;
import dev.lions.unionflow.server.api.dto.tontine.TontineRequest;
import dev.lions.unionflow.server.api.dto.tontine.TontineResponse;
import dev.lions.unionflow.server.api.enums.tontine.StatutTontine;
import dev.lions.unionflow.server.service.tontine.TontineService;
import dev.lions.unionflow.server.security.RequiresModule;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.List;
import java.util.UUID;
@Path("/api/v1/tontines")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RequiresModule("TONTINE")
public class TontineResource {
@Inject
TontineService tontineService;
@POST
@RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "TONTINE_RESP" })
public Response creerTontine(@Valid TontineRequest request) {
TontineResponse response = tontineService.creerTontine(request);
return Response.status(Response.Status.CREATED).entity(response).build();
}
@GET
@Path("/{id}")
@RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "TONTINE_RESP", "MEMBRE", "USER" })
public Response getTontineById(@PathParam("id") UUID id) {
TontineResponse response = tontineService.getTontineById(id);
return Response.ok(response).build();
}
@GET
@Path("/organisation/{organisationId}")
@RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "TONTINE_RESP" })
public Response getTontinesByOrganisation(@PathParam("organisationId") UUID organisationId) {
List<TontineResponse> response = tontineService.getTontinesByOrganisation(organisationId);
return Response.ok(response).build();
}
@PATCH
@Path("/{id}/statut")
@RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "TONTINE_RESP" })
public Response changerStatut(@PathParam("id") UUID id, @QueryParam("statut") StatutTontine statut) {
if (statut == null) {
return Response.status(Response.Status.BAD_REQUEST).entity("Le statut est requis").build();
}
TontineResponse response = tontineService.changerStatut(id, statut);
return Response.ok(response).build();
}
}