diff --git a/.github/workflows/cd_deploy_with_github_runner.yml b/.github/workflows/cd_deploy_with_github_runner.yml index 9157f6e..20ed817 100644 --- a/.github/workflows/cd_deploy_with_github_runner.yml +++ b/.github/workflows/cd_deploy_with_github_runner.yml @@ -44,30 +44,8 @@ permissions: contents: read jobs: - create_runner: - name: Create Runner - runs-on: ubuntu-22.04 - environment: - name: ${{ inputs.environment }} - outputs: - runner_name: ${{ steps.create_github_runner.outputs.runner_name }} - steps: - - name: Create GitHub Runner - id: create_github_runner - # 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 }} - tenant_id: ${{ secrets.TENANT_ID }} - subscription_id: ${{ secrets.SUBSCRIPTION_ID }} - container_app_environment_name: ${{ vars.CONTAINER_APP_ENVIRONMENT_NAME }} - resource_group_name: ${{ vars.CONTAINER_APP_ENVIRONMENT_RESOURCE_GROUP_NAME }} # RG of the runner - pat_token: ${{ secrets.BOT_TOKEN_GITHUB }} - self_hosted_runner_image_tag: "latest" - deploy: - needs: [ create_runner ] - runs-on: [ self-hosted, "${{ needs.create_runner.outputs.runner_name }}" ] + runs-on: [ self-hosted-job, "${{ inputs.environment }}" ] name: Deploy on AKS environment: ${{ inputs.environment }} steps: @@ -84,7 +62,7 @@ jobs: cluster_name: ${{ vars.CLUSTER_NAME }} resource_group: ${{ vars.CLUSTER_RESOURCE_GROUP }} app_name: ${{ env.APP_NAME }}${{inputs.suffix_name}} - helm_upgrade_options: "--debug ${{inputs.helm_options}}" + helm_upgrade_options: '--debug --set microservice-chart.azure.workloadIdentityClientId=${{vars.WORKLOAD_IDENTITY_ID}} ${{inputs.helm_options}}' timeout: '10m0s' - name: Remove deployment @@ -93,25 +71,6 @@ jobs: run: | helm uninstall ${{ env.APP_NAME }}${{inputs.suffix_name}} -n ${{ vars.NAMESPACE }} - cleanup_runner: - name: Cleanup Runner - needs: [ create_runner, deploy ] - if: ${{ success() || failure() }} - runs-on: ubuntu-22.04 - environment: ${{ inputs.environment }} - steps: - - name: Cleanup GitHub Runner - id: cleanup_github_runner - # 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@main - with: - client_id: ${{ secrets.CLIENT_ID }} - tenant_id: ${{ secrets.TENANT_ID }} - subscription_id: ${{ secrets.SUBSCRIPTION_ID }} - 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 diff --git a/.github/workflows/ci_code_review.yml b/.github/workflows/ci_code_review.yml index 62aab14..aa73dca 100644 --- a/.github/workflows/ci_code_review.yml +++ b/.github/workflows/ci_code_review.yml @@ -18,7 +18,7 @@ on: workflow_dispatch: env: - PROJECT_KEY: # TODO + PROJECT_KEY: pagopa_pagopa-gps-mbd-service permissions: id-token: write @@ -40,7 +40,7 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} sonar_token: ${{ secrets.SONAR_TOKEN }} project_key: ${{env.PROJECT_KEY}} - coverage_exclusions: "**/config/*,**/*Mock*,**/model/**,**/entity/*,**/exception/**" + coverage_exclusions: "**/config/*,**/*Mock*,**/model/**,**/entity/*,**/exception/**,**/util/**" cpd_exclusions: "**/model/**,**/entity/*" java_version: 17 diff --git a/.identity/.terraform.lock.hcl b/.identity/.terraform.lock.hcl new file mode 100644 index 0000000..e659e8d --- /dev/null +++ b/.identity/.terraform.lock.hcl @@ -0,0 +1,64 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azuread" { + version = "2.30.0" + constraints = "2.30.0" + hashes = [ + "h1:Uw4TcmJBEJ71h+oCwwidlkk5jFpyFRDPAFCMs/bT/cw=", + "zh:1c3e89cf19118fc07d7b04257251fc9897e722c16e0a0df7b07fcd261f8c12e7", + "zh:2e62c193030e04ebb10cc0526119cf69824bf2d7e4ea5a2f45bd5d5fb7221d36", + "zh:2f3c7a35257332d68b778cefc5201a5f044e4914dd03794a4da662ddfe756483", + "zh:35d0d3a1b58fdb8b8c4462d6b7e7016042da43ea9cc734ce897f52a73407d9b0", + "zh:47ede0cd0206ec953d40bf4a80aa6e59af64e26cbbd877614ac424533dbb693b", + "zh:48c190307d4d42ea67c9b8cc544025024753f46cef6ea64db84735e7055a72da", + "zh:6fff9b2c6a962252a70a15b400147789ab369b35a781e9d21cce3804b04d29af", + "zh:7646980cf3438bff29c91ffedb74458febbb00a996638751fbd204ab1c628c9b", + "zh:77aa2fa7ca6d5446afa71d4ff83cb87b70a2f3b72110fc442c339e8e710b2928", + "zh:e20b2b2c37175b89dd0db058a096544d448032e28e3b56e2db368343533a9684", + "zh:eab175b1dfe9865ad9404dccb6d5542899f8c435095aa7c679314b811c717ce7", + "zh:efc862bd78c55d2ff089729e2a34c1831ab4b0644fc11b36ee4ebed00a4797ba", + ] +} + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "3.45.0" + constraints = "3.45.0" + hashes = [ + "h1:VQWxV5+qelZeUCjpdLvZ7iAom4RvG+fVVgK6ELvw/cs=", + "zh:04c5dbb8845366ce5eb0dc2d55e151270cc2c0ace20993867fdae9af43b953ad", + "zh:2589585da615ccae341400d45d672ee3fae413fdd88449b5befeff12a85a44b2", + "zh:603869ed98fff5d9bf841a51afd9e06b628533c59356c8433aef4b15df63f5f7", + "zh:853fecab9c987b6772c8d9aa10362675f6c626b60ebc7118aa33ce91366fcc38", + "zh:979848c45e8e058862c36ba3a661457f7c81ef26ebb6634f479600de9c203d65", + "zh:9b512c8588ecc9c1b803b746a3a8517422561a918f0dfb0faaa707ed53ef1760", + "zh:a9601ffb58043426bcff1220662d6d137f0b2857a24f2dcf180aeac2c9cea688", + "zh:d52d2652328f0ed3ba202561d88cb9f43c174edbfaab1abf69f772125dbfe15e", + "zh:d92d91ca597c47f575bf3ae129f4b723be9b7dcb71b906ec6ec740fac29b1aaa", + "zh:ded73b730e4197b70fda9e83447c119f92f75dc37be3ff2ed45730c8f0348c28", + "zh:ec37ac332d50f8ca5827f97198346b0f8ecbf470e2e3ba1e027bb389d826b902", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/integrations/github" { + version = "5.18.3" + constraints = "5.18.3" + hashes = [ + "h1:WbZvLB2qXKVoh4BvOOwFfEds+SZQrkINfSAWPnWFxGo=", + "zh:050b37d96628cb7451137755929ca8d21ea546bc46d11a715652584070e83ff2", + "zh:053051061f1b7f7673b0ceffac1f239ba28b0e5b375999206fd39976e85d9f2b", + "zh:0c300a977ca66d0347ed62bb116fd8fc9abb376a554d4c192d14f3ea71c83500", + "zh:1d5a1a5243eba78819d2f92ff2d504ebf9a9008a6670fb5f5660f44eb6a156d8", + "zh:a13ac15d251ebf4e7dc40acb0e40df066f443f4c7799186a29e2e44addc7d8e7", + "zh:a316d94b885953c036ebc9fba64a23da93974746bc3ac9d207462a6f02d44540", + "zh:a658a00373bff5979cc227052c693cbde8ca4c8f9fef1bc8094a3516f2e2a96d", + "zh:a7bfc6ad8465d5dc11b6f19d6805364de87fffe27622bb4f37da2319bb1c4956", + "zh:d7379a76861f1a6bfc36eca7a20f1f477711247563b105744d69d7bd1f365fad", + "zh:de1cd959fd4821248e8d21570601193408648474e74f49597f1d0c43185a4ab7", + "zh:e0b281240dd6f2aa405b2d6fe329bc15ab877161affe163fb150d1efca2fccdb", + "zh:e372c171358757a983d7aa878abfd05a84484fb4d22167e45c9c1267e78ed060", + "zh:f6d3116526030b3f6905f530cd6c04b23d42890d973fa2abe10ce9c89cb1db80", + "zh:f99eec731e03cc6a28996c875bd435887cd7ea75ec07cc77b9e768bb12da2227", + ] +} diff --git a/.identity/00_data.tf b/.identity/00_data.tf index 21a1449..6df6058 100644 --- a/.identity/00_data.tf +++ b/.identity/00_data.tf @@ -34,8 +34,8 @@ data "azurerm_user_assigned_identity" "identity_pr_01" { } data "azurerm_key_vault" "domain_key_vault" { - name = "pagopa-${var.env_short}-${local.domain}-kv" - resource_group_name = "pagopa-${var.env_short}-${local.domain}-sec-rg" + name = "pagopa-${var.env_short}-itn-${local.domain}-kv" + resource_group_name = "pagopa-${var.env_short}-itn-${local.domain}-sec-rg" } data "azurerm_key_vault_secret" "key_vault_sonar" { @@ -57,3 +57,8 @@ 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_user_assigned_identity" "workload_identity_clientid" { + name = "ebollo-workload-identity" + resource_group_name = "pagopa-${var.env_short}-itn-${var.env}-aks-rg" +} \ No newline at end of file diff --git a/.identity/01_github_environment.tf b/.identity/01_github_environment.tf index d570189..d084ccb 100644 --- a/.identity/01_github_environment.tf +++ b/.identity/01_github_environment.tf @@ -32,6 +32,7 @@ locals { "CLUSTER_NAME" : local.aks_cluster.name, "CLUSTER_RESOURCE_GROUP" : local.aks_cluster.resource_group_name, "NAMESPACE" : local.domain, + "WORKLOAD_IDENTITY_ID": data.azurerm_user_assigned_identity.workload_identity_clientid.client_id } repo_secrets = { "SONAR_TOKEN" : data.azurerm_key_vault_secret.key_vault_sonar.value, diff --git a/.identity/99_variables.tf b/.identity/99_variables.tf index b57e1b5..d5acdff 100644 --- a/.identity/99_variables.tf +++ b/.identity/99_variables.tf @@ -1,11 +1,11 @@ locals { github = { org = "pagopa" - repository = "TODO" #TODO + repository = "pagopa-gps-mbd-service" } prefix = "pagopa" - domain = "TODO" #TODO + domain = "ebollo" 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..5cde786 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-gps-mbd-service.tfstate" diff --git a/.identity/env/dev/terraform.tfvars b/.identity/env/dev/terraform.tfvars index 3345e07..d9a57ae 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-gps-mbd-service" CostCenter = "TS310 - PAGAMENTI & SERVIZI" } diff --git a/.identity/env/prod/backend.tfvars b/.identity/env/prod/backend.tfvars index d8d402c..8b5cd7b 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-gps-mbd-service.tfstate" diff --git a/.identity/env/prod/terraform.tfvars b/.identity/env/prod/terraform.tfvars index ee41cf5..1c8c6e5 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-gps-mbd-service" CostCenter = "TS310 - PAGAMENTI & SERVIZI" } diff --git a/.identity/env/uat/backend.tfvars b/.identity/env/uat/backend.tfvars index 502eaf6..0e7cfdb 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-gps-mbd-service.tfstate" diff --git a/.identity/env/uat/terraform.tfvars b/.identity/env/uat/terraform.tfvars index 86ec8fc..7600f7f 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-gps-mbd-service" CostCenter = "TS310 - PAGAMENTI & SERVIZI" } diff --git a/.opex/env/prod/backend.tfvars b/.opex/env/prod/backend.tfvars index ae0e6e4..8b5cd7b 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 = "pagopa-gps-mbd-service.tfstate" diff --git a/.opex/env/prod/config.yaml b/.opex/env/prod/config.yaml index 80a9d54..8201e8f 100644 --- a/.opex/env/prod/config.yaml +++ b/.opex/env/prod/config.yaml @@ -1,10 +1,13 @@ oa3_spec: ./openapi/openapi.json # If start with http the file would be downloaded from the internet -name: opex_ # TODO -location: West Europe +name: opex_pagopa-gps-mbd-service +location: Italy North timespan: 5m # Default, a number or a timespan https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/scalar-data-types/timespan +# public data_source data_source: /subscriptions/b9fc9419-6097-45fe-9f74-ba0641c91912/resourceGroups/pagopa-p-vnet-rg/providers/Microsoft.Network/applicationGateways/pagopa-p-app-gw +# private data_source #data_source: /subscriptions/b9fc9419-6097-45fe-9f74-ba0641c91912/resourceGroups/pagopa-p-api-rg/providers/Microsoft.ApiManagement/service/pagopa-p-apim -#resource_type: api-management + +#resource_type: api-management # default is app-gw. uncomment for api-management action_groups: - /subscriptions/b9fc9419-6097-45fe-9f74-ba0641c91912/resourceGroups/pagopa-p-monitor-rg/providers/microsoft.insights/actionGroups/PagoPA - /subscriptions/b9fc9419-6097-45fe-9f74-ba0641c91912/resourceGroups/pagopa-p-monitor-rg/providers/microsoft.insights/actionGroups/SlackPagoPA diff --git a/.opex/env/prod/terraform.tfvars b/.opex/env/prod/terraform.tfvars index 1445485..d498227 100644 --- a/.opex/env/prod/terraform.tfvars +++ b/.opex/env/prod/terraform.tfvars @@ -1,11 +1,10 @@ prefix = "pagopa" env_short = "p" - tags = { CreatedBy = "Terraform" Environment = "Prod" Owner = "pagoPA" - Source = "https://github.com/pagopa/your-repository" # TODO + Source = "https://github.com/pagopa/pagopa-gps-mbd-service" CostCenter = "TS310 - PAGAMENTI & SERVIZI" } diff --git a/CODEOWNERS b/CODEOWNERS index bac36ae..2fe107d 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 @alessio-cialini @svariant @gioelemella diff --git a/README.md b/README.md index 3726a09..e25bfde 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,40 @@ -# Template for Java Spring Microservice project - -[![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/ci_integration_test.yml/badge.svg?branch=main)](https://github.com/pagopa//actions/workflows/ci_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 +# pagoPA MBD GPS Service + +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=paogpa_pagopa-gps-mbd-service&metric=alert_status)](https://sonarcloud.io/dashboard?id=paogpa_pagopa-gps-mbd-service) +[![Integration Tests](https://github.com/pagopa/pagopa-gps-mbd-service/actions/workflows/ci_integration_test.yml/badge.svg?branch=main)](https://github.com/pagopa/pagopa-gps-mbd-service/actions/workflows/ci_integration_test.yml) + +Expose an API that will be used by GPS Payments to generate MBD payment options + +- [pagoPA MBD GPS Service](#pagopa-gps-mbd-service) + * [Api Documentation πŸ“–](#api-documentation-) + * [Technology Stack](#technology-stack) + * [Start Project Locally πŸš€](#start-project-locally-) + + [Prerequisites](#prerequisites) + + [Run docker container](#run-docker-container) + * [Develop Locally πŸ’»](#develop-locally-) + + [Prerequisites](#prerequisites-1) + + [Run the project](#run-the-project) + + [Spring Profiles](#spring-profiles) + + [Testing πŸ§ͺ](#testing-) + - [Unit testing](#unit-testing) + - [Integration testing](#integration-testing) + - [Performance testing](#performance-testing) + * [Contributors πŸ‘₯](#contributors-) + + [Maintainers](#maintainers) --- ## Api Documentation πŸ“– -See the [OpenApi 3 here.](https://editor.swagger.io/?url=https://raw.githubusercontent.com/pagopa//main/openapi/openapi.json) +See the [OpenApi 3 here.](https://editor.swagger.io/?url=https://raw.githubusercontent.com/pagopa/pagopa-gps-mbd-service/main/openapi/openapi.json) --- ## Technology Stack -- Java 11 -- Spring Boot +- Java 17 +- Spring Boot 3 - Spring Web -- Hibernate -- JPA -- ... -- TODO --- @@ -41,8 +50,6 @@ from `./docker` directory `sh ./run_docker.sh local` -ℹ️ Note: for PagoPa ACR is required the login `az acr login -n ` - --- ## Develop Locally πŸ’» @@ -51,13 +58,13 @@ from `./docker` directory - git - maven -- jdk-11 +- jdk-17 ### Run the project Start the springboot application with this command: -`mvn spring-boot:run -Dspring-boot.run.profiles=local` +`mvn spring-boot:run -Dspring.profiles.active=local` ### Spring Profiles diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index d119b75..2b7d549 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: 'pagopa-gps-mbd-service' image: ${image} platform: linux/amd64 build: diff --git a/helm/Chart.yaml b/helm/Chart.yaml index 2f15c6e..bceb4e7 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -6,5 +6,5 @@ version: 0.0.0 appVersion: 0.0.0 dependencies: - name: microservice-chart - version: 5.9.0 + version: 7.1.1 repository: "https://pagopa.github.io/aks-microservice-chart-blueprint" diff --git a/helm/values-dev.yaml b/helm/values-dev.yaml index c265ccd..c042c2a 100644 --- a/helm/values-dev.yaml +++ b/helm/values-dev.yaml @@ -1,12 +1,13 @@ microservice-chart: - namespace: "your-namespace" # TODO: set your AKS namespace + namespace: "ebollo" nameOverride: "" - fullnameOverride: "" + fullnameOverride: "pagopa-gps-mbd-service" image: - repository: ghcr.io/pagopa/yourname # TODO + repository: ghcr.io/pagopa/pagopa-gps-mbd-service tag: "0.0.0" pullPolicy: Always livenessProbe: + handlerType: httpGet httpGet: path: /actuator/health/liveness port: 8080 @@ -14,6 +15,7 @@ microservice-chart: failureThreshold: 6 periodSeconds: 10 readinessProbe: + handlerType: httpGet httpGet: path: /actuator/health/readiness port: 8080 @@ -22,6 +24,15 @@ microservice-chart: periodSeconds: 10 deployment: create: true + serviceMonitor: + create: true + endpoints: + - interval: 10s #jmx-exporter + targetPort: 12345 + path: /metrics + ports: + - 8080 + - 12345 #jmx-exporter service: create: true type: ClusterIP @@ -29,13 +40,13 @@ microservice-chart: - 8080 ingress: create: true - host: "your.host" # TODO: set the host - path: /your-path-here/(.*) # TODO: set your path + host: "ebollo.itn.internal.dev.platform.pagopa.it" + path: /pagopa-gps-mbd-service/(.*) servicePort: 8080 serviceAccount: - create: false - annotations: { } - name: "" + name: "ebollo-workload-identity" + azure: + workloadIdentityClientId: podAnnotations: { } podSecurityContext: seccompProfile: @@ -51,8 +62,8 @@ microservice-chart: memory: "512Mi" cpu: "0.25" limits: - memory: "512Mi" - cpu: "0.25" + memory: "768Mi" + cpu: "0.5" autoscaling: enable: true minReplica: 1 @@ -66,26 +77,24 @@ 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 + APP_NAME: "pagopa-gps-mbd-service" + APP_ENVIRONMENT: "dev" + WEBSITE_SITE_NAME: 'pagopa-gps-mbd-service' # required to show cloud role name in application insights ENV: 'azure-dev' - APP_LOGGING_LEVEL: 'DEBUG' + APP_LOGGING_LEVEL: 'INFO' DEFAULT_LOGGING_LEVEL: 'INFO' CORS_CONFIGURATION: '{"origins": ["*"], "methods": ["*"]}' - - OTEL_SERVICE_NAME: # TODO - OTEL_RESOURCE_ATTRIBUTES: "deployment.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" + MBD_PAYMENT_OPTION_DESCRIPTION: 'Pagamento marca da bollo digitale' + MBD_PAYMENT_OPTION_DUE_DATE_DELTA: 20 + MBD_PAYMENT_OPTION_DUE_DATE_TIME_UNIT: 'Minutes' + MBD_PAYMENT_OPTION_RETENTION_DATE_DELTA: 1 + MBD_PAYMENT_OPTION_RETENTION_DATE_TIME_UNIT: 'Hours' + MBD_TRANSFER_REMITTANCE_INFORMATION: 'Pagamento marca da bollo digitale' envSecret: # required - APPLICATIONINSIGHTS_CONNECTION_STRING: 'ai-d-connection-string' - OTEL_EXPORTER_OTLP_HEADERS: elastic-apm-secret-token + APPLICATIONINSIGHTS_CONNECTION_STRING: 'app-insight-connection-string' keyvault: - name: "pagopa-d-name-kv" #TODO + name: "pagopa-d-itn-ebollo-kv" tenantId: "7788edaf-0346-4068-9d79-c868aed15b3d" nodeSelector: { } tolerations: [ ] @@ -112,7 +121,7 @@ microservice-chart: deployment: create: true image: - repository: ghcr.io/pagopa/yourname # TODO + repository: ghcr.io/pagopa/pagopa-gps-mbd-service tag: "0.0.0" pullPolicy: Always envConfig: { } diff --git a/helm/values-prod.yaml b/helm/values-prod.yaml index 4c58544..3e3ca08 100644 --- a/helm/values-prod.yaml +++ b/helm/values-prod.yaml @@ -1,12 +1,13 @@ microservice-chart: - namespace: "your-namespace" # TODO: set your AKS namespace + namespace: "ebollo" nameOverride: "" - fullnameOverride: "" + fullnameOverride: "pagopa-gps-mbd-service" image: - repository: ghcr.io/pagopa/yourname # TODO + repository: ghcr.io/pagopa/pagopa-gps-mbd-service tag: "0.0.0" pullPolicy: Always livenessProbe: + handlerType: httpGet httpGet: path: /actuator/health/liveness port: 8080 @@ -14,6 +15,7 @@ microservice-chart: failureThreshold: 6 periodSeconds: 10 readinessProbe: + handlerType: httpGet httpGet: path: /actuator/health/readiness port: 8080 @@ -22,6 +24,15 @@ microservice-chart: periodSeconds: 10 deployment: create: true + serviceMonitor: + create: true + endpoints: + - interval: 10s #jmx-exporter + targetPort: 12345 + path: /metrics + ports: + - 8080 + - 12345 #jmx-exporter service: create: true type: ClusterIP @@ -29,13 +40,13 @@ microservice-chart: - 8080 ingress: create: true - host: "your.host" # TODO: set the host - path: /your-path-here/(.*) # TODO: set your path + host: "ebollo.itn.internal.platform.pagopa.it" + path: /pagopa-gps-mbd-service/(.*) servicePort: 8080 serviceAccount: - create: false - annotations: { } - name: "" + name: "ebollo-workload-identity" + azure: + workloadIdentityClientId: podAnnotations: { } podSecurityContext: seccompProfile: @@ -51,8 +62,8 @@ microservice-chart: memory: "512Mi" cpu: "0.25" limits: - memory: "512Mi" - cpu: "0.25" + memory: "768Mi" + cpu: "0.5" autoscaling: enable: true minReplica: 3 @@ -66,26 +77,24 @@ 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 + APP_NAME: "pagopa-gps-mbd-service" + APP_ENVIRONMENT: "prod" + WEBSITE_SITE_NAME: 'pagopa-gps-mbd-service' # required to show cloud role name in application insights ENV: 'azure-prod' APP_LOGGING_LEVEL: 'INFO' DEFAULT_LOGGING_LEVEL: 'INFO' CORS_CONFIGURATION: '{"origins": ["*"], "methods": ["*"]}' - - OTEL_SERVICE_NAME: # TODO - OTEL_RESOURCE_ATTRIBUTES: "deployment.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" + MBD_PAYMENT_OPTION_DESCRIPTION: 'Pagamento marca da bollo digitale' + MBD_PAYMENT_OPTION_DUE_DATE_DELTA: 20 + MBD_PAYMENT_OPTION_DUE_DATE_TIME_UNIT: 'Minutes' + MBD_PAYMENT_OPTION_RETENTION_DATE_DELTA: 1 + MBD_PAYMENT_OPTION_RETENTION_DATE_TIME_UNIT: 'Hours' + MBD_TRANSFER_REMITTANCE_INFORMATION: 'Pagamento marca da bollo digitale' envSecret: # required - APPLICATIONINSIGHTS_CONNECTION_STRING: 'ai-p-connection-string' - OTEL_EXPORTER_OTLP_HEADERS: elastic-apm-secret-token + APPLICATIONINSIGHTS_CONNECTION_STRING: 'app-insight-connection-string' keyvault: - name: "pagopa-p-name-kv" #TODO + name: "pagopa-p-itn-ebollo-kv" tenantId: "7788edaf-0346-4068-9d79-c868aed15b3d" nodeSelector: { } tolerations: [ ] @@ -98,6 +107,15 @@ microservice-chart: operator: In values: - user + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchLabels: + app.kubernetes.io/instance: pagopa-gps-mbd-service + namespaces: [ "ebollo" ] + topologyKey: topology.kubernetes.io/zone canaryDelivery: create: true ingress: @@ -112,7 +130,7 @@ microservice-chart: deployment: create: true image: - repository: ghcr.io/pagopa/yourname # TODO + repository: ghcr.io/pagopa/pagopa-gps-mbd-service tag: "0.0.0" pullPolicy: Always envConfig: { } diff --git a/helm/values-uat.yaml b/helm/values-uat.yaml index 38a99cc..4b63218 100644 --- a/helm/values-uat.yaml +++ b/helm/values-uat.yaml @@ -1,12 +1,13 @@ microservice-chart: - namespace: "your-namespace" # TODO: set your AKS namespace + namespace: "ebollo" nameOverride: "" - fullnameOverride: "" + fullnameOverride: "pagopa-gps-mbd-service" image: - repository: ghcr.io/pagopa/yourname # TODO + repository: ghcr.io/pagopa/pagopa-gps-mbd-service tag: "0.0.0" pullPolicy: Always livenessProbe: + handlerType: httpGet httpGet: path: /actuator/health/liveness port: 8080 @@ -14,6 +15,7 @@ microservice-chart: failureThreshold: 6 periodSeconds: 10 readinessProbe: + handlerType: httpGet httpGet: path: /actuator/health/readiness port: 8080 @@ -22,6 +24,15 @@ microservice-chart: periodSeconds: 10 deployment: create: true + serviceMonitor: + create: true + endpoints: + - interval: 10s #jmx-exporter + targetPort: 12345 + path: /metrics + ports: + - 8080 + - 12345 #jmx-exporter service: create: true type: ClusterIP @@ -29,13 +40,13 @@ microservice-chart: - 8080 ingress: create: true - host: "your.host" # TODO: set the host - path: /your-path-here/(.*) # TODO: set your path + host: "ebollo.itn.internal.uat.platform.pagopa.it" + path: /pagopa-gps-mbd-service/(.*) servicePort: 8080 serviceAccount: - create: false - annotations: { } - name: "" + name: "ebollo-workload-identity" + azure: + workloadIdentityClientId: podAnnotations: { } podSecurityContext: seccompProfile: @@ -51,8 +62,8 @@ microservice-chart: memory: "512Mi" cpu: "0.25" limits: - memory: "512Mi" - cpu: "0.25" + memory: "768Mi" + cpu: "0.5" autoscaling: enable: true minReplica: 3 @@ -66,25 +77,24 @@ 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 + APP_NAME: "pagopa-gps-mbd-service" + APP_ENVIRONMENT: "uat" + WEBSITE_SITE_NAME: 'pagopa-gps-mbd-service' # required to show cloud role name in application insights ENV: 'azure-uat' - APP_LOGGING_LEVEL: 'DEBUG' + APP_LOGGING_LEVEL: 'INFO' DEFAULT_LOGGING_LEVEL: 'INFO' CORS_CONFIGURATION: '{"origins": ["*"], "methods": ["*"]}' - OTEL_SERVICE_NAME: # TODO - OTEL_RESOURCE_ATTRIBUTES: "deployment.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" + MBD_PAYMENT_OPTION_DESCRIPTION: 'Pagamento marca da bollo digitale' + MBD_PAYMENT_OPTION_DUE_DATE_DELTA: 20 + MBD_PAYMENT_OPTION_DUE_DATE_TIME_UNIT: 'Minutes' + MBD_PAYMENT_OPTION_RETENTION_DATE_DELTA: 1 + MBD_PAYMENT_OPTION_RETENTION_DATE_TIME_UNIT: 'Hours' + MBD_TRANSFER_REMITTANCE_INFORMATION: 'Pagamento marca da bollo digitale' envSecret: # required - APPLICATIONINSIGHTS_CONNECTION_STRING: 'ai-u-connection-string' - OTEL_EXPORTER_OTLP_HEADERS: elastic-apm-secret-token + APPLICATIONINSIGHTS_CONNECTION_STRING: 'app-insight-connection-string' keyvault: - name: "pagopa-u-name-kv" #TODO + name: "pagopa-u-itn-ebollo-kv" tenantId: "7788edaf-0346-4068-9d79-c868aed15b3d" nodeSelector: { } tolerations: [ ] @@ -111,7 +121,7 @@ microservice-chart: deployment: create: true image: - repository: ghcr.io/pagopa/yourname # TODO + repository: ghcr.io/pagopa/pagopa-gps-mbd-service tag: "0.0.0" pullPolicy: Always envConfig: { } diff --git a/infra/04_apim_api.tf b/infra/04_apim_api.tf index b24ae4d..a716b7e 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-gps-mbd-service" - display_name = "TODO" # TODO - description = "TODO" # TODO - path = "TODO" # TODO add your base path + display_name = "MBD GPS Service" + description = "API for MBD GPS Service" + path = "pagopa-gps-mbd-service" 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..0ac3333 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 = "ebollo-gps-service" } } diff --git a/infra/env/weu-dev/backend.ini b/infra/env/itn-dev/backend.ini similarity index 100% rename from infra/env/weu-dev/backend.ini rename to infra/env/itn-dev/backend.ini diff --git a/infra/env/weu-dev/backend.tfvars b/infra/env/itn-dev/backend.tfvars similarity index 66% rename from infra/env/weu-dev/backend.tfvars rename to infra/env/itn-dev/backend.tfvars index 619395b..d3a438f 100644 --- a/infra/env/weu-dev/backend.tfvars +++ b/infra/env/itn-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-gps-mbd-service.infra.tfstate" diff --git a/infra/env/itn-dev/terraform.tfvars b/infra/env/itn-dev/terraform.tfvars new file mode 100644 index 0000000..e98f40b --- /dev/null +++ b/infra/env/itn-dev/terraform.tfvars @@ -0,0 +1,15 @@ +prefix = "pagopa" +env = "dev" +env_short = "d" + +tags = { + CreatedBy = "Terraform" + Environment = "Dev" + Owner = "pagoPA" + Source = "https://github.com/pagopa/pagopa-gps-mbd-service" + CostCenter = "TS310 - PAGAMENTI & SERVIZI" +} + +apim_dns_zone_prefix = "dev.platform" +external_domain = "pagopa.it" +hostname = "ebollo.itn.internal.dev.platform.pagopa.it" diff --git a/infra/env/weu-prod/backend.ini b/infra/env/itn-prod/backend.ini similarity index 100% rename from infra/env/weu-prod/backend.ini rename to infra/env/itn-prod/backend.ini diff --git a/infra/env/weu-prod/backend.tfvars b/infra/env/itn-prod/backend.tfvars similarity index 66% rename from infra/env/weu-prod/backend.tfvars rename to infra/env/itn-prod/backend.tfvars index dac1727..39d1934 100644 --- a/infra/env/weu-prod/backend.tfvars +++ b/infra/env/itn-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-gps-mbd-service.infra.tfstate" diff --git a/infra/env/itn-prod/terraform.tfvars b/infra/env/itn-prod/terraform.tfvars new file mode 100644 index 0000000..c03a753 --- /dev/null +++ b/infra/env/itn-prod/terraform.tfvars @@ -0,0 +1,15 @@ +prefix = "pagopa" +env = "prod" +env_short = "p" + +tags = { + CreatedBy = "Terraform" + Environment = "Prod" + Owner = "pagoPA" + Source = "https://github.com/pagopa/pagopa-gps-mbd-service" + CostCenter = "TS310 - PAGAMENTI & SERVIZI" +} + +apim_dns_zone_prefix = "platform" +external_domain = "pagopa.it" +hostname = "ebollo.itn.internal.platform.pagopa.it" diff --git a/infra/env/weu-uat/backend.ini b/infra/env/itn-uat/backend.ini similarity index 100% rename from infra/env/weu-uat/backend.ini rename to infra/env/itn-uat/backend.ini diff --git a/infra/env/weu-uat/backend.tfvars b/infra/env/itn-uat/backend.tfvars similarity index 66% rename from infra/env/weu-uat/backend.tfvars rename to infra/env/itn-uat/backend.tfvars index 6f406b1..fac072a 100644 --- a/infra/env/weu-uat/backend.tfvars +++ b/infra/env/itn-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-gps-mbd-service.infra.tfstate" diff --git a/infra/env/itn-uat/terraform.tfvars b/infra/env/itn-uat/terraform.tfvars new file mode 100644 index 0000000..7b5cb1d --- /dev/null +++ b/infra/env/itn-uat/terraform.tfvars @@ -0,0 +1,15 @@ +prefix = "pagopa" +env = "uat" +env_short = "u" + +tags = { + CreatedBy = "Terraform" + Environment = "Uat" + Owner = "pagoPA" + Source = "https://github.com/pagopa/pagopa-gps-mbd-service" + CostCenter = "TS310 - PAGAMENTI & SERVIZI" +} + +apim_dns_zone_prefix = "uat.platform" +external_domain = "pagopa.it" +hostname = "ebollo.itn.internal.uat.platform.pagopa.it" diff --git a/infra/env/weu-dev/terraform.tfvars b/infra/env/weu-dev/terraform.tfvars deleted file mode 100644 index 63a0705..0000000 --- a/infra/env/weu-dev/terraform.tfvars +++ /dev/null @@ -1,15 +0,0 @@ -prefix = "pagopa" -env = "dev" -env_short = "d" - -tags = { - CreatedBy = "Terraform" - Environment = "Dev" - Owner = "pagoPA" - Source = "https://github.com/pagopa/your-repository" # TODO - CostCenter = "TS310 - PAGAMENTI & SERVIZI" -} - -apim_dns_zone_prefix = "dev.platform" -external_domain = "pagopa.it" -hostname = "weudev..internal.dev.platform.pagopa.it" # TODO diff --git a/infra/env/weu-prod/terraform.tfvars b/infra/env/weu-prod/terraform.tfvars deleted file mode 100644 index 77f85af..0000000 --- a/infra/env/weu-prod/terraform.tfvars +++ /dev/null @@ -1,15 +0,0 @@ -prefix = "pagopa" -env = "prod" -env_short = "p" - -tags = { - CreatedBy = "Terraform" - Environment = "Prod" - Owner = "pagoPA" - Source = "https://github.com/pagopa/your-repository" # TODO - CostCenter = "TS310 - PAGAMENTI & SERVIZI" -} - -apim_dns_zone_prefix = "platform" -external_domain = "pagopa.it" -hostname = "weuprod..internal.platform.pagopa.it" # TODO diff --git a/infra/env/weu-uat/terraform.tfvars b/infra/env/weu-uat/terraform.tfvars deleted file mode 100644 index e8160f1..0000000 --- a/infra/env/weu-uat/terraform.tfvars +++ /dev/null @@ -1,15 +0,0 @@ -prefix = "pagopa" -env = "uat" -env_short = "u" - -tags = { - CreatedBy = "Terraform" - Environment = "Uat" - Owner = "pagoPA" - Source = "https://github.com/pagopa/your-repository" # TODO - CostCenter = "TS310 - PAGAMENTI & SERVIZI" -} - -apim_dns_zone_prefix = "uat.platform" -external_domain = "pagopa.it" -hostname = "weuuat..internal.uat.platform.pagopa.it" # TODO diff --git a/infra/policy/_base_policy.xml b/infra/policy/_base_policy.xml index e3b583c..e812c61 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 0967ef4..32b2306 100644 --- a/openapi/openapi.json +++ b/openapi/openapi.json @@ -1 +1,467 @@ -{} +{ + "openapi": "3.0.1", + "info": { + "description": "MBD GPS Service", + "termsOfService": "https://www.pagopa.gov.it/", + "title": "pagopa-gps-mbd-service", + "version": "0.0.0" + }, + "servers": [ + { + "url": "http://localhost:8080" + }, + { + "url": "https://{host}{basePath}", + "variables": { + "basePath": { + "default": "/pagopa-gps-mbd-service/v1" + }, + "host": { + "default": "api.dev.platform.pagopa.it", + "enum": [ + "api.dev.platform.pagopa.it", + "api.uat.platform.pagopa.it", + "api.platform.pagopa.it" + ] + } + } + } + ], + "paths": { + "/info": { + "get": { + "description": "Return OK if application is started", + "operationId": "healthCheck", + "responses": { + "200": { + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/AppInfo" + } + } + }, + "description": "OK", + "headers": { + "X-Request-Id": { + "description": "This header identifies the call", + "schema": { + "type": "string" + } + } + } + } + }, + "security": [ + { + "ApiKey": [] + } + ], + "summary": "health check", + "tags": [ + "Home" + ] + }, + "parameters": [ + { + "description": "This header identifies the call, if not passed it is self-generated. This ID is returned in the response.", + "in": "header", + "name": "X-Request-Id", + "schema": { + "type": "string" + } + } + ] + }, + "/mbd/paymentOption": { + "parameters": [ + { + "description": "This header identifies the call, if not passed it is self-generated. This ID is returned in the response.", + "in": "header", + "name": "X-Request-Id", + "schema": { + "type": "string" + } + } + ], + "post": { + "operationId": "buildMbdPaymentOption", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MbdPaymentOptionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MbdPaymentOptionResponse" + } + } + }, + "description": "OK", + "headers": { + "X-Request-Id": { + "description": "This header identifies the call", + "schema": { + "type": "string" + } + } + } + }, + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MbdPaymentOptionResponse" + } + } + }, + "description": "Created", + "headers": { + "X-Request-Id": { + "description": "This header identifies the call", + "schema": { + "type": "string" + } + } + } + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemJson" + } + } + }, + "description": "Bad Request", + "headers": { + "X-Request-Id": { + "description": "This header identifies the call", + "schema": { + "type": "string" + } + } + } + }, + "401": { + "description": "Unauthorized", + "headers": { + "X-Request-Id": { + "description": "This header identifies the call", + "schema": { + "type": "string" + } + } + } + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemJson" + } + } + }, + "description": "Not found", + "headers": { + "X-Request-Id": { + "description": "This header identifies the call", + "schema": { + "type": "string" + } + } + } + }, + "429": { + "description": "Too many requests", + "headers": { + "X-Request-Id": { + "description": "This header identifies the call", + "schema": { + "type": "string" + } + } + } + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemJson" + } + } + }, + "description": "Service unavailable", + "headers": { + "X-Request-Id": { + "description": "This header identifies the call", + "schema": { + "type": "string" + } + } + } + } + }, + "security": [ + { + "ApiKey": [] + } + ], + "summary": "Build MBD payment option model", + "tags": [ + "MBD GPS" + ] + } + } + }, + "components": { + "schemas": { + "AppInfo": { + "type": "object", + "properties": { + "environment": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "MbdPaymentOptionRequest": { + "required": [ + "properties" + ], + "type": "object", + "properties": { + "properties": { + "$ref": "#/components/schemas/MbdPaymentOptionRequestProperties" + } + } + }, + "MbdPaymentOptionRequestProperties": { + "required": [ + "amount", + "documentHash", + "firstName", + "fiscalCode", + "lastName", + "provincialResidence" + ], + "type": "object", + "properties": { + "amount": { + "type": "integer", + "description": "MBD amount", + "format": "int64" + }, + "documentHash": { + "maxLength": 44, + "minLength": 44, + "type": "string", + "description": "MBD document's hash" + }, + "firstName": { + "type": "string", + "description": "Debtor's name" + }, + "fiscalCode": { + "type": "string", + "description": "Debtor's fiscal code" + }, + "lastName": { + "type": "string", + "description": "Debtor's last name" + }, + "provincialResidence": { + "type": "string", + "description": "Debtor's residence province" + } + }, + "description": "MBD payment option's properties" + }, + "MbdPaymentOptionResponse": { + "required": [ + "paymentOption" + ], + "type": "object", + "properties": { + "paymentOption": { + "type": "array", + "description": "MBD payment option", + "items": { + "$ref": "#/components/schemas/PaymentOption" + } + } + } + }, + "PaymentOption": { + "required": [ + "amount", + "description", + "dueDate", + "firstName", + "isPartialPayment", + "lastName", + "retentionDate", + "transfer" + ], + "type": "object", + "properties": { + "amount": { + "type": "integer", + "description": "Payment option's amount", + "format": "int64" + }, + "description": { + "maxLength": 140, + "minLength": 0, + "type": "string", + "description": "Payment option's description" + }, + "dueDate": { + "type": "string", + "description": "Payment option's due date", + "format": "date-time" + }, + "firstName": { + "type": "string", + "description": "Debtor's name" + }, + "isPartialPayment": { + "type": "boolean", + "description": "Payment option's is partial payment flag" + }, + "lastName": { + "type": "string", + "description": "Debtor's last name" + }, + "retentionDate": { + "type": "string", + "description": "Payment option's retention date", + "format": "date-time" + }, + "transfer": { + "type": "array", + "description": "Payment option's transfer list", + "items": { + "$ref": "#/components/schemas/Transfer" + } + } + }, + "description": "MBD payment option" + }, + "ProblemJson": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "A human readable explanation specific to this occurrence of the problem.", + "example": "There was an error processing the request" + }, + "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 + }, + "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" + } + } + }, + "Stamp": { + "required": [ + "hashDocument", + "provincialResidence", + "stampType" + ], + "type": "object", + "properties": { + "hashDocument": { + "maxLength": 72, + "minLength": 0, + "type": "string", + "description": "Document hash type is stBase64Binary72 as described in https://github.com/pagopa/pagopa-api." + }, + "provincialResidence": { + "pattern": "[A-Z]{2}", + "type": "string", + "description": "The province of residence", + "example": "RM" + }, + "stampType": { + "maxLength": 2, + "minLength": 2, + "type": "string", + "description": "The type of the stamp" + } + }, + "description": "Transfer's stamp" + }, + "Transfer": { + "required": [ + "amount", + "idTransfer", + "remittanceInformation", + "stamp" + ], + "type": "object", + "properties": { + "amount": { + "type": "integer", + "description": "Transfer's amount", + "format": "int64" + }, + "idTransfer": { + "type": "string", + "description": "Transfer's id", + "enum": [ + "1", + "2", + "3", + "4", + "5" + ] + }, + "organizationFiscalCode": { + "type": "string", + "description": "Fiscal code related to the organization targeted by this transfer.", + "example": "00000000000" + }, + "remittanceInformation": { + "maxLength": 140, + "minLength": 0, + "type": "string", + "description": "Transfer's remittance information" + }, + "stamp": { + "$ref": "#/components/schemas/Stamp" + } + }, + "description": "Payment option's transfer list" + } + }, + "securitySchemes": { + "ApiKey": { + "description": "The API key to access this function app.", + "in": "header", + "name": "Ocp-Apim-Subscription-Key", + "type": "apiKey" + } + } + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8b8fb4c..96e4ccf 100644 --- a/pom.xml +++ b/pom.xml @@ -1,175 +1,145 @@ - 4.0.0 + xmlns="http://maven.apache.org/POM/4.0.0" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.2.3 - - - it.gov.pagopa - microservice - 0.0.0 - Your Name - Your description - - - 17 - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-validation - - - - - org.springframework.boot - spring-boot-devtools - runtime - true - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.boot - spring-boot-starter-actuator - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - org.springframework.data - spring-data-jpa - - - - org.springframework.boot - spring-boot-starter-cache - - - com.github.ben-manes.caffeine - caffeine - - - - - org.springdoc - springdoc-openapi-ui - 1.6.11 - - - - com.h2database - h2 - runtime - - - - org.hibernate.orm - hibernate-core - 6.1.3.Final - - - - org.springframework.cloud - spring-cloud-starter-openfeign - 4.1.2 - - - - - org.modelmapper - modelmapper - 3.1.0 - - - org.projectlombok - lombok - true - - - - junit - junit - test - - - co.elastic.logging - logback-ecs-encoder - 1.5.0 - - - - - - + org.springframework.boot - spring-boot-maven-plugin - - - - org.jacoco - jacoco-maven-plugin - 0.8.7 - - - - - - - - prepare-agent - - - - report - test - - report - - - - - - org.sonarsource.scanner.maven - sonar-maven-plugin - 3.3.0.603 - - - verify - - sonar - - - - - - - - src/test/resources - true - - - + spring-boot-starter-parent + 3.2.3 + + + it.gov.pagopa + pagopa-gps-mbd-service + 0.0.0 + MBD GPS Service + MBD GPS Service + + + 17 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-aop + + + org.springframework.boot + spring-boot-starter-actuator + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.3.0 + + + + + + org.modelmapper + modelmapper + 3.1.0 + + + org.projectlombok + lombok + true + + + co.elastic.logging + logback-ecs-encoder + 1.5.0 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.jacoco + jacoco-maven-plugin + 0.8.7 + + + + + + + + prepare-agent + + + + report + test + + report + + + + + + org.sonarsource.scanner.maven + sonar-maven-plugin + 3.7.0.1746 + + + verify + + sonar + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + + + + + src/test/resources + true + + + diff --git a/sops.sh b/sops.sh index dbf1ec7..bc35456 100755 --- a/sops.sh +++ b/sops.sh @@ -54,8 +54,7 @@ main() { env_short=$(echo "$environment" | cut -c1) - # TODO set your kv - azure_kv_url=$(az keyvault key show --name pagopa-"$env_short"-TODO-sops-key --vault-name pagopa-"$env_short"-TODO-kv --query key.kid | sed 's/"//g') + azure_kv_url=$(az keyvault key show --name pagopa-"$env_short"-ebollo-sops-key --vault-name pagopa-"$env_short"-ebollo-kv --query key.kid | sed 's/"//g') if [ "$action" == "enc" ]; then sops --encrypt --azure-kv "$azure_kv_url" --input-type dotenv --output-type dotenv ./"$filepath" > ./"$filepath".encrypted diff --git a/src/main/java/it/gov/pagopa/microservice/Application.java b/src/main/java/it/gov/pagopa/mbd/gps/service/Application.java similarity index 80% rename from src/main/java/it/gov/pagopa/microservice/Application.java rename to src/main/java/it/gov/pagopa/mbd/gps/service/Application.java index 465b6cb..5b59b26 100644 --- a/src/main/java/it/gov/pagopa/microservice/Application.java +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/Application.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.microservice; // TODO: refactor the package +package it.gov.pagopa.mbd.gps.service; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -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/mbd/gps/service/config/LoggingAspect.java b/src/main/java/it/gov/pagopa/mbd/gps/service/config/LoggingAspect.java new file mode 100644 index 0000000..ca73f9a --- /dev/null +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/config/LoggingAspect.java @@ -0,0 +1,163 @@ +package it.gov.pagopa.mbd.gps.service.config; + +import static it.gov.pagopa.mbd.gps.service.util.CommonUtility.deNull; + +import it.gov.pagopa.mbd.gps.service.exception.AppError; +import it.gov.pagopa.mbd.gps.service.model.ProblemJson; +import jakarta.annotation.PostConstruct; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +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.aspectj.lang.reflect.CodeSignature; +import org.slf4j.MDC; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +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"; + public static final String FAULT_CODE = "faultCode"; + public static final String FAULT_DETAIL = "faultDetail"; + public static final String REQUEST_ID = "requestId"; + public static final String OPERATION_ID = "operationId"; + public static final String ARGS = "args"; + + HttpServletRequest httRequest; + HttpServletResponse httpResponse; + private final String name; + private final String version; + private final String environment; + + @Autowired + public LoggingAspect( + HttpServletRequest httRequest, + HttpServletResponse httpResponse, + @Value("${info.application.name}") String name, + @Value("${info.application.version}") String version, + @Value("${info.properties.environment}") String environment) { + this.httRequest = httRequest; + this.httpResponse = httpResponse; + this.name = name; + this.version = version; + this.environment = environment; + } + + @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 {}", name, version, environment); + } + + @Around(value = "restController()") + public Object logApiInvocation(ProceedingJoinPoint joinPoint) throws Throwable { + MDC.put(METHOD, joinPoint.getSignature().getName()); + MDC.put(START_TIME, String.valueOf(System.currentTimeMillis())); + MDC.put(OPERATION_ID, UUID.randomUUID().toString()); + if (MDC.get(REQUEST_ID) == null) { + var requestId = UUID.randomUUID().toString(); + MDC.put(REQUEST_ID, requestId); + } + Map params = getParams(joinPoint); + MDC.put(ARGS, params.toString()); + + log.info("Invoking API operation {} - args: {}", joinPoint.getSignature().getName(), params); + + 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()); + MDC.put(FAULT_CODE, getTitle(result)); + MDC.put(FAULT_DETAIL, getDetail(result)); + log.info("Failed API operation {} - error: {}", MDC.get(METHOD), result); + MDC.clear(); + } + + @Around(value = "repository() || service()") + public Object logTrace(ProceedingJoinPoint joinPoint) throws Throwable { + Map params = getParams(joinPoint); + log.debug("Call method {} - args: {}", joinPoint.getSignature().toShortString(), params); + Object result = joinPoint.proceed(); + log.debug("Return method {} - result: {}", joinPoint.getSignature().toShortString(), result); + return result; + } + + private static String getDetail(ResponseEntity result) { + if (result != null && result.getBody() != null && result.getBody().getDetail() != null) { + return result.getBody().getDetail(); + } else return AppError.UNKNOWN.getDetails(); + } + + private static String getTitle(ResponseEntity result) { + if (result != null && result.getBody() != null && result.getBody().getTitle() != null) { + return result.getBody().getTitle(); + } else return AppError.UNKNOWN.getTitle(); + } + + public 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 "-"; + } + + private static Map getParams(ProceedingJoinPoint joinPoint) { + CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature(); + Map params = new HashMap<>(); + int i = 0; + for (var paramName : codeSignature.getParameterNames()) { + params.put(paramName, deNull(joinPoint.getArgs()[i++])); + } + return params; + } +} diff --git a/src/main/java/it/gov/pagopa/mbd/gps/service/config/MappingsConfiguration.java b/src/main/java/it/gov/pagopa/mbd/gps/service/config/MappingsConfiguration.java new file mode 100644 index 0000000..c4c998a --- /dev/null +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/config/MappingsConfiguration.java @@ -0,0 +1,25 @@ +package it.gov.pagopa.mbd.gps.service.config; + +import it.gov.pagopa.mbd.gps.service.mapper.ConvertMbdPaymentOptionRequestToMbdPaymentOptionResponse; +import it.gov.pagopa.mbd.gps.service.model.MbdPaymentOptionRequest; +import it.gov.pagopa.mbd.gps.service.model.MbdPaymentOptionResponse; +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( + ConvertMbdPaymentOptionRequestToMbdPaymentOptionResponse mdbPaymentOptionResponseConverter) { + ModelMapper mapper = new ModelMapper(); + mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT); + + mapper + .createTypeMap(MbdPaymentOptionRequest.class, MbdPaymentOptionResponse.class) + .setConverter(mdbPaymentOptionResponseConverter); + return mapper; + } +} diff --git a/src/main/java/it/gov/pagopa/microservice/config/OpenApiConfig.java b/src/main/java/it/gov/pagopa/mbd/gps/service/config/OpenApiConfig.java similarity index 71% rename from src/main/java/it/gov/pagopa/microservice/config/OpenApiConfig.java rename to src/main/java/it/gov/pagopa/mbd/gps/service/config/OpenApiConfig.java index 6b7ec18..3a6801e 100644 --- a/src/main/java/it/gov/pagopa/microservice/config/OpenApiConfig.java +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/config/OpenApiConfig.java @@ -1,6 +1,6 @@ -package it.gov.pagopa.microservice.config; +package it.gov.pagopa.mbd.gps.service.config; -import static it.gov.pagopa.microservice.util.Constants.HEADER_REQUEST_ID; +import static it.gov.pagopa.mbd.gps.service.util.Constants.HEADER_REQUEST_ID; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; @@ -11,11 +11,15 @@ 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 io.swagger.v3.oas.models.servers.Server; +import io.swagger.v3.oas.models.servers.ServerVariable; +import io.swagger.v3.oas.models.servers.ServerVariables; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import org.springdoc.core.customizers.OpenApiCustomiser; +import org.springdoc.core.customizers.GlobalOpenApiCustomizer; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -23,12 +27,32 @@ @Configuration public class OpenApiConfig { + public static final String BASE_PATH = "/pagopa-gps-mbd-service/v1"; + @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() + .servers( + List.of( + new Server().url("http://localhost:8080"), + new Server() + .url("https://{host}{basePath}") + .variables( + new ServerVariables() + .addServerVariable( + "host", + new ServerVariable() + ._enum( + List.of( + "api.dev.platform.pagopa.it", + "api.uat.platform.pagopa.it", + "api.platform.pagopa.it")) + ._default("api.dev.platform.pagopa.it")) + .addServerVariable( + "basePath", new ServerVariable()._default(BASE_PATH))))) .components( new Components() .addSecuritySchemes( @@ -47,13 +71,10 @@ public OpenAPI customOpenAPI( } @Bean - public OpenApiCustomiser sortOperationsAlphabetically() { + public GlobalOpenApiCustomizer sortOperationsAlphabetically() { return openApi -> { Paths paths = - openApi - .getPaths() - .entrySet() - .stream() + openApi.getPaths().entrySet().stream() .sorted(Map.Entry.comparingByKey()) .collect( Paths::new, @@ -67,10 +88,7 @@ public OpenApiCustomiser sortOperationsAlphabetically() { .forEach( operation -> { var responses = - operation - .getResponses() - .entrySet() - .stream() + operation.getResponses().entrySet().stream() .sorted(Map.Entry.comparingByKey()) .collect( ApiResponses::new, @@ -84,7 +102,7 @@ public OpenApiCustomiser sortOperationsAlphabetically() { } @Bean - public OpenApiCustomiser addCommonHeaders() { + public GlobalOpenApiCustomizer addCommonHeaders() { return openApi -> openApi .getPaths() @@ -105,7 +123,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/mbd/gps/service/config/RequestFilter.java similarity index 71% rename from src/main/java/it/gov/pagopa/microservice/config/RequestFilter.java rename to src/main/java/it/gov/pagopa/mbd/gps/service/config/RequestFilter.java index 63726e3..031bf11 100644 --- a/src/main/java/it/gov/pagopa/microservice/config/RequestFilter.java +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/config/RequestFilter.java @@ -1,16 +1,16 @@ -package it.gov.pagopa.microservice.config; +package it.gov.pagopa.mbd.gps.service.config; -import static it.gov.pagopa.microservice.util.Constants.HEADER_REQUEST_ID; +import static it.gov.pagopa.mbd.gps.service.util.Constants.HEADER_REQUEST_ID; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; 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; @@ -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/mbd/gps/service/config/ResponseValidator.java similarity index 72% rename from src/main/java/it/gov/pagopa/microservice/config/ResponseValidator.java rename to src/main/java/it/gov/pagopa/mbd/gps/service/config/ResponseValidator.java index 67cc3af..c7ca490 100644 --- a/src/main/java/it/gov/pagopa/microservice/config/ResponseValidator.java +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/config/ResponseValidator.java @@ -1,9 +1,9 @@ -package it.gov.pagopa.microservice.config; +package it.gov.pagopa.mbd.gps.service.config; -import it.gov.pagopa.microservice.exception.AppException; +import it.gov.pagopa.mbd.gps.service.exception.AppException; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validator; 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; @@ -17,18 +17,22 @@ @Component public class ResponseValidator { - @Autowired - private Validator validator; + private final Validator validator; + @Autowired + public ResponseValidator(Validator validator) { + this.validator = validator; + } /** - * This method validates the response annotated with the {@link javax.validation.constraints} + * This method validates the response annotated with the {@link jakarta.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.mbd.gps.service.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/mbd/gps/service/config/WebMvcConfiguration.java similarity index 71% rename from src/main/java/it/gov/pagopa/microservice/config/WebMvcConfiguration.java rename to src/main/java/it/gov/pagopa/mbd/gps/service/config/WebMvcConfiguration.java index c61db33..8f43fda 100644 --- a/src/main/java/it/gov/pagopa/microservice/config/WebMvcConfiguration.java +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/config/WebMvcConfiguration.java @@ -1,30 +1,27 @@ -package it.gov.pagopa.microservice.config; +package it.gov.pagopa.mbd.gps.service.config; import com.fasterxml.jackson.databind.ObjectMapper; -import it.gov.pagopa.microservice.model.AppCorsConfiguration; +import it.gov.pagopa.mbd.gps.service.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("/**") + 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/mbd/gps/service/controller/HomeController.java b/src/main/java/it/gov/pagopa/mbd/gps/service/controller/HomeController.java new file mode 100644 index 0000000..d858b41 --- /dev/null +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/controller/HomeController.java @@ -0,0 +1,60 @@ +package it.gov.pagopa.mbd.gps.service.controller; + +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import it.gov.pagopa.mbd.gps.service.model.AppInfo; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +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; + + @Value("${info.application.name}") + private String name; + + @Value("${info.application.version}") + private String version; + + @Value("${info.properties.environment}") + private String environment; + + /** + * @return redirect to Swagger page documentation + */ + @Hidden + @GetMapping("") + public RedirectView home() { + if (!basePath.endsWith("/")) { + basePath += "/"; + } + return new RedirectView(basePath + "swagger-ui.html"); + } + + /** + * Return app name, version and environment + * + * @return the app info + */ + @Operation( + summary = "health check", + description = "Return OK if application is started", + security = {@SecurityRequirement(name = "ApiKey")}, + tags = {"Home"}) + @GetMapping(value = "/info") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity healthCheck() { + AppInfo info = AppInfo.builder().name(name).version(version).environment(environment).build(); + return ResponseEntity.status(HttpStatus.OK).body(info); + } +} diff --git a/src/main/java/it/gov/pagopa/mbd/gps/service/controller/MbdGpsController.java b/src/main/java/it/gov/pagopa/mbd/gps/service/controller/MbdGpsController.java new file mode 100644 index 0000000..38379f3 --- /dev/null +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/controller/MbdGpsController.java @@ -0,0 +1,90 @@ +package it.gov.pagopa.mbd.gps.service.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 io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import it.gov.pagopa.mbd.gps.service.model.MbdPaymentOptionRequest; +import it.gov.pagopa.mbd.gps.service.model.MbdPaymentOptionResponse; +import it.gov.pagopa.mbd.gps.service.model.ProblemJson; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping( + consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) +@Tag(name = "MBD GPS") +public class MbdGpsController { + + private final ModelMapper modelMapper; + + @Autowired + public MbdGpsController(ModelMapper modelMapper) { + this.modelMapper = modelMapper; + } + + /** + * Map MBD service specific data into payment option model + * + * @param mbdPaymentOptionRequest MBD data + * @return the mapped model + */ + @PostMapping("/mbd/paymentOption") + @ResponseStatus(HttpStatus.CREATED) + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "OK", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = MbdPaymentOptionResponse.class))), + @ApiResponse( + responseCode = "400", + description = "Bad Request", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ProblemJson.class))), + @ApiResponse( + responseCode = "401", + description = "Unauthorized", + content = @Content(schema = @Schema())), + @ApiResponse( + responseCode = "404", + description = "Not found", + content = @Content(schema = @Schema(implementation = ProblemJson.class))), + @ApiResponse( + responseCode = "429", + description = "Too many requests", + content = @Content(schema = @Schema())), + @ApiResponse( + responseCode = "500", + description = "Service unavailable", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ProblemJson.class))) + }) + @Operation( + summary = "Build MBD payment option model", + security = {@SecurityRequirement(name = "ApiKey")}) + public @Valid MbdPaymentOptionResponse buildMbdPaymentOption( + @RequestBody @NotNull @Valid MbdPaymentOptionRequest mbdPaymentOptionRequest) { + return this.modelMapper.map(mbdPaymentOptionRequest, MbdPaymentOptionResponse.class); + } +} diff --git a/src/main/java/it/gov/pagopa/microservice/exception/AppError.java b/src/main/java/it/gov/pagopa/mbd/gps/service/exception/AppError.java similarity index 68% rename from src/main/java/it/gov/pagopa/microservice/exception/AppError.java rename to src/main/java/it/gov/pagopa/mbd/gps/service/exception/AppError.java index d9ccb08..1f73112 100644 --- a/src/main/java/it/gov/pagopa/microservice/exception/AppError.java +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/exception/AppError.java @@ -1,30 +1,27 @@ -package it.gov.pagopa.microservice.exception; +package it.gov.pagopa.mbd.gps.service.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"), + INTERNAL_SERVER_ERROR( + HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error", "Something was wrong"), BAD_REQUEST(HttpStatus.INTERNAL_SERVER_ERROR, "Bad Request", "%s"), UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "Unauthorized", "Error during authentication"), FORBIDDEN(HttpStatus.FORBIDDEN, "Forbidden", "This method is forbidden"), - RESPONSE_NOT_READABLE(HttpStatus.BAD_GATEWAY, "Response Not Readable", "The response body is not readable"), + RESPONSE_NOT_READABLE( + HttpStatus.BAD_GATEWAY, "Response Not Readable", "The response body is not readable"), UNKNOWN(null, null, null); - 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/mbd/gps/service/exception/AppException.java similarity index 51% rename from src/main/java/it/gov/pagopa/microservice/exception/AppException.java rename to src/main/java/it/gov/pagopa/mbd/gps/service/exception/AppException.java index e16fd36..15e0273 100644 --- a/src/main/java/it/gov/pagopa/microservice/exception/AppException.java +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/exception/AppException.java @@ -1,7 +1,7 @@ -package it.gov.pagopa.microservice.exception; +package it.gov.pagopa.mbd.gps.service.exception; +import jakarta.validation.constraints.NotNull; import java.util.Formatter; -import javax.validation.constraints.NotNull; import lombok.EqualsAndHashCode; import lombok.Value; import org.springframework.http.HttpStatus; @@ -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,21 +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)); @@ -65,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/mbd/gps/service/exception/ErrorHandler.java b/src/main/java/it/gov/pagopa/mbd/gps/service/exception/ErrorHandler.java new file mode 100644 index 0000000..314d025 --- /dev/null +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/exception/ErrorHandler.java @@ -0,0 +1,190 @@ +package it.gov.pagopa.mbd.gps.service.exception; + +import it.gov.pagopa.mbd.gps.service.model.ProblemJson; +import jakarta.validation.ConstraintViolationException; +import java.util.ArrayList; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.TypeMismatchException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +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 { + + /** + * 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, + HttpStatusCode status, + WebRequest request) { + log.warn("Input not readable: ", ex); + var errorResponse = + ProblemJson.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .title(AppError.BAD_REQUEST.getTitle()) + .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, + HttpStatusCode status, + WebRequest request) { + log.warn("Missing request parameter: ", ex); + var errorResponse = + ProblemJson.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .title(AppError.BAD_REQUEST.getTitle()) + .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, HttpStatusCode status, WebRequest request) { + log.warn("Type mismatch: ", ex); + var errorResponse = + ProblemJson.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .title(AppError.BAD_REQUEST.getTitle()) + .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, + HttpStatusCode 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(AppError.BAD_REQUEST.getTitle()) + .detail(detailsMessage) + .build(); + return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler({ConstraintViolationException.class}) + public ResponseEntity handleConstraintViolationException( + final ConstraintViolationException ex, final WebRequest request) { + log.warn("Validation Error raised:", ex); + var errorResponse = + ProblemJson.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .title(AppError.BAD_REQUEST.getTitle()) + .detail(ex.getMessage()) + .build(); + return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); + } + + /** + * 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: {}\nCause of the App Exception: ", ex.getMessage(), ex.getCause()); + } else { + log.warn("App Exception raised: ", 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(AppError.INTERNAL_SERVER_ERROR.getTitle()) + .detail(ex.getMessage()) + .build(); + return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/src/main/java/it/gov/pagopa/mbd/gps/service/mapper/ConvertMbdPaymentOptionRequestToMbdPaymentOptionResponse.java b/src/main/java/it/gov/pagopa/mbd/gps/service/mapper/ConvertMbdPaymentOptionRequestToMbdPaymentOptionResponse.java new file mode 100644 index 0000000..f015718 --- /dev/null +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/mapper/ConvertMbdPaymentOptionRequestToMbdPaymentOptionResponse.java @@ -0,0 +1,82 @@ +package it.gov.pagopa.mbd.gps.service.mapper; + +import it.gov.pagopa.mbd.gps.service.model.MbdPaymentOptionRequest; +import it.gov.pagopa.mbd.gps.service.model.MbdPaymentOptionRequestProperties; +import it.gov.pagopa.mbd.gps.service.model.MbdPaymentOptionResponse; +import it.gov.pagopa.mbd.gps.service.model.PaymentOption; +import it.gov.pagopa.mbd.gps.service.model.Stamp; +import it.gov.pagopa.mbd.gps.service.model.Transfer; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import org.modelmapper.Converter; +import org.modelmapper.spi.MappingContext; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * Converter class that specify how to convert a {@link MbdPaymentOptionRequest} instance to a + * {@link MbdPaymentOptionResponse} instance + */ +@Component +public class ConvertMbdPaymentOptionRequestToMbdPaymentOptionResponse + implements Converter { + + private static final boolean IS_PARTIAL_PAYMENT = false; + private static final String ID_TRANSFER = "1"; + private static final String STAMP_TYPE = "st"; + + @Value("${mbd.payment-option.description}") + private String description; + + @Value("${mbd.transfer.remittance-information}") + private String remittanceInformation; + + @Value("${mbd.payment-option.due-date-delta}") + private int dueDateDelta; + + @Value("${mbd.payment-option.due-date-delta-time-unit}") + private ChronoUnit dueDateDeltaTimeUnit; + + @Value("${mbd.payment-option.retention-date-delta}") + private int retentionDateDelta; + + @Value("${mbd.payment-option.retention-date-delta-time-unit}") + private ChronoUnit retentionDateDeltaTimeUnit; + + @Override + public MbdPaymentOptionResponse convert( + MappingContext context) { + MbdPaymentOptionRequestProperties model = context.getSource().getProperties(); + + Transfer transfer = + Transfer.builder() + .amount(model.getAmount()) + .idTransfer(ID_TRANSFER) + .organizationFiscalCode(model.getFiscalCode()) + .remittanceInformation(remittanceInformation) + .stamp( + Stamp.builder() + .hashDocument(model.getDocumentHash()) + .provincialResidence(model.getProvincialResidence()) + .stampType(STAMP_TYPE) + .build()) + .build(); + + PaymentOption paymentOption = + PaymentOption.builder() + .firstName(model.getFirstName()) + .lastName(model.getLastName()) + .amount(model.getAmount()) + .description(description) + .dueDate(LocalDateTime.now().plus(dueDateDelta, dueDateDeltaTimeUnit)) + .retentionDate(LocalDateTime.now().plus(retentionDateDelta, retentionDateDeltaTimeUnit)) + .isPartialPayment(IS_PARTIAL_PAYMENT) + .transfer(Collections.singletonList(transfer)) + .build(); + + return MbdPaymentOptionResponse.builder() + .paymentOption(Collections.singletonList(paymentOption)) + .build(); + } +} diff --git a/src/main/java/it/gov/pagopa/microservice/model/AppCorsConfiguration.java b/src/main/java/it/gov/pagopa/mbd/gps/service/model/AppCorsConfiguration.java similarity index 92% rename from src/main/java/it/gov/pagopa/microservice/model/AppCorsConfiguration.java rename to src/main/java/it/gov/pagopa/mbd/gps/service/model/AppCorsConfiguration.java index 8d99b89..0a228c5 100644 --- a/src/main/java/it/gov/pagopa/microservice/model/AppCorsConfiguration.java +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/model/AppCorsConfiguration.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.microservice.model; +package it.gov.pagopa.mbd.gps.service.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/src/main/java/it/gov/pagopa/microservice/model/AppInfo.java b/src/main/java/it/gov/pagopa/mbd/gps/service/model/AppInfo.java similarity index 91% rename from src/main/java/it/gov/pagopa/microservice/model/AppInfo.java rename to src/main/java/it/gov/pagopa/mbd/gps/service/model/AppInfo.java index d381de0..d0665f8 100644 --- a/src/main/java/it/gov/pagopa/microservice/model/AppInfo.java +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/model/AppInfo.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.microservice.model; +package it.gov.pagopa.mbd.gps.service.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.AccessLevel; diff --git a/src/main/java/it/gov/pagopa/mbd/gps/service/model/MbdPaymentOptionRequest.java b/src/main/java/it/gov/pagopa/mbd/gps/service/model/MbdPaymentOptionRequest.java new file mode 100644 index 0000000..88f3c0c --- /dev/null +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/model/MbdPaymentOptionRequest.java @@ -0,0 +1,22 @@ +package it.gov.pagopa.mbd.gps.service.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** Model class that holds MBD GPS request */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class MbdPaymentOptionRequest { + + @Valid + @Schema( + description = "MBD payment option's properties", + requiredMode = Schema.RequiredMode.REQUIRED) + private MbdPaymentOptionRequestProperties properties; +} diff --git a/src/main/java/it/gov/pagopa/mbd/gps/service/model/MbdPaymentOptionRequestProperties.java b/src/main/java/it/gov/pagopa/mbd/gps/service/model/MbdPaymentOptionRequestProperties.java new file mode 100644 index 0000000..7f5b133 --- /dev/null +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/model/MbdPaymentOptionRequestProperties.java @@ -0,0 +1,43 @@ +package it.gov.pagopa.mbd.gps.service.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** Model class that holds MBD service specific data */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MbdPaymentOptionRequestProperties { + + @NotNull(message = "amount is required") + @Schema(description = "MBD amount", requiredMode = Schema.RequiredMode.REQUIRED) + private Long amount; + + @NotBlank + @Schema(description = "Debtor's name", requiredMode = Schema.RequiredMode.REQUIRED) + private String firstName; + + @NotBlank + @Schema(description = "Debtor's last name", requiredMode = Schema.RequiredMode.REQUIRED) + private String lastName; + + @NotBlank + @Schema(description = "Debtor's fiscal code", requiredMode = Schema.RequiredMode.REQUIRED) + private String fiscalCode; + + @NotBlank + @Schema(description = "Debtor's residence province", requiredMode = Schema.RequiredMode.REQUIRED) + private String provincialResidence; + + @NotBlank + @Size(min = 44, max = 44) + @Schema(description = "MBD document's hash", requiredMode = Schema.RequiredMode.REQUIRED) + private String documentHash; +} diff --git a/src/main/java/it/gov/pagopa/mbd/gps/service/model/MbdPaymentOptionResponse.java b/src/main/java/it/gov/pagopa/mbd/gps/service/model/MbdPaymentOptionResponse.java new file mode 100644 index 0000000..f7530d9 --- /dev/null +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/model/MbdPaymentOptionResponse.java @@ -0,0 +1,21 @@ +package it.gov.pagopa.mbd.gps.service.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** Model class that holds MBD GPS response */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class MbdPaymentOptionResponse { + + @Valid + @Schema(description = "MBD payment option", requiredMode = Schema.RequiredMode.REQUIRED) + private List<@Valid PaymentOption> paymentOption; +} diff --git a/src/main/java/it/gov/pagopa/mbd/gps/service/model/PaymentOption.java b/src/main/java/it/gov/pagopa/mbd/gps/service/model/PaymentOption.java new file mode 100644 index 0000000..59b87f2 --- /dev/null +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/model/PaymentOption.java @@ -0,0 +1,59 @@ +package it.gov.pagopa.mbd.gps.service.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.LocalDateTime; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** Model class that holds MBD payment option data */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PaymentOption { + + @NotBlank + @Schema(description = "Debtor's name", requiredMode = Schema.RequiredMode.REQUIRED) + private String firstName; + + @NotBlank + @Schema(description = "Debtor's last name", requiredMode = Schema.RequiredMode.REQUIRED) + private String lastName; + + @NotNull(message = "amount is required") + @Schema(description = "Payment option's amount", requiredMode = Schema.RequiredMode.REQUIRED) + private Long amount; + + @NotBlank(message = "payment option description is required") + @Size(max = 140) // compliant to paForNode.xsd + @Schema(description = "Payment option's description", requiredMode = Schema.RequiredMode.REQUIRED) + private String description; + + @NotNull(message = "is partial payment is required") + @Schema( + description = "Payment option's is partial payment flag", + requiredMode = Schema.RequiredMode.REQUIRED) + private Boolean isPartialPayment; + + @NotNull(message = "due date is required") + @Schema(description = "Payment option's due date", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime dueDate; + + @Schema( + description = "Payment option's retention date", + requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime retentionDate; + + @Valid + @Schema( + description = "Payment option's transfer list", + requiredMode = Schema.RequiredMode.REQUIRED) + private List<@Valid Transfer> transfer; +} diff --git a/src/main/java/it/gov/pagopa/mbd/gps/service/model/ProblemJson.java b/src/main/java/it/gov/pagopa/mbd/gps/service/model/ProblemJson.java new file mode 100644 index 0000000..424e475 --- /dev/null +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/model/ProblemJson.java @@ -0,0 +1,50 @@ +package it.gov.pagopa.mbd.gps.service.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import it.gov.pagopa.mbd.gps.service.exception.ErrorHandler; +import jakarta.validation.constraints.Max; +import jakarta.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 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/mbd/gps/service/model/Stamp.java b/src/main/java/it/gov/pagopa/mbd/gps/service/model/Stamp.java new file mode 100644 index 0000000..7a2a035 --- /dev/null +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/model/Stamp.java @@ -0,0 +1,50 @@ +package it.gov.pagopa.mbd.gps.service.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** Model class that holds MBD stamp data */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Stamp { + + @NotBlank + @Size(max = 72) + @Schema( + description = + "Document hash type is stBase64Binary72 as described in" + + " https://github.com/pagopa/pagopa-api.", + requiredMode = Schema.RequiredMode.REQUIRED) + // Stamp generally get as input a base64sha256, that is the SHA256 hash of a given string encoded + // with Base64. + // It is not equivalent to base64encode(sha256(β€œtest”)), if sha256() returns a hexadecimal + // representation. + // The result should normally be 44 characters, to be compliant with as-is it was extended to 72 + private String hashDocument; + + @NotBlank + @Size(min = 2, max = 2) + @Schema( + description = "The type of the stamp", + minLength = 2, + maxLength = 2, + requiredMode = Schema.RequiredMode.REQUIRED) + private String stampType; + + @NotBlank + @Pattern(regexp = "[A-Z]{2}") + @Schema( + description = "The province of residence", + example = "RM", + pattern = "[A-Z]{2,2}", + requiredMode = Schema.RequiredMode.REQUIRED) + private String provincialResidence; +} diff --git a/src/main/java/it/gov/pagopa/mbd/gps/service/model/Transfer.java b/src/main/java/it/gov/pagopa/mbd/gps/service/model/Transfer.java new file mode 100644 index 0000000..8acb154 --- /dev/null +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/model/Transfer.java @@ -0,0 +1,50 @@ +package it.gov.pagopa.mbd.gps.service.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** Model class that holds MBD transfer data */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Transfer { + + @NotBlank(message = "id transfer is required") + @Schema( + description = "Transfer's id", + allowableValues = {"1", "2", "3", "4", "5"}, + requiredMode = Schema.RequiredMode.REQUIRED) + private String idTransfer; + + @NotNull(message = "amount is required") + @Schema(description = "Transfer's amount", requiredMode = Schema.RequiredMode.REQUIRED) + private Long amount; + + @Schema( + description = "Fiscal code related to the organization targeted by this transfer.", + example = "00000000000") + private String organizationFiscalCode; + + @NotBlank(message = "remittance information is required") + @Size( + max = 140, + message = + "remittance information must be compliant to EACT FORMATTING RULES, up to 140 chars") + @Schema( + description = "Transfer's remittance information", + requiredMode = Schema.RequiredMode.REQUIRED) + // https://docs.pagopa.it/saci/specifiche-attuative-dei-codici-identificativi-di-versamento-riversamento-e-rendicontazione/operazione-di-trasferimento-fondi + private String remittanceInformation; // causale + + @Valid + @Schema(description = "Transfer's stamp", requiredMode = Schema.RequiredMode.REQUIRED) + private Stamp stamp; +} diff --git a/src/main/java/it/gov/pagopa/mbd/gps/service/util/CommonUtility.java b/src/main/java/it/gov/pagopa/mbd/gps/service/util/CommonUtility.java new file mode 100644 index 0000000..32e3ae1 --- /dev/null +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/util/CommonUtility.java @@ -0,0 +1,51 @@ +package it.gov.pagopa.mbd.gps.service.util; + +import java.util.Calendar; +import java.util.List; +import java.util.Optional; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class CommonUtility { + + /** + * @param value value to deNullify. + * @return return empty string if value is null + */ + public static String deNull(String value) { + return Optional.ofNullable(value).orElse(""); + } + + /** + * @param value value to deNullify. + * @return return empty string if value is null + */ + public static String deNull(Object value) { + return Optional.ofNullable(value).orElse("").toString(); + } + + /** + * @param value value to deNullify. + * @return return false if value is null + */ + public static Boolean deNull(Boolean value) { + return Optional.ofNullable(value).orElse(false); + } + + /** + * @param headers header of the CSV file + * @param rows data of the CSV file + * @return byte array of the CSV using commas (;) as separator + */ + public static byte[] createCsv(List headers, List> rows) { + var csv = new StringBuilder(); + csv.append(String.join(";", headers)); + rows.forEach(row -> csv.append(System.lineSeparator()).append(String.join(";", row))); + return csv.toString().getBytes(); + } + + public static long getTimelapse(long startTime) { + return Calendar.getInstance().getTimeInMillis() - startTime; + } +} diff --git a/src/main/java/it/gov/pagopa/microservice/util/Constants.java b/src/main/java/it/gov/pagopa/mbd/gps/service/util/Constants.java similarity index 77% rename from src/main/java/it/gov/pagopa/microservice/util/Constants.java rename to src/main/java/it/gov/pagopa/mbd/gps/service/util/Constants.java index e5812c7..0e3c68d 100644 --- a/src/main/java/it/gov/pagopa/microservice/util/Constants.java +++ b/src/main/java/it/gov/pagopa/mbd/gps/service/util/Constants.java @@ -1,11 +1,9 @@ -package it.gov.pagopa.microservice.util; +package it.gov.pagopa.mbd.gps.service.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/microservice/config/LoggingAspect.java b/src/main/java/it/gov/pagopa/microservice/config/LoggingAspect.java deleted file mode 100644 index 8990632..0000000 --- a/src/main/java/it/gov/pagopa/microservice/config/LoggingAspect.java +++ /dev/null @@ -1,162 +0,0 @@ -package it.gov.pagopa.microservice.config; - -import it.gov.pagopa.microservice.exception.AppError; -import it.gov.pagopa.microservice.model.ProblemJson; -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.aspectj.lang.reflect.CodeSignature; -import org.slf4j.MDC; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -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.HashMap; -import java.util.Map; -import java.util.UUID; - -import static it.gov.pagopa.microservice.util.CommonUtility.deNull; - - -@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"; - public static final String FAULT_CODE = "faultCode"; - public static final String FAULT_DETAIL = "faultDetail"; - public static final String REQUEST_ID = "requestId"; - public static final String OPERATION_ID = "operationId"; - public static final String ARGS = "args"; - - @Autowired - HttpServletRequest httRequest; - - @Autowired - HttpServletResponse httpResponse; - - @Value("${info.application.name}") - private String name; - - @Value("${info.application.version}") - private String version; - - @Value("${info.properties.environment}") - private String environment; - - - @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 {}", name, version, environment); - } - - @Around(value = "restController()") - public Object logApiInvocation(ProceedingJoinPoint joinPoint) throws Throwable { - MDC.put(METHOD, joinPoint.getSignature().getName()); - MDC.put(START_TIME, String.valueOf(System.currentTimeMillis())); - MDC.put(OPERATION_ID, UUID.randomUUID().toString()); - if(MDC.get(REQUEST_ID) == null) { - var requestId = UUID.randomUUID().toString(); - MDC.put(REQUEST_ID, requestId); - } - Map params = getParams(joinPoint); - MDC.put(ARGS, params.toString()); - - log.info("Invoking API operation {} - args: {}", joinPoint.getSignature().getName(), params); - - 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()); - MDC.put(FAULT_CODE, getTitle(result)); - MDC.put(FAULT_DETAIL, getDetail(result)); - log.info("Failed API operation {} - error: {}", MDC.get(METHOD), result); - MDC.clear(); - } - - @Around(value = "repository() || service()") - public Object logTrace(ProceedingJoinPoint joinPoint) throws Throwable { - Map params = getParams(joinPoint); - log.debug("Call method {} - args: {}", joinPoint.getSignature().toShortString(), params); - Object result = joinPoint.proceed(); - log.debug("Return method {} - result: {}", joinPoint.getSignature().toShortString(), result); - return result; - } - - private static String getDetail(ResponseEntity result) { - if(result != null && result.getBody() != null && result.getBody().getDetail() != null) { - return result.getBody().getDetail(); - } else return AppError.UNKNOWN.getDetails(); - } - - private static String getTitle(ResponseEntity result) { - if(result != null && result.getBody() != null && result.getBody().getTitle() != null) { - return result.getBody().getTitle(); - } else return AppError.UNKNOWN.getTitle(); - } - - public 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 "-"; - } - - private static Map getParams(ProceedingJoinPoint joinPoint) { - CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature(); - Map params = new HashMap<>(); - int i = 0; - for (var paramName : codeSignature.getParameterNames()) { - params.put(paramName, deNull(joinPoint.getArgs()[i++])); - } - return params; - } -} 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 b403822..0000000 --- a/src/main/java/it/gov/pagopa/microservice/config/MappingsConfiguration.java +++ /dev/null @@ -1,19 +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/controller/HomeController.java b/src/main/java/it/gov/pagopa/microservice/controller/HomeController.java deleted file mode 100644 index 70b6dd1..0000000 --- a/src/main/java/it/gov/pagopa/microservice/controller/HomeController.java +++ /dev/null @@ -1,30 +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/ErrorHandler.java b/src/main/java/it/gov/pagopa/microservice/exception/ErrorHandler.java deleted file mode 100644 index 3fd7e49..0000000 --- a/src/main/java/it/gov/pagopa/microservice/exception/ErrorHandler.java +++ /dev/null @@ -1,220 +0,0 @@ -package it.gov.pagopa.microservice.exception; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import feign.FeignException; -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; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; - -/** - * All Exceptions are handled by this class - */ -@ControllerAdvice -@Slf4j -public class ErrorHandler extends ResponseEntityExceptionHandler { - - /** - * 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(AppError.BAD_REQUEST.getTitle()) - .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(AppError.BAD_REQUEST.getTitle()) - .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(AppError.BAD_REQUEST.getTitle()) - .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(AppError.BAD_REQUEST.getTitle()) - .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(AppError.BAD_REQUEST.getTitle()) - .detail(ex.getMessage()) - .build(); - return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); - } - - - /** - * Handle if a {@link FeignException} is raised - * - * @param ex {@link FeignException} exception raised - * @param request from frontend - * @return a {@link ProblemJson} as response with the cause and with an appropriated HTTP status - */ - @ExceptionHandler({FeignException.class}) - public ResponseEntity handleFeignException(final FeignException ex, final WebRequest request) { - log.warn("FeignException raised: ", ex); - - ProblemJson problem; - if(ex.responseBody().isPresent()) { - var body = new String(ex.responseBody().get().array(), StandardCharsets.UTF_8); - try { - problem = new ObjectMapper().readValue(body, ProblemJson.class); - } catch (JsonProcessingException e) { - problem = ProblemJson.builder() - .status(HttpStatus.BAD_GATEWAY.value()) - .title(AppError.RESPONSE_NOT_READABLE.getTitle()) - .detail(AppError.RESPONSE_NOT_READABLE.getDetails()) - .build(); - } - } else { - problem = ProblemJson.builder() - .status(HttpStatus.BAD_GATEWAY.value()) - .title("No Response Body") - .detail("Error with external dependency") - .build(); - } - - return new ResponseEntity<>(problem, HttpStatus.valueOf(problem.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); - } - 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(AppError.INTERNAL_SERVER_ERROR.getTitle()) - .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 deleted file mode 100644 index db11a37..0000000 --- a/src/main/java/it/gov/pagopa/microservice/model/ProblemJson.java +++ /dev/null @@ -1,40 +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/CommonUtility.java b/src/main/java/it/gov/pagopa/microservice/util/CommonUtility.java deleted file mode 100644 index f080c47..0000000 --- a/src/main/java/it/gov/pagopa/microservice/util/CommonUtility.java +++ /dev/null @@ -1,55 +0,0 @@ -package it.gov.pagopa.microservice.util; - -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - -import java.util.Calendar; -import java.util.List; -import java.util.Optional; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class CommonUtility { - - - /** - * @param value value to deNullify. - * @return return empty string if value is null - */ - public static String deNull(String value) { - return Optional.ofNullable(value).orElse(""); - } - - /** - * @param value value to deNullify. - * @return return empty string if value is null - */ - public static String deNull(Object value) { - return Optional.ofNullable(value).orElse("").toString(); - } - - /** - * @param value value to deNullify. - * @return return false if value is null - */ - public static Boolean deNull(Boolean value) { - return Optional.ofNullable(value).orElse(false); - } - - /** - * @param headers header of the CSV file - * @param rows data of the CSV file - * @return byte array of the CSV using commas (;) as separator - */ - public static byte[] createCsv(List headers, List> rows) { - var csv = new StringBuilder(); - csv.append(String.join(";", headers)); - rows.forEach(row -> csv.append(System.lineSeparator()).append(String.join(";", row))); - return csv.toString().getBytes(); - } - - public static long getTimelapse(long startTime) { - return Calendar.getInstance().getTimeInMillis() - startTime; - } - - -} diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties index 1e89894..f46d6a5 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -1,7 +1,43 @@ # Info +info.application.name=@project.name@ +info.application.artifactId=@project.artifactId@ +info.application.version=@project.version@ +info.application.description=@project.description@ info.properties.environment=local + +# Actuator +management.endpoints.web.exposure.include=health,info +management.endpoints.jmx.exposure.include=health,info +management.info.env.enabled=true +management.endpoint.health.probes.enabled=true +management.health.livenessState.enabled=true +management.health.readinessState.enabled=true + +# Openapi +springdoc.writer-with-order-by-keys=true +springdoc.writer-with-default-pretty-printer=true + +# Server +server.servlet.context-path=/ +server.port=8080 + # Logging logging.level.root=INFO logging.level.it.gov.pagopa=DEBUG + # CORS configuration cors.configuration={"origins": ["*"], "methods": ["*"]} + +# Jackson serialization +spring.jackson.default-property-inclusion=NON_NULL +spring.jackson.serialization.write-dates-as-timestamps=false +spring.jackson.serialization.fail-on-empty-beans=false +spring.jackson.deserialization.fail-on-unknown-properties=false + +# App config +mbd.payment-option.description=Pagamento marca da bollo digitale +mbd.payment-option.due-date-delta=20 +mbd.payment-option.due-date-delta-time-unit=Minutes +mbd.payment-option.retention-date-delta=1 +mbd.payment-option.retention-date-delta-time-unit=Hours +mbd.transfer.remittance-information=Pagamento marca da bollo digitale \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 5ad4771..0e86a07 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,8 +1,10 @@ # Info +info.application.name=@project.name@ info.application.artifactId=@project.artifactId@ info.application.version=@project.version@ info.application.description=@project.description@ info.properties.environment=${ENV:azure} + # Actuator management.endpoints.web.exposure.include=health,info management.endpoints.jmx.exposure.include=health,info @@ -10,16 +12,32 @@ management.info.env.enabled=true management.endpoint.health.probes.enabled=true management.health.livenessState.enabled=true management.health.readinessState.enabled=true + # Openapi 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.port=8080 + # Logging 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": ["*"]}} + +# Jackson serialization +spring.jackson.default-property-inclusion=NON_NULL +spring.jackson.serialization.write-dates-as-timestamps=false +spring.jackson.serialization.fail-on-empty-beans=false +spring.jackson.deserialization.fail-on-unknown-properties=false +# App config +mbd.payment-option.description=${MBD_PAYMENT_OPTION_DESCRIPTION:Pagamento marca da bollo digitale} +mbd.payment-option.due-date-delta=${MBD_PAYMENT_OPTION_DUE_DATE_DELTA:20} +mbd.payment-option.due-date-delta-time-unit=${MBD_PAYMENT_OPTION_DUE_DATE_TIME_UNIT:Minutes} +mbd.payment-option.retention-date-delta=${MBD_PAYMENT_OPTION_RETENTION_DATE_DELTA:1} +mbd.payment-option.retention-date-delta-time-unit=${MBD_PAYMENT_OPTION_RETENTION_DATE_TIME_UNIT:Hours} +mbd.transfer.remittance-information=${MBD_TRANSFER_REMITTANCE_INFORMATION:Pagamento marca da bollo digitale} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/microservice/ApplicationTest.java b/src/test/java/it/gov/pagopa/mbd/gps/service/ApplicationTest.java similarity index 88% rename from src/test/java/it/gov/pagopa/microservice/ApplicationTest.java rename to src/test/java/it/gov/pagopa/mbd/gps/service/ApplicationTest.java index 80c6e10..91215c9 100644 --- a/src/test/java/it/gov/pagopa/microservice/ApplicationTest.java +++ b/src/test/java/it/gov/pagopa/mbd/gps/service/ApplicationTest.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.microservice; +package it.gov.pagopa.mbd.gps.service; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/src/test/java/it/gov/pagopa/microservice/OpenApiGenerationTest.java b/src/test/java/it/gov/pagopa/mbd/gps/service/OpenApiGenerationTest.java similarity index 96% rename from src/test/java/it/gov/pagopa/microservice/OpenApiGenerationTest.java rename to src/test/java/it/gov/pagopa/mbd/gps/service/OpenApiGenerationTest.java index 07f521b..67586b3 100644 --- a/src/test/java/it/gov/pagopa/microservice/OpenApiGenerationTest.java +++ b/src/test/java/it/gov/pagopa/mbd/gps/service/OpenApiGenerationTest.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.microservice; +package it.gov.pagopa.mbd.gps.service; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -29,7 +29,7 @@ void swaggerSpringPlugin() throws Exception { mvc.perform(MockMvcRequestBuilders.get("/v3/api-docs").accept(MediaType.APPLICATION_JSON)) .andExpect(MockMvcResultMatchers.status().is2xxSuccessful()) .andDo( - (result) -> { + result -> { assertNotNull(result); assertNotNull(result.getResponse()); final String content = result.getResponse().getContentAsString(); diff --git a/src/test/java/it/gov/pagopa/mbd/gps/service/controller/HomeControllerTest.java b/src/test/java/it/gov/pagopa/mbd/gps/service/controller/HomeControllerTest.java new file mode 100644 index 0000000..56db799 --- /dev/null +++ b/src/test/java/it/gov/pagopa/mbd/gps/service/controller/HomeControllerTest.java @@ -0,0 +1,27 @@ +package it.gov.pagopa.mbd.gps.service.controller; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +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.test.web.servlet.MockMvc; + +@AutoConfigureMockMvc +@SpringBootTest +class HomeControllerTest { + + @Autowired private MockMvc mvc; + + @Test + void healthCheckTestSuccess() throws Exception { + mvc.perform(get("/info")).andExpect(status().isOk()); + } + + @Test + void homeTestSuccess() throws Exception { + mvc.perform(get("")).andExpect(status().is3xxRedirection()); + } +} diff --git a/src/test/java/it/gov/pagopa/mbd/gps/service/controller/MbdGpsControllerTest.java b/src/test/java/it/gov/pagopa/mbd/gps/service/controller/MbdGpsControllerTest.java new file mode 100644 index 0000000..30e4084 --- /dev/null +++ b/src/test/java/it/gov/pagopa/mbd/gps/service/controller/MbdGpsControllerTest.java @@ -0,0 +1,177 @@ +package it.gov.pagopa.mbd.gps.service.controller; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import it.gov.pagopa.mbd.gps.service.model.MbdPaymentOptionRequest; +import it.gov.pagopa.mbd.gps.service.model.MbdPaymentOptionRequestProperties; +import it.gov.pagopa.mbd.gps.service.model.MbdPaymentOptionResponse; +import it.gov.pagopa.mbd.gps.service.model.PaymentOption; +import it.gov.pagopa.mbd.gps.service.model.Transfer; +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.MvcResult; + +@AutoConfigureMockMvc +@SpringBootTest +class MbdGpsControllerTest { + + @Autowired private MockMvc mvc; + + @Autowired private ObjectMapper objectMapper; + + @Test + void buildMbdPaymentOptionTestSuccess() throws Exception { + MbdPaymentOptionRequest request = buildMbdPaymentOptionRequest(); + + MvcResult result = + mvc.perform( + post("/mbd/paymentOption") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andReturn(); + + String json = result.getResponse().getContentAsString(); + + assertNotNull(json); + + MbdPaymentOptionResponse response = + objectMapper.readValue(json, MbdPaymentOptionResponse.class); + + assertNotNull(response); + assertNotNull(response.getPaymentOption()); + assertEquals(1, response.getPaymentOption().size()); + + PaymentOption paymentOption = response.getPaymentOption().get(0); + assertEquals(request.getProperties().getFirstName(), paymentOption.getFirstName()); + assertEquals(request.getProperties().getLastName(), paymentOption.getLastName()); + assertEquals(request.getProperties().getAmount(), paymentOption.getAmount()); + assertNotNull(paymentOption.getDescription()); + assertNotNull(paymentOption.getDueDate()); + assertNotNull(paymentOption.getRetentionDate()); + assertFalse(paymentOption.getIsPartialPayment()); + assertNotNull(paymentOption.getTransfer()); + assertEquals(1, paymentOption.getTransfer().size()); + + Transfer transfer = paymentOption.getTransfer().get(0); + assertEquals(request.getProperties().getAmount(), transfer.getAmount()); + assertEquals(request.getProperties().getFiscalCode(), transfer.getOrganizationFiscalCode()); + assertEquals("1", transfer.getIdTransfer()); + assertNotNull(transfer.getRemittanceInformation()); + assertNotNull(transfer.getStamp()); + assertEquals( + request.getProperties().getProvincialResidence(), + transfer.getStamp().getProvincialResidence()); + assertEquals(request.getProperties().getDocumentHash(), transfer.getStamp().getHashDocument()); + assertEquals("st", transfer.getStamp().getStampType()); + } + + @Test + void buildMbdPaymentOptionTestFailAmountMissing() throws Exception { + MbdPaymentOptionRequest request = buildMbdPaymentOptionRequest(); + request.getProperties().setAmount(null); + + mvc.perform( + post("/mbd/paymentOption") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().is4xxClientError()); + } + + @Test + void buildMbdPaymentOptionTestFailFirstNameMissing() throws Exception { + MbdPaymentOptionRequest request = buildMbdPaymentOptionRequest(); + request.getProperties().setFirstName(null); + + mvc.perform( + post("/mbd/paymentOption") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().is4xxClientError()); + } + + @Test + void buildMbdPaymentOptionTestFailLastNameMissing() throws Exception { + MbdPaymentOptionRequest request = buildMbdPaymentOptionRequest(); + request.getProperties().setLastName(null); + + mvc.perform( + post("/mbd/paymentOption") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().is4xxClientError()); + } + + @Test + void buildMbdPaymentOptionTestFailFiscalCodeMissing() throws Exception { + MbdPaymentOptionRequest request = buildMbdPaymentOptionRequest(); + request.getProperties().setFiscalCode(null); + + mvc.perform( + post("/mbd/paymentOption") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().is4xxClientError()); + } + + @Test + void buildMbdPaymentOptionTestFailProvincialResidenceMissing() throws Exception { + MbdPaymentOptionRequest request = buildMbdPaymentOptionRequest(); + request.getProperties().setProvincialResidence(null); + + mvc.perform( + post("/mbd/paymentOption") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().is4xxClientError()); + } + + @Test + void buildMbdPaymentOptionTestFailDocumentHashMissing() throws Exception { + MbdPaymentOptionRequest request = buildMbdPaymentOptionRequest(); + request.getProperties().setDocumentHash(null); + + mvc.perform( + post("/mbd/paymentOption") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().is4xxClientError()); + } + + @Test + void buildMbdPaymentOptionTestFailDocumentHashWrongSize() throws Exception { + MbdPaymentOptionRequest request = buildMbdPaymentOptionRequest(); + request.getProperties().setDocumentHash("asdfsdf"); + + mvc.perform( + post("/mbd/paymentOption") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().is4xxClientError()); + } + + private MbdPaymentOptionRequest buildMbdPaymentOptionRequest() { + return MbdPaymentOptionRequest.builder() + .properties( + MbdPaymentOptionRequestProperties.builder() + .amount(16L) + .firstName("Mario") + .lastName("Rossi") + .fiscalCode("0000000000000000") + .provincialResidence("AS") + .documentHash("1trA5qyjSZNwiwtGG46dyjRpL16TFgGCFvnfFzQrFHbB") + .build()) + .build(); + } +} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index cd4c92f..8468f23 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -1,7 +1,43 @@ # Info +info.application.name=MBD GPS Service +info.application.artifactId=pagopa-gps-mbd-service +info.application.version=0.0.0 +info.application.description=MBD GPS Service info.properties.environment=test -# logging + +# Actuator +management.endpoints.web.exposure.include=health,info +management.endpoints.jmx.exposure.include=health,info +management.info.env.enabled=true +management.endpoint.health.probes.enabled=true +management.health.livenessState.enabled=true +management.health.readinessState.enabled=true + +# Server +server.servlet.context-path=/ +server.port=8080 + +# Logging logging.level.root=INFO logging.level.it.gov.pagopa=INFO + # CORS configuration cors.configuration={"origins": ["*"], "methods": ["*"]} + +# Openapi +springdoc.writer-with-order-by-keys=true +springdoc.writer-with-default-pretty-printer=true + +# Jackson serialization +spring.jackson.default-property-inclusion=NON_NULL +spring.jackson.serialization.write-dates-as-timestamps=false +spring.jackson.serialization.fail-on-empty-beans=false +spring.jackson.deserialization.fail-on-unknown-properties=false + +# App config +mbd.payment-option.description=Pagamento marca da bollo digitale +mbd.payment-option.due-date-delta=20 +mbd.payment-option.due-date-delta-time-unit=Minutes +mbd.payment-option.retention-date-delta=1 +mbd.payment-option.retention-date-delta-time-unit=Hours +mbd.transfer.remittance-information=Pagamento marca da bollo digitale \ No newline at end of file