diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index c9b965f..873d364 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -34,61 +34,31 @@ services: - postgres-dev restart: unless-stopped - kafka: - image: apache/kafka:4.0.0 - container_name: unionflow-kafka-dev - environment: - KAFKA_NODE_ID: 1 - KAFKA_PROCESS_ROLES: broker,controller - KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093 - KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093 - KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT - KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER - KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT - KAFKA_LOG_DIRS: /var/lib/kafka/data - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 - KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 - KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 - KAFKA_DEFAULT_REPLICATION_FACTOR: 1 - KAFKA_NUM_PARTITIONS: 3 - KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true" - CLUSTER_ID: MkU3OEVBNTcwNTJENDM2Qk - ports: - - "9092:9092" - volumes: - - kafka_dev_data:/var/lib/kafka/data - networks: - - unionflow-dev - healthcheck: - test: ["CMD-SHELL", "/opt/kafka/bin/kafka-topics.sh --list --bootstrap-server localhost:9092 || exit 1"] - interval: 15s - timeout: 10s - retries: 10 - start_period: 30s - restart: unless-stopped + # Kafka est géré comme conteneur standalone (apache/kafka:4.0.0) sur port 9092. + # Ne pas le redéfinir ici pour éviter le conflit de port. + # Démarrer manuellement si nécessaire : + # docker run -d --name kafka --network unionflow-dev \ + # -p 9092:9092 apache/kafka:4.0.0 + # Puis recréer les topics : + # docker exec kafka /opt/kafka/bin/kafka-topics.sh --create \ + # --bootstrap-server localhost:9092 --topic unionflow.finance.approvals --partitions 3 --replication-factor 1 kafka-ui: image: ghcr.io/kafbat/kafka-ui:v1.4.2 container_name: unionflow-kafka-ui-dev environment: KAFKA_CLUSTERS_0_NAME: unionflow-dev - KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092 + KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: host.docker.internal:9092 DYNAMIC_CONFIG_ENABLED: "true" ports: - "8082:8080" networks: - unionflow-dev - depends_on: - kafka: - condition: service_healthy restart: unless-stopped volumes: postgres_dev_data: driver: local - kafka_dev_data: - driver: local networks: unionflow-dev: diff --git a/src/main/java/dev/lions/unionflow/server/resource/TypeOrganisationReferenceResource.java b/src/main/java/dev/lions/unionflow/server/resource/TypeOrganisationReferenceResource.java index afffe71..bfd3cf4 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/TypeOrganisationReferenceResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/TypeOrganisationReferenceResource.java @@ -70,6 +70,8 @@ public class TypeOrganisationReferenceResource { .estDefaut(request.estDefaut()) .estSysteme(request.estSysteme()) .organisationId(request.organisationId()) + .categorie(request.categorie()) + .modulesRequis(request.modulesRequis()) .build(); try { TypeReferenceResponse created = typeReferenceService.creer(withDomaine); diff --git a/src/main/java/dev/lions/unionflow/server/service/AlertMonitoringService.java b/src/main/java/dev/lions/unionflow/server/service/AlertMonitoringService.java index 85179ea..893c37e 100644 --- a/src/main/java/dev/lions/unionflow/server/service/AlertMonitoringService.java +++ b/src/main/java/dev/lions/unionflow/server/service/AlertMonitoringService.java @@ -5,6 +5,7 @@ import dev.lions.unionflow.server.entity.SystemAlert; import dev.lions.unionflow.server.repository.AlertConfigurationRepository; import dev.lions.unionflow.server.repository.SystemAlertRepository; import dev.lions.unionflow.server.repository.SystemLogRepository; +import io.quarkus.arc.Arc; import io.quarkus.scheduler.Scheduled; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -47,6 +48,10 @@ public class AlertMonitoringService { @Scheduled(cron = "0 * * * * ?") // Toutes les minutes à la seconde 0 @Transactional public void monitorSystemMetrics() { + // Guard contre l'exécution pendant le shutdown Quarkus (Arc.container() null → NPE) + if (!Arc.container().isRunning()) { + return; + } try { log.debug("Running scheduled system metrics monitoring..."); diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 2b8fc03..bc1bfaf 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -50,10 +50,12 @@ quarkus.log.category."io.quarkus.security".level=INFO wave.mock.enabled=true wave.redirect.base.url=http://localhost:8085 -# OIDC client "admin-service" — service account pour appels admin vers lions-user-manager -quarkus.oidc-client.admin-service.auth-server-url=http://localhost:8180/realms/unionflow +# OIDC client "admin-service" — service account pour appels vers lions-user-manager +# Utilise le realm lions-user-manager (cohérent avec le serveur LUM qui valide ce realm) +# Le client unionflow-server existe dans lions-user-manager realm avec ce secret +quarkus.oidc-client.admin-service.auth-server-url=http://localhost:8180/realms/lions-user-manager quarkus.oidc-client.admin-service.client-id=unionflow-server -quarkus.oidc-client.admin-service.credentials.secret=unionflow-secret-2025 +quarkus.oidc-client.admin-service.credentials.secret=Esj0DzyRt7wSPtcePDae1dQQdqmQxlJm quarkus.oidc-client.admin-service.grant.type=client quarkus.oidc-client.admin-service.tls.verification=none quarkus.oidc-client.admin-service.early-tokens-acquisition=true diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 3383095..e197b29 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -58,9 +58,10 @@ quarkus.log.category."org.jboss.resteasy".level=WARN quarkus.rest-client.lions-user-manager-api.url=${LIONS_USER_MANAGER_URL:http://lions-user-manager:8081} # OIDC client "admin-service" — service account pour appels admin vers lions-user-manager -quarkus.oidc-client.admin-service.auth-server-url=${KEYCLOAK_AUTH_SERVER_URL:https://security.lions.dev/realms/unionflow} +# Utilise le realm lions-user-manager (cohérent avec le serveur LUM qui valide ce realm) +quarkus.oidc-client.admin-service.auth-server-url=${KEYCLOAK_LUM_AUTH_SERVER_URL:https://security.lions.dev/realms/lions-user-manager} quarkus.oidc-client.admin-service.client-id=unionflow-server -quarkus.oidc-client.admin-service.credentials.secret=${KEYCLOAK_CLIENT_SECRET} +quarkus.oidc-client.admin-service.credentials.secret=${KEYCLOAK_ADMIN_SERVICE_SECRET:${KEYCLOAK_CLIENT_SECRET}} quarkus.oidc-client.admin-service.grant.type=client # Wave Money — Production diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 7022bb5..7212b0f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -45,6 +45,7 @@ quarkus.hibernate-orm.metrics.enabled=false quarkus.flyway.migrate-at-start=true quarkus.flyway.baseline-on-migrate=true quarkus.flyway.baseline-version=0 +quarkus.flyway.out-of-order=true # Configuration Keycloak OIDC — base commune quarkus.oidc.application-type=service diff --git a/src/main/resources/db/migration/V10__Fix_ModulesDisponibles_Version_Column_Type.sql b/src/main/resources/db/migration/V10_1__Fix_ModulesDisponibles_Version_Column_Type.sql similarity index 100% rename from src/main/resources/db/migration/V10__Fix_ModulesDisponibles_Version_Column_Type.sql rename to src/main/resources/db/migration/V10_1__Fix_ModulesDisponibles_Version_Column_Type.sql diff --git a/src/test/java/dev/lions/unionflow/server/resource/TypeOrganisationReferenceResourceTest.java b/src/test/java/dev/lions/unionflow/server/resource/TypeOrganisationReferenceResourceTest.java index 66fb6a8..54b73b9 100644 --- a/src/test/java/dev/lions/unionflow/server/resource/TypeOrganisationReferenceResourceTest.java +++ b/src/test/java/dev/lions/unionflow/server/resource/TypeOrganisationReferenceResourceTest.java @@ -2,9 +2,13 @@ package dev.lions.unionflow.server.resource; import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.notNullValue; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import dev.lions.unionflow.server.api.dto.reference.request.CreateTypeReferenceRequest; +import org.mockito.ArgumentCaptor; + import dev.lions.unionflow.server.api.dto.reference.response.TypeReferenceResponse; import dev.lions.unionflow.server.service.TypeReferenceService; import io.quarkus.security.identity.SecurityIdentity; @@ -83,6 +87,40 @@ class TypeOrganisationReferenceResourceTest { .contentType(ContentType.JSON); } + @Test + @TestSecurity(user = "superadmin@test.com", roles = {"SUPER_ADMIN"}) + void creerType_withCategorieAndModules_forwardsFields() { + TypeReferenceResponse response = TypeReferenceResponse.builder() + .domaine("TYPE_ORGANISATION") + .code("TONTINE") + .libelle("Tontine") + .categorie("FINANCIER_SOLIDAIRE") + .modulesRequis("TONTINE,COTISATIONS") + .build(); + response.setId(UUID.randomUUID()); + ArgumentCaptor captor = + ArgumentCaptor.forClass(CreateTypeReferenceRequest.class); + when(typeReferenceService.creer(captor.capture())).thenReturn(response); + + String body = """ + {"code":"TONTINE","libelle":"Tontine","domaine":"TYPE_ORGANISATION", + "categorie":"FINANCIER_SOLIDAIRE","modulesRequis":"TONTINE,COTISATIONS"} + """; + + given() + .contentType(ContentType.JSON) + .body(body) + .when() + .post(BASE_PATH) + .then() + .statusCode(201); + + CreateTypeReferenceRequest captured = captor.getValue(); + assertEquals("FINANCIER_SOLIDAIRE", captured.categorie()); + assertEquals("TONTINE,COTISATIONS", captured.modulesRequis()); + assertEquals("TYPE_ORGANISATION", captured.domaine()); + } + @Test @TestSecurity(user = "superadmin@test.com", roles = {"SUPER_ADMIN"}) void creerType_illegalArg_returns400() { diff --git a/src/test/java/dev/lions/unionflow/server/service/TypeReferenceServiceTest.java b/src/test/java/dev/lions/unionflow/server/service/TypeReferenceServiceTest.java index 7f820a9..a0c5272 100644 --- a/src/test/java/dev/lions/unionflow/server/service/TypeReferenceServiceTest.java +++ b/src/test/java/dev/lions/unionflow/server/service/TypeReferenceServiceTest.java @@ -201,7 +201,7 @@ class TypeReferenceServiceTest { UpdateTypeReferenceRequest updateRequest = new UpdateTypeReferenceRequest( null, // code (pas de changement) "Nouveau libellé", - null, null, null, null, null, null, null); + null, null, null, null, null, null, null, null, null); TypeReferenceResponse updated = typeReferenceService.modifier(created.getId(), updateRequest); assertThat(updated.getLibelle()).isEqualTo("Nouveau libellé"); @@ -212,7 +212,7 @@ class TypeReferenceServiceTest { @DisplayName("modifier avec UUID inexistant lance IllegalArgumentException") void modifier_inexistant_throws() { UpdateTypeReferenceRequest updateRequest = new UpdateTypeReferenceRequest( - null, "Libellé", null, null, null, null, null, null, null); + null, "Libellé", null, null, null, null, null, null, null, null, null); assertThatThrownBy(() -> typeReferenceService.modifier(UUID.randomUUID(), updateRequest)) .isInstanceOf(IllegalArgumentException.class) @@ -317,7 +317,7 @@ class TypeReferenceServiceTest { .build()); UpdateTypeReferenceRequest updateRequest = new UpdateTypeReferenceRequest( - newCode, null, null, null, null, null, null, null, null); + newCode, null, null, null, null, null, null, null, null, null, null); TypeReferenceResponse updated = typeReferenceService.modifier(created.getId(), updateRequest); assertThat(updated.getCode()).isEqualTo(newCode.toUpperCase()); @@ -340,7 +340,7 @@ class TypeReferenceServiceTest { .build()); UpdateTypeReferenceRequest updateRequest = new UpdateTypeReferenceRequest( - "CHANGED_CODE", null, null, null, null, null, null, null, null); + "CHANGED_CODE", null, null, null, null, null, null, null, null, null, null); assertThatThrownBy(() -> typeReferenceService.modifier(created.getId(), updateRequest)) .isInstanceOf(IllegalArgumentException.class) @@ -365,7 +365,7 @@ class TypeReferenceServiceTest { // Même code (case-insensitive) → !equalsIgnoreCase = false → if body skipped UpdateTypeReferenceRequest updateRequest = new UpdateTypeReferenceRequest( - code.toLowerCase(), "Nouveau libelle", null, null, null, null, null, null, null); + code.toLowerCase(), "Nouveau libelle", null, null, null, null, null, null, null, null, null); TypeReferenceResponse updated = typeReferenceService.modifier(created.getId(), updateRequest); assertThat(updated).isNotNull(); @@ -457,7 +457,7 @@ class TypeReferenceServiceTest { // Modifier le code avec l'organisation non-null → L195-197 : orgId = entity.getOrganisation().getId() UpdateTypeReferenceRequest updateRequest = new UpdateTypeReferenceRequest( - newCode, null, null, null, null, null, null, null, null); + newCode, null, null, null, null, null, null, null, null, null, null); TypeReferenceResponse updated = typeReferenceService.modifier(created.getId(), updateRequest); @@ -491,7 +491,9 @@ class TypeReferenceServiceTest { "warning", // severity 10, // ordreAffichage true, // estDefaut - false // actif + false, // actif + null, // categorie + null // modulesRequis ); TypeReferenceResponse updated = typeReferenceService.modifier(created.getId(), updateRequest);