package dev.lions.unionflow.server.resource; import dev.lions.unionflow.server.api.dto.compliance.response.KpiPublicSnapshot; import dev.lions.unionflow.server.service.compliance.KpiPublicService; import dev.lions.unionflow.server.service.compliance.KpiShareTokenService; import io.quarkus.security.PermissionsAllowed; import jakarta.annotation.security.PermitAll; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import java.util.UUID; import org.jboss.logging.Logger; /** * Endpoint public KPI (Sprint 17) — consommation par autorités externes via token signé. * *

Pas de @Authenticated : accès anonyme avec token signé HMAC-SHA256. Toute requête * (succès ou échec) est tracée dans l'audit trail pour transparency. * * @since 2026-04-25 (Sprint 17) */ @Path("/api/public/kpi") @Produces(MediaType.APPLICATION_JSON) @PermitAll public class PublicKpiResource { private static final Logger LOG = Logger.getLogger(PublicKpiResource.class); @Inject KpiShareTokenService tokenService; @Inject KpiPublicService kpiService; @GET public Response consulter(@QueryParam("token") String token) { if (token == null || token.isBlank()) { return Response.status(Response.Status.BAD_REQUEST) .entity(java.util.Map.of("error", "token query param requis")) .build(); } var orgIdOpt = tokenService.verifier(token); if (orgIdOpt.isEmpty()) { LOG.warnf("PublicKpi: token invalide ou expiré"); return Response.status(Response.Status.UNAUTHORIZED) .entity(java.util.Map.of("error", "Token invalide ou expiré")) .build(); } UUID orgId = orgIdOpt.get(); try { KpiPublicSnapshot snap = kpiService.snapshotPublic(orgId, "PUBLIC_TOKEN"); return Response.ok(snap).build(); } catch (IllegalArgumentException e) { LOG.warnf("PublicKpi: org %s introuvable", orgId); return Response.status(Response.Status.NOT_FOUND) .entity(java.util.Map.of("error", "Organisation introuvable")) .build(); } catch (Exception e) { LOG.errorf(e, "PublicKpi: erreur snapshot org=%s", orgId); return Response.serverError() .entity(java.util.Map.of("error", "Erreur interne")) .build(); } } }