From eba36a65a6b0944b170bac2977c258d38150656e Mon Sep 17 00:00:00 2001 From: Lorenzo Catalano Date: Thu, 11 Jan 2024 17:33:11 +0100 Subject: [PATCH 01/10] wip alpha --- .github/workflows/release_deploy.yml | 38 +- .identity/99_variables.tf | 4 +- .identity/env/dev/backend.tfvars | 2 +- README.md | 16 +- TODO | 7 + helm/Chart.yaml | 2 +- helm/values-dev.yaml | 41 +- helm/values-prod.yaml | 45 +- helm/values-uat.yaml | 44 +- pom.xml | 107 +- .../standintechsupport/Application.java | 40 + .../config/LoggingAspect.java | 152 +++ .../config/MappingsConfiguration.java | 17 + .../config/OpenApiConfig.java | 124 ++ .../config/RequestFilter.java | 57 + .../config/ResponseValidator.java | 52 + .../config/WebMvcConfiguration.java | 27 + .../controller/SupportController.java | 51 + .../controller/model/CosmosEventModel.java | 25 + .../controller/model/ResponseContainer.java | 23 + .../controller/model/StandInStation.java | 18 + .../exception/AppError.java | 21 + .../exception/AppException.java | 86 ++ .../exception/ErrorHandler.java | 249 ++++ .../model/AppCorsConfiguration.java | 23 + .../standintechsupport/model/AppInfo.java | 22 + .../standintechsupport/model/DateRequest.java | 16 + .../standintechsupport/model/ProblemJson.java | 49 + .../BlacklistStationsRepository.java | 15 + .../repository/CosmosEventsRepository.java | 87 ++ .../repository/CosmosNodeDataRepository.java | 59 + .../CosmosStationDataRepository.java | 51 + .../repository/CosmosStationRepository.java | 70 ++ .../repository/StandInStationsRepository.java | 14 + .../repository/entity/BlacklistStation.java | 17 + .../repository/entity/StandInStation.java | 21 + .../repository/model/CosmosEvent.java | 25 + .../model/CosmosForwarderCallCounts.java | 24 + .../model/CosmosNodeCallCounts.java | 29 + .../model/CosmosStandInStation.java | 23 + .../service/SupportService.java | 84 ++ .../standintechsupport/util/Constants.java | 15 + .../pagopa/standintechsupport/util/Util.java | 31 + .../resources/application-local.properties | 18 +- src/main/resources/application.properties | 8 +- src/main/resources/openapi_config.json | 1039 +++++++++++++++++ .../standintechsupport/ApplicationTest.java | 16 + .../OpenApiGenerationTest.java | 47 + 48 files changed, 2915 insertions(+), 136 deletions(-) create mode 100644 TODO create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/Application.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/config/LoggingAspect.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/config/MappingsConfiguration.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/config/OpenApiConfig.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/config/RequestFilter.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/config/ResponseValidator.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/config/WebMvcConfiguration.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/controller/SupportController.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/controller/model/CosmosEventModel.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/controller/model/ResponseContainer.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/controller/model/StandInStation.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/exception/AppError.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/exception/AppException.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/exception/ErrorHandler.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/model/AppCorsConfiguration.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/model/AppInfo.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/model/DateRequest.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/model/ProblemJson.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/BlacklistStationsRepository.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosEventsRepository.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosNodeDataRepository.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosStationDataRepository.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosStationRepository.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/StandInStationsRepository.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/entity/BlacklistStation.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/entity/StandInStation.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosEvent.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosForwarderCallCounts.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosNodeCallCounts.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosStandInStation.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/service/SupportService.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/util/Constants.java create mode 100644 src/main/java/it/gov/pagopa/standintechsupport/util/Util.java create mode 100644 src/main/resources/openapi_config.json create mode 100644 src/test/java/it/gov/pagopa/standintechsupport/ApplicationTest.java create mode 100644 src/test/java/it/gov/pagopa/standintechsupport/OpenApiGenerationTest.java diff --git a/.github/workflows/release_deploy.yml b/.github/workflows/release_deploy.yml index fb5368a..3b72f7c 100644 --- a/.github/workflows/release_deploy.yml +++ b/.github/workflows/release_deploy.yml @@ -22,7 +22,8 @@ on: description: Select the version options: - '' - - skip_or_promote + - skip + - promote - new_release - breaking_change @@ -72,7 +73,7 @@ jobs: - if: ${{ github.ref_name != 'main' }} run: echo "SEMVER=buildNumber" >> $GITHUB_ENV - - if: ${{ inputs.version == 'skip_or_promote' }} + - if: ${{ inputs.version == 'skip' || inputs.version == 'promote' }} run: echo "SEMVER=skip" >> $GITHUB_ENV - id: get_semver @@ -93,7 +94,7 @@ jobs: release: name: Create a New Release runs-on: ubuntu-latest - needs: [ setup ] + needs: [setup] outputs: version: ${{ steps.release.outputs.version }} steps: @@ -110,19 +111,38 @@ jobs: needs: [ setup, release ] name: Build and Push Docker Image runs-on: ubuntu-latest - if: ${{ needs.setup.outputs.semver != 'skip' }} + if: ${{ inputs.semver != 'skip' }} steps: - name: Build and Push id: semver uses: pagopa/github-actions-template/ghcr-build-push@v1.5.4 with: + branch: ${{ github.ref_name}} github_token: ${{ secrets.GITHUB_TOKEN }} tag: ${{ needs.release.outputs.version }} - deploy_aks: - name: Deploy on AKS + + deploy_aks_dev: + name: Deploy on AKS DEV + needs: [ setup, release, image ] + uses: ./.github/workflows/deploy_with_github_runner.yml + with: + environment: ${{ needs.setup.outputs.environment }} + secrets: inherit + + deploy_aks_uat: + name: Deploy on AKS UAT + needs: [ setup, release, image ] + if: ${{ needs.setup.outputs.environment != 'dev' }} + uses: ./.github/workflows/deploy_with_github_runner.yml + with: + environment: ${{ needs.setup.outputs.environment }} + secrets: inherit + + deploy_aks_prod: + name: Deploy on AKS PROD needs: [ setup, release, image ] - if: ${{ always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }} + if: ${{ needs.setup.outputs.environment == 'prod' }} uses: ./.github/workflows/deploy_with_github_runner.yml with: environment: ${{ needs.setup.outputs.environment }} @@ -130,7 +150,7 @@ jobs: notify: - needs: [ setup, release, deploy_aks ] + needs: [ deploy_aks_prod ] runs-on: ubuntu-latest name: Notify if: always() @@ -139,7 +159,7 @@ jobs: if: ${{ needs.setup.outputs.environment == 'prod' }} uses: ravsamhq/notify-slack-action@v2 with: - status: ${{ needs.deploy_aks.result }} + status: ${{ needs.deploy_aks_prod.result }} token: ${{ secrets.GITHUB_TOKEN }} notification_title: 'New Release on Production ${{ needs.release.outputs.version }} has {status_message}' message_format: '{emoji} <{run_url}|{workflow}> {status_message} in <{repo_url}|{repo}>' diff --git a/.identity/99_variables.tf b/.identity/99_variables.tf index b57e1b5..28da1dd 100644 --- a/.identity/99_variables.tf +++ b/.identity/99_variables.tf @@ -1,11 +1,11 @@ locals { github = { org = "pagopa" - repository = "TODO" #TODO + repository = "pagopa-stand-in-manager" } prefix = "pagopa" - domain = "TODO" #TODO + domain = "nodo" location_short = "weu" product = "${var.prefix}-${var.env_short}" diff --git a/.identity/env/dev/backend.tfvars b/.identity/env/dev/backend.tfvars index b5dbac8..3c60029 100644 --- a/.identity/env/dev/backend.tfvars +++ b/.identity/env/dev/backend.tfvars @@ -1,4 +1,4 @@ resource_group_name = "io-infra-rg" storage_account_name = "pagopainfraterraformdev" container_name = "azurermstate" -key = ".tfstate" # TODO +key = "pagopa-stand-in-manager.tfstate" diff --git a/README.md b/README.md index 4a70c72..0a43881 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,9 @@ -# Template for Java Spring Microservice project +# StandIn Manager [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=TODO-set-your-id&metric=alert_status)](https://sonarcloud.io/dashboard?id=TODO-set-your-id) [![Integration Tests](https://github.com/pagopa//actions/workflows/integration_test.yml/badge.svg?branch=main)](https://github.com/pagopa//actions/workflows/integration_test.yml) -TODO: add a description - -TODO: generate a index with this tool: https://ecotrust-canada.github.io/markdown-toc/ - -TODO: resolve all the TODOs in this template - ---- - -## Api Documentation 📖 - -See the [OpenApi 3 here.](https://editor.swagger.io/?url=https://raw.githubusercontent.com/pagopa//main/openapi/openapi.json) +Monitors the events of nodo-dei-pagamenti for station problems and activates/deactivates the standIn for that station. --- @@ -34,6 +24,8 @@ See the [OpenApi 3 here.](https://editor.swagger.io/?url=https://raw.githubuserc ### Prerequisites - docker +- cosmosdb emulator +- dataexplorer emulator ### Run docker container diff --git a/TODO b/TODO new file mode 100644 index 0000000..a17a670 --- /dev/null +++ b/TODO @@ -0,0 +1,7 @@ +documentazione + +notifica entrata uscita + - mail a elenco destinatari + +cache singola chiave + refresh totale + diff --git a/helm/Chart.yaml b/helm/Chart.yaml index b608062..46d479c 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 -name: pagopa-afm-calculator +name: pagopa-stand-in-technical-support description: Microservice that handles calculation for pagoPA Advanced Fees Management type: application version: 0.0.0 diff --git a/helm/values-dev.yaml b/helm/values-dev.yaml index ec62f6d..84e6548 100644 --- a/helm/values-dev.yaml +++ b/helm/values-dev.yaml @@ -1,9 +1,7 @@ microservice-chart: - namespace: "your-namespace" # TODO: set your AKS namespace - nameOverride: "" - fullnameOverride: "" + namespace: "nodo" image: - repository: ghcr.io/pagopa/yourname # TODO + repository: ghcr.io/pagopa/pagopa-stand-in-technical-support tag: "0.0.0" pullPolicy: Always livenessProbe: @@ -62,14 +60,17 @@ microservice-chart: type: Utilization # Allowed types are 'Utilization' or 'AverageValue' value: "75" envConfig: - # TODO: set your name - WEBSITE_SITE_NAME: 'yourProjectName' # required to show cloud role name in application insights - ENV: 'azure-dev' + WEBSITE_SITE_NAME: 'pagopa-stand-in-technical-support' # required to show cloud role name in application insights + ENV: 'dev' APP_LOGGING_LEVEL: 'DEBUG' DEFAULT_LOGGING_LEVEL: 'INFO' CORS_CONFIGURATION: '{"origins": ["*"], "methods": ["*"]}' - OTEL_SERVICE_NAME: # TODO + SPRING_DATASOURCE_URL: "jdbc:postgresql://pagopa-d-weu-nodo-flexible-postgresql.postgres.database.azure.com:6432/nodo?prepareThreshold=0" + SPRING_DATASOURCE_USERNAME: "cfg" + COSMOS_ENDPOINT: + + OTEL_SERVICE_NAME: "pagopa-stand-in-manager" OTEL_RESOURCE_ATTRIBUTES: "deployment.environment=dev" OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.elastic-system.svc:4317" OTEL_TRACES_EXPORTER: otlp @@ -80,8 +81,10 @@ microservice-chart: # required APPLICATIONINSIGHTS_CONNECTION_STRING: 'ai-d-connection-string' OTEL_EXPORTER_OTLP_HEADERS: elastic-apm-secret-token + SPRING_DATASOURCE_PASSWORD: "db-cfg-password" + COSMOS_KEY: "" keyvault: - name: "pagopa-d-name-kv" #TODO + name: "pagopa-d-nodo-kv" tenantId: "7788edaf-0346-4068-9d79-c868aed15b3d" nodeSelector: { } tolerations: [ ] @@ -94,23 +97,3 @@ microservice-chart: operator: In values: - user - canaryDelivery: - create: true - ingress: - create: true - canary: - type: header - headerName: X-Canary - headerValue: canary - weightPercent: 0 - service: - create: true - deployment: - create: true - image: - repository: ghcr.io/pagopa/yourname # TODO - tag: "0.0.0" - pullPolicy: Always - envConfig: { } - envSecret: { } - diff --git a/helm/values-prod.yaml b/helm/values-prod.yaml index 7a751aa..622945a 100644 --- a/helm/values-prod.yaml +++ b/helm/values-prod.yaml @@ -1,9 +1,7 @@ microservice-chart: - namespace: "your-namespace" # TODO: set your AKS namespace - nameOverride: "" - fullnameOverride: "" + namespace: "nodo" image: - repository: ghcr.io/pagopa/yourname # TODO + repository: ghcr.io/pagopa/pagopa-stand-in-technical-support tag: "0.0.0" pullPolicy: Always livenessProbe: @@ -51,7 +49,7 @@ microservice-chart: cpu: "0.25" autoscaling: enable: true - minReplica: 3 + minReplica: 1 maxReplica: 10 pollingInterval: 10 # seconds cooldownPeriod: 50 # seconds @@ -62,14 +60,17 @@ microservice-chart: type: Utilization # Allowed types are 'Utilization' or 'AverageValue' value: "75" envConfig: - # TODO: set your name - WEBSITE_SITE_NAME: 'yourProjectName' # required to show cloud role name in application insights - ENV: 'azure-prod' - APP_LOGGING_LEVEL: 'INFO' + WEBSITE_SITE_NAME: 'pagopa-stand-in-technical-support' # required to show cloud role name in application insights + ENV: 'prod' + APP_LOGGING_LEVEL: 'DEBUG' DEFAULT_LOGGING_LEVEL: 'INFO' CORS_CONFIGURATION: '{"origins": ["*"], "methods": ["*"]}' - OTEL_SERVICE_NAME: # TODO + SPRING_DATASOURCE_URL: "jdbc:postgresql://pagopa-p-weu-nodo-flexible-postgresql.postgres.database.azure.com:6432/nodo?prepareThreshold=0" + SPRING_DATASOURCE_USERNAME: "cfg" + COSMOS_ENDPOINT: + + OTEL_SERVICE_NAME: "pagopa-stand-in-manager" OTEL_RESOURCE_ATTRIBUTES: "deployment.environment=prod" OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.elastic-system.svc:4317" OTEL_TRACES_EXPORTER: otlp @@ -80,8 +81,10 @@ microservice-chart: # required APPLICATIONINSIGHTS_CONNECTION_STRING: 'ai-p-connection-string' OTEL_EXPORTER_OTLP_HEADERS: elastic-apm-secret-token + SPRING_DATASOURCE_PASSWORD: "db-cfg-password" + COSMOS_KEY: "" keyvault: - name: "pagopa-p-name-kv" #TODO + name: "pagopa-p-name-kv" tenantId: "7788edaf-0346-4068-9d79-c868aed15b3d" nodeSelector: { } tolerations: [ ] @@ -94,23 +97,3 @@ microservice-chart: operator: In values: - user - canaryDelivery: - create: true - ingress: - create: true - canary: - type: header - headerName: X-Canary - headerValue: canary - weightPercent: 0 - service: - create: true - deployment: - create: true - image: - repository: ghcr.io/pagopa/yourname # TODO - tag: "0.0.0" - pullPolicy: Always - envConfig: { } - envSecret: { } - diff --git a/helm/values-uat.yaml b/helm/values-uat.yaml index 80e74af..c3a6fec 100644 --- a/helm/values-uat.yaml +++ b/helm/values-uat.yaml @@ -1,9 +1,7 @@ microservice-chart: - namespace: "your-namespace" # TODO: set your AKS namespace - nameOverride: "" - fullnameOverride: "" + namespace: "nodo" image: - repository: ghcr.io/pagopa/yourname # TODO + repository: ghcr.io/pagopa/pagopa-stand-in-technical-support tag: "0.0.0" pullPolicy: Always livenessProbe: @@ -51,7 +49,7 @@ microservice-chart: cpu: "0.25" autoscaling: enable: true - minReplica: 3 + minReplica: 1 maxReplica: 10 pollingInterval: 10 # seconds cooldownPeriod: 50 # seconds @@ -62,13 +60,17 @@ microservice-chart: type: Utilization # Allowed types are 'Utilization' or 'AverageValue' value: "75" envConfig: - # TODO: set your name - WEBSITE_SITE_NAME: 'yourProjectName' # required to show cloud role name in application insights - ENV: 'azure-uat' + WEBSITE_SITE_NAME: 'pagopa-stand-in-technical-support' # required to show cloud role name in application insights + ENV: 'uat' APP_LOGGING_LEVEL: 'DEBUG' DEFAULT_LOGGING_LEVEL: 'INFO' CORS_CONFIGURATION: '{"origins": ["*"], "methods": ["*"]}' - OTEL_SERVICE_NAME: # TODO + + SPRING_DATASOURCE_URL: "jdbc:postgresql://pagopa-u-weu-nodo-flexible-postgresql.postgres.database.azure.com:6432/nodo?prepareThreshold=0" + SPRING_DATASOURCE_USERNAME: "cfg" + COSMOS_ENDPOINT: + + OTEL_SERVICE_NAME: "pagopa-stand-in-manager" OTEL_RESOURCE_ATTRIBUTES: "deployment.environment=uat" OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.elastic-system.svc:4317" OTEL_TRACES_EXPORTER: otlp @@ -79,8 +81,10 @@ microservice-chart: # required APPLICATIONINSIGHTS_CONNECTION_STRING: 'ai-u-connection-string' OTEL_EXPORTER_OTLP_HEADERS: elastic-apm-secret-token + SPRING_DATASOURCE_PASSWORD: "db-cfg-password" + COSMOS_KEY: "" keyvault: - name: "pagopa-u-name-kv" #TODO + name: "pagopa-u-name-kv" tenantId: "7788edaf-0346-4068-9d79-c868aed15b3d" nodeSelector: { } tolerations: [ ] @@ -93,23 +97,3 @@ microservice-chart: operator: In values: - user - canaryDelivery: - create: true - ingress: - create: true - canary: - type: header - headerName: X-Canary - headerValue: canary - weightPercent: 0 - service: - create: true - deployment: - create: true - image: - repository: ghcr.io/pagopa/yourname # TODO - tag: "0.0.0" - pullPolicy: Always - envConfig: { } - envSecret: { } - diff --git a/pom.xml b/pom.xml index 4e0fb28..7249486 100644 --- a/pom.xml +++ b/pom.xml @@ -7,13 +7,13 @@ org.springframework.boot spring-boot-starter-parent - 2.7.3 + 2.7.16 it.gov.pagopa - microservice + pagopa-stand-in-technical-support 0.0.0 - Your description + Stand in Manager Tech Support 11 @@ -28,14 +28,13 @@ org.springframework.boot spring-boot-starter-validation - - - org.springframework.boot - spring-boot-devtools - runtime - true + org.postgresql + postgresql + 42.5.4 + + org.springframework.boot spring-boot-configuration-processor @@ -78,15 +77,15 @@ - com.h2database - h2 - runtime + com.azure + azure-cosmos + 4.52.0 - org.hibernate.orm - hibernate-core - 6.1.3.Final + org.projectlombok + lombok + true @@ -100,16 +99,39 @@ lombok true + + software.amazon.awssdk + ses + 2.17.24 + junit junit test + + + jakarta.annotation + jakarta.annotation-api + 2.1.1 + + - co.elastic.logging - logback-ecs-encoder - 1.5.0 + javax.annotation + javax.annotation-api + 1.3.2 + + + com.microsoft.azure.kusto + kusto-data + 5.0.2 + + + org.slf4j + slf4j-simple + + @@ -120,6 +142,55 @@ spring-boot-maven-plugin + + org.openapitools + openapi-generator-maven-plugin + 7.0.1 + + + cache-openapi + + generate + + + ${project.basedir}/src/main/resources/openapi_config.json + java + false + false + + it.gov.pagopa.standintechsupport.config + it.gov.pagopa.standintechsupport.config.model + java8 + false + true + true + resttemplate + + + + + + + + + + + + + + + + + + + + + + + + + + org.jacoco jacoco-maven-plugin diff --git a/src/main/java/it/gov/pagopa/standintechsupport/Application.java b/src/main/java/it/gov/pagopa/standintechsupport/Application.java new file mode 100644 index 0000000..4eec3f9 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/Application.java @@ -0,0 +1,40 @@ +package it.gov.pagopa.standintechsupport; // TODO: refactor the package + +import com.azure.cosmos.CosmosClient; +import com.azure.cosmos.CosmosClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.ses.SesClient; + +@SpringBootApplication() +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @Value("${cosmos.endpoint}") + private String cosmosEndpoint; + + @Value("${cosmos.key}") + private String cosmosKey; + + @Value("${aws.region}") + private String region; + + @Bean + public CosmosClient getCosmosClient() { + return new CosmosClientBuilder().endpoint(cosmosEndpoint).key(cosmosKey).buildClient(); + } + + @Bean + public SesClient sesClient() { + return SesClient.builder() + .region(Region.of(region)) + .build(); + } + +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/config/LoggingAspect.java b/src/main/java/it/gov/pagopa/standintechsupport/config/LoggingAspect.java new file mode 100644 index 0000000..4c07e1e --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/config/LoggingAspect.java @@ -0,0 +1,152 @@ +package it.gov.pagopa.standintechsupport.config; + +import jakarta.annotation.PostConstruct; +import java.util.Arrays; +import java.util.stream.StreamSupport; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.slf4j.MDC; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.core.env.AbstractEnvironment; +import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.core.env.Environment; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +@Aspect +@Component +@Slf4j +public class LoggingAspect { + + public static final String START_TIME = "startTime"; + public static final String METHOD = "method"; + public static final String STATUS = "status"; + public static final String CODE = "httpCode"; + public static final String RESPONSE_TIME = "responseTime"; + + @Autowired HttpServletRequest httRequest; + + @Autowired HttpServletResponse httpResponse; + + @Value("${info.application.artifactId}") + private String artifactId; + + @Value("${info.application.version}") + private String version; + + @Value("${info.properties.environment}") + private String environment; + + private static String getExecutionTime() { + String startTime = MDC.get(START_TIME); + if (startTime != null) { + long endTime = System.currentTimeMillis(); + long executionTime = endTime - Long.parseLong(startTime); + return String.valueOf(executionTime); + } + return "1"; + } + + @Pointcut("@within(org.springframework.web.bind.annotation.RestController)") + public void restController() { + // all rest controllers + } + + @Pointcut("@within(org.springframework.stereotype.Repository)") + public void repository() { + // all repository methods + } + + @Pointcut("@within(org.springframework.stereotype.Service)") + public void service() { + // all service methods + } + + /** Log essential info of application during the startup. */ + @PostConstruct + public void logStartup() { + log.info("-> Starting {} version {} - environment {}", artifactId, version, environment); + } + + /** + * If DEBUG log-level is enabled prints the env variables and the application properties. + * + * @param event Context of application + */ + @EventListener + public void handleContextRefresh(ContextRefreshedEvent event) { + final Environment env = event.getApplicationContext().getEnvironment(); + log.debug("Active profiles: {}", Arrays.toString(env.getActiveProfiles())); + final MutablePropertySources sources = ((AbstractEnvironment) env).getPropertySources(); + StreamSupport.stream(sources.spliterator(), false) + .filter(EnumerablePropertySource.class::isInstance) + .map(ps -> ((EnumerablePropertySource) ps).getPropertyNames()) + .flatMap(Arrays::stream) + .distinct() + .filter( + prop -> + !(prop.toLowerCase().contains("credentials") + || prop.toLowerCase().contains("password") + || prop.toLowerCase().contains("pass") + || prop.toLowerCase().contains("pwd") + || prop.toLowerCase().contains("key") + || prop.toLowerCase().contains("secret"))) + .forEach(prop -> log.debug("{}: {}", prop, env.getProperty(prop))); + } + + @Around(value = "restController()") + public Object logApiInvocation(ProceedingJoinPoint joinPoint) throws Throwable { + MDC.put(METHOD, joinPoint.getSignature().getName()); + MDC.put(START_TIME, String.valueOf(System.currentTimeMillis())); + log.info("{} {}", httRequest.getMethod(), httRequest.getRequestURI()); + log.info( + "Invoking API operation {} - args: {}", + joinPoint.getSignature().getName(), + joinPoint.getArgs()); + + Object result = joinPoint.proceed(); + + MDC.put(STATUS, "OK"); + MDC.put(CODE, String.valueOf(httpResponse.getStatus())); + MDC.put(RESPONSE_TIME, getExecutionTime()); + log.info( + "Successful API operation {} - result: {}", joinPoint.getSignature().getName(), result); + MDC.remove(STATUS); + MDC.remove(CODE); + MDC.remove(RESPONSE_TIME); + MDC.remove(START_TIME); + return result; + } + + @AfterReturning(value = "execution(* *..exception.ErrorHandler.*(..))", returning = "result") + public void trowingApiInvocation(JoinPoint joinPoint, ResponseEntity result) { + MDC.put(STATUS, "KO"); + MDC.put(CODE, String.valueOf(result.getStatusCodeValue())); + MDC.put(RESPONSE_TIME, getExecutionTime()); + log.info("Failed API operation {} - error: {}", MDC.get(METHOD), result); + MDC.remove(STATUS); + MDC.remove(CODE); + MDC.remove(RESPONSE_TIME); + MDC.remove(START_TIME); + } + + @Around(value = "repository() || service()") + public Object logTrace(ProceedingJoinPoint joinPoint) throws Throwable { + log.debug( + "Call method {} - args: {}", joinPoint.getSignature().toShortString(), joinPoint.getArgs()); + Object result = joinPoint.proceed(); + log.debug("Return method {} - result: {}", joinPoint.getSignature().toShortString(), result); + return result; + } +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/config/MappingsConfiguration.java b/src/main/java/it/gov/pagopa/standintechsupport/config/MappingsConfiguration.java new file mode 100644 index 0000000..199a288 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/config/MappingsConfiguration.java @@ -0,0 +1,17 @@ +package it.gov.pagopa.standintechsupport.config; + +import org.modelmapper.ModelMapper; +import org.modelmapper.convention.MatchingStrategies; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MappingsConfiguration { + + @Bean + ModelMapper modelMapper() { + ModelMapper mapper = new ModelMapper(); + mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT); + return mapper; + } +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/config/OpenApiConfig.java b/src/main/java/it/gov/pagopa/standintechsupport/config/OpenApiConfig.java new file mode 100644 index 0000000..d7ef4f0 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/config/OpenApiConfig.java @@ -0,0 +1,124 @@ +package it.gov.pagopa.standintechsupport.config; + +import static it.gov.pagopa.standintechsupport.util.Constants.HEADER_REQUEST_ID; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.headers.Header; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.media.StringSchema; +import io.swagger.v3.oas.models.parameters.Parameter; +import io.swagger.v3.oas.models.responses.ApiResponses; +import io.swagger.v3.oas.models.security.SecurityScheme; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.springdoc.core.customizers.OpenApiCustomiser; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class OpenApiConfig { + + @Bean + public OpenAPI customOpenAPI( + @Value("${info.application.artifactId}") String appName, + @Value("${info.application.description}") String appDescription, + @Value("${info.application.version}") String appVersion) { + return new OpenAPI() + .components( + new Components() + .addSecuritySchemes( + "ApiKey", + new SecurityScheme() + .type(SecurityScheme.Type.APIKEY) + .description("The API key to access this function app.") + .name("Ocp-Apim-Subscription-Key") + .in(SecurityScheme.In.HEADER))) + .info( + new Info() + .title(appName) + .version(appVersion) + .description(appDescription) + .termsOfService("https://www.pagopa.gov.it/")); + } + + @Bean + public OpenApiCustomiser sortOperationsAlphabetically() { + return openApi -> { + Paths paths = + openApi.getPaths().entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .collect( + Paths::new, + (map, item) -> map.addPathItem(item.getKey(), item.getValue()), + Paths::putAll); + + paths.forEach( + (key, value) -> + value + .readOperations() + .forEach( + operation -> { + var responses = + operation.getResponses().entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .collect( + ApiResponses::new, + (map, item) -> + map.addApiResponse(item.getKey(), item.getValue()), + ApiResponses::putAll); + operation.setResponses(responses); + })); + openApi.setPaths(paths); + }; + } + + @Bean + public OpenApiCustomiser addCommonHeaders() { + return openApi -> + openApi + .getPaths() + .forEach( + (key, value) -> { + + // add Request-ID as request header + var header = + Optional.ofNullable(value.getParameters()) + .orElse(Collections.emptyList()) + .parallelStream() + .filter(Objects::nonNull) + .anyMatch(elem -> HEADER_REQUEST_ID.equals(elem.getName())); + if (!header) { + value.addParametersItem( + new Parameter() + .in("header") + .name(HEADER_REQUEST_ID) + .schema(new StringSchema()) + .description( + "This header identifies the call, if not passed it is" + + " self-generated. This ID is returned in the response.")); + } + + // add Request-ID as response header + value + .readOperations() + .forEach( + operation -> + operation + .getResponses() + .values() + .forEach( + response -> + response.addHeaderObject( + HEADER_REQUEST_ID, + new Header() + .schema(new StringSchema()) + .description( + "This header identifies the call")))); + }); + } +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/config/RequestFilter.java b/src/main/java/it/gov/pagopa/standintechsupport/config/RequestFilter.java new file mode 100644 index 0000000..05848fd --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/config/RequestFilter.java @@ -0,0 +1,57 @@ +package it.gov.pagopa.standintechsupport.config; + +import static it.gov.pagopa.standintechsupport.util.Constants.HEADER_REQUEST_ID; + +import java.io.IOException; +import java.util.UUID; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +@Slf4j +public class RequestFilter implements Filter { + + /** + * Get the request ID from the custom header "X-Request-Id" if present, otherwise it generates + * one. Set the X-Request-Id value in the {@code response} and in the MDC + * + * @param request http request + * @param response http response + * @param chain next filter + * @throws IOException if an I/O error occurs during this filter's processing of the request + * @throws ServletException if the processing fails for any other reason + */ + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + try { + HttpServletRequest httRequest = (HttpServletRequest) request; + + // get requestId from header or generate one + String requestId = httRequest.getHeader(HEADER_REQUEST_ID); + if (requestId == null || requestId.isEmpty()) { + requestId = UUID.randomUUID().toString(); + } + + // set requestId in MDC + MDC.put("requestId", requestId); + + // set requestId in the response header + ((HttpServletResponse) response).setHeader(HEADER_REQUEST_ID, requestId); + chain.doFilter(request, response); + } finally { + MDC.clear(); + } + } +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/config/ResponseValidator.java b/src/main/java/it/gov/pagopa/standintechsupport/config/ResponseValidator.java new file mode 100644 index 0000000..ed1db69 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/config/ResponseValidator.java @@ -0,0 +1,52 @@ +package it.gov.pagopa.standintechsupport.config; + +import it.gov.pagopa.standintechsupport.exception.AppException; +import java.util.Set; +import javax.validation.ConstraintViolation; +import javax.validation.Validator; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +@Aspect +@Component +public class ResponseValidator { + + @Autowired private Validator validator; + + /** + * This method validates the response annotated with the {@link javax.validation.constraints} + * + * @param joinPoint not used + * @param result the response to validate + */ + // TODO: set your package + @AfterReturning( + pointcut = "execution(* it.gov.pagopa.microservice.controller.*.*(..))", + returning = "result") + public void validateResponse(JoinPoint joinPoint, Object result) { + if (result instanceof ResponseEntity) { + validateResponse((ResponseEntity) result); + } + } + + private void validateResponse(ResponseEntity response) { + if (response.getBody() != null) { + Set> validationResults = validator.validate(response.getBody()); + + if (!validationResults.isEmpty()) { + var sb = new StringBuilder(); + for (ConstraintViolation error : validationResults) { + sb.append(error.getPropertyPath()).append(" ").append(error.getMessage()).append(". "); + } + var msg = StringUtils.chop(sb.toString()); + throw new AppException(HttpStatus.INTERNAL_SERVER_ERROR, "Invalid response", msg); + } + } + } +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/config/WebMvcConfiguration.java b/src/main/java/it/gov/pagopa/standintechsupport/config/WebMvcConfiguration.java new file mode 100644 index 0000000..150ff7a --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/config/WebMvcConfiguration.java @@ -0,0 +1,27 @@ +package it.gov.pagopa.standintechsupport.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import it.gov.pagopa.standintechsupport.model.AppCorsConfiguration; +import lombok.SneakyThrows; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebMvcConfiguration implements WebMvcConfigurer { + + @Value("${cors.configuration}") + private String corsConfiguration; + + @SneakyThrows + @Override + public void addCorsMappings(CorsRegistry registry) { + AppCorsConfiguration appCorsConfiguration = + new ObjectMapper().readValue(corsConfiguration, AppCorsConfiguration.class); + registry + .addMapping("/**") + .allowedOrigins(appCorsConfiguration.getOrigins()) + .allowedMethods(appCorsConfiguration.getMethods()); + } +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/controller/SupportController.java b/src/main/java/it/gov/pagopa/standintechsupport/controller/SupportController.java new file mode 100644 index 0000000..1217370 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/controller/SupportController.java @@ -0,0 +1,51 @@ +package it.gov.pagopa.standintechsupport.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import it.gov.pagopa.standintechsupport.controller.model.ResponseContainer; +import it.gov.pagopa.standintechsupport.controller.model.StandInStation; +import it.gov.pagopa.standintechsupport.service.SupportService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +@RestController +@Slf4j +public class SupportController { + + @Autowired + private SupportService supportService; + + @Operation(summary = "Get the list of filtered events") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Get the list", + content = { @Content(mediaType = "application/json", + schema = @Schema(implementation = ResponseContainer.class)) }), + @ApiResponse(responseCode = "400", description = "Invalid request", + content = @Content) + }) + @GetMapping("/events") + public ResponseContainer getEvents(Optional station, @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Optional from, @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Optional to){ + return supportService.getEvents(station,from.orElse(null),to.orElse(null)); + } + + @Operation(summary = "Get the list of standin station") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Get the list", + content = { @Content(mediaType = "application/json", + schema = @Schema(implementation = List.class)) }) + }) + @GetMapping("/stations") + public List getStations(){ + return supportService.getStations(); + } +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/controller/model/CosmosEventModel.java b/src/main/java/it/gov/pagopa/standintechsupport/controller/model/CosmosEventModel.java new file mode 100644 index 0000000..3e91030 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/controller/model/CosmosEventModel.java @@ -0,0 +1,25 @@ + +package it.gov.pagopa.standintechsupport.controller.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CosmosEventModel { + private String id; + private String station; + private Instant timestamp; + private String type; + private String info; + + public String getDate() { + return timestamp.toString(); + } +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/controller/model/ResponseContainer.java b/src/main/java/it/gov/pagopa/standintechsupport/controller/model/ResponseContainer.java new file mode 100644 index 0000000..3b8831b --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/controller/model/ResponseContainer.java @@ -0,0 +1,23 @@ +package it.gov.pagopa.standintechsupport.controller.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDate; +import java.util.List; + +@Data +@AllArgsConstructor +@Builder +public class ResponseContainer { + private LocalDate dateFrom; + private LocalDate dateTo; + + private int count; + + @JsonProperty("data") + private List data; +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/controller/model/StandInStation.java b/src/main/java/it/gov/pagopa/standintechsupport/controller/model/StandInStation.java new file mode 100644 index 0000000..caa9569 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/controller/model/StandInStation.java @@ -0,0 +1,18 @@ +package it.gov.pagopa.standintechsupport.controller.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class StandInStation { + private String station; + private Instant timestamp; +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/exception/AppError.java b/src/main/java/it/gov/pagopa/standintechsupport/exception/AppError.java new file mode 100644 index 0000000..eed0416 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/exception/AppError.java @@ -0,0 +1,21 @@ +package it.gov.pagopa.standintechsupport.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public enum AppError { + BAD_REQUEST(HttpStatus.BAD_REQUEST, "Invalid Request", "%s"), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error", "Something was wrong"), + INTERVAL_TOO_LARGE(HttpStatus.BAD_REQUEST, "Date interval too large", "%s"); + + public final HttpStatus httpStatus; + public final String title; + public final String details; + + AppError(HttpStatus httpStatus, String title, String details) { + this.httpStatus = httpStatus; + this.title = title; + this.details = details; + } +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/exception/AppException.java b/src/main/java/it/gov/pagopa/standintechsupport/exception/AppException.java new file mode 100644 index 0000000..6ca9676 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/exception/AppException.java @@ -0,0 +1,86 @@ +package it.gov.pagopa.standintechsupport.exception; + +import java.util.Formatter; +import javax.validation.constraints.NotNull; +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; + +/** + * Custom exception. + * + *

See {@link ErrorHandler} + */ +@EqualsAndHashCode(callSuper = true) +@Value +@Validated +public class AppException extends RuntimeException { + + /** title returned to the response when this exception occurred */ + String title; + + /** http status returned to the response when this exception occurred */ + HttpStatus httpStatus; + + /** + * @param httpStatus HTTP status returned to the response + * @param title title returned to the response when this exception occurred + * @param message the detail message returend to the response + * @param cause The cause of this {@link AppException} + */ + public AppException( + @NotNull HttpStatus httpStatus, + @NotNull String title, + @NotNull String message, + Throwable cause) { + super(message, cause); + this.title = title; + this.httpStatus = httpStatus; + } + + /** + * @param httpStatus HTTP status returned to the response + * @param title title returned to the response when this exception occurred + * @param message the detail message returend to the response + */ + public AppException( + @NotNull HttpStatus httpStatus, @NotNull String title, @NotNull String message) { + super(message); + this.title = title; + this.httpStatus = httpStatus; + } + + /** + * @param appError Response template returned to the response + * @param args {@link Formatter} replaces the placeholders in "details" string of {@link AppError} + * with the arguments. If there are more arguments than format specifiers, the extra arguments + * are ignored. + */ + public AppException(@NotNull AppError appError, Object... args) { + super(formatDetails(appError, args)); + this.httpStatus = appError.httpStatus; + this.title = appError.title; + } + + /** + * @param appError Response template returned to the response + * @param cause The cause of this {@link AppException} + * @param args Arguments for the details of {@link AppError} replaced by the {@link Formatter}. If + * there are more arguments than format specifiers, the extra arguments are ignored. + */ + public AppException(@NotNull AppError appError, Throwable cause, Object... args) { + super(formatDetails(appError, args), cause); + this.httpStatus = appError.httpStatus; + this.title = appError.title; + } + + private static String formatDetails(AppError appError, Object[] args) { + return String.format(appError.details, args); + } + + @Override + public String toString() { + return "AppException(" + httpStatus + ", " + title + ")" + super.toString(); + } +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/exception/ErrorHandler.java b/src/main/java/it/gov/pagopa/standintechsupport/exception/ErrorHandler.java new file mode 100644 index 0000000..ac7c43e --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/exception/ErrorHandler.java @@ -0,0 +1,249 @@ +package it.gov.pagopa.standintechsupport.exception; + +import it.gov.pagopa.standintechsupport.model.ProblemJson; +import java.util.ArrayList; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.hibernate.exception.ConstraintViolationException; +import org.springframework.beans.TypeMismatchException; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +/** All Exceptions are handled by this class */ +@ControllerAdvice +@Slf4j +public class ErrorHandler extends ResponseEntityExceptionHandler { + + public static final String INTERNAL_SERVER_ERROR = "INTERNAL SERVER ERROR"; + public static final String BAD_REQUEST = "BAD REQUEST"; + public static final String FOREIGN_KEY_VIOLATION = "23503"; + public static final int CHILD_RECORD_VIOLATION = 2292; + + /** + * Handle if the input request is not a valid JSON + * + * @param ex {@link HttpMessageNotReadableException} exception raised + * @param headers of the response + * @param status of the response + * @param request from frontend + * @return a {@link ProblemJson} as response with the cause and with a 400 as HTTP status + */ + @Override + public ResponseEntity handleHttpMessageNotReadable( + HttpMessageNotReadableException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest request) { + log.warn("Input not readable: ", ex); + var errorResponse = + ProblemJson.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .title(BAD_REQUEST) + .detail("Invalid input format") + .build(); + return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); + } + + /** + * Handle if missing some request parameters in the request + * + * @param ex {@link MissingServletRequestParameterException} exception raised + * @param headers of the response + * @param status of the response + * @param request from frontend + * @return a {@link ProblemJson} as response with the cause and with a 400 as HTTP status + */ + @Override + public ResponseEntity handleMissingServletRequestParameter( + MissingServletRequestParameterException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest request) { + log.warn("Missing request parameter: ", ex); + var errorResponse = + ProblemJson.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .title(BAD_REQUEST) + .detail(ex.getMessage()) + .build(); + return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); + } + + /** + * Customize the response for TypeMismatchException. + * + * @param ex the exception + * @param headers the headers to be written to the response + * @param status the selected response status + * @param request the current request + * @return a {@code ResponseEntity} instance + */ + @Override + protected ResponseEntity handleTypeMismatch( + TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + log.warn("Type mismatch: ", ex); + var errorResponse = + ProblemJson.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .title(BAD_REQUEST) + .detail( + String.format( + "Invalid value %s for property %s", + ex.getValue(), ((MethodArgumentTypeMismatchException) ex).getName())) + .build(); + return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); + } + + /** + * Handle if validation constraints are unsatisfied + * + * @param ex {@link MethodArgumentNotValidException} exception raised + * @param headers of the response + * @param status of the response + * @param request from frontend + * @return a {@link ProblemJson} as response with the cause and with a 400 as HTTP status + */ + @Override + protected ResponseEntity handleMethodArgumentNotValid( + MethodArgumentNotValidException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest request) { + List details = new ArrayList<>(); + for (FieldError error : ex.getBindingResult().getFieldErrors()) { + details.add(error.getField() + ": " + error.getDefaultMessage()); + } + var detailsMessage = String.join(", ", details); + log.warn("Input not valid: " + detailsMessage); + var errorResponse = + ProblemJson.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .title(BAD_REQUEST) + .detail(detailsMessage) + .build(); + return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler({javax.validation.ConstraintViolationException.class}) + public ResponseEntity handleConstraintViolationException( + final javax.validation.ConstraintViolationException ex, final WebRequest request) { + log.warn("Validation Error raised:", ex); + var errorResponse = + ProblemJson.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .title(BAD_REQUEST) + .detail(ex.getMessage()) + .build(); + return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); + } + + /** + * @param ex {@link DataIntegrityViolationException} exception raised when the SQL statement + * cannot be executed + * @param request from frontend + * @return a {@link ProblemJson} as response with the cause and with an appropriated HTTP status + */ + @ExceptionHandler({DataIntegrityViolationException.class}) + public ResponseEntity handleDataIntegrityViolationException( + final DataIntegrityViolationException ex, final WebRequest request) { + ProblemJson errorResponse = null; + + if (ex.getCause() instanceof ConstraintViolationException) { + String sqlState = ((ConstraintViolationException) ex.getCause()).getSQLState(); + var errorCode = + ((ConstraintViolationException) ex.getCause()).getSQLException().getErrorCode(); + // check the reason of ConstraintViolationException: is true if the object is referenced by a + // foreign key + // more info: https://docs.oracle.com/javadb/10.8.3.0/ref/rrefexcept71493.html + if (sqlState.equals(FOREIGN_KEY_VIOLATION)) { + log.warn("Can't delete from Database", ex); + errorResponse = + ProblemJson.builder() + .status(HttpStatus.CONFLICT.value()) + .title("Conflict with the current state of the resource") + .detail("There is a relation with other resource. Delete it first.") + .build(); + } + if (errorCode == CHILD_RECORD_VIOLATION) { + log.warn("Can't update the Database", ex); + errorResponse = + ProblemJson.builder() + .status(HttpStatus.CONFLICT.value()) + .title("Conflict with the current state of the resource") + .detail("There is a relation with other resource. Delete it first.") + .build(); + } + } + + // default response + if (errorResponse == null) { + log.warn("Data Integrity Violation", ex); + errorResponse = + ProblemJson.builder() + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .title(INTERNAL_SERVER_ERROR) + .detail(ex.getMessage()) + .build(); + } + + return new ResponseEntity<>(errorResponse, HttpStatus.valueOf(errorResponse.getStatus())); + } + + /** + * Handle if a {@link AppException} is raised + * + * @param ex {@link AppException} exception raised + * @param request from frontend + * @return a {@link ProblemJson} as response with the cause and with an appropriated HTTP status + */ + @ExceptionHandler({AppException.class}) + public ResponseEntity handleAppException( + final AppException ex, final WebRequest request) { + if (ex.getCause() != null) { + log.warn( + "App Exception raised: " + ex.getMessage() + "\nCause of the App Exception: ", + ex.getCause()); + } else { + log.warn("App Exception raised: {}", ex.getMessage()); + log.debug("Trace error: ", ex); + } + var errorResponse = + ProblemJson.builder() + .status(ex.getHttpStatus().value()) + .title(ex.getTitle()) + .detail(ex.getMessage()) + .build(); + return new ResponseEntity<>(errorResponse, ex.getHttpStatus()); + } + + /** + * Handle if a {@link Exception} is raised + * + * @param ex {@link Exception} exception raised + * @param request from frontend + * @return a {@link ProblemJson} as response with the cause and with 500 as HTTP status + */ + @ExceptionHandler({Exception.class}) + public ResponseEntity handleGenericException( + final Exception ex, final WebRequest request) { + log.error("Generic Exception raised:", ex); + var errorResponse = + ProblemJson.builder() + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .title(INTERNAL_SERVER_ERROR) + .detail(ex.getMessage()) + .build(); + return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/model/AppCorsConfiguration.java b/src/main/java/it/gov/pagopa/standintechsupport/model/AppCorsConfiguration.java new file mode 100644 index 0000000..12f7139 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/model/AppCorsConfiguration.java @@ -0,0 +1,23 @@ +package it.gov.pagopa.standintechsupport.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Data +@Builder(toBuilder = true) +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@ToString +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class AppCorsConfiguration { + + private String[] origins; + private String[] methods; +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/model/AppInfo.java b/src/main/java/it/gov/pagopa/standintechsupport/model/AppInfo.java new file mode 100644 index 0000000..16b0b1a --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/model/AppInfo.java @@ -0,0 +1,22 @@ +package it.gov.pagopa.standintechsupport.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Data +@Builder(toBuilder = true) +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@ToString +@JsonIgnoreProperties(ignoreUnknown = true) +public class AppInfo { + + private String name; + private String version; + private String environment; +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/model/DateRequest.java b/src/main/java/it/gov/pagopa/standintechsupport/model/DateRequest.java new file mode 100644 index 0000000..35fd5dd --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/model/DateRequest.java @@ -0,0 +1,16 @@ +package it.gov.pagopa.standintechsupport.model; + +import lombok.*; + +import java.time.LocalDate; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class DateRequest { + + private LocalDate from; + private LocalDate to; +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/model/ProblemJson.java b/src/main/java/it/gov/pagopa/standintechsupport/model/ProblemJson.java new file mode 100644 index 0000000..ae2e46f --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/model/ProblemJson.java @@ -0,0 +1,49 @@ +package it.gov.pagopa.standintechsupport.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * Object returned as response in case of an error. + * + *

See {@link it.pagopa.microservice.exception.ErrorHandler} + */ +@Data +@Builder(toBuilder = true) +@NoArgsConstructor +@AllArgsConstructor +@ToString +@JsonIgnoreProperties(ignoreUnknown = true) +public class ProblemJson { + + @JsonProperty("title") + @Schema( + description = + "A short, summary of the problem type. Written in english and readable for engineers" + + " (usually not suited for non technical stakeholders and not localized); example:" + + " Service Unavailable") + private String title; + + @JsonProperty("status") + @Schema( + example = "200", + description = + "The HTTP status code generated by the origin server for this occurrence of the problem.") + @Min(100) + @Max(600) + private Integer status; + + @JsonProperty("detail") + @Schema( + example = "There was an error processing the request", + description = "A human readable explanation specific to this occurrence of the problem.") + private String detail; +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/BlacklistStationsRepository.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/BlacklistStationsRepository.java new file mode 100644 index 0000000..ace3ee7 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/repository/BlacklistStationsRepository.java @@ -0,0 +1,15 @@ +package it.gov.pagopa.standintechsupport.repository; + +import it.gov.pagopa.standintechsupport.repository.entity.BlacklistStation; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface BlacklistStationsRepository extends JpaRepository { + + @Query("select station from BlacklistStation") + public List findAllStations(); +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosEventsRepository.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosEventsRepository.java new file mode 100644 index 0000000..e42fc46 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosEventsRepository.java @@ -0,0 +1,87 @@ +package it.gov.pagopa.standintechsupport.repository; + +import com.azure.cosmos.CosmosClient; +import com.azure.cosmos.CosmosContainer; +import com.azure.cosmos.models.*; +import com.azure.cosmos.util.CosmosPagedIterable; +import it.gov.pagopa.standintechsupport.repository.model.CosmosEvent; +import java.time.Instant; +import java.time.LocalDate; +import java.util.*; + +import it.gov.pagopa.standintechsupport.util.Util; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class CosmosEventsRepository { + + @Autowired private CosmosClient cosmosClient; + + @Value("${cosmos.db.name}") + private String dbname; + + public static String tablename = "events"; + + private CosmosPagedIterable query(SqlQuerySpec query) { + log.info("executing query:" + query.getQueryText()); + CosmosContainer container = cosmosClient.getDatabase(dbname).getContainer(tablename); + return container.queryItems(query, new CosmosQueryRequestOptions(), CosmosEvent.class); + } + + public void newEvent(String type, String info) { + save( + CosmosEvent.builder() + .id(UUID.randomUUID().toString()) + .timestamp(Instant.now()) + .info(info) + .type(type) + .build()); + } + + public CosmosItemResponse save(CosmosEvent item) { + CosmosContainer container = cosmosClient.getDatabase(dbname).getContainer(tablename); + return container.createItem(item); + } + + public List find( + Optional station, + LocalDate dateFrom, + LocalDate dateTo) { + List paramList; + if(station.isEmpty()){ + paramList = Arrays.asList( + new SqlParameter("@from", Util.format(dateFrom)), + new SqlParameter("@to", Util.format(dateTo.plusDays(1))) + ); + }else{ + paramList = Arrays.asList( + new SqlParameter("@from", Util.format(dateFrom)), + new SqlParameter("@to", Util.format(dateTo.plusDays(1))), + new SqlParameter("@station",station.get()) + ); + } + + SqlQuerySpec q = + new SqlQuerySpec( + "SELECT * FROM c where c.PartitionKey >= @from and c.PartitionKey < @to" + + station.map(pt->" and c.station = @station").orElse("") + + " order by c.timestamp desc" + ) + .setParameters(paramList); + String continuationToken = null; + List results = new ArrayList<>(); + do{ + Iterable> feedResponses = query(q).iterableByPage(continuationToken,100); + for (FeedResponse page : feedResponses) { + results.addAll(page.getResults()); + continuationToken = page.getContinuationToken(); + } + }while (continuationToken!=null); + + return results; + } +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosNodeDataRepository.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosNodeDataRepository.java new file mode 100644 index 0000000..5fe3981 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosNodeDataRepository.java @@ -0,0 +1,59 @@ +//package it.gov.pagopa.standintechsupport.repository; +// +//import com.azure.cosmos.CosmosClient; +//import com.azure.cosmos.CosmosContainer; +//import com.azure.cosmos.models.*; +//import com.azure.cosmos.util.CosmosPagedIterable; +//import it.gov.pagopa.standintechsupport.repository.model.CosmosNodeCallCounts; +//import java.time.ZonedDateTime; +//import java.util.ArrayList; +//import java.util.Arrays; +//import java.util.List; +//import java.util.stream.Collectors; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.stereotype.Component; +// +//@Slf4j +//@Component +//public class CosmosNodeDataRepository { +// +// @Autowired private CosmosClient cosmosClient; +// +// @Value("${cosmos.db.name}") +// private String dbname; +// +// public static String tablename = "node_data"; +// +// private CosmosPagedIterable query(SqlQuerySpec query) { +// log.info("executing query:" + query.getQueryText()); +// CosmosContainer container = cosmosClient.getDatabase(dbname).getContainer(tablename); +// return container.queryItems(query, new CosmosQueryRequestOptions(), CosmosNodeCallCounts.class); +// } +// +// public Iterable> saveAll(List items) { +// CosmosContainer container = cosmosClient.getDatabase(dbname).getContainer(tablename); +// List cosmosItemOperationStream = +// items.stream() +// .map( +// s -> +// CosmosBulkOperations.getCreateItemOperation( +// s, new PartitionKey(s.getPartitionKey()))) +// .collect(Collectors.toList()); +// return container.executeBulkOperations(cosmosItemOperationStream); +// } +// +// public CosmosItemResponse save(CosmosNodeCallCounts item) { +// CosmosContainer container = cosmosClient.getDatabase(dbname).getContainer(tablename); +// return container.createItem(item); +// } +// +// public List getStationCounts(ZonedDateTime dateFrom) { +// List paramList = new ArrayList<>(); +// paramList.addAll(Arrays.asList(new SqlParameter("@from", dateFrom.toInstant()))); +// SqlQuerySpec q = +// new SqlQuerySpec("SELECT * FROM c where c.timestamp >= @from").setParameters(paramList); +// return query(q).stream().collect(Collectors.toList()); +// } +//} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosStationDataRepository.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosStationDataRepository.java new file mode 100644 index 0000000..d10d2f7 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosStationDataRepository.java @@ -0,0 +1,51 @@ +//package it.gov.pagopa.standintechsupport.repository; +// +//import com.azure.cosmos.CosmosClient; +//import com.azure.cosmos.CosmosContainer; +//import com.azure.cosmos.models.CosmosItemResponse; +//import com.azure.cosmos.models.CosmosQueryRequestOptions; +//import com.azure.cosmos.models.SqlParameter; +//import com.azure.cosmos.models.SqlQuerySpec; +//import com.azure.cosmos.util.CosmosPagedIterable; +//import it.gov.pagopa.standintechsupport.repository.model.CosmosForwarderCallCounts; +//import java.time.ZonedDateTime; +//import java.util.ArrayList; +//import java.util.Arrays; +//import java.util.List; +//import java.util.stream.Collectors; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.stereotype.Component; +// +//@Slf4j +//@Component +//public class CosmosStationDataRepository { +// +// @Autowired private CosmosClient cosmosClient; +// +// @Value("${cosmos.db.name}") +// private String dbname; +// +// public static String tablename = "station_data"; +// +// private CosmosPagedIterable query(SqlQuerySpec query) { +// log.info("executing query:" + query.getQueryText()); +// CosmosContainer container = cosmosClient.getDatabase(dbname).getContainer(tablename); +// return container.queryItems( +// query, new CosmosQueryRequestOptions(), CosmosForwarderCallCounts.class); +// } +// +// public CosmosItemResponse save(CosmosForwarderCallCounts item) { +// CosmosContainer container = cosmosClient.getDatabase(dbname).getContainer(tablename); +// return container.createItem(item); +// } +// +// public List getStationCounts(ZonedDateTime dateFrom) { +// List paramList = new ArrayList<>(); +// paramList.addAll(Arrays.asList(new SqlParameter("@from", dateFrom.toInstant()))); +// SqlQuerySpec q = +// new SqlQuerySpec("SELECT * FROM c where c.timestamp >= @from").setParameters(paramList); +// return query(q).stream().collect(Collectors.toList()); +// } +//} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosStationRepository.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosStationRepository.java new file mode 100644 index 0000000..84cbfc3 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosStationRepository.java @@ -0,0 +1,70 @@ +package it.gov.pagopa.standintechsupport.repository; + +import com.azure.cosmos.CosmosClient; +import com.azure.cosmos.CosmosContainer; +import com.azure.cosmos.models.*; +import com.azure.cosmos.util.CosmosPagedIterable; +import it.gov.pagopa.standintechsupport.repository.model.CosmosStandInStation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class CosmosStationRepository { + + @Autowired private CosmosClient cosmosClient; + + @Value("${cosmos.db.name}") + private String dbname; + + public static String tablename = "stand_in_stations"; + + private CosmosPagedIterable query(SqlQuerySpec query) { + log.info("executing query:" + query.getQueryText()); + CosmosContainer container = cosmosClient.getDatabase(dbname).getContainer(tablename); + return container.queryItems( + query, new CosmosQueryRequestOptions(), CosmosStandInStation.class); + } + + private void delete(CosmosStandInStation station) { + log.info("deleting station:" + station.getStation()); + CosmosContainer container = cosmosClient.getDatabase(dbname).getContainer(tablename); + container.deleteItem(station,new CosmosItemRequestOptions()); + } + + public CosmosItemResponse save(CosmosStandInStation item) { + CosmosContainer container = cosmosClient.getDatabase(dbname).getContainer(tablename); + return container.createItem(item); + } + + public List getStations() { + SqlQuerySpec q = new SqlQuerySpec("SELECT * FROM c"); + return query(q).stream().collect(Collectors.toList()); + } + + public List getStation(String station) { + SqlQuerySpec q = new SqlQuerySpec("SELECT * FROM c where c.station = @station"); + List paramList = new ArrayList<>(); + paramList.addAll(Arrays.asList( + new SqlParameter("@station", station) + )); + return query(q.setParameters(paramList)).stream().collect(Collectors.toList()); + } + + public Boolean removeStation(CosmosStandInStation station) { + try{ + delete(station); + return true; + }catch (Exception e){ + log.error("error removing station",e); + } + return false; + } +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/StandInStationsRepository.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/StandInStationsRepository.java new file mode 100644 index 0000000..467fa7c --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/repository/StandInStationsRepository.java @@ -0,0 +1,14 @@ +package it.gov.pagopa.standintechsupport.repository; + +import it.gov.pagopa.standintechsupport.repository.entity.StandInStation; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface StandInStationsRepository extends JpaRepository { + @Query("select station from StandInStation") + public List findAllStations(); +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/entity/BlacklistStation.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/entity/BlacklistStation.java new file mode 100644 index 0000000..5be6ca2 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/repository/entity/BlacklistStation.java @@ -0,0 +1,17 @@ +package it.gov.pagopa.standintechsupport.repository.entity; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "STAND_IN_STATIONS_BLACKLIST") +@Data +public class BlacklistStation { + @Id + @Column(name = "STATION_CODE") + private String station; +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/entity/StandInStation.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/entity/StandInStation.java new file mode 100644 index 0000000..48d8f6a --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/repository/entity/StandInStation.java @@ -0,0 +1,21 @@ +package it.gov.pagopa.standintechsupport.repository.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "STAND_IN_STATIONS") +@Data +@AllArgsConstructor +@NoArgsConstructor +public class StandInStation { + @Id + @Column(name = "STATION_CODE") + private String station; +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosEvent.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosEvent.java new file mode 100644 index 0000000..5931fc3 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosEvent.java @@ -0,0 +1,25 @@ +package it.gov.pagopa.standintechsupport.repository.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.Instant; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CosmosEvent { + private String id; + private String station; + private Instant timestamp; + private String type; + private String info; + + @JsonProperty("PartitionKey") + public String getPartitionKey() { + return timestamp.toString().substring(0, 10); + } +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosForwarderCallCounts.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosForwarderCallCounts.java new file mode 100644 index 0000000..42033a6 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosForwarderCallCounts.java @@ -0,0 +1,24 @@ +package it.gov.pagopa.standintechsupport.repository.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.Instant; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CosmosForwarderCallCounts { + private String id; + private String station; + private Instant timestamp; + private Boolean outcome; + + @JsonProperty("PartitionKey") + public String getPartitionKey() { + return timestamp.toString().substring(0, 10); + } +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosNodeCallCounts.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosNodeCallCounts.java new file mode 100644 index 0000000..6b435c9 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosNodeCallCounts.java @@ -0,0 +1,29 @@ +package it.gov.pagopa.standintechsupport.repository.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.Instant; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CosmosNodeCallCounts { + private String id; + private String station; + private Instant timestamp; + private Integer total; + private Integer faults; + + @JsonProperty("PartitionKey") + public String getPartitionKey() { + return timestamp.toString().substring(0, 10); + } + + public double getPerc() { + return ((getFaults() / (double) getTotal()) * 100); + } +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosStandInStation.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosStandInStation.java new file mode 100644 index 0000000..c89afdd --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosStandInStation.java @@ -0,0 +1,23 @@ +package it.gov.pagopa.standintechsupport.repository.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CosmosStandInStation { + private String id; + private String station; + private Instant timestamp; + @JsonProperty("PartitionKey") + public String getPartitionKey() { + return timestamp.toString().substring(0, 10); + } +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/service/SupportService.java b/src/main/java/it/gov/pagopa/standintechsupport/service/SupportService.java new file mode 100644 index 0000000..833fde1 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/service/SupportService.java @@ -0,0 +1,84 @@ +package it.gov.pagopa.standintechsupport.service; + +import it.gov.pagopa.standintechsupport.controller.model.CosmosEventModel; +import it.gov.pagopa.standintechsupport.controller.model.ResponseContainer; +import it.gov.pagopa.standintechsupport.controller.model.StandInStation; +import it.gov.pagopa.standintechsupport.exception.AppError; +import it.gov.pagopa.standintechsupport.exception.AppException; +import it.gov.pagopa.standintechsupport.model.DateRequest; +import it.gov.pagopa.standintechsupport.repository.BlacklistStationsRepository; +import it.gov.pagopa.standintechsupport.repository.CosmosEventsRepository; +import it.gov.pagopa.standintechsupport.repository.CosmosStationRepository; +import it.gov.pagopa.standintechsupport.repository.StandInStationsRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class SupportService { + + @Value("#{T(java.lang.Integer).parseInt('${date-range-limit}')}") + Integer dateRangeLimit; + + @Autowired + private BlacklistStationsRepository blacklistStationsRepository; + @Autowired + private CosmosStationRepository standInStationsRepository; + @Autowired + private CosmosEventsRepository cosmosEventsRepository; + + public ResponseContainer getEvents(Optional station, LocalDate from, LocalDate to) { + DateRequest dateRequest = verifyDate(from, to); + List collect = cosmosEventsRepository.find(station, dateRequest.getFrom(), dateRequest.getTo()).stream().map(ee -> { + return CosmosEventModel.builder() + .id(ee.getId()) + .type(ee.getType()) + .info(ee.getInfo()) + .timestamp(ee.getTimestamp()) + .station(ee.getStation()).build(); + }).collect(Collectors.toList()); + return ResponseContainer.builder().count(collect.size()) + .data(collect) + .dateFrom(dateRequest.getFrom()) + .dateTo(dateRequest.getTo()).build(); + } + + public List getStations() { + return standInStationsRepository.getStations().stream().map(s->StandInStation.builder().station(s.getStation()).timestamp(s.getTimestamp()).build()).collect(Collectors.toList()); + } + + public List getBlacklist() { + return blacklistStationsRepository.findAllStations(); + } + + private DateRequest verifyDate(LocalDate dateFrom, LocalDate dateTo) { + if (dateFrom == null && dateTo != null || dateFrom != null && dateTo == null) { + throw new AppException( + AppError.BAD_REQUEST, + "Date from and date to must be both defined"); + } else if (dateFrom != null && dateTo != null && dateFrom.isAfter(dateTo)) { + throw new AppException( + AppError.BAD_REQUEST, + "Date from must be before date to"); + } + if (dateFrom == null && dateTo == null) { + dateTo = LocalDate.now(); + dateFrom = dateTo.minusDays(dateRangeLimit); + } + if (ChronoUnit.DAYS.between(dateFrom, dateTo) > dateRangeLimit) { + throw new AppException( + AppError.INTERVAL_TOO_LARGE, + dateRangeLimit); + } + return DateRequest.builder().from(dateFrom).to(dateTo).build(); + } + +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/util/Constants.java b/src/main/java/it/gov/pagopa/standintechsupport/util/Constants.java new file mode 100644 index 0000000..c5375e1 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/util/Constants.java @@ -0,0 +1,15 @@ +package it.gov.pagopa.standintechsupport.util; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public class Constants { + + public static final String HEADER_REQUEST_ID = "X-Request-Id"; + + public static final String EVENT_FORWARDER_CALL = "FORWARDER_CALL"; + public static final String EVENT_FORWARDER_CALL_RESP_SUCCCESS = "FORWARDER_CALL_RESP_SUCCESS"; + public static final String EVENT_FORWARDER_CALL_RESP_ERROR = "FORWARDER_CALL_RESP_ERROR"; + public static final String EVENT_ADD_TO_STANDIN = "ADD_TO_STANDIN"; + public static final String EVENT_REMOVE_FROM_STANDIN = "REMOVE_FROM_STANDIN"; +} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/util/Util.java b/src/main/java/it/gov/pagopa/standintechsupport/util/Util.java new file mode 100644 index 0000000..005bc04 --- /dev/null +++ b/src/main/java/it/gov/pagopa/standintechsupport/util/Util.java @@ -0,0 +1,31 @@ +package it.gov.pagopa.standintechsupport.util; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.function.Function; + +public class Util { + public static String ifNotNull(Object o, String s) { + if (o != null) { + return s; + } else { + return ""; + } + } + + public static void ifNotNull(Object o, Function func) { + if (o != null) { + func.apply(null); + } + } + + public static Long toMillis(LocalDateTime d) { + return d.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + } + + public static String format(LocalDate d) { + return d.format(DateTimeFormatter.ISO_DATE); + } +} diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties index 1e89894..caea635 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -2,6 +2,22 @@ info.properties.environment=local # Logging logging.level.root=INFO -logging.level.it.gov.pagopa=DEBUG +logging.level.it.gov.pagopa=INFO # CORS configuration cors.configuration={"origins": ["*"], "methods": ["*"]} + +spring.datasource.url=jdbc:postgresql://pagopa-d-weu-nodo-flexible-postgresql.postgres.database.azure.com:6432/nodo?prepareThreshold=0 +spring.datasource.username=cfg +spring.datasource.password=password + +api-config-cache.base-path= +api-config-cache.api-key= + +cosmos.endpoint= +cosmos.key= + +logging.level.it.gov.pagopa.standinmanager.service=DEBUG + +date-range-limit=100 +aws.region=weu +aws.ses.user=noreply@pagopa.it \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 5ad4771..3a8ab8f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -14,8 +14,7 @@ management.health.readinessState.enabled=true springdoc.writer-with-order-by-keys=true springdoc.writer-with-default-pretty-printer=true # Server -# TODO: set your base path -server.servlet.context-path=/ +server.servlet.context-path=/ server.port=8080 # Logging logging.level.root=${DEFAULT_LOGGING_LEVEL:INFO} @@ -23,3 +22,8 @@ logging.level.it.gov.pagopa=${APP_LOGGING_LEVEL:INFO} # CORS configuration cors.configuration=${CORS_CONFIGURATION:'{"origins": ["*"], "methods": ["*"]}'} +cosmos.endpoint=https://localhost:8081 +cosmos.key=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw== +cosmos.db.name=standin + +date-range-limit=10 \ No newline at end of file diff --git a/src/main/resources/openapi_config.json b/src/main/resources/openapi_config.json new file mode 100644 index 0000000..1e7a554 --- /dev/null +++ b/src/main/resources/openapi_config.json @@ -0,0 +1,1039 @@ +{ + "openapi" : "3.0.1", + "info" : { + "title" : "API-Config Cacher", + "description" : "Generate cache for regarding Nodo dei Pagamenti configuration", + "termsOfService" : "https://www.pagopa.gov.it/", + "version" : "0.8.1" + }, + "servers" : [ { + "url" : "http://localhost:8080" + }, { + "url" : "https://{host}/{basePath}", + "variables" : { + "host" : { + "default" : "api.dev.platform.pagopa.it", + "enum" : [ "api.dev.platform.pagopa.it", "api.uat.platform.pagopa.it", "api.platform.pagopa.it" ] + }, + "basePath" : { + "default" : "api-config-cache/o/v1", + "enum" : [ "api-config-cache/o/v1", "api-config-cache/p/v1", "api-config-cache/odev/v1" ] + } + } + } ], + "paths" : { + "/stakeholders/node/cache/schemas/v1" : { + "get" : { + "tags" : [ "Cache" ], + "summary" : "Get selected key of cache v1 config", + "operationId" : "cache", + "parameters" : [ { + "name" : "refresh", + "in" : "query", + "description" : "to force the refresh of the cache", + "required" : false, + "schema" : { + "type" : "boolean" + } + }, { + "name" : "keys", + "in" : "query", + "required" : false, + "schema" : { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "creditorInstitutions", "creditorInstitutionBrokers", "stations", "creditorInstitutionStations", "encodings", "creditorInstitutionEncodings", "ibans", "creditorInstitutionInformations", "psps", "pspBrokers", "paymentTypes", "pspChannelPaymentTypes", "plugins", "pspInformationTemplates", "pspInformations", "channels", "cdsServices", "cdsSubjects", "cdsSubjectServices", "cdsCategories", "configurations", "ftpServers", "languages", "gdeConfigurations", "metadataDict" ] + } + } + } ], + "responses" : { + "403" : { + "description" : "Forbidden" + }, + "200" : { + "description" : "OK", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ConfigDataV1" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ProblemJson" + } + } + } + }, + "429" : { + "description" : "Too many requests" + }, + "401" : { + "description" : "Unauthorized" + }, + "500" : { + "description" : "Service unavailable", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ProblemJson" + } + } + } + } + }, + "security" : [ { + "ApiKey" : [ ] + } ] + } + }, + "/stakeholders/node/cache/schemas/v1/id" : { + "get" : { + "tags" : [ "Cache" ], + "summary" : "Get last v1 cache version", + "operationId" : "idV1", + "responses" : { + "404" : { + "description" : "Not Found" + }, + "403" : { + "description" : "Forbidden" + }, + "400" : { + "description" : "Bad Request", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ProblemJson" + } + } + } + }, + "429" : { + "description" : "Too many requests" + }, + "200" : { + "description" : "OK", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/CacheVersion" + } + } + } + }, + "401" : { + "description" : "Unauthorized" + }, + "500" : { + "description" : "Service unavailable", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ProblemJson" + } + } + } + } + }, + "security" : [ { + "ApiKey" : [ ] + } ] + } + } + }, + "components" : { + "schemas" : { + "BrokerCreditorInstitution" : { + "required" : [ "broker_code", "enabled", "extended_fault_bean" ], + "type" : "object", + "properties" : { + "broker_code" : { + "type" : "string" + }, + "enabled" : { + "type" : "boolean" + }, + "description" : { + "type" : "string" + }, + "extended_fault_bean" : { + "type" : "boolean" + } + } + }, + "BrokerPsp" : { + "required" : [ "broker_psp_code", "enabled", "extended_fault_bean" ], + "type" : "object", + "properties" : { + "broker_psp_code" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "enabled" : { + "type" : "boolean" + }, + "extended_fault_bean" : { + "type" : "boolean" + } + } + }, + "CdsCategory" : { + "required" : [ "description" ], + "type" : "object", + "properties" : { + "description" : { + "type" : "string" + } + } + }, + "CdsService" : { + "required" : [ "category", "description", "id", "reference_xsd", "version" ], + "type" : "object", + "properties" : { + "id" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "reference_xsd" : { + "type" : "string" + }, + "version" : { + "type" : "integer", + "format" : "int64" + }, + "category" : { + "type" : "string" + } + } + }, + "CdsSubject" : { + "required" : [ "creditor_institution_code", "creditor_institution_description" ], + "type" : "object", + "properties" : { + "creditor_institution_code" : { + "type" : "string" + }, + "creditor_institution_description" : { + "type" : "string" + } + } + }, + "CdsSubjectService" : { + "required" : [ "fee", "service", "start_date", "subject", "subject_service_id" ], + "type" : "object", + "properties" : { + "subject" : { + "type" : "string" + }, + "service" : { + "type" : "string" + }, + "subject_service_id" : { + "type" : "string" + }, + "start_date" : { + "type" : "string", + "format" : "date-time" + }, + "end_date" : { + "type" : "string", + "format" : "date-time" + }, + "fee" : { + "type" : "boolean" + }, + "station_code" : { + "type" : "string" + }, + "service_description" : { + "type" : "string" + } + } + }, + "Channel" : { + "required" : [ "agid", "broker_psp_code", "channel_code", "connection", "digital_stamp", "enabled", "flag_io", "flag_travaso", "new_fault_code", "password", "payment_model", "primitive_version", "recovery", "redirect", "rt_push", "thread_number", "timeouts" ], + "type" : "object", + "properties" : { + "channel_code" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "enabled" : { + "type" : "boolean" + }, + "password" : { + "type" : "string" + }, + "connection" : { + "$ref" : "#/components/schemas/Connection" + }, + "broker_psp_code" : { + "type" : "string" + }, + "proxy" : { + "$ref" : "#/components/schemas/Proxy" + }, + "service" : { + "$ref" : "#/components/schemas/Service" + }, + "service_nmp" : { + "$ref" : "#/components/schemas/Service" + }, + "thread_number" : { + "type" : "integer", + "format" : "int64" + }, + "timeouts" : { + "$ref" : "#/components/schemas/Timeouts" + }, + "new_fault_code" : { + "type" : "boolean" + }, + "redirect" : { + "$ref" : "#/components/schemas/Redirect" + }, + "payment_model" : { + "type" : "string" + }, + "serv_plugin" : { + "type" : "string" + }, + "rt_push" : { + "type" : "boolean" + }, + "recovery" : { + "type" : "boolean" + }, + "digital_stamp" : { + "type" : "boolean" + }, + "flag_io" : { + "type" : "boolean" + }, + "agid" : { + "type" : "boolean" + }, + "primitive_version" : { + "type" : "integer", + "format" : "int32" + }, + "flag_travaso" : { + "type" : "boolean" + } + } + }, + "ConfigDataV1" : { + "required" : [ "cdsCategories", "cdsServices", "cdsSubjectServices", "cdsSubjects", "channels", "configurations", "creditorInstitutionBrokers", "creditorInstitutionEncodings", "creditorInstitutionInformations", "creditorInstitutionStations", "creditorInstitutions", "encodings", "ftpServers", "gdeConfigurations", "ibans", "languages", "metadataDict", "paymentTypes", "plugins", "pspBrokers", "pspChannelPaymentTypes", "pspInformationTemplates", "pspInformations", "psps", "stations", "version" ], + "type" : "object", + "properties" : { + "version" : { + "type" : "string" + }, + "creditorInstitutions" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/CreditorInstitution" + } + }, + "creditorInstitutionBrokers" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/BrokerCreditorInstitution" + } + }, + "stations" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/Station" + } + }, + "creditorInstitutionStations" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/StationCreditorInstitution" + } + }, + "encodings" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/Encoding" + } + }, + "creditorInstitutionEncodings" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/CreditorInstitutionEncoding" + } + }, + "ibans" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/Iban" + } + }, + "creditorInstitutionInformations" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/CreditorInstitutionInformation" + } + }, + "psps" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/PaymentServiceProvider" + } + }, + "pspBrokers" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/BrokerPsp" + } + }, + "paymentTypes" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/PaymentType" + } + }, + "pspChannelPaymentTypes" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/PspChannelPaymentType" + } + }, + "plugins" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/Plugin" + } + }, + "pspInformationTemplates" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/PspInformation" + } + }, + "pspInformations" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/PspInformation" + } + }, + "channels" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/Channel" + } + }, + "cdsServices" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/CdsService" + } + }, + "cdsSubjects" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/CdsSubject" + } + }, + "cdsSubjectServices" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/CdsSubjectService" + } + }, + "cdsCategories" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/CdsCategory" + } + }, + "configurations" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/ConfigurationKey" + } + }, + "ftpServers" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/FtpServer" + } + }, + "languages" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "gdeConfigurations" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/GdeConfiguration" + } + }, + "metadataDict" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/MetadataDict" + } + } + } + }, + "ConfigurationKey" : { + "required" : [ "category", "key", "value" ], + "type" : "object", + "properties" : { + "category" : { + "type" : "string" + }, + "key" : { + "type" : "string" + }, + "value" : { + "type" : "string" + }, + "description" : { + "type" : "string" + } + } + }, + "Connection" : { + "required" : [ "ip", "port", "protocol" ], + "type" : "object", + "properties" : { + "protocol" : { + "type" : "string", + "enum" : [ "HTTPS", "HTTP" ] + }, + "ip" : { + "type" : "string" + }, + "port" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "CreditorInstitution" : { + "required" : [ "creditor_institution_code", "enabled", "psp_payment", "reporting_ftp", "reporting_zip" ], + "type" : "object", + "properties" : { + "creditor_institution_code" : { + "type" : "string" + }, + "enabled" : { + "type" : "boolean" + }, + "business_name" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "address" : { + "$ref" : "#/components/schemas/CreditorInstitutionAddress" + }, + "psp_payment" : { + "type" : "boolean" + }, + "reporting_ftp" : { + "type" : "boolean" + }, + "reporting_zip" : { + "type" : "boolean" + } + } + }, + "CreditorInstitutionAddress" : { + "type" : "object", + "properties" : { + "location" : { + "type" : "string" + }, + "city" : { + "type" : "string" + }, + "zip_code" : { + "type" : "string" + }, + "country_code" : { + "type" : "string" + }, + "tax_domicile" : { + "type" : "string" + } + } + }, + "CreditorInstitutionEncoding" : { + "required" : [ "code_type", "creditor_institution_code", "encoding_code" ], + "type" : "object", + "properties" : { + "code_type" : { + "type" : "string" + }, + "encoding_code" : { + "type" : "string" + }, + "creditor_institution_code" : { + "type" : "string" + } + } + }, + "CreditorInstitutionInformation" : { + "required" : [ "informativa" ], + "type" : "object", + "properties" : { + "informativa" : { + "type" : "string" + } + } + }, + "Encoding" : { + "required" : [ "code_type", "description" ], + "type" : "object", + "properties" : { + "code_type" : { + "type" : "string" + }, + "description" : { + "type" : "string" + } + } + }, + "FtpServer" : { + "required" : [ "enabled", "history_path", "host", "id", "in_path", "out_path", "password", "port", "root_path", "service", "type", "username" ], + "type" : "object", + "properties" : { + "host" : { + "type" : "string" + }, + "port" : { + "type" : "integer", + "format" : "int32" + }, + "enabled" : { + "type" : "boolean" + }, + "username" : { + "type" : "string" + }, + "password" : { + "type" : "string" + }, + "root_path" : { + "type" : "string" + }, + "service" : { + "type" : "string" + }, + "type" : { + "type" : "string" + }, + "in_path" : { + "type" : "string" + }, + "out_path" : { + "type" : "string" + }, + "history_path" : { + "type" : "string" + }, + "id" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "GdeConfiguration" : { + "required" : [ "event_hub_enabled", "event_hub_payload_enabled", "primitive", "type" ], + "type" : "object", + "properties" : { + "primitive" : { + "type" : "string" + }, + "type" : { + "type" : "string" + }, + "event_hub_enabled" : { + "type" : "boolean" + }, + "event_hub_payload_enabled" : { + "type" : "boolean" + } + } + }, + "Iban" : { + "required" : [ "creditor_institution_code", "iban", "publication_date", "validity_date" ], + "type" : "object", + "properties" : { + "iban" : { + "type" : "string" + }, + "creditor_institution_code" : { + "type" : "string" + }, + "validity_date" : { + "type" : "string", + "format" : "date-time" + }, + "publication_date" : { + "type" : "string", + "format" : "date-time" + }, + "shop_id" : { + "type" : "string" + }, + "seller_bank_id" : { + "type" : "string" + }, + "avvio_key" : { + "type" : "string" + }, + "esito_key" : { + "type" : "string" + } + } + }, + "MetadataDict" : { + "required" : [ "key", "start_date" ], + "type" : "object", + "properties" : { + "key" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "start_date" : { + "type" : "string", + "format" : "date-time" + }, + "end_date" : { + "type" : "string", + "format" : "date-time" + } + } + }, + "PaymentServiceProvider" : { + "required" : [ "agid_psp", "digital_stamp", "enabled", "psp_code" ], + "type" : "object", + "properties" : { + "psp_code" : { + "type" : "string" + }, + "enabled" : { + "type" : "boolean" + }, + "description" : { + "type" : "string" + }, + "business_name" : { + "type" : "string" + }, + "abi" : { + "type" : "string" + }, + "bic" : { + "type" : "string" + }, + "my_bank_code" : { + "type" : "string" + }, + "digital_stamp" : { + "type" : "boolean" + }, + "agid_psp" : { + "type" : "boolean" + }, + "tax_code" : { + "type" : "string" + }, + "vat_number" : { + "type" : "string" + } + } + }, + "PaymentType" : { + "required" : [ "payment_type" ], + "type" : "object", + "properties" : { + "payment_type" : { + "type" : "string" + }, + "description" : { + "type" : "string" + } + } + }, + "Plugin" : { + "required" : [ "id_serv_plugin" ], + "type" : "object", + "properties" : { + "id_serv_plugin" : { + "type" : "string" + }, + "pag_const_string_profile" : { + "type" : "string" + }, + "pag_soap_rule_profile" : { + "type" : "string" + }, + "pag_rpt_xpath_profile" : { + "type" : "string" + }, + "id_bean" : { + "type" : "string" + } + } + }, + "Proxy" : { + "type" : "object", + "properties" : { + "proxy_host" : { + "type" : "string" + }, + "proxy_port" : { + "type" : "integer", + "format" : "int64" + }, + "proxy_username" : { + "type" : "string" + }, + "proxy_password" : { + "type" : "string" + } + } + }, + "PspChannelPaymentType" : { + "required" : [ "channel_code", "payment_type", "psp_code" ], + "type" : "object", + "properties" : { + "psp_code" : { + "type" : "string" + }, + "channel_code" : { + "type" : "string" + }, + "payment_type" : { + "type" : "string" + } + } + }, + "PspInformation" : { + "required" : [ "informativa" ], + "type" : "object", + "properties" : { + "informativa" : { + "type" : "string" + } + } + }, + "Redirect" : { + "type" : "object", + "properties" : { + "protocol" : { + "type" : "string", + "enum" : [ "HTTPS", "HTTP" ] + }, + "ip" : { + "type" : "string" + }, + "path" : { + "type" : "string" + }, + "port" : { + "type" : "integer", + "format" : "int64" + }, + "query_string" : { + "type" : "string" + } + } + }, + "Service" : { + "type" : "object", + "properties" : { + "path" : { + "type" : "string" + }, + "target_host" : { + "type" : "string" + }, + "target_port" : { + "type" : "integer", + "format" : "int64" + }, + "target_path" : { + "type" : "string" + } + } + }, + "Station" : { + "required" : [ "broker_code", "connection", "enabled", "invio_rt_istantaneo", "password", "primitive_version", "redirect", "station_code", "thread_number", "timeouts", "version" ], + "type" : "object", + "properties" : { + "station_code" : { + "type" : "string" + }, + "enabled" : { + "type" : "boolean" + }, + "version" : { + "type" : "integer", + "format" : "int64" + }, + "connection" : { + "$ref" : "#/components/schemas/Connection" + }, + "connection_mod4" : { + "$ref" : "#/components/schemas/Connection" + }, + "password" : { + "type" : "string" + }, + "redirect" : { + "$ref" : "#/components/schemas/Redirect" + }, + "service" : { + "$ref" : "#/components/schemas/Service" + }, + "service_pof" : { + "$ref" : "#/components/schemas/Service" + }, + "service_mod4" : { + "$ref" : "#/components/schemas/Service" + }, + "broker_code" : { + "type" : "string" + }, + "proxy" : { + "$ref" : "#/components/schemas/Proxy" + }, + "thread_number" : { + "type" : "integer", + "format" : "int64" + }, + "timeouts" : { + "$ref" : "#/components/schemas/Timeouts" + }, + "invio_rt_istantaneo" : { + "type" : "boolean" + }, + "primitive_version" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "StationCreditorInstitution" : { + "required" : [ "broadcast", "creditor_institution_code", "mod4", "primitive_version", "spontaneous_payment", "station_code" ], + "type" : "object", + "properties" : { + "creditor_institution_code" : { + "type" : "string" + }, + "station_code" : { + "type" : "string" + }, + "application_code" : { + "type" : "integer", + "format" : "int64" + }, + "aux_digit" : { + "type" : "integer", + "format" : "int64" + }, + "segregation_code" : { + "type" : "integer", + "format" : "int64" + }, + "mod4" : { + "type" : "boolean" + }, + "broadcast" : { + "type" : "boolean" + }, + "primitive_version" : { + "type" : "integer", + "format" : "int32" + }, + "spontaneous_payment" : { + "type" : "boolean" + } + } + }, + "Timeouts" : { + "required" : [ "timeout_a", "timeout_b", "timeout_c" ], + "type" : "object", + "properties" : { + "timeout_a" : { + "type" : "integer", + "format" : "int64" + }, + "timeout_b" : { + "type" : "integer", + "format" : "int64" + }, + "timeout_c" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "ProblemJson" : { + "type" : "object", + "properties" : { + "title" : { + "type" : "string", + "description" : "A short, summary of the problem type. Written in english and readable for engineers (usually not suited for non technical stakeholders and not localized); example: Service Unavailable" + }, + "status" : { + "maximum" : 600, + "minimum" : 100, + "type" : "integer", + "description" : "The HTTP status code generated by the origin server for this occurrence of the problem.", + "format" : "int32", + "example" : 200 + }, + "detail" : { + "type" : "string", + "description" : "A human readable explanation specific to this occurrence of the problem.", + "example" : "There was an error processing the request" + } + } + }, + "CacheVersion" : { + "required" : [ "version" ], + "type" : "object", + "properties" : { + "version" : { + "type" : "string" + } + } + } + }, + "securitySchemes" : { + "ApiKey" : { + "type" : "apiKey", + "description" : "The API key to access this function app.", + "name" : "Ocp-Apim-Subscription-Key", + "in" : "header" + } + } + } +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/standintechsupport/ApplicationTest.java b/src/test/java/it/gov/pagopa/standintechsupport/ApplicationTest.java new file mode 100644 index 0000000..2b43569 --- /dev/null +++ b/src/test/java/it/gov/pagopa/standintechsupport/ApplicationTest.java @@ -0,0 +1,16 @@ +package it.gov.pagopa.standintechsupport; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ApplicationTest { + + @Test + void contextLoads() { + // check only if the context is loaded + assertTrue(true); + } +} diff --git a/src/test/java/it/gov/pagopa/standintechsupport/OpenApiGenerationTest.java b/src/test/java/it/gov/pagopa/standintechsupport/OpenApiGenerationTest.java new file mode 100644 index 0000000..543cdd4 --- /dev/null +++ b/src/test/java/it/gov/pagopa/standintechsupport/OpenApiGenerationTest.java @@ -0,0 +1,47 @@ +package it.gov.pagopa.standintechsupport; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +@SpringBootTest(classes = Application.class) +@AutoConfigureMockMvc +class OpenApiGenerationTest { + + @Autowired ObjectMapper objectMapper; + + @Autowired private MockMvc mvc; + + @Test + void swaggerSpringPlugin() throws Exception { + mvc.perform(MockMvcRequestBuilders.get("/v3/api-docs").accept(MediaType.APPLICATION_JSON)) + .andExpect(MockMvcResultMatchers.status().is2xxSuccessful()) + .andDo( + (result) -> { + assertNotNull(result); + assertNotNull(result.getResponse()); + final String content = result.getResponse().getContentAsString(); + assertFalse(content.isBlank()); + assertFalse(content.contains("${"), "Generated swagger contains placeholders"); + Object swagger = + objectMapper.readValue(result.getResponse().getContentAsString(), Object.class); + String formatted = + objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(swagger); + Path basePath = Paths.get("openapi/"); + Files.createDirectories(basePath); + Files.write(basePath.resolve("openapi.json"), formatted.getBytes()); + }); + } +} From 8936b386448849cd668dc20bba224e7c6a8c87f0 Mon Sep 17 00:00:00 2001 From: Lorenzo Catalano Date: Mon, 15 Jan 2024 10:18:01 +0100 Subject: [PATCH 02/10] wip alpha --- .identity/03_github_environment.tf | 15 ++ .identity/99_variables.tf | 2 +- .identity/env/dev/backend.tfvars | 2 +- README.md | 2 +- TODO | 7 - helm/Chart.yaml | 8 +- helm/values-dev.yaml | 73 ++++--- helm/values-prod.yaml | 80 +++++--- helm/values-uat.yaml | 71 ++++--- pom.xml | 21 -- .../gov/pagopa/microservice/Application.java | 1 - .../microservice/config/LoggingAspect.java | 59 +++--- .../config/MappingsConfiguration.java | 2 - .../microservice/config/OpenApiConfig.java | 13 +- .../microservice/config/RequestFilter.java | 7 +- .../config/ResponseValidator.java | 10 +- .../config/WebMvcConfiguration.java | 11 +- .../controller/HomeController.java | 2 - .../microservice/exception/AppError.java | 8 +- .../microservice/exception/AppException.java | 46 ++--- .../microservice/exception/ErrorHandler.java | 189 ++++++++++-------- .../microservice/model/ProblemJson.java | 19 +- .../pagopa/microservice/util/Constants.java | 2 - .../standintechsupport/Application.java | 16 +- .../controller/SupportController.java | 68 ++++--- .../controller/model/CosmosEventModel.java | 4 +- .../controller/model/ResponseContainer.java | 16 +- .../controller/model/StandInStation.java | 4 +- .../exception/AppError.java | 3 +- .../standintechsupport/model/DateRequest.java | 3 +- .../BlacklistStationsRepository.java | 15 -- .../repository/CosmosEventsRepository.java | 44 ++-- .../repository/CosmosNodeDataRepository.java | 59 ------ .../CosmosStationDataRepository.java | 51 ----- .../repository/CosmosStationRepository.java | 24 +-- .../repository/StandInStationsRepository.java | 14 -- .../repository/entity/BlacklistStation.java | 17 -- .../repository/entity/StandInStation.java | 21 -- .../model/CosmosForwarderCallCounts.java | 24 --- .../model/CosmosNodeCallCounts.java | 29 --- .../model/CosmosStandInStation.java | 4 +- .../service/SupportService.java | 110 +++++----- .../standintechsupport/util/Constants.java | 7 - .../resources/application-local.properties | 22 +- src/main/resources/application.properties | 6 +- src/main/resources/logback-spring.xml | 6 +- .../pagopa/microservice/ApplicationTest.java | 16 -- .../microservice/OpenApiGenerationTest.java | 47 ----- 48 files changed, 499 insertions(+), 781 deletions(-) delete mode 100644 TODO delete mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/BlacklistStationsRepository.java delete mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosNodeDataRepository.java delete mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosStationDataRepository.java delete mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/StandInStationsRepository.java delete mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/entity/BlacklistStation.java delete mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/entity/StandInStation.java delete mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosForwarderCallCounts.java delete mode 100644 src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosNodeCallCounts.java delete mode 100644 src/test/java/it/gov/pagopa/microservice/ApplicationTest.java delete mode 100644 src/test/java/it/gov/pagopa/microservice/OpenApiGenerationTest.java diff --git a/.identity/03_github_environment.tf b/.identity/03_github_environment.tf index 0f94270..aac48a1 100644 --- a/.identity/03_github_environment.tf +++ b/.identity/03_github_environment.tf @@ -77,3 +77,18 @@ resource "github_actions_secret" "repo_secrets" { plaintext_value = each.value } +############ +## Labels ## +############ +resource "github_issue_label" "patch" { + repository = local.github.repository + name = "patch" + color = "FF0000" +} + +resource "github_issue_label" "ignore_for_release" { + repository = local.github.repository + name = "ignore-for-release" + color = "008000" +} + diff --git a/.identity/99_variables.tf b/.identity/99_variables.tf index 28da1dd..232ef7e 100644 --- a/.identity/99_variables.tf +++ b/.identity/99_variables.tf @@ -1,7 +1,7 @@ locals { github = { org = "pagopa" - repository = "pagopa-stand-in-manager" + repository = "pagopa-stand-in-technical-support" } prefix = "pagopa" diff --git a/.identity/env/dev/backend.tfvars b/.identity/env/dev/backend.tfvars index 3c60029..9572c01 100644 --- a/.identity/env/dev/backend.tfvars +++ b/.identity/env/dev/backend.tfvars @@ -1,4 +1,4 @@ resource_group_name = "io-infra-rg" storage_account_name = "pagopainfraterraformdev" container_name = "azurermstate" -key = "pagopa-stand-in-manager.tfstate" +key = "pagopa-stand-in-tech-support.tfstate" diff --git a/README.md b/README.md index 0a43881..4e0f850 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# StandIn Manager +# StandIn Tech Support API [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=TODO-set-your-id&metric=alert_status)](https://sonarcloud.io/dashboard?id=TODO-set-your-id) [![Integration Tests](https://github.com/pagopa//actions/workflows/integration_test.yml/badge.svg?branch=main)](https://github.com/pagopa//actions/workflows/integration_test.yml) diff --git a/TODO b/TODO deleted file mode 100644 index a17a670..0000000 --- a/TODO +++ /dev/null @@ -1,7 +0,0 @@ -documentazione - -notifica entrata uscita - - mail a elenco destinatari - -cache singola chiave + refresh totale - diff --git a/helm/Chart.yaml b/helm/Chart.yaml index 46d479c..1c29d48 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -1,10 +1,10 @@ apiVersion: v2 -name: pagopa-stand-in-technical-support -description: Microservice that handles calculation for pagoPA Advanced Fees Management +name: pagopa-stand-in-tech-support +description: Microservice that handles Stand-In tech support api type: application -version: 0.0.0 +version: 0.46.0 appVersion: 0.0.0 dependencies: - name: microservice-chart - version: 2.8.0 + version: 3.0.0 repository: "https://pagopa.github.io/aks-microservice-chart-blueprint" diff --git a/helm/values-dev.yaml b/helm/values-dev.yaml index 84e6548..be37301 100644 --- a/helm/values-dev.yaml +++ b/helm/values-dev.yaml @@ -1,5 +1,7 @@ microservice-chart: namespace: "nodo" + nameOverride: "" + fullnameOverride: "" image: repository: ghcr.io/pagopa/pagopa-stand-in-technical-support tag: "0.0.0" @@ -26,15 +28,12 @@ microservice-chart: ports: - 8080 ingress: - create: true - host: "your.host" # TODO: set the host - path: /your-path-here/(.*) # TODO: set your path - servicePort: 8080 + create: false serviceAccount: create: false - annotations: { } + annotations: {} name: "" - podAnnotations: { } + podAnnotations: {} podSecurityContext: seccompProfile: type: RuntimeDefault @@ -45,12 +44,12 @@ microservice-chart: memory: "512Mi" cpu: "0.25" limits: - memory: "512Mi" - cpu: "0.25" + memory: "768Mi" + cpu: "0.5" autoscaling: enable: true minReplica: 1 - maxReplica: 10 + maxReplica: 1 pollingInterval: 10 # seconds cooldownPeriod: 50 # seconds triggers: @@ -61,33 +60,32 @@ microservice-chart: value: "75" envConfig: WEBSITE_SITE_NAME: 'pagopa-stand-in-technical-support' # required to show cloud role name in application insights - ENV: 'dev' + ENV: 'azure-dev' APP_LOGGING_LEVEL: 'DEBUG' DEFAULT_LOGGING_LEVEL: 'INFO' CORS_CONFIGURATION: '{"origins": ["*"], "methods": ["*"]}' - - SPRING_DATASOURCE_URL: "jdbc:postgresql://pagopa-d-weu-nodo-flexible-postgresql.postgres.database.azure.com:6432/nodo?prepareThreshold=0" - SPRING_DATASOURCE_USERNAME: "cfg" - COSMOS_ENDPOINT: - - OTEL_SERVICE_NAME: "pagopa-stand-in-manager" - OTEL_RESOURCE_ATTRIBUTES: "deployment.environment=dev" + COSMOS_ENDPOINT: "https://pagopa-d-weu-nodo-standin-cosmos-account.documents.azure.com:443/" + OTEL_SERVICE_NAME: "pagopastandintechsupport" + OTEL_RESOURCE_ATTRIBUTES: "service.environment=dev" OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.elastic-system.svc:4317" OTEL_TRACES_EXPORTER: otlp OTEL_METRICS_EXPORTER: otlp OTEL_LOGS_EXPORTER: none OTEL_TRACES_SAMPLER: "always_on" - envSecret: - # required - APPLICATIONINSIGHTS_CONNECTION_STRING: 'ai-d-connection-string' - OTEL_EXPORTER_OTLP_HEADERS: elastic-apm-secret-token - SPRING_DATASOURCE_PASSWORD: "db-cfg-password" - COSMOS_KEY: "" - keyvault: - name: "pagopa-d-nodo-kv" - tenantId: "7788edaf-0346-4068-9d79-c868aed15b3d" - nodeSelector: { } - tolerations: [ ] + secretProvider: + create: true + envSecrets: + # required + APPLICATIONINSIGHTS_CONNECTION_STRING: "ai-d-connection-string" + OTEL_EXPORTER_OTLP_HEADERS: "elastic-apm-secret-token" + COSMOS_KEY: "cosmos-standin-account-key" + keyvault: + name: "pagopa-d-nodo-kv" + tenantId: "7788edaf-0346-4068-9d79-c868aed15b3d" + tmpVolumeMount: + create: true + nodeSelector: {} + tolerations: [] affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: @@ -97,3 +95,22 @@ microservice-chart: operator: In values: - user + canaryDelivery: + create: false + ingress: + create: false + canary: + type: header + headerName: X-Canary + headerValue: canary + weightPercent: 0 + service: + create: true + deployment: + create: true + image: + repository: ghcr.io/pagopa/pagopa-stand-in-technical-support + tag: "0.0.0" + pullPolicy: Always + envConfig: {} + envSecrets: {} diff --git a/helm/values-prod.yaml b/helm/values-prod.yaml index 622945a..fdf076c 100644 --- a/helm/values-prod.yaml +++ b/helm/values-prod.yaml @@ -1,5 +1,7 @@ microservice-chart: namespace: "nodo" + nameOverride: "" + fullnameOverride: "" image: repository: ghcr.io/pagopa/pagopa-stand-in-technical-support tag: "0.0.0" @@ -26,31 +28,32 @@ microservice-chart: ports: - 8080 ingress: - create: true - host: "your.host" # TODO: set the host - path: /your-path-here/(.*) # TODO: set your path - servicePort: 8080 + create: false serviceAccount: create: false - annotations: { } + annotations: {} name: "" - podAnnotations: { } + podAnnotations: {} podSecurityContext: seccompProfile: type: RuntimeDefault securityContext: allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + capabilities: + drop: + - all resources: requests: memory: "512Mi" cpu: "0.25" limits: - memory: "512Mi" - cpu: "0.25" + memory: "768Mi" + cpu: "0.5" autoscaling: enable: true minReplica: 1 - maxReplica: 10 + maxReplica: 1 pollingInterval: 10 # seconds cooldownPeriod: 50 # seconds triggers: @@ -61,33 +64,32 @@ microservice-chart: value: "75" envConfig: WEBSITE_SITE_NAME: 'pagopa-stand-in-technical-support' # required to show cloud role name in application insights - ENV: 'prod' + ENV: 'azure-prod' APP_LOGGING_LEVEL: 'DEBUG' DEFAULT_LOGGING_LEVEL: 'INFO' CORS_CONFIGURATION: '{"origins": ["*"], "methods": ["*"]}' - - SPRING_DATASOURCE_URL: "jdbc:postgresql://pagopa-p-weu-nodo-flexible-postgresql.postgres.database.azure.com:6432/nodo?prepareThreshold=0" - SPRING_DATASOURCE_USERNAME: "cfg" - COSMOS_ENDPOINT: - - OTEL_SERVICE_NAME: "pagopa-stand-in-manager" - OTEL_RESOURCE_ATTRIBUTES: "deployment.environment=prod" + COSMOS_ENDPOINT: "https://pagopa-d-weu-nodo-standin-cosmos-account.documents.azure.com:443/" + OTEL_SERVICE_NAME: "pagopastandintechsupport" + OTEL_RESOURCE_ATTRIBUTES: "service.environment=prod" OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.elastic-system.svc:4317" OTEL_TRACES_EXPORTER: otlp OTEL_METRICS_EXPORTER: otlp OTEL_LOGS_EXPORTER: none OTEL_TRACES_SAMPLER: "always_on" - envSecret: - # required - APPLICATIONINSIGHTS_CONNECTION_STRING: 'ai-p-connection-string' - OTEL_EXPORTER_OTLP_HEADERS: elastic-apm-secret-token - SPRING_DATASOURCE_PASSWORD: "db-cfg-password" - COSMOS_KEY: "" + secretProvider: + create: true + envSecrets: + # required + APPLICATIONINSIGHTS_CONNECTION_STRING: "ai-p-connection-string" + OTEL_EXPORTER_OTLP_HEADERS: "elastic-apm-secret-token" + COSMOS_KEY: "cosmos-standin-account-key" keyvault: name: "pagopa-p-name-kv" tenantId: "7788edaf-0346-4068-9d79-c868aed15b3d" - nodeSelector: { } - tolerations: [ ] + tmpVolumeMount: + create: true + nodeSelector: {} + tolerations: [] affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: @@ -97,3 +99,31 @@ microservice-chart: operator: In values: - user + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchLabels: + app.kubernetes.io/instance: "standintechsupport" + namespaces: ["nodo"] + topologyKey: topology.kubernetes.io/zone + canaryDelivery: + create: false + ingress: + create: true + canary: + type: header + headerName: X-Canary + headerValue: canary + weightPercent: 0 + service: + create: true + deployment: + create: true + image: + repository: ghcr.io/pagopa/pagopa-stand-in-technical-support + tag: "0.0.0" + pullPolicy: Always + envConfig: {} + envSecrets: {} diff --git a/helm/values-uat.yaml b/helm/values-uat.yaml index c3a6fec..73b2b58 100644 --- a/helm/values-uat.yaml +++ b/helm/values-uat.yaml @@ -1,5 +1,7 @@ microservice-chart: namespace: "nodo" + nameOverride: "" + fullnameOverride: "" image: repository: ghcr.io/pagopa/pagopa-stand-in-technical-support tag: "0.0.0" @@ -26,31 +28,32 @@ microservice-chart: ports: - 8080 ingress: - create: true - host: "your.host" # TODO: set the host - path: /your-path-here/(.*) # TODO: set your path - servicePort: 8080 + create: false serviceAccount: create: false - annotations: { } + annotations: {} name: "" - podAnnotations: { } + podAnnotations: {} podSecurityContext: seccompProfile: type: RuntimeDefault securityContext: allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + capabilities: + drop: + - all resources: requests: memory: "512Mi" cpu: "0.25" limits: - memory: "512Mi" - cpu: "0.25" + memory: "768Mi" + cpu: "0.5" autoscaling: enable: true minReplica: 1 - maxReplica: 10 + maxReplica: 1 pollingInterval: 10 # seconds cooldownPeriod: 50 # seconds triggers: @@ -61,33 +64,32 @@ microservice-chart: value: "75" envConfig: WEBSITE_SITE_NAME: 'pagopa-stand-in-technical-support' # required to show cloud role name in application insights - ENV: 'uat' + ENV: 'azure-uat' APP_LOGGING_LEVEL: 'DEBUG' DEFAULT_LOGGING_LEVEL: 'INFO' CORS_CONFIGURATION: '{"origins": ["*"], "methods": ["*"]}' - - SPRING_DATASOURCE_URL: "jdbc:postgresql://pagopa-u-weu-nodo-flexible-postgresql.postgres.database.azure.com:6432/nodo?prepareThreshold=0" - SPRING_DATASOURCE_USERNAME: "cfg" - COSMOS_ENDPOINT: - - OTEL_SERVICE_NAME: "pagopa-stand-in-manager" - OTEL_RESOURCE_ATTRIBUTES: "deployment.environment=uat" + COSMOS_ENDPOINT: "https://pagopa-d-weu-nodo-standin-cosmos-account.documents.azure.com:443/" + OTEL_SERVICE_NAME: "pagopastandintechsupport" + OTEL_RESOURCE_ATTRIBUTES: "service.environment=uat" OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.elastic-system.svc:4317" OTEL_TRACES_EXPORTER: otlp OTEL_METRICS_EXPORTER: otlp OTEL_LOGS_EXPORTER: none OTEL_TRACES_SAMPLER: "always_on" - envSecret: - # required - APPLICATIONINSIGHTS_CONNECTION_STRING: 'ai-u-connection-string' - OTEL_EXPORTER_OTLP_HEADERS: elastic-apm-secret-token - SPRING_DATASOURCE_PASSWORD: "db-cfg-password" - COSMOS_KEY: "" + secretProvider: + create: true + envSecrets: + # required + APPLICATIONINSIGHTS_CONNECTION_STRING: "ai-u-connection-string" + OTEL_EXPORTER_OTLP_HEADERS: "elastic-apm-secret-token" + COSMOS_KEY: "cosmos-standin-account-key" keyvault: name: "pagopa-u-name-kv" tenantId: "7788edaf-0346-4068-9d79-c868aed15b3d" - nodeSelector: { } - tolerations: [ ] + tmpVolumeMount: + create: true + nodeSelector: {} + tolerations: [] affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: @@ -97,3 +99,22 @@ microservice-chart: operator: In values: - user + canaryDelivery: + create: false + ingress: + create: true + canary: + type: header + headerName: X-Canary + headerValue: canary + weightPercent: 0 + service: + create: true + deployment: + create: true + image: + repository: ghcr.io/pagopa/pagopa-stand-in-technical-support + tag: "0.0.0" + pullPolicy: Always + envConfig: {} + envSecrets: {} diff --git a/pom.xml b/pom.xml index 7249486..6e2eefd 100644 --- a/pom.xml +++ b/pom.xml @@ -28,11 +28,6 @@ org.springframework.boot spring-boot-starter-validation - - org.postgresql - postgresql - 42.5.4 - @@ -99,11 +94,6 @@ lombok true - - software.amazon.awssdk - ses - 2.17.24 - junit @@ -122,17 +112,6 @@ javax.annotation-api 1.3.2 - - com.microsoft.azure.kusto - kusto-data - 5.0.2 - - - org.slf4j - slf4j-simple - - - diff --git a/src/main/java/it/gov/pagopa/microservice/Application.java b/src/main/java/it/gov/pagopa/microservice/Application.java index 465b6cb..450079b 100644 --- a/src/main/java/it/gov/pagopa/microservice/Application.java +++ b/src/main/java/it/gov/pagopa/microservice/Application.java @@ -9,5 +9,4 @@ public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } - } diff --git a/src/main/java/it/gov/pagopa/microservice/config/LoggingAspect.java b/src/main/java/it/gov/pagopa/microservice/config/LoggingAspect.java index 87b261d..64b798f 100644 --- a/src/main/java/it/gov/pagopa/microservice/config/LoggingAspect.java +++ b/src/main/java/it/gov/pagopa/microservice/config/LoggingAspect.java @@ -1,5 +1,10 @@ package it.gov.pagopa.microservice.config; +import java.util.Arrays; +import java.util.stream.StreamSupport; +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; @@ -19,12 +24,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.Arrays; -import java.util.stream.StreamSupport; - @Aspect @Component @Slf4j @@ -36,11 +35,9 @@ public class LoggingAspect { public static final String CODE = "httpCode"; public static final String RESPONSE_TIME = "responseTime"; - @Autowired - HttpServletRequest httRequest; + @Autowired HttpServletRequest httRequest; - @Autowired - HttpServletResponse httpResponse; + @Autowired HttpServletResponse httpResponse; @Value("${info.application.artifactId}") private String artifactId; @@ -76,9 +73,7 @@ public void service() { // all service methods } - /** - * Log essential info of application during the startup. - */ + /** Log essential info of application during the startup. */ @PostConstruct public void logStartup() { log.info("-> Starting {} version {} - environment {}", artifactId, version, environment); @@ -95,20 +90,19 @@ public void handleContextRefresh(ContextRefreshedEvent event) { log.debug("Active profiles: {}", Arrays.toString(env.getActiveProfiles())); final MutablePropertySources sources = ((AbstractEnvironment) env).getPropertySources(); StreamSupport.stream(sources.spliterator(), false) - .filter(EnumerablePropertySource.class::isInstance) - .map(ps -> ((EnumerablePropertySource) ps).getPropertyNames()) - .flatMap(Arrays::stream) - .distinct() - .filter( - prop -> - !(prop.toLowerCase().contains("credentials") - || prop.toLowerCase().contains("password") - || prop.toLowerCase().contains("pass") - || prop.toLowerCase().contains("pwd") - || prop.toLowerCase().contains("key") - || prop.toLowerCase().contains("secret") - )) - .forEach(prop -> log.debug("{}: {}", prop, env.getProperty(prop))); + .filter(EnumerablePropertySource.class::isInstance) + .map(ps -> ((EnumerablePropertySource) ps).getPropertyNames()) + .flatMap(Arrays::stream) + .distinct() + .filter( + prop -> + !(prop.toLowerCase().contains("credentials") + || prop.toLowerCase().contains("password") + || prop.toLowerCase().contains("pass") + || prop.toLowerCase().contains("pwd") + || prop.toLowerCase().contains("key") + || prop.toLowerCase().contains("secret"))) + .forEach(prop -> log.debug("{}: {}", prop, env.getProperty(prop))); } @Around(value = "restController()") @@ -116,14 +110,18 @@ public Object logApiInvocation(ProceedingJoinPoint joinPoint) throws Throwable { MDC.put(METHOD, joinPoint.getSignature().getName()); MDC.put(START_TIME, String.valueOf(System.currentTimeMillis())); log.info("{} {}", httRequest.getMethod(), httRequest.getRequestURI()); - log.info("Invoking API operation {} - args: {}", joinPoint.getSignature().getName(), joinPoint.getArgs()); + log.info( + "Invoking API operation {} - args: {}", + joinPoint.getSignature().getName(), + joinPoint.getArgs()); Object result = joinPoint.proceed(); MDC.put(STATUS, "OK"); MDC.put(CODE, String.valueOf(httpResponse.getStatus())); MDC.put(RESPONSE_TIME, getExecutionTime()); - log.info("Successful API operation {} - result: {}", joinPoint.getSignature().getName(), result); + log.info( + "Successful API operation {} - result: {}", joinPoint.getSignature().getName(), result); MDC.remove(STATUS); MDC.remove(CODE); MDC.remove(RESPONSE_TIME); @@ -145,7 +143,8 @@ public void trowingApiInvocation(JoinPoint joinPoint, ResponseEntity result) @Around(value = "repository() || service()") public Object logTrace(ProceedingJoinPoint joinPoint) throws Throwable { - log.debug("Call method {} - args: {}", joinPoint.getSignature().toShortString(), joinPoint.getArgs()); + log.debug( + "Call method {} - args: {}", joinPoint.getSignature().toShortString(), joinPoint.getArgs()); Object result = joinPoint.proceed(); log.debug("Return method {} - result: {}", joinPoint.getSignature().toShortString(), result); return result; diff --git a/src/main/java/it/gov/pagopa/microservice/config/MappingsConfiguration.java b/src/main/java/it/gov/pagopa/microservice/config/MappingsConfiguration.java index b403822..ee6d624 100644 --- a/src/main/java/it/gov/pagopa/microservice/config/MappingsConfiguration.java +++ b/src/main/java/it/gov/pagopa/microservice/config/MappingsConfiguration.java @@ -1,6 +1,5 @@ package it.gov.pagopa.microservice.config; - import org.modelmapper.ModelMapper; import org.modelmapper.convention.MatchingStrategies; import org.springframework.context.annotation.Bean; @@ -15,5 +14,4 @@ ModelMapper modelMapper() { mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT); return mapper; } - } diff --git a/src/main/java/it/gov/pagopa/microservice/config/OpenApiConfig.java b/src/main/java/it/gov/pagopa/microservice/config/OpenApiConfig.java index 6b7ec18..e387982 100644 --- a/src/main/java/it/gov/pagopa/microservice/config/OpenApiConfig.java +++ b/src/main/java/it/gov/pagopa/microservice/config/OpenApiConfig.java @@ -50,10 +50,7 @@ public OpenAPI customOpenAPI( public OpenApiCustomiser sortOperationsAlphabetically() { return openApi -> { Paths paths = - openApi - .getPaths() - .entrySet() - .stream() + openApi.getPaths().entrySet().stream() .sorted(Map.Entry.comparingByKey()) .collect( Paths::new, @@ -67,10 +64,7 @@ public OpenApiCustomiser sortOperationsAlphabetically() { .forEach( operation -> { var responses = - operation - .getResponses() - .entrySet() - .stream() + operation.getResponses().entrySet().stream() .sorted(Map.Entry.comparingByKey()) .collect( ApiResponses::new, @@ -105,7 +99,8 @@ public OpenApiCustomiser addCommonHeaders() { .name(HEADER_REQUEST_ID) .schema(new StringSchema()) .description( - "This header identifies the call, if not passed it is self-generated. This ID is returned in the response.")); + "This header identifies the call, if not passed it is" + + " self-generated. This ID is returned in the response.")); } // add Request-ID as response header diff --git a/src/main/java/it/gov/pagopa/microservice/config/RequestFilter.java b/src/main/java/it/gov/pagopa/microservice/config/RequestFilter.java index 63726e3..237c224 100644 --- a/src/main/java/it/gov/pagopa/microservice/config/RequestFilter.java +++ b/src/main/java/it/gov/pagopa/microservice/config/RequestFilter.java @@ -26,10 +26,10 @@ public class RequestFilter implements Filter { * Get the request ID from the custom header "X-Request-Id" if present, otherwise it generates * one. Set the X-Request-Id value in the {@code response} and in the MDC * - * @param request http request + * @param request http request * @param response http response - * @param chain next filter - * @throws IOException if an I/O error occurs during this filter's processing of the request + * @param chain next filter + * @throws IOException if an I/O error occurs during this filter's processing of the request * @throws ServletException if the processing fails for any other reason */ @Override @@ -54,5 +54,4 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha MDC.clear(); } } - } diff --git a/src/main/java/it/gov/pagopa/microservice/config/ResponseValidator.java b/src/main/java/it/gov/pagopa/microservice/config/ResponseValidator.java index 67cc3af..440d0c5 100644 --- a/src/main/java/it/gov/pagopa/microservice/config/ResponseValidator.java +++ b/src/main/java/it/gov/pagopa/microservice/config/ResponseValidator.java @@ -17,18 +17,18 @@ @Component public class ResponseValidator { - @Autowired - private Validator validator; - + @Autowired private Validator validator; /** * This method validates the response annotated with the {@link javax.validation.constraints} * * @param joinPoint not used - * @param result the response to validate + * @param result the response to validate */ // TODO: set your package - @AfterReturning(pointcut = "execution(* it.gov.pagopa.microservice.controller.*.*(..))", returning = "result") + @AfterReturning( + pointcut = "execution(* it.gov.pagopa.microservice.controller.*.*(..))", + returning = "result") public void validateResponse(JoinPoint joinPoint, Object result) { if (result instanceof ResponseEntity) { validateResponse((ResponseEntity) result); diff --git a/src/main/java/it/gov/pagopa/microservice/config/WebMvcConfiguration.java b/src/main/java/it/gov/pagopa/microservice/config/WebMvcConfiguration.java index c61db33..bcfb55d 100644 --- a/src/main/java/it/gov/pagopa/microservice/config/WebMvcConfiguration.java +++ b/src/main/java/it/gov/pagopa/microservice/config/WebMvcConfiguration.java @@ -8,23 +8,20 @@ import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - @Configuration public class WebMvcConfiguration implements WebMvcConfigurer { @Value("${cors.configuration}") private String corsConfiguration; - @SneakyThrows @Override public void addCorsMappings(CorsRegistry registry) { - AppCorsConfiguration appCorsConfiguration = new ObjectMapper().readValue(corsConfiguration, - AppCorsConfiguration.class); - registry.addMapping("/**") + AppCorsConfiguration appCorsConfiguration = + new ObjectMapper().readValue(corsConfiguration, AppCorsConfiguration.class); + registry + .addMapping("/**") .allowedOrigins(appCorsConfiguration.getOrigins()) .allowedMethods(appCorsConfiguration.getMethods()); } } - - diff --git a/src/main/java/it/gov/pagopa/microservice/controller/HomeController.java b/src/main/java/it/gov/pagopa/microservice/controller/HomeController.java index 70b6dd1..a3b593c 100644 --- a/src/main/java/it/gov/pagopa/microservice/controller/HomeController.java +++ b/src/main/java/it/gov/pagopa/microservice/controller/HomeController.java @@ -14,7 +14,6 @@ public class HomeController { @Value("${server.servlet.context-path}") String basePath; - /** * @return redirect to Swagger page documentation */ @@ -26,5 +25,4 @@ public RedirectView home() { } return new RedirectView(basePath + "swagger-ui.html"); } - } diff --git a/src/main/java/it/gov/pagopa/microservice/exception/AppError.java b/src/main/java/it/gov/pagopa/microservice/exception/AppError.java index 3f7f95e..46a97c8 100644 --- a/src/main/java/it/gov/pagopa/microservice/exception/AppError.java +++ b/src/main/java/it/gov/pagopa/microservice/exception/AppError.java @@ -3,22 +3,18 @@ import lombok.Getter; import org.springframework.http.HttpStatus; - @Getter public enum AppError { - INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error", - "Something was wrong"); + INTERNAL_SERVER_ERROR( + HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error", "Something was wrong"); public final HttpStatus httpStatus; public final String title; public final String details; - AppError(HttpStatus httpStatus, String title, String details) { this.httpStatus = httpStatus; this.title = title; this.details = details; } } - - diff --git a/src/main/java/it/gov/pagopa/microservice/exception/AppException.java b/src/main/java/it/gov/pagopa/microservice/exception/AppException.java index a185d0b..7922d22 100644 --- a/src/main/java/it/gov/pagopa/microservice/exception/AppException.java +++ b/src/main/java/it/gov/pagopa/microservice/exception/AppException.java @@ -9,31 +9,31 @@ /** * Custom exception. - *

See {@link ErrorHandler} + * + *

See {@link ErrorHandler} */ @EqualsAndHashCode(callSuper = true) @Value @Validated public class AppException extends RuntimeException { - /** - * title returned to the response when this exception occurred - */ + /** title returned to the response when this exception occurred */ String title; - /** - * http status returned to the response when this exception occurred - */ + /** http status returned to the response when this exception occurred */ HttpStatus httpStatus; /** * @param httpStatus HTTP status returned to the response - * @param title title returned to the response when this exception occurred - * @param message the detail message returend to the response - * @param cause The cause of this {@link AppException} + * @param title title returned to the response when this exception occurred + * @param message the detail message returend to the response + * @param cause The cause of this {@link AppException} */ - public AppException(@NotNull HttpStatus httpStatus, @NotNull String title, - @NotNull String message, Throwable cause) { + public AppException( + @NotNull HttpStatus httpStatus, + @NotNull String title, + @NotNull String message, + Throwable cause) { super(message, cause); this.title = title; this.httpStatus = httpStatus; @@ -41,22 +41,21 @@ public AppException(@NotNull HttpStatus httpStatus, @NotNull String title, /** * @param httpStatus HTTP status returned to the response - * @param title title returned to the response when this exception occurred - * @param message the detail message returend to the response + * @param title title returned to the response when this exception occurred + * @param message the detail message returend to the response */ - public AppException(@NotNull HttpStatus httpStatus, @NotNull String title, - @NotNull String message) { + public AppException( + @NotNull HttpStatus httpStatus, @NotNull String title, @NotNull String message) { super(message); this.title = title; this.httpStatus = httpStatus; } - /** * @param appError Response template returned to the response - * @param args {@link Formatter} replaces the placeholders in "details" string of - * {@link AppError} with the arguments. If there are more arguments than format - * specifiers, the extra arguments are ignored. + * @param args {@link Formatter} replaces the placeholders in "details" string of {@link AppError} + * with the arguments. If there are more arguments than format specifiers, the extra arguments + * are ignored. */ public AppException(@NotNull AppError appError, Object... args) { super(formatDetails(appError, args)); @@ -66,10 +65,9 @@ public AppException(@NotNull AppError appError, Object... args) { /** * @param appError Response template returned to the response - * @param cause The cause of this {@link AppException} - * @param args Arguments for the details of {@link AppError} replaced by the - * {@link Formatter}. If there are more arguments than format specifiers, the - * extra arguments are ignored. + * @param cause The cause of this {@link AppException} + * @param args Arguments for the details of {@link AppError} replaced by the {@link Formatter}. If + * there are more arguments than format specifiers, the extra arguments are ignored. */ public AppException(@NotNull AppError appError, Throwable cause, Object... args) { super(formatDetails(appError, args), cause); diff --git a/src/main/java/it/gov/pagopa/microservice/exception/ErrorHandler.java b/src/main/java/it/gov/pagopa/microservice/exception/ErrorHandler.java index 325918a..3503b24 100644 --- a/src/main/java/it/gov/pagopa/microservice/exception/ErrorHandler.java +++ b/src/main/java/it/gov/pagopa/microservice/exception/ErrorHandler.java @@ -20,110 +20,118 @@ import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; - -/** - * All Exceptions are handled by this class - */ +/** All Exceptions are handled by this class */ @ControllerAdvice @Slf4j public class ErrorHandler extends ResponseEntityExceptionHandler { - public static final String INTERNAL_SERVER_ERROR = "INTERNAL SERVER ERROR"; public static final String BAD_REQUEST = "BAD REQUEST"; public static final String FOREIGN_KEY_VIOLATION = "23503"; public static final int CHILD_RECORD_VIOLATION = 2292; - /** * Handle if the input request is not a valid JSON * - * @param ex {@link HttpMessageNotReadableException} exception raised + * @param ex {@link HttpMessageNotReadableException} exception raised * @param headers of the response - * @param status of the response + * @param status of the response * @param request from frontend * @return a {@link ProblemJson} as response with the cause and with a 400 as HTTP status */ @Override - public ResponseEntity handleHttpMessageNotReadable(HttpMessageNotReadableException ex, - HttpHeaders headers, HttpStatus status, WebRequest request) { + public ResponseEntity handleHttpMessageNotReadable( + HttpMessageNotReadableException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest request) { log.warn("Input not readable: ", ex); - var errorResponse = ProblemJson.builder() - .status(HttpStatus.BAD_REQUEST.value()) - .title(BAD_REQUEST) - .detail("Invalid input format") - .build(); + var errorResponse = + ProblemJson.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .title(BAD_REQUEST) + .detail("Invalid input format") + .build(); return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); } /** * Handle if missing some request parameters in the request * - * @param ex {@link MissingServletRequestParameterException} exception raised + * @param ex {@link MissingServletRequestParameterException} exception raised * @param headers of the response - * @param status of the response + * @param status of the response * @param request from frontend * @return a {@link ProblemJson} as response with the cause and with a 400 as HTTP status */ @Override public ResponseEntity handleMissingServletRequestParameter( - MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, + MissingServletRequestParameterException ex, + HttpHeaders headers, + HttpStatus status, WebRequest request) { log.warn("Missing request parameter: ", ex); - var errorResponse = ProblemJson.builder() - .status(HttpStatus.BAD_REQUEST.value()) - .title(BAD_REQUEST) - .detail(ex.getMessage()) - .build(); + var errorResponse = + ProblemJson.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .title(BAD_REQUEST) + .detail(ex.getMessage()) + .build(); return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); } - /** * Customize the response for TypeMismatchException. * - * @param ex the exception + * @param ex the exception * @param headers the headers to be written to the response - * @param status the selected response status + * @param status the selected response status * @param request the current request * @return a {@code ResponseEntity} instance */ @Override - protected ResponseEntity handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, - HttpStatus status, WebRequest request) { + protected ResponseEntity handleTypeMismatch( + TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { log.warn("Type mismatch: ", ex); - var errorResponse = ProblemJson.builder() - .status(HttpStatus.BAD_REQUEST.value()) - .title(BAD_REQUEST) - .detail(String.format("Invalid value %s for property %s", ex.getValue(), - ((MethodArgumentTypeMismatchException) ex).getName())) - .build(); + var errorResponse = + ProblemJson.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .title(BAD_REQUEST) + .detail( + String.format( + "Invalid value %s for property %s", + ex.getValue(), ((MethodArgumentTypeMismatchException) ex).getName())) + .build(); return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); } /** * Handle if validation constraints are unsatisfied * - * @param ex {@link MethodArgumentNotValidException} exception raised + * @param ex {@link MethodArgumentNotValidException} exception raised * @param headers of the response - * @param status of the response + * @param status of the response * @param request from frontend * @return a {@link ProblemJson} as response with the cause and with a 400 as HTTP status */ @Override - protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, - HttpHeaders headers, HttpStatus status, WebRequest request) { + protected ResponseEntity handleMethodArgumentNotValid( + MethodArgumentNotValidException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest request) { List details = new ArrayList<>(); for (FieldError error : ex.getBindingResult().getFieldErrors()) { details.add(error.getField() + ": " + error.getDefaultMessage()); } var detailsMessage = String.join(", ", details); log.warn("Input not valid: " + detailsMessage); - var errorResponse = ProblemJson.builder() - .status(HttpStatus.BAD_REQUEST.value()) - .title(BAD_REQUEST) - .detail(detailsMessage) - .build(); + var errorResponse = + ProblemJson.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .title(BAD_REQUEST) + .detail(detailsMessage) + .build(); return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); } @@ -131,18 +139,18 @@ protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotV public ResponseEntity handleConstraintViolationException( final javax.validation.ConstraintViolationException ex, final WebRequest request) { log.warn("Validation Error raised:", ex); - var errorResponse = ProblemJson.builder() - .status(HttpStatus.BAD_REQUEST.value()) - .title(BAD_REQUEST) - .detail(ex.getMessage()) - .build(); + var errorResponse = + ProblemJson.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .title(BAD_REQUEST) + .detail(ex.getMessage()) + .build(); return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); } - /** - * @param ex {@link DataIntegrityViolationException} exception raised when the SQL statement - * cannot be executed + * @param ex {@link DataIntegrityViolationException} exception raised when the SQL statement + * cannot be executed * @param request from frontend * @return a {@link ProblemJson} as response with the cause and with an appropriated HTTP status */ @@ -153,84 +161,89 @@ public ResponseEntity handleDataIntegrityViolationException( if (ex.getCause() instanceof ConstraintViolationException) { String sqlState = ((ConstraintViolationException) ex.getCause()).getSQLState(); - var errorCode = ((ConstraintViolationException) ex.getCause()).getSQLException() - .getErrorCode(); - // check the reason of ConstraintViolationException: is true if the object is referenced by a foreign key + var errorCode = + ((ConstraintViolationException) ex.getCause()).getSQLException().getErrorCode(); + // check the reason of ConstraintViolationException: is true if the object is referenced by a + // foreign key // more info: https://docs.oracle.com/javadb/10.8.3.0/ref/rrefexcept71493.html if (sqlState.equals(FOREIGN_KEY_VIOLATION)) { log.warn("Can't delete from Database", ex); - errorResponse = ProblemJson.builder() - .status(HttpStatus.CONFLICT.value()) - .title("Conflict with the current state of the resource") - .detail("There is a relation with other resource. Delete it first.") - .build(); + errorResponse = + ProblemJson.builder() + .status(HttpStatus.CONFLICT.value()) + .title("Conflict with the current state of the resource") + .detail("There is a relation with other resource. Delete it first.") + .build(); } if (errorCode == CHILD_RECORD_VIOLATION) { log.warn("Can't update the Database", ex); - errorResponse = ProblemJson.builder() - .status(HttpStatus.CONFLICT.value()) - .title("Conflict with the current state of the resource") - .detail("There is a relation with other resource. Delete it first.") - .build(); + errorResponse = + ProblemJson.builder() + .status(HttpStatus.CONFLICT.value()) + .title("Conflict with the current state of the resource") + .detail("There is a relation with other resource. Delete it first.") + .build(); } } // default response if (errorResponse == null) { log.warn("Data Integrity Violation", ex); - errorResponse = ProblemJson.builder() - .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) - .title(INTERNAL_SERVER_ERROR) - .detail(ex.getMessage()) - .build(); + errorResponse = + ProblemJson.builder() + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .title(INTERNAL_SERVER_ERROR) + .detail(ex.getMessage()) + .build(); } return new ResponseEntity<>(errorResponse, HttpStatus.valueOf(errorResponse.getStatus())); } - /** * Handle if a {@link AppException} is raised * - * @param ex {@link AppException} exception raised + * @param ex {@link AppException} exception raised * @param request from frontend * @return a {@link ProblemJson} as response with the cause and with an appropriated HTTP status */ @ExceptionHandler({AppException.class}) - public ResponseEntity handleAppException(final AppException ex, - final WebRequest request) { + public ResponseEntity handleAppException( + final AppException ex, final WebRequest request) { if (ex.getCause() != null) { - log.warn("App Exception raised: " + ex.getMessage() + "\nCause of the App Exception: ", + log.warn( + "App Exception raised: " + ex.getMessage() + "\nCause of the App Exception: ", ex.getCause()); } else { log.warn("App Exception raised: {}", ex.getMessage()); log.debug("Trace error: ", ex); } - var errorResponse = ProblemJson.builder() - .status(ex.getHttpStatus().value()) - .title(ex.getTitle()) - .detail(ex.getMessage()) - .build(); + var errorResponse = + ProblemJson.builder() + .status(ex.getHttpStatus().value()) + .title(ex.getTitle()) + .detail(ex.getMessage()) + .build(); return new ResponseEntity<>(errorResponse, ex.getHttpStatus()); } - /** * Handle if a {@link Exception} is raised * - * @param ex {@link Exception} exception raised + * @param ex {@link Exception} exception raised * @param request from frontend * @return a {@link ProblemJson} as response with the cause and with 500 as HTTP status */ @ExceptionHandler({Exception.class}) - public ResponseEntity handleGenericException(final Exception ex, - final WebRequest request) { + public ResponseEntity handleGenericException( + final Exception ex, final WebRequest request) { log.error("Generic Exception raised:", ex); - var errorResponse = ProblemJson.builder() - .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) - .title(INTERNAL_SERVER_ERROR) - .detail(ex.getMessage()) - .build(); + var errorResponse = + ProblemJson.builder() + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .title(INTERNAL_SERVER_ERROR) + .detail(ex.getMessage()) + .build(); return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); } } diff --git a/src/main/java/it/gov/pagopa/microservice/model/ProblemJson.java b/src/main/java/it/gov/pagopa/microservice/model/ProblemJson.java index db11a37..854fb33 100644 --- a/src/main/java/it/gov/pagopa/microservice/model/ProblemJson.java +++ b/src/main/java/it/gov/pagopa/microservice/model/ProblemJson.java @@ -13,7 +13,8 @@ /** * Object returned as response in case of an error. - *

See {@link it.pagopa.microservice.exception.ErrorHandler} + * + *

See {@link it.pagopa.microservice.exception.ErrorHandler} */ @Data @Builder(toBuilder = true) @@ -24,17 +25,25 @@ public class ProblemJson { @JsonProperty("title") - @Schema(description = "A short, summary of the problem type. Written in english and readable for engineers (usually not suited for non technical stakeholders and not localized); example: Service Unavailable") + @Schema( + description = + "A short, summary of the problem type. Written in english and readable for engineers" + + " (usually not suited for non technical stakeholders and not localized); example:" + + " Service Unavailable") private String title; @JsonProperty("status") - @Schema(example = "200", description = "The HTTP status code generated by the origin server for this occurrence of the problem.") + @Schema( + example = "200", + description = + "The HTTP status code generated by the origin server for this occurrence of the problem.") @Min(100) @Max(600) private Integer status; @JsonProperty("detail") - @Schema(example = "There was an error processing the request", description = "A human readable explanation specific to this occurrence of the problem.") + @Schema( + example = "There was an error processing the request", + description = "A human readable explanation specific to this occurrence of the problem.") private String detail; - } diff --git a/src/main/java/it/gov/pagopa/microservice/util/Constants.java b/src/main/java/it/gov/pagopa/microservice/util/Constants.java index e5812c7..510e28a 100644 --- a/src/main/java/it/gov/pagopa/microservice/util/Constants.java +++ b/src/main/java/it/gov/pagopa/microservice/util/Constants.java @@ -5,7 +5,5 @@ @UtilityClass public class Constants { - public static final String HEADER_REQUEST_ID = "X-Request-Id"; - } diff --git a/src/main/java/it/gov/pagopa/standintechsupport/Application.java b/src/main/java/it/gov/pagopa/standintechsupport/Application.java index 4eec3f9..e6ea899 100644 --- a/src/main/java/it/gov/pagopa/standintechsupport/Application.java +++ b/src/main/java/it/gov/pagopa/standintechsupport/Application.java @@ -5,11 +5,10 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.context.annotation.Bean; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.ses.SesClient; -@SpringBootApplication() +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class Application { public static void main(String[] args) { @@ -22,19 +21,8 @@ public static void main(String[] args) { @Value("${cosmos.key}") private String cosmosKey; - @Value("${aws.region}") - private String region; - @Bean public CosmosClient getCosmosClient() { return new CosmosClientBuilder().endpoint(cosmosEndpoint).key(cosmosKey).buildClient(); } - - @Bean - public SesClient sesClient() { - return SesClient.builder() - .region(Region.of(region)) - .build(); - } - } diff --git a/src/main/java/it/gov/pagopa/standintechsupport/controller/SupportController.java b/src/main/java/it/gov/pagopa/standintechsupport/controller/SupportController.java index 1217370..087b954 100644 --- a/src/main/java/it/gov/pagopa/standintechsupport/controller/SupportController.java +++ b/src/main/java/it/gov/pagopa/standintechsupport/controller/SupportController.java @@ -8,44 +8,56 @@ import it.gov.pagopa.standintechsupport.controller.model.ResponseContainer; import it.gov.pagopa.standintechsupport.controller.model.StandInStation; import it.gov.pagopa.standintechsupport.service.SupportService; +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -import java.time.LocalDate; -import java.util.List; -import java.util.Optional; - @RestController @Slf4j public class SupportController { - @Autowired - private SupportService supportService; + @Autowired private SupportService supportService; - @Operation(summary = "Get the list of filtered events") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Get the list", - content = { @Content(mediaType = "application/json", - schema = @Schema(implementation = ResponseContainer.class)) }), - @ApiResponse(responseCode = "400", description = "Invalid request", - content = @Content) - }) - @GetMapping("/events") - public ResponseContainer getEvents(Optional station, @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Optional from, @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Optional to){ - return supportService.getEvents(station,from.orElse(null),to.orElse(null)); - } + @Operation(summary = "Get the list of events") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "Get the list", + content = { + @Content( + mediaType = "application/json", + schema = @Schema(implementation = ResponseContainer.class)) + }), + @ApiResponse(responseCode = "400", description = "Invalid request", content = @Content) + }) + @GetMapping("/events") + public ResponseContainer getEvents( + Optional station, + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Optional from, + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Optional to) { + return supportService.getEvents(station, from.orElse(null), to.orElse(null)); + } - @Operation(summary = "Get the list of standin station") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Get the list", - content = { @Content(mediaType = "application/json", - schema = @Schema(implementation = List.class)) }) - }) - @GetMapping("/stations") - public List getStations(){ - return supportService.getStations(); - } + @Operation(summary = "Get the list of standin station") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "Get the list", + content = { + @Content( + mediaType = "application/json", + schema = @Schema(implementation = List.class)) + }) + }) + @GetMapping("/stations") + public List getStations() { + return supportService.getStations(); + } } diff --git a/src/main/java/it/gov/pagopa/standintechsupport/controller/model/CosmosEventModel.java b/src/main/java/it/gov/pagopa/standintechsupport/controller/model/CosmosEventModel.java index 3e91030..0c95ffe 100644 --- a/src/main/java/it/gov/pagopa/standintechsupport/controller/model/CosmosEventModel.java +++ b/src/main/java/it/gov/pagopa/standintechsupport/controller/model/CosmosEventModel.java @@ -1,13 +1,11 @@ - package it.gov.pagopa.standintechsupport.controller.model; +import java.time.Instant; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import java.time.Instant; - @Data @Builder @NoArgsConstructor diff --git a/src/main/java/it/gov/pagopa/standintechsupport/controller/model/ResponseContainer.java b/src/main/java/it/gov/pagopa/standintechsupport/controller/model/ResponseContainer.java index 3b8831b..fcc8e22 100644 --- a/src/main/java/it/gov/pagopa/standintechsupport/controller/model/ResponseContainer.java +++ b/src/main/java/it/gov/pagopa/standintechsupport/controller/model/ResponseContainer.java @@ -1,23 +1,21 @@ package it.gov.pagopa.standintechsupport.controller.model; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; +import java.time.LocalDate; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; -import java.time.LocalDate; -import java.util.List; - @Data @AllArgsConstructor @Builder public class ResponseContainer { - private LocalDate dateFrom; - private LocalDate dateTo; + private LocalDate dateFrom; + private LocalDate dateTo; - private int count; + private int count; - @JsonProperty("data") - private List data; + @JsonProperty("data") + private List data; } diff --git a/src/main/java/it/gov/pagopa/standintechsupport/controller/model/StandInStation.java b/src/main/java/it/gov/pagopa/standintechsupport/controller/model/StandInStation.java index caa9569..942e8af 100644 --- a/src/main/java/it/gov/pagopa/standintechsupport/controller/model/StandInStation.java +++ b/src/main/java/it/gov/pagopa/standintechsupport/controller/model/StandInStation.java @@ -1,13 +1,11 @@ package it.gov.pagopa.standintechsupport.controller.model; -import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.Instant; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import java.time.Instant; - @Data @Builder @NoArgsConstructor diff --git a/src/main/java/it/gov/pagopa/standintechsupport/exception/AppError.java b/src/main/java/it/gov/pagopa/standintechsupport/exception/AppError.java index eed0416..0eef0c6 100644 --- a/src/main/java/it/gov/pagopa/standintechsupport/exception/AppError.java +++ b/src/main/java/it/gov/pagopa/standintechsupport/exception/AppError.java @@ -6,7 +6,8 @@ @Getter public enum AppError { BAD_REQUEST(HttpStatus.BAD_REQUEST, "Invalid Request", "%s"), - INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error", "Something was wrong"), + INTERNAL_SERVER_ERROR( + HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error", "Something was wrong"), INTERVAL_TOO_LARGE(HttpStatus.BAD_REQUEST, "Date interval too large", "%s"); public final HttpStatus httpStatus; diff --git a/src/main/java/it/gov/pagopa/standintechsupport/model/DateRequest.java b/src/main/java/it/gov/pagopa/standintechsupport/model/DateRequest.java index 35fd5dd..27df1a3 100644 --- a/src/main/java/it/gov/pagopa/standintechsupport/model/DateRequest.java +++ b/src/main/java/it/gov/pagopa/standintechsupport/model/DateRequest.java @@ -1,8 +1,7 @@ package it.gov.pagopa.standintechsupport.model; -import lombok.*; - import java.time.LocalDate; +import lombok.*; @Getter @Setter diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/BlacklistStationsRepository.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/BlacklistStationsRepository.java deleted file mode 100644 index ace3ee7..0000000 --- a/src/main/java/it/gov/pagopa/standintechsupport/repository/BlacklistStationsRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package it.gov.pagopa.standintechsupport.repository; - -import it.gov.pagopa.standintechsupport.repository.entity.BlacklistStation; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -public interface BlacklistStationsRepository extends JpaRepository { - - @Query("select station from BlacklistStation") - public List findAllStations(); -} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosEventsRepository.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosEventsRepository.java index e42fc46..83c3607 100644 --- a/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosEventsRepository.java +++ b/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosEventsRepository.java @@ -5,11 +5,10 @@ import com.azure.cosmos.models.*; import com.azure.cosmos.util.CosmosPagedIterable; import it.gov.pagopa.standintechsupport.repository.model.CosmosEvent; +import it.gov.pagopa.standintechsupport.util.Util; import java.time.Instant; import java.time.LocalDate; import java.util.*; - -import it.gov.pagopa.standintechsupport.util.Util; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -47,40 +46,37 @@ public CosmosItemResponse save(CosmosEvent item) { return container.createItem(item); } - public List find( - Optional station, - LocalDate dateFrom, - LocalDate dateTo) { + public List find(Optional station, LocalDate dateFrom, LocalDate dateTo) { List paramList; - if(station.isEmpty()){ - paramList = Arrays.asList( + if (station.isEmpty()) { + paramList = + Arrays.asList( new SqlParameter("@from", Util.format(dateFrom)), - new SqlParameter("@to", Util.format(dateTo.plusDays(1))) - ); - }else{ - paramList = Arrays.asList( + new SqlParameter("@to", Util.format(dateTo.plusDays(1)))); + } else { + paramList = + Arrays.asList( new SqlParameter("@from", Util.format(dateFrom)), new SqlParameter("@to", Util.format(dateTo.plusDays(1))), - new SqlParameter("@station",station.get()) - ); + new SqlParameter("@station", station.get())); } SqlQuerySpec q = - new SqlQuerySpec( - "SELECT * FROM c where c.PartitionKey >= @from and c.PartitionKey < @to" - + station.map(pt->" and c.station = @station").orElse("") - + " order by c.timestamp desc" - ) - .setParameters(paramList); + new SqlQuerySpec( + "SELECT * FROM c where c.PartitionKey >= @from and c.PartitionKey < @to" + + station.map(pt -> " and c.station = @station").orElse("") + + " order by c.timestamp desc") + .setParameters(paramList); String continuationToken = null; - List results = new ArrayList<>(); - do{ - Iterable> feedResponses = query(q).iterableByPage(continuationToken,100); + List results = new ArrayList<>(); + do { + Iterable> feedResponses = + query(q).iterableByPage(continuationToken, 100); for (FeedResponse page : feedResponses) { results.addAll(page.getResults()); continuationToken = page.getContinuationToken(); } - }while (continuationToken!=null); + } while (continuationToken != null); return results; } diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosNodeDataRepository.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosNodeDataRepository.java deleted file mode 100644 index 5fe3981..0000000 --- a/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosNodeDataRepository.java +++ /dev/null @@ -1,59 +0,0 @@ -//package it.gov.pagopa.standintechsupport.repository; -// -//import com.azure.cosmos.CosmosClient; -//import com.azure.cosmos.CosmosContainer; -//import com.azure.cosmos.models.*; -//import com.azure.cosmos.util.CosmosPagedIterable; -//import it.gov.pagopa.standintechsupport.repository.model.CosmosNodeCallCounts; -//import java.time.ZonedDateTime; -//import java.util.ArrayList; -//import java.util.Arrays; -//import java.util.List; -//import java.util.stream.Collectors; -//import lombok.extern.slf4j.Slf4j; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.beans.factory.annotation.Value; -//import org.springframework.stereotype.Component; -// -//@Slf4j -//@Component -//public class CosmosNodeDataRepository { -// -// @Autowired private CosmosClient cosmosClient; -// -// @Value("${cosmos.db.name}") -// private String dbname; -// -// public static String tablename = "node_data"; -// -// private CosmosPagedIterable query(SqlQuerySpec query) { -// log.info("executing query:" + query.getQueryText()); -// CosmosContainer container = cosmosClient.getDatabase(dbname).getContainer(tablename); -// return container.queryItems(query, new CosmosQueryRequestOptions(), CosmosNodeCallCounts.class); -// } -// -// public Iterable> saveAll(List items) { -// CosmosContainer container = cosmosClient.getDatabase(dbname).getContainer(tablename); -// List cosmosItemOperationStream = -// items.stream() -// .map( -// s -> -// CosmosBulkOperations.getCreateItemOperation( -// s, new PartitionKey(s.getPartitionKey()))) -// .collect(Collectors.toList()); -// return container.executeBulkOperations(cosmosItemOperationStream); -// } -// -// public CosmosItemResponse save(CosmosNodeCallCounts item) { -// CosmosContainer container = cosmosClient.getDatabase(dbname).getContainer(tablename); -// return container.createItem(item); -// } -// -// public List getStationCounts(ZonedDateTime dateFrom) { -// List paramList = new ArrayList<>(); -// paramList.addAll(Arrays.asList(new SqlParameter("@from", dateFrom.toInstant()))); -// SqlQuerySpec q = -// new SqlQuerySpec("SELECT * FROM c where c.timestamp >= @from").setParameters(paramList); -// return query(q).stream().collect(Collectors.toList()); -// } -//} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosStationDataRepository.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosStationDataRepository.java deleted file mode 100644 index d10d2f7..0000000 --- a/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosStationDataRepository.java +++ /dev/null @@ -1,51 +0,0 @@ -//package it.gov.pagopa.standintechsupport.repository; -// -//import com.azure.cosmos.CosmosClient; -//import com.azure.cosmos.CosmosContainer; -//import com.azure.cosmos.models.CosmosItemResponse; -//import com.azure.cosmos.models.CosmosQueryRequestOptions; -//import com.azure.cosmos.models.SqlParameter; -//import com.azure.cosmos.models.SqlQuerySpec; -//import com.azure.cosmos.util.CosmosPagedIterable; -//import it.gov.pagopa.standintechsupport.repository.model.CosmosForwarderCallCounts; -//import java.time.ZonedDateTime; -//import java.util.ArrayList; -//import java.util.Arrays; -//import java.util.List; -//import java.util.stream.Collectors; -//import lombok.extern.slf4j.Slf4j; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.beans.factory.annotation.Value; -//import org.springframework.stereotype.Component; -// -//@Slf4j -//@Component -//public class CosmosStationDataRepository { -// -// @Autowired private CosmosClient cosmosClient; -// -// @Value("${cosmos.db.name}") -// private String dbname; -// -// public static String tablename = "station_data"; -// -// private CosmosPagedIterable query(SqlQuerySpec query) { -// log.info("executing query:" + query.getQueryText()); -// CosmosContainer container = cosmosClient.getDatabase(dbname).getContainer(tablename); -// return container.queryItems( -// query, new CosmosQueryRequestOptions(), CosmosForwarderCallCounts.class); -// } -// -// public CosmosItemResponse save(CosmosForwarderCallCounts item) { -// CosmosContainer container = cosmosClient.getDatabase(dbname).getContainer(tablename); -// return container.createItem(item); -// } -// -// public List getStationCounts(ZonedDateTime dateFrom) { -// List paramList = new ArrayList<>(); -// paramList.addAll(Arrays.asList(new SqlParameter("@from", dateFrom.toInstant()))); -// SqlQuerySpec q = -// new SqlQuerySpec("SELECT * FROM c where c.timestamp >= @from").setParameters(paramList); -// return query(q).stream().collect(Collectors.toList()); -// } -//} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosStationRepository.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosStationRepository.java index 84cbfc3..0b32abe 100644 --- a/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosStationRepository.java +++ b/src/main/java/it/gov/pagopa/standintechsupport/repository/CosmosStationRepository.java @@ -5,15 +5,14 @@ import com.azure.cosmos.models.*; import com.azure.cosmos.util.CosmosPagedIterable; import it.gov.pagopa.standintechsupport.repository.model.CosmosStandInStation; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; @Slf4j @Component @@ -29,14 +28,13 @@ public class CosmosStationRepository { private CosmosPagedIterable query(SqlQuerySpec query) { log.info("executing query:" + query.getQueryText()); CosmosContainer container = cosmosClient.getDatabase(dbname).getContainer(tablename); - return container.queryItems( - query, new CosmosQueryRequestOptions(), CosmosStandInStation.class); + return container.queryItems(query, new CosmosQueryRequestOptions(), CosmosStandInStation.class); } private void delete(CosmosStandInStation station) { log.info("deleting station:" + station.getStation()); CosmosContainer container = cosmosClient.getDatabase(dbname).getContainer(tablename); - container.deleteItem(station,new CosmosItemRequestOptions()); + container.deleteItem(station, new CosmosItemRequestOptions()); } public CosmosItemResponse save(CosmosStandInStation item) { @@ -52,18 +50,16 @@ public List getStations() { public List getStation(String station) { SqlQuerySpec q = new SqlQuerySpec("SELECT * FROM c where c.station = @station"); List paramList = new ArrayList<>(); - paramList.addAll(Arrays.asList( - new SqlParameter("@station", station) - )); + paramList.addAll(Arrays.asList(new SqlParameter("@station", station))); return query(q.setParameters(paramList)).stream().collect(Collectors.toList()); } public Boolean removeStation(CosmosStandInStation station) { - try{ + try { delete(station); return true; - }catch (Exception e){ - log.error("error removing station",e); + } catch (Exception e) { + log.error("error removing station", e); } return false; } diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/StandInStationsRepository.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/StandInStationsRepository.java deleted file mode 100644 index 467fa7c..0000000 --- a/src/main/java/it/gov/pagopa/standintechsupport/repository/StandInStationsRepository.java +++ /dev/null @@ -1,14 +0,0 @@ -package it.gov.pagopa.standintechsupport.repository; - -import it.gov.pagopa.standintechsupport.repository.entity.StandInStation; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -public interface StandInStationsRepository extends JpaRepository { - @Query("select station from StandInStation") - public List findAllStations(); -} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/entity/BlacklistStation.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/entity/BlacklistStation.java deleted file mode 100644 index 5be6ca2..0000000 --- a/src/main/java/it/gov/pagopa/standintechsupport/repository/entity/BlacklistStation.java +++ /dev/null @@ -1,17 +0,0 @@ -package it.gov.pagopa.standintechsupport.repository.entity; - -import lombok.Data; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; - -@Entity -@Table(name = "STAND_IN_STATIONS_BLACKLIST") -@Data -public class BlacklistStation { - @Id - @Column(name = "STATION_CODE") - private String station; -} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/entity/StandInStation.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/entity/StandInStation.java deleted file mode 100644 index 48d8f6a..0000000 --- a/src/main/java/it/gov/pagopa/standintechsupport/repository/entity/StandInStation.java +++ /dev/null @@ -1,21 +0,0 @@ -package it.gov.pagopa.standintechsupport.repository.entity; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; - -@Entity -@Table(name = "STAND_IN_STATIONS") -@Data -@AllArgsConstructor -@NoArgsConstructor -public class StandInStation { - @Id - @Column(name = "STATION_CODE") - private String station; -} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosForwarderCallCounts.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosForwarderCallCounts.java deleted file mode 100644 index 42033a6..0000000 --- a/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosForwarderCallCounts.java +++ /dev/null @@ -1,24 +0,0 @@ -package it.gov.pagopa.standintechsupport.repository.model; - -import com.fasterxml.jackson.annotation.JsonProperty; -import java.time.Instant; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class CosmosForwarderCallCounts { - private String id; - private String station; - private Instant timestamp; - private Boolean outcome; - - @JsonProperty("PartitionKey") - public String getPartitionKey() { - return timestamp.toString().substring(0, 10); - } -} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosNodeCallCounts.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosNodeCallCounts.java deleted file mode 100644 index 6b435c9..0000000 --- a/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosNodeCallCounts.java +++ /dev/null @@ -1,29 +0,0 @@ -package it.gov.pagopa.standintechsupport.repository.model; - -import com.fasterxml.jackson.annotation.JsonProperty; -import java.time.Instant; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class CosmosNodeCallCounts { - private String id; - private String station; - private Instant timestamp; - private Integer total; - private Integer faults; - - @JsonProperty("PartitionKey") - public String getPartitionKey() { - return timestamp.toString().substring(0, 10); - } - - public double getPerc() { - return ((getFaults() / (double) getTotal()) * 100); - } -} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosStandInStation.java b/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosStandInStation.java index c89afdd..4ac644c 100644 --- a/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosStandInStation.java +++ b/src/main/java/it/gov/pagopa/standintechsupport/repository/model/CosmosStandInStation.java @@ -1,13 +1,12 @@ package it.gov.pagopa.standintechsupport.repository.model; import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.Instant; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import java.time.Instant; - @Data @Builder @NoArgsConstructor @@ -16,6 +15,7 @@ public class CosmosStandInStation { private String id; private String station; private Instant timestamp; + @JsonProperty("PartitionKey") public String getPartitionKey() { return timestamp.toString().substring(0, 10); diff --git a/src/main/java/it/gov/pagopa/standintechsupport/service/SupportService.java b/src/main/java/it/gov/pagopa/standintechsupport/service/SupportService.java index 833fde1..0bb19a0 100644 --- a/src/main/java/it/gov/pagopa/standintechsupport/service/SupportService.java +++ b/src/main/java/it/gov/pagopa/standintechsupport/service/SupportService.java @@ -6,79 +6,75 @@ import it.gov.pagopa.standintechsupport.exception.AppError; import it.gov.pagopa.standintechsupport.exception.AppException; import it.gov.pagopa.standintechsupport.model.DateRequest; -import it.gov.pagopa.standintechsupport.repository.BlacklistStationsRepository; import it.gov.pagopa.standintechsupport.repository.CosmosEventsRepository; import it.gov.pagopa.standintechsupport.repository.CosmosStationRepository; -import it.gov.pagopa.standintechsupport.repository.StandInStationsRepository; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; @Slf4j @Service public class SupportService { - @Value("#{T(java.lang.Integer).parseInt('${date-range-limit}')}") - Integer dateRangeLimit; + @Value("#{T(java.lang.Integer).parseInt('${date-range-limit}')}") + Integer dateRangeLimit; - @Autowired - private BlacklistStationsRepository blacklistStationsRepository; - @Autowired - private CosmosStationRepository standInStationsRepository; - @Autowired - private CosmosEventsRepository cosmosEventsRepository; + @Autowired private CosmosStationRepository standInStationsRepository; + @Autowired private CosmosEventsRepository cosmosEventsRepository; - public ResponseContainer getEvents(Optional station, LocalDate from, LocalDate to) { - DateRequest dateRequest = verifyDate(from, to); - List collect = cosmosEventsRepository.find(station, dateRequest.getFrom(), dateRequest.getTo()).stream().map(ee -> { - return CosmosEventModel.builder() - .id(ee.getId()) - .type(ee.getType()) - .info(ee.getInfo()) - .timestamp(ee.getTimestamp()) - .station(ee.getStation()).build(); - }).collect(Collectors.toList()); - return ResponseContainer.builder().count(collect.size()) - .data(collect) - .dateFrom(dateRequest.getFrom()) - .dateTo(dateRequest.getTo()).build(); - } + public ResponseContainer getEvents(Optional station, LocalDate from, LocalDate to) { + DateRequest dateRequest = verifyDate(from, to); + List collect = + cosmosEventsRepository.find(station, dateRequest.getFrom(), dateRequest.getTo()).stream() + .map( + ee -> { + return CosmosEventModel.builder() + .id(ee.getId()) + .type(ee.getType()) + .info(ee.getInfo()) + .timestamp(ee.getTimestamp()) + .station(ee.getStation()) + .build(); + }) + .collect(Collectors.toList()); + return ResponseContainer.builder() + .count(collect.size()) + .data(collect) + .dateFrom(dateRequest.getFrom()) + .dateTo(dateRequest.getTo()) + .build(); + } - public List getStations() { - return standInStationsRepository.getStations().stream().map(s->StandInStation.builder().station(s.getStation()).timestamp(s.getTimestamp()).build()).collect(Collectors.toList()); - } + public List getStations() { + return standInStationsRepository.getStations().stream() + .map( + s -> + StandInStation.builder() + .station(s.getStation()) + .timestamp(s.getTimestamp()) + .build()) + .collect(Collectors.toList()); + } - public List getBlacklist() { - return blacklistStationsRepository.findAllStations(); + private DateRequest verifyDate(LocalDate dateFrom, LocalDate dateTo) { + if (dateFrom == null && dateTo != null || dateFrom != null && dateTo == null) { + throw new AppException(AppError.BAD_REQUEST, "Date from and date to must be both defined"); + } else if (dateFrom != null && dateTo != null && dateFrom.isAfter(dateTo)) { + throw new AppException(AppError.BAD_REQUEST, "Date from must be before date to"); } - - private DateRequest verifyDate(LocalDate dateFrom, LocalDate dateTo) { - if (dateFrom == null && dateTo != null || dateFrom != null && dateTo == null) { - throw new AppException( - AppError.BAD_REQUEST, - "Date from and date to must be both defined"); - } else if (dateFrom != null && dateTo != null && dateFrom.isAfter(dateTo)) { - throw new AppException( - AppError.BAD_REQUEST, - "Date from must be before date to"); - } - if (dateFrom == null && dateTo == null) { - dateTo = LocalDate.now(); - dateFrom = dateTo.minusDays(dateRangeLimit); - } - if (ChronoUnit.DAYS.between(dateFrom, dateTo) > dateRangeLimit) { - throw new AppException( - AppError.INTERVAL_TOO_LARGE, - dateRangeLimit); - } - return DateRequest.builder().from(dateFrom).to(dateTo).build(); + if (dateFrom == null && dateTo == null) { + dateTo = LocalDate.now(); + dateFrom = dateTo.minusDays(dateRangeLimit); } - + if (ChronoUnit.DAYS.between(dateFrom, dateTo) > dateRangeLimit) { + throw new AppException(AppError.INTERVAL_TOO_LARGE, dateRangeLimit); + } + return DateRequest.builder().from(dateFrom).to(dateTo).build(); + } } diff --git a/src/main/java/it/gov/pagopa/standintechsupport/util/Constants.java b/src/main/java/it/gov/pagopa/standintechsupport/util/Constants.java index c5375e1..15a539d 100644 --- a/src/main/java/it/gov/pagopa/standintechsupport/util/Constants.java +++ b/src/main/java/it/gov/pagopa/standintechsupport/util/Constants.java @@ -4,12 +4,5 @@ @UtilityClass public class Constants { - public static final String HEADER_REQUEST_ID = "X-Request-Id"; - - public static final String EVENT_FORWARDER_CALL = "FORWARDER_CALL"; - public static final String EVENT_FORWARDER_CALL_RESP_SUCCCESS = "FORWARDER_CALL_RESP_SUCCESS"; - public static final String EVENT_FORWARDER_CALL_RESP_ERROR = "FORWARDER_CALL_RESP_ERROR"; - public static final String EVENT_ADD_TO_STANDIN = "ADD_TO_STANDIN"; - public static final String EVENT_REMOVE_FROM_STANDIN = "REMOVE_FROM_STANDIN"; } diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties index caea635..45b0e36 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -1,23 +1,5 @@ # Info info.properties.environment=local -# Logging -logging.level.root=INFO -logging.level.it.gov.pagopa=INFO -# CORS configuration -cors.configuration={"origins": ["*"], "methods": ["*"]} -spring.datasource.url=jdbc:postgresql://pagopa-d-weu-nodo-flexible-postgresql.postgres.database.azure.com:6432/nodo?prepareThreshold=0 -spring.datasource.username=cfg -spring.datasource.password=password - -api-config-cache.base-path= -api-config-cache.api-key= - -cosmos.endpoint= -cosmos.key= - -logging.level.it.gov.pagopa.standinmanager.service=DEBUG - -date-range-limit=100 -aws.region=weu -aws.ses.user=noreply@pagopa.it \ No newline at end of file +cosmos.endpoint=${COSMOS_STANDIN_URL} +cosmos.key=${COSMOS_STANDIN_KEY} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3a8ab8f..601d3a8 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -20,10 +20,10 @@ server.port=8080 logging.level.root=${DEFAULT_LOGGING_LEVEL:INFO} logging.level.it.gov.pagopa=${APP_LOGGING_LEVEL:INFO} # CORS configuration -cors.configuration=${CORS_CONFIGURATION:'{"origins": ["*"], "methods": ["*"]}'} +cors.configuration=${CORS_CONFIGURATION:{"origins": ["*"], "methods": ["*"]}} -cosmos.endpoint=https://localhost:8081 -cosmos.key=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw== +cosmos.endpoint=${COSMOS_ENDPOINT} +cosmos.key=${COSMOS_KEY} cosmos.db.name=standin date-range-limit=10 \ No newline at end of file diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index f7beb7e..d583bec 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -22,9 +22,9 @@ - ${OTEL_SERVICE_NAME} - ${ECS_SERVICE_VERSION} - ${ENV} + ${OTEL_SERVICE_NAME:pagopastandintechsupport} + ${ECS_SERVICE_VERSION:0.0.0} + ${ENV:local} diff --git a/src/test/java/it/gov/pagopa/microservice/ApplicationTest.java b/src/test/java/it/gov/pagopa/microservice/ApplicationTest.java deleted file mode 100644 index 80c6e10..0000000 --- a/src/test/java/it/gov/pagopa/microservice/ApplicationTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package it.gov.pagopa.microservice; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class ApplicationTest { - - @Test - void contextLoads() { - // check only if the context is loaded - assertTrue(true); - } -} diff --git a/src/test/java/it/gov/pagopa/microservice/OpenApiGenerationTest.java b/src/test/java/it/gov/pagopa/microservice/OpenApiGenerationTest.java deleted file mode 100644 index 07f521b..0000000 --- a/src/test/java/it/gov/pagopa/microservice/OpenApiGenerationTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package it.gov.pagopa.microservice; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import com.fasterxml.jackson.databind.ObjectMapper; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; - -@SpringBootTest(classes = Application.class) -@AutoConfigureMockMvc -class OpenApiGenerationTest { - - @Autowired ObjectMapper objectMapper; - - @Autowired private MockMvc mvc; - - @Test - void swaggerSpringPlugin() throws Exception { - mvc.perform(MockMvcRequestBuilders.get("/v3/api-docs").accept(MediaType.APPLICATION_JSON)) - .andExpect(MockMvcResultMatchers.status().is2xxSuccessful()) - .andDo( - (result) -> { - assertNotNull(result); - assertNotNull(result.getResponse()); - final String content = result.getResponse().getContentAsString(); - assertFalse(content.isBlank()); - assertFalse(content.contains("${"), "Generated swagger contains placeholders"); - Object swagger = - objectMapper.readValue(result.getResponse().getContentAsString(), Object.class); - String formatted = - objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(swagger); - Path basePath = Paths.get("openapi/"); - Files.createDirectories(basePath); - Files.write(basePath.resolve("openapi.json"), formatted.getBytes()); - }); - } -} From a8ab30f4734b283aa69c53cadc0ac5b2a07dcbbb Mon Sep 17 00:00:00 2001 From: Lorenzo Catalano Date: Mon, 15 Jan 2024 10:44:41 +0100 Subject: [PATCH 03/10] wip alpha --- pom.xml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 6e2eefd..8e56ff1 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ it.gov.pagopa pagopa-stand-in-technical-support 0.0.0 - Stand in Manager Tech Support + Stand in Tech Support API 11 @@ -50,11 +50,6 @@ spring-boot-starter-data-jpa - - org.springframework.data - spring-data-jpa - - org.springframework.boot spring-boot-starter-cache From 116ccc2b5ed2f397982da9f56839c3c019b98651 Mon Sep 17 00:00:00 2001 From: Lorenzo Catalano Date: Mon, 15 Jan 2024 12:36:35 +0100 Subject: [PATCH 04/10] wip alpha --- openapi/openapi.json | 180 ++++++++++++- pom.xml | 20 ++ .../gov/pagopa/microservice/Application.java | 12 - .../microservice/config/LoggingAspect.java | 152 ----------- .../config/MappingsConfiguration.java | 17 -- .../microservice/config/OpenApiConfig.java | 124 --------- .../microservice/config/RequestFilter.java | 57 ---- .../config/ResponseValidator.java | 52 ---- .../config/WebMvcConfiguration.java | 27 -- .../controller/HomeController.java | 28 -- .../microservice/exception/AppError.java | 20 -- .../microservice/exception/AppException.java | 86 ------ .../microservice/exception/ErrorHandler.java | 249 ------------------ .../model/AppCorsConfiguration.java | 23 -- .../pagopa/microservice/model/AppInfo.java | 22 -- .../microservice/model/ProblemJson.java | 49 ---- .../pagopa/microservice/util/Constants.java | 9 - .../standintechsupport/Application.java | 3 +- .../standintechsupport/Initializer.java | 90 +++++++ .../OpenApiGenerationTest.java | 26 +- src/test/resources/application.properties | 16 +- src/test/resources/logback-spring.xml | 18 ++ 22 files changed, 339 insertions(+), 941 deletions(-) delete mode 100644 src/main/java/it/gov/pagopa/microservice/Application.java delete mode 100644 src/main/java/it/gov/pagopa/microservice/config/LoggingAspect.java delete mode 100644 src/main/java/it/gov/pagopa/microservice/config/MappingsConfiguration.java delete mode 100644 src/main/java/it/gov/pagopa/microservice/config/OpenApiConfig.java delete mode 100644 src/main/java/it/gov/pagopa/microservice/config/RequestFilter.java delete mode 100644 src/main/java/it/gov/pagopa/microservice/config/ResponseValidator.java delete mode 100644 src/main/java/it/gov/pagopa/microservice/config/WebMvcConfiguration.java delete mode 100644 src/main/java/it/gov/pagopa/microservice/controller/HomeController.java delete mode 100644 src/main/java/it/gov/pagopa/microservice/exception/AppError.java delete mode 100644 src/main/java/it/gov/pagopa/microservice/exception/AppException.java delete mode 100644 src/main/java/it/gov/pagopa/microservice/exception/ErrorHandler.java delete mode 100644 src/main/java/it/gov/pagopa/microservice/model/AppCorsConfiguration.java delete mode 100644 src/main/java/it/gov/pagopa/microservice/model/AppInfo.java delete mode 100644 src/main/java/it/gov/pagopa/microservice/model/ProblemJson.java delete mode 100644 src/main/java/it/gov/pagopa/microservice/util/Constants.java create mode 100644 src/test/java/it/gov/pagopa/standintechsupport/Initializer.java create mode 100644 src/test/resources/logback-spring.xml diff --git a/openapi/openapi.json b/openapi/openapi.json index 0967ef4..0cad7a5 100644 --- a/openapi/openapi.json +++ b/openapi/openapi.json @@ -1 +1,179 @@ -{} +{ + "openapi" : "3.0.1", + "info" : { + "title" : "pagopa-stand-in-technical-support", + "description" : "Stand in Tech Support API", + "termsOfService" : "https://www.pagopa.gov.it/", + "version" : "0.0.0" + }, + "servers" : [ { + "url" : "http://localhost", + "description" : "Generated server url" + } ], + "paths" : { + "/events" : { + "get" : { + "tags" : [ "support-controller" ], + "summary" : "Get the list of events", + "operationId" : "getEvents", + "parameters" : [ { + "name" : "station", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + }, { + "name" : "from", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string", + "format" : "date" + } + }, { + "name" : "to", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string", + "format" : "date" + } + } ], + "responses" : { + "200" : { + "description" : "Get the list", + "headers" : { + "X-Request-Id" : { + "description" : "This header identifies the call", + "schema" : { + "type" : "string" + } + } + }, + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ResponseContainer" + } + } + } + }, + "400" : { + "description" : "Invalid request", + "headers" : { + "X-Request-Id" : { + "description" : "This header identifies the call", + "schema" : { + "type" : "string" + } + } + } + } + } + }, + "parameters" : [ { + "name" : "X-Request-Id", + "in" : "header", + "description" : "This header identifies the call, if not passed it is self-generated. This ID is returned in the response.", + "schema" : { + "type" : "string" + } + } ] + }, + "/stations" : { + "get" : { + "tags" : [ "support-controller" ], + "summary" : "Get the list of standin station", + "operationId" : "getStations", + "responses" : { + "200" : { + "description" : "Get the list", + "headers" : { + "X-Request-Id" : { + "description" : "This header identifies the call", + "schema" : { + "type" : "string" + } + } + }, + "content" : { + "application/json" : { + "schema" : { + "type" : "string" + } + } + } + } + } + }, + "parameters" : [ { + "name" : "X-Request-Id", + "in" : "header", + "description" : "This header identifies the call, if not passed it is self-generated. This ID is returned in the response.", + "schema" : { + "type" : "string" + } + } ] + } + }, + "components" : { + "schemas" : { + "CosmosEventModel" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "string" + }, + "station" : { + "type" : "string" + }, + "timestamp" : { + "type" : "string", + "format" : "date-time" + }, + "type" : { + "type" : "string" + }, + "info" : { + "type" : "string" + }, + "date" : { + "type" : "string" + } + } + }, + "ResponseContainer" : { + "type" : "object", + "properties" : { + "dateFrom" : { + "type" : "string", + "format" : "date" + }, + "dateTo" : { + "type" : "string", + "format" : "date" + }, + "count" : { + "type" : "integer", + "format" : "int32" + }, + "data" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/CosmosEventModel" + } + } + } + } + }, + "securitySchemes" : { + "ApiKey" : { + "type" : "apiKey", + "description" : "The API key to access this function app.", + "name" : "Ocp-Apim-Subscription-Key", + "in" : "header" + } + } + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8e56ff1..f884013 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,26 @@ caffeine + + org.testcontainers + testcontainers + 1.17.2 + test + + + org.testcontainers + azure + 1.17.2 + test + + + com.github.terma + javaniotcpproxy + 1.6 + test + + + org.springdoc diff --git a/src/main/java/it/gov/pagopa/microservice/Application.java b/src/main/java/it/gov/pagopa/microservice/Application.java deleted file mode 100644 index 450079b..0000000 --- a/src/main/java/it/gov/pagopa/microservice/Application.java +++ /dev/null @@ -1,12 +0,0 @@ -package it.gov.pagopa.microservice; // TODO: refactor the package - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class Application { - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } -} diff --git a/src/main/java/it/gov/pagopa/microservice/config/LoggingAspect.java b/src/main/java/it/gov/pagopa/microservice/config/LoggingAspect.java deleted file mode 100644 index 64b798f..0000000 --- a/src/main/java/it/gov/pagopa/microservice/config/LoggingAspect.java +++ /dev/null @@ -1,152 +0,0 @@ -package it.gov.pagopa.microservice.config; - -import java.util.Arrays; -import java.util.stream.StreamSupport; -import javax.annotation.PostConstruct; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.AfterReturning; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Pointcut; -import org.slf4j.MDC; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.context.event.EventListener; -import org.springframework.core.env.AbstractEnvironment; -import org.springframework.core.env.EnumerablePropertySource; -import org.springframework.core.env.Environment; -import org.springframework.core.env.MutablePropertySources; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Component; - -@Aspect -@Component -@Slf4j -public class LoggingAspect { - - public static final String START_TIME = "startTime"; - public static final String METHOD = "method"; - public static final String STATUS = "status"; - public static final String CODE = "httpCode"; - public static final String RESPONSE_TIME = "responseTime"; - - @Autowired HttpServletRequest httRequest; - - @Autowired HttpServletResponse httpResponse; - - @Value("${info.application.artifactId}") - private String artifactId; - - @Value("${info.application.version}") - private String version; - - @Value("${info.properties.environment}") - private String environment; - - private static String getExecutionTime() { - String startTime = MDC.get(START_TIME); - if (startTime != null) { - long endTime = System.currentTimeMillis(); - long executionTime = endTime - Long.parseLong(startTime); - return String.valueOf(executionTime); - } - return "1"; - } - - @Pointcut("@within(org.springframework.web.bind.annotation.RestController)") - public void restController() { - // all rest controllers - } - - @Pointcut("@within(org.springframework.stereotype.Repository)") - public void repository() { - // all repository methods - } - - @Pointcut("@within(org.springframework.stereotype.Service)") - public void service() { - // all service methods - } - - /** Log essential info of application during the startup. */ - @PostConstruct - public void logStartup() { - log.info("-> Starting {} version {} - environment {}", artifactId, version, environment); - } - - /** - * If DEBUG log-level is enabled prints the env variables and the application properties. - * - * @param event Context of application - */ - @EventListener - public void handleContextRefresh(ContextRefreshedEvent event) { - final Environment env = event.getApplicationContext().getEnvironment(); - log.debug("Active profiles: {}", Arrays.toString(env.getActiveProfiles())); - final MutablePropertySources sources = ((AbstractEnvironment) env).getPropertySources(); - StreamSupport.stream(sources.spliterator(), false) - .filter(EnumerablePropertySource.class::isInstance) - .map(ps -> ((EnumerablePropertySource) ps).getPropertyNames()) - .flatMap(Arrays::stream) - .distinct() - .filter( - prop -> - !(prop.toLowerCase().contains("credentials") - || prop.toLowerCase().contains("password") - || prop.toLowerCase().contains("pass") - || prop.toLowerCase().contains("pwd") - || prop.toLowerCase().contains("key") - || prop.toLowerCase().contains("secret"))) - .forEach(prop -> log.debug("{}: {}", prop, env.getProperty(prop))); - } - - @Around(value = "restController()") - public Object logApiInvocation(ProceedingJoinPoint joinPoint) throws Throwable { - MDC.put(METHOD, joinPoint.getSignature().getName()); - MDC.put(START_TIME, String.valueOf(System.currentTimeMillis())); - log.info("{} {}", httRequest.getMethod(), httRequest.getRequestURI()); - log.info( - "Invoking API operation {} - args: {}", - joinPoint.getSignature().getName(), - joinPoint.getArgs()); - - Object result = joinPoint.proceed(); - - MDC.put(STATUS, "OK"); - MDC.put(CODE, String.valueOf(httpResponse.getStatus())); - MDC.put(RESPONSE_TIME, getExecutionTime()); - log.info( - "Successful API operation {} - result: {}", joinPoint.getSignature().getName(), result); - MDC.remove(STATUS); - MDC.remove(CODE); - MDC.remove(RESPONSE_TIME); - MDC.remove(START_TIME); - return result; - } - - @AfterReturning(value = "execution(* *..exception.ErrorHandler.*(..))", returning = "result") - public void trowingApiInvocation(JoinPoint joinPoint, ResponseEntity result) { - MDC.put(STATUS, "KO"); - MDC.put(CODE, String.valueOf(result.getStatusCodeValue())); - MDC.put(RESPONSE_TIME, getExecutionTime()); - log.info("Failed API operation {} - error: {}", MDC.get(METHOD), result); - MDC.remove(STATUS); - MDC.remove(CODE); - MDC.remove(RESPONSE_TIME); - MDC.remove(START_TIME); - } - - @Around(value = "repository() || service()") - public Object logTrace(ProceedingJoinPoint joinPoint) throws Throwable { - log.debug( - "Call method {} - args: {}", joinPoint.getSignature().toShortString(), joinPoint.getArgs()); - Object result = joinPoint.proceed(); - log.debug("Return method {} - result: {}", joinPoint.getSignature().toShortString(), result); - return result; - } -} diff --git a/src/main/java/it/gov/pagopa/microservice/config/MappingsConfiguration.java b/src/main/java/it/gov/pagopa/microservice/config/MappingsConfiguration.java deleted file mode 100644 index ee6d624..0000000 --- a/src/main/java/it/gov/pagopa/microservice/config/MappingsConfiguration.java +++ /dev/null @@ -1,17 +0,0 @@ -package it.gov.pagopa.microservice.config; - -import org.modelmapper.ModelMapper; -import org.modelmapper.convention.MatchingStrategies; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class MappingsConfiguration { - - @Bean - ModelMapper modelMapper() { - ModelMapper mapper = new ModelMapper(); - mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT); - return mapper; - } -} diff --git a/src/main/java/it/gov/pagopa/microservice/config/OpenApiConfig.java b/src/main/java/it/gov/pagopa/microservice/config/OpenApiConfig.java deleted file mode 100644 index e387982..0000000 --- a/src/main/java/it/gov/pagopa/microservice/config/OpenApiConfig.java +++ /dev/null @@ -1,124 +0,0 @@ -package it.gov.pagopa.microservice.config; - -import static it.gov.pagopa.microservice.util.Constants.HEADER_REQUEST_ID; - -import io.swagger.v3.oas.models.Components; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.Paths; -import io.swagger.v3.oas.models.headers.Header; -import io.swagger.v3.oas.models.info.Info; -import io.swagger.v3.oas.models.media.StringSchema; -import io.swagger.v3.oas.models.parameters.Parameter; -import io.swagger.v3.oas.models.responses.ApiResponses; -import io.swagger.v3.oas.models.security.SecurityScheme; -import java.util.Collections; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.springdoc.core.customizers.OpenApiCustomiser; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class OpenApiConfig { - - @Bean - public OpenAPI customOpenAPI( - @Value("${info.application.artifactId}") String appName, - @Value("${info.application.description}") String appDescription, - @Value("${info.application.version}") String appVersion) { - return new OpenAPI() - .components( - new Components() - .addSecuritySchemes( - "ApiKey", - new SecurityScheme() - .type(SecurityScheme.Type.APIKEY) - .description("The API key to access this function app.") - .name("Ocp-Apim-Subscription-Key") - .in(SecurityScheme.In.HEADER))) - .info( - new Info() - .title(appName) - .version(appVersion) - .description(appDescription) - .termsOfService("https://www.pagopa.gov.it/")); - } - - @Bean - public OpenApiCustomiser sortOperationsAlphabetically() { - return openApi -> { - Paths paths = - openApi.getPaths().entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .collect( - Paths::new, - (map, item) -> map.addPathItem(item.getKey(), item.getValue()), - Paths::putAll); - - paths.forEach( - (key, value) -> - value - .readOperations() - .forEach( - operation -> { - var responses = - operation.getResponses().entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .collect( - ApiResponses::new, - (map, item) -> - map.addApiResponse(item.getKey(), item.getValue()), - ApiResponses::putAll); - operation.setResponses(responses); - })); - openApi.setPaths(paths); - }; - } - - @Bean - public OpenApiCustomiser addCommonHeaders() { - return openApi -> - openApi - .getPaths() - .forEach( - (key, value) -> { - - // add Request-ID as request header - var header = - Optional.ofNullable(value.getParameters()) - .orElse(Collections.emptyList()) - .parallelStream() - .filter(Objects::nonNull) - .anyMatch(elem -> HEADER_REQUEST_ID.equals(elem.getName())); - if (!header) { - value.addParametersItem( - new Parameter() - .in("header") - .name(HEADER_REQUEST_ID) - .schema(new StringSchema()) - .description( - "This header identifies the call, if not passed it is" - + " self-generated. This ID is returned in the response.")); - } - - // add Request-ID as response header - value - .readOperations() - .forEach( - operation -> - operation - .getResponses() - .values() - .forEach( - response -> - response.addHeaderObject( - HEADER_REQUEST_ID, - new Header() - .schema(new StringSchema()) - .description( - "This header identifies the call")))); - }); - } -} diff --git a/src/main/java/it/gov/pagopa/microservice/config/RequestFilter.java b/src/main/java/it/gov/pagopa/microservice/config/RequestFilter.java deleted file mode 100644 index 237c224..0000000 --- a/src/main/java/it/gov/pagopa/microservice/config/RequestFilter.java +++ /dev/null @@ -1,57 +0,0 @@ -package it.gov.pagopa.microservice.config; - -import static it.gov.pagopa.microservice.util.Constants.HEADER_REQUEST_ID; - -import java.io.IOException; -import java.util.UUID; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; -import org.slf4j.MDC; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.stereotype.Component; - -@Component -@Order(Ordered.HIGHEST_PRECEDENCE) -@Slf4j -public class RequestFilter implements Filter { - - /** - * Get the request ID from the custom header "X-Request-Id" if present, otherwise it generates - * one. Set the X-Request-Id value in the {@code response} and in the MDC - * - * @param request http request - * @param response http response - * @param chain next filter - * @throws IOException if an I/O error occurs during this filter's processing of the request - * @throws ServletException if the processing fails for any other reason - */ - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - try { - HttpServletRequest httRequest = (HttpServletRequest) request; - - // get requestId from header or generate one - String requestId = httRequest.getHeader(HEADER_REQUEST_ID); - if (requestId == null || requestId.isEmpty()) { - requestId = UUID.randomUUID().toString(); - } - - // set requestId in MDC - MDC.put("requestId", requestId); - - // set requestId in the response header - ((HttpServletResponse) response).setHeader(HEADER_REQUEST_ID, requestId); - chain.doFilter(request, response); - } finally { - MDC.clear(); - } - } -} diff --git a/src/main/java/it/gov/pagopa/microservice/config/ResponseValidator.java b/src/main/java/it/gov/pagopa/microservice/config/ResponseValidator.java deleted file mode 100644 index 440d0c5..0000000 --- a/src/main/java/it/gov/pagopa/microservice/config/ResponseValidator.java +++ /dev/null @@ -1,52 +0,0 @@ -package it.gov.pagopa.microservice.config; - -import it.gov.pagopa.microservice.exception.AppException; -import java.util.Set; -import javax.validation.ConstraintViolation; -import javax.validation.Validator; -import org.apache.commons.lang3.StringUtils; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.annotation.AfterReturning; -import org.aspectj.lang.annotation.Aspect; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Component; - -@Aspect -@Component -public class ResponseValidator { - - @Autowired private Validator validator; - - /** - * This method validates the response annotated with the {@link javax.validation.constraints} - * - * @param joinPoint not used - * @param result the response to validate - */ - // TODO: set your package - @AfterReturning( - pointcut = "execution(* it.gov.pagopa.microservice.controller.*.*(..))", - returning = "result") - public void validateResponse(JoinPoint joinPoint, Object result) { - if (result instanceof ResponseEntity) { - validateResponse((ResponseEntity) result); - } - } - - private void validateResponse(ResponseEntity response) { - if (response.getBody() != null) { - Set> validationResults = validator.validate(response.getBody()); - - if (!validationResults.isEmpty()) { - var sb = new StringBuilder(); - for (ConstraintViolation error : validationResults) { - sb.append(error.getPropertyPath()).append(" ").append(error.getMessage()).append(". "); - } - var msg = StringUtils.chop(sb.toString()); - throw new AppException(HttpStatus.INTERNAL_SERVER_ERROR, "Invalid response", msg); - } - } - } -} diff --git a/src/main/java/it/gov/pagopa/microservice/config/WebMvcConfiguration.java b/src/main/java/it/gov/pagopa/microservice/config/WebMvcConfiguration.java deleted file mode 100644 index bcfb55d..0000000 --- a/src/main/java/it/gov/pagopa/microservice/config/WebMvcConfiguration.java +++ /dev/null @@ -1,27 +0,0 @@ -package it.gov.pagopa.microservice.config; - -import com.fasterxml.jackson.databind.ObjectMapper; -import it.gov.pagopa.microservice.model.AppCorsConfiguration; -import lombok.SneakyThrows; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class WebMvcConfiguration implements WebMvcConfigurer { - - @Value("${cors.configuration}") - private String corsConfiguration; - - @SneakyThrows - @Override - public void addCorsMappings(CorsRegistry registry) { - AppCorsConfiguration appCorsConfiguration = - new ObjectMapper().readValue(corsConfiguration, AppCorsConfiguration.class); - registry - .addMapping("/**") - .allowedOrigins(appCorsConfiguration.getOrigins()) - .allowedMethods(appCorsConfiguration.getMethods()); - } -} diff --git a/src/main/java/it/gov/pagopa/microservice/controller/HomeController.java b/src/main/java/it/gov/pagopa/microservice/controller/HomeController.java deleted file mode 100644 index a3b593c..0000000 --- a/src/main/java/it/gov/pagopa/microservice/controller/HomeController.java +++ /dev/null @@ -1,28 +0,0 @@ -package it.gov.pagopa.microservice.controller; - -import io.swagger.v3.oas.annotations.Hidden; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.view.RedirectView; - -@RestController -@Validated -public class HomeController { - - @Value("${server.servlet.context-path}") - String basePath; - - /** - * @return redirect to Swagger page documentation - */ - @Hidden - @GetMapping("") - public RedirectView home() { - if (!basePath.endsWith("/")) { - basePath += "/"; - } - return new RedirectView(basePath + "swagger-ui.html"); - } -} diff --git a/src/main/java/it/gov/pagopa/microservice/exception/AppError.java b/src/main/java/it/gov/pagopa/microservice/exception/AppError.java deleted file mode 100644 index 46a97c8..0000000 --- a/src/main/java/it/gov/pagopa/microservice/exception/AppError.java +++ /dev/null @@ -1,20 +0,0 @@ -package it.gov.pagopa.microservice.exception; - -import lombok.Getter; -import org.springframework.http.HttpStatus; - -@Getter -public enum AppError { - INTERNAL_SERVER_ERROR( - HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error", "Something was wrong"); - - public final HttpStatus httpStatus; - public final String title; - public final String details; - - AppError(HttpStatus httpStatus, String title, String details) { - this.httpStatus = httpStatus; - this.title = title; - this.details = details; - } -} diff --git a/src/main/java/it/gov/pagopa/microservice/exception/AppException.java b/src/main/java/it/gov/pagopa/microservice/exception/AppException.java deleted file mode 100644 index 7922d22..0000000 --- a/src/main/java/it/gov/pagopa/microservice/exception/AppException.java +++ /dev/null @@ -1,86 +0,0 @@ -package it.gov.pagopa.microservice.exception; - -import java.util.Formatter; -import javax.validation.constraints.NotNull; -import lombok.EqualsAndHashCode; -import lombok.Value; -import org.springframework.http.HttpStatus; -import org.springframework.validation.annotation.Validated; - -/** - * Custom exception. - * - *

See {@link ErrorHandler} - */ -@EqualsAndHashCode(callSuper = true) -@Value -@Validated -public class AppException extends RuntimeException { - - /** title returned to the response when this exception occurred */ - String title; - - /** http status returned to the response when this exception occurred */ - HttpStatus httpStatus; - - /** - * @param httpStatus HTTP status returned to the response - * @param title title returned to the response when this exception occurred - * @param message the detail message returend to the response - * @param cause The cause of this {@link AppException} - */ - public AppException( - @NotNull HttpStatus httpStatus, - @NotNull String title, - @NotNull String message, - Throwable cause) { - super(message, cause); - this.title = title; - this.httpStatus = httpStatus; - } - - /** - * @param httpStatus HTTP status returned to the response - * @param title title returned to the response when this exception occurred - * @param message the detail message returend to the response - */ - public AppException( - @NotNull HttpStatus httpStatus, @NotNull String title, @NotNull String message) { - super(message); - this.title = title; - this.httpStatus = httpStatus; - } - - /** - * @param appError Response template returned to the response - * @param args {@link Formatter} replaces the placeholders in "details" string of {@link AppError} - * with the arguments. If there are more arguments than format specifiers, the extra arguments - * are ignored. - */ - public AppException(@NotNull AppError appError, Object... args) { - super(formatDetails(appError, args)); - this.httpStatus = appError.httpStatus; - this.title = appError.title; - } - - /** - * @param appError Response template returned to the response - * @param cause The cause of this {@link AppException} - * @param args Arguments for the details of {@link AppError} replaced by the {@link Formatter}. If - * there are more arguments than format specifiers, the extra arguments are ignored. - */ - public AppException(@NotNull AppError appError, Throwable cause, Object... args) { - super(formatDetails(appError, args), cause); - this.httpStatus = appError.httpStatus; - this.title = appError.title; - } - - private static String formatDetails(AppError appError, Object[] args) { - return String.format(appError.details, args); - } - - @Override - public String toString() { - return "AppException(" + httpStatus + ", " + title + ")" + super.toString(); - } -} diff --git a/src/main/java/it/gov/pagopa/microservice/exception/ErrorHandler.java b/src/main/java/it/gov/pagopa/microservice/exception/ErrorHandler.java deleted file mode 100644 index 3503b24..0000000 --- a/src/main/java/it/gov/pagopa/microservice/exception/ErrorHandler.java +++ /dev/null @@ -1,249 +0,0 @@ -package it.gov.pagopa.microservice.exception; - -import it.gov.pagopa.microservice.model.ProblemJson; -import java.util.ArrayList; -import java.util.List; -import lombok.extern.slf4j.Slf4j; -import org.hibernate.exception.ConstraintViolationException; -import org.springframework.beans.TypeMismatchException; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.validation.FieldError; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.MissingServletRequestParameterException; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.WebRequest; -import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; -import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; - -/** All Exceptions are handled by this class */ -@ControllerAdvice -@Slf4j -public class ErrorHandler extends ResponseEntityExceptionHandler { - - public static final String INTERNAL_SERVER_ERROR = "INTERNAL SERVER ERROR"; - public static final String BAD_REQUEST = "BAD REQUEST"; - public static final String FOREIGN_KEY_VIOLATION = "23503"; - public static final int CHILD_RECORD_VIOLATION = 2292; - - /** - * Handle if the input request is not a valid JSON - * - * @param ex {@link HttpMessageNotReadableException} exception raised - * @param headers of the response - * @param status of the response - * @param request from frontend - * @return a {@link ProblemJson} as response with the cause and with a 400 as HTTP status - */ - @Override - public ResponseEntity handleHttpMessageNotReadable( - HttpMessageNotReadableException ex, - HttpHeaders headers, - HttpStatus status, - WebRequest request) { - log.warn("Input not readable: ", ex); - var errorResponse = - ProblemJson.builder() - .status(HttpStatus.BAD_REQUEST.value()) - .title(BAD_REQUEST) - .detail("Invalid input format") - .build(); - return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); - } - - /** - * Handle if missing some request parameters in the request - * - * @param ex {@link MissingServletRequestParameterException} exception raised - * @param headers of the response - * @param status of the response - * @param request from frontend - * @return a {@link ProblemJson} as response with the cause and with a 400 as HTTP status - */ - @Override - public ResponseEntity handleMissingServletRequestParameter( - MissingServletRequestParameterException ex, - HttpHeaders headers, - HttpStatus status, - WebRequest request) { - log.warn("Missing request parameter: ", ex); - var errorResponse = - ProblemJson.builder() - .status(HttpStatus.BAD_REQUEST.value()) - .title(BAD_REQUEST) - .detail(ex.getMessage()) - .build(); - return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); - } - - /** - * Customize the response for TypeMismatchException. - * - * @param ex the exception - * @param headers the headers to be written to the response - * @param status the selected response status - * @param request the current request - * @return a {@code ResponseEntity} instance - */ - @Override - protected ResponseEntity handleTypeMismatch( - TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { - log.warn("Type mismatch: ", ex); - var errorResponse = - ProblemJson.builder() - .status(HttpStatus.BAD_REQUEST.value()) - .title(BAD_REQUEST) - .detail( - String.format( - "Invalid value %s for property %s", - ex.getValue(), ((MethodArgumentTypeMismatchException) ex).getName())) - .build(); - return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); - } - - /** - * Handle if validation constraints are unsatisfied - * - * @param ex {@link MethodArgumentNotValidException} exception raised - * @param headers of the response - * @param status of the response - * @param request from frontend - * @return a {@link ProblemJson} as response with the cause and with a 400 as HTTP status - */ - @Override - protected ResponseEntity handleMethodArgumentNotValid( - MethodArgumentNotValidException ex, - HttpHeaders headers, - HttpStatus status, - WebRequest request) { - List details = new ArrayList<>(); - for (FieldError error : ex.getBindingResult().getFieldErrors()) { - details.add(error.getField() + ": " + error.getDefaultMessage()); - } - var detailsMessage = String.join(", ", details); - log.warn("Input not valid: " + detailsMessage); - var errorResponse = - ProblemJson.builder() - .status(HttpStatus.BAD_REQUEST.value()) - .title(BAD_REQUEST) - .detail(detailsMessage) - .build(); - return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler({javax.validation.ConstraintViolationException.class}) - public ResponseEntity handleConstraintViolationException( - final javax.validation.ConstraintViolationException ex, final WebRequest request) { - log.warn("Validation Error raised:", ex); - var errorResponse = - ProblemJson.builder() - .status(HttpStatus.BAD_REQUEST.value()) - .title(BAD_REQUEST) - .detail(ex.getMessage()) - .build(); - return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); - } - - /** - * @param ex {@link DataIntegrityViolationException} exception raised when the SQL statement - * cannot be executed - * @param request from frontend - * @return a {@link ProblemJson} as response with the cause and with an appropriated HTTP status - */ - @ExceptionHandler({DataIntegrityViolationException.class}) - public ResponseEntity handleDataIntegrityViolationException( - final DataIntegrityViolationException ex, final WebRequest request) { - ProblemJson errorResponse = null; - - if (ex.getCause() instanceof ConstraintViolationException) { - String sqlState = ((ConstraintViolationException) ex.getCause()).getSQLState(); - var errorCode = - ((ConstraintViolationException) ex.getCause()).getSQLException().getErrorCode(); - // check the reason of ConstraintViolationException: is true if the object is referenced by a - // foreign key - // more info: https://docs.oracle.com/javadb/10.8.3.0/ref/rrefexcept71493.html - if (sqlState.equals(FOREIGN_KEY_VIOLATION)) { - log.warn("Can't delete from Database", ex); - errorResponse = - ProblemJson.builder() - .status(HttpStatus.CONFLICT.value()) - .title("Conflict with the current state of the resource") - .detail("There is a relation with other resource. Delete it first.") - .build(); - } - if (errorCode == CHILD_RECORD_VIOLATION) { - log.warn("Can't update the Database", ex); - errorResponse = - ProblemJson.builder() - .status(HttpStatus.CONFLICT.value()) - .title("Conflict with the current state of the resource") - .detail("There is a relation with other resource. Delete it first.") - .build(); - } - } - - // default response - if (errorResponse == null) { - log.warn("Data Integrity Violation", ex); - errorResponse = - ProblemJson.builder() - .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) - .title(INTERNAL_SERVER_ERROR) - .detail(ex.getMessage()) - .build(); - } - - return new ResponseEntity<>(errorResponse, HttpStatus.valueOf(errorResponse.getStatus())); - } - - /** - * Handle if a {@link AppException} is raised - * - * @param ex {@link AppException} exception raised - * @param request from frontend - * @return a {@link ProblemJson} as response with the cause and with an appropriated HTTP status - */ - @ExceptionHandler({AppException.class}) - public ResponseEntity handleAppException( - final AppException ex, final WebRequest request) { - if (ex.getCause() != null) { - log.warn( - "App Exception raised: " + ex.getMessage() + "\nCause of the App Exception: ", - ex.getCause()); - } else { - log.warn("App Exception raised: {}", ex.getMessage()); - log.debug("Trace error: ", ex); - } - var errorResponse = - ProblemJson.builder() - .status(ex.getHttpStatus().value()) - .title(ex.getTitle()) - .detail(ex.getMessage()) - .build(); - return new ResponseEntity<>(errorResponse, ex.getHttpStatus()); - } - - /** - * Handle if a {@link Exception} is raised - * - * @param ex {@link Exception} exception raised - * @param request from frontend - * @return a {@link ProblemJson} as response with the cause and with 500 as HTTP status - */ - @ExceptionHandler({Exception.class}) - public ResponseEntity handleGenericException( - final Exception ex, final WebRequest request) { - log.error("Generic Exception raised:", ex); - var errorResponse = - ProblemJson.builder() - .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) - .title(INTERNAL_SERVER_ERROR) - .detail(ex.getMessage()) - .build(); - return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); - } -} diff --git a/src/main/java/it/gov/pagopa/microservice/model/AppCorsConfiguration.java b/src/main/java/it/gov/pagopa/microservice/model/AppCorsConfiguration.java deleted file mode 100644 index 8d99b89..0000000 --- a/src/main/java/it/gov/pagopa/microservice/model/AppCorsConfiguration.java +++ /dev/null @@ -1,23 +0,0 @@ -package it.gov.pagopa.microservice.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.ToString; - -@Data -@Builder(toBuilder = true) -@NoArgsConstructor -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@ToString -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonIgnoreProperties(ignoreUnknown = true) -public class AppCorsConfiguration { - - private String[] origins; - private String[] methods; -} diff --git a/src/main/java/it/gov/pagopa/microservice/model/AppInfo.java b/src/main/java/it/gov/pagopa/microservice/model/AppInfo.java deleted file mode 100644 index d381de0..0000000 --- a/src/main/java/it/gov/pagopa/microservice/model/AppInfo.java +++ /dev/null @@ -1,22 +0,0 @@ -package it.gov.pagopa.microservice.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.ToString; - -@Data -@Builder(toBuilder = true) -@NoArgsConstructor -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@ToString -@JsonIgnoreProperties(ignoreUnknown = true) -public class AppInfo { - - private String name; - private String version; - private String environment; -} diff --git a/src/main/java/it/gov/pagopa/microservice/model/ProblemJson.java b/src/main/java/it/gov/pagopa/microservice/model/ProblemJson.java deleted file mode 100644 index 854fb33..0000000 --- a/src/main/java/it/gov/pagopa/microservice/model/ProblemJson.java +++ /dev/null @@ -1,49 +0,0 @@ -package it.gov.pagopa.microservice.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import javax.validation.constraints.Max; -import javax.validation.constraints.Min; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.ToString; - -/** - * Object returned as response in case of an error. - * - *

See {@link it.pagopa.microservice.exception.ErrorHandler} - */ -@Data -@Builder(toBuilder = true) -@NoArgsConstructor -@AllArgsConstructor -@ToString -@JsonIgnoreProperties(ignoreUnknown = true) -public class ProblemJson { - - @JsonProperty("title") - @Schema( - description = - "A short, summary of the problem type. Written in english and readable for engineers" - + " (usually not suited for non technical stakeholders and not localized); example:" - + " Service Unavailable") - private String title; - - @JsonProperty("status") - @Schema( - example = "200", - description = - "The HTTP status code generated by the origin server for this occurrence of the problem.") - @Min(100) - @Max(600) - private Integer status; - - @JsonProperty("detail") - @Schema( - example = "There was an error processing the request", - description = "A human readable explanation specific to this occurrence of the problem.") - private String detail; -} diff --git a/src/main/java/it/gov/pagopa/microservice/util/Constants.java b/src/main/java/it/gov/pagopa/microservice/util/Constants.java deleted file mode 100644 index 510e28a..0000000 --- a/src/main/java/it/gov/pagopa/microservice/util/Constants.java +++ /dev/null @@ -1,9 +0,0 @@ -package it.gov.pagopa.microservice.util; - -import lombok.experimental.UtilityClass; - -@UtilityClass -public class Constants { - - public static final String HEADER_REQUEST_ID = "X-Request-Id"; -} diff --git a/src/main/java/it/gov/pagopa/standintechsupport/Application.java b/src/main/java/it/gov/pagopa/standintechsupport/Application.java index e6ea899..3479ab8 100644 --- a/src/main/java/it/gov/pagopa/standintechsupport/Application.java +++ b/src/main/java/it/gov/pagopa/standintechsupport/Application.java @@ -2,6 +2,7 @@ import com.azure.cosmos.CosmosClient; import com.azure.cosmos.CosmosClientBuilder; +import com.azure.cosmos.DirectConnectionConfig; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -23,6 +24,6 @@ public static void main(String[] args) { @Bean public CosmosClient getCosmosClient() { - return new CosmosClientBuilder().endpoint(cosmosEndpoint).key(cosmosKey).buildClient(); + return new CosmosClientBuilder().endpoint(cosmosEndpoint).key(cosmosKey).directMode(new DirectConnectionConfig()).buildClient(); } } diff --git a/src/test/java/it/gov/pagopa/standintechsupport/Initializer.java b/src/test/java/it/gov/pagopa/standintechsupport/Initializer.java new file mode 100644 index 0000000..c1f4f48 --- /dev/null +++ b/src/test/java/it/gov/pagopa/standintechsupport/Initializer.java @@ -0,0 +1,90 @@ +package it.gov.pagopa.standintechsupport; +import com.github.terma.javaniotcpproxy.StaticTcpProxyConfig; +import com.github.terma.javaniotcpproxy.TcpProxy; +import org.junit.rules.TemporaryFolder; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.testcontainers.containers.CosmosDBEmulatorContainer; +import org.testcontainers.utility.DockerImageName; + +import java.io.FileOutputStream; +import java.net.InetAddress; +import java.nio.file.Path; +import java.security.KeyStore; +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings("resource") +public class Initializer implements ApplicationContextInitializer{ + + public static TemporaryFolder tempFolder = new TemporaryFolder(); + + private static final Integer[] exposedPorts = {8081, 10251, 10252, 10253, 10254}; + + private static CosmosDBEmulatorContainer emulator = null; + + private static final List startedProxies = new ArrayList<>(); + + static { + try { + emulator = new CosmosDBEmulatorContainer( + DockerImageName.parse("mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator")) + .withExposedPorts(exposedPorts) + .withEnv("AZURE_COSMOS_EMULATOR_IP_ADDRESS_OVERRIDE", InetAddress.getLocalHost().getHostAddress()) + .withEnv("AZURE_COSMOS_EMULATOR_PARTITION_COUNT", "3") + .withEnv("AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE", "true"); + emulator.start(); + + // TCP proxy workaround for Cosmos DB Emulator bug, see: https://github.com/testcontainers/testcontainers-java/issues/5518 + Initializer.startTcpProxy(exposedPorts); + + } catch (Exception e) { + e.printStackTrace(); + emulator.stop(); + } + } + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + try { + tempFolder.create(); + Path keyStoreFile = tempFolder.newFile("azure-cosmos-emulator.keystore").toPath(); + KeyStore keyStore = emulator.buildNewKeyStore(); + keyStore.store(new FileOutputStream(keyStoreFile.toFile()), emulator.getEmulatorKey().toCharArray()); + + System.setProperty("javax.net.ssl.trustStore", keyStoreFile.toString()); + System.setProperty("javax.net.ssl.trustStorePassword", emulator.getEmulatorKey()); + System.setProperty("javax.net.ssl.trustStoreType", "PKCS12"); + + TestPropertyValues.of( + "cosmos.endpoint=" + emulator.getEmulatorEndpoint(), + "cosmos.key=" + emulator.getEmulatorKey() + ).applyTo(applicationContext.getEnvironment()); + + } + catch (Exception e) { + e.printStackTrace(); + } + + } + + public static CosmosDBEmulatorContainer getEmulator() { + return emulator; + } + + public static List getStartedProxies() { + return startedProxies; + } + + private static void startTcpProxy(Integer... ports) { + for (Integer port: ports) { + StaticTcpProxyConfig tcpProxyConfig = new StaticTcpProxyConfig(port, emulator.getHost(), emulator.getMappedPort(port)); + tcpProxyConfig.setWorkerCount(1); + TcpProxy tcpProxy = new TcpProxy(tcpProxyConfig); + tcpProxy.start(); + startedProxies.add(tcpProxy); + } + } + +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/standintechsupport/OpenApiGenerationTest.java b/src/test/java/it/gov/pagopa/standintechsupport/OpenApiGenerationTest.java index 543cdd4..0c0172c 100644 --- a/src/test/java/it/gov/pagopa/standintechsupport/OpenApiGenerationTest.java +++ b/src/test/java/it/gov/pagopa/standintechsupport/OpenApiGenerationTest.java @@ -1,32 +1,44 @@ package it.gov.pagopa.standintechsupport; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; - import com.fasterxml.jackson.databind.ObjectMapper; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.testcontainers.containers.CosmosDBEmulatorContainer; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; @SpringBootTest(classes = Application.class) @AutoConfigureMockMvc +@ContextConfiguration(initializers = {Initializer.class}) class OpenApiGenerationTest { @Autowired ObjectMapper objectMapper; +private static final CosmosDBEmulatorContainer cosmos = Initializer.getEmulator(); + + @Autowired private MockMvc mvc; @Test void swaggerSpringPlugin() throws Exception { - mvc.perform(MockMvcRequestBuilders.get("/v3/api-docs").accept(MediaType.APPLICATION_JSON)) + + cosmos.start(); + String emulatorEndpoint = cosmos.getEmulatorEndpoint(); + cosmos.getEmulatorKey(); + + mvc.perform(MockMvcRequestBuilders.get("/v3/api-docs").accept(MediaType.APPLICATION_JSON)) .andExpect(MockMvcResultMatchers.status().is2xxSuccessful()) .andDo( (result) -> { diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index cd4c92f..7be6a74 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -1,7 +1,13 @@ # Info +info.application.artifactId=@project.artifactId@ +info.application.version=@project.version@ +info.application.description=@project.description@ info.properties.environment=test -# logging -logging.level.root=INFO -logging.level.it.gov.pagopa=INFO -# CORS configuration -cors.configuration={"origins": ["*"], "methods": ["*"]} + +cors.configuration=${CORS_CONFIGURATION:{"origins": ["*"], "methods": ["*"]}} + +cosmos.endpoint=cosmos.endpoint +cosmos.key=cosmos.key +cosmos.db.name=standin + +date-range-limit=10 \ No newline at end of file diff --git a/src/test/resources/logback-spring.xml b/src/test/resources/logback-spring.xml new file mode 100644 index 0000000..09c6697 --- /dev/null +++ b/src/test/resources/logback-spring.xml @@ -0,0 +1,18 @@ + + + + + + + + + + %clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m %clr(%mdc){magenta}%n%wEx + + + + + + + + From c1fe8f7e0fd4224a5f2b41e415911ca02cb25f63 Mon Sep 17 00:00:00 2001 From: Lorenzo Catalano Date: Mon, 15 Jan 2024 16:42:44 +0100 Subject: [PATCH 05/10] wip alpha --- openapi/openapi.json | 188 +----------------- .../pagopa/standintechsupport/ApiTest.java | 113 +++++++++++ .../OpenApiGenerationTest.java | 5 - 3 files changed, 124 insertions(+), 182 deletions(-) create mode 100644 src/test/java/it/gov/pagopa/standintechsupport/ApiTest.java diff --git a/openapi/openapi.json b/openapi/openapi.json index 0cad7a5..e193a19 100644 --- a/openapi/openapi.json +++ b/openapi/openapi.json @@ -1,179 +1,13 @@ { - "openapi" : "3.0.1", - "info" : { - "title" : "pagopa-stand-in-technical-support", - "description" : "Stand in Tech Support API", - "termsOfService" : "https://www.pagopa.gov.it/", - "version" : "0.0.0" - }, - "servers" : [ { - "url" : "http://localhost", - "description" : "Generated server url" - } ], - "paths" : { - "/events" : { - "get" : { - "tags" : [ "support-controller" ], - "summary" : "Get the list of events", - "operationId" : "getEvents", - "parameters" : [ { - "name" : "station", - "in" : "query", - "required" : false, - "schema" : { - "type" : "string" - } - }, { - "name" : "from", - "in" : "query", - "required" : false, - "schema" : { - "type" : "string", - "format" : "date" - } - }, { - "name" : "to", - "in" : "query", - "required" : false, - "schema" : { - "type" : "string", - "format" : "date" - } - } ], - "responses" : { - "200" : { - "description" : "Get the list", - "headers" : { - "X-Request-Id" : { - "description" : "This header identifies the call", - "schema" : { - "type" : "string" - } - } - }, - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ResponseContainer" - } - } - } - }, - "400" : { - "description" : "Invalid request", - "headers" : { - "X-Request-Id" : { - "description" : "This header identifies the call", - "schema" : { - "type" : "string" - } - } - } - } - } - }, - "parameters" : [ { - "name" : "X-Request-Id", - "in" : "header", - "description" : "This header identifies the call, if not passed it is self-generated. This ID is returned in the response.", - "schema" : { - "type" : "string" - } - } ] - }, - "/stations" : { - "get" : { - "tags" : [ "support-controller" ], - "summary" : "Get the list of standin station", - "operationId" : "getStations", - "responses" : { - "200" : { - "description" : "Get the list", - "headers" : { - "X-Request-Id" : { - "description" : "This header identifies the call", - "schema" : { - "type" : "string" - } - } - }, - "content" : { - "application/json" : { - "schema" : { - "type" : "string" - } - } - } - } - } - }, - "parameters" : [ { - "name" : "X-Request-Id", - "in" : "header", - "description" : "This header identifies the call, if not passed it is self-generated. This ID is returned in the response.", - "schema" : { - "type" : "string" - } - } ] - } - }, - "components" : { - "schemas" : { - "CosmosEventModel" : { - "type" : "object", - "properties" : { - "id" : { - "type" : "string" - }, - "station" : { - "type" : "string" - }, - "timestamp" : { - "type" : "string", - "format" : "date-time" - }, - "type" : { - "type" : "string" - }, - "info" : { - "type" : "string" - }, - "date" : { - "type" : "string" - } - } - }, - "ResponseContainer" : { - "type" : "object", - "properties" : { - "dateFrom" : { - "type" : "string", - "format" : "date" - }, - "dateTo" : { - "type" : "string", - "format" : "date" - }, - "count" : { - "type" : "integer", - "format" : "int32" - }, - "data" : { - "type" : "array", - "items" : { - "$ref" : "#/components/schemas/CosmosEventModel" - } - } - } - } - }, - "securitySchemes" : { - "ApiKey" : { - "type" : "apiKey", - "description" : "The API key to access this function app.", - "name" : "Ocp-Apim-Subscription-Key", - "in" : "header" - } - } - } + "dateFrom" : "2024-01-05", + "dateTo" : "2024-01-15", + "count" : 1, + "data" : [ { + "id" : "4eeb2399-1393-4eb3-ac34-e82dc63efe56", + "station" : "test", + "timestamp" : "2024-01-15T11:51:12.917986400Z", + "type" : "TEST", + "info" : "test", + "date" : "2024-01-15T11:51:12.917986400Z" + } ] } \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/standintechsupport/ApiTest.java b/src/test/java/it/gov/pagopa/standintechsupport/ApiTest.java new file mode 100644 index 0000000..634e286 --- /dev/null +++ b/src/test/java/it/gov/pagopa/standintechsupport/ApiTest.java @@ -0,0 +1,113 @@ +package it.gov.pagopa.standintechsupport; + +import com.azure.cosmos.CosmosAsyncClient; +import com.azure.cosmos.CosmosClient; +import com.azure.cosmos.CosmosClientBuilder; +import com.fasterxml.jackson.databind.ObjectMapper; +import it.gov.pagopa.standintechsupport.controller.model.ResponseContainer; +import it.gov.pagopa.standintechsupport.controller.model.StandInStation; +import it.gov.pagopa.standintechsupport.repository.model.CosmosEvent; +import it.gov.pagopa.standintechsupport.repository.model.CosmosStandInStation; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.testcontainers.containers.CosmosDBEmulatorContainer; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Instant; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest(classes = Application.class) +@AutoConfigureMockMvc +@ContextConfiguration(initializers = {Initializer.class}) +class ApiTest { + + @Autowired ObjectMapper objectMapper; + + @Autowired private MockMvc mvc; + + private static final CosmosDBEmulatorContainer emulator = Initializer.getEmulator(); + + @Test + void swaggerSpringPlugin() throws Exception { + + CosmosClient client = new CosmosClientBuilder().directMode().endpointDiscoveryEnabled(false) + .endpoint(emulator.getEmulatorEndpoint()).key(emulator.getEmulatorKey()).buildClient(); + client.createDatabaseIfNotExists("standin"); + client.getDatabase("standin").createContainer("events","/PartitionKey"); + client.getDatabase("standin").createContainer("stand_in_stations","/PartitionKey"); + + client.getDatabase("standin").getContainer("events").createItem( + CosmosEvent.builder().id(UUID.randomUUID().toString()).type("TEST").info("test").station("test").timestamp(Instant.now()).build() + ); + client.getDatabase("standin").getContainer("events").createItem( + CosmosEvent.builder().id(UUID.randomUUID().toString()).type("TEST").info("test").station("test2").timestamp(Instant.now()).build() + ); + client.getDatabase("standin").getContainer("stand_in_stations").createItem( + CosmosStandInStation.builder().id(UUID.randomUUID().toString()).station("test1").timestamp(Instant.now()).build() + ); + client.getDatabase("standin").getContainer("stand_in_stations").createItem( + CosmosStandInStation.builder().id(UUID.randomUUID().toString()).station("test2").timestamp(Instant.now()).build() + ); + client.getDatabase("standin").getContainer("stand_in_stations").createItem( + CosmosStandInStation.builder().id(UUID.randomUUID().toString()).station("test3").timestamp(Instant.now()).build() + ); + client.getDatabase("standin").getContainer("stand_in_stations").createItem( + CosmosStandInStation.builder().id(UUID.randomUUID().toString()).station("test4").timestamp(Instant.now()).build() + ); + client.getDatabase("standin").getContainer("stand_in_stations").createItem( + CosmosStandInStation.builder().id(UUID.randomUUID().toString()).station("test5").timestamp(Instant.now()).build() + ); + + + mvc.perform(MockMvcRequestBuilders.get("/events").accept(MediaType.APPLICATION_JSON)) + .andExpect(MockMvcResultMatchers.status().is2xxSuccessful()) + .andDo( + (result) -> { + assertNotNull(result); + assertNotNull(result.getResponse()); + final String content = result.getResponse().getContentAsString(); + assertFalse(content.isBlank()); + ResponseContainer res = + objectMapper.readValue(result.getResponse().getContentAsString(), ResponseContainer.class); + assertEquals(res.getData().size(),2); + }); + + mvc.perform(MockMvcRequestBuilders.get("/events").queryParam("station","test").accept(MediaType.APPLICATION_JSON)) + .andExpect(MockMvcResultMatchers.status().is2xxSuccessful()) + .andDo( + (result) -> { + assertNotNull(result); + assertNotNull(result.getResponse()); + final String content = result.getResponse().getContentAsString(); + assertFalse(content.isBlank()); + ResponseContainer res = + objectMapper.readValue(result.getResponse().getContentAsString(), ResponseContainer.class); + assertEquals(res.getData().size(),1); + }); + + mvc.perform(MockMvcRequestBuilders.get("/stations").accept(MediaType.APPLICATION_JSON)) + .andExpect(MockMvcResultMatchers.status().is2xxSuccessful()) + .andDo( + (result) -> { + assertNotNull(result); + assertNotNull(result.getResponse()); + final String content = result.getResponse().getContentAsString(); + assertFalse(content.isBlank()); + List res = + objectMapper.readValue(result.getResponse().getContentAsString(), List.class); + assertEquals(res.size(),5); + }); + } +} diff --git a/src/test/java/it/gov/pagopa/standintechsupport/OpenApiGenerationTest.java b/src/test/java/it/gov/pagopa/standintechsupport/OpenApiGenerationTest.java index 0c0172c..c53ab17 100644 --- a/src/test/java/it/gov/pagopa/standintechsupport/OpenApiGenerationTest.java +++ b/src/test/java/it/gov/pagopa/standintechsupport/OpenApiGenerationTest.java @@ -33,11 +33,6 @@ class OpenApiGenerationTest { @Test void swaggerSpringPlugin() throws Exception { - - cosmos.start(); - String emulatorEndpoint = cosmos.getEmulatorEndpoint(); - cosmos.getEmulatorKey(); - mvc.perform(MockMvcRequestBuilders.get("/v3/api-docs").accept(MediaType.APPLICATION_JSON)) .andExpect(MockMvcResultMatchers.status().is2xxSuccessful()) .andDo( From bf6b62233cb950c3d54d163d469e9607ef935202 Mon Sep 17 00:00:00 2001 From: Lorenzo Catalano Date: Tue, 16 Jan 2024 09:28:19 +0100 Subject: [PATCH 06/10] infra --- infra/04_apim_api.tf | 8 +- infra/99_locals.tf | 2 +- infra/env/weu-dev/backend.tfvars | 2 +- infra/env/weu-dev/terraform.tfvars | 4 +- infra/env/weu-prod/backend.tfvars | 2 +- infra/env/weu-prod/terraform.tfvars | 4 +- infra/env/weu-uat/backend.tfvars | 2 +- infra/env/weu-uat/terraform.tfvars | 4 +- infra/policy/_base_policy.xml | 2 +- openapi/openapi.json | 188 ++++++++++++++++++++++++++-- 10 files changed, 192 insertions(+), 26 deletions(-) diff --git a/infra/04_apim_api.tf b/infra/04_apim_api.tf index b24ae4d..5ff47b4 100644 --- a/infra/04_apim_api.tf +++ b/infra/04_apim_api.tf @@ -1,9 +1,9 @@ locals { - repo_name = "TODO" # TODO add the name of the repository + repo_name = "pagopa-stand-in-technical-support" - display_name = "TODO" # TODO - description = "TODO" # TODO - path = "TODO" # TODO add your base path + display_name = "Stand-in Technical Support" + description = "API Assistenza Stand-in" + path = "technical-support/stand-in/api" host = "api.${var.apim_dns_zone_prefix}.${var.external_domain}" hostname = var.hostname diff --git a/infra/99_locals.tf b/infra/99_locals.tf index 5ed42d0..9c61db3 100644 --- a/infra/99_locals.tf +++ b/infra/99_locals.tf @@ -4,7 +4,7 @@ locals { apim = { name = "${local.product}-apim" rg = "${local.product}-api-rg" - product_id = "TODO" # TODO product id to import from pagopa-infra + product_id = "pagopa-stand-in-technical-support" } } diff --git a/infra/env/weu-dev/backend.tfvars b/infra/env/weu-dev/backend.tfvars index 619395b..a889c1d 100644 --- a/infra/env/weu-dev/backend.tfvars +++ b/infra/env/weu-dev/backend.tfvars @@ -1,4 +1,4 @@ resource_group_name = "io-infra-rg" storage_account_name = "pagopainfraterraformdev" container_name = "azurermstate" -key = ".infra.tfstate" # TODO +key = "pagopa-node-technical-support-worker.infra.tfstate" diff --git a/infra/env/weu-dev/terraform.tfvars b/infra/env/weu-dev/terraform.tfvars index 63a0705..6bf5938 100644 --- a/infra/env/weu-dev/terraform.tfvars +++ b/infra/env/weu-dev/terraform.tfvars @@ -6,10 +6,10 @@ tags = { CreatedBy = "Terraform" Environment = "Dev" Owner = "pagoPA" - Source = "https://github.com/pagopa/your-repository" # TODO + Source = "https://github.com/pagopa/pagopa-stand-in-technical-support" CostCenter = "TS310 - PAGAMENTI & SERVIZI" } apim_dns_zone_prefix = "dev.platform" external_domain = "pagopa.it" -hostname = "weudev..internal.dev.platform.pagopa.it" # TODO +hostname = "weudev.nodo.internal.dev.platform.pagopa.it" diff --git a/infra/env/weu-prod/backend.tfvars b/infra/env/weu-prod/backend.tfvars index dac1727..94ab732 100644 --- a/infra/env/weu-prod/backend.tfvars +++ b/infra/env/weu-prod/backend.tfvars @@ -1,4 +1,4 @@ resource_group_name = "io-infra-rg" storage_account_name = "pagopainfraterraformprod" container_name = "azurermstate" -key = ".infra.tfstate" # TODO +key = "pagopa-node-technical-support-worker.infra.tfstate" diff --git a/infra/env/weu-prod/terraform.tfvars b/infra/env/weu-prod/terraform.tfvars index 77f85af..c8c3356 100644 --- a/infra/env/weu-prod/terraform.tfvars +++ b/infra/env/weu-prod/terraform.tfvars @@ -6,10 +6,10 @@ tags = { CreatedBy = "Terraform" Environment = "Prod" Owner = "pagoPA" - Source = "https://github.com/pagopa/your-repository" # TODO + Source = "https://github.com/pagopa/pagopa-stand-in-technical-suppor" CostCenter = "TS310 - PAGAMENTI & SERVIZI" } apim_dns_zone_prefix = "platform" external_domain = "pagopa.it" -hostname = "weuprod..internal.platform.pagopa.it" # TODO +hostname = "weuprod.nodo.internal.platform.pagopa.it" diff --git a/infra/env/weu-uat/backend.tfvars b/infra/env/weu-uat/backend.tfvars index 6f406b1..32c5214 100644 --- a/infra/env/weu-uat/backend.tfvars +++ b/infra/env/weu-uat/backend.tfvars @@ -1,4 +1,4 @@ resource_group_name = "io-infra-rg" storage_account_name = "pagopainfraterraformuat" container_name = "azurermstate" -key = ".infra.tfstate" # TODO +key = "pagopa-node-technical-support-worker.infra.tfstate" diff --git a/infra/env/weu-uat/terraform.tfvars b/infra/env/weu-uat/terraform.tfvars index e8160f1..134583d 100644 --- a/infra/env/weu-uat/terraform.tfvars +++ b/infra/env/weu-uat/terraform.tfvars @@ -6,10 +6,10 @@ tags = { CreatedBy = "Terraform" Environment = "Uat" Owner = "pagoPA" - Source = "https://github.com/pagopa/your-repository" # TODO + Source = "https://github.com/pagopa/pagopa-stand-in-technical-suppor" CostCenter = "TS310 - PAGAMENTI & SERVIZI" } apim_dns_zone_prefix = "uat.platform" external_domain = "pagopa.it" -hostname = "weuuat..internal.uat.platform.pagopa.it" # TODO +hostname = "weuuat.nodo.internal.uat.platform.pagopa.it" diff --git a/infra/policy/_base_policy.xml b/infra/policy/_base_policy.xml index e3b583c..8bc4d42 100644 --- a/infra/policy/_base_policy.xml +++ b/infra/policy/_base_policy.xml @@ -1,7 +1,7 @@ - + diff --git a/openapi/openapi.json b/openapi/openapi.json index e193a19..0cad7a5 100644 --- a/openapi/openapi.json +++ b/openapi/openapi.json @@ -1,13 +1,179 @@ { - "dateFrom" : "2024-01-05", - "dateTo" : "2024-01-15", - "count" : 1, - "data" : [ { - "id" : "4eeb2399-1393-4eb3-ac34-e82dc63efe56", - "station" : "test", - "timestamp" : "2024-01-15T11:51:12.917986400Z", - "type" : "TEST", - "info" : "test", - "date" : "2024-01-15T11:51:12.917986400Z" - } ] + "openapi" : "3.0.1", + "info" : { + "title" : "pagopa-stand-in-technical-support", + "description" : "Stand in Tech Support API", + "termsOfService" : "https://www.pagopa.gov.it/", + "version" : "0.0.0" + }, + "servers" : [ { + "url" : "http://localhost", + "description" : "Generated server url" + } ], + "paths" : { + "/events" : { + "get" : { + "tags" : [ "support-controller" ], + "summary" : "Get the list of events", + "operationId" : "getEvents", + "parameters" : [ { + "name" : "station", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + }, { + "name" : "from", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string", + "format" : "date" + } + }, { + "name" : "to", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string", + "format" : "date" + } + } ], + "responses" : { + "200" : { + "description" : "Get the list", + "headers" : { + "X-Request-Id" : { + "description" : "This header identifies the call", + "schema" : { + "type" : "string" + } + } + }, + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ResponseContainer" + } + } + } + }, + "400" : { + "description" : "Invalid request", + "headers" : { + "X-Request-Id" : { + "description" : "This header identifies the call", + "schema" : { + "type" : "string" + } + } + } + } + } + }, + "parameters" : [ { + "name" : "X-Request-Id", + "in" : "header", + "description" : "This header identifies the call, if not passed it is self-generated. This ID is returned in the response.", + "schema" : { + "type" : "string" + } + } ] + }, + "/stations" : { + "get" : { + "tags" : [ "support-controller" ], + "summary" : "Get the list of standin station", + "operationId" : "getStations", + "responses" : { + "200" : { + "description" : "Get the list", + "headers" : { + "X-Request-Id" : { + "description" : "This header identifies the call", + "schema" : { + "type" : "string" + } + } + }, + "content" : { + "application/json" : { + "schema" : { + "type" : "string" + } + } + } + } + } + }, + "parameters" : [ { + "name" : "X-Request-Id", + "in" : "header", + "description" : "This header identifies the call, if not passed it is self-generated. This ID is returned in the response.", + "schema" : { + "type" : "string" + } + } ] + } + }, + "components" : { + "schemas" : { + "CosmosEventModel" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "string" + }, + "station" : { + "type" : "string" + }, + "timestamp" : { + "type" : "string", + "format" : "date-time" + }, + "type" : { + "type" : "string" + }, + "info" : { + "type" : "string" + }, + "date" : { + "type" : "string" + } + } + }, + "ResponseContainer" : { + "type" : "object", + "properties" : { + "dateFrom" : { + "type" : "string", + "format" : "date" + }, + "dateTo" : { + "type" : "string", + "format" : "date" + }, + "count" : { + "type" : "integer", + "format" : "int32" + }, + "data" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/CosmosEventModel" + } + } + } + } + }, + "securitySchemes" : { + "ApiKey" : { + "type" : "apiKey", + "description" : "The API key to access this function app.", + "name" : "Ocp-Apim-Subscription-Key", + "in" : "header" + } + } + } } \ No newline at end of file From 4f463f8386fd5ae3b56b3d98f25c289814bfda36 Mon Sep 17 00:00:00 2001 From: Lorenzo Catalano Date: Tue, 16 Jan 2024 09:35:32 +0100 Subject: [PATCH 07/10] infra --- .github/workflows/code_review.yml | 2 +- .github/workflows/deploy_with_github_runner.yml | 2 +- .identity/env/dev/backend.tfvars | 2 +- .identity/env/dev/terraform.tfvars | 2 +- .identity/env/prod/backend.tfvars | 2 +- .identity/env/prod/terraform.tfvars | 2 +- .identity/env/uat/backend.tfvars | 2 +- .identity/env/uat/terraform.tfvars | 2 +- .opex/env/prod/backend.tfvars | 2 +- CODEOWNERS | 3 +-- README.md | 4 ++-- docker/docker-compose.yml | 2 +- integration-test/src/package.json | 2 +- .../java/it/gov/pagopa/standintechsupport/Application.java | 2 +- .../pagopa/standintechsupport/config/ResponseValidator.java | 3 +-- 15 files changed, 16 insertions(+), 18 deletions(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 3ea4730..3f2650d 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -18,7 +18,7 @@ on: workflow_dispatch: env: - PROJECT_KEY: # TODO + PROJECT_KEY: pagopa-stand-in-technical-support permissions: id-token: write diff --git a/.github/workflows/deploy_with_github_runner.yml b/.github/workflows/deploy_with_github_runner.yml index 58fe083..d911499 100644 --- a/.github/workflows/deploy_with_github_runner.yml +++ b/.github/workflows/deploy_with_github_runner.yml @@ -9,7 +9,7 @@ on: type: string env: - APP_NAME: # TODO + APP_NAME: stand-in-node-technicalsupport permissions: diff --git a/.identity/env/dev/backend.tfvars b/.identity/env/dev/backend.tfvars index 9572c01..a47b12b 100644 --- a/.identity/env/dev/backend.tfvars +++ b/.identity/env/dev/backend.tfvars @@ -1,4 +1,4 @@ resource_group_name = "io-infra-rg" storage_account_name = "pagopainfraterraformdev" container_name = "azurermstate" -key = "pagopa-stand-in-tech-support.tfstate" +key = "pagopa-stand-in-technical-support.tfstate" diff --git a/.identity/env/dev/terraform.tfvars b/.identity/env/dev/terraform.tfvars index 3345e07..0486ec8 100644 --- a/.identity/env/dev/terraform.tfvars +++ b/.identity/env/dev/terraform.tfvars @@ -6,6 +6,6 @@ tags = { CreatedBy = "Terraform" Environment = "Dev" Owner = "pagoPA" - Source = "https://github.com/pagopa/your-repository" # TODO + Source = "https://github.com/pagopa/pagopa-stand-in-technical-support" CostCenter = "TS310 - PAGAMENTI & SERVIZI" } diff --git a/.identity/env/prod/backend.tfvars b/.identity/env/prod/backend.tfvars index d8d402c..466ab17 100644 --- a/.identity/env/prod/backend.tfvars +++ b/.identity/env/prod/backend.tfvars @@ -1,4 +1,4 @@ resource_group_name = "io-infra-rg" storage_account_name = "pagopainfraterraformprod" container_name = "azurermstate" -key = ".tfstate" # TODO +key = "pagopa-stand-in-technical-support.tfstate" diff --git a/.identity/env/prod/terraform.tfvars b/.identity/env/prod/terraform.tfvars index ee41cf5..ca32b9a 100644 --- a/.identity/env/prod/terraform.tfvars +++ b/.identity/env/prod/terraform.tfvars @@ -6,6 +6,6 @@ tags = { CreatedBy = "Terraform" Environment = "Prod" Owner = "pagoPA" - Source = "https://github.com/pagopa/your-repository" # TODO + Source = "https://github.com/pagopa/pagopa-stand-in-technical-support" CostCenter = "TS310 - PAGAMENTI & SERVIZI" } diff --git a/.identity/env/uat/backend.tfvars b/.identity/env/uat/backend.tfvars index 502eaf6..1cdff3a 100644 --- a/.identity/env/uat/backend.tfvars +++ b/.identity/env/uat/backend.tfvars @@ -1,4 +1,4 @@ resource_group_name = "io-infra-rg" storage_account_name = "pagopainfraterraformuat" container_name = "azurermstate" -key = ".tfstate" # TODO +key = "pagopa-stand-in-technical-support.tfstate" diff --git a/.identity/env/uat/terraform.tfvars b/.identity/env/uat/terraform.tfvars index 86ec8fc..85b7807 100644 --- a/.identity/env/uat/terraform.tfvars +++ b/.identity/env/uat/terraform.tfvars @@ -6,6 +6,6 @@ tags = { CreatedBy = "Terraform" Environment = "Uat" Owner = "pagoPA" - Source = "https://github.com/pagopa/your-repository" # TODO + Source = "https://github.com/pagopa/pagopa-stand-in-technical-support" CostCenter = "TS310 - PAGAMENTI & SERVIZI" } diff --git a/.opex/env/prod/backend.tfvars b/.opex/env/prod/backend.tfvars index ae0e6e4..43396ad 100644 --- a/.opex/env/prod/backend.tfvars +++ b/.opex/env/prod/backend.tfvars @@ -1,4 +1,4 @@ resource_group_name = "io-infra-rg" storage_account_name = "pagopainfraterraformprod" container_name = "azurermstate" -key = "opex..terraform.tfstate" #TODO +key = "opex.pagopa-stand-in-technical-support.terraform.tfstate" #TODO diff --git a/CODEOWNERS b/CODEOWNERS index bac36ae..50852b6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,4 +1,3 @@ # see https://help.github.com/en/articles/about-code-owners#example-of-a-codeowners-file -* @pagopa/pagopa-tech -# TODO: set your codeowners +* @pagopa/pagopa-team-core @lorenzo-catalano diff --git a/README.md b/README.md index 4e0f850..87d35ba 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # StandIn Tech Support API -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=TODO-set-your-id&metric=alert_status)](https://sonarcloud.io/dashboard?id=TODO-set-your-id) -[![Integration Tests](https://github.com/pagopa//actions/workflows/integration_test.yml/badge.svg?branch=main)](https://github.com/pagopa//actions/workflows/integration_test.yml) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=pagopa-stand-in-technical-support&metric=alert_status)](https://sonarcloud.io/dashboard?id=pagopa-stand-in-technical-support) +[![Integration Tests](https://github.com/pagopa/pagopa-stand-in-technical-support/actions/workflows/integration_test.yml/badge.svg?branch=main)](https://github.com/pagopa/pagopa-stand-in-technical-support/actions/workflows/integration_test.yml) Monitors the events of nodo-dei-pagamenti for station problems and activates/deactivates the standIn for that station. diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index d119b75..ff7ad45 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.8' services: app: - container_name: 'service' # TODO + container_name: 'standin-ts' image: ${image} platform: linux/amd64 build: diff --git a/integration-test/src/package.json b/integration-test/src/package.json index 391c883..1136f7f 100644 --- a/integration-test/src/package.json +++ b/integration-test/src/package.json @@ -1,5 +1,5 @@ { - "name": "your-project-name", // TODO: set your name + "name": "pagopa-stand-in-technical-support", "license": "MIT", "version": "1.0.0", "scripts": { diff --git a/src/main/java/it/gov/pagopa/standintechsupport/Application.java b/src/main/java/it/gov/pagopa/standintechsupport/Application.java index 3479ab8..6086da8 100644 --- a/src/main/java/it/gov/pagopa/standintechsupport/Application.java +++ b/src/main/java/it/gov/pagopa/standintechsupport/Application.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.standintechsupport; // TODO: refactor the package +package it.gov.pagopa.standintechsupport; import com.azure.cosmos.CosmosClient; import com.azure.cosmos.CosmosClientBuilder; diff --git a/src/main/java/it/gov/pagopa/standintechsupport/config/ResponseValidator.java b/src/main/java/it/gov/pagopa/standintechsupport/config/ResponseValidator.java index ed1db69..fc45460 100644 --- a/src/main/java/it/gov/pagopa/standintechsupport/config/ResponseValidator.java +++ b/src/main/java/it/gov/pagopa/standintechsupport/config/ResponseValidator.java @@ -25,9 +25,8 @@ public class ResponseValidator { * @param joinPoint not used * @param result the response to validate */ - // TODO: set your package @AfterReturning( - pointcut = "execution(* it.gov.pagopa.microservice.controller.*.*(..))", + pointcut = "execution(* it.gov.pagopa.standintechsupport.controller.*.*(..))", returning = "result") public void validateResponse(JoinPoint joinPoint, Object result) { if (result instanceof ResponseEntity) { From ea0b3ca7163578acb3a0dc7552073ae8d0cb8cdf Mon Sep 17 00:00:00 2001 From: Francesco Cesareo Date: Tue, 16 Jan 2024 10:26:09 +0100 Subject: [PATCH 08/10] identity --- .../workflows/deploy_with_github_runner.yml | 10 +- .identity/00_data.tf | 15 +++ .identity/02_application_action.tf | 96 ------------------- .identity/03_github_environment.tf | 47 ++++++--- .identity/99_variables.tf | 11 +++ 5 files changed, 65 insertions(+), 114 deletions(-) delete mode 100644 .identity/02_application_action.tf diff --git a/.github/workflows/deploy_with_github_runner.yml b/.github/workflows/deploy_with_github_runner.yml index d911499..ca560b3 100644 --- a/.github/workflows/deploy_with_github_runner.yml +++ b/.github/workflows/deploy_with_github_runner.yml @@ -30,7 +30,7 @@ jobs: # from https://github.com/pagopa/eng-github-actions-iac-template/tree/main/azure/github-self-hosted-runner-azure-create-action uses: pagopa/eng-github-actions-iac-template/azure/github-self-hosted-runner-azure-create-action@main with: - client_id: ${{ secrets.CLIENT_ID }} + client_id: ${{ secrets.CD_CLIENT_ID }} tenant_id: ${{ secrets.TENANT_ID }} subscription_id: ${{ secrets.SUBSCRIPTION_ID }} container_app_environment_name: ${{ vars.CONTAINER_APP_ENVIRONMENT_NAME }} @@ -47,7 +47,7 @@ jobs: uses: pagopa/github-actions-template/aks-deploy@main with: branch: ${{ github.ref_name }} - client_id: ${{ secrets.CLIENT_ID }} + client_id: ${{ secrets.CD_CLIENT_ID }} subscription_id: ${{ secrets.SUBSCRIPTION_ID }} tenant_id: ${{ secrets.TENANT_ID }} env: ${{ inputs.environment }} @@ -69,7 +69,7 @@ jobs: # from https://github.com/pagopa/eng-github-actions-iac-template/tree/main/azure/github-self-hosted-runner-azure-cleanup-action uses: pagopa/eng-github-actions-iac-template/azure/github-self-hosted-runner-azure-cleanup-action@0ee2f58fd46d10ac7f00bce4304b98db3dbdbe9a with: - client_id: ${{ secrets.CLIENT_ID }} + client_id: ${{ secrets.CD_CLIENT_ID }} tenant_id: ${{ secrets.TENANT_ID }} subscription_id: ${{ secrets.SUBSCRIPTION_ID }} resource_group_name: ${{ vars.CONTAINER_APP_ENVIRONMENT_RESOURCE_GROUP_NAME }} @@ -100,7 +100,7 @@ jobs: # from https://github.com/Azure/login/commits/master uses: azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 with: - client-id: ${{ secrets.CLIENT_ID }} + client-id: ${{ secrets.CD_CLIENT_ID }} tenant-id: ${{ secrets.TENANT_ID }} subscription-id: ${{ secrets.SUBSCRIPTION_ID }} @@ -109,7 +109,7 @@ jobs: shell: bash run: | cd ./infra - export ARM_CLIENT_ID="${{ secrets.CLIENT_ID }}" + export ARM_CLIENT_ID="${{ secrets.CD_CLIENT_ID }}" export ARM_SUBSCRIPTION_ID=$(az account show --query id --output tsv) export ARM_TENANT_ID=$(az account show --query tenantId --output tsv) export ARM_USE_OIDC=true diff --git a/.identity/00_data.tf b/.identity/00_data.tf index 8273e72..02b207b 100644 --- a/.identity/00_data.tf +++ b/.identity/00_data.tf @@ -50,3 +50,18 @@ data "azurerm_key_vault_secret" "key_vault_integration_test_subkey" { name = "integration-test-subkey" key_vault_id = data.azurerm_key_vault.key_vault.id } + +data "azurerm_key_vault_secret" "key_vault_slack_webhook_url" { + name = "slack-webhook-url" + key_vault_id = data.azurerm_key_vault.domain_key_vault.id +} + +data "azurerm_key_vault_secret" "key_vault_read_package_token" { + name = "github-token-read-packages-bot" + key_vault_id = data.azurerm_key_vault.key_vault.id +} + +data "azurerm_user_assigned_identity" "identity_cd" { + name = "${local.product}-${local.domain}-01-github-cd-identity" + resource_group_name = "${local.product}-identity-rg" +} \ No newline at end of file diff --git a/.identity/02_application_action.tf b/.identity/02_application_action.tf deleted file mode 100644 index d6a7a24..0000000 --- a/.identity/02_application_action.tf +++ /dev/null @@ -1,96 +0,0 @@ -module "github_runner_app" { - source = "git::https://github.com/pagopa/github-actions-tf-modules.git//app-github-runner-creator?ref=main" - - app_name = local.app_name - - subscription_id = data.azurerm_subscription.current.id - - github_org = local.github.org - github_repository = local.github.repository - github_environment_name = var.env - - container_app_github_runner_env_rg = local.container_app_environment.resource_group -} - -resource "null_resource" "github_runner_app_permissions_to_namespace" { - triggers = { - aks_id = data.azurerm_kubernetes_cluster.aks.id - service_principal_id = module.github_runner_app.client_id - namespace = local.domain - version = "v2" - } - - provisioner "local-exec" { - command = < Date: Tue, 16 Jan 2024 10:32:14 +0100 Subject: [PATCH 09/10] identity --- .../workflows/{anchore.yml => 00_anchore.yml} | 0 .github/workflows/01_add_patch_label.yml | 60 +++++ .github/workflows/01_assignee.yml | 26 ++ .github/workflows/02_check_pr.yml | 125 ++++++++++ .github/workflows/03_code_review.yml | 84 +++++++ .github/workflows/04_release_deploy.yml | 167 +++++++++++++ ....yml => 04h_deploy_with_github_runner.yml} | 53 +--- .github/workflows/check_pr.yml | 183 -------------- .github/workflows/code_review.yml | 118 --------- .github/workflows/create_dashboard.yaml | 84 ------- .github/workflows/integration_test.yml | 232 +++++++++--------- .github/workflows/release_deploy.yml | 169 ------------- 12 files changed, 587 insertions(+), 714 deletions(-) rename .github/workflows/{anchore.yml => 00_anchore.yml} (100%) create mode 100644 .github/workflows/01_add_patch_label.yml create mode 100644 .github/workflows/01_assignee.yml create mode 100644 .github/workflows/02_check_pr.yml create mode 100644 .github/workflows/03_code_review.yml create mode 100644 .github/workflows/04_release_deploy.yml rename .github/workflows/{deploy_with_github_runner.yml => 04h_deploy_with_github_runner.yml} (62%) delete mode 100644 .github/workflows/check_pr.yml delete mode 100644 .github/workflows/code_review.yml delete mode 100644 .github/workflows/create_dashboard.yaml delete mode 100644 .github/workflows/release_deploy.yml diff --git a/.github/workflows/anchore.yml b/.github/workflows/00_anchore.yml similarity index 100% rename from .github/workflows/anchore.yml rename to .github/workflows/00_anchore.yml diff --git a/.github/workflows/01_add_patch_label.yml b/.github/workflows/01_add_patch_label.yml new file mode 100644 index 0000000..00e0fc5 --- /dev/null +++ b/.github/workflows/01_add_patch_label.yml @@ -0,0 +1,60 @@ +name: Add PATCH default label + +# Controls when the workflow will run +on: + # Triggers the workflow on push or pull request events but only for the main branch + pull_request_target: + branches: + - main + types: [ opened, reopened ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + add_patch_label: + runs-on: ubuntu-latest + name: Add default label + steps: + - name: Check user labels + id: check_user_labels + uses: actions/github-script@v6.3.3 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + var addPatch = "true"; + // retrieve label list + let labels = await github.rest.issues.listLabelsOnIssue({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo + }); + + // verify if user have already added IGNORE-FOR-RELEASE, then skip add PATCH + // note: GitHub labels are added in .identity/03_github_environment.tf as github_issue_label resource + if (labels.data.find(label => label.name === 'ignore-for-release')){ + addPatch = "false"; + } + return addPatch; + result-encoding: string + + - name: Add PATCH label + if: ${{ steps.check_user_labels.outputs.result == 'true' }} + uses: pagopa/github-actions-template/default-label@main + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + label: 'patch' + + - name: Add comment + if: ${{ steps.check_user_labels.outputs.result == 'true' }} + uses: actions/github-script@v6.3.3 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: 'The default action is to increase the `PATCH` number of `SEMVER`. Set `IGNORE-FOR-RELEASE` if you want to skip `SEMVER` bump. `BREAKING-CHANGE` and `NEW-RELEASE` must be run from GH Actions section manually.' + }); \ No newline at end of file diff --git a/.github/workflows/01_assignee.yml b/.github/workflows/01_assignee.yml new file mode 100644 index 0000000..0611917 --- /dev/null +++ b/.github/workflows/01_assignee.yml @@ -0,0 +1,26 @@ +name: Auto Assign + +# Controls when the workflow will run +on: + # Triggers the workflow on push or pull request events but only for the main branch + pull_request_target: + branches: + - main + types: [ opened, reopened ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + - name: Assign Me + # You may pin to the exact commit or the version. + uses: kentaro-m/auto-assign-action@v1.2.1 + with: + configuration-path: '.github/auto_assign.yml' diff --git a/.github/workflows/02_check_pr.yml b/.github/workflows/02_check_pr.yml new file mode 100644 index 0000000..8dc9b00 --- /dev/null +++ b/.github/workflows/02_check_pr.yml @@ -0,0 +1,125 @@ +name: Check PR + +# Controls when the workflow will run +on: + pull_request: + branches: + - main + types: [ opened, synchronize, labeled, unlabeled, reopened, edited ] + + +permissions: + pull-requests: write + + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + auto_assign: + name: Auto Assign + + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + - name: Assign Me + # You may pin to the exact commit or the version. + uses: kentaro-m/auto-assign-action@v1.2.1 + with: + configuration-path: '.github/auto_assign.yml' + + check_labels: + name: Check Required Labels + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + - name: Verify PR Labels + if: ${{ !contains(github.event.pull_request.labels.*.name, 'patch') && !contains(github.event.pull_request.labels.*.name, 'ignore-for-release') }} + uses: actions/github-script@v6.3.3 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + var comments = await github.rest.issues.listComments({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo + }); + for (const comment of comments.data) { + if (comment.body.includes('This pull request does not contain a valid label')){ + github.rest.issues.deleteComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: comment.id + }) + } + } + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: 'This pull request does not contain a valid label. Please add one of the following labels: `[patch, ignore-for-release]`' + }) + core.setFailed('Missing required labels') + + + check_format: + name: Check Format + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Formatting + id: format + continue-on-error: true + uses: axel-op/googlejavaformat-action@v3 + with: + args: "--set-exit-if-changed" + + - uses: actions/github-script@v6.3.3 + if: steps.format.outcome != 'success' + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + console.log(context); + var comments = await github.rest.issues.listComments({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo + }); + for (const comment of comments.data) { + console.log(comment); + if (comment.body.includes('Comment this PR with')){ + github.rest.issues.deleteComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: comment.id + }) + } + } + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: 'Comment this PR with *update_code* to format the code. Consider to use pre-commit to format the code.' + }) + core.setFailed('Format your code.') + + check_size: + runs-on: ubuntu-latest + name: Check Size + steps: + + - name: Dump GitHub context + run: echo $JSON + env: + JSON: ${{ toJSON(github) }} + + - name: Check PR Size + uses: pagopa/github-actions-template/check-pr-size@main + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + ignored_files: 'openapi.json' diff --git a/.github/workflows/03_code_review.yml b/.github/workflows/03_code_review.yml new file mode 100644 index 0000000..6a8f893 --- /dev/null +++ b/.github/workflows/03_code_review.yml @@ -0,0 +1,84 @@ +name: Code Review + +# Controls when the workflow will run +on: + pull_request: + branches: + - main + types: + - opened + - synchronize + - reopened + push: + branches: + - main + + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +env: + PROJECT_KEY: pagopa_pagopa-stand-in-technical-support + + +permissions: + id-token: write + contents: read + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + code-review: + name: Code Review + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + - name: Code Review + uses: pagopa/github-actions-template/maven-code-review@v1.8.3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + sonar_token: ${{ secrets.SONAR_TOKEN }} + project_key: ${{env.PROJECT_KEY}} + coverage_exclusions: "**/config/*,**/*Mock*,**/model/**,**/entity/*,**/repository/*" + cpd_exclusions: "**/model/**,**/entity/*" + java_version: '17' + +# smoke-test: +# name: Smoke Test +# runs-on: ubuntu-latest +# environment: +# name: dev +# steps: +# - name: Checkout +# id: checkout +# uses: actions/checkout@1f9a0c22da41e6ebfa534300ef656657ea2c6707 +# +# - name: Login +# id: login +# # from https://github.com/Azure/login/commits/master +# uses: azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 +# with: +# client-id: ${{ secrets.CT_CLIENT_ID }} +# tenant-id: ${{ secrets.TENANT_ID }} +# subscription-id: ${{ secrets.SUBSCRIPTION_ID }} +# +# - name: Run Service on Docker +# shell: bash +# run: | +# cd ./docker +# chmod +x ./run_docker.sh +# ./run_docker.sh local +# +# - name: Run Integration Tests +# shell: bash +# run: | +# export CANARY=${{ inputs.canary }} +# export CUCUMBER_PUBLISH_TOKEN=${{ secrets.CUCUMBER_PUBLISH_TOKEN }} +# export EVENT_HUB_TX_PRIMARY_KEY=${{ secrets.EVENT_HUB_TX_PRIMARY_KEY }} +# export COSMOS_DB_PRIMARY_KEY=${{ secrets.COSMOS_DB_PRIMARY_KEY }} +# export EVENTHUB_CONN_STRING=${{secrets.EVENTHUB_CONN_STRING}} +# +# cd ./integration-test +# chmod +x ./run_integration_test.sh +# ./run_integration_test.sh local diff --git a/.github/workflows/04_release_deploy.yml b/.github/workflows/04_release_deploy.yml new file mode 100644 index 0000000..4e27974 --- /dev/null +++ b/.github/workflows/04_release_deploy.yml @@ -0,0 +1,167 @@ +name: Release And Deploy + +# Controls when the workflow will run +on: + pull_request: + types: [ closed ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + inputs: + environment: + required: true + type: choice + description: Select the Environment + options: + - dev + - uat + - prod + semver: + required: false + type: choice + description: Select the version + options: + - '' + - skip + - promote + - patch + - new_release + - breaking_change + beta: + required: false + type: boolean + description: deploy beta version on AKS + default: false + # skip_release: + # required: false + # type: boolean + # description: skip the release. Only deploy + # default: false + + workflow_call: + inputs: + environment: + required: true + type: string + semver: + required: true + type: string + default: skip + +permissions: + packages: write + contents: write + issues: write + id-token: write + actions: read + + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + setup: + name: Setup + runs-on: ubuntu-latest + outputs: + semver: ${{ steps.semver_setup.outputs.semver }} + environment: ${{ steps.semver_setup.outputs.environment }} + steps: + - name: Semver setup + id: semver_setup + uses: pagopa/github-actions-template/nodo5-semver-setup@ce252c8501c9242bd6045f7cdd650736b2f38777 + with: + semver: ${{ inputs.semver }} + + release: + needs: [setup] + name: Create a New Release + runs-on: ubuntu-latest + if: ${{ needs.setup.outputs.semver != 'skip' }} + outputs: + version: ${{ steps.release.outputs.version }} + steps: + - name: Make Release + id: release + uses: pagopa/github-actions-template/maven-release@v1.5.4 + with: + semver: ${{ needs.setup.outputs.semver }} + github_token: ${{ secrets.BOT_TOKEN_GITHUB }} + beta: ${{ inputs.beta }} + skip_ci: false + + image: + needs: [ setup, release ] + name: Build and Push Docker Image + runs-on: ubuntu-latest + if: ${{ needs.setup.outputs.semver != 'skip' }} + steps: +# - name: Build and Push +# id: semver +# uses: pagopa/github-actions-template/ghcr-build-push@v1.5.4 +# with: +# branch: ${{ github.ref_name}} +# github_token: ${{ secrets.GITHUB_TOKEN }} +# tag: ${{ needs.release.outputs.version }} + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ github.ref_name }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Docker meta + id: meta + uses: docker/metadata-action@v4.3.0 + with: + images: ghcr.io/${{ github.repository }} + tags: | + latest + ${{ needs.release.outputs.version }} + type=ref,event=branch + type=sha + + - name: Build and push + uses: docker/build-push-action@v4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + secrets: | + GH_TOKEN=${{ secrets.READ_PACKAGES_TOKEN }} + + deploy_aks: + name: Deploy on AKS + needs: [ setup, release, image ] + if: ${{ always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }} + strategy: + matrix: + environment: [ dev, uat, prod ] + uses: ./.github/workflows/04h_deploy_with_github_runner.yml + with: + environment: ${{ matrix.environment }} + target: ${{ needs.setup.outputs.environment }} + secrets: inherit + + notify: + name: Notify + needs: [ setup, release, deploy_aks ] + runs-on: ubuntu-latest + if: always() + steps: + - name: Report Status + if: always() + uses: ravsamhq/notify-slack-action@v2 + with: + status: ${{ needs.deploy_aks.result }} + token: ${{ secrets.GITHUB_TOKEN }} + notification_title: 'New Release on ${{ needs.setup.outputs.environment }} for ${{ needs.release.outputs.version }} has {status_message}' + message_format: '{emoji} <{run_url}|{workflow}> {status_message} in <{repo_url}|{repo}>' + footer: 'Linked to <{workflow_url}| workflow file>' + icon_success: ':white_check_mark:' + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/deploy_with_github_runner.yml b/.github/workflows/04h_deploy_with_github_runner.yml similarity index 62% rename from .github/workflows/deploy_with_github_runner.yml rename to .github/workflows/04h_deploy_with_github_runner.yml index ca560b3..b236d46 100644 --- a/.github/workflows/deploy_with_github_runner.yml +++ b/.github/workflows/04h_deploy_with_github_runner.yml @@ -7,9 +7,13 @@ on: required: true description: The name of the environment where to deploy type: string + target: + required: true + description: The environment target of the job + type: string env: - APP_NAME: stand-in-node-technicalsupport + APP_NAME: pagopa-stand-in-technical-support permissions: @@ -22,6 +26,7 @@ jobs: runs-on: ubuntu-22.04 environment: name: ${{ inputs.environment }} + if: ${{ inputs.target == inputs.environment || inputs.target == 'all' }} outputs: runner_name: ${{ steps.create_github_runner.outputs.runner_name }} steps: @@ -40,6 +45,7 @@ jobs: deploy: needs: [ create_runner ] runs-on: [ self-hosted, "${{ needs.create_runner.outputs.runner_name }}" ] + if: ${{ inputs.target == inputs.environment || inputs.target == 'all' }} name: Deploy on AKS environment: ${{ inputs.environment }} steps: @@ -55,12 +61,12 @@ jobs: cluster_name: ${{ vars.CLUSTER_NAME }} resource_group: ${{ vars.CLUSTER_RESOURCE_GROUP }} app_name: ${{ env.APP_NAME }} - helm_upgrade_options: "--debug" + helm_upgrade_options: "--debug --set microservice-chart.forceRedeploy=true" cleanup_runner: name: Cleanup Runner needs: [ create_runner, deploy ] - if: ${{ success() || failure() }} + if: ${{ success() || failure() && inputs.target == inputs.environment || inputs.target == 'all' }} runs-on: ubuntu-22.04 environment: ${{ inputs.environment }} steps: @@ -75,44 +81,3 @@ jobs: resource_group_name: ${{ vars.CONTAINER_APP_ENVIRONMENT_RESOURCE_GROUP_NAME }} runner_name: ${{ needs.create_runner.outputs.runner_name }} pat_token: ${{ secrets.BOT_TOKEN_GITHUB }} - - update_openapi: - needs: [ deploy ] - runs-on: ubuntu-latest - name: Update OpenAPI - environment: ${{ inputs.environment }} - steps: - - name: Checkout - id: checkout - # from https://github.com/actions/checkout/commits/main - uses: actions/checkout@1f9a0c22da41e6ebfa534300ef656657ea2c6707 - with: - persist-credentials: false - - - name: Setup Terraform - # from https://github.com/hashicorp/setup-terraform/commits/main - uses: hashicorp/setup-terraform@8feba2b913ea459066180f9cb177f58a881cf146 - with: - terraform_version: "1.3.6" - - - name: Login - id: login - # from https://github.com/Azure/login/commits/master - uses: azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 - with: - client-id: ${{ secrets.CD_CLIENT_ID }} - tenant-id: ${{ secrets.TENANT_ID }} - subscription-id: ${{ secrets.SUBSCRIPTION_ID }} - - - - name: Terraform Apply - shell: bash - run: | - cd ./infra - export ARM_CLIENT_ID="${{ secrets.CD_CLIENT_ID }}" - export ARM_SUBSCRIPTION_ID=$(az account show --query id --output tsv) - export ARM_TENANT_ID=$(az account show --query tenantId --output tsv) - export ARM_USE_OIDC=true - export ARM_ACCESS_KEY=$(az storage account keys list --resource-group io-infra-rg --account-name pagopainfraterraform${{inputs.environment}} --query '[0].value' -o tsv) - bash ./terraform.sh init weu-${{ inputs.environment }} - bash ./terraform.sh apply weu-${{ inputs.environment }} -auto-approve diff --git a/.github/workflows/check_pr.yml b/.github/workflows/check_pr.yml deleted file mode 100644 index cce975c..0000000 --- a/.github/workflows/check_pr.yml +++ /dev/null @@ -1,183 +0,0 @@ -name: Check PR - -# Controls when the workflow will run -on: - pull_request: - branches: - - main - types: [ opened, synchronize, labeled, unlabeled, reopened, edited ] - - -permissions: - pull-requests: write - - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - auto_assign: - name: Auto Assign - - # The type of runner that the job will run on - runs-on: ubuntu-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - name: Assign Me - # You may pin to the exact commit or the version. - uses: kentaro-m/auto-assign-action@v1.2.1 - with: - configuration-path: '.github/auto_assign.yml' - - check_format: - name: Check Format - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Formatting - id: format - continue-on-error: true - uses: findologic/intellij-format-action@main - with: - path: . - fail-on-changes: false - - - uses: actions/github-script@v6.3.3 - if: steps.format.outcome != 'success' - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - console.log(context); - var comments = await github.rest.issues.listComments({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo - }); - for (const comment of comments.data) { - console.log(comment); - if (comment.body.includes('Comment this PR with')){ - github.rest.issues.deleteComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: comment.id - }) - } - } - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: 'Comment this PR with *update_code* to update `openapi.json` and format the code. Consider to use pre-commit to format the code.' - }) - core.setFailed('Format your code.') - - check_size: - runs-on: ubuntu-latest - name: Check Size - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Check Size - uses: actions/github-script@v6.3.3 - env: - IGNORED_FILES: openapi.json, openapi-node.json - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const additions = context.payload.pull_request.additions || 0 - const deletions = context.payload.pull_request.deletions || 0 - var changes = additions + deletions - console.log('additions: '+additions+' + deletions: '+deletions+ ' = total changes: ' + changes); - - const { IGNORED_FILES } = process.env - const ignored_files = IGNORED_FILES.trim().split(',').filter(word => word.length > 0); - if (ignored_files.length > 0){ - var ignored = 0 - const execSync = require('child_process').execSync; - for (const file of IGNORED_FILES.trim().split(',')) { - - const ignored_additions_str = execSync('git --no-pager diff --numstat origin/main..origin/${{ github.head_ref}} | grep ' + file + ' | cut -f 1', { encoding: 'utf-8' }) - const ignored_deletions_str = execSync('git --no-pager diff --numstat origin/main..origin/${{ github.head_ref}} | grep ' + file + ' | cut -f 2', { encoding: 'utf-8' }) - - const ignored_additions = ignored_additions_str.split('\n').map(elem=> parseInt(elem || 0)).reduce( - (accumulator, currentValue) => accumulator + currentValue, - 0); - const ignored_deletions = ignored_deletions_str.split('\n').map(elem=> parseInt(elem || 0)).reduce( - (accumulator, currentValue) => accumulator + currentValue, - 0); - - ignored += ignored_additions + ignored_deletions; - } - changes -= ignored - console.log('ignored lines: ' + ignored + ' , consider changes: ' + changes); - } - - var labels = await github.rest.issues.listLabelsOnIssue({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo - }); - - if (labels.data.find(label => label.name == 'size/large')){ - github.rest.issues.removeLabel({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - name: 'size/large' - }) - } - if (labels.data.find(label => label.name == 'size/small')){ - github.rest.issues.removeLabel({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - name: 'size/small' - }) - } - - var comments = await github.rest.issues.listComments({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo - }); - for (const comment of comments.data) { - if (comment.body.includes('This PR exceeds the recommended size')){ - github.rest.issues.deleteComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: comment.id - }) - } - } - - if (changes < 200){ - github.rest.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: ['size/small'] - }) - } - - if (changes > 400){ - github.rest.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: ['size/large'] - }) - - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: 'This PR exceeds the recommended size of 400 lines. Please make sure you are NOT addressing multiple issues with one PR. _Note this PR might be rejected due to its size._' - }) - - } - diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml deleted file mode 100644 index 3f2650d..0000000 --- a/.github/workflows/code_review.yml +++ /dev/null @@ -1,118 +0,0 @@ -name: Code Review - -# Controls when the workflow will run -on: - pull_request: - branches: - - main - types: - - opened - - synchronize - - reopened - push: - branches: - - main - - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -env: - PROJECT_KEY: pagopa-stand-in-technical-support - -permissions: - id-token: write - contents: read - deployments: write - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - code-review: - name: Code Review - # The type of runner that the job will run on - runs-on: ubuntu-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - name: Code Review - uses: pagopa/github-actions-template/maven-code-review@v1.4.2 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - sonar_token: ${{ secrets.SONAR_TOKEN }} - project_key: ${{env.PROJECT_KEY}} - coverage_exclusions: "**/config/*,**/*Mock*,**/model/**,**/entity/*" - cpd_exclusions: "**/model/**,**/entity/*" - - smoke-test: - name: Smoke Test - runs-on: ubuntu-latest - environment: - name: dev - steps: - - name: Checkout - id: checkout - uses: actions/checkout@1f9a0c22da41e6ebfa534300ef656657ea2c6707 - - - name: Login - id: login - # from https://github.com/Azure/login/commits/master - uses: azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 - with: - client-id: ${{ secrets.CLIENT_ID }} - tenant-id: ${{ secrets.TENANT_ID }} - subscription-id: ${{ secrets.SUBSCRIPTION_ID }} - - - name: Run Service on Docker - shell: bash - id: run_service_docker - run: | - cd ./docker - chmod +x ./run_docker.sh - ./run_docker.sh local - - - name: Run Integration Tests - shell: bash - id: run_integration_test - run: | - export SUBKEY=${{ secrets.SUBKEY }} - export CANARY=${{ inputs.canary }} - export CUCUMBER_PUBLISH_TOKEN=${{ secrets.CUCUMBER_PUBLISH_TOKEN }} - - cd ./integration-test - chmod +x ./run_integration_test.sh - ./run_integration_test.sh local - - - delete_github_deployments: - runs-on: ubuntu-latest - needs: smoke-test - if: ${{ always() }} - steps: - - name: Delete Previous deployments - uses: actions/github-script@v6 - env: - SHA_HEAD: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.sha) || github.sha}} - with: - script: | - const { SHA_HEAD } = process.env - - const deployments = await github.rest.repos.listDeployments({ - owner: context.repo.owner, - repo: context.repo.repo, - sha: SHA_HEAD - }); - await Promise.all( - deployments.data.map(async (deployment) => { - await github.rest.repos.createDeploymentStatus({ - owner: context.repo.owner, - repo: context.repo.repo, - deployment_id: deployment.id, - state: 'inactive' - }); - return github.rest.repos.deleteDeployment({ - owner: context.repo.owner, - repo: context.repo.repo, - deployment_id: deployment.id - }); - }) - ); diff --git a/.github/workflows/create_dashboard.yaml b/.github/workflows/create_dashboard.yaml deleted file mode 100644 index 61fa251..0000000 --- a/.github/workflows/create_dashboard.yaml +++ /dev/null @@ -1,84 +0,0 @@ -name: Create Dashboard - -# Controls when the workflow will run -on: - push: - branches: - - main - paths: - - 'openapi/**' - - '.github/workflows/create_dashboard.yaml' - - '.opex/**' - - workflow_dispatch: - -permissions: - id-token: write - contents: read - deployments: write - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - dashboard: - # The type of runner that the job will run on - runs-on: ubuntu-22.04 - - strategy: - matrix: - environment: [prod] - environment: - name: ${{ matrix.environment }} - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - name: Checkout - id: checkout - # from https://github.com/actions/checkout/commits/main - uses: actions/checkout@1f9a0c22da41e6ebfa534300ef656657ea2c6707 - with: - persist-credentials: false - - # from https://github.com/pagopa/opex-dashboard-azure-action/ - - uses: pagopa/opex-dashboard-azure-action@v1.1.2 - with: - environment: ${{ matrix.environment }} - api-name: - config: .opex/env/${{ matrix.environment }}/config.yaml - client-id: ${{ secrets.CLIENT_ID }} - tenant-id: ${{ secrets.TENANT_ID }} - subscription-id: ${{ secrets.SUBSCRIPTION_ID }} - # from https://github.com/pagopa/opex-dashboard-azure-action/pkgs/container/opex-dashboard-azure-action - docker-version: sha256:e4245954566cd3470e1b5527d33bb58ca132ce7493eac01be9e808fd25a11c8d - - delete_github_deployments: - runs-on: ubuntu-latest - needs: dashboard - if: ${{ always() }} - steps: - - name: Delete Previous deployments - uses: actions/github-script@v6 - env: - SHA_HEAD: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.sha) || github.sha}} - with: - script: | - const { SHA_HEAD } = process.env - - const deployments = await github.rest.repos.listDeployments({ - owner: context.repo.owner, - repo: context.repo.repo, - sha: SHA_HEAD - }); - await Promise.all( - deployments.data.map(async (deployment) => { - await github.rest.repos.createDeploymentStatus({ - owner: context.repo.owner, - repo: context.repo.repo, - deployment_id: deployment.id, - state: 'inactive' - }); - return github.rest.repos.deleteDeployment({ - owner: context.repo.owner, - repo: context.repo.repo, - deployment_id: deployment.id - }); - }) - ); diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index c405e3e..b87b4ea 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -1,116 +1,116 @@ -name: Integration Tests - -on: - schedule: - - cron: '00 08 * * *' - - workflow_dispatch: - inputs: - environment: - required: true - type: choice - description: Select the Environment - options: - - dev - - uat - - prod - canary: - description: 'run the tests on canary version' - required: false - type: boolean - default: false - notify: - required: false - type: boolean - description: 'send the slack notification' - default: true - - -permissions: - id-token: write - contents: read - deployments: write - - -jobs: - integration_test: - name: Test ${{(github.event.inputs == null && 'dev') || inputs.environment }} - runs-on: ubuntu-latest - environment: ${{(github.event.inputs == null && 'dev') || inputs.environment }} - steps: - - name: Checkout - id: checkout - uses: actions/checkout@1f9a0c22da41e6ebfa534300ef656657ea2c6707 - - - name: Login - id: login - # from https://github.com/Azure/login/commits/master - uses: azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 - with: - client-id: ${{ secrets.CLIENT_ID }} - tenant-id: ${{ secrets.TENANT_ID }} - subscription-id: ${{ secrets.SUBSCRIPTION_ID }} - - - name: Run Integration Tests - shell: bash - run: | - export SUBKEY=${{ secrets.SUBKEY }} - export CANARY=${{ inputs.canary }} - export CUCUMBER_PUBLISH_TOKEN=${{ secrets.CUCUMBER_PUBLISH_TOKEN }} - - cd ./integration-test - chmod +x ./run_integration_test.sh - ./run_integration_test.sh ${{( github.event.inputs == null && 'dev') || inputs.environment }} - - notify: - needs: [ integration_test ] - runs-on: ubuntu-latest - name: Notify - if: ${{ always() && inputs.notify == 'true' }} - steps: - - name: Report Status - if: ${{ inputs.notify }} - uses: ravsamhq/notify-slack-action@v2 - with: - status: ${{ needs.integration_test.result }} - token: ${{ secrets.GITHUB_TOKEN }} - notify_when: 'failure,skipped' - notification_title: '<{run_url}|Scheduled Integration Test> has {status_message}' - message_format: '{emoji} <{run_url}|{workflow}> {status_message} in <{repo_url}|{repo}>' - footer: 'Linked to <{workflow_url}| workflow file>' - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - - delete_github_deployments: - runs-on: ubuntu-latest - needs: integration_test - if: ${{ always() }} - steps: - - name: Delete Previous deployments - uses: actions/github-script@v6 - env: - SHA_HEAD: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.sha) || github.sha}} - with: - script: | - const { SHA_HEAD } = process.env - - const deployments = await github.rest.repos.listDeployments({ - owner: context.repo.owner, - repo: context.repo.repo, - sha: SHA_HEAD - }); - await Promise.all( - deployments.data.map(async (deployment) => { - await github.rest.repos.createDeploymentStatus({ - owner: context.repo.owner, - repo: context.repo.repo, - deployment_id: deployment.id, - state: 'inactive' - }); - return github.rest.repos.deleteDeployment({ - owner: context.repo.owner, - repo: context.repo.repo, - deployment_id: deployment.id - }); - }) - ); +#name: Integration Tests +# +#on: +# schedule: +# - cron: '00 08 * * *' +# +# workflow_dispatch: +# inputs: +# environment: +# required: true +# type: choice +# description: Select the Environment +# options: +# - dev +# - uat +# - prod +# canary: +# description: 'run the tests on canary version' +# required: false +# type: boolean +# default: false +# notify: +# required: false +# type: boolean +# description: 'send the slack notification' +# default: true +# +# +#permissions: +# id-token: write +# contents: read +# deployments: write +# +# +#jobs: +# integration_test: +# name: Test ${{(github.event.inputs == null && 'dev') || inputs.environment }} +# runs-on: ubuntu-latest +# environment: ${{(github.event.inputs == null && 'dev') || inputs.environment }} +# steps: +# - name: Checkout +# id: checkout +# uses: actions/checkout@1f9a0c22da41e6ebfa534300ef656657ea2c6707 +# +# - name: Login +# id: login +# # from https://github.com/Azure/login/commits/master +# uses: azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 +# with: +# client-id: ${{ secrets.CLIENT_ID }} +# tenant-id: ${{ secrets.TENANT_ID }} +# subscription-id: ${{ secrets.SUBSCRIPTION_ID }} +# +# - name: Run Integration Tests +# shell: bash +# run: | +# export SUBKEY=${{ secrets.SUBKEY }} +# export CANARY=${{ inputs.canary }} +# export CUCUMBER_PUBLISH_TOKEN=${{ secrets.CUCUMBER_PUBLISH_TOKEN }} +# +# cd ./integration-test +# chmod +x ./run_integration_test.sh +# ./run_integration_test.sh ${{( github.event.inputs == null && 'dev') || inputs.environment }} +# +# notify: +# needs: [ integration_test ] +# runs-on: ubuntu-latest +# name: Notify +# if: ${{ always() && inputs.notify == 'true' }} +# steps: +# - name: Report Status +# if: ${{ inputs.notify }} +# uses: ravsamhq/notify-slack-action@v2 +# with: +# status: ${{ needs.integration_test.result }} +# token: ${{ secrets.GITHUB_TOKEN }} +# notify_when: 'failure,skipped' +# notification_title: '<{run_url}|Scheduled Integration Test> has {status_message}' +# message_format: '{emoji} <{run_url}|{workflow}> {status_message} in <{repo_url}|{repo}>' +# footer: 'Linked to <{workflow_url}| workflow file>' +# env: +# SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} +# +# delete_github_deployments: +# runs-on: ubuntu-latest +# needs: integration_test +# if: ${{ always() }} +# steps: +# - name: Delete Previous deployments +# uses: actions/github-script@v6 +# env: +# SHA_HEAD: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.sha) || github.sha}} +# with: +# script: | +# const { SHA_HEAD } = process.env +# +# const deployments = await github.rest.repos.listDeployments({ +# owner: context.repo.owner, +# repo: context.repo.repo, +# sha: SHA_HEAD +# }); +# await Promise.all( +# deployments.data.map(async (deployment) => { +# await github.rest.repos.createDeploymentStatus({ +# owner: context.repo.owner, +# repo: context.repo.repo, +# deployment_id: deployment.id, +# state: 'inactive' +# }); +# return github.rest.repos.deleteDeployment({ +# owner: context.repo.owner, +# repo: context.repo.repo, +# deployment_id: deployment.id +# }); +# }) +# ); diff --git a/.github/workflows/release_deploy.yml b/.github/workflows/release_deploy.yml deleted file mode 100644 index 3b72f7c..0000000 --- a/.github/workflows/release_deploy.yml +++ /dev/null @@ -1,169 +0,0 @@ -name: Release And Deploy - -# Controls when the workflow will run -on: - pull_request: - types: [ closed ] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - inputs: - environment: - required: true - type: choice - description: Select the Environment - options: - - dev - - uat - - prod - version: - required: false - type: choice - description: Select the version - options: - - '' - - skip - - promote - - new_release - - breaking_change - - beta: - required: false - type: boolean - description: deploy beta version on AKS - default: false - - -permissions: - packages: write - contents: write - issues: write - id-token: write - actions: read - - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - setup: - name: Setup - runs-on: ubuntu-latest - outputs: - semver: ${{ steps.get_semver.outputs.semver }} - environment: ${{ steps.get_env.outputs.environment }} - steps: - - name: pull request rejected - if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged != true - run: | - echo "❌ PR was closed without a merge" - exit 1 - - # Set Semvar - - run: echo "SEMVER=patch" >> $GITHUB_ENV - - - if: ${{ (github.event.pull_request.merged && contains(github.event.pull_request.labels.*.name, 'breaking-change')) }} - run: echo "SEMVER=major" >> $GITHUB_ENV - - # force semver if dev, !=main or skip release - - if: ${{ inputs.version == 'new_release' }} - run: echo "SEMVER=minor" >> $GITHUB_ENV - - - if: ${{ inputs.version == 'breaking_change' }} - run: echo "SEMVER=major" >> $GITHUB_ENV - - - if: ${{ github.ref_name != 'main' }} - run: echo "SEMVER=buildNumber" >> $GITHUB_ENV - - - if: ${{ inputs.version == 'skip' || inputs.version == 'promote' }} - run: echo "SEMVER=skip" >> $GITHUB_ENV - - - id: get_semver - name: Set Output - run: echo "semver=${{env.SEMVER}}" >> $GITHUB_OUTPUT - - # Set Environment - - run: echo "ENVIRNOMENT=${{ inputs.environment}}" >> $GITHUB_ENV - - - if: ${{ inputs.environment == null }} - run: echo "ENVIRNOMENT=dev" >> $GITHUB_ENV - - - id: get_env - name: Set Output - run: echo "environment=${{env.ENVIRNOMENT}}" >> $GITHUB_OUTPUT - - - release: - name: Create a New Release - runs-on: ubuntu-latest - needs: [setup] - outputs: - version: ${{ steps.release.outputs.version }} - steps: - - name: Make Release - id: release - uses: pagopa/github-actions-template/maven-release@v1.6.8 - with: - semver: ${{ needs.setup.outputs.semver }} - github_token: ${{ secrets.BOT_TOKEN_GITHUB }} - beta: ${{ inputs.beta }} - skip_ci: false - - image: - needs: [ setup, release ] - name: Build and Push Docker Image - runs-on: ubuntu-latest - if: ${{ inputs.semver != 'skip' }} - steps: - - name: Build and Push - id: semver - uses: pagopa/github-actions-template/ghcr-build-push@v1.5.4 - with: - branch: ${{ github.ref_name}} - github_token: ${{ secrets.GITHUB_TOKEN }} - tag: ${{ needs.release.outputs.version }} - - - deploy_aks_dev: - name: Deploy on AKS DEV - needs: [ setup, release, image ] - uses: ./.github/workflows/deploy_with_github_runner.yml - with: - environment: ${{ needs.setup.outputs.environment }} - secrets: inherit - - deploy_aks_uat: - name: Deploy on AKS UAT - needs: [ setup, release, image ] - if: ${{ needs.setup.outputs.environment != 'dev' }} - uses: ./.github/workflows/deploy_with_github_runner.yml - with: - environment: ${{ needs.setup.outputs.environment }} - secrets: inherit - - deploy_aks_prod: - name: Deploy on AKS PROD - needs: [ setup, release, image ] - if: ${{ needs.setup.outputs.environment == 'prod' }} - uses: ./.github/workflows/deploy_with_github_runner.yml - with: - environment: ${{ needs.setup.outputs.environment }} - secrets: inherit - - - notify: - needs: [ deploy_aks_prod ] - runs-on: ubuntu-latest - name: Notify - if: always() - steps: - - name: Report Status - if: ${{ needs.setup.outputs.environment == 'prod' }} - uses: ravsamhq/notify-slack-action@v2 - with: - status: ${{ needs.deploy_aks_prod.result }} - token: ${{ secrets.GITHUB_TOKEN }} - notification_title: 'New Release on Production ${{ needs.release.outputs.version }} has {status_message}' - message_format: '{emoji} <{run_url}|{workflow}> {status_message} in <{repo_url}|{repo}>' - footer: 'Linked to <{workflow_url}| workflow file>' - icon_success: ':white_check_mark:' - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} From d91468f3a06bc67b0720a6c1243acbda8a936201 Mon Sep 17 00:00:00 2001 From: Francesco Cesareo Date: Tue, 16 Jan 2024 10:32:38 +0100 Subject: [PATCH 10/10] identity --- .github/workflows/create_dashboard.yaml | 84 +++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 .github/workflows/create_dashboard.yaml diff --git a/.github/workflows/create_dashboard.yaml b/.github/workflows/create_dashboard.yaml new file mode 100644 index 0000000..824cfb4 --- /dev/null +++ b/.github/workflows/create_dashboard.yaml @@ -0,0 +1,84 @@ +#name: Create Dashboard +# +## Controls when the workflow will run +#on: +# push: +# branches: +# - main +# paths: +# - 'openapi/**' +# - '.github/workflows/create_dashboard.yaml' +# - '.opex/**' +# +# workflow_dispatch: +# +#permissions: +# id-token: write +# contents: read +# deployments: write +# +## A workflow run is made up of one or more jobs that can run sequentially or in parallel +#jobs: +# dashboard: +# # The type of runner that the job will run on +# runs-on: ubuntu-22.04 +# +# strategy: +# matrix: +# environment: [prod] +# environment: +# name: ${{ matrix.environment }} +# # Steps represent a sequence of tasks that will be executed as part of the job +# steps: +# - name: Checkout +# id: checkout +# # from https://github.com/actions/checkout/commits/main +# uses: actions/checkout@1f9a0c22da41e6ebfa534300ef656657ea2c6707 +# with: +# persist-credentials: false +# +# # from https://github.com/pagopa/opex-dashboard-azure-action/ +# - uses: pagopa/opex-dashboard-azure-action@v1.1.2 +# with: +# environment: ${{ matrix.environment }} +# api-name: +# config: .opex/env/${{ matrix.environment }}/config.yaml +# client-id: ${{ secrets.CLIENT_ID }} +# tenant-id: ${{ secrets.TENANT_ID }} +# subscription-id: ${{ secrets.SUBSCRIPTION_ID }} +# # from https://github.com/pagopa/opex-dashboard-azure-action/pkgs/container/opex-dashboard-azure-action +# docker-version: sha256:e4245954566cd3470e1b5527d33bb58ca132ce7493eac01be9e808fd25a11c8d +# +# delete_github_deployments: +# runs-on: ubuntu-latest +# needs: dashboard +# if: ${{ always() }} +# steps: +# - name: Delete Previous deployments +# uses: actions/github-script@v6 +# env: +# SHA_HEAD: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.sha) || github.sha}} +# with: +# script: | +# const { SHA_HEAD } = process.env +# +# const deployments = await github.rest.repos.listDeployments({ +# owner: context.repo.owner, +# repo: context.repo.repo, +# sha: SHA_HEAD +# }); +# await Promise.all( +# deployments.data.map(async (deployment) => { +# await github.rest.repos.createDeploymentStatus({ +# owner: context.repo.owner, +# repo: context.repo.repo, +# deployment_id: deployment.id, +# state: 'inactive' +# }); +# return github.rest.repos.deleteDeployment({ +# owner: context.repo.owner, +# repo: context.repo.repo, +# deployment_id: deployment.id +# }); +# }) +# );