feat: BackupService real pg_dump, OrganisationService region stats, SystemConfigService overrides

- BackupService: DB-persisted metadata (BackupRecord/BackupConfig entities + V16 Flyway migration),
  real pg_dump execution via ProcessBuilder, soft-delete on deleteBackup, pg_restore manual guidance
- OrganisationService: repartitionRegion now queries Adresse entities (was Map.of() stub)
- SystemConfigService: in-memory config overrides via AtomicReference (no DB dependency)
- SystemMetricsService: null-guard on MemoryMXBean in getSystemStatus() (fixes test NPE)
- Souscription workflow: SouscriptionService, SouscriptionResource, FormuleAbonnementRepository,
  V11 Flyway migration, admin REST clients
- Flyway V8-V15: notes membres, types référence, type orga constraint, seed roles,
  première connexion, Wave checkout URL, Wave telephone column length fix
- .gitignore: added uploads/ and .claude/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dahoud
2026-04-04 16:14:30 +00:00
parent 9c66909eff
commit e00a9301d8
98 changed files with 5571 additions and 636 deletions

View File

@@ -8,13 +8,16 @@ import dev.lions.unionflow.server.entity.DemandeAdhesion;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.AdhesionRepository;
import dev.lions.unionflow.server.repository.MembreOrganisationRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.ForbiddenException;
import jakarta.ws.rs.NotFoundException;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@@ -23,6 +26,7 @@ import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.jwt.JsonWebToken;
/**
* Service métier pour la gestion des demandes d'adhésion.
@@ -42,9 +46,15 @@ public class AdhesionService {
@Inject
OrganisationRepository organisationRepository;
@Inject
MembreOrganisationRepository membreOrganisationRepository;
@Inject
MembreKeycloakSyncService keycloakSyncService;
@Inject
DefaultsService defaultsService;
@Inject
SecurityIdentity securityIdentity;
@Inject
JsonWebToken jwt;
public List<AdhesionResponse> getAllAdhesions(int page, int size) {
log.debug("Récupération des adhésions - page: {}, size: {}", page, size);
@@ -142,6 +152,8 @@ public class AdhesionService {
.findByIdOptional(id)
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
verifierAccesOrganisation(adhesion);
if (!adhesion.isEnAttente()) {
throw new IllegalStateException("Seules les adhésions en attente peuvent être approuvées");
}
@@ -175,6 +187,8 @@ public class AdhesionService {
.findByIdOptional(id)
.orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
verifierAccesOrganisation(adhesion);
if (!adhesion.isEnAttente()) {
throw new IllegalStateException("Seules les adhésions en attente peuvent être rejetées");
}
@@ -279,6 +293,35 @@ public class AdhesionService {
"tauxRejet", total > 0 ? (rejetees * 100.0 / total) : 0.0);
}
/**
* Vérifie que l'ADMIN_ORGANISATION n'agit que sur les adhésions de sa propre organisation.
* Les rôles SUPER_ADMIN et ADMIN ont accès sans restriction.
*/
private void verifierAccesOrganisation(DemandeAdhesion adhesion) {
if (!securityIdentity.hasRole("ADMIN_ORGANISATION")) {
return; // SUPER_ADMIN / ADMIN : accès libre
}
UUID adhesionOrgId = adhesion.getOrganisation() != null ? adhesion.getOrganisation().getId() : null;
if (adhesionOrgId == null) {
throw new ForbiddenException("L'adhésion n'est rattachée à aucune organisation");
}
String keycloakSubject = jwt.getSubject();
Membre adminMembre = membreRepository.findByKeycloakUserId(keycloakSubject)
.orElseThrow(() -> new ForbiddenException("Compte admin introuvable pour le sujet JWT: " + keycloakSubject));
boolean appartient = membreOrganisationRepository
.findByMembreIdAndOrganisationId(adminMembre.getId(), adhesionOrgId)
.isPresent();
if (!appartient) {
log.warn("ADMIN_ORGANISATION {} tente d'agir sur une adhésion de l'organisation {} qui n'est pas la sienne",
keycloakSubject, adhesionOrgId);
throw new ForbiddenException("Vous ne pouvez gérer que les adhésions de votre organisation");
}
}
private AdhesionResponse convertToDTO(DemandeAdhesion adhesion) {
AdhesionResponse response = new AdhesionResponse();
response.setId(adhesion.getId());