diff --git a/backend/src/main/java/br/com/fiap/vigisus/config/SecurityConfig.java b/backend/src/main/java/br/com/fiap/vigisus/config/SecurityConfig.java index 147644a..102c9b4 100644 --- a/backend/src/main/java/br/com/fiap/vigisus/config/SecurityConfig.java +++ b/backend/src/main/java/br/com/fiap/vigisus/config/SecurityConfig.java @@ -31,6 +31,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("/actuator/**").permitAll() .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() .requestMatchers("/api/**").permitAll() + .requestMatchers("/admin/**").authenticated() .anyRequest().authenticated() ); return http.build(); diff --git a/backend/src/main/java/br/com/fiap/vigisus/controller/AdminDashboardController.java b/backend/src/main/java/br/com/fiap/vigisus/controller/AdminDashboardController.java new file mode 100644 index 0000000..a3e30a0 --- /dev/null +++ b/backend/src/main/java/br/com/fiap/vigisus/controller/AdminDashboardController.java @@ -0,0 +1,51 @@ +package br.com.fiap.vigisus.controller; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.search.Search; +import io.swagger.v3.oas.annotations.Hidden; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Dashboard de monitoramento operacional — acesso restrito (requer autenticação). + * Disponível na porta de gestão (9090) e oculto do Swagger público. + * + * @Hidden oculta do Swagger público. + */ +@Hidden +@RestController +@RequestMapping("/admin") +@RequiredArgsConstructor +public class AdminDashboardController { + + private final MeterRegistry meterRegistry; + + /** Retorna top municípios mais buscados (nome, UF e contagem de buscas) */ + @GetMapping("/top-municipios") + public List> getTopMunicipios() { + return Search.in(meterRegistry) + .name("busca.municipio") + .counters() + .stream() + .map(counter -> { + String municipio = counter.getId().getTag("municipio"); + String uf = counter.getId().getTag("uf"); + long buscas = (long) counter.count(); + return Map.of( + "municipio", municipio != null ? municipio : "", + "uf", uf != null ? uf : "", + "buscas", buscas); + }) + .sorted(Comparator.comparingLong(m -> -((Long) m.get("buscas")))) + .limit(10) + .collect(Collectors.toList()); + } +} diff --git a/backend/src/main/java/br/com/fiap/vigisus/controller/BuscaController.java b/backend/src/main/java/br/com/fiap/vigisus/controller/BuscaController.java index dee4f2c..d5724b7 100644 --- a/backend/src/main/java/br/com/fiap/vigisus/controller/BuscaController.java +++ b/backend/src/main/java/br/com/fiap/vigisus/controller/BuscaController.java @@ -3,6 +3,8 @@ import br.com.fiap.vigisus.application.busca.BuscaCompletaUseCase; import br.com.fiap.vigisus.dto.BuscaCompletaResponse; import br.com.fiap.vigisus.dto.BuscaRequest; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -24,6 +26,7 @@ public class BuscaController { private final BuscaCompletaUseCase buscaCompletaUseCase; + private final MeterRegistry meterRegistry; @PostMapping @Operation( @@ -40,6 +43,11 @@ public ResponseEntity buscarDireto( @RequestParam String uf, @RequestParam(defaultValue = "dengue") String doenca, @RequestParam(required = false) Integer ano) { + Counter.builder("busca.municipio") + .tag("municipio", municipio) + .tag("uf", uf) + .register(meterRegistry) + .increment(); return ResponseEntity.ok(buscaCompletaUseCase.buscarDireto(municipio, uf, doenca, ano)); } } diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index f040ec7..6094901 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -24,10 +24,12 @@ server: port: 8080 management: + server: + port: 9090 endpoints: web: exposure: - include: health,info + include: health,info,metrics endpoint: health: show-details: always diff --git a/backend/src/test/java/br/com/fiap/vigisus/controller/AdminDashboardControllerTest.java b/backend/src/test/java/br/com/fiap/vigisus/controller/AdminDashboardControllerTest.java new file mode 100644 index 0000000..696b01e --- /dev/null +++ b/backend/src/test/java/br/com/fiap/vigisus/controller/AdminDashboardControllerTest.java @@ -0,0 +1,69 @@ +package br.com.fiap.vigisus.controller; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class AdminDashboardControllerTest { + + private SimpleMeterRegistry meterRegistry; + private AdminDashboardController controller; + + @BeforeEach + void setUp() { + meterRegistry = new SimpleMeterRegistry(); + controller = new AdminDashboardController(meterRegistry); + } + + @Test + void getTopMunicipios_retornaListaVaziaQuandoSemBuscas() { + List> result = controller.getTopMunicipios(); + assertThat(result).isEmpty(); + } + + @Test + void getTopMunicipios_retornaMunicipiosOrdenadosPorBuscas() { + Counter.builder("busca.municipio").tag("municipio", "Campinas").tag("uf", "SP").register(meterRegistry).increment(); + Counter.builder("busca.municipio").tag("municipio", "Campinas").tag("uf", "SP").register(meterRegistry).increment(); + Counter.builder("busca.municipio").tag("municipio", "Sao Paulo").tag("uf", "SP").register(meterRegistry).increment(); + + List> result = controller.getTopMunicipios(); + + assertThat(result).hasSize(2); + assertThat(result.get(0).get("municipio")).isEqualTo("Campinas"); + assertThat(result.get(0).get("buscas")).isEqualTo(2L); + assertThat(result.get(1).get("municipio")).isEqualTo("Sao Paulo"); + assertThat(result.get(1).get("buscas")).isEqualTo(1L); + } + + @Test + void getTopMunicipios_limita10Resultados() { + for (int i = 1; i <= 15; i++) { + Counter.builder("busca.municipio") + .tag("municipio", "Municipio" + i) + .tag("uf", "SP") + .register(meterRegistry) + .increment(); + } + + List> result = controller.getTopMunicipios(); + + assertThat(result).hasSize(10); + } + + @Test + void getTopMunicipios_incluiCampoUf() { + Counter.builder("busca.municipio").tag("municipio", "Manaus").tag("uf", "AM").register(meterRegistry).increment(); + + List> result = controller.getTopMunicipios(); + + assertThat(result).hasSize(1); + assertThat(result.get(0).get("uf")).isEqualTo("AM"); + } +} diff --git a/backend/src/test/java/br/com/fiap/vigisus/controller/BuscaControllerTest.java b/backend/src/test/java/br/com/fiap/vigisus/controller/BuscaControllerTest.java index 3a7e861..2484655 100644 --- a/backend/src/test/java/br/com/fiap/vigisus/controller/BuscaControllerTest.java +++ b/backend/src/test/java/br/com/fiap/vigisus/controller/BuscaControllerTest.java @@ -4,6 +4,7 @@ import br.com.fiap.vigisus.dto.BuscaCompletaResponse; import br.com.fiap.vigisus.dto.BuscaRequest; import br.com.fiap.vigisus.exception.RecursoNaoEncontradoException; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -20,7 +21,7 @@ class BuscaControllerTest { @BeforeEach void setUp() { buscaCompletaUseCase = mock(BuscaCompletaUseCase.class); - controller = new BuscaController(buscaCompletaUseCase); + controller = new BuscaController(buscaCompletaUseCase, new SimpleMeterRegistry()); } @Test