package dev.lions.unionflow.server.resource; import dev.lions.unionflow.server.api.dto.organisation.request.CreateOrganisationRequest; import dev.lions.unionflow.server.api.dto.organisation.request.UpdateOrganisationRequest; import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse; import dev.lions.unionflow.server.entity.Organisation; import dev.lions.unionflow.server.service.KeycloakService; import dev.lions.unionflow.server.service.OrganisationService; import io.quarkus.security.Authenticated; import io.quarkus.security.identity.SecurityIdentity; 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.net.URI; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; 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; /** * Resource REST pour la gestion des organisations * * @author UnionFlow Team * @version 1.0 * @since 2025-01-15 */ @Path("/api/organisations") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @Tag(name = "Organisations", description = "Gestion des organisations") @RolesAllowed({"SUPER_ADMIN", "ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER"}) public class OrganisationResource { private static final Logger LOG = Logger.getLogger(OrganisationResource.class); @Inject OrganisationService organisationService; @Inject KeycloakService keycloakService; @Inject SecurityIdentity securityIdentity; /** Récupère les organisations du membre connecté (pour admin d'organisation) */ @GET @Path("/mes") @Authenticated @Operation( summary = "Mes organisations", description = "Liste les organisations auxquelles le membre connecté appartient (pour ADMIN_ORGANISATION)") @APIResponses({ @APIResponse( responseCode = "200", description = "Liste des organisations du membre", content = @Content( mediaType = MediaType.APPLICATION_JSON, schema = @Schema(type = SchemaType.ARRAY, implementation = OrganisationResponse.class))) }) public Response listerMesOrganisations() { String email = securityIdentity.getPrincipal() != null ? securityIdentity.getPrincipal().getName() : null; if (email == null || email.isBlank()) { return Response.ok(List.of()).build(); } List organisations = organisationService.listerOrganisationsPourUtilisateur(email); List dtos = organisations.stream() .map(organisationService::convertToResponse) .collect(Collectors.toList()); LOG.infof("Mes organisations pour %s: %d", email, dtos.size()); return Response.ok(dtos).build(); } /** Crée une nouvelle organisation */ @POST @RolesAllowed({"ADMIN", "MEMBRE"}) @Operation( summary = "Créer une nouvelle organisation", description = "Crée une nouvelle organisation dans le système") @APIResponses({ @APIResponse( responseCode = "201", description = "Organisation créée avec succès", content = @Content( mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = OrganisationResponse.class))), @APIResponse(responseCode = "400", description = "Données invalides"), @APIResponse(responseCode = "409", description = "Organisation déjà existante"), @APIResponse(responseCode = "401", description = "Non authentifié"), @APIResponse(responseCode = "403", description = "Non autorisé") }) public Response creerOrganisation(@Valid CreateOrganisationRequest request) { LOG.infof("Création d'une nouvelle organisation: %s", request.nom()); try { Organisation organisation = organisationService.convertFromCreateRequest(request); Organisation organisationCreee = organisationService.creerOrganisation(organisation, "system"); OrganisationResponse dto = organisationService.convertToResponse(organisationCreee); return Response.created(URI.create("/api/organisations/" + organisationCreee.getId())) .entity(dto) .build(); } catch (IllegalArgumentException e) { LOG.warnf("Erreur lors de la création de l'organisation: %s", e.getMessage()); return Response.status(Response.Status.CONFLICT) .entity(Map.of("error", e.getMessage())) .build(); } catch (Exception e) { LOG.errorf(e, "Erreur inattendue lors de la création de l'organisation"); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", "Erreur interne du serveur")) .build(); } } /** Récupère toutes les organisations actives */ @GET @jakarta.annotation.security.PermitAll // ✅ Accès public pour inscription @Operation( summary = "Lister les organisations", description = "Récupère la liste des organisations actives avec pagination") @APIResponses({ @APIResponse( responseCode = "200", description = "Liste des organisations récupérée avec succès", content = @Content( mediaType = MediaType.APPLICATION_JSON, schema = @Schema(type = SchemaType.ARRAY, implementation = OrganisationResponse.class))), @APIResponse(responseCode = "401", description = "Non authentifié"), @APIResponse(responseCode = "403", description = "Non autorisé") }) public Response listerOrganisations( @Parameter(description = "Numéro de page (commence à 0)", example = "0") @QueryParam("page") @DefaultValue("0") int page, @Parameter(description = "Taille de la page", example = "20") @QueryParam("size") @DefaultValue("20") int size, @Parameter(description = "Terme de recherche (nom ou nom court)") @QueryParam("recherche") String recherche) { LOG.infof( "Récupération des organisations - page: %d, size: %d, recherche: %s", page, size, recherche); try { List organisations; // Admin d'organisation (sans rôle ADMIN/SUPER_ADMIN) : ne retourner que ses organisations java.util.Set roles = securityIdentity.getRoles() != null ? securityIdentity.getRoles() : java.util.Set.of(); boolean onlyOrgAdmin = roles.contains("ADMIN_ORGANISATION") && !roles.contains("ADMIN") && !roles.contains("SUPER_ADMIN"); if (onlyOrgAdmin && securityIdentity.getPrincipal() != null) { String email = securityIdentity.getPrincipal().getName(); organisations = organisationService.listerOrganisationsPourUtilisateur(email); if (recherche != null && !recherche.trim().isEmpty()) { String term = recherche.trim().toLowerCase(); organisations = organisations.stream() .filter(o -> (o.getNom() != null && o.getNom().toLowerCase().contains(term)) || (o.getNomCourt() != null && o.getNomCourt().toLowerCase().contains(term))) .collect(Collectors.toList()); } // Pagination en mémoire pour /mes int from = Math.min(page * size, organisations.size()); int to = Math.min(from + size, organisations.size()); organisations = organisations.subList(from, to); LOG.infof("ADMIN_ORGANISATION: retour de %d organisation(s) pour %s", organisations.size(), email); } else if (recherche != null && !recherche.trim().isEmpty()) { organisations = organisationService.rechercherOrganisations(recherche.trim(), page, size); } else { organisations = organisationService.listerOrganisationsActives(page, size); } List dtos = organisations.stream() .map(organisationService::convertToResponse) .collect(Collectors.toList()); return Response.ok(dtos).build(); } catch (Exception e) { LOG.errorf(e, "Erreur lors de la récupération des organisations"); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", "Erreur interne du serveur")) .build(); } } /** Récupère une organisation par son ID */ @GET @Path("/{id}") @Operation( summary = "Récupérer une organisation", description = "Récupère une organisation par son ID") @APIResponses({ @APIResponse( responseCode = "200", description = "Organisation trouvée", content = @Content( mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = OrganisationResponse.class))), @APIResponse(responseCode = "404", description = "Organisation non trouvée"), @APIResponse(responseCode = "401", description = "Non authentifié"), @APIResponse(responseCode = "403", description = "Non autorisé") }) public Response obtenirOrganisation( @Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) { LOG.infof("Récupération de l'organisation ID: %s", id); return organisationService .trouverParId(id) .map( organisation -> { OrganisationResponse dto = organisationService.convertToResponse(organisation); return Response.ok(dto).build(); }) .orElse( Response.status(Response.Status.NOT_FOUND) .entity(Map.of("error", "Organisation non trouvée")) .build()); } /** Met à jour une organisation */ @PUT @RolesAllowed({"ADMIN", "MEMBRE"}) @Path("/{id}") @Operation( summary = "Mettre à jour une organisation", description = "Met à jour les informations d'une organisation") @APIResponses({ @APIResponse( responseCode = "200", description = "Organisation mise à jour avec succès", content = @Content( mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = OrganisationResponse.class))), @APIResponse(responseCode = "400", description = "Données invalides"), @APIResponse(responseCode = "404", description = "Organisation non trouvée"), @APIResponse(responseCode = "409", description = "Conflit de données"), @APIResponse(responseCode = "401", description = "Non authentifié"), @APIResponse(responseCode = "403", description = "Non autorisé") }) public Response mettreAJourOrganisation( @Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id, @Valid UpdateOrganisationRequest request) { LOG.infof("Mise à jour de l'organisation ID: %s", id); try { Organisation organisationMiseAJour = organisationService.convertFromUpdateRequest(request); Organisation organisation = organisationService.mettreAJourOrganisation(id, organisationMiseAJour, "system"); OrganisationResponse dto = organisationService.convertToResponse(organisation); return Response.ok(dto).build(); } catch (NotFoundException e) { return Response.status(Response.Status.NOT_FOUND) .entity(Map.of("error", e.getMessage())) .build(); } catch (IllegalArgumentException e) { LOG.warnf("Erreur lors de la mise à jour de l'organisation: %s", e.getMessage()); return Response.status(Response.Status.CONFLICT) .entity(Map.of("error", e.getMessage())) .build(); } catch (Exception e) { LOG.errorf(e, "Erreur inattendue lors de la mise à jour de l'organisation"); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", "Erreur interne du serveur")) .build(); } } /** Supprime une organisation */ @DELETE @RolesAllowed({"ADMIN"}) @Path("/{id}") @Operation( summary = "Supprimer une organisation", description = "Supprime une organisation (soft delete)") @APIResponses({ @APIResponse(responseCode = "204", description = "Organisation supprimée avec succès"), @APIResponse(responseCode = "404", description = "Organisation non trouvée"), @APIResponse(responseCode = "409", description = "Impossible de supprimer l'organisation"), @APIResponse(responseCode = "401", description = "Non authentifié"), @APIResponse(responseCode = "403", description = "Non autorisé") }) public Response supprimerOrganisation( @Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) { LOG.infof("Suppression de l'organisation ID: %d", id); try { organisationService.supprimerOrganisation(id, "system"); return Response.noContent().build(); } catch (NotFoundException e) { return Response.status(Response.Status.NOT_FOUND) .entity(Map.of("error", e.getMessage())) .build(); } catch (IllegalStateException e) { LOG.warnf("Erreur lors de la suppression de l'organisation: %s", e.getMessage()); return Response.status(Response.Status.CONFLICT) .entity(Map.of("error", e.getMessage())) .build(); } catch (Exception e) { LOG.errorf(e, "Erreur inattendue lors de la suppression de l'organisation"); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", "Erreur interne du serveur")) .build(); } } /** Recherche avancée d'organisations */ @GET @Path("/recherche") @Operation( summary = "Recherche avancée", description = "Recherche d'organisations avec critères multiples") @APIResponses({ @APIResponse( responseCode = "200", description = "Résultats de recherche", content = @Content( mediaType = MediaType.APPLICATION_JSON, schema = @Schema(type = SchemaType.ARRAY, implementation = OrganisationResponse.class))), @APIResponse(responseCode = "401", description = "Non authentifié"), @APIResponse(responseCode = "403", description = "Non autorisé") }) public Response rechercheAvancee( @Parameter(description = "Nom de l'organisation") @QueryParam("nom") String nom, @Parameter(description = "Type d'organisation") @QueryParam("type") String typeOrganisation, @Parameter(description = "Statut") @QueryParam("statut") String statut, @Parameter(description = "Ville") @QueryParam("ville") String ville, @Parameter(description = "Région") @QueryParam("region") String region, @Parameter(description = "Pays") @QueryParam("pays") String pays, @Parameter(description = "Numéro de page") @QueryParam("page") @DefaultValue("0") int page, @Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("20") int size) { LOG.infof("Recherche avancée d'organisations avec critères multiples"); try { List organisations = organisationService.rechercheAvancee( nom, typeOrganisation, statut, ville, region, pays, page, size); List dtos = organisations.stream() .map(organisationService::convertToResponse) .collect(Collectors.toList()); return Response.ok(dtos).build(); } catch (Exception e) { LOG.errorf(e, "Erreur lors de la recherche avancée"); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", "Erreur interne du serveur")) .build(); } } /** Active une organisation */ @POST @RolesAllowed({"ADMIN", "MEMBRE"}) @Path("/{id}/activer") @Operation( summary = "Activer une organisation", description = "Active une organisation suspendue") @APIResponses({ @APIResponse(responseCode = "200", description = "Organisation activée avec succès"), @APIResponse(responseCode = "404", description = "Organisation non trouvée"), @APIResponse(responseCode = "401", description = "Non authentifié"), @APIResponse(responseCode = "403", description = "Non autorisé") }) public Response activerOrganisation( @Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) { LOG.infof("Activation de l'organisation ID: %d", id); try { Organisation organisation = organisationService.activerOrganisation(id, "system"); OrganisationResponse dto = organisationService.convertToResponse(organisation); return Response.ok(dto).build(); } catch (NotFoundException e) { return Response.status(Response.Status.NOT_FOUND) .entity(Map.of("error", e.getMessage())) .build(); } catch (Exception e) { LOG.errorf(e, "Erreur lors de l'activation de l'organisation"); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", "Erreur interne du serveur")) .build(); } } /** Suspend une organisation */ @POST @RolesAllowed({"ADMIN", "MEMBRE"}) @Path("/{id}/suspendre") @Operation( summary = "Suspendre une organisation", description = "Suspend une organisation active") @APIResponses({ @APIResponse(responseCode = "200", description = "Organisation suspendue avec succès"), @APIResponse(responseCode = "404", description = "Organisation non trouvée"), @APIResponse(responseCode = "401", description = "Non authentifié"), @APIResponse(responseCode = "403", description = "Non autorisé") }) public Response suspendreOrganisation( @Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) { LOG.infof("Suspension de l'organisation ID: %d", id); try { Organisation organisation = organisationService.suspendreOrganisation(id, "system"); OrganisationResponse dto = organisationService.convertToResponse(organisation); return Response.ok(dto).build(); } catch (NotFoundException e) { return Response.status(Response.Status.NOT_FOUND) .entity(Map.of("error", e.getMessage())) .build(); } catch (Exception e) { LOG.errorf(e, "Erreur lors de la suspension de l'organisation"); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", "Erreur interne du serveur")) .build(); } } /** Obtient les statistiques des organisations */ @GET @Path("/statistiques") @Operation( summary = "Statistiques des organisations", description = "Récupère les statistiques globales des organisations") @APIResponses({ @APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès"), @APIResponse(responseCode = "401", description = "Non authentifié"), @APIResponse(responseCode = "403", description = "Non autorisé") }) public Response obtenirStatistiques() { LOG.info("Récupération des statistiques des organisations"); try { Map statistiques = organisationService.obtenirStatistiques(); return Response.ok(statistiques).build(); } catch (Exception e) { LOG.errorf(e, "Erreur lors de la récupération des statistiques"); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", "Erreur interne du serveur")) .build(); } } }