fix(security): audit RBAC complet v3.0 — rôles normalisés, lifecycle, changement mdp mobile
RBAC:
- HealthResource: @PermitAll
- RoleResource: @RolesAllowed ADMIN/SUPER_ADMIN/ADMIN_ORGANISATION class-level
- PropositionAideResource: @RolesAllowed MEMBRE/USER class-level
- AuthCallbackResource: @PermitAll
- EvenementResource: @PermitAll /publics et /test, count restreint
- BackupResource/LogsMonitoringResource/SystemResource: MODERATOR → MODERATEUR
- AnalyticsResource: MANAGER/MEMBER → ADMIN_ORGANISATION/MEMBRE
- RoleConstant.java: constantes de rôles centralisées
Cycle de vie membres:
- MemberLifecycleService: ajouterMembre()/retirerMembre() sur activation/radiation/archivage
- MembreResource: endpoint GET /numero/{numeroMembre}
- MembreService: méthode trouverParNumeroMembre()
Changement mot de passe:
- CompteAdherentResource: endpoint POST /auth/change-password (mobile)
- MembreKeycloakSyncService: changerMotDePasseDirectKeycloak() via API Admin Keycloak directe
- Fallback automatique si lions-user-manager indisponible
Workflow:
- Flyway V17-V23: rôles, types org, formules Option C, lifecycle columns, bareme cotisation
- Nouvelles classes: MemberLifecycleService, OrganisationModuleService, scheduler
- Security: OrganisationContextFilter, OrganisationContextHolder, ModuleAccessFilter
This commit is contained in:
@@ -50,7 +50,7 @@ public class AnalyticsResource {
|
||||
/** Calcule une métrique analytics pour une période donnée */
|
||||
@GET
|
||||
@Path("/metriques/{typeMetrique}")
|
||||
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
|
||||
@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")
|
||||
@@ -88,7 +88,7 @@ public class AnalyticsResource {
|
||||
/** Calcule les tendances d'un KPI sur une période */
|
||||
@GET
|
||||
@Path("/tendances/{typeMetrique}")
|
||||
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
|
||||
@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")
|
||||
@@ -127,7 +127,7 @@ public class AnalyticsResource {
|
||||
/** Obtient tous les KPI pour une organisation */
|
||||
@GET
|
||||
@Path("/kpis")
|
||||
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
|
||||
@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")
|
||||
@@ -163,7 +163,7 @@ public class AnalyticsResource {
|
||||
/** Calcule le KPI de performance globale */
|
||||
@GET
|
||||
@Path("/performance-globale")
|
||||
@RolesAllowed({"ADMIN", "MANAGER"})
|
||||
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "SUPER_ADMIN"})
|
||||
@Operation(
|
||||
summary = "Calculer la performance globale",
|
||||
description = "Calcule le score de performance globale de l'organisation")
|
||||
@@ -208,7 +208,7 @@ public class AnalyticsResource {
|
||||
/** Obtient les évolutions des KPI par rapport à la période précédente */
|
||||
@GET
|
||||
@Path("/evolutions")
|
||||
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
|
||||
@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")
|
||||
@@ -248,7 +248,7 @@ public class AnalyticsResource {
|
||||
/** Obtient les widgets du tableau de bord pour un utilisateur */
|
||||
@GET
|
||||
@Path("/dashboard/widgets")
|
||||
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
|
||||
@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")
|
||||
@@ -285,7 +285,7 @@ public class AnalyticsResource {
|
||||
/** Obtient les types de métriques disponibles */
|
||||
@GET
|
||||
@Path("/types-metriques")
|
||||
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
|
||||
@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")
|
||||
@@ -300,7 +300,7 @@ public class AnalyticsResource {
|
||||
/** Obtient les périodes d'analyse disponibles */
|
||||
@GET
|
||||
@Path("/periodes-analyse")
|
||||
@RolesAllowed({"ADMIN", "MANAGER", "MEMBER"})
|
||||
@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")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.service.ApprovalService;
|
||||
import dev.lions.unionflow.server.api.dto.common.ErrorResponse;
|
||||
import dev.lions.unionflow.server.api.dto.finance_workflow.request.ApproveTransactionRequest;
|
||||
import dev.lions.unionflow.server.api.dto.finance_workflow.request.RejectTransactionRequest;
|
||||
import dev.lions.unionflow.server.api.dto.finance_workflow.response.TransactionApprovalResponse;
|
||||
@@ -53,7 +54,7 @@ public class ApprovalResource {
|
||||
|
||||
if (transactionId == null || transactionType == null || amount == null) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("transactionId, transactionType et amount sont requis"))
|
||||
.entity(ErrorResponse.of("transactionId, transactionType et amount sont requis"))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -64,12 +65,12 @@ public class ApprovalResource {
|
||||
return Response.status(Response.Status.CREATED).entity(approval).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Erreur lors de la création de la demande d'approbation", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -88,7 +89,7 @@ public class ApprovalResource {
|
||||
} catch (Exception e) {
|
||||
LOG.error("Erreur lors de la récupération des approbations en attente", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -106,12 +107,12 @@ public class ApprovalResource {
|
||||
return Response.ok(approval).build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Erreur lors de la récupération de l'approbation", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -131,16 +132,16 @@ public class ApprovalResource {
|
||||
return Response.ok(approval).build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
} catch (ForbiddenException e) {
|
||||
return Response.status(Response.Status.FORBIDDEN)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Erreur lors de l'approbation de la transaction", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -160,16 +161,16 @@ public class ApprovalResource {
|
||||
return Response.ok(approval).build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
} catch (ForbiddenException e) {
|
||||
return Response.status(Response.Status.FORBIDDEN)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Erreur lors du rejet de la transaction", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -197,12 +198,12 @@ public class ApprovalResource {
|
||||
return Response.ok(approvals).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Erreur lors de la récupération de l'historique", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -221,12 +222,11 @@ public class ApprovalResource {
|
||||
} catch (Exception e) {
|
||||
LOG.error("Erreur lors du comptage des approbations", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// Classes internes pour les réponses
|
||||
record ErrorResponse(String message) {}
|
||||
// Classe interne pour le comptage
|
||||
record CountResponse(long count) {}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public class BackupResource {
|
||||
* Lister toutes les sauvegardes
|
||||
*/
|
||||
@GET
|
||||
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATOR"})
|
||||
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
|
||||
@Operation(summary = "Lister toutes les sauvegardes disponibles")
|
||||
public List<BackupResponse> getAllBackups() {
|
||||
log.info("GET /api/backups");
|
||||
@@ -48,7 +48,7 @@ public class BackupResource {
|
||||
*/
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATOR"})
|
||||
@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);
|
||||
@@ -98,7 +98,7 @@ public class BackupResource {
|
||||
*/
|
||||
@GET
|
||||
@Path("/config")
|
||||
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATOR"})
|
||||
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
|
||||
@Operation(summary = "Récupérer la configuration des sauvegardes automatiques")
|
||||
public BackupConfigResponse getBackupConfig() {
|
||||
log.info("GET /api/backups/config");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.service.BudgetService;
|
||||
import dev.lions.unionflow.server.api.dto.common.ErrorResponse;
|
||||
import dev.lions.unionflow.server.api.dto.finance_workflow.request.CreateBudgetRequest;
|
||||
import dev.lions.unionflow.server.api.dto.finance_workflow.response.BudgetResponse;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
@@ -51,12 +52,12 @@ public class BudgetResource {
|
||||
return Response.ok(budgets).build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Erreur lors de la récupération des budgets", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -74,12 +75,12 @@ public class BudgetResource {
|
||||
return Response.ok(budget).build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Erreur lors de la récupération du budget", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -98,16 +99,16 @@ public class BudgetResource {
|
||||
.build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Erreur lors de la création du budget", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -125,12 +126,12 @@ public class BudgetResource {
|
||||
return Response.ok(tracking).build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Erreur lors de la récupération du suivi budgétaire", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -150,16 +151,16 @@ public class BudgetResource {
|
||||
return Response.ok(budget).build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
} catch (BadRequestException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Erreur lors de la mise à jour du budget", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -177,16 +178,15 @@ public class BudgetResource {
|
||||
return Response.noContent().build();
|
||||
} catch (NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Erreur lors de la suppression du budget", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.of(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// Classe interne pour les réponses d'erreur
|
||||
record ErrorResponse(String message) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.comptabilite.request.*;
|
||||
import dev.lions.unionflow.server.api.dto.comptabilite.response.*;
|
||||
import dev.lions.unionflow.server.api.dto.common.ErrorResponse;
|
||||
import dev.lions.unionflow.server.service.ComptabiliteService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -52,12 +53,12 @@ public class ComptabiliteResource {
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.ofError(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création du compte comptable");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création du compte comptable: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la création du compte comptable: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -76,12 +77,12 @@ public class ComptabiliteResource {
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Compte comptable non trouvé"))
|
||||
.entity(ErrorResponse.ofError("Compte comptable non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche du compte comptable");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche du compte comptable: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la recherche du compte comptable: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -100,7 +101,7 @@ public class ComptabiliteResource {
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des comptes comptables");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la liste des comptes comptables: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la liste des comptes comptables: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -124,12 +125,12 @@ public class ComptabiliteResource {
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.ofError(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création du journal comptable");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création du journal comptable: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la création du journal comptable: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -148,12 +149,12 @@ public class ComptabiliteResource {
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Journal comptable non trouvé"))
|
||||
.entity(ErrorResponse.ofError("Journal comptable non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche du journal comptable");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche du journal comptable: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la recherche du journal comptable: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -172,7 +173,7 @@ public class ComptabiliteResource {
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des journaux comptables");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la liste des journaux comptables: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la liste des journaux comptables: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -196,12 +197,12 @@ public class ComptabiliteResource {
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.ofError(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création de l'écriture comptable");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création de l'écriture comptable: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la création de l'écriture comptable: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -220,12 +221,12 @@ public class ComptabiliteResource {
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Écriture comptable non trouvée"))
|
||||
.entity(ErrorResponse.ofError("Écriture comptable non trouvée"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche de l'écriture comptable");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche de l'écriture comptable: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la recherche de l'écriture comptable: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -245,7 +246,7 @@ public class ComptabiliteResource {
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des écritures");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la liste des écritures: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la liste des écritures: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -265,17 +266,10 @@ public class ComptabiliteResource {
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des écritures");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la liste des écritures: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la liste des écritures: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Classe interne pour les réponses d'erreur */
|
||||
public static class ErrorResponse {
|
||||
public String error;
|
||||
|
||||
public ErrorResponse(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -247,4 +247,55 @@ public class CompteAdherentResource {
|
||||
membreKeycloakSyncService.changerMotDePassePremierLogin(membreOpt.get().getId(), nouveauMotDePasse);
|
||||
return Response.ok(Map.of("message", "Mot de passe mis à jour avec succès.")).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint mobile : changement de mot de passe depuis l'app Flutter.
|
||||
* Bypass lions-user-manager — appel direct à l'API Admin Keycloak.
|
||||
*
|
||||
* <p>Body attendu : {@code { "userId": "...", "oldPassword": "...", "newPassword": "..." }}
|
||||
*/
|
||||
@POST
|
||||
@Path("/auth/change-password")
|
||||
@Authenticated
|
||||
@Operation(
|
||||
summary = "Changer le mot de passe (mobile)",
|
||||
description = "Endpoint dédié à l'application mobile. Bypass lions-user-manager via API Admin Keycloak directe."
|
||||
)
|
||||
public Response changerMotDePasseMobile(Map<String, String> body) {
|
||||
String email = securiteHelper.resolveEmail();
|
||||
if (email == null || email.isBlank()) {
|
||||
return Response.status(Response.Status.UNAUTHORIZED).build();
|
||||
}
|
||||
|
||||
String newPassword = body == null ? null : body.get("newPassword");
|
||||
if (newPassword == null || newPassword.isBlank()) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("message", "Le champ 'newPassword' est requis."))
|
||||
.build();
|
||||
}
|
||||
if (newPassword.length() < 8) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("message", "Le mot de passe doit contenir au moins 8 caractères."))
|
||||
.build();
|
||||
}
|
||||
|
||||
Optional<Membre> membreOpt = membreRepository.findByEmail(email.trim())
|
||||
.or(() -> membreRepository.findByEmail(email.trim().toLowerCase()));
|
||||
|
||||
if (membreOpt.isEmpty()) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(Map.of("message", "Aucun membre trouvé pour ce compte."))
|
||||
.build();
|
||||
}
|
||||
|
||||
try {
|
||||
membreKeycloakSyncService.changerMotDePasseDirectKeycloak(membreOpt.get().getId(), newPassword);
|
||||
return Response.ok(Map.of("message", "Mot de passe mis à jour avec succès.")).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur changement mot de passe pour %s", email);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("message", "Erreur lors du changement de mot de passe: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,19 +60,19 @@ public class CotisationResource {
|
||||
try {
|
||||
log.info("GET /api/cotisations/public - page: {}, size: {}", page, size);
|
||||
|
||||
List<CotisationSummaryResponse> cotisations = cotisationService.getAllCotisations(page, size);
|
||||
List<CotisationResponse> cotisations = cotisationService.getAllCotisations(page, size);
|
||||
|
||||
List<Map<String, Object>> content = cotisations.stream()
|
||||
.map(c -> {
|
||||
Map<String, Object> map = new java.util.HashMap<>();
|
||||
map.put("id", c.id().toString());
|
||||
map.put("reference", c.numeroReference());
|
||||
map.put("nomMembre", c.nomMembre());
|
||||
map.put("montantDu", c.montantDu());
|
||||
map.put("montantPaye", c.montantPaye());
|
||||
map.put("statut", c.statut());
|
||||
map.put("statutLibelle", c.statutLibelle());
|
||||
map.put("dateEcheance", c.dateEcheance().toString());
|
||||
map.put("id", c.getId() != null ? c.getId().toString() : null);
|
||||
map.put("reference", c.getNumeroReference());
|
||||
map.put("nomMembre", c.getNomMembre());
|
||||
map.put("montantDu", c.getMontantDu());
|
||||
map.put("montantPaye", c.getMontantPaye());
|
||||
map.put("statut", c.getStatut());
|
||||
map.put("statutLibelle", c.getStatutLibelle());
|
||||
map.put("dateEcheance", c.getDateEcheance() != null ? c.getDateEcheance().toString() : null);
|
||||
return map;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
@@ -113,7 +113,7 @@ public class CotisationResource {
|
||||
|
||||
try {
|
||||
log.info("GET /api/cotisations - page: {}, size: {}", page, size);
|
||||
List<CotisationSummaryResponse> cotisations = cotisationService.getAllCotisations(page, size);
|
||||
List<CotisationResponse> cotisations = cotisationService.getAllCotisations(page, size);
|
||||
return Response.ok(cotisations).build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lister cotisations", e);
|
||||
@@ -233,7 +233,7 @@ public class CotisationResource {
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size) {
|
||||
try {
|
||||
List<CotisationSummaryResponse> results = cotisationService.getCotisationsByMembre(membreId, page, size);
|
||||
List<CotisationResponse> results = cotisationService.getCotisationsByMembre(membreId, page, size);
|
||||
return Response.ok(results).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(Map.of("error", e.getMessage())).build();
|
||||
@@ -251,7 +251,7 @@ public class CotisationResource {
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size) {
|
||||
try {
|
||||
List<CotisationSummaryResponse> results = cotisationService.getCotisationsByStatut(statut, page, size);
|
||||
List<CotisationResponse> results = cotisationService.getCotisationsByStatut(statut, page, size);
|
||||
return Response.ok(results).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(Map.of("error", e.getMessage())).build();
|
||||
@@ -268,7 +268,7 @@ public class CotisationResource {
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size) {
|
||||
try {
|
||||
List<CotisationSummaryResponse> results = cotisationService.getCotisationsEnRetard(page, size);
|
||||
List<CotisationResponse> results = cotisationService.getCotisationsEnRetard(page, size);
|
||||
return Response.ok(results).build();
|
||||
} catch (Exception e) {
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(Map.of("error", e.getMessage())).build();
|
||||
@@ -290,7 +290,7 @@ public class CotisationResource {
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size) {
|
||||
try {
|
||||
List<CotisationSummaryResponse> results = cotisationService.rechercherCotisations(
|
||||
List<CotisationResponse> results = cotisationService.rechercherCotisations(
|
||||
membreId, statut, typeCotisation, annee, mois, page, size);
|
||||
return Response.ok(results).build();
|
||||
} catch (Exception e) {
|
||||
@@ -399,7 +399,7 @@ public class CotisationResource {
|
||||
@QueryParam("size") @DefaultValue("50") int size) {
|
||||
try {
|
||||
log.info("GET /api/cotisations/mes-cotisations");
|
||||
List<CotisationSummaryResponse> results = cotisationService.getMesCotisations(page, size);
|
||||
List<CotisationResponse> results = cotisationService.getMesCotisations(page, size);
|
||||
return Response.ok(results).build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur récupération mes cotisations", e);
|
||||
@@ -423,7 +423,7 @@ public class CotisationResource {
|
||||
public Response getMesCotisationsEnAttente() {
|
||||
try {
|
||||
log.info("GET /api/cotisations/mes-cotisations/en-attente");
|
||||
List<CotisationSummaryResponse> results = cotisationService.getMesCotisationsEnAttente();
|
||||
List<CotisationResponse> results = cotisationService.getMesCotisationsEnAttente();
|
||||
return Response.ok(results).build();
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur récupération mes cotisations en attente", e);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.common.ErrorResponse;
|
||||
import dev.lions.unionflow.server.api.dto.document.request.CreateDocumentRequest;
|
||||
import dev.lions.unionflow.server.api.dto.document.response.DocumentResponse;
|
||||
import dev.lions.unionflow.server.api.dto.document.request.CreatePieceJointeRequest;
|
||||
@@ -62,7 +63,7 @@ public class DocumentResource {
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création du document");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création du document: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la création du document: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -87,7 +88,7 @@ public class DocumentResource {
|
||||
try {
|
||||
if (file == null || file.fileName() == null) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Aucun fichier fourni"))
|
||||
.entity(ErrorResponse.ofError("Aucun fichier fourni"))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -135,12 +136,31 @@ public class DocumentResource {
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warnf("Validation échouée pour upload: %s", e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.ofError(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'upload du fichier");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(new ErrorResponse("Erreur lors de l'upload: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de l'upload: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste les documents de l'utilisateur connecté
|
||||
*
|
||||
* @return Liste des documents du membre connecté
|
||||
*/
|
||||
@GET
|
||||
@Path("/mes-documents")
|
||||
public Response listerMesDocuments() {
|
||||
try {
|
||||
List<DocumentResponse> result = documentService.listerMesDocuments();
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des documents du membre connecté");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la récupération des documents: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -159,12 +179,12 @@ public class DocumentResource {
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Document non trouvé"))
|
||||
.entity(ErrorResponse.ofError("Document non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche du document");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche du document: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la recherche du document: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -184,14 +204,12 @@ public class DocumentResource {
|
||||
return Response.ok().build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Document non trouvé"))
|
||||
.entity(ErrorResponse.ofError("Document non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'enregistrement du téléchargement");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(
|
||||
new ErrorResponse(
|
||||
"Erreur lors de l'enregistrement du téléchargement: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de l'enregistrement du téléchargement: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -211,12 +229,12 @@ public class DocumentResource {
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.ofError(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création de la pièce jointe");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création de la pièce jointe: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la création de la pièce jointe: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -236,17 +254,10 @@ public class DocumentResource {
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des pièces jointes");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la liste des pièces jointes: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la liste des pièces jointes: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Classe interne pour les réponses d'erreur */
|
||||
public static class ErrorResponse {
|
||||
public String error;
|
||||
|
||||
public ErrorResponse(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import dev.lions.unionflow.server.entity.InscriptionEvenement;
|
||||
import dev.lions.unionflow.server.service.EvenementService;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import io.quarkus.security.PermitAll;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
@@ -53,6 +54,7 @@ public class EvenementResource {
|
||||
/** Endpoint de test public pour vérifier la connectivité */
|
||||
@GET
|
||||
@Path("/test")
|
||||
@PermitAll
|
||||
@Operation(summary = "Test de connectivité", description = "Endpoint public pour tester la connectivité")
|
||||
@APIResponse(responseCode = "200", description = "Test réussi")
|
||||
public Response testConnectivity() {
|
||||
@@ -69,6 +71,7 @@ public class EvenementResource {
|
||||
/** Endpoint de debug pour vérifier le chargement des données */
|
||||
@GET
|
||||
@Path("/count")
|
||||
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "SUPER_ADMIN" })
|
||||
@Operation(summary = "Compter les événements", description = "Compte le nombre d'événements dans la base")
|
||||
@APIResponse(responseCode = "200", description = "Nombre d'événements")
|
||||
public Response countEvenements() {
|
||||
@@ -194,6 +197,7 @@ public class EvenementResource {
|
||||
/** Liste les événements publics */
|
||||
@GET
|
||||
@Path("/publics")
|
||||
@PermitAll
|
||||
@Operation(summary = "Événements publics")
|
||||
public Response evenementsPublics(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@@ -379,6 +383,7 @@ public class EvenementResource {
|
||||
/** Liste des feedbacks d'un événement */
|
||||
@GET
|
||||
@Path("/{id}/feedbacks")
|
||||
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE", "USER" })
|
||||
@Operation(summary = "Liste des feedbacks de l'événement")
|
||||
@APIResponse(responseCode = "200", description = "Liste des feedbacks")
|
||||
public Response getFeedbacks(@PathParam("id") UUID evenementId) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import io.quarkus.security.PermitAll;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
@@ -15,6 +16,7 @@ import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
@Path("/api/status")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@ApplicationScoped
|
||||
@PermitAll
|
||||
@Tag(name = "Status", description = "API de statut du serveur")
|
||||
public class HealthResource {
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ public class LogsMonitoringResource {
|
||||
*/
|
||||
@POST
|
||||
@Path("/logs/search")
|
||||
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATOR"})
|
||||
@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());
|
||||
@@ -90,7 +90,7 @@ public class LogsMonitoringResource {
|
||||
*/
|
||||
@GET
|
||||
@Path("/monitoring/metrics")
|
||||
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATOR", "HR_MANAGER", "ACTIVE_MEMBER"})
|
||||
@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");
|
||||
@@ -102,7 +102,7 @@ public class LogsMonitoringResource {
|
||||
*/
|
||||
@GET
|
||||
@Path("/alerts")
|
||||
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATOR"})
|
||||
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
|
||||
@Operation(summary = "Récupérer toutes les alertes actives")
|
||||
public List<SystemAlertResponse> getActiveAlerts() {
|
||||
log.info("GET /api/alerts");
|
||||
@@ -114,7 +114,7 @@ public class LogsMonitoringResource {
|
||||
*/
|
||||
@POST
|
||||
@Path("/alerts/{id}/acknowledge")
|
||||
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATOR"})
|
||||
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATEUR"})
|
||||
@Operation(summary = "Acquitter une alerte")
|
||||
public Response acknowledgeAlert(@PathParam("id") UUID id) {
|
||||
log.info("POST /api/alerts/{}/acknowledge", id);
|
||||
@@ -127,7 +127,7 @@ public class LogsMonitoringResource {
|
||||
*/
|
||||
@GET
|
||||
@Path("/alerts/config")
|
||||
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATOR"})
|
||||
@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");
|
||||
|
||||
@@ -421,6 +421,55 @@ public class MembreResource {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste tous les membres actifs (statut compte = ACTIF).
|
||||
* Utilisé notamment pour la création de campagnes de cotisations.
|
||||
*/
|
||||
@GET
|
||||
@Path("/actifs")
|
||||
@RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "TRESORIER" })
|
||||
@Operation(summary = "Membres actifs", description = "Liste tous les membres dont le compte est actif")
|
||||
@APIResponse(responseCode = "200", description = "Liste des membres actifs")
|
||||
public Response getMembresActifs() {
|
||||
try {
|
||||
LOG.info("GET /api/membres/actifs");
|
||||
List<Membre> membres = membreService.listerMembresActifs();
|
||||
List<MembreResponse> membresDTO = membreService.convertToResponseList(membres);
|
||||
return Response.ok(membresDTO).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur récupération membres actifs");
|
||||
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.
|
||||
*/
|
||||
@GET
|
||||
@Path("/organisation/{organisationId}")
|
||||
@RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "TRESORIER" })
|
||||
@Operation(summary = "Membres d'une organisation", description = "Liste les membres actifs d'une organisation")
|
||||
@APIResponse(responseCode = "200", description = "Liste des membres")
|
||||
public Response getMembresParOrganisation(
|
||||
@Parameter(description = "UUID de l'organisation") @PathParam("organisationId") UUID organisationId) {
|
||||
try {
|
||||
LOG.infof("GET /api/membres/organisation/%s", organisationId);
|
||||
List<dev.lions.unionflow.server.entity.MembreOrganisation> liens =
|
||||
membreOrgRepository.findMembresActifsParOrganisation(organisationId);
|
||||
List<MembreResponse> membresDTO = liens.stream()
|
||||
.filter(mo -> mo.getMembre() != null)
|
||||
.map(mo -> membreService.convertToResponse(mo.getMembre()))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
return Response.ok(membresDTO).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur récupération membres organisation %s", organisationId);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", e.getMessage())).build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/recherche")
|
||||
@Operation(summary = "Rechercher des membres par nom ou prénom")
|
||||
@@ -1248,6 +1297,23 @@ public class MembreResource {
|
||||
return Response.ok(Map.of("statut", updated.getStatutMembre())).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un membre par son numéro de membre (ex: MBR-0001).
|
||||
* Utilisé notamment pour la recherche de parrain lors de l'inscription.
|
||||
*/
|
||||
@GET
|
||||
@Path("/numero/{numeroMembre}")
|
||||
@RolesAllowed({ "ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MODERATEUR", "MEMBRE", "USER" })
|
||||
@Operation(summary = "Trouver un membre par son numéro")
|
||||
@APIResponse(responseCode = "200", description = "Membre trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Membre non trouvé")
|
||||
public Response obtenirParNumero(@PathParam("numeroMembre") String numeroMembre) {
|
||||
LOG.infof("GET /api/membres/numero/%s", numeroMembre);
|
||||
Membre membre = membreService.trouverParNumeroMembre(numeroMembre)
|
||||
.orElseThrow(() -> new NotFoundException("Membre non trouvé avec le numéro: " + numeroMembre));
|
||||
return Response.ok(membreService.convertToResponse(membre)).build();
|
||||
}
|
||||
|
||||
/** Résout l'UUID de l'admin connecté depuis le JWT subject. */
|
||||
private UUID resolveCurrentAdminId() {
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.common.ErrorResponse;
|
||||
import dev.lions.unionflow.server.api.dto.notification.request.CreateNotificationRequest;
|
||||
import dev.lions.unionflow.server.api.dto.notification.request.CreateTemplateNotificationRequest;
|
||||
import dev.lions.unionflow.server.api.dto.notification.response.NotificationResponse;
|
||||
@@ -61,7 +62,7 @@ public class NotificationResource {
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur liste notifications membre connecté");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -83,7 +84,7 @@ public class NotificationResource {
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur liste notifications non lues");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -107,12 +108,12 @@ public class NotificationResource {
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.ofError(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création du template");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création du template: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la création du template: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -136,7 +137,7 @@ public class NotificationResource {
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création de la notification");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création de la notification: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la création de la notification: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -156,12 +157,12 @@ public class NotificationResource {
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Notification non trouvée"))
|
||||
.entity(ErrorResponse.ofError("Notification non trouvée"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors du marquage de la notification");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors du marquage de la notification: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors du marquage de la notification: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -180,12 +181,12 @@ public class NotificationResource {
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Notification non trouvée"))
|
||||
.entity(ErrorResponse.ofError("Notification non trouvée"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche de la notification");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche de la notification: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la recherche de la notification: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -205,7 +206,7 @@ public class NotificationResource {
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des notifications");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la liste des notifications: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la liste des notifications: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -225,9 +226,7 @@ public class NotificationResource {
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des notifications non lues");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(
|
||||
new ErrorResponse(
|
||||
"Erreur lors de la liste des notifications non lues: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la liste des notifications non lues: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -246,9 +245,7 @@ public class NotificationResource {
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des notifications en attente");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(
|
||||
new ErrorResponse(
|
||||
"Erreur lors de la liste des notifications en attente: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la liste des notifications en attente: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -269,27 +266,16 @@ public class NotificationResource {
|
||||
return Response.ok(Map.of("notificationsCreees", notificationsCreees)).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.ofError(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'envoi des notifications groupées");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(
|
||||
new ErrorResponse(
|
||||
"Erreur lors de l'envoi des notifications groupées: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de l'envoi des notifications groupées: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Classe interne pour les réponses d'erreur */
|
||||
public static class ErrorResponse {
|
||||
public String error;
|
||||
|
||||
public ErrorResponse(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
/** Classe interne pour les requêtes de notifications groupées (WOU/DRY) */
|
||||
public static class NotificationGroupeeRequest {
|
||||
public List<UUID> membreIds;
|
||||
|
||||
@@ -170,6 +170,24 @@ public class PaiementResource {
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Polling du statut d'une IntentionPaiement Wave.
|
||||
* Si Wave a confirmé le paiement, réconcilie automatiquement la cotisation (PAYEE) et retourne COMPLETEE.
|
||||
* Le client web appelle cet endpoint toutes les 3 secondes pendant l'affichage du QR code.
|
||||
*
|
||||
* @param intentionId UUID de l'intention (clientReference retourné par initier-paiement-en-ligne)
|
||||
* @return Statut courant + waveLaunchUrl (pour re-générer le QR si besoin) + message
|
||||
*/
|
||||
@GET
|
||||
@Path("/statut-intention/{intentionId}")
|
||||
@RolesAllowed({ "MEMBRE", "MEMBRE_ACTIF", "ADMIN", "ADMIN_ORGANISATION", "USER" })
|
||||
public Response getStatutIntention(@PathParam("intentionId") java.util.UUID intentionId) {
|
||||
LOG.infof("GET /api/paiements/statut-intention/%s", intentionId);
|
||||
dev.lions.unionflow.server.api.dto.paiement.response.IntentionStatutResponse result =
|
||||
paiementService.verifierStatutIntention(intentionId);
|
||||
return Response.ok(result).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Déclare un paiement manuel (espèces, virement, chèque).
|
||||
* Le paiement est créé avec le statut EN_ATTENTE_VALIDATION.
|
||||
|
||||
@@ -4,6 +4,7 @@ import dev.lions.unionflow.server.api.dto.solidarite.request.CreatePropositionAi
|
||||
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.*;
|
||||
@@ -21,6 +22,7 @@ import java.util.List;
|
||||
@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 {
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ 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;
|
||||
@@ -19,6 +20,7 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
@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 {
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.DefaultValue;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
@@ -158,6 +159,36 @@ public class SouscriptionResource {
|
||||
|
||||
// ── SuperAdmin ────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Liste toutes les souscriptions (SuperAdmin dashboard), avec filtre optionnel par org.
|
||||
*/
|
||||
@GET
|
||||
@Path("/admin/toutes")
|
||||
@RolesAllowed({"SUPER_ADMIN"})
|
||||
public Response getSouscriptionsToutes(
|
||||
@QueryParam("organisationId") UUID organisationId,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("1000") int size) {
|
||||
LOG.debug("GET /api/souscriptions/admin/toutes");
|
||||
List<SouscriptionStatutResponse> liste = souscriptionService.listerToutes(organisationId, page, size);
|
||||
return Response.ok(liste).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la souscription active d'une organisation (SuperAdmin).
|
||||
*/
|
||||
@GET
|
||||
@Path("/admin/organisation/{organisationId}/active")
|
||||
@RolesAllowed({"SUPER_ADMIN"})
|
||||
public Response getActiveParOrganisation(@PathParam("organisationId") UUID organisationId) {
|
||||
LOG.debugf("GET /api/souscriptions/admin/organisation/%s/active", organisationId);
|
||||
SouscriptionStatutResponse resp = souscriptionService.obtenirActiveParOrganisation(organisationId);
|
||||
if (resp == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND).build();
|
||||
}
|
||||
return Response.ok(resp).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste les souscriptions en attente de validation SuperAdmin (statut PAIEMENT_CONFIRME).
|
||||
*/
|
||||
|
||||
@@ -38,7 +38,7 @@ public class SystemResource {
|
||||
*/
|
||||
@GET
|
||||
@Path("/config")
|
||||
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATOR"})
|
||||
@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");
|
||||
@@ -62,7 +62,7 @@ public class SystemResource {
|
||||
*/
|
||||
@GET
|
||||
@Path("/cache/stats")
|
||||
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATOR"})
|
||||
@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");
|
||||
@@ -111,7 +111,7 @@ public class SystemResource {
|
||||
*/
|
||||
@GET
|
||||
@Path("/metrics")
|
||||
@RolesAllowed({"SUPER_ADMIN", "ADMIN", "MODERATOR"})
|
||||
@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.)"
|
||||
|
||||
@@ -4,12 +4,13 @@ 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.api.enums.paiement.StatutIntentionPaiement;
|
||||
import dev.lions.unionflow.server.entity.Cotisation;
|
||||
import dev.lions.unionflow.server.entity.IntentionPaiement;
|
||||
import dev.lions.unionflow.server.repository.IntentionPaiementRepository;
|
||||
import dev.lions.unionflow.server.service.PaiementService;
|
||||
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;
|
||||
@@ -21,7 +22,6 @@ import org.jboss.logging.Logger;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URI;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -50,18 +50,65 @@ public class WaveRedirectResource {
|
||||
@Inject
|
||||
TransactionEpargneService transactionEpargneService;
|
||||
|
||||
@Inject
|
||||
PaiementService paiementService;
|
||||
|
||||
@GET
|
||||
@Path("/success")
|
||||
@Transactional
|
||||
public Response success(@QueryParam("ref") String ref) {
|
||||
LOG.infof("Wave redirect success, ref=%s", ref);
|
||||
if (mockEnabled && ref != null && !ref.isBlank()) {
|
||||
applyMockCompletion(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) {
|
||||
@@ -85,40 +132,31 @@ public class WaveRedirectResource {
|
||||
if (ref == null || ref.isBlank()) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity("ref requis").build();
|
||||
}
|
||||
applyMockCompletion(ref);
|
||||
applyCompletion(ref);
|
||||
return Response.seeOther(URI.create(buildDeepLink("success", ref))).build();
|
||||
}
|
||||
|
||||
/** En mode mock : marque l'intention COMPLETEE et les cotisations liées PAYEE (simulation Wave). */
|
||||
private void applyMockCompletion(String ref) {
|
||||
/**
|
||||
* 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 pour mock: %s", ref);
|
||||
LOG.warnf("Intention non trouvée: %s", ref);
|
||||
return;
|
||||
}
|
||||
intention.setStatut(StatutIntentionPaiement.COMPLETEE);
|
||||
intention.setDateCompletion(LocalDateTime.now());
|
||||
intentionPaiementRepository.persist(intention);
|
||||
|
||||
// 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 (node.has("type") && "COTISATION".equals(node.get("type").asText()) && node.has("id")) {
|
||||
UUID cotisationId = UUID.fromString(node.get("id").asText());
|
||||
Cotisation cotisation = intentionPaiementRepository.getEntityManager().find(Cotisation.class, cotisationId);
|
||||
if (cotisation != null) {
|
||||
BigDecimal montant = node.has("montant") ? new BigDecimal(node.get("montant").asText()) : cotisation.getMontantDu();
|
||||
cotisation.setMontantPaye(montant);
|
||||
cotisation.setStatut("PAYEE");
|
||||
cotisation.setDatePaiement(LocalDateTime.now());
|
||||
intentionPaiementRepository.getEntityManager().merge(cotisation);
|
||||
LOG.infof("Mock Wave: cotisation %s marquée PAYEE", cotisationId);
|
||||
}
|
||||
} else if (node.has("type") && "DEPOT_EPARGNE".equals(node.get("type").asText()) && node.has("compteId") && node.has("montant")) {
|
||||
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()
|
||||
@@ -128,14 +166,17 @@ public class WaveRedirectResource {
|
||||
.motif("Dépôt via Wave (mobile money)")
|
||||
.build();
|
||||
transactionEpargneService.executerTransaction(req);
|
||||
LOG.infof("Mock Wave: dépôt épargne %s XOF sur compte %s", montant, compteId);
|
||||
LOG.infof("Wave: dépôt épargne %s XOF sur compte %s", montant, compteId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG.infof("Mock Wave: intention %s complétée (validation simulée)", ref);
|
||||
|
||||
// Déléguer la complétion cotisations au service
|
||||
paiementService.completerIntention(intention, null);
|
||||
LOG.infof("Wave: intention %s complétée", ref);
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Mock Wave: erreur applyMockCompletion ref=%s", ref);
|
||||
LOG.errorf(e, "Wave: erreur applyCompletion ref=%s", ref);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.common.ErrorResponse;
|
||||
import dev.lions.unionflow.server.api.dto.wave.CompteWaveDTO;
|
||||
import dev.lions.unionflow.server.api.dto.wave.TransactionWaveDTO;
|
||||
import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
|
||||
@@ -56,12 +57,12 @@ public class WaveResource {
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.entity(ErrorResponse.ofError(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création du compte Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création du compte Wave: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la création du compte Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -83,12 +84,12 @@ public class WaveResource {
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Compte Wave non trouvé"))
|
||||
.entity(ErrorResponse.ofError("Compte Wave non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la mise à jour du compte Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la mise à jour du compte Wave: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la mise à jour du compte Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -109,12 +110,12 @@ public class WaveResource {
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Compte Wave non trouvé"))
|
||||
.entity(ErrorResponse.ofError("Compte Wave non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la vérification du compte Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la vérification du compte Wave: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la vérification du compte Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -133,12 +134,12 @@ public class WaveResource {
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Compte Wave non trouvé"))
|
||||
.entity(ErrorResponse.ofError("Compte Wave non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche du compte Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche du compte Wave: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la recherche du compte Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -156,14 +157,14 @@ public class WaveResource {
|
||||
CompteWaveDTO result = waveService.trouverCompteWaveParTelephone(numeroTelephone);
|
||||
if (result == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Compte Wave non trouvé"))
|
||||
.entity(ErrorResponse.ofError("Compte Wave non trouvé"))
|
||||
.build();
|
||||
}
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche du compte Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche du compte Wave: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la recherche du compte Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -183,7 +184,7 @@ public class WaveResource {
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des comptes Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la liste des comptes Wave: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la liste des comptes Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -207,7 +208,7 @@ public class WaveResource {
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création de la transaction Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création de la transaction Wave: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la création de la transaction Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -229,14 +230,12 @@ public class WaveResource {
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Transaction Wave non trouvée"))
|
||||
.entity(ErrorResponse.ofError("Transaction Wave non trouvée"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la mise à jour du statut de la transaction Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(
|
||||
new ErrorResponse(
|
||||
"Erreur lors de la mise à jour du statut de la transaction Wave: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la mise à jour du statut de la transaction Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -255,22 +254,15 @@ public class WaveResource {
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Transaction Wave non trouvée"))
|
||||
.entity(ErrorResponse.ofError("Transaction Wave non trouvée"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche de la transaction Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche de la transaction Wave: " + e.getMessage()))
|
||||
.entity(ErrorResponse.ofError("Erreur lors de la recherche de la transaction Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Classe interne pour les réponses d'erreur */
|
||||
public static class ErrorResponse {
|
||||
public String error;
|
||||
|
||||
public ErrorResponse(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user