diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/.gitignore b/jdk_21_maven/cs/rest/pn-paper-tracker/.gitignore new file mode 100644 index 000000000..a3d88a0e7 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/.gitignore @@ -0,0 +1,49 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +.history +.vscode +target + +# coverage +.nyc_output/ +coverage/ + +# lambda +function.zip +code_layer.zip +lib_layer.zip +nodejs/ + +# Node.js +node_modules/ \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/CODEOWNERS b/jdk_21_maven/cs/rest/pn-paper-tracker/CODEOWNERS new file mode 100644 index 000000000..fdc25761c --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/CODEOWNERS @@ -0,0 +1,5 @@ +# Root folder ownership to administrators and developers team +* @pagopa/pn-pagopa-core-analog + +# scripts/aws/cfn ownership on yml files to administrators +scripts/aws/cfn/*.yml @pagopa/pn-pagopa-infra \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/LICENSE b/jdk_21_maven/cs/rest/pn-paper-tracker/LICENSE new file mode 100644 index 000000000..a74f04438 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/LICENSE @@ -0,0 +1,274 @@ +European Union Public Licence +V. 1.2 + +EUPL © the European Union 2007, 2016 + +This European Union Public Licence (the ‘EUPL’) applies to the Work (as +defined below) which is provided under the terms of this Licence. Any use of +the Work, other than as authorised under this Licence is prohibited (to the +extent such use is covered by a right of the copyright holder of the Work). + +The Work is provided under the terms of this Licence when the Licensor (as +defined below) has placed the following notice immediately following the +copyright notice for the Work: “Licensed under the EUPL”, or has expressed by +any other means his willingness to license under the EUPL. + +1. Definitions + +In this Licence, the following terms have the following meaning: +— ‘The Licence’: this Licence. +— ‘The Original Work’: the work or software distributed or communicated by the + ‘Licensor under this Licence, available as Source Code and also as + ‘Executable Code as the case may be. +— ‘Derivative Works’: the works or software that could be created by the + ‘Licensee, based upon the Original Work or modifications thereof. This + ‘Licence does not define the extent of modification or dependence on the + ‘Original Work required in order to classify a work as a Derivative Work; + ‘this extent is determined by copyright law applicable in the country + ‘mentioned in Article 15. +— ‘The Work’: the Original Work or its Derivative Works. +— ‘The Source Code’: the human-readable form of the Work which is the most + convenient for people to study and modify. + +— ‘The Executable Code’: any code which has generally been compiled and which + is meant to be interpreted by a computer as a program. +— ‘The Licensor’: the natural or legal person that distributes or communicates + the Work under the Licence. +— ‘Contributor(s)’: any natural or legal person who modifies the Work under + the Licence, or otherwise contributes to the creation of a Derivative Work. +— ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of + the Work under the terms of the Licence. +— ‘Distribution’ or ‘Communication’: any act of selling, giving, lending, + renting, distributing, communicating, transmitting, or otherwise making + available, online or offline, copies of the Work or providing access to its + essential functionalities at the disposal of any other natural or legal + person. + +2. Scope of the rights granted by the Licence + +The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +sublicensable licence to do the following, for the duration of copyright +vested in the Original Work: + +— use the Work in any circumstance and for all usage, +— reproduce the Work, +— modify the Work, and make Derivative Works based upon the Work, +— communicate to the public, including the right to make available or display + the Work or copies thereof to the public and perform publicly, as the case + may be, the Work, +— distribute the Work or copies thereof, +— lend and rent the Work or copies thereof, +— sublicense rights in the Work or copies thereof. + +Those rights can be exercised on any media, supports and formats, whether now +known or later invented, as far as the applicable law permits so. + +In the countries where moral rights apply, the Licensor waives his right to +exercise his moral right to the extent allowed by law in order to make +effective the licence of the economic rights here above listed. + +The Licensor grants to the Licensee royalty-free, non-exclusive usage rights +to any patents held by the Licensor, to the extent necessary to make use of +the rights granted on the Work under this Licence. + +3. Communication of the Source Code + +The Licensor may provide the Work either in its Source Code form, or as +Executable Code. If the Work is provided as Executable Code, the Licensor +provides in addition a machine-readable copy of the Source Code of the Work +along with each copy of the Work that the Licensor distributes or indicates, +in a notice following the copyright notice attached to the Work, a repository +where the Source Code is easily and freely accessible for as long as the +Licensor continues to distribute or communicate the Work. + +4. Limitations on copyright + +Nothing in this Licence is intended to deprive the Licensee of the benefits +from any exception or limitation to the exclusive rights of the rights owners +in the Work, of the exhaustion of those rights or of other applicable +limitations thereto. + +5. Obligations of the Licensee + +The grant of the rights mentioned above is subject to some restrictions and +obligations imposed on the Licensee. Those obligations are the following: + +Attribution right: The Licensee shall keep intact all copyright, patent or +trademarks notices and all notices that refer to the Licence and to the +disclaimer of warranties. The Licensee must include a copy of such notices and +a copy of the Licence with every copy of the Work he/she distributes or +communicates. The Licensee must cause any Derivative Work to carry prominent +notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes or communicates copies of the +Original Works or Derivative Works, this Distribution or Communication will be +done under the terms of this Licence or of a later version of this Licence +unless the Original Work is expressly distributed only under this version of +the Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee +(becoming Licensor) cannot offer or impose any additional terms or conditions +on the Work or Derivative Work that alter or restrict the terms of the +Licence. + +Compatibility clause: If the Licensee Distributes or Communicates Derivative +Works or copies thereof based upon both the Work and another work licensed +under a Compatible Licence, this Distribution or Communication can be done +under the terms of this Compatible Licence. For the sake of this clause, +‘Compatible Licence’ refers to the licences listed in the appendix attached to +this Licence. Should the Licensee's obligations under the Compatible Licence +conflict with his/her obligations under this Licence, the obligations of the +Compatible Licence shall prevail. + +Provision of Source Code: When distributing or communicating copies of the +Work, the Licensee will provide a machine-readable copy of the Source Code or +indicate a repository where this Source will be easily and freely available +for as long as the Licensee continues to distribute or communicate the Work. + +Legal Protection: This Licence does not grant permission to use the trade +names, trademarks, service marks, or names of the Licensor, except as required +for reasonable and customary use in describing the origin of the Work and +reproducing the content of the copyright notice. + +6. Chain of Authorship + +The original Licensor warrants that the copyright in the Original Work granted +hereunder is owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each Contributor warrants that the copyright in the modifications he/she +brings to the Work are owned by him/her or licensed to him/her and that he/she +has the power and authority to grant the Licence. + +Each time You accept the Licence, the original Licensor and subsequent +Contributors grant You a licence to their contributions to the Work, under the +terms of this Licence. + +7. Disclaimer of Warranty + +The Work is a work in progress, which is continuously improved by numerous +Contributors. It is not a finished work and may therefore contain defects or +‘bugs’ inherent to this type of development. + +For the above reason, the Work is provided under the Licence on an ‘as is’ +basis and without warranties of any kind concerning the Work, including +without limitation merchantability, fitness for a particular purpose, absence +of defects or errors, accuracy, non-infringement of intellectual property +rights other than copyright as stated in Article 6 of this Licence. + +This disclaimer of warranty is an essential part of the Licence and a +condition for the grant of any rights to the Work. + +8. Disclaimer of Liability + +Except in the cases of wilful misconduct or damages directly caused to natural +persons, the Licensor will in no event be liable for any direct or indirect, +material or moral, damages of any kind, arising out of the Licence or of the +use of the Work, including without limitation, damages for loss of goodwill, +work stoppage, computer failure or malfunction, loss of data or any commercial +damage, even if the Licensor has been advised of the possibility of such +damage. However, the Licensor will be liable under statutory product liability +laws as far such laws apply to the Work. + +9. Additional agreements + +While distributing the Work, You may choose to conclude an additional +agreement, defining obligations or services consistent with this Licence. +However, if accepting obligations, You may act only on your own behalf and on +your sole responsibility, not on behalf of the original Licensor or any other +Contributor, and only if You agree to indemnify, defend, and hold each +Contributor harmless for any liability incurred by, or claims asserted against +such Contributor by the fact You have accepted any warranty or additional +liability. + +10. Acceptance of the Licence + +The provisions of this Licence can be accepted by clicking on an icon ‘I +agree’ placed under the bottom of a window displaying the text of this Licence +or by affirming consent in any other similar way, in accordance with the rules +of applicable law. Clicking on that icon indicates your clear and irrevocable +acceptance of this Licence and all of its terms and conditions. + +Similarly, you irrevocably accept this Licence and all of its terms and +conditions by exercising any rights granted to You by Article 2 of this +Licence, such as the use of the Work, the creation by You of a Derivative Work +or the Distribution or Communication by You of the Work or copies thereof. + +11. Information to the public + +In case of any Distribution or Communication of the Work by means of +electronic communication by You (for example, by offering to download the Work +from a remote location) the distribution channel or media (for example, a +website) must at least provide to the public the information requested by the +applicable law regarding the Licensor, the Licence and the way it may be +accessible, concluded, stored and reproduced by the Licensee. + +12. Termination of the Licence + +The Licence and the rights granted hereunder will terminate automatically upon +any breach by the Licensee of the terms of the Licence. Such a termination +will not terminate the licences of any person who has received the Work from +the Licensee under the Licence, provided such persons remain in full +compliance with the Licence. + +13. Miscellaneous + +Without prejudice of Article 9 above, the Licence represents the complete +agreement between the Parties as to the Work. + +If any provision of the Licence is invalid or unenforceable under applicable +law, this will not affect the validity or enforceability of the Licence as a +whole. Such provision will be construed or reformed so as necessary to make it +valid and enforceable. + +The European Commission may publish other linguistic versions or new versions +of this Licence or updated versions of the Appendix, so far this is required +and reasonable, without reducing the scope of the rights granted by the +Licence. New versions of the Licence will be published with a unique version +number. + +All linguistic versions of this Licence, approved by the European Commission, +have identical value. Parties can take advantage of the linguistic version of +their choice. + +14. Jurisdiction + +Without prejudice to specific agreement between parties, +— any litigation resulting from the interpretation of this License, arising + between the European Union institutions, bodies, offices or agencies, as a + Licensor, and any Licensee, will be subject to the jurisdiction of the Court + of Justice of the European Union, as laid down in article 272 of the Treaty + on the Functioning of the European Union, +— any litigation arising between other parties and resulting from the + interpretation of this License, will be subject to the exclusive + jurisdiction of the competent court where the Licensor resides or conducts + its primary business. + +15. Applicable Law + +Without prejudice to specific agreement between parties, +— this Licence shall be governed by the law of the European Union Member State + where the Licensor has his seat, resides or has his registered office, +— this licence shall be governed by Belgian law if the Licensor has no seat, + residence or registered office inside a European Union Member State. + +Appendix + +‘Compatible Licences’ according to Article 5 EUPL are: +— GNU General Public License (GPL) v. 2, v. 3 +— GNU Affero General Public License (AGPL) v. 3 +— Open Software License (OSL) v. 2.1, v. 3.0 +— Eclipse Public License (EPL) v. 1.0 +— CeCILL v. 2.0, v. 2.1 +— Mozilla Public Licence (MPL) v. 2 +— GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 +— Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for + works other than software +— European Union Public Licence (EUPL) v. 1.1, v. 1.2 +— Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or + Strong Reciprocity (LiLiQ-R+) + +— The European Commission may update this Appendix to later versions of the + above licences without producing a new version of the EUPL, as long as they + provide the rights granted in Article 2 of this Licence and protect the + covered Source Code from exclusive appropriation. +— All other changes or additions to this Appendix require the production of a + new EUPL version. diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/README.md b/jdk_21_maven/cs/rest/pn-paper-tracker/README.md new file mode 100644 index 000000000..842a7672b --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/README.md @@ -0,0 +1,133 @@ +# pn-paper-tracker + +## Indice +- [Descrizione](#descrizione) +- [Tecnologie Utilizzate](#tecnologie-utilizzate) +- [Architettura](#architettura) +- [Interfacce del Servizio](#interfacce-del-servizio) +- [Allarmi e monitoraggio](#allarmi-e-monitoraggio) +- [Configurazione](#configurazione) +- [Esecuzione](#esecuzione) + +## Descrizione +**pn-paper-tracker** è il microservizio responsabile della validazione e tracciamento delle spedizioni analogiche (cartacee) all'interno del sistema **[SEND](https://notifichedigitali.it/)**. + +### Responsabilità principali + +* **Ricezione eventi:** Riceve gli aggiornamenti di stato delle spedizioni dal consolidatore tramite **pn-external-channel** +* **Validazione triplette:** Valida la coerenza degli eventi ricevuti (pre-esito, dematerializzazione, fascicolo chiuso) verificando: + * Presenza di tutti gli eventi richiesti per ogni flusso + * Coerenza dei timestamp di business + * Correttezza dei metadati (`registeredLetterCode`, `deliveryFailureCause`, ecc.) +* **Validazione OCR/AI:** Integra il servizio OCR per la verifica dei documenti allegati scansionati +* **Tracciamento spedizioni:** Mantiene lo stato completo di ogni tentativo di spedizione verso un destinatario +* **Inoltro eventi:** Produce eventi validati verso **pn-delivery-push** per l'aggiornamento della timeline notifica + +### Prodotti postali gestiti +* AR (Raccomandata con Ricevuta di Ritorno) +* RIR (Raccomandata Internazionale con Ricevuta di Ritorno) +* 890 (Atti Giudiziari) +* RS, RIS (Raccomandata Semplice, Raccomandati Internazionale Semplice) + +### Contesto storico +Il servizio è stato introdotto per separare le responsabilità di validazione e tracciamento dal microservizio **pn-paper-channel**, che ora gestisce solo le fasi iniziali (PREPARE e SEND). +Questa riorganizzazione permette di: +* Ridurre la complessità di pn-paper-channel +* Migliorare la manutenibilità +* Introdurre nuove validazioni OCR + +## Tecnologie Utilizzate +### Stack Tecnologico +* **Java 21** con **Spring Boot 3.x** e **WebFlux** +* **AWS SDK v2** per integrazione servizi AWS +* **OpenAPI 3.0** per definizione contratti API +* **AsyncAPI 3.0** per definizione DTO code +* **Maven** per build management + +### Servizi utilizzati +* **Amazon DynamoDB:** DB principale per tracciamento spedizioni +* **Amazon SQS:** messaggistica asincrona + +## Architettura + +Il microservizio pn-paper-tracker implementa una logica di business articolata per la gestione degli eventi provenienti dal consolidatore. +Per una descrizione dettagliata dei flussi, delle classi principali e delle sequenze di eventi, +è possibile consultare il file [ArchitetturaInterna.md](docs/ms/ArchitetturaInterna.md). + +### Dipendenze interne + +- **pn-paper-channel:** per la richiesta di retry nel caso di eventi retryable +- **pn-data-vault:** per l'anonimizzazione/deanonimizzazione dei dati sensibili + +### Dipendenze esterne + +- **servizio OCR:** per la validazione dei documenti allegati alla spedizione + +## Interfacce del Servizio + +| Tipo | Dir | Risorsa | Protocollo | Metodo | Route / Destinazione | Descrizione | +|-------|-----|---------------------------------------------|-------------|---------|-------------------------------------------------------------------------|--------------------------------------------------------| +| API | IN | pn-paper-tracker | REST | POST | /paper-tracker-private/v1/init | Inizializzazione entità di tracking | +| API | IN | pn-paper-tracker | REST | POST | /paper-tracker-private/v1/trackings | Recupero entità di tracking da lista di trackingId | +| API | IN | pn-paper-tracker | REST | POST | /paper-tracker-private/v1/errors | Recupero errori di tracking | +| API | IN | pn-paper-tracker | REST | POST | /paper-tracker-private/v1/outputs | Recupero degli oggetti in output | +| API | IN | pn-paper-tracker | REST | GET | /paper-tracker-private/v1/attempts/{attemptId} | Recupero tracking tramite attemptId | +| API | IN | pn-paper-tracker | REST | GET | /paper-tracker-private/v1/notification-rework/sequence | Recupero sequence e finalStatus per rework notifica | +| API | IN | pn-paper-tracker | REST | PUT | /paper-tracker-private/v1/notification-rework/{trackingId}/init | Avvio processo di invalidazione timeline per rework | +| API | OUT | pn-paper-channel | REST | GET | /paper-channel-private/v1/b2b/pc-retry/{requestId} | Verifica se ci sono altri retry | +| API | OUT | pn-data-vault | REST | PUT | /datavault-private/v1/paper-addresses/{paperRequestId}/{paperAddressId} | Inserisci o modifica un indirizzo | +| API | OUT | pn-data-vault | REST | GET | /datavault-private/v1/paper-addresses/{paperRequestId}/{paperAddressId} | Recupera tutti gli indirizzi associati alla spedizione | +| EVENT | IN | pn-ocr_outputs | SQS | CONSUME | - | Risposta validazione OCR degli allegati spedizione | +| EVENT | OUT | send-receipt-validation-input | SQS | PRODUCE | - | Invio allegati di spedizione all'OCR per validazione | +| EVENT | IN | pn-external_channel_to_paper_channel | SQS | CONSUME | - | Ricezione eventi dal consolidatore | +| EVENT | IN | pn-external_channel_to_paper_tracker | SQS | CONSUME | - | Ricezione eventi smistati da pn-paper-tracker | +| EVENT | OUT | pn-external_channel_to_paper_channel_dryrun | SQS | PRODUCE | - | Produzione eventi smistati verso pn-paper-channel | +| EVENT | OUT | pn-CoreEventBus | EventBridge | PUBLISH | - | Pubblicazione eventi su EventBridge | + +* **OpenAPI**: [api-internal-v1.yaml](docs/openapi/api-internal-v1.yaml) +* **AsyncAPI**: [internal-datalake-v1.yaml](docs/asyncapi/internal-datalake-v1.yaml) + +## Allarmi e monitoraggio + +| Tipo | Nome | Descrizione | +|-----------|----------------------------------|----------------------------------------------------------------------------------------------------------------| +| ALARM | pn-paper-tracker-890Errors-Alarm | Allarme su errori di tipo 890 (Atti Giudiziari). Scatta se il numero di errori supera la soglia configurata. | +| ALARM | pn-paper-tracker-RISErrors-Alarm | Allarme su errori di tipo RIS (Raccomandata Internazionale Semplice). | +| ALARM | pn-paper-tracker-ARErrors-Alarm | Allarme su errori di tipo AR (Raccomandata con Ricevuta di Ritorno). | +| ALARM | pn-paper-tracker-RSErrors-Alarm | Allarme su errori di tipo RS (Raccomandata Semplice). | +| ALARM | pn-paper-tracker-RIRErrors-Alarm | Allarme su errori di tipo RIR (Raccomandata Internazionale con Ricevuta di Ritorno). | +| LOG | /aws/ecs/pn-paper-tracker | Log applicativi ECS del microservizio, consultabili su CloudWatch Logs. | + +**Note operative:** +- Le metriche monitorate includono il conteggio degli errori per categoria di prodotto postale (890, AR, RS, RIS, RIR). + +Per consultare lo stato operativo del servizio, accedere alla dashboard CloudWatch `pn-paper-tracker` e al log group `/aws/ecs/pn-paper-tracker`. + +## Configurazione + +Le principali configurazioni del microservizio sono gestite tramite variabili d'ambiente: + +| Nome | Sorgente | Valori | Descrizione | +|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|--------|------------------------------------------------------------------------------------------| +| [PN_PAPERTRACKER_ENABLEOCRVALIDATIONFOR](https://github.com/pagopa/pn-paper-tracker/blob/ca6886a5053248cfcfe4635734d3ebb50197d2d3/scripts/aws/cfn/application-dev.env#L4) | ENV | - | Abilita l'OCR per singolo prodotto | +| [PN_PAPERTRACKER_OCRFILTERTEMPORAL](https://github.com/pagopa/pn-paper-tracker/blob/ca6886a5053248cfcfe4635734d3ebb50197d2d3/scripts/aws/cfn/application-dev.env#L8) | ENV | - | Abilita l'OCR in modalità RUN per spedizioni in specifiche fasce orarie | +| [PN_PAPERTRACKER_OCRFILTERUNIFIEDDELIVERYDRIVER](https://github.com/pagopa/pn-paper-tracker/blob/ca6886a5053248cfcfe4635734d3ebb50197d2d3/scripts/aws/cfn/application-dev.env#L13) | ENV | - | Abilita l'OCR in modalità RUN per determinati recapitisti | +| [PN_PAPERTRACKER_ENABLEOCRVALIDATIONFORFILE](https://github.com/pagopa/pn-paper-tracker/blob/ca6886a5053248cfcfe4635734d3ebb50197d2d3/scripts/aws/cfn/application-dev.env#L15) | ENV | - | Estensione file abilitata per la validazione OCR | +| [PN_PAPERTRACKER_SAVEANDNOTSENDTODELIVERYPUSH](https://github.com/pagopa/pn-paper-tracker/blob/ca6886a5053248cfcfe4635734d3ebb50197d2d3/scripts/aws/cfn/application-dev.env#L20) | ENV | - | StatusCode che non invia a pn-delivery-push ma vengono salvati nel tracking | +| [PN_PAPERTRACKER_REQUIREDATTACHMENTSREFINEMENTSTOCK890](https://github.com/pagopa/pn-paper-tracker/blob/ca6886a5053248cfcfe4635734d3ebb50197d2d3/scripts/aws/cfn/application-dev.env#L42) | ENV | - | Allegati necessari al perfezionamento giacenza 890 | +| [PN_PAPERTRACKER_SENDOCRATTACHMENTSFINALVALIDATION](https://github.com/pagopa/pn-paper-tracker/blob/ca6886a5053248cfcfe4635734d3ebb50197d2d3/scripts/aws/cfn/application-dev.env#L60) | ENV | - | Allegati da inviare all'OCR per la validazione finale | +| [PN_PAPERTRACKER_PRODUCTSPROCESSINGMODES](https://github.com/pagopa/pn-paper-tracker/blob/ca6886a5053248cfcfe4635734d3ebb50197d2d3/scripts/aws/cfn/application-dev.env#L85) | ENV | - | Modalità di processamento per prodotto | +| [PN_PAPERTRACKER_SENDTOCONSOLIDATOREERRORCATEGORIES](https://github.com/pagopa/pn-paper-tracker/blob/22b6733be808fb7fa5d6cf4fb904b6cebc6cde75/scripts/aws/cfn/application-dev.env#L89) | ENV | - | Categorie di errore previste per l'inoltro al consolidatore | +| [PN_PAPERTRACKER_PAPERTRACKINGSTTLDURATION](https://github.com/pagopa/pn-paper-tracker/blob/ca6886a5053248cfcfe4635734d3ebb50197d2d3/scripts/aws/cfn/application-dev.env#L31) | ENV | 3650d | Durata di conservazione dei PaperTrackings | +| [PN_PAPERTRACKER_PAPERTRACKINGSERRORSTTLDURATION](https://github.com/pagopa/pn-paper-tracker/blob/ca6886a5053248cfcfe4635734d3ebb50197d2d3/scripts/aws/cfn/application-dev.env#L34) | ENV | 3650d | Durata di conservazione dei PaperTrackingErrors | + +Per l'elenco completo e i dettagli di tutte le variabili, è possibile consultare il file [application-dev.env](scripts/aws/cfn/application-dev.env). + +## Esecuzione +**Prerequisiti** +* Docker/Podman avviato + +Eseguire il comando Maven: +```bash +mvn spring-boot:run +``` diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/config/application.properties b/jdk_21_maven/cs/rest/pn-paper-tracker/config/application.properties new file mode 100644 index 000000000..fb6e63bbb --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/config/application.properties @@ -0,0 +1,84 @@ +logging.config=config/logback-local.xml + +spring.application.name=pn-paper-tracker + +# Runtime mode +pn.env.runtime=DEVELOPMENT +#pn.env.runtime=PROD + +pn.paper-tracker.dao.paper-tracker-dry-run-outputs-table=pn-PaperTrackerDryRunOutputs +pn.paper-tracker.dao.paper-trackings-errors-table=pn-PaperTrackingsErrors +pn.paper-tracker.dao.paper-trackings-table=pn-PaperTrackings +server.port=8081 + +# AWS Configuration +aws.profile-name=default +aws.region-code=us-east-1 +aws.endpoint-url=http://localhost:4566 +spring.cloud.aws.credentials.profile-name=${PN_AWS_PROFILE_NAME:default} +cloud.aws.sqs.endpoint=http://localhost:4566 +cloud.aws.sqs.region=us-east-1 + +pn.paper-tracker.topics.external-channel-to-paper-tracker-queue=pn-external_channel_to_paper_tracker +pn.paper-tracker.topics.pn-ocr-outputs-queue=pn-paper_tracker_ocr_outputs + +pn.paper-tracker.topics.queue-ocr-inputs-url=http://localhost:4566/000000000000/dl-sqs +pn.paper-tracker.topics.queue-ocr-inputs-region=us-east-1 + +pn.paper-tracker.topics.external-channel-outputs-queue=pn-external_channel_outputs +pn.paper-tracker.topics.external-channel-to-paper-channel-queue=pn-external_channel_to_paper_channel +pn.paper-tracker.topics.external-channel-to-paper-channel_dryrun-queue=pn-external_channel_to_paper_channel_dryrun + +pn.paper-tracker.paper-trackings-ttl-duration=3650d +pn.paper-tracker.paper-trackings-errors-ttl-duration=3650d + +pn.paper-tracker.enable-ocr-validation-for= +pn.paper-tracker.enable-ocr-validation-for-file= +pn.paper-tracker.save-and-not-send-to-delivery-push=CON018 +pn.paper-tracker.paper-channel-base-url=http://localhost:8080 + +pn.paper-tracker.compiuta-giacenza-ar-duration=30d +pn.paper-tracker.enable-truncated-date-for-refinement-check=true +pn.paper-tracker.refinement-duration=10d + +pn.paper-tracker.required-attachments-refinement-stock890=1970-01-01;23L +pn.paper-tracker.send-ocr-attachments-refinement-stock890=1970-01-01;23L +pn.paper-tracker.send-ocr-attachments-final-validation-stock890=1970-01-01; +pn.paper-tracker.send-ocr-attachments-final-validation=1970-01-01;Plico;AR;23L +pn.paper-tracker.strict-final-validation-stock890=1970-01-01;false +pn.paper-tracker.strict-delivery-failure-cause=1970-01-01;false +pn.paper-tracker.products-processing-modes=1970-01-01;AR:RUN;890:DRY;RIR:DRY;RS:DRY;RIS:DRY +pn.paper-tracker.ocr-filter-temporal=* * 9-18 * * MON +pn.paper-tracker.ocr-filter-unified-delivery-driver=POSTE + +pn.paper-tracker.disable-all-consumers=true + +pn.paper-tracker.eventbus.name = paper-tracker-event-bus-name +pn.paper-tracker.eventbus.detailType = PaperChannelOutcomeEvent +pn.paper-tracker.eventbus.source = pn-paper-tracker + +# application-test.properties + +# Spring Cloud AWS 3.x Configuration +spring.cloud.aws.credentials.access-key=test +spring.cloud.aws.credentials.secret-key=test +spring.cloud.aws.region.static=us-east-1 +spring.cloud.aws.sqs.endpoint=http://localhost:4566 +spring.cloud.aws.sqs.region=us-east-1 +spring.cloud.aws.endpoint=http://localhost:4566 + +# SQS Listener Configuration +spring.cloud.aws.sqs.listener.acknowledgement-mode=manual +spring.cloud.aws.sqs.listener.poll-timeout=10s +spring.cloud.aws.sqs.listener.max-messages-per-poll=10 +spring.cloud.aws.sqs.listener.queue-stop-timeout=30s +spring.cloud.aws.sqs.listener.back-off-time=1s +spring.cloud.aws.sqs.listener.max-concurrent-messages=10 + +# Disable SSL for LocalStack +aws.disable-ssl=true + +# Debug Logging +logging.level.io.awspring.cloud=DEBUG +logging.level.software.amazon.awssdk=DEBUG +logging.level.software.amazon.awssdk.request=DEBUG \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/config/logback-local.xml b/jdk_21_maven/cs/rest/pn-paper-tracker/config/logback-local.xml new file mode 100644 index 000000000..7315d41ca --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/config/logback-local.xml @@ -0,0 +1,12 @@ + + + + + + %d [%X{trace_id}] %highlight(%-5level) - %-4relative - [%thread] - %cyan(%logger{20}) - %msg %n + + + + + + \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/docs/asyncapi/external-datalake-v1.yaml b/jdk_21_maven/cs/rest/pn-paper-tracker/docs/asyncapi/external-datalake-v1.yaml new file mode 100644 index 000000000..b4585231b --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/docs/asyncapi/external-datalake-v1.yaml @@ -0,0 +1,144 @@ +asyncapi: '3.0.0' +info: + title: OCR Data Exchange + version: '1.0.0' + description: An event schema for data request to OCR/AI Service + +servers: + dl_sqs: + host: 'sqs.us-east-1.amazonaws.com' + protocol: 'sqs' + description: DataLake SQS + +channels: + dl_sqs: + address: arn:aws:sqs:us-east-1:123456789012:ocr + description: SQS queue + servers: + - $ref: '#/servers/dl_sqs' + messages: + ocr_event: + $ref: '#/components/messages/ocr_event' + +operations: + OCR_Command: + action: send + channel: + $ref: '#/channels/dl_sqs' + summary: >- + Send OCR command to the queue + messages: + - $ref: '#/channels/dl_sqs/messages/ocr_event' + + +components: + messages: + ocr_event: + name: ocr_event + title: OCR event + summary: >- + Send a command to the OCR service + contentType: application/json + payload: + $ref: '#/components/schemas/ocrDataPayload' + + schemas: + ocrDataPayload: + type: object + properties: + version: + type: string + description: Version of the schema + example: "1.0.0" + commandId: + type: string + description: Command unique ID + example: "1234567890" + commandType: + type: string + description: OCR command (TBD) + enum: + - postal + example: "postal" + data: + type: object + properties: + productType: + type: string + description: Product type + enum: + - AR + - RIR + - 890 + - RS + - RIS + example: "AR" + documentType: + type: string + description: Document type + enum: + - AR + - Plico + - 23L + - ARCAD + example: "AR" + unifiedDeliveryDriver: + type: string + description: Delivery driver + enum: + - Poste + - Sailpost + - PostAndService + - Fulmine + example: "Poste" + details: + type: object + properties: + deliveryDetailCode: + type: string + example: "RECRN001C" + notificationDate: + type: string + format: date-time + example: "2024-09-20T17:00:00Z" + registeredLetterCode: + type: string + example: "R14700511649" + deliveryFailureCause: + type: string + enum: + - M01 + - M02 + - M03 + - M04 + - M05 + - M06 + - M07 + - M08 + - M09 + - M10 + example: "M02" + attachment: + type: string + format: uri + example: "safestorage://PN_EXTERNAL_LEGAL_FACTS-14f3dbdc57574459812a96a0a0bc204d.pdf" + deliveryAttemptDate: + type: string + format: date-time + example: "2024-09-21T10:30:00Z" + allOf: + - if: + properties: + deliveryDetailCode: + enum: + - RECRN002C + - RECRN002F + - RECAG003C + - RECAG003F + then: + required: + - deliveryFailureCause + else: + not: + required: + - deliveryFailureCause \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/docs/asyncapi/internal-datalake-v1.yaml b/jdk_21_maven/cs/rest/pn-paper-tracker/docs/asyncapi/internal-datalake-v1.yaml new file mode 100644 index 000000000..bdf33b9aa --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/docs/asyncapi/internal-datalake-v1.yaml @@ -0,0 +1,88 @@ +asyncapi: '3.0.0' +info: + title: OCR Data Exchange + version: '1.0.0' + description: An event schema for data request to OCR/AI Service + +servers: + pn_sqs: + host: 'sqs.us-east-1.amazonaws.com' + protocol: 'sqs' + description: PN SQS + +channels: + pn_sqs: + address: arn:aws:sqs:us-east-1:123456789012:ocr + description: SQS queue + servers: + - $ref: '#/servers/pn_sqs' + messages: + ocr_result: + $ref: '#/components/messages/ocr_result' + +operations: + OCR_Result: + action: receive + channel: + $ref: '#/channels/pn_sqs' + summary: >- + Send OCR result from the queue + messages: + - $ref: '#/channels/pn_sqs/messages/ocr_result' + +components: + messages: + ocr_result: + name: ocr_result + title: OCR result + summary: >- + Retrieve information about OCR data result + contentType: application/json + payload: + $ref: '#/components/schemas/ocrDataResultPayload' + + schemas: + ocrDataResultPayload: + type: object + properties: + version: + type: string + description: Version of the schema + example: "1.0.0" + commandId: + type: string + description: Command unique ID + example: "1234567890" + commandType: + type: string + description: OCR command + enum: + - postal + example: "postal" + data: + type: object + properties: + validationType: + type: string + description: Validation type + enum: + - operator + - ai + example: "ai" + validationStatus: + type: string + description: Validation status + enum: + - OK + - KO + - PENDING + example: "OK" + description: + type: string + description: Description of the validation status + example: "The document has been validated" + predictedRefinementType: + type: string + enum: + - PRE10 + - POST10 \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/docs/ms/ArchitetturaInterna.md b/jdk_21_maven/cs/rest/pn-paper-tracker/docs/ms/ArchitetturaInterna.md new file mode 100644 index 000000000..a49c11898 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/docs/ms/ArchitetturaInterna.md @@ -0,0 +1,148 @@ +# Logica di business + +## Inizializzazione della spedizione tramite API initTracking +Il flusso operativo di pn-paper-tracker ha inizio con la chiamata all’API `initTracking`, esposta dal controller +[`PaperTrackerTrackingController`](../../src/main/java/it/pagopa/pn/papertracker/rest/PaperTrackerTrackingController.java) e +invocata dal microservizio pn-paper-channel. + +Questa API viene utilizzata per inizializzare una nuova spedizione, salvando un oggetto `PaperTrackings` su DynamoDB. +I dati inizializzati comprendono: +- **trackingId**: identificativo univoco della spedizione cartacea +- **productType**: tipologia di prodotto (AR, RIR, 890, RS, RIS) +- **unifiedDeliveryDriver**: recapitista +- **analogRequestClientId**: identificativo del client che ha effettuato la richiesta di spedizione +- **altri metadati**: configurazioni ed eventuali dati aggiuntivi utili al tracciamento e alla gestione della spedizione + +Mentre l'`analogRequestClientId` viene ricevuto tramite header, gli altri dati vengono ricevuti tramite il payload della richiesta (`TrackingCreationRequest`) e +salvati tramite il servizio [`PaperTrackerTrackingService`](../../src/main/java/it/pagopa/pn/papertracker/service/PaperTrackerTrackingService.java). +L’inizializzazione della spedizione è un prerequisito per la corretta gestione degli eventi successivi relativi alla stessa spedizione. + + +La classe [`PnEventInboundService`](../../src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/PnEventInboundService.java) +gestisce la ricezione dei messaggi da tre code distinte: `pn-external_channel_to_paper_channel`, `pn-external_channel_to_paper_tracker` e `pn-ocr_outputs`. +Ogni coda segue un flusso separato, descritto di seguito. + +## Consumer pn-external_channel_to_paper_channel +I messaggi ricevuti vengono inoltrati, in definitiva, alla classe +[`SourceQueueProxyService`](../../src/main/java/it/pagopa/pn/papertracker/service/impl/SourceQueueProxyServiceImpl.java), +che si occupa di gestire il routing degli eventi provenienti da pn-ec in base alla modalità operativa associata alla spedizione +(attributo `processingMode` della classe [`PaperTrackings`](../../src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackings.java)). + +La logica applicata è la seguente: +- **Spedizione NON presente su pn-PaperTrackings**: l'evento viene inoltrato solo a pn-paper-channel, unicamente se non è un duplicato (`isDuplicate == false` oppure `isDuplicate == null`). +- **Spedizione presente e modalità DRY**: l'evento viene inoltrato a pn-paper-tracker e a pn-paper-channel; l'inoltro a pn-paper-channel avviene solo se l'evento non è un duplicato (`isDuplicate == false` oppure `isDuplicate == null`). +- **Spedizione presente e modalità RUN**: l'evento viene inoltrato solo a pn-paper-tracker. +- **Spedizione con processingMode null**: l'evento viene gestito come in modalità DRY (inoltro a pn-paper-tracker e, solo se non duplicato, a pn-paper-channel). + +Il messaggio viene arricchito con header e flag dry-run, e inoltrato ai consumer appropriati tramite i producer dedicati. + +## Consumer pn-external_channel_to_paper_tracker +I messaggi ricevuti vengono inoltrati alla classe +[`ExternalChannelHandler`](../../src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/ExternalChannelHandler.java). +Qui, in base al `productType` e all' `eventType` (ricavato tramite lo `statusCode` e l’enum +[`EventStatusCodeEnum`](../../src/main/java/it/pagopa/pn/papertracker/model/EventStatusCodeEnum.java)), il messaggio +viene smistato, tramite l’[`HandlersRegistry`](../../src/main/java/it/pagopa/pn/papertracker/service/handler_step/generic/HandlersRegistry.java), +verso il relativo handler, definito nella classe astratta +[`AbstractHandlersFactory`](../../src/main/java/it/pagopa/pn/papertracker/service/handler_step/generic/AbstractHandlersFactory.java) +o sue estensioni (es. +[`HandlersFactory890`](../src/main/java/it/pagopa/pn/papertracker/service/handler_step/_890/HandlersFactory890.java)). + +Ogni handler esegue una sequenza di step (classi che implementano l’interfaccia +[`HandlerStep`](../src/main/java/it/pagopa/pn/papertracker/service/handler_step/HandlerStep.java)), +utilizzando le informazioni condivise tramite la classe +[`HandlerContext`](../src/main/java/it/pagopa/pn/papertracker/model/HandlerContext.java). + +### Esempio di sequence e gestione degli eventi +Esempio di configurazione di una sequence: + +```json +{ + "sequenceName": "OK_AR", + "sequence": "@sequence.5s-CON080.5s-CON020[DOC:7ZIP;PAGES:3].5s-CON018.5s-RECRN001A.5s-RECRN001B[DOC:AR].5s-RECRN001C" +} +``` + +In questo esempio del prodotto `AR`, i primi tre `statusCode` (`CON080`, `CON020`, `CON018`), +essendo definiti nell’enum come `INTERMEDIATE_EVENT`, vengono processati dal rispettivo handler `buildIntermediateEventsHandler`. +Gli eventi `RECRN001A` e `RECRN001B`, essendo anch’essi eventi intermedi, vengono gestiti dallo stesso handler. +Diversamente, l’evento `RECRN001C`, essendo di tipo `EventTypeEnum.FINAL_EVENT`, viene processato dall’handler `buildFinalEventsHandler`. + +```mermaid +flowchart LR + + %% Eventi → Handler + A[CON080] -->|INTERMEDIATE_EVENT| INT + B[CON020] -->|INTERMEDIATE_EVENT| INT + C[CON018] -->|INTERMEDIATE_EVENT| INT + D[RECRN001A] -->|INTERMEDIATE_EVENT| INT + E[RECRN001B] -->|INTERMEDIATE_EVENT| INT + + F[RECRN001C] -->|FINAL_EVENT| FIN + + %% Intermediate Handler + subgraph INT[buildIntermediateEventsHandler] + H1[metadataUpserter] --> + H2[checkTrackingState] --> + H3[duplicatedEventFiltering] --> + H4[intermediateEventsBuilder] --> + H5[outputTargetSender] + end + + %% Final Handler + subgraph FIN[buildFinalEventsHandler] + I1[metadataUpserter] --> + I2[checkTrackingState] --> + I3[sequenceValidator] --> + I4[dematValidator] --> + I5[finalEventBuilder] --> + I6[outputTargetSender] + end +``` + +### Giacenza 890 +Per quanto riguarda la giacenza 890, si tiene conto di due flussi paralleli: il perfezionamento della spedizione e la chiusura fascicolo. +Come mostrato nel diagramma sotto, il perfezionamento si conclude con l'invio del feedback `RECAG012`, qualora la condizione +di refinement sia soddisfatta (ricezione del documento 23L e del `RECAG012`), +mentre, la chiusura fascicolo, termina con l'invio del progress `RECAG005C`, una volta validato l'ARCAD. + +```mermaid +sequenceDiagram +participant pn-ec +participant pn-paper-tracker +participant OCR +participant pn-delivery-push + + pn-ec->>pn-paper-tracker: RECAG011B[23L, ARCAD] + pn-ec->>pn-paper-tracker: RECAG005A + pn-ec->>pn-paper-tracker: RECAG012 + pn-ec->>pn-paper-tracker: RECAG005C + + pn-paper-tracker->>pn-delivery-push: PROGRESS - RECAG011B + pn-paper-tracker->>pn-delivery-push: PROGRESS - RECAG005A + pn-paper-tracker->>OCR: 23L + OCR->>pn-paper-tracker: 23L - OCR_OK + pn-paper-tracker->>pn-delivery-push: FEEDBACK - RECAG012 + pn-paper-tracker->>OCR: ARCAD + OCR->>pn-paper-tracker: ARCAD - OCR_OK + pn-paper-tracker->>pn-delivery-push: PROGRESS - RECAG005C +``` + +## Consumer pn-ocr_outputs +I messaggi ricevuti vengono gestiti dalla classe +[`OcrEventHandler`](../../src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/OcrEventHandler.java), +che recupera l’oggetto `PaperTrackings` da DynamoDB tramite il `commandId` presente nel payload. +Successivamente, come per la coda precedente, il messaggio viene smistato al relativo handler in base al `productType` +e all’`EventTypeEnum.OCR_RESPONSE_EVENT`. + +```mermaid +flowchart LR + + F[EVENTXXX] -->|OCR_RESPONSE_EVENT| OCR + + %% Ocr response handler + subgraph OCR[buildOcrResponseHandler] + I1[checkOcrResponse] --> + I2[finalEventBuilder] --> + I3[outputTargetSender] + end +``` diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/docs/openapi/api-internal-v1.yaml b/jdk_21_maven/cs/rest/pn-paper-tracker/docs/openapi/api-internal-v1.yaml new file mode 100644 index 000000000..a9b0b4140 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/docs/openapi/api-internal-v1.yaml @@ -0,0 +1,709 @@ +openapi: 3.0.1 +info: + title: Pn-paper-tracker + termsOfService: https://termofservice.it + x-api-id: api-paper-tracker-private + x-summary: OpenAPI servizio Paper tracker + description: |- + ## Abstract + Questo servizio ha il compito di: + - Ricevere gli aggiornamenti di spedizione provenienti dal sistema pn-external-channels (consolidatore); + - Validare gli eventi ricevuti; + - Validare i documenti allegati tramite un servizio OCR/AI; + - Tracciare ogni singolo tentativo di spedizione verso un destinatario; + - Inoltrare gli eventi validati al workflow analogico. + contact: + email: pn@pagopa.it + license: + name: PN software license + url: https://www.pn.pagopa.it/LICENSE + version: '1.0.0' +tags: + - name: HealthCheck + description: >- + Invocazioni per sapere lo stato del microservizio + - name: PaperTrackerTracking + - name: PaperTrackerAttempt + - name: PaperTrackerError + - name: PaperTrackerOutput + - name: NotificationRework +paths: + /status: + get: + summary: healthCheck path + description: health check path per verificare lo stato del micro servizio + tags: + - HealthCheck + operationId: status + responses: + '200': + description: Ok + '500': + description: Internal Server Error + '/paper-tracker-private/v1/init': + post: + summary: Inserimento tracker + description: >- + Permette l'inizializzazione di un'entità di tracking. + tags: + - PaperTrackerTracking + operationId: initTracking + parameters: + - name: X-Origin-Client-Id + in: header + description: Identificativo del client che ha fatto la richiesta di spedizione analogica + # TODO: da cambiare in true nella prossima release + required: false + schema: + type: string + minLength: 1 + maxLength: 64 + default: pn-delivery-push + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TrackingCreationRequest' + required: true + responses: + '201': + description: Created - Tracking created + '400': + description: Bad request + content: + application/problem+json: + schema: + $ref: 'remote-refs.yaml#/components/schemas/Problem' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: 'remote-refs.yaml#/components/schemas/Problem' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: 'remote-refs.yaml#/components/schemas/Problem' + '/paper-tracker-private/v1/trackings': + post: + summary: Recupero eventi + description: >- + Permette il recupero delle entità di tracking da una lista di trackingId. + tags: + - PaperTrackerTracking + operationId: retrieveTrackings + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TrackingsRequest' + required: true + responses: + '200': + description: returns related Tracking entity for each trackingId + content: + application/json: + schema: + $ref: '#/components/schemas/TrackingsResponse' + '404': + description: Bad request + content: + application/problem+json: + schema: + $ref: 'remote-refs.yaml#/components/schemas/Problem' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: 'remote-refs.yaml#/components/schemas/Problem' + '/paper-tracker-private/v1/errors': + post: + summary: Recupero errori + description: >- + Permette il recupero degli errori presenti nella tabella PnPaperTrackingsErrors. + tags: + - PaperTrackerError + operationId: retrieveTrackingErrors + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TrackingsRequest' + required: true + responses: + '200': + description: returns TrackingErrorsResponse entity for each trackingId + content: + application/json: + schema: + $ref: '#/components/schemas/TrackingErrorsResponse' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: 'remote-refs.yaml#/components/schemas/Problem' + '/paper-tracker-private/v1/outputs': + post: + summary: Recupero output prodotto + description: >- + Permette il recupero degli oggetti in output nella tabella PnPaperTrackerDryRunOutputs. + tags: + - PaperTrackerOutput + operationId: retrieveTrackingOutputs + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TrackingsRequest' + required: true + responses: + '200': + description: returns PnPaperTrackerDryRunOutputs entities for each trackingId + content: + application/json: + schema: + $ref: '#/components/schemas/PaperTrackerOutputsResponse' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: 'remote-refs.yaml#/components/schemas/Problem' + '/paper-tracker-private/v1/attempts/{attemptId}': + get: + summary: Oggetto tracking dato l'attemptId + description: >- + Recupera il tracking della spedizione dato l'attemptId. + tags: + - PaperTrackerTracking + operationId: retrieveTrackingsByAttemptId + parameters: + - $ref: '#/components/parameters/attemptId' + - name: pcRetry + in: query + required: false + description: Identificativo della richiesta + schema: + type: string + maxLength: 102 + responses: + '200': + description: returns Tracking entities using the attemptId + content: + application/json: + schema: + $ref: '#/components/schemas/TrackingsResponse' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: 'remote-refs.yaml#/components/schemas/Problem' + '/paper-tracker-private/v1/notification-rework/sequence': + get: + summary: Recupera le sequence e il finalStatus di una richiesta di rework + description: >- + Permette la verifica della possibilità di rework di una notifica + tags: + - NotificationRework + operationId: retrieveSequenceAndFinalStatus + parameters: + - $ref: '#/components/parameters/queryStatusCode' + - $ref: '#/components/parameters/deliveryFailureCause' + - $ref: '#/components/parameters/queryProductType' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/SequenceResponse" + '400': + description: Bad request - Invalid status code + content: + application/problem+json: + schema: + $ref: 'remote-refs.yaml#/components/schemas/Problem' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: 'remote-refs.yaml#/components/schemas/Problem' + '/paper-tracker-private/v1/notification-rework/{trackingId}/init': + put: + summary: Avvio processo di invalidazione timeline + description: Aggiorna lo stato dell'entità di tracking per accettare gli eventi di recapito correttivi + tags: + - NotificationRework + operationId: initNotificationRework + parameters: + - name: reworkId + in: query + required: true + schema: + type: string + - $ref: '#/components/parameters/trackingId' + responses: + '204': + description: Aggiornamento effettuato + '400': + description: Bad request + content: + application/problem+json: + schema: + $ref: 'remote-refs.yaml#/components/schemas/Problem' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: 'remote-refs.yaml#/components/schemas/Problem' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: 'remote-refs.yaml#/components/schemas/Problem' +components: + parameters: + attemptId: + description: >- + Timeline id dell'elemento PREPARE_ANALOG_DOMICILE + name: attemptId + in: path + required: true + schema: + type: string + # ASCII printable characters + pattern: ^[ -~]*$ + maxLength: 1024 + example: "PREPARE_ANALOG_DOMICILE.IUN_LKXM-QMZH-KELD-202507-H-1.RECINDEX_0.ATTEMPT_0" + trackingId: + name: trackingId + in: path + required: true + schema: + type: string + queryStatusCode: + in: query + name: statusCode + required: true + schema: + type: string + queryProductType: + in: query + name: productType + required: true + schema: + type: string + deliveryFailureCause: + in: query + name: deliveryFailureCause + required: false + schema: + type: string + schemas: + TrackingsRequest: + type: object + properties: + trackingIds: + type: array + items: + type: string + required: + - trackingIds + TrackingsResponse: + type: object + required: + - trackings + properties: + trackings: + type: array + items: + $ref: '#/components/schemas/Tracking' + Tracking: + type: object + required: + - trackingId + - events + - productType + - unifiedDeliveryDriver + - state + - businessState + properties: + trackingId: + type: string + attemptId: + type: string + pcRetry: + type: string + events: + type: array + items: + $ref: '#/components/schemas/PaperEvent' + validationFlow: + $ref: '#/components/schemas/ValidationFlow' + validationConfig: + $ref: '#/components/schemas/ValidationConfig' + productType: + $ref: '#/components/schemas/ProductType' + paperStatus: + $ref: '#/components/schemas/PaperStatus' + nextRequestIdPcretry: + type: string + unifiedDeliveryDriver: + type: string + notificationReworkId: + type: string + notificationReworkTimestamp: + type: string + format: date-time + state: + type: string + enum: + - AWAITING_REFINEMENT + - AWAITING_REWORK_EVENTS + - AWAITING_OCR + - DONE + - KO + businessState: + type: string + enum: + - AWAITING_FINAL_STATUS_CODE + - AWAITING_OCR + - AWAITING_REFINEMENT_OCR + - AWAITING_REWORK_EVENTS + - DONE + - KO + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + processingMode: + type: string + enum: + - DRY + - RUN + PaperEvent: + type: object + properties: + id: + type: string + requestTimestamp: + type: string + format: date-time + statusCode: + type: string + statusTimestamp: + type: string + format: date-time + statusDescription: + type: string + iun: + type: string + productType: + $ref: '#/components/schemas/ProductType' + deliveryFailureCause: + type: string + anonymizedDiscoveredAddressId: + type: string + attachments: + type: array + items: + $ref: '#/components/schemas/Attachment' + registeredLetterCode: + type: string + notificationReworkId: + type: string + createdAt: + type: string + format: date-time + dryRun: + type: boolean + printer: + type: string + du: + type: string + + ProductType: + type: string + Attachment: + type: object + properties: + id: + type: string + documentType: + type: string + uri: + type: string + date: + type: string + format: date-time + sha256: + type: string + sourceType: + type: string + originType: + type: string + ValidationFlow: + type: object + properties: + sequencesValidationTimestamp: + type: string + format: date-time + ocrRequests: + type: array + items: + $ref: '#/components/schemas/OcrRequest' + finalEventDematValidationTimestamp: + type: string + format: date-time + refinementDematValidationTimestamp: + type: string + format: date-time + finalEventBuilderTimestamp: + type: string + format: date-time + recag012StatusTimestamp: + type: string + format: date-time + OcrRequest: + type: object + properties: + documentType: + type: string + finalEventId: + type: string + attachmentEventId: + type: string + requestTimestamp: + type: string + format: date-time + responseTimestamp: + type: string + format: date-time + uri: + type: string + responseStatus: + type: string + ValidationConfig: + type: object + properties: + requiredAttachmentsRefinementStock890: + type: array + items: + type: string + sendOcrAttachmentsRefinementStock890: + type: array + items: + type: string + sendOcrAttachmentsFinalValidationStock890: + type: array + items: + type: string + sendOcrAttachmentsFinalValidation: + type: array + items: + type: string + strictFinalValidationStock890: + type: boolean + strictDeliveryFailureCause: + type: boolean + ocrEnabled: + type: string + enum: + - RUN + - DRY + - DISABLED + ocrFilterTemporal: + type: string + ocrFilterUnifiedDeliveryDriver: + type: array + items: + type: string + ocrFileTypes: + type: array + items: + type: string + PaperStatus: + type: object + properties: + registeredLetterCode: + type: string + description: Codice spedizione cartacea + deliveryFailureCause: + type: string + description: Causa del fallimento della consegna + anonymizedDiscoveredAddress: + type: string + description: Indirizzo scoperto durante la spedizione + finalStatusCode: + type: string + description: Codice di stato finale della spedizione + example: "RECRN005C" + validatedSequenceTimestamp: + type: string + format: date-time + description: Timestamp validato delle triplette (data di rendicontazione documento) + validatedEvents: + type: array + description: Lista di eventi validati + items: + type: string + finalDematFound: + type: boolean + description: Presenza evento demat finale + paperDeliveryTimestamp: + type: string + format: date-time + description: Timestamp di affido al consolidatore recuperato dallo statusCode P000 + TrackingErrorsResponse: + type: object + properties: + results: + type: array + items: + type: object + required: + - trackingId + - errors + properties: + trackingId: + type: string + errors: + type: array + items: + $ref: '#/components/schemas/TrackingError' + TrackingError: + type: object + properties: + trackingId: + type: string + created: + type: string + format: date-time + errorCategory: + type: string + details: + type: object + properties: + cause: + type: string + message: + type: string + additionalDetails: + type: object + additionalProperties: true + flowThrow: + type: string + enum: + - INIT_TRACKING + - NOT_RETRYABLE_EVENT_HANDLER + - DUPLICATED_EVENT_VALIDATION + - CHECK_TRACKING_PRODUCT + - SEQUENCE_VALIDATION + - DEMAT_VALIDATION + - FINAL_EVENT_BUILDING + - CHECK_TRACKING_STATE + - RETRY_PHASE + eventThrow: + type: string + eventIdThrow: + type: string + productType: + $ref: '#/components/schemas/ProductType' + type: + type: string + enum: + - ERROR + - WARNING + PaperTrackerOutputsResponse: + type: object + properties: + results: + type: array + items: + type: object + properties: + trackingId: + type: string + outputs: + type: array + items: + $ref: '#/components/schemas/PaperTrackerOutput' + PaperTrackerOutput: + type: object + properties: + created: + type: string + registeredLetterCode: + type: string + statusCode: + type: string + statusDetail: + type: string + statusDescription: + type: string + statusDateTime: + type: string + deliveryFailureCause: + type: string + attachments: + type: array + items: + $ref: '#/components/schemas/Attachment' + anonymizedDiscoveredAddressId: + type: string + clientRequestTimestamp: + type: string + TrackingCreationRequest: + type: object + properties: + productType: + type: string + description: Tipo prodotto + example: "AR" + unifiedDeliveryDriver: + type: string + description: unifiedDeliveryDriver pn-paper-tenders + attemptId: + type: string + description: Identificativo tentativo di spedizione + example: "PREPARE_ANALOG_DOMICILE.IUN_LKHJ-HKGV-HQUP-202402-Q-1.RECINDEX_0.ATTEMPT_0" + pcRetry: + type: string + description: Identificativo retry di spedizione + example: "PCRETRY_1" + required: + - productType + - unifiedDeliveryDriver + - attemptId + - pcRetry + SequenceResponse: + type: object + properties: + finalStatusCode: + type: string + enum: + - OK + - KO + sequence: + type: array + items: + $ref: '#/components/schemas/SequenceItem' + SequenceItem: + type: object + properties: + statusCode: + type: string + attachments: + type: array + items: + type: string \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/docs/openapi/external-channel/pn-external-channel.yaml b/jdk_21_maven/cs/rest/pn-paper-tracker/docs/openapi/external-channel/pn-external-channel.yaml new file mode 100644 index 000000000..01e9b825e --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/docs/openapi/external-channel/pn-external-channel.yaml @@ -0,0 +1,468 @@ +openapi: 3.0.3 +info: + title: ExternalChannel (AKA CarrierPigeon) + description: >- + ## Abstract + Questo servizio permette di inviare messaggi in modo asyncrono e ricevere aggiornamenti + sul loro stato di avanzamento. I canali gestiti sono i seguenti: + + - __Messaggi digitali a valore legale__: PEC. + - __Messaggi digitali "di cortesia"__: eMail, SMS + - __Messaggi cartacei__, nello specifico: + - Raccomandata Internazionale, + - Raccomandata Semplice, + - Raccomandata con ricevuta di ritorno, + - Raccomandata a norma di legge 890/1982. + license: + name: PN software license + url: 'https://www.pn.pagopa.it/LICENSE' + version: v1.0 +servers: + - url: >- + https://api.externalchannel.pagopa.local + description: Server url +tags: + - name: DigitalLegalMessages + description: Operazioni per inviare e monitorare messaggi digitali a valore legale + - name: DigitalCourtesyMessages + description: Operazioni per inviare e monitorare messaggi digitali senza valore legale + - name: PaperMessages + description: Operazioni per inviare e monitorare messaggi di corrispondenza cartacea + - name: Configurations + description: Operazioni per le configurazioni dei clients +paths: + + ############################################################################################# + ### DIGITAL LEGAL MESSAGES ### + ############################################################################################# + /external-channels/v1/digital-deliveries/legal-full-message-requests/{requestIdx}: + parameters: + - $ref: '#/components/parameters/requestIdx' + - $ref: '#/components/parameters/xPagopaExtchCxId' # NO EXTERNAL + put: + tags: + - DigitalLegalMessages + operationId: sendDigitalLegalMessage + summary: Invio messaggio digitale legale + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalNotificationRequest' + required: true + responses: + '204': + $ref: '#/components/responses/putDeliveryOk' + '400': + $ref: '#/components/responses/putDeliveryBad' + '409': + $ref: '#/components/responses/putDeliveryDuplicated' + callbacks: + deliveryStatusPushUpdates: + 'indirizzo_webhook_oppure_coda_sqs_legato_al_valore_header_xPagopaExtchCxId': + post: + summary: Eventi avanzamento spediti ad SQS + requestBody: + content: + application/json: + schema: + type: array + minItems: 1 + maxItems: 1000 + items: + $ref: '#/components/schemas/SingleStatusUpdate' + required: true + responses: + default: + description: Error processing the update, the update must be re-sended + '200': + description: Update correctly received + '400': + description: Malformed update status + get: + tags: + - DigitalLegalMessages + operationId: getDigitalLegalMessageStatus + summary: Pull avanzamenti messaggi digitali legali + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/LegalMessageSentDetails' + '404': + $ref: '#/components/responses/getDeliveryStatusHistoryNotFound' + + + ############################################################################################# + ### DIGITAL COURTESY MESSAGES ### + ############################################################################################# + + /external-channels/v1/digital-deliveries/courtesy-full-message-requests/{requestIdx}: + parameters: + - $ref: '#/components/parameters/requestIdx' + - $ref: '#/components/parameters/xPagopaExtchCxId' # NO EXTERNAL + put: + tags: + - DigitalCourtesyMessages + operationId: sendDigitalCourtesyMessage + summary: Invio messaggio digitale di cortesia + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalCourtesyMailRequest' + required: true + responses: + '204': + $ref: '#/components/responses/putDeliveryOk' + '400': + $ref: '#/components/responses/putDeliveryBad' + '409': + $ref: '#/components/responses/putDeliveryDuplicated' + callbacks: + deliveryStatusPushUpdates: + 'indirizzo_webhook_oppure_coda_sqs_legato_al_valore_header_xPagopaExtchCxId': + post: + summary: Eventi avanzamento spediti ad SQS + requestBody: + content: + application/json: + schema: + type: array + minItems: 1 + maxItems: 1000 + items: + $ref: '#/components/schemas/SingleStatusUpdate' + required: true + responses: + default: + description: Error processing the update, the update must be re-sended + '200': + description: Update correctly received + '400': + description: Malformed update status + get: + tags: + - DigitalCourtesyMessages + operationId: getDigitalCourtesyMessageStatus + summary: Pull avanzamenti messaggi digitali di cortesia + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/CourtesyMessageProgressEvent' + '404': + $ref: '#/components/responses/getDeliveryStatusHistoryNotFound' + + ############################################################################################# + ### DIGITAL COURTESY SHORT MESSAGES ### + ############################################################################################# + + /external-channels/v1/digital-deliveries/courtesy-simple-message-requests/{requestIdx}: + parameters: + - $ref: '#/components/parameters/requestIdx' + - $ref: '#/components/parameters/xPagopaExtchCxId' # NO EXTERNAL + put: + tags: + - DigitalCourtesyMessages + operationId: sendCourtesyShortMessage + summary: Invio messaggio digitale di cortesia breve + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalCourtesySmsRequest' + required: true + responses: + '204': + $ref: '#/components/responses/putDeliveryOk' + '400': + $ref: '#/components/responses/putDeliveryBad' + '409': + $ref: '#/components/responses/putDeliveryDuplicated' + callbacks: + deliveryStatusPushUpdates: + 'indirizzo_webhook_oppure_coda_sqs_legato_al_valore_header_xPagopaExtchCxId': + post: + summary: Eventi avanzamento spediti ad SQS + requestBody: + content: + application/json: + schema: + type: array + minItems: 1 + maxItems: 1000 + items: + $ref: '#/components/schemas/SingleStatusUpdate' + required: true + responses: + default: + description: Error processing the update, the update must be re-sended + '200': + description: Update correctly received + '400': + description: Malformed update status + get: + tags: + - DigitalCourtesyMessages + operationId: getCourtesyShortMessageStatus + summary: Pull avanzamenti messaggi digitali di cortesia brevi + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/CourtesyMessageProgressEvent' + '404': + $ref: '#/components/responses/getDeliveryStatusHistoryNotFound' + + + + ############################################################################################# + ### PAPER MAIL MESSAGES ### + ############################################################################################# + /external-channels/v1/paper-deliveries-engagements/{requestIdx}: + parameters: + - $ref: '#/components/parameters/requestIdx' + - $ref: '#/components/parameters/xPagopaExtchCxId' # NO EXTERNAL + put: + operationId: sendPaperEngageRequest + tags: + - PaperMessages + summary: Invio di corrispondenza cartacea + description: >- + Questa operazione sottomette ad external-channel una richiesta di invio di corrispondenza + cartacea. + External channel deve validare la sintatticamente la richiesta e, se tale validazione ha + successo, registrare la richiesta in maniera sincrona. Se vengono inviate molteplici + richieste con lo stesso requestId solo una deve avere successo (status code HTTP 204, le + altre devono ricevere lo status code 409). + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PaperEngageRequest' + required: true + responses: + '204': + $ref: '#/components/responses/putDeliveryOk' + '400': + $ref: '#/components/responses/putDeliveryBad' + '409': + $ref: '#/components/responses/putDeliveryDuplicated' + callbacks: + deliveryStatusPushUpdates: + 'indirizzo_webhook_oppure_coda_sqs_legato_al_valore_header_xPagopaExtchCxId': + post: + summary: Eventi avanzamento spediti ad SQS + requestBody: + content: + application/json: + schema: + type: array + minItems: 1 + maxItems: 1000 + items: + $ref: '#/components/schemas/SingleStatusUpdate' + required: true + responses: + default: + description: Error processing the update, the update must be re-sended + '200': + description: Update correctly received + '400': + description: Malformed update status + + get: + operationId: getPaperEngageProgresses + tags: + - PaperMessages + summary: Pull avanzamenti messaggi cartacei + description: >- + Questa richiesta permette di ottenere l'elenco degli eventi salienti + riguardanti un processo di postalizzazione. + Oltre all'elenco degli eventi si ottengono informazioni sulle cause di tali eventi + e URL per effettuare il download delle scansioni digitali dei documenti prodotti + durante il processo di postalizzazione. + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PaperProgressStatusEvent' + '404': + $ref: '#/components/responses/getDeliveryStatusHistoryNotFound' + + ############################################################################################# + ### CONFIGURAZIONI CLIENTS ### + ############################################################################################# + /external-channels/v1/configurations: + get: + tags: + - Configurations + operationId: getConfigurations + summary: Configurazioni dei client + description: >- + __Per ora in sola lettura__ + + Gestione delle configurazioni di un client, per ogni client si definiscono i + seguenti campi: + - __cxId__: identificativo del client, + - __sqsArn__: arn della coda SQS su cui il client riceverà gli esiti, + - __sqsName__: nome della coda SQS su cui ricevere, + - __pecReplyTo__ (facoltativo): indirizzo di risposta delle PEC, + - __mailReplyTo__ (facoltativo): indirizzo di risposta delle eMail, + - __senderPhysicalAddress__: campo complesso contenente l'indirizzo che risulterà + come mittente dei messaggi cartacei. + - _name_: ragione sociale del mittente + - _address_: indirizzo (casella postale) del mittente + - _cap_: cap + - _city_: comune + - _pr_: provincia + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + type: object + additionalProperties: true + example: + - cxId: pn-delivery-push + sqsArn: 'un ARN della coda' + sqsName: 'pn-delivery-push-extch-progress' + pecReplyTo: null + mailReplyTo: null + senderPhysicalAddress: + name: PagoPA SpA + address: Via non lo so 23 + cap: '0100' + city: Roma + pr: ROMA + - cxId: pn-user-attributes + sqsArn: 'un ARN della coda' + sqsName: 'pn-user-attributes-extch-progress' + pecReplyTo: null + mailReplyTo: null + senderPhysicalAddress: null + '400': + description: Bad Request + + +components: + + ############################################################################################# + ### PARAMETRI E RISPOSTE IN COMUNE PER TUTTI I TIPI DI MESSAGGIO ### + ############################################################################################# + parameters: + + xPagopaExtchCxId: + name: x-pagopa-extch-cx-id + in: header + required: true + schema: + type: string + + requestIdx: + name: requestIdx + in: path + required: true + schema: + type: string + # pattern: '[0-9A-Za-z_-]' + minLength: 5 + maxLength: 100 + + responses: + + putDeliveryOk: + description: OK + + putDeliveryBad: + description: Errore di validazione sintattica della richiesta + content: + application/json: + schema: + $ref: 'schemas-pn-errors-v1.yaml#/components/schemas/Problem' + + putDeliveryDuplicated: + description: >- + Indica che la richiesta è già stata effettuata e non può essere sovrascritta. + content: + application/json: + schema: + $ref: 'schemas-pn-errors-v1.yaml#/components/schemas/Problem' + + getDeliveryStatusHistoryNotFound: + description: requestIdNotFound + content: + application/json: + schema: + $ref: 'schemas-pn-errors-v1.yaml#/components/schemas/Problem' + + schemas: + + ### - DTO PER RICEZIONE ASINCRONA AVANZAMENTI MESSAGGI + ###################################################### + + SingleStatusUpdate: + type: object + properties: + digitalCourtesy: + $ref: '#/components/schemas/CourtesyMessageProgressEvent' + digitalLegal: + $ref: '#/components/schemas/LegalMessageSentDetails' + analogMail: + $ref: '#/components/schemas/PaperProgressStatusEvent' + clientId: + type: string + eventTimestamp: + type: string + format: date-time + description: >- + Data di produzione dell'evento. + + + ### - RIFERIMENTI AD ALTRI FILE + ###################################################### + DigitalNotificationRequest: + $ref: 'schemas-digital-v1.yaml#/components/schemas/DigitalNotificationRequest' + DigitalCourtesyMailRequest: + $ref: 'schemas-digital-v1.yaml#/components/schemas/DigitalCourtesyMailRequest' + DigitalCourtesySmsRequest: + $ref: 'schemas-digital-v1.yaml#/components/schemas/DigitalCourtesySmsRequest' + LegalMessageSentDetails: + $ref: 'schemas-digital-v1.yaml#/components/schemas/LegalMessageSentDetails' + CourtesyMessageProgressEvent: + $ref: 'schemas-digital-v1.yaml#/components/schemas/CourtesyMessageProgressEvent' + PaperEngageRequest: + $ref: 'schemas-paper-v1.yaml#/components/schemas/PaperEngageRequest' + PaperProgressStatusEvent: + $ref: 'schemas-paper-v1.yaml#/components/schemas/PaperProgressStatusEvent' + + + + # securitySchemes: # ONLY EXTERNAL + # ApiKeyAuth: # ONLY EXTERNAL + # type: apiKey # ONLY EXTERNAL + # in: header # ONLY EXTERNAL + # name: x-api-key # ONLY EXTERNAL + + #security: # ONLY EXTERNAL + # - ApiKeyAuth: [] # use the same name as under securitySchemes # ONLY EXTERNAL diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/docs/openapi/external-channel/schemas-digital-v1.yaml b/jdk_21_maven/cs/rest/pn-paper-tracker/docs/openapi/external-channel/schemas-digital-v1.yaml new file mode 100644 index 000000000..1eefac8fe --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/docs/openapi/external-channel/schemas-digital-v1.yaml @@ -0,0 +1,233 @@ +components: + schemas: + + ############################################################################################# + ### RICHIESTE DI INVIO DI MESSAGGISTICA DIGITALE ### + ############################################################################################# + BaseDigitalMessageRequest: + title: Schema base per i messaggi digitali + required: + - requestId + - eventType + - qos + - clientRequestTimeStamp + - channel + - receiverDigitalAddress + - messageText + type: object + properties: + requestId: + type: string + description: >- + Identificativo della richiesta. + correlationId: + type: string + description: >- + Id di correlazione, permette al sender di correlare eventualmente la response. Se assente viene inizializzato con il requestId + eventType: + type: string + description: >- + Tipologia di richiesta. Permette di identificare il tipo di informazione che viene richiesto. + Permette di clusterizzare le tipologie di comunicazione in ingresso + (se sender diversi usano EventType diversi, a fini ad es di fatturazione diventa semplice individuare gli oggetti). + qos: + type: string + enum: + - INTERACTIVE + - BATCH + description: >- + Interactive/Batch (quality of service, "entro quanto tempo" devo rispondere, con che livello di servizio) + # TODO: visto che da problemi con openapi generator 5.4.0, essendo opzionale, per ora lo commentiamo + # provare a decommentarlo al passaggio a openapi generator 6.0.0 + #tags: + # type: object + # properties: + # iun: + # type: string + # description: >- + # Identificativo Unico della Notifica. + # Non è obbligatorio (ad es in una richiesta di verifica indirizzi non abbiamo un identificativo di notifica). + # additionalProperties: true + clientRequestTimeStamp: + type: string + format: date-time + description: >- + Timestamp della richiesta in UTC + receiverDigitalAddress: + type: string + description: >- + Indirizzo PEC del destinatario (o in genere recapito digitale certificato a valore legale). + L’indirizzo si assume valido ed attivo: i servizi erogatori non eseguono ulteriori validazioni sullo stesso. + Non sono previste mail con più destinatari. + In caso di comunicazioni a più destinatari saranno richieste più comunicazioni (in questo modo avremo un requestId univoco per ogni comunicazione/evento/pec). + messageText: + type: string + description: >- + Testo del corpo della comunicazione digitale. + Può essere a discrezione del chiamante in plain text od html (un campo ad hoc che discrimini il formato è opportuno?). + senderDigitalAddress: + type: string + description: >- + Indirizzo PEC (o analoga comunicazione digitale a valore legale) del mittente della comunicazione. + Se non passato va recuperato da configurazione sulla base del sistema chiamante (i.e. X-PagoPA-EXTCH-CX), + va censito in configurazione (obbligatorio). + + ### - PEC, REM, SERCQ, .... + ########################################### + DigitalNotificationRequest: + title: schema per notifiche a valore legale, con allegati + allOf: + - $ref: '#/components/schemas/BaseDigitalMessageRequest' + - type: object + required: + - channel + - subjectText + - messageContentType + properties: + channel: + type: string + enum: + - PEC + - REM + description: >- + Canale di recapito con cui il Sender chiede l’erogazione del servizio. In un primo momento sarà PEC, + ma il dominio dovrà prevedere a breve SERQ, PEC_REM, ed in futuro ogni forma di recapito digitale a valore legale. + subjectText: + type: string + description: >- + Oggetto del corpo della comunicazione digitale. + messageContentType: + type: string + enum: + - 'text/plain' + - 'text/html' + description: >- + Formato: HTML o PLAIN + attachmentUrls: + type: array + items: + type: string + description: >- + Path in cui si trova il documento da allegare alla comunicazione digitale + (i.e. url del file dentro Safe Storage che si occuperà di crypt/decrypt dei documenti). + Il Safe Storge si occuperà di calcolare la presigned url per il recupero dell'allegato che sarà decifrato. + documentUrl può rispondere (alla GET) con un 403 per redirect vs servizio (i.e. lambda, safe storage, presigned url vs bucket s3 non cifrato). + + ### - EMAIL + ########################################### + DigitalCourtesyMailRequest: + title: schema per notifiche __senza__ valore legale, con allegati. + allOf: + - $ref: '#/components/schemas/DigitalNotificationRequest' + - type: object + required: + - channel + properties: + channel: + type: string + enum: + - EMAIL + description: >- + Canale di recapito con cui il Sender chiede l’erogazione del servizio. Per ora solo email + + ### - SMS + ########################################### + DigitalCourtesySmsRequest: + title: schema per notifiche brevi __senza__ valore legale. + allOf: + - $ref: '#/components/schemas/BaseDigitalMessageRequest' + - type: object + required: + - channel + properties: + channel: + type: string + enum: + - SMS + description: >- + Canale di recapito con cui il Sender chiede l’erogazione del servizio. In un primo momento sarà PEC, + ma il dominio dovrà prevedere a breve SERQ, PEC_REM, ed in futuro ogni forma di recapito digitale a valore legale. + + + ############################################################################################# + ### EVENTI DI PROGRESSIONE MESSAGGISTICA DIGITALE ### + ############################################################################################# + + ProgressEventCategory: + type: string + enum: + - PROGRESS + - OK + - ERROR + + DigitalMessageReference: + type: object + required: + - system + - id + properties: + system: + type: string + id: + type: string + location: + type: string + + BaseMessageProgressEvent: + title: Schema base per gli eventi di avanzamento messaggi digitali + type: object + required: + - requestId + - eventTimestamp + - status + - eventCode + properties: + requestId: + type: string + eventTimestamp: + type: string + format: date-time + status: + $ref: '#/components/schemas/ProgressEventCategory' + eventCode: + type: string + enum: + - C000 + - C001 + - C002 + - C003 + - C004 + - C005 + - C006 + - C007 + - C008 + - C009 + - C010 + description: >- + * STATUS EVENTCODE + * PROGRESS C000 = PREACCETTAZIONE (Conferma avvenuta comunicazione con Wrapper Pec) + * PROGRESS C001 = StatusPec.ACCETTAZIONE + * PROGRESS C005 = StatusPec.PRESA_IN_CARICO + * PROGRESS C007 = StatusPec.PREAVVISO_ERRORE_CONSEGNA + * ERROR C002 = StatusPec.NON_ACCETTAZIONE + * ERROR C004 = StatusPec.ERRORE_CONSEGNA + * ERROR C006 = StatusPec.RILEVAZIONE_VIRUS + * ERROR C008 = ERRORE_COMUNICAZIONE_SERVER_PEC + * ERROR C009 = ERRORE_DOMINIO_PEC_NON_VALIDO + * ERROR C010 = ERROR_INVIO_PEC + * OK C003 = StatusPec.AVVENUTA_CONSEGNA + eventDetails: + type: string + generatedMessage: + $ref: '#/components/schemas/DigitalMessageReference' + + + CourtesyMessageProgressEvent: + title: Avanzamento per messaggi di cortesia + allOf: + - $ref: '#/components/schemas/BaseMessageProgressEvent' + + LegalMessageSentDetails: + title: Avanzamento per messaggi legali + allOf: + - $ref: '#/components/schemas/BaseMessageProgressEvent' \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/docs/openapi/external-channel/schemas-paper-v1.yaml b/jdk_21_maven/cs/rest/pn-paper-tracker/docs/openapi/external-channel/schemas-paper-v1.yaml new file mode 100644 index 000000000..4f12080f8 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/docs/openapi/external-channel/schemas-paper-v1.yaml @@ -0,0 +1,423 @@ +components: + schemas: + + ########################################################################## + ### RICHIESTA DI POSTALIZZAZIONE ### + ########################################################################## + + ### - RICHIESTA + ################## + + PaperEngageRequest: + required: + - requestId + - requestSenderId + - requestPAId + - clientRequestTimeStamp + - eventType + - productType + - attachments + - printType + - receiverName + - receiverAddress + - receiverCity + - senderName + - senderAddress + - senderCity + - senderPr + type: object + properties: + iun: + type: string + example: 'ABCD-HILM-YKWX-202202-1' + description: >- + Identificativo Unico della Notifica. Può essere usato per migliorare il servizio + di archiviazione degli originali cartacei. Permettendo alle + Pubbliche Amministrazioni di ricercare anche per Identificativo Univoco Notifica. + requestId: + type: string + example: 'ABCD-HILM-YKWX-202202-1_rec0_try1' + description: >- + Identificativo della richiesta. + requestPaId: + type: string + example: '00414580183' + description: >- + Identificativo della PA che ha richiesto il recapito. Utile per autorizzare il + recupero, da parte delle pubbliche amministrazioni, degli originali cartacei + clientRequestTimeStamp: + type: string + format: date-time + description: >- + Timestamp della richiesta in UTC + productType: + description: >- + Tipo prodotto di cui viene chiesto il recapito: + - __AR__: Raccomandata Andata e Ritorno, + - __890__: Recapito a norma della legge 890/1982, + - __RI__: Raccomandata Internazionale, + - __RS__: Raccomandata Semplice (per Avviso di mancato Recapito). + - ... ulteriori prodotti + type: string + example: 'AR' + attachments: + type: array + items: + type: object + required: + - uri + - order + - documentType + - sha256 + properties: + uri: + type: string + description: URI indicante + example: safestorage://PN_EXTERNAL_LEGALFACT_.... + minLength: 5 + maxLength: 2000 + order: + type: number + description: Ordine di stampa, intero positivo, continuo, inizia da 0 + example: 0 + documentType: + type: string + minLength: 3 + maxLength: 4 + description: >- + Tipo di documento: + - __AAR__: Avviso di avvenuta ricezione, tipicamente associato a campo _order_ valorizzato a 0 + - __ATTO__: Documento inviato dalla pubblica amministrazione. + example: AAR + sha256: + type: string + description: >- + sha256, codificato in base 64, del contenuto del file riferenziato dal campo uri + minLength: 40 + maxLength: 50 + printType: + type: string + description: >- + Indica il tipo di stampa richiesto al Consolidatore + - __BN_FRONTE__: bianco e nero solo fronte + - __BN_FRONTE_RETRO__: bianco e nero fronte e retro + - ... ulteriori modalità previste dal contratto + example: 'BN_FRONTE_RETRO' + receiverName: + x-field-extra-annotation: "@lombok.ToString.Exclude" + type: string + description: >- + Cognome e nome o ragione sociale del destinatario + example: 'Mario Rossi' + receiverNameRow2: + x-field-extra-annotation: "@lombok.ToString.Exclude" + type: string + description: >- + Seconda riga sulla busta. + example: 'c/o famiglia Bianchi' + receiverAddress: + x-field-extra-annotation: "@lombok.ToString.Exclude" + type: string + description: >- + Indirizzo del destinatario. + example: 'via senza nome 610106' + receiverAddressRow2: + x-field-extra-annotation: "@lombok.ToString.Exclude" + type: string + description: >- + Specifica dell’indirizzo di residenza del destinatario (seconda riga indirizzo sulla busta). + example: 'scala Z interno 400' + receiverCap: + x-field-extra-annotation: "@lombok.ToString.Exclude" + type: string + description: >- + Cap del destinatario; in caso di invio estero diventa facoltativo. + example: '40050' + receiverCity: + x-field-extra-annotation: "@lombok.ToString.Exclude" + type: string + description: >- + Comune del destinatario. + example: Argelato + receiverCity2: + x-field-extra-annotation: "@lombok.ToString.Exclude" + type: string + description: >- + Frazione del destinatario. Potrebbe essere utile se il chiamante non fornisce il cap. + example: 'fraz. malacappa' + receiverPr: + x-field-extra-annotation: "@lombok.ToString.Exclude" + type: string + description: >- + Provincia del destinatario; in caso di invio estero diventa facoltativo. + example: 'BO' + receiverCountry: + x-field-extra-annotation: "@lombok.ToString.Exclude" + type: string + description: >- + In caso di destinatario estero, diventa obbligatoria l’indicazione della nazione di destinazione, + in standard UPU o altro standard condiviso. + receiverFiscalCode: + x-field-extra-annotation: "@lombok.ToString.Exclude" + type: string + description: >- + Potrebbe servire ai recapitisti in caso di offerte migliorative per i destinatari (servizi a valore aggiunto basati su cf). + senderName: + type: string + description: >- + Ragione sociale del mittente (PagoPA). + senderAddress: + type: string + description: >- + Indirizzo del mittente. + senderCity: + type: string + description: >- + Città del mittente + senderPr: + type: string + description: >- + Provincia del mittente. + senderDigitalAddress: + type: string + description: >- + Indirizzo PEC del mittente. + arName: + type: string + description: >- + Nominativo di riferimento per l’Avviso di Ritorno. + arAddress: + type: string + description: >- + Indirizzo per AR. + arCap: + type: string + description: >- + CAP AR. + arCity: + type: string + description: >- + Città per AR. + vas: + type: object + additionalProperties: + type: string + description: >- + Servizi a valore aggiunto + applyRasterization: + type: boolean + example: true + description: >- + Flag che indica se bisogna attivare la rasterizzazione per i documenti oppure no. + + ########################################################################## + ### NOTIFICHE DI AVANZAMENTO DEL PROCESSO DI POSTALIZZAZIONE ### + ########################################################################## + + ### - EVENTO SINGOLO + ##################### + + PaperProgressStatusEvent: + required: + - requestId + - statusCode + - statusDescription + - statusDateTime + - productType + - clientRequestTimeStamp + type: object + properties: + requestId: + type: string + description: >- + Identificativo della richiesta. + example: 'ABCD-HILM-YKWX-202202-1_rec0_try1' + registeredLetterCode: + type: string + description: >- + Il codice di tracciatura obbligatorio per i prodotti di corrispondenza tracciata. + example: '123456789abc' + productType: + description: >- + Tipo prodotto di cui viene chiesto il recapito: + - __AR__: Raccomandata Andata e Ritorno, + - __890__: Recapito a norma della legge 890/1982, + - __RI__: Raccomandata Internazionale, + - __RS__: Raccomandata Semplice (per Avviso di mancato Recapito). + - ... ulteriori prodotti concordati con il consolidatore + type: string + iun: + type: string + description: >- + Identificativo Unico della Notifica. Proviene dalla richiesta originaria di PN. + example: 'ABCD-HILM-YKWX-202202-1' + statusCode: + type: string + description: >- + _Codifica sintetica dello stato dell'esito._ + - __001__ Stampato + - __002__ Disponibile al recapitista + - __003__ Preso in carico dal recapitista + - __004__ Consegnata + - __005__ Mancata consegna + - __006__ Furto/Smarrimanto/deterioramento + - __007__ Consegnato Ufficio Postale + - __008__ Mancata consegna Ufficio Postale + - __009__ Compiuta giacenza + - ... Stati aggiuntivi concordati con il consolidatore + statusDescription: + type: string + example: 'Stampato' + description: >- + Descrizione dello stato del delivery cartaceo che viene notificato. + statusDateTime: + type: string + format: date-time + description: >- + Data stato con timezone + deliveryFailureCause: + description: >- + _Motivazione di mancata consegna_ obbligtorie negli stati di mancata consegna + - __01__ destinatario irreperibile + - __02__ destinatario deceduto + - __03__ destinatario sconosciuto + - __04__ destinatario trasferito + - __05__ invio rifiutato + - __06__ indirizzo inesatto + - __07__ indirizzo inesistente + - __08__ indirizzo insufficiente + - ... Motivazioni aggiuntive concordate con il consolidatore + type: string + attachments: + description: >- + elenco dei documenti prodotti che attestano quanto accaduto durante il processo + di postalizzazione + type: array + items: + $ref: '#/components/schemas/AttachmentDetails' + discoveredAddress: + $ref: '#/components/schemas/DiscoveredAddress' + clientRequestTimeStamp: + type: string + format: date-time + description: >- + Timestamp della richiesta in UTC + isDuplicate: + type: boolean + nullable: true + description: >- + Indica se l'evento risulta duplicato rispetto agli eventi già presenti sulla richiesta. + Valorizzato sempre da pn-ec per gli eventi analogici. + printer: + type: string + description: >- + Identificativo dello stampatore. + example: '123456789abc' + du: + type: string + description: >- + Distinta Unica, raggruppamento di spedizioni in un unico lotto. + example: '123456789abc' + + + AttachmentDetails: + title: dettagli allegato a un evento del processo postale + type: object + required: + - id + - documentType + - uri + - sha256 + - date + properties: + id: + type: string + description: >- + Identificativo di riga. + documentType: + type: string + description: >- + La descrizione della tipologia di oggetto. + uri: + type: string + description: >- + Le coordinate del documento correlato allo stato. + sha256: + type: string + description: >- + sha256, codificato in base 64, del contenuto del file riferenziato dal campo uri + date: + type: string + format: date-time + description: >- + Data di produzione del documento. + sourceType: + type: string + description: >- + Indica se il documento è stato prodotto tramite scansione cartacea (`SCANNED`) oppure generato digitalmente (`DIGITAL`). + - __SCANNED__ + - __DIGITAL__ + In caso di assenza del campo, la piattaforma considererà DIGITAL gli allegati con MIME/Type application/zip. + originType: + type: string + description: >- + Specifica l'origine del documento + - __ORIGINAL__ + - __DUPLICATED__ + - altri valori da specificare in fase di approfondimento tecnico + In caso di assenza del campo la piattaforma considererà come originale (`ORIGINAL`) il documento + + DiscoveredAddress: + description: >- + Indirizzo del destinatario desunto dalle indagini del personale postale. + required: + - name + - address + - city + properties: + name: + x-field-extra-annotation: "@lombok.ToString.Exclude" + type: string + description: >- + Cognome e nome o ragione sociale del destinatario + nameRow2: + x-field-extra-annotation: "@lombok.ToString.Exclude" + type: string + description: >- + Seconda riga sulla busta. + address: + x-field-extra-annotation: "@lombok.ToString.Exclude" + type: string + description: >- + Indirizzo del destinatario. + addressRow2: + x-field-extra-annotation: "@lombok.ToString.Exclude" + type: string + description: >- + Specifica dell’indirizzo di residenza del destinatario (seconda riga indirizzo sulla busta). + cap: + x-field-extra-annotation: "@lombok.ToString.Exclude" + type: string + description: >- + Cap del destinatario; in caso di invio estero diventa facoltativo. + city: + x-field-extra-annotation: "@lombok.ToString.Exclude" + type: string + description: >- + Comune del destinatario. + city2: + x-field-extra-annotation: "@lombok.ToString.Exclude" + type: string + description: >- + Frazione del destinatario. Potrebbe essere utile se il chiamante non fornisce il cap. + pr: + x-field-extra-annotation: "@lombok.ToString.Exclude" + type: string + description: >- + Provincia del destinatario; in caso di invio estero diventa facoltativo. + country: + x-field-extra-annotation: "@lombok.ToString.Exclude" + type: string + description: >- + In caso di destinatario estero, diventa obbligatoria l’indicazione della nazione di destinazione, + in standard UPU o altro standard condiviso. \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/docs/openapi/external-channel/schemas-pn-errors-v1.yaml b/jdk_21_maven/cs/rest/pn-paper-tracker/docs/openapi/external-channel/schemas-pn-errors-v1.yaml new file mode 100644 index 000000000..a0949cb07 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/docs/openapi/external-channel/schemas-pn-errors-v1.yaml @@ -0,0 +1,61 @@ + +components: + schemas: + Problem: + properties: + status: + description: The HTTP status code generated by the origin server for this occurrence of the problem. + type: integer + format: int32 + example: 503 + maximum: 600 + minimum: 100 + exclusiveMaximum: true + title: + description: A short, summary of the problem type. Written in english and readable + example: Service Unavailable + maxLength: 64 + pattern: ^[ -~]{0,64}$ + type: string + detail: + description: A human readable explanation of the problem. + example: Request took too long to complete. + maxLength: 4096 + pattern: ^.{0,1024}$ + type: string + traceId: + description: Internal support identifier associated to error + example: 123e4567-e89b-12d3-a456-426614174000 + type: string + errors: + type: array + minItems: 1 + items: + $ref: '#/components/schemas/ProblemError' + additionalProperties: false + required: + - type + - status + - title + - errors + ProblemError: + properties: + code: + description: Internal code of the error + example: 123-4567 + type: string + pattern: ^[0-9]{3}-[0-9]{4}$ + minLength: 8 + maxLength: 8 + element: + description: Parameter or request body field name for validation error + example: 'body.order.item[2].quantity' + type: string + detail: + description: A human readable explanation specific to this occurrence of the problem. + example: Parameter not valid + maxLength: 1024 + type: string + required: + - code + - detail \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/docs/openapi/remote-refs.yaml b/jdk_21_maven/cs/rest/pn-paper-tracker/docs/openapi/remote-refs.yaml new file mode 100644 index 000000000..b6049d9ee --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/docs/openapi/remote-refs.yaml @@ -0,0 +1,38 @@ +info: + version: v1.0 +components: + + parameters: + + ############################################################################################ + ### PARAMETRI DI AUTENTICAZIONE E AUTORIZZAZIONE ### + ############################################################################################ + cxTypeAuthFleet: + $ref: 'https://raw.githubusercontent.com/pagopa/pn-auth-fleet/a1f457ddc112983ec79be0be93256476dd11102c/docs/openapi/authenticationParameters-v1.yaml#/components/parameters/cxTypeAuthFleet' + cxIdAuthFleet: + $ref: 'https://raw.githubusercontent.com/pagopa/pn-auth-fleet/a1f457ddc112983ec79be0be93256476dd11102c/docs/openapi/authenticationParameters-v1.yaml#/components/parameters/cxIdAuthFleet' + cxGroupsAuthFleet: + $ref: 'https://raw.githubusercontent.com/pagopa/pn-auth-fleet/a1f457ddc112983ec79be0be93256476dd11102c/docs/openapi/authenticationParameters-v1.yaml#/components/parameters/cxGroups' + uidAuthFleet: + $ref: 'https://raw.githubusercontent.com/pagopa/pn-auth-fleet/a1f457ddc112983ec79be0be93256476dd11102c/docs/openapi/authenticationParameters-v1.yaml#/components/parameters/uidAuthFleet' + cxTaxIdAuthFleet: + $ref: 'https://raw.githubusercontent.com/pagopa/pn-auth-fleet/a1f457ddc112983ec79be0be93256476dd11102c/docs/openapi/authenticationParameters-v1.yaml#/components/parameters/cxTaxIdAuthFleet' + + schemas: + ############################################################################################ + ### PARAMETRI NOTIFICA ### + ############################################################################################ + IUN: + $ref: "https://raw.githubusercontent.com/pagopa/pn-delivery/1750230e82ea6e03516e9f4bfae09c1737c1d5e7/docs/openapi/schemas-pn-notification.yaml#/components/schemas/IUN" + NotificationFeePolicy: + $ref: "https://raw.githubusercontent.com/pagopa/pn-delivery/1750230e82ea6e03516e9f4bfae09c1737c1d5e7/docs/openapi/schemas-pn-notification.yaml#/components/schemas/NotificationFeePolicy" + PaTaxId: + $ref: "https://raw.githubusercontent.com/pagopa/pn-delivery/1750230e82ea6e03516e9f4bfae09c1737c1d5e7/docs/openapi/schemas-pn-notification.yaml#/components/schemas/paTaxId" + NoticeCode: + $ref: "https://raw.githubusercontent.com/pagopa/pn-delivery/1750230e82ea6e03516e9f4bfae09c1737c1d5e7/docs/openapi/schemas-pn-notification.yaml#/components/schemas/noticeCode" + ############################################################################################ + ### STRUTTURE DATI DI PN-ERROR ### + ############################################################################################ + Problem: + $ref: 'https://raw.githubusercontent.com/pagopa/pn-commons/61cdf0712f133c8c69e056abfad6ce5223242ba0/docs/openapi/pn-errors.yaml#/components/schemas/Problem' + \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/index.js b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/index.js new file mode 100644 index 000000000..ec2540a1b --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/index.js @@ -0,0 +1,8 @@ +const { handleEvent } = require("./src/app/eventHandler.js"); + +async function handler(event, context) { + console.info("New Execution scheduled ", event); + return handleEvent(event, context); +} + +exports.handler = handler; diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/package-lock.json b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/package-lock.json new file mode 100644 index 000000000..7f435789b --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/package-lock.json @@ -0,0 +1,5620 @@ +{ + "name": "notificationReworkEventsValidation", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "notificationReworkEventsValidation", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@aws-sdk/client-sqs": "3.995.0", + "@aws-sdk/lib-dynamodb": "3.995.0", + "@aws-sdk/node-http-handler": "3.370.0", + "@aws-sdk/util-dynamodb": "3.995.0", + "axios": "^1.6.8", + "config": "^3.3.11", + "pn-action-common": "file:../../functionsLib/actionCommon", + "uuid": "^9.0.1" + }, + "devDependencies": { + "aws-sdk-client-mock": "^2.2.0", + "axios-mock-adapter": "^1.22.0", + "chai": "^4.4.1", + "chai-as-promised": "^7.1.1", + "eslint": "^8.57.0", + "lambda-tester": "^4.0.1", + "mocha": "^10.3.0", + "mocha-lcov-reporter": "^1.3.0", + "nyc": "^15.1.0", + "proxyquire": "^2.1.3", + "sinon": "^17.0.1" + } + }, + "../../functionsLib/actionCommon": {}, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/abort-controller": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.370.0.tgz", + "integrity": "sha512-/W4arzC/+yVW/cvEXbuwvG0uly4yFSZnnIA+gkqgAm+0HVfacwcPpNf4BjyxjnvIdh03l7w2DriF6MlKUfiQ3A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.370.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/abort-controller/node_modules/@aws-sdk/types": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.370.0.tgz", + "integrity": "sha512-8PGMKklSkRKjunFhzM2y5Jm0H2TBu7YRNISdYzXLUHKSP9zlMEYagseKVdmox0zKHf1LXVNuSlUV2b6SRrieCQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^1.1.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/abort-controller/node_modules/@smithy/types": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.2.0.tgz", + "integrity": "sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-dynamodb": { + "version": "3.1005.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.1005.0.tgz", + "integrity": "sha512-lPkeRwkXhXEjyO3YENmxseFMfAQQAjecT9BjohoZqurvceLrqTml94Dt84OYtJpAvAVQua5SK7rOrjk2vEeU5A==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/credential-provider-node": "^3.972.19", + "@aws-sdk/dynamodb-codec": "^3.972.20", + "@aws-sdk/middleware-endpoint-discovery": "^3.972.7", + "@aws-sdk/middleware-host-header": "^3.972.7", + "@aws-sdk/middleware-logger": "^3.972.7", + "@aws-sdk/middleware-recursion-detection": "^3.972.7", + "@aws-sdk/middleware-user-agent": "^3.972.20", + "@aws-sdk/region-config-resolver": "^3.972.7", + "@aws-sdk/types": "^3.973.5", + "@aws-sdk/util-endpoints": "^3.996.4", + "@aws-sdk/util-user-agent-browser": "^3.972.7", + "@aws-sdk/util-user-agent-node": "^3.973.5", + "@smithy/config-resolver": "^4.4.10", + "@smithy/core": "^3.23.9", + "@smithy/fetch-http-handler": "^5.3.13", + "@smithy/hash-node": "^4.2.11", + "@smithy/invalid-dependency": "^4.2.11", + "@smithy/middleware-content-length": "^4.2.11", + "@smithy/middleware-endpoint": "^4.4.23", + "@smithy/middleware-retry": "^4.4.40", + "@smithy/middleware-serde": "^4.2.12", + "@smithy/middleware-stack": "^4.2.11", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/node-http-handler": "^4.4.14", + "@smithy/protocol-http": "^5.3.11", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.11", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.39", + "@smithy/util-defaults-mode-node": "^4.2.42", + "@smithy/util-endpoints": "^3.3.2", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-retry": "^4.2.11", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.2.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs": { + "version": "3.995.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sqs/-/client-sqs-3.995.0.tgz", + "integrity": "sha512-vGE9tY5m+eCarVFRHo4Me/49531+Tp3qpTV8Ka4/AOwv/zWkJNG+zQhBUHHibnxepZcTkpoPRMro4wk7fCA9og==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.11", + "@aws-sdk/credential-provider-node": "^3.972.10", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-sdk-sqs": "^3.972.8", + "@aws-sdk/middleware-user-agent": "^3.972.11", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.995.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.10", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.23.2", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/md5-js": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.16", + "@smithy/middleware-retry": "^4.4.33", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.10", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.5", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.32", + "@smithy/util-defaults-mode-node": "^4.2.35", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/util-endpoints": { + "version": "3.995.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.995.0.tgz", + "integrity": "sha512-aym/pjB8SLbo9w2nmkrDdAAVKVlf7CM71B9mKhjDbJTzwpSFBPHqJIMdDyj0mLumKC0aIVDr1H6U+59m9GvMFw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.973.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.19.tgz", + "integrity": "sha512-56KePyOcZnKTWCd89oJS1G6j3HZ9Kc+bh/8+EbvtaCCXdP6T7O7NzCiPuHRhFLWnzXIaXX3CxAz0nI5My9spHQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.5", + "@aws-sdk/xml-builder": "^3.972.10", + "@smithy/core": "^3.23.9", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/property-provider": "^4.2.11", + "@smithy/protocol-http": "^5.3.11", + "@smithy/signature-v4": "^5.3.11", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.17.tgz", + "integrity": "sha512-MBAMW6YELzE1SdkOniqr51mrjapQUv8JXSGxtwRjQV0mwVDutVsn22OPAUt4RcLRvdiHQmNBDEFP9iTeSVCOlA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/types": "^3.973.5", + "@smithy/property-provider": "^4.2.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.19.tgz", + "integrity": "sha512-9EJROO8LXll5a7eUFqu48k6BChrtokbmgeMWmsH7lBb6lVbtjslUYz/ShLi+SHkYzTomiGBhmzTW7y+H4BxsnA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/types": "^3.973.5", + "@smithy/fetch-http-handler": "^5.3.13", + "@smithy/node-http-handler": "^4.4.14", + "@smithy/property-provider": "^4.2.11", + "@smithy/protocol-http": "^5.3.11", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "@smithy/util-stream": "^4.5.17", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.18", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.18.tgz", + "integrity": "sha512-vthIAXJISZnj2576HeyLBj4WTeX+I7PwWeRkbOa0mVX39K13SCGxCgOFuKj2ytm9qTlLOmXe4cdEnroteFtJfw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/credential-provider-env": "^3.972.17", + "@aws-sdk/credential-provider-http": "^3.972.19", + "@aws-sdk/credential-provider-login": "^3.972.18", + "@aws-sdk/credential-provider-process": "^3.972.17", + "@aws-sdk/credential-provider-sso": "^3.972.18", + "@aws-sdk/credential-provider-web-identity": "^3.972.18", + "@aws-sdk/nested-clients": "^3.996.8", + "@aws-sdk/types": "^3.973.5", + "@smithy/credential-provider-imds": "^4.2.11", + "@smithy/property-provider": "^4.2.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.18", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.18.tgz", + "integrity": "sha512-kINzc5BBxdYBkPZ0/i1AMPMOk5b5QaFNbYMElVw5QTX13AKj6jcxnv/YNl9oW9mg+Y08ti19hh01HhyEAxsSJQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/nested-clients": "^3.996.8", + "@aws-sdk/types": "^3.973.5", + "@smithy/property-provider": "^4.2.11", + "@smithy/protocol-http": "^5.3.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.19.tgz", + "integrity": "sha512-yDWQ9dFTr+IMxwanFe7+tbN5++q8psZBjlUwOiCXn1EzANoBgtqBwcpYcHaMGtn0Wlfj4NuXdf2JaEx1lz5RaQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.17", + "@aws-sdk/credential-provider-http": "^3.972.19", + "@aws-sdk/credential-provider-ini": "^3.972.18", + "@aws-sdk/credential-provider-process": "^3.972.17", + "@aws-sdk/credential-provider-sso": "^3.972.18", + "@aws-sdk/credential-provider-web-identity": "^3.972.18", + "@aws-sdk/types": "^3.973.5", + "@smithy/credential-provider-imds": "^4.2.11", + "@smithy/property-provider": "^4.2.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.17.tgz", + "integrity": "sha512-c8G8wT1axpJDgaP3xzcy+q8Y1fTi9A2eIQJvyhQ9xuXrUZhlCfXbC0vM9bM1CUXiZppFQ1p7g0tuUMvil/gCPg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/types": "^3.973.5", + "@smithy/property-provider": "^4.2.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.18", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.18.tgz", + "integrity": "sha512-YHYEfj5S2aqInRt5ub8nDOX8vAxgMvd84wm2Y3WVNfFa/53vOv9T7WOAqXI25qjj3uEcV46xxfqdDQk04h5XQA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/nested-clients": "^3.996.8", + "@aws-sdk/token-providers": "3.1005.0", + "@aws-sdk/types": "^3.973.5", + "@smithy/property-provider": "^4.2.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.18", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.18.tgz", + "integrity": "sha512-OqlEQpJ+J3T5B96qtC1zLLwkBloechP+fezKbCH0sbd2cCc0Ra55XpxWpk/hRj69xAOYtHvoC4orx6eTa4zU7g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/nested-clients": "^3.996.8", + "@aws-sdk/types": "^3.973.5", + "@smithy/property-provider": "^4.2.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/dynamodb-codec": { + "version": "3.972.20", + "resolved": "https://registry.npmjs.org/@aws-sdk/dynamodb-codec/-/dynamodb-codec-3.972.20.tgz", + "integrity": "sha512-MQ2W0zeBMNaQYgHcQ7aul7g5783qFdP2AKcJnpaID0ekl2QbiKF+St1JMx5lgOXHlnERD9X3exr2B0SIg35oOA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-sdk/core": "^3.973.19", + "@smithy/core": "^3.23.9", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/endpoint-cache": { + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.972.4.tgz", + "integrity": "sha512-GdASDnWanLnHxKK0hqV97xz23QmfA/C8yGe0PiuEmWiHSe+x+x+mFEj4sXqx9IbfyPncWz8f4EhNwBSG9cgYCg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "mnemonist": "0.38.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/lib-dynamodb": { + "version": "3.995.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.995.0.tgz", + "integrity": "sha512-Z8MW7b8jrdP+0BMMy7ruft6/7aJFyOpS4nGMJ/0JfhppPyIpIfMead1BkJKkakB3T+p9UU9GXSQkGhkdlyASwQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.11", + "@aws-sdk/util-dynamodb": "3.995.0", + "@smithy/core": "^3.23.2", + "@smithy/smithy-client": "^4.11.5", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.995.0" + } + }, + "node_modules/@aws-sdk/middleware-endpoint-discovery": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.972.7.tgz", + "integrity": "sha512-ZeFfgAVOGR+fDq/JAPsVA3P07ba74hIppoGfmQyfzZMfAQAzc9Lbg5pndZU8EanzfKnlXbv6y09OMrSkTsUuOg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-sdk/endpoint-cache": "^3.972.4", + "@aws-sdk/types": "^3.973.5", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.7.tgz", + "integrity": "sha512-aHQZgztBFEpDU1BB00VWCIIm85JjGjQW1OG9+98BdmaOpguJvzmXBGbnAiYcciCd+IS4e9BEq664lhzGnWJHgQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.5", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.7.tgz", + "integrity": "sha512-LXhiWlWb26txCU1vcI9PneESSeRp/RYY/McuM4SpdrimQR5NgwaPb4VJCadVeuGWgh6QmqZ6rAKSoL1ob16W6w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.5", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.7.tgz", + "integrity": "sha512-l2VQdcBcYLzIzykCHtXlbpiVCZ94/xniLIkAj0jpnpjY4xlgZx7f56Ypn+uV1y3gG0tNVytJqo3K9bfMFee7SQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.5", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-sqs": { + "version": "3.972.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.972.14.tgz", + "integrity": "sha512-MmN/j0D3MLkR0cca8/V2GXjGAkcgp1tlrQZZduLb6G+UhfOJuzFW3rSrCeiXTBgiXSIIZ6sc/gsuACpg/5TL1Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.5", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.20", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.20.tgz", + "integrity": "sha512-3kNTLtpUdeahxtnJRnj/oIdLAUdzTfr9N40KtxNhtdrq+Q1RPMdCJINRXq37m4t5+r3H70wgC3opW46OzFcZYA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/types": "^3.973.5", + "@aws-sdk/util-endpoints": "^3.996.4", + "@smithy/core": "^3.23.9", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "@smithy/util-retry": "^4.2.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.996.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.8.tgz", + "integrity": "sha512-6HlLm8ciMW8VzfB80kfIx16PBA9lOa9Dl+dmCBi78JDhvGlx3I7Rorwi5PpVRkL31RprXnYna3yBf6UKkD/PqA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/middleware-host-header": "^3.972.7", + "@aws-sdk/middleware-logger": "^3.972.7", + "@aws-sdk/middleware-recursion-detection": "^3.972.7", + "@aws-sdk/middleware-user-agent": "^3.972.20", + "@aws-sdk/region-config-resolver": "^3.972.7", + "@aws-sdk/types": "^3.973.5", + "@aws-sdk/util-endpoints": "^3.996.4", + "@aws-sdk/util-user-agent-browser": "^3.972.7", + "@aws-sdk/util-user-agent-node": "^3.973.5", + "@smithy/config-resolver": "^4.4.10", + "@smithy/core": "^3.23.9", + "@smithy/fetch-http-handler": "^5.3.13", + "@smithy/hash-node": "^4.2.11", + "@smithy/invalid-dependency": "^4.2.11", + "@smithy/middleware-content-length": "^4.2.11", + "@smithy/middleware-endpoint": "^4.4.23", + "@smithy/middleware-retry": "^4.4.40", + "@smithy/middleware-serde": "^4.2.12", + "@smithy/middleware-stack": "^4.2.11", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/node-http-handler": "^4.4.14", + "@smithy/protocol-http": "^5.3.11", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.11", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.39", + "@smithy/util-defaults-mode-node": "^4.2.42", + "@smithy/util-endpoints": "^3.3.2", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-retry": "^4.2.11", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/node-http-handler": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.370.0.tgz", + "integrity": "sha512-v2mCtrbzbsK5YFopIeD7oIl+aEHtyFeNDq2ODkNlO0HPYBRbvFHFKFeUsyR991tfmffPonae4oeI9RI8eZQu2A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/abort-controller": "3.370.0", + "@aws-sdk/protocol-http": "3.370.0", + "@aws-sdk/querystring-builder": "3.370.0", + "@aws-sdk/types": "3.370.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/node-http-handler/node_modules/@aws-sdk/types": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.370.0.tgz", + "integrity": "sha512-8PGMKklSkRKjunFhzM2y5Jm0H2TBu7YRNISdYzXLUHKSP9zlMEYagseKVdmox0zKHf1LXVNuSlUV2b6SRrieCQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^1.1.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/node-http-handler/node_modules/@smithy/types": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.2.0.tgz", + "integrity": "sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/protocol-http": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.370.0.tgz", + "integrity": "sha512-MfZCgSsVmir+4kJps7xT0awOPNi+swBpcVp9ZtAP7POduUVV6zVLurMNLXsppKsErggssD5E9HUgQFs5w06U4Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.370.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/protocol-http/node_modules/@aws-sdk/types": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.370.0.tgz", + "integrity": "sha512-8PGMKklSkRKjunFhzM2y5Jm0H2TBu7YRNISdYzXLUHKSP9zlMEYagseKVdmox0zKHf1LXVNuSlUV2b6SRrieCQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^1.1.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/protocol-http/node_modules/@smithy/types": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.2.0.tgz", + "integrity": "sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/querystring-builder": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.370.0.tgz", + "integrity": "sha512-yrDWn3AtXArHWXh9NATcf+aaF6SPBxgroSIHYKKDA7B0UlSEpCOroz7anj0Lvewwo1D3hLlXcJlBSGVtWI0Xyg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.370.0", + "@aws-sdk/util-uri-escape": "3.310.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/querystring-builder/node_modules/@aws-sdk/types": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.370.0.tgz", + "integrity": "sha512-8PGMKklSkRKjunFhzM2y5Jm0H2TBu7YRNISdYzXLUHKSP9zlMEYagseKVdmox0zKHf1LXVNuSlUV2b6SRrieCQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^1.1.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/querystring-builder/node_modules/@smithy/types": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.2.0.tgz", + "integrity": "sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.7.tgz", + "integrity": "sha512-/Ev/6AI8bvt4HAAptzSjThGUMjcWaX3GX8oERkB0F0F9x2dLSBdgFDiyrRz3i0u0ZFZFQ1b28is4QhyqXTUsVA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.5", + "@smithy/config-resolver": "^4.4.10", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1005.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1005.0.tgz", + "integrity": "sha512-vMxd+ivKqSxU9bHx5vmAlFKDAkjGotFU56IOkDa5DaTu1WWwbcse0yFHEm9I537oVvodaiwMl3VBwgHfzQ2rvw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/nested-clients": "^3.996.8", + "@aws-sdk/types": "^3.973.5", + "@smithy/property-provider": "^4.2.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.973.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.5.tgz", + "integrity": "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-dynamodb": { + "version": "3.995.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.995.0.tgz", + "integrity": "sha512-ErHBnAaxlPAn/TCBY8+Alr/IF9E3tFeK6NCY1GdRUl6Lz2PFZnZeoh7GrLZZn0jvlX4OirXlhwAds6mqp+NKoA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.995.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.996.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.4.tgz", + "integrity": "sha512-Hek90FBmd4joCFj+Vc98KLJh73Zqj3s2W56gjAcTkrNLMDI5nIFkG9YpfcJiVI1YlE2Ne1uOQNe+IgQ/Vz2XRA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.5", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.11", + "@smithy/util-endpoints": "^3.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", + "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-uri-escape": { + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.310.0.tgz", + "integrity": "sha512-drzt+aB2qo2LgtDoiy/3sVG8w63cgLkqFIa2NFlGpUgHFWTXkqtbgf4L5QdjRGKWhmZsnqkbtL7vkSWEcYDJ4Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.7.tgz", + "integrity": "sha512-7SJVuvhKhMF/BkNS1n0QAJYgvEwYbK2QLKBrzDiwQGiTRU6Yf1f3nehTzm/l21xdAOtWSfp2uWSddPnP2ZtsVw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.5", + "@smithy/types": "^4.13.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.973.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.5.tgz", + "integrity": "sha512-Dyy38O4GeMk7UQ48RupfHif//gqnOPbq/zlvRssc11E2mClT+aUfc3VS2yD8oLtzqO3RsqQ9I3gOBB4/+HjPOw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.20", + "@aws-sdk/types": "^3.973.5", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.10.tgz", + "integrity": "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "fast-xml-parser": "5.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@extra-number/significant-digits": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@extra-number/significant-digits/-/significant-digits-1.3.9.tgz", + "integrity": "sha512-E5PY/bCwrNqEHh4QS6AQBinLZ+sxM1lT8tsSVYk8VwhWIPp6fCU/BMRVq0V8iJ8LwS3FHmaA4vUzb78s4BIIyA==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/commons/node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.3.1.tgz", + "integrity": "sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", + "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "type-detect": "^4.1.0" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", + "dev": true + }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.11.tgz", + "integrity": "sha512-Hj4WoYWMJnSpM6/kchsm4bUNTL9XiSyhvoMb2KIq4VJzyDt7JpGHUZHkVNPZVC7YE1tf8tPeVauxpFBKGW4/KQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.10.tgz", + "integrity": "sha512-IRTkd6ps0ru+lTWnfnsbXzW80A8Od8p3pYiZnW98K2Hb20rqfsX7VTlfUwhrcOeSSy68Gn9WBofwPuw3e5CCsg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.11", + "@smithy/types": "^4.13.0", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-endpoints": "^3.3.2", + "@smithy/util-middleware": "^4.2.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.23.9", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.9.tgz", + "integrity": "sha512-1Vcut4LEL9HZsdpI0vFiRYIsaoPwZLjAxnVQDUMQK8beMS+EYPLDQCXtbzfxmM5GzSgjfe2Q9M7WaXwIMQllyQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.12", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-stream": "^4.5.17", + "@smithy/util-utf8": "^4.2.2", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.11.tgz", + "integrity": "sha512-lBXrS6ku0kTj3xLmsJW0WwqWbGQ6ueooYyp/1L9lkyT0M02C+DWwYwc5aTyXFbRaK38ojALxNixg+LxKSHZc0g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.11", + "@smithy/property-provider": "^4.2.11", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.13", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.13.tgz", + "integrity": "sha512-U2Hcfl2s3XaYjikN9cT4mPu8ybDbImV3baXR0PkVlC0TTx808bRP3FaPGAzPtB8OByI+JqJ1kyS+7GEgae7+qQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.11", + "@smithy/querystring-builder": "^4.2.11", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.11.tgz", + "integrity": "sha512-T+p1pNynRkydpdL015ruIoyPSRw9e/SQOWmSAMmmprfswMrd5Ow5igOWNVlvyVFZlxXqGmyH3NQwfwy8r5Jx0A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.11.tgz", + "integrity": "sha512-cGNMrgykRmddrNhYy1yBdrp5GwIgEkniS7k9O1VLB38yxQtlvrxpZtUVvo6T4cKpeZsriukBuuxfJcdZQc/f/g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.11.tgz", + "integrity": "sha512-350X4kGIrty0Snx2OWv7rPM6p6vM7RzryvFs6B/56Cux3w3sChOb3bymo5oidXJlPcP9fIRxGUCk7GqpiSOtng==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.11.tgz", + "integrity": "sha512-UvIfKYAKhCzr4p6jFevPlKhQwyQwlJ6IeKLDhmV1PlYfcW3RL4ROjNEDtSik4NYMi9kDkH7eSwyTP3vNJ/u/Dw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.23", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.23.tgz", + "integrity": "sha512-UEFIejZy54T1EJn2aWJ45voB7RP2T+IRzUqocIdM6GFFa5ClZncakYJfcYnoXt3UsQrZZ9ZRauGm77l9UCbBLw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.9", + "@smithy/middleware-serde": "^4.2.12", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.11", + "@smithy/util-middleware": "^4.2.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.40", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.40.tgz", + "integrity": "sha512-YhEMakG1Ae57FajERdHNZ4ShOPIY7DsgV+ZoAxo/5BT0KIe+f6DDU2rtIymNNFIj22NJfeeI6LWIifrwM0f+rA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.11", + "@smithy/protocol-http": "^5.3.11", + "@smithy/service-error-classification": "^4.2.11", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-retry": "^4.2.11", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.12.tgz", + "integrity": "sha512-W9g1bOLui7Xn5FABRVS0o3rXL0gfN37d/8I/W7i0N7oxjx9QecUmXEMSUMADTODwdtka9cN43t5BI2CodLJpng==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.11.tgz", + "integrity": "sha512-s+eenEPW6RgliDk2IhjD2hWOxIx1NKrOHxEwNUaUXxYBxIyCcDfNULZ2Mu15E3kwcJWBedTET/kEASPV1A1Akg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.11.tgz", + "integrity": "sha512-xD17eE7kaLgBBGf5CZQ58hh2YmwK1Z0O8YhffwB/De2jsL0U3JklmhVYJ9Uf37OtUDLF2gsW40Xwwag9U869Gg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.4.14", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.14.tgz", + "integrity": "sha512-DamSqaU8nuk0xTJDrYnRzZndHwwRnyj/n/+RqGGCcBKB4qrQem0mSDiWdupaNWdwxzyMU91qxDmHOCazfhtO3A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.11", + "@smithy/protocol-http": "^5.3.11", + "@smithy/querystring-builder": "^4.2.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.11.tgz", + "integrity": "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.11.tgz", + "integrity": "sha512-hI+barOVDJBkNt4y0L2mu3Ugc0w7+BpJ2CZuLwXtSltGAAwCb3IvnalGlbDV/UCS6a9ZuT3+exd1WxNdLb5IlQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.11.tgz", + "integrity": "sha512-7spdikrYiljpket6u0up2Ck2mxhy7dZ0+TDd+S53Dg2DHd6wg+YNJrTCHiLdgZmEXZKI7LJZcwL3721ZRDFiqA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "@smithy/util-uri-escape": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.11.tgz", + "integrity": "sha512-nE3IRNjDltvGcoThD2abTozI1dkSy8aX+a2N1Rs55en5UsdyyIXgGEmevUL3okZFoJC77JgRGe99xYohhsjivQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.11.tgz", + "integrity": "sha512-HkMFJZJUhzU3HvND1+Yw/kYWXp4RPDLBWLcK1n+Vqw8xn4y2YiBhdww8IxhkQjP/QlZun5bwm3vcHc8AqIU3zw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.6.tgz", + "integrity": "sha512-IB/M5I8G0EeXZTHsAxpx51tMQ5R719F3aq+fjEB6VtNcCHDc0ajFDIGDZw+FW9GxtEkgTduiPpjveJdA/CX7sw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.11.tgz", + "integrity": "sha512-V1L6N9aKOBAN4wEHLyqjLBnAz13mtILU0SeDrjOaIZEeN6IFa6DxwRt1NNpOdmSpQUfkBj0qeD3m6P77uzMhgQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-uri-escape": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.3.tgz", + "integrity": "sha512-7k4UxjSpHmPN2AxVhvIazRSzFQjWnud3sOsXcFStzagww17j1cFQYqTSiQ8xuYK3vKLR1Ni8FzuT3VlKr3xCNw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.9", + "@smithy/middleware-endpoint": "^4.4.23", + "@smithy/middleware-stack": "^4.2.11", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "@smithy/util-stream": "^4.5.17", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.0.tgz", + "integrity": "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.11.tgz", + "integrity": "sha512-oTAGGHo8ZYc5VZsBREzuf5lf2pAurJQsccMusVZ85wDkX66ojEc/XauiGjzCj50A61ObFTPe6d7Pyt6UBYaing==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", + "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", + "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", + "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", + "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.39", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.39.tgz", + "integrity": "sha512-ui7/Ho/+VHqS7Km2wBw4/Ab4RktoiSshgcgpJzC4keFPs6tLJS4IQwbeahxQS3E/w98uq6E1mirCH/id9xIXeQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.11", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.42", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.42.tgz", + "integrity": "sha512-QDA84CWNe8Akpj15ofLO+1N3Rfg8qa2K5uX0y6HnOp4AnRYRgWrKx/xzbYNbVF9ZsyJUYOfcoaN3y93wA/QJ2A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.10", + "@smithy/credential-provider-imds": "^4.2.11", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/property-provider": "^4.2.11", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.2.tgz", + "integrity": "sha512-+4HFLpE5u29AbFlTdlKIT7jfOzZ8PDYZKTb3e+AgLz986OYwqTourQ5H+jg79/66DB69Un1+qKecLnkZdAsYcA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.11.tgz", + "integrity": "sha512-r3dtF9F+TpSZUxpOVVtPfk09Rlo4lT6ORBqEvX3IBT6SkQAdDSVKR5GcfmZbtl7WKhKnmb3wbDTQ6ibR2XHClw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.11.tgz", + "integrity": "sha512-XSZULmL5x6aCTTii59wJqKsY1l3eMIAomRAccW7Tzh9r8s7T/7rdo03oektuH5jeYRlJMPcNP92EuRDvk9aXbw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.17", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.17.tgz", + "integrity": "sha512-793BYZ4h2JAQkNHcEnyFxDTcZbm9bVybD0UV/LEWmZ5bkTms7JqjfrLMi2Qy0E5WFcCzLwCAPgcvcvxoeALbAQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.13", + "@smithy/node-http-handler": "^4.4.14", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.11.tgz", + "integrity": "sha512-x7Rh2azQPs3XxbvCzcttRErKKvLnbZfqRf/gOjw2pb+ZscX88e5UkRPCB67bVnsFHxayvMvmePfKTqsRb+is1A==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@smithy/abort-controller": "^4.2.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", + "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@types/sinon": { + "version": "10.0.20", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.20.tgz", + "integrity": "sha512-2APKKruFNCAZgx3daAyACGzWuJ028VVCUDk6o2rw/Z4PXT0ogwdV4KUegW0MwVs0Zu59auPXbbuBJHF12Sx1Eg==", + "dev": true, + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-15.0.1.tgz", + "integrity": "sha512-Ko2tjWJq8oozHzHV+reuvS5KYIRAokHnGbDwGh/J64LntgpbuylF74ipEL24HCyRjf9FOlBiBHWBR1RlVKsI1w==", + "dev": true + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/app-root-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", + "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", + "dev": true, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/aws-sdk-client-mock": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/aws-sdk-client-mock/-/aws-sdk-client-mock-2.2.0.tgz", + "integrity": "sha512-Kq2N+6gHRDedbrgTA0NMMfyN1XDWEA5Kbpm9/M/cenSxoNjfvQBOtBawI1lQe5h4UziLl///E7u17K9PBoHEKA==", + "dev": true, + "dependencies": { + "@types/sinon": "^10.0.10", + "sinon": "^14.0.2", + "tslib": "^2.1.0" + } + }, + "node_modules/aws-sdk-client-mock/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/aws-sdk-client-mock/node_modules/@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/aws-sdk-client-mock/node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/aws-sdk-client-mock/node_modules/@sinonjs/samsam": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-7.0.1.tgz", + "integrity": "sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/aws-sdk-client-mock/node_modules/sinon": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-14.0.2.tgz", + "integrity": "sha512-PDpV0ZI3ZCS3pEqx0vpNp6kzPhHrLx72wA0G+ZLaaJjLIYeE0n8INlgaohKuGy7hP0as5tbUd23QWu5U233t+w==", + "deprecated": "16.1.1", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "^9.1.2", + "@sinonjs/samsam": "^7.0.1", + "diff": "^5.0.0", + "nise": "^5.1.2", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/aws-sdk-client-mock/node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/axios": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.1.tgz", + "integrity": "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios-mock-adapter": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.22.0.tgz", + "integrity": "sha512-dmI0KbkyAhntUR05YY96qg2H6gg0XMl2+qTW0xmYg6Up+BFBAJYRLROMXRdDEL06/Wqwa0TJThAYvFtSFdRCZw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "is-buffer": "^2.0.5" + }, + "peerDependencies": { + "axios": ">= 0.17.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.21", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.21.tgz", + "integrity": "sha512-JU0h5APyQNsHOlAM7HnQnPToSDQoEBZqzu/YBlqDnEeymPnZDREeXJA3KBMQee+dKteAxZ2AtvQEvVYdZf241Q==", + "dev": true, + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001751", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", + "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chai-as-promised": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.2.tgz", + "integrity": "sha512-aBDHZxRzYnUYuIAIPBH2s511DjlKPzXNlXSGFC8CwmroWQLfrW0LtE1nK3MAwwNhJPa9raEjNCmRoFpG0Hurdw==", + "dev": true, + "dependencies": { + "check-error": "^1.0.2" + }, + "peerDependencies": { + "chai": ">= 2.1.2 < 6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/config": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/config/-/config-3.3.12.tgz", + "integrity": "sha512-Vmx389R/QVM3foxqBzXO8t2tUikYZP64Q6vQxGrsMpREeJc/aWRnPRERXWsYzOHAumx/AOoILWe6nU3ZJL+6Sw==", + "dependencies": { + "json5": "^2.2.3" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dotenv-json/-/dotenv-json-1.0.0.tgz", + "integrity": "sha512-jAssr+6r4nKhKRudQ0HOzMskOFFi9+ubXWwmrSGJFgTvpjyPXCXsCsYbjif6mXp7uxA7xY3/LGaiTQukZzSbOQ==", + "dev": true + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.243", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.243.tgz", + "integrity": "sha512-ZCphxFW3Q1TVhcgS9blfut1PX8lusVi2SvXQgmEEnK4TCmE1JhH2JkjJN+DNt0pJJwfBri5AROBnz2b/C+YU9g==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-xml-builder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz", + "integrity": "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/fast-xml-parser": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", + "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "fast-xml-builder": "^1.0.0", + "strnum": "^2.1.2" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", + "integrity": "sha512-tcgI872xXjwFF4xgQmLxi76GnwJG3g/3isB1l4/G5Z4zrbddGpBjqZCO9oEAcB5wX0Hj/5iQB3toxfO7in1hHA==", + "dev": true, + "dependencies": { + "is-object": "~1.0.1", + "merge-descriptors": "~1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-report/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lambda-event-mock": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lambda-event-mock/-/lambda-event-mock-1.5.0.tgz", + "integrity": "sha512-vx1d+vZqi7FF6B3+mAfHnY/6Tlp6BheL2ta0MJS0cIRB3Rc4I5cviHTkiJxHdE156gXx3ZjlQRJrS4puXvtrhA==", + "dev": true, + "dependencies": { + "@extra-number/significant-digits": "^1.1.1", + "clone-deep": "^4.0.1", + "uuid": "^3.3.3", + "vandium-utils": "^1.2.0" + }, + "engines": { + "node": ">=12.13" + } + }, + "node_modules/lambda-event-mock/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/lambda-event-mock/node_modules/vandium-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vandium-utils/-/vandium-utils-1.2.0.tgz", + "integrity": "sha512-yxYUDZz4BNo0CW/z5w4mvclitt5zolY7zjW97i6tTE+sU63cxYs1A6Bl9+jtIQa3+0hkeqY87k+7ptRvmeHe3g==", + "dev": true + }, + "node_modules/lambda-leak": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lambda-leak/-/lambda-leak-2.0.0.tgz", + "integrity": "sha512-2c9jwUN3ZLa2GEiOhObbx2BMGQplEUCDHSIkhDtYwUjsTfiV/3jCF6ThIuEXfsvqbUK+0QpZcugIKB8YMbSevQ==", + "dev": true, + "engines": { + "node": ">=6.10.0" + } + }, + "node_modules/lambda-tester": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lambda-tester/-/lambda-tester-4.0.1.tgz", + "integrity": "sha512-ft6XHk84B6/dYEzyI3anKoGWz08xQ5allEHiFYDUzaYTymgVK7tiBkCEbuWx+MFvH7OpFNsJXVtjXm0X8iH3Iw==", + "dev": true, + "dependencies": { + "app-root-path": "^3.0.0", + "dotenv": "^8.0.0", + "dotenv-json": "^1.0.0", + "lambda-event-mock": "^1.5.0", + "lambda-leak": "^2.0.0", + "semver": "^6.1.1", + "uuid": "^3.3.3", + "vandium-utils": "^2.0.0" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/lambda-tester/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mnemonist": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.3.tgz", + "integrity": "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==", + "license": "MIT", + "peer": true, + "dependencies": { + "obliterator": "^1.6.1" + } + }, + "node_modules/mocha": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha-lcov-reporter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mocha-lcov-reporter/-/mocha-lcov-reporter-1.3.0.tgz", + "integrity": "sha512-/5zI2tW4lq/ft8MGpYQ1nIH6yePPtIzdGeUEwFMKfMRdLfAQ1QW2c68eEJop32tNdN5srHa/E2TzB+erm3YMYA==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/module-not-found-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", + "integrity": "sha512-pEk4ECWQXV6z2zjhRZUongnLJNUeGQJ3w6OQ5ctGwD+i5o93qjRQUk2Rt6VdNeu3sEP0AB4LcfvdebpxBRVr4g==", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/nyc/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/nyc/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/obliterator": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz", + "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==", + "license": "MIT", + "peer": true + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pn-action-common": { + "resolved": "../../functionsLib/actionCommon", + "link": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process-on-spawn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz", + "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/proxyquire": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz", + "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", + "dev": true, + "dependencies": { + "fill-keys": "^1.0.2", + "module-not-found-error": "^1.0.1", + "resolve": "^1.11.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sinon": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz", + "integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vandium-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vandium-utils/-/vandium-utils-2.0.0.tgz", + "integrity": "sha512-XWbQ/0H03TpYDXk8sLScBEZpE7TbA0CHDL6/Xjt37IBYKLsHUQuBlL44ttAUs9zoBOLFxsW7HehXcuWCNyqOxQ==", + "dev": true, + "engines": { + "node": ">=10.16" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/package.json b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/package.json new file mode 100644 index 000000000..f2c8c6c0e --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/package.json @@ -0,0 +1,37 @@ +{ + "name": "notificationReworkEventsValidation", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test-build": "npm run-script test && npm run-script coverage && npm run-script build", + "build": "npm prune --production && rm -f ./function.zip && zip -r ./function.zip . -x './src/test/*' -x '*.md' -x '*.env' -x './coverage/*' -x './.nyc_output/*' -x './.scannerwork/*'", + "test": "nyc --reporter=html --reporter=text mocha './src/test/**/*.test.js' --recursive --timeout=3000 --exit -r dotenv/config", + "coverage": "nyc report --reporter=lcov" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "aws-sdk-client-mock": "^2.2.0", + "axios-mock-adapter": "^1.22.0", + "chai": "^4.4.1", + "chai-as-promised": "^7.1.1", + "eslint": "^8.57.0", + "lambda-tester": "^4.0.1", + "mocha": "^10.3.0", + "mocha-lcov-reporter": "^1.3.0", + "nyc": "^15.1.0", + "proxyquire": "^2.1.3", + "sinon": "^17.0.1" + }, + "dependencies": { + "@aws-sdk/client-sqs": "3.995.0", + "@aws-sdk/lib-dynamodb": "3.995.0", + "@aws-sdk/node-http-handler": "3.370.0", + "@aws-sdk/util-dynamodb": "3.995.0", + "axios": "^1.6.8", + "config": "^3.3.11", + "pn-action-common": "file:../../functionsLib/actionCommon", + "uuid": "^9.0.1" + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/app/dynamo.js b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/app/dynamo.js new file mode 100644 index 000000000..2c755569b --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/app/dynamo.js @@ -0,0 +1,102 @@ +const { DynamoDBClient } = require("@aws-sdk/client-dynamodb"); +const { + DynamoDBDocumentClient, + QueryCommand, + UpdateCommand, + PutCommand +} = require("@aws-sdk/lib-dynamodb"); + +const client = new DynamoDBClient({}); +const docClient = DynamoDBDocumentClient.from(client); + +const NOTIFICATION_REWORK_TABLE = process.env.NOTIFICATION_REWORK_TABLE; +const EVENTS_ERROR_TABLE = process.env.NOTIFICATION_REWORK_EVENTS_ERROR_TABLE; + +async function getLatestReworkRequestByIun(iun, requestId) { + const params = { + TableName: NOTIFICATION_REWORK_TABLE, + KeyConditionExpression: "iun = :iun", + ExpressionAttributeValues: { + ":iun": iun + }, + ScanIndexForward: false + }; + + const result = await docClient.send(new QueryCommand(params)); + const recIndex = getRecIndexFromRequestId(requestId); + if (!result.Items || result.Items.length === 0) { + return null; + } + + return result.Items.reduce((latest, item) => { + if (item.recIndex !== recIndex || !item.createdAt) { + return latest; + } + + if (!latest) { + return item; + } + + return Date.parse(item.createdAt) > Date.parse(latest.createdAt) + ? item + : latest; + }, null); +} + +function getRecIndexFromRequestId(requestId){ + if (!requestId) return null; + const match = requestId.match(/(RECINDEX_\d+)/); + return match ? match[1] : null; +} + +async function appendReceivedStatusCode(iun, reworkId, receivedStatusCodesEntry, analogMail) { + try { + await docClient.send(new UpdateCommand({ + TableName: NOTIFICATION_REWORK_TABLE, + Key: { iun, reworkId }, + UpdateExpression: "SET receivedStatusCodes = list_append(if_not_exists(receivedStatusCodes, :empty), :new)", + ConditionExpression: "NOT contains(receivedStatusCodes, :check)", + ExpressionAttributeValues: { + ":new": [receivedStatusCodesEntry], + ":check": receivedStatusCodesEntry, + ":empty": [] + } + })); + } catch (error) { + if (error.name === 'ConditionalCheckFailedException') { + // Elemento già presente, non è un errore + console.log(`Status code ${receivedStatusCodesEntry} già presente per iun=${iun}, reworkId=${reworkId}`); + await insertEventsError(iun, reworkId, analogMail, "Evento duplicato"); + } + } +} + +async function insertEventsError(iun, reworkId, analogMail, errorMessage) { + const now = new Date().toISOString(); + const uniqueReworkId = reworkId ? `${reworkId}~${crypto.randomUUID()}` : crypto.randomUUID(); + + const item = { + iun, + reworkId: uniqueReworkId, + requestId: analogMail.requestId, + statusCode: analogMail.statusCode, + statusDateTime: analogMail.statusDateTime, + productType: analogMail.productType, + deliveryFailureCause: analogMail.deliveryFailureCause, + attachments: analogMail.attachments, + errorMessage, + created: now + }; + + const command = new PutCommand({ + TableName: EVENTS_ERROR_TABLE, + Item: item + }); + await docClient.send(command); +} + +module.exports = { + getLatestReworkRequestByIun, + appendReceivedStatusCode, + insertEventsError +}; \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/app/eventHandler.js b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/app/eventHandler.js new file mode 100644 index 000000000..821f955f9 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/app/eventHandler.js @@ -0,0 +1,140 @@ +const { getLatestReworkRequestByIun, appendReceivedStatusCode, insertEventsError } = require("./dynamo"); +const { sendToQueue, createMessageAttributes } = require("./sqs"); +const { logOperation } = require("./log"); +const {checkRejectedStatusCode, checkAlreadyReceived, checkExpected, checkAttachments, checkDeliveryFailureCause, buildReceivedStatusCodeEntry} = require("./utils"); + +async function handleEvent(event){ + const batchItemFailures = []; + // Processa tutti i record, collezionando i fallimenti + for (const record of event.Records) { + try { + await processRecord(record); + } catch (error) { + batchItemFailures.push({ itemIdentifier: record.messageId}); + logOperation("ERROR", record.messageId, { message: "Record added to batch failures for retry", error: error.message,}); + } + } + + if (batchItemFailures.length > 0) { + return { + batchItemFailures: batchItemFailures + }; + } + return { status: "ok" }; +}; + +function parseBody(record) { + try { + return JSON.parse(record.body); + } catch (err) { + logOperation("ERROR", messageId, { + error: "Invalid JSON", + rawBody: record.body, + }); + throw err; + } +} + +/** + * Processa un singolo record SQS + */ +const processRecord = async (record) => { + const { messageId } = record; + logOperation("INFO", messageId, { message: "Processing message",record }); + + let body = parseBody(record); + + const analogMail = body?.analogMail; + + const statusCode = analogMail.statusCode; + const statusDateTime = analogMail.statusDateTime; + const attachments = analogMail.attachments?.map(att => att.documentType) || []; + const deliveryFailureCause = analogMail.deliveryFailureCause; + const iun = analogMail.iun; + const requestId = analogMail.requestId; + + const reworkEntry = await getLatestReworkRequestByIun(iun, requestId); + if (!reworkEntry) { + logOperation("FATAL", messageId, { message: "No notification rework request found for IUN", iun }); + await insertEventsError(iun, null, analogMail, "Richiesta di rework non presente"); + return; + } + + if(reworkEntry.status != "READY" && reworkEntry.status != "IN_PROGRESS"){ + logOperation("FATAL", messageId, { message: "Rework request is in invalid status", reworkStatus: reworkEntry.status }); + const reworkId = reworkEntry?.reworkId; + await insertEventsError(iun, reworkId, analogMail, `La richiesta di rework è in uno stato non valido: ${reworkEntry.status}`); + return; + } + + const reworkId = reworkEntry?.reworkId; + let continueProcessing = await checkStatusCode(messageId, reworkEntry, analogMail, statusCode, statusDateTime, attachments, deliveryFailureCause); + if(continueProcessing){ + const attributes = await createMessageAttributes(record.messageAttributes, reworkId); + await sendToQueue(body, attributes); + const receivedStatusCodeEntry = buildReceivedStatusCodeEntry(statusCode, attachments, statusDateTime); + await appendReceivedStatusCode(iun, reworkId, receivedStatusCodeEntry, analogMail); + } + + logOperation("DEBUG", messageId, { message: "Message processed successfully" }); +} + +async function checkStatusCode(messageId, reworkEntry, analogMail, statusCode, statusDateTime, attachments, deliveryFailureCause){ + const reworkId = reworkEntry?.reworkId; + const iun = reworkEntry?.iun; + const rejected = checkRejectedStatusCode(statusCode); + if (rejected) { + logOperation("FATAL", messageId, { message: "Scartato in quanto evento di furto, smarrimento o deterioramento"}); + await insertEventsError(iun, reworkId, analogMail, "Evento di furto, smarrimento o deterioramento"); + return false; + } + + const expected = checkExpected(reworkEntry, statusCode); + + if(!expected){ + const message = "Evento non atteso. Gli eventi accettati sono: " + JSON.stringify(reworkEntry.expectedStatusCodes); + logOperation("ERROR", messageId, { + message: message, + expectedStatusCodes: JSON.stringify(reworkEntry.expectedStatusCode) + }); + await insertEventsError(iun, reworkId, analogMail, message); + return false; + } + + const attachment = checkAttachments(reworkEntry, statusCode, attachments); + if(!attachment){ + const message = "Allegati non validi. Gli eventi accettati sono: " + JSON.stringify(reworkEntry.expectedStatusCodes); + logOperation("ERROR", messageId, { + message: message, + expectedStatusCodes: JSON.stringify(reworkEntry.expectedStatusCode) + }); + await insertEventsError(iun, reworkId, analogMail, message); + return false; + } + + if(statusCode.endsWith("A") || statusCode.endsWith("D")){ + const deliveryFailureCauseValidation = checkDeliveryFailureCause(reworkEntry, deliveryFailureCause); + if(!deliveryFailureCauseValidation){ + const message = "DeliveryFailureCause non valida."; + logOperation("ERROR", messageId, { + message: message + }); + await insertEventsError(iun, reworkId, analogMail, message); + return false; + } + } + + + const alreadyReceived = checkAlreadyReceived(reworkEntry, statusCode, attachments, statusDateTime); + + if(alreadyReceived){ + logOperation("ERROR", messageId, { message: "Evento duplicato"}); + await insertEventsError(iun, reworkId, analogMail, "Evento duplicato"); + return false; + } + return true +} + +module.exports = { + handleEvent +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/app/log.js b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/app/log.js new file mode 100644 index 000000000..d7690266d --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/app/log.js @@ -0,0 +1,27 @@ +/** + * Log strutturato per operazioni + * @param {string} level - Livello di log + * @param {string} messageId - ID del messaggio + * @param {object} details - Dettagli aggiuntivi + */ +function logOperation(level, messageId, details = {}){ + const logData = { + level, + messageId, + ...details, + }; + if(level === "FATAL"){ + console.fatal(JSON.stringify(logData)); + } + else if (level === "ERROR") { + console.error(JSON.stringify(logData)); + } else if (level === "DEBUG") { + console.debug(JSON.stringify(logData)); + } else { + console.log(JSON.stringify(logData)); + } +} + +module.exports = { + logOperation +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/app/sqs.js b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/app/sqs.js new file mode 100644 index 000000000..3226bb62c --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/app/sqs.js @@ -0,0 +1,52 @@ +const { SQSClient, SendMessageCommand } = require("@aws-sdk/client-sqs"); + +const SQS_QUEUE_URL = process.env.EXTERNAL_TO_PAPER_QUEUE_URL; +const sqsClient = new SQSClient({}); + +/** + * Invia un messaggio a una coda SQS + * @param {string} queueUrl - URL della coda + * @param {object} messageBody - Corpo del messaggio + * @param {object} messageAttributes - Attributi del messaggio + * @returns {Promise} Promise dell'invio + */ +async function sendToQueue (messageBody, messageAttributes) { + return await sqsClient.send( + new SendMessageCommand({ + QueueUrl: SQS_QUEUE_URL, + MessageBody: JSON.stringify(messageBody), + MessageAttributes: messageAttributes, + }) + ); +}; + +/** + * Crea gli attributi del messaggio copiando quelli originali + * @param {object} originalAttributes - Attributi originali del record SQS + * @param {boolean} addDryRun - Se aggiungere l'attributo dryRun + * @returns {object} Attributi del messaggio + */ +async function createMessageAttributes (originalAttributes = {}, reworkId) { + const attributes = {}; + // Copia attributi originali + for (const [key, attr] of Object.entries(originalAttributes)) { + // Saltiamo reworkId qui perché verrà sovrascritto esplicitamente dopo + if (key === 'reworkId') continue; + + attributes[key] = { + DataType: attr.dataType || attr.DataType || 'String', // Gestisce sia input camelCase che PascalCase + StringValue: attr.stringValue || attr.StringValue + }; + } + + attributes["reworkId"] = { + DataType: "String", + StringValue: reworkId, + }; + return attributes; +} + +module.exports = { + sendToQueue, + createMessageAttributes +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/app/utils.js b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/app/utils.js new file mode 100644 index 000000000..23dddf39a --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/app/utils.js @@ -0,0 +1,95 @@ +function checkExpected(reworkEntry, statusCode){ + if (!reworkEntry?.expectedStatusCodes || !Array.isArray(reworkEntry.expectedStatusCodes)) { + return false; + } + return reworkEntry.expectedStatusCodes.some(item => { + if (item.statusCode !== statusCode) return false; + return true; + }); +} + +function checkAttachments(reworkEntry, statusCode, attachments){ + return reworkEntry.expectedStatusCodes + .filter(item => item.statusCode == statusCode) + .some(item => { + const hasInputAttachments = attachments && attachments.length > 0; + const hasItemAttachments = item.attachments && item.attachments.length > 0; + + if ((hasItemAttachments && !hasInputAttachments) || (!hasItemAttachments && hasInputAttachments)) return false; + + // Caso: item ha allegati → controlla corrispondenza parziale (input ⊆ item) + if (hasItemAttachments) { + const isSubset = attachments.every(att => item.attachments.includes(att)); + const sameLength = item.attachments.length === attachments.length; + + // true solo se uguali o se input ⊂ item (come [a] vs [a,b]) + return isSubset && (sameLength || item.attachments.length > attachments.length); + } + return true; + }); +} + +function checkDeliveryFailureCause(reworkEntry, deliveryFailureCause){ + if(reworkEntry.expectedDeliveryFailureCause && !deliveryFailureCause){ + return false; + } + + if(!reworkEntry.expectedDeliveryFailureCause && deliveryFailureCause){ + return false; + } + + if(reworkEntry.expectedDeliveryFailureCause && deliveryFailureCause){ + return reworkEntry.expectedDeliveryFailureCause == deliveryFailureCause; + } + + return true; +} + +function checkAlreadyReceived(reworkEntry, statusCode, attachments, statusDateTime){ + if (!reworkEntry?.receivedStatusCodes || !Array.isArray(reworkEntry.receivedStatusCodes)) { + return false; + } + return reworkEntry.receivedStatusCodes.some(item => { + if (item.statusCode !== statusCode || item.statusDateTime !== statusDateTime) { + return false; + } + + const hasInputAttachments = attachments && attachments.length > 0; + const hasItemAttachments = item.attachments && item.attachments.length > 0; + + if (hasInputAttachments !== hasItemAttachments) { + return false; + } + + if (hasInputAttachments && hasItemAttachments) { + return item.attachments.length === attachments.length && + item.attachments.every(att => attachments.includes(att)); + } + return true; + }); +} + +function checkRejectedStatusCode(statusCode){ + const INVALID_STATUS_CODE = process.env.INVALID_STATUS_CODE ? process.env.INVALID_STATUS_CODE.split(",") : []; + if (INVALID_STATUS_CODE.includes(statusCode)) { + return true; + } + return false; +} + +function buildReceivedStatusCodeEntry(statusCode, attachments, statusDateTime){ + return { + statusCode, + attachments: attachments && attachments.length > 0 ? attachments : [], + statusDateTime + } +} + +module.exports = { + checkExpected, + checkAttachments, + checkDeliveryFailureCause, + checkAlreadyReceived, + checkRejectedStatusCode, + buildReceivedStatusCodeEntry +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/test/dynamo.test.js b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/test/dynamo.test.js new file mode 100644 index 000000000..137e5468c --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/test/dynamo.test.js @@ -0,0 +1,92 @@ +const { getLatestReworkRequestByIun, appendReceivedStatusCode, insertEventsError } = require('../app/dynamo'); +const { DynamoDBDocumentClient, QueryCommand, UpdateCommand, PutCommand } = require("@aws-sdk/lib-dynamodb"); +const { mockClient } = require('aws-sdk-client-mock'); +const assert = require('assert'); + +const ddbMock = mockClient(DynamoDBDocumentClient); + +describe("getLatestReworkRequestByIun", () => { + beforeEach(() => { + ddbMock.reset(); + }); + + it("returns the latest rework request when items are found", async () => { + ddbMock.on(QueryCommand).resolves({ + Items: [{ iun: "test-iun", reworkId: "rework-123" , recIndex: "RECINDEX_0", createdAt: "2025-11-27T11:48:17.194069270Z" }, + { iun: "test-iun", reworkId: "rework-124" , recIndex: "RECINDEX_0", createdAt: "2025-11-27T14:52:17.194069270Z" }, + { iun: "test-iun", reworkId: "rework-125" , recIndex: "RECINDEX_0", createdAt: "2025-11-27T19:48:17.194069270Z"}, + ] + }); + + const result = await getLatestReworkRequestByIun("test-iun", "PREPARE_ANALOG_DOMICILE.IUN_12345.RECINDEX_0.ATTEMPT_0"); + + assert(ddbMock.commandCalls(QueryCommand).length == 1); + assert.deepStrictEqual(result.reworkId, "rework-125"); + }); + + it("returns null when no items are found", async () => { + ddbMock.on(QueryCommand).resolves({ Items: [] }); + + const result = await getLatestReworkRequestByIun("test-iun"); + + assert(ddbMock.commandCalls(QueryCommand).length == 1); + assert.strictEqual(result, null); + }); +}); + +describe("appendReceivedStatusCode", () => { + beforeEach(() => { + ddbMock.reset(); + }); + + it("appends a new received status code entry", async () => { + ddbMock.on(UpdateCommand).resolves({}); + + await appendReceivedStatusCode( "test-iun", "rework-123",{ statusCode: "RECRN001A", attachments: [], statusDateTime: '2025-05-01'}, null); + + assert(ddbMock.commandCalls(UpdateCommand).length == 1); + }); + + it("appends a new received status code entry with error", async () => { + const analogMail = { + requestId: "req-123", + clientRequestTimeStamp: "2023-01-01T00:00:00Z", + statusCode: 500, + statusDateTime: "2023-01-01T00:00:00Z", + productType: "type-a", + deliveryFailureCause: "cause-a", + attachments: ["file1"], + registeredLetterCode: "code-123" + }; + ddbMock.on(UpdateCommand).rejects({ name: "ConditionalCheckFailedException" }); + await appendReceivedStatusCode( "test-iun", "rework-123",{ statusCode: "RECRN001A", attachments: [], statusDateTime: '2025-05-01'}, analogMail); + + assert(ddbMock.commandCalls(UpdateCommand).length == 1); + assert(ddbMock.commandCalls(PutCommand).length == 1); + }); +}); + +describe("insertEventsError", () => { + beforeEach(() => { + ddbMock.reset(); + }); + + it("inserts an event error into the table", async () => { + ddbMock.on(PutCommand).resolves({}); + + const analogMail = { + requestId: "req-123", + clientRequestTimeStamp: "2023-01-01T00:00:00Z", + statusCode: 500, + statusDateTime: "2023-01-01T00:00:00Z", + productType: "type-a", + deliveryFailureCause: "cause-a", + attachments: ["file1"], + registeredLetterCode: "code-123" + }; + + await insertEventsError("test-iun", "rework-123", analogMail, "Error message"); + + assert(ddbMock.commandCalls(PutCommand).length == 1); + }); +}); diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/test/eventHandler.test.js b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/test/eventHandler.test.js new file mode 100644 index 000000000..7b4a66b66 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/test/eventHandler.test.js @@ -0,0 +1,439 @@ +const assert = require('assert'); +const proxyquire = require('proxyquire'); + +// Mock modules +let mockDynamo = {}; +let mockSqs = {}; +let mockLog = {}; +let handleEvent; + +// Setup mocks before requiring handler +function setupMocks() { + // Reset mocks + mockDynamo = { + getLatestReworkRequestByIun: async () => null, + appendReceivedStatusCode: async () => {}, + insertEventsError: async () => {} + }; + + mockSqs = { + sendToQueue: async () => {}, + createMessageAttributes: () => ({}) + }; + + mockLog = { + logOperation: () => {} + }; +} + +// Helper functions +function createValidRecord(overrides = {}) { + const record = { + messageId: 'test-message-id', + body: JSON.stringify({ + analogMail: { + statusCode: 'RECAG001', + statusDateTime: '2025-01-15T10:00:00Z', + iun: 'TEST-IUN-123', + attachments: [{ documentType: 'AR' }], + deliveryFailureCause: null + } + }), + messageAttributes: {} + }; + + if (overrides.messageId) record.messageId = overrides.messageId; + if (overrides.body) record.body = overrides.body; + if (overrides.analogMail) { + const body = JSON.parse(record.body); + body.analogMail = { ...body.analogMail, ...overrides.analogMail }; + record.body = JSON.stringify(body); + } + + return record; +} + +function createInvalidRecord(overrides = {}) { + const record = { + messageId: 'test-message-id', + body: JSON.stringify({ + analogMail: { + statusCode: 'RECAG003', + statusDateTime: '2025-01-15T10:00:00Z', + iun: 'TEST-IUN-123', + attachments: [{ documentType: 'AR' }], + deliveryFailureCause: null + } + }), + messageAttributes: {} + }; + + if (overrides.messageId) record.messageId = overrides.messageId; + if (overrides.body) record.body = overrides.body; + if (overrides.analogMail) { + const body = JSON.parse(record.body); + body.analogMail = { ...body.analogMail, ...overrides.analogMail }; + record.body = JSON.stringify(body); + } + + return record; +} + +function createReworkEntry(overrides = {}) { + return { + iun: 'TEST-IUN-123', + reworkId: 'rework-123', + status: 'READY', + expectedStatusCodes: [ + { statusCode: 'RECAG001', attachments: ['AR'] }, + { statusCode: 'RECAG002', attachments: [] } + ], + receivedStatusCodes: [], + ...overrides + }; +} + +describe("handleEvent - Successful Processing", () => { + beforeEach(() => { + setupMocks(); }); + + it("should process valid record successfully", async () => { + let appendCalled = false; + let queueCalled = false; + + // Set up mocks before loading handler + mockDynamo.getLatestReworkRequestByIun = async (iun) => { + assert.strictEqual(iun, 'TEST-IUN-123'); + return createReworkEntry(); + }; + + mockDynamo.appendReceivedStatusCode = async () => { + appendCalled = true; + }; + + mockSqs.sendToQueue = async () => { + queueCalled = true; + }; + + mockSqs.createMessageAttributes = () => ({ reworkId: 'rework-123' }); + + // Reload handler with updated mocks + handleEvent = proxyquire('../app/eventHandler', { + './dynamo': mockDynamo, + './sqs': mockSqs, + './log': mockLog + }).handleEvent; + + const event = { Records: [createValidRecord()] }; + const result = await handleEvent(event); + + assert.deepStrictEqual(result, { status: 'ok' }); + assert.strictEqual(appendCalled, true, 'appendReceivedStatusCode should be called'); + assert.strictEqual(queueCalled, true, 'sendToQueue should be called'); + }); + + it("should process multiple records successfully", async () => { + let sqsCounter = 0; + let appendCounter = 0; + + // Set up mocks before loading handler + mockDynamo.getLatestReworkRequestByIun = async (iun) => { + assert.strictEqual(iun, 'TEST-IUN-123'); + return createReworkEntry(); + }; + + mockDynamo.appendReceivedStatusCode = async () => { + appendCounter++; + }; + + mockSqs.sendToQueue = async () => { + sqsCounter++; + }; + + mockSqs.createMessageAttributes = () => ({ reworkId: 'rework-123' }); + + // Reload handler with updated mocks + handleEvent = proxyquire('../app/eventHandler', { + './dynamo': mockDynamo, + './sqs': mockSqs, + './log': mockLog + }).handleEvent; + + const event = { + Records: [ + createValidRecord({ messageId: 'msg-1' }), + createValidRecord({ messageId: 'msg-2' }), + createValidRecord({ messageId: 'msg-3' }) + ] + }; + + const result = await handleEvent(event); + + assert.deepStrictEqual(result, { status: 'ok' }); + assert.strictEqual(appendCounter, 3, 'appendReceivedStatusCode should be called 3 times'); + assert.strictEqual(sqsCounter, 3, 'sendToQueue should be called 3 times'); + }); +}); + +describe("handleEvent - Error Cases", () => { + beforeEach(() => { + setupMocks(); + }); + + it("should handle missing rework entry", async () => { + let callAppend = false; + let callSqs = false; + + mockDynamo.appendReceivedStatusCode = async () => { + callAppend = true; + }; + + mockSqs.sendToQueue = async () => { + callSqs = true; + }; + + mockDynamo.insertEventsError = async (...args) => { + errorInserted = true; + capturedArgs = args; + }; + + // Reload handler with updated mocks + handleEvent = proxyquire('../app/eventHandler', { + './dynamo': mockDynamo, + './sqs': mockSqs, + './log': mockLog + }).handleEvent; + + const event = { Records: [createValidRecord()] }; + const result = await handleEvent(event); + + assert.deepStrictEqual(result, {"status": "ok"}); + assert.strictEqual(errorInserted, true); + assert.strictEqual(capturedArgs[3], 'Richiesta di rework non presente'); + assert.strictEqual(callAppend,false, 'appendReceivedStatusCode should not be called'); + assert.strictEqual(callSqs, false, 'sendToQueue should not be called'); + }); + + it("should reject rework with COMPLETED status", async () => { + let callAppend = false; + let callSqs = false; + let reworkEntry = createReworkEntry(); + reworkEntry.status = "DONE"; + + mockDynamo.getLatestReworkRequestByIun = async (iun) => { + assert.strictEqual(iun, 'TEST-IUN-123'); + return reworkEntry; + }; + + mockDynamo.appendReceivedStatusCode = async () => { + callAppend = true; + }; + + mockSqs.sendToQueue = async () => { + callSqs = true; + }; + + mockDynamo.insertEventsError = async (...args) => { + errorInserted = true; + capturedArgs = args; + }; + + // Reload handler with updated mocks + handleEvent = proxyquire('../app/eventHandler', { + './dynamo': mockDynamo, + './sqs': mockSqs, + './log': mockLog + }).handleEvent; + + const event = { Records: [createValidRecord()] }; + const result = await handleEvent(event); + + assert.deepStrictEqual(result, {"status": "ok"}); + assert.strictEqual(errorInserted, true); + assert.strictEqual(capturedArgs[3], `La richiesta di rework è in uno stato non valido: DONE`); + assert.strictEqual(callAppend,false, 'appendReceivedStatusCode should not be called'); + assert.strictEqual(callSqs, false, 'sendToQueue should not be called'); + }); + + it("should reject rework with ERROR status", async () => { + let callAppend = false; + let callSqs = false; + let reworkEntry = createReworkEntry(); + reworkEntry.status = "ERROR"; + + mockDynamo.getLatestReworkRequestByIun = async (iun) => { + assert.strictEqual(iun, 'TEST-IUN-123'); + return reworkEntry; + }; + + mockDynamo.appendReceivedStatusCode = async () => { + callAppend = true; + }; + + mockSqs.sendToQueue = async () => { + callSqs = true; + }; + + mockDynamo.insertEventsError = async (...args) => { + errorInserted = true; + capturedArgs = args; + }; + + // Reload handler with updated mocks + handleEvent = proxyquire('../app/eventHandler', { + './dynamo': mockDynamo, + './sqs': mockSqs, + './log': mockLog + }).handleEvent; + + const event = { Records: [createValidRecord()] }; + const result = await handleEvent(event); + + assert.deepStrictEqual(result, {"status": "ok"}); + assert.strictEqual(errorInserted, true); + assert.strictEqual(capturedArgs[3], `La richiesta di rework è in uno stato non valido: ERROR`); + assert.strictEqual(callAppend,false, 'appendReceivedStatusCode should not be called'); + assert.strictEqual(callSqs, false, 'sendToQueue should not be called'); + }); + + it("should reject rework with DeliveryFailureCause not valid", async () => { + let callAppend = false; + let callSqs = false; + let errorInserted = false; + let capturedArgs = []; + let reworkEntry = createReworkEntry(); + reworkEntry.expectedStatusCodes.push({ statusCode: 'RECAG011A', attachments: ['AR'] }); + + mockDynamo.getLatestReworkRequestByIun = async (iun) => { + assert.strictEqual(iun, 'TEST-IUN-123'); + return reworkEntry; + }; + + mockDynamo.appendReceivedStatusCode = async () => { + callAppend = true; + }; + + mockSqs.sendToQueue = async () => { + callSqs = true; + }; + + mockDynamo.insertEventsError = async (...args) => { + errorInserted = true; + capturedArgs = args; + }; + + handleEvent = proxyquire('../app/eventHandler', { + './dynamo': mockDynamo, + './sqs': mockSqs, + './log': mockLog + }).handleEvent; + + const record = createValidRecord({ + analogMail: { + statusCode: 'RECAG011A', + deliveryFailureCause: 'INVALID_CAUSE' + } + }); + const event = { Records: [record] }; + const result = await handleEvent(event); + + assert.deepStrictEqual(result, {"status": "ok"}); + assert.strictEqual(errorInserted, true); + assert.strictEqual(capturedArgs[3], 'DeliveryFailureCause non valida.'); + assert.strictEqual(callAppend, false, 'appendReceivedStatusCode should not be called'); + assert.strictEqual(callSqs, false, 'sendToQueue should not be called'); + }); +}); + +describe("handleEvent - Status Code Validation", () => { + beforeEach(() => { + setupMocks(); + }); + + it("should reject unexpected status codes", async () => { + let appendCalled = false; + let queueCalled = false; + let errorInserted = false; + const reworkEntry = createReworkEntry(); + const message = "Evento non atteso. Gli eventi accettati sono: " + JSON.stringify(reworkEntry.expectedStatusCodes); + + // Set up mocks before loading handler + mockDynamo.getLatestReworkRequestByIun = async (iun) => { + assert.strictEqual(iun, 'TEST-IUN-123'); + return reworkEntry; + }; + + mockDynamo.appendReceivedStatusCode = async () => { + appendCalled = true; + }; + + mockSqs.sendToQueue = async () => { + queueCalled = true; + }; + + mockDynamo.insertEventsError = async (...args) => { + errorInserted = true; + capturedArgs = args; + }; + + // Reload handler with updated mocks + handleEvent = proxyquire('../app/eventHandler', { + './dynamo': mockDynamo, + './sqs': mockSqs, + './log': mockLog + }).handleEvent; + + const event = { Records: [createInvalidRecord()] }; + const result = await handleEvent(event); + + assert.deepStrictEqual(result, {"status": "ok"}); + assert.strictEqual(errorInserted, true); + assert.strictEqual(capturedArgs[3], message); + assert.strictEqual(appendCalled,false, 'appendReceivedStatusCode should not be called'); + assert.strictEqual(queueCalled, false, 'sendToQueue should not be called'); + }); + + it("should reject stolen/lost/damaged events", async () => { + let appendCalled = false; + let queueCalled = false; + let errorInserted = false; + const reworkEntry = createReworkEntry(); + process.env.INVALID_STATUS_CODE = 'RECAG001'; + + // Set up mocks before loading handler + mockDynamo.getLatestReworkRequestByIun = async (iun) => { + assert.strictEqual(iun, 'TEST-IUN-123'); + return reworkEntry; + }; + + mockDynamo.appendReceivedStatusCode = async () => { + appendCalled = true; + }; + + mockSqs.sendToQueue = async () => { + queueCalled = true; + }; + + mockDynamo.insertEventsError = async (...args) => { + errorInserted = true; + capturedArgs = args; + }; + + // Reload handler with updated mocks + handleEvent = proxyquire('../app/eventHandler', { + './dynamo': mockDynamo, + './sqs': mockSqs, + './log': mockLog + }).handleEvent; + + let record = createValidRecord(); + const event = { Records: [record] }; + const result = await handleEvent(event); + + assert.deepStrictEqual(result, {"status": "ok"}); + assert.strictEqual(errorInserted, true); + assert.strictEqual(capturedArgs[3], "Evento di furto, smarrimento o deterioramento"); + assert.strictEqual(appendCalled,false, 'appendReceivedStatusCode should not be called'); + assert.strictEqual(queueCalled, false, 'sendToQueue should not be called'); + }); +}); \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/test/sqs.test.js b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/test/sqs.test.js new file mode 100644 index 000000000..2732c360e --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/test/sqs.test.js @@ -0,0 +1,79 @@ +const { test, describe, beforeEach, mock } = require('node:test'); +const assert = require('node:assert'); +const { sendToQueue, createMessageAttributes } = require('../app/sqs'); +const { SQSClient } = require("@aws-sdk/client-sqs"); + +describe("sendToQueue", () => { + test("sends a message to the specified SQS queue", async () => { + const queueUrl = "https://example-queue-url"; + const messageBody = { key: "value" }; + const messageAttributes = { attr: { DataType: "String", StringValue: "test" } }; + + const sendMock = mock.fn(); + sendMock.mock.mockImplementation(() => Promise.resolve({ MessageId: "12345" })); + SQSClient.prototype.send = sendMock; + + const result = await sendToQueue(queueUrl, messageBody, messageAttributes); + + assert.strictEqual(sendMock.mock.callCount(), 1); + const callArgs = sendMock.mock.calls[0].arguments[0]; + assert.strictEqual(callArgs.input.QueueUrl, queueUrl); + assert.strictEqual(callArgs.input.MessageBody, JSON.stringify(messageBody)); + assert.deepStrictEqual(callArgs.input.MessageAttributes, messageAttributes); + assert.deepStrictEqual(result, { MessageId: "12345" }); + }); + + test("throws an error if the SQS client fails", async () => { + const queueUrl = "https://example-queue-url"; + const messageBody = { key: "value" }; + const messageAttributes = { attr: { DataType: "String", StringValue: "test" } }; + + const sendMock = mock.fn(); + sendMock.mock.mockImplementation(() => Promise.reject(new Error("SQS error"))); + SQSClient.prototype.send = sendMock; + + await assert.rejects( + () => sendToQueue(queueUrl, messageBody, messageAttributes), + { message: "SQS error" } + ); + }); +}); + +describe("createMessageAttributes", () => { + test("creates message attributes with reworkId", async () => { + const originalAttributes = { + attr1: { DataType: "String", StringValue: "value1" }, + }; + const reworkId = "rework-123"; + + const result = await createMessageAttributes(originalAttributes, reworkId); + + assert.deepStrictEqual(result, { + attr1: { DataType: "String", StringValue: "value1" }, + reworkId: { DataType: "String", StringValue: "rework-123" }, + }); + }); + + test("returns an empty object if no original attributes are provided", async () => { + const reworkId = "rework-123"; + + const result = await createMessageAttributes({}, reworkId); + + assert.deepStrictEqual(result, { + reworkId: { DataType: "String", StringValue: "rework-123" }, + }); + }); + + test("overrides existing reworkId attribute if present in original attributes", async () => { + const originalAttributes = { + reworkId: { DataType: "String", StringValue: "old-rework-id" }, + }; + const reworkId = "new-rework-id"; + + const result = await createMessageAttributes(originalAttributes, reworkId); + + assert.deepStrictEqual(result, { + reworkId: { DataType: "String", StringValue: "new-rework-id" }, + }); + }); +}); diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/test/utils.test.js b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/test/utils.test.js new file mode 100644 index 000000000..70f2586cb --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/functions/notificationReworkEventsValidation/src/test/utils.test.js @@ -0,0 +1,169 @@ +const assert = require('assert'); +const { checkExpected, checkAlreadyReceived, checkAttachments, checkDeliveryFailureCause, checkRejectedStatusCode, buildReceivedStatusCodeEntry } = require('../app/utils'); + +describe("checkExpected", () => { + it("returns false if reworkEntry has no expectedStatusCodes", () => { + const result = checkExpected({}, "RECRN001A", [], null); + assert.strictEqual(result, false); + }); + + it("returns true if statusCode matches and attachments are a subset", () => { + const reworkEntry = { + expectedStatusCodes: [ + { statusCode: "RECRN001B", attachments: ["a", "b"] } + ] + }; + const result = checkAttachments(reworkEntry, "RECRN001B", ["a"]); + assert.strictEqual(result, true); + }); + + it("returns false if statusCode matches but attachments is not present in entity", () => { + const reworkEntry = { + expectedStatusCodes: [ + { statusCode: "RECRN001B", attachments: [] } + ] + }; + const result = checkAttachments(reworkEntry, "RECRN001B", ["c"]); + assert.strictEqual(result, false); + }); + + it("returns false if statusCode matches but attachments is not present in entity and there is another expected status code", () => { + const reworkEntry = { + expectedStatusCodes: [ + { statusCode: "RECRN001B", attachments: ["Plico"] }, + { statusCode: "RECRN001A", attachments: [] } + ] + }; + const result = checkAttachments(reworkEntry, "RECRN001B", []); + assert.strictEqual(result, false); + }); + + it("returns false if statusCode matches but attachments are not a subset", () => { + const reworkEntry = { + expectedStatusCodes: [ + { statusCode: "RECRN001B", attachments: ["a", "b"] } + ] + }; + const result = checkAttachments(reworkEntry, "RECRN001B", ["c"]); + assert.strictEqual(result, false); + }); + + it("returns false if statusCode matches but attachments are not a subset 2", () => { + const reworkEntry = { + expectedStatusCodes: [ + { statusCode: "RECRN001B", attachments: ["a", "b"] } + ] + }; + const result = checkAttachments(reworkEntry, "RECRN001B", ["a", "b", "c"]); + assert.strictEqual(result, false); + }); + + it("returns true if deliveryFailureCause matches", () => { + const reworkEntry = { + expectedStatusCodes: [{ statusCode: "RECRN002A" }], + expectedDeliveryFailureCause: "M02" + }; + const result = checkDeliveryFailureCause(reworkEntry, "M02"); + assert.strictEqual(result, true); + }); + + it("returns false if deliveryFailureCause not matches", () => { + const reworkEntry = { + expectedStatusCodes: [{ statusCode: "RECRN002A" }], + expectedDeliveryFailureCause: "M05" + }; + const result = checkDeliveryFailureCause(reworkEntry, "M02"); + assert.strictEqual(result, false); + }); +}); + +describe("checkAlreadyReceived", () => { + it("returns false if reworkEntry has no receivedStatusCodes", () => { + const result = checkAlreadyReceived({}, "RECRN002A", [], "2023-01-01T00:00:00Z"); + assert.strictEqual(result, false); + }); + + it("returns true if statusCode, statusDateTime, and attachments match", () => { + const reworkEntry = { + receivedStatusCodes: [ + { statusCode: "RECRN002B", statusDateTime: "2023-01-01T00:00:00Z", attachments: ["a"] } + ] + }; + const result = checkAlreadyReceived(reworkEntry, "RECRN002B", ["a"], "2023-01-01T00:00:00Z"); + assert.strictEqual(result, true); + }); + + it("returns false if statusDateTime does not match", () => { + const reworkEntry = { + receivedStatusCodes: [ + { statusCode: "RECRN002B", statusDateTime: "2023-01-01T00:00:00Z", attachments: ["a"] } + ] + }; + const result = checkAlreadyReceived(reworkEntry, "RECRN002B", ["a"], "2023-01-02T00:00:00Z"); + assert.strictEqual(result, false); + }); + + it("returns false if attachments do not match", () => { + const reworkEntry = { + receivedStatusCodes: [ + { statusCode: "RECRN002B", statusDateTime: "2023-01-01T00:00:00Z", attachments: ["b"] } + ] + }; + const result = checkAlreadyReceived(reworkEntry, "RECRN002B", ["a"], "2023-01-01T00:00:00Z"); + assert.strictEqual(result, false); + }); + + it("returns false if attachments do not match2", () => { + const reworkEntry = { + receivedStatusCodes: [ + { statusCode: "RECRN002B", statusDateTime: "2023-01-01T00:00:00Z", attachments: ["a"] } + ] + }; + const result = checkAlreadyReceived(reworkEntry, "RECRN002B", ["a", "b"], "2023-01-01T00:00:00Z"); + assert.strictEqual(result, false); + }); + + it("returns false if attachments do not match3", () => { + const reworkEntry = { + receivedStatusCodes: [ + { statusCode: "RECRN002B", statusDateTime: "2023-01-01T00:00:00Z", attachments: ["a", "b"] } + ] + }; + const result = checkAlreadyReceived(reworkEntry, "RECRN002B", ["b"], "2023-01-01T00:00:00Z"); + assert.strictEqual(result, false); + }); +}); + +describe("checkRejectedStatusCode", () => { + it("returns true if statusCode is in INVALID_STATUS_CODE", () => { + process.env.INVALID_STATUS_CODE = "RECRN006,RECRI005"; + const result = checkRejectedStatusCode("RECRN006"); + assert.strictEqual(result, true); + }); + + it("returns false if statusCode is not in INVALID_STATUS_CODE", () => { + process.env.INVALID_STATUS_CODE = "RECRN006,RECRI005"; + const result = checkRejectedStatusCode("RECRN002A"); + assert.strictEqual(result, false); + }); +}); + +describe("buildReceivedStatusCodeEntry", () => { + it("creates an entry with statusCode, attachments, and statusDateTime", () => { + const result = buildReceivedStatusCodeEntry("RECRN002B", ["a"], "2023-01-01T00:00:00Z"); + assert.deepStrictEqual(result, { + statusCode: "RECRN002B", + attachments: ["a"], + statusDateTime: "2023-01-01T00:00:00Z" + }); + }); + + it("creates an entry with empty attachments if none are provided", () => { + const result = buildReceivedStatusCodeEntry("RECRN002B", [], "2023-01-01T00:00:00Z"); + assert.deepStrictEqual(result, { + statusCode: "RECRN002B", + attachments: [], + statusDateTime: "2023-01-01T00:00:00Z" + }); + }); +}); diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/lombok.config b/jdk_21_maven/cs/rest/pn-paper-tracker/lombok.config new file mode 100644 index 000000000..864f2e5d2 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/lombok.config @@ -0,0 +1,2 @@ +lombok.addLombokGeneratedAnnotation = true +lombok.log.custom.declaration = it.pagopa.pn.commons.log.PnLogger it.pagopa.pn.commons.log.PnLogger.getLogger(NAME) \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/mvnw b/jdk_21_maven/cs/rest/pn-paper-tracker/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/mvnw.cmd b/jdk_21_maven/cs/rest/pn-paper-tracker/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/pom.xml b/jdk_21_maven/cs/rest/pn-paper-tracker/pom.xml new file mode 100644 index 000000000..2a17c50b7 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/pom.xml @@ -0,0 +1,440 @@ + + + 4.0.0 + + it.pagopa.pn + pn-parent + 2.3.1-SPRINGBOOT3 + + + pn-paper-tracker + 1.8.0-SNAPSHOT + pn-paper-tracker + PN paper-tracker Microservice + + ${git.conn} + ${git.devConn} + ${git.url} + HEAD + + + + + + it.pagopa.pn + pn-commons + 2.18.0-SPRINGBOOT3 + + + + it.pagopa.pn + pn-model + 2.11.0 + + + + org.mapstruct + mapstruct + + + + commons-io + commons-io + 2.15.1 + + + + io.swagger + swagger-annotations + 1.6.5 + + + + org.projectlombok + lombok + provided + + + + org.springframework.boot + spring-boot-starter-aop + + + + software.amazon.awssdk + sqs + + + + software.amazon.awssdk + bom + 2.31.77 + pom + runtime + + + + io.awspring.cloud + spring-cloud-aws-dependencies + 3.4.0 + pom + + + + io.awspring.cloud + spring-cloud-aws-starter-sqs + 3.4.0 + + + + software.amazon.awssdk + regions + 2.30.26 + + + + software.amazon.awssdk + kms + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + net.logstash.logback + logstash-logback-encoder + + + + io.projectreactor + reactor-test + test + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework + spring-test + + + + org.testcontainers + localstack + + 1.20.6 + test + + + + org.testcontainers + junit-jupiter + test + + + + io.swagger.parser.v3 + swagger-parser-core + 2.1.20 + + + + io.swagger.core.v3 + swagger-annotations-jakarta + 2.2.20 + + + + jakarta.validation + jakarta.validation-api + 3.0.2 + + + + javax.validation + validation-api + 2.0.1.Final + + + + com.asyncapi + asyncapi-plugin-core + 1.0.0-EAP-1 + + + + io.micrometer + micrometer-tracing-bridge-otel + + + io.opentelemetry + opentelemetry-exporter-otlp + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-dependency-plugin + 3.8.1 + + + unpack-dependencies + generate-resources + + unpack-dependencies + + + pn-commons + ${project.build.directory}/dependency-resources + + + + + + org.jacoco + jacoco-maven-plugin + + + **/generated/** + **it/pagopa/pn/template/rest/v1/** + + + + + org.openapitools + openapi-generator-maven-plugin + 7.13.0 + + + generate-paper-tracker-server + + generate + + generate-resources + + ${project.basedir}/docs/openapi/api-internal-v1.yaml + spring + spring-boot + + java8 + true + true + true + none + source + false + true + false + true + true + false + true + ${project.groupId}.papertracker.generated.openapi.server.v1 + ${project.groupId}.papertracker.generated.openapi.server.v1.dto + ${project.groupId}.papertracker.generated.openapi.server.v1.api + ${project.groupId}.papertracker.generated.openapi.server.v1.config + + + date-time=Instant + DateTime=Instant + date=LocalDate + + + Instant=java.time.Instant + LocalDate=java.time.LocalDate + + + false + false + false + + + + generate-client-pn-safe-storage + + generate + + generate-resources + + https://raw.githubusercontent.com/pagopa/pn-safe-storage/59b1c5255759262e4eb645ad3e8345bc810d03e1/docs/openapi/pn-safestorage-v1-api.yaml + java + webclient + false + false + false + true + + true + false + + @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY) + + + @lombok.Builder(toBuilder = true); + @lombok.AllArgsConstructor + + + + ${project.groupId}.papertracker.generated.openapi.msclient.safestorage.api + + + ${project.groupId}.papertracker.generated.openapi.msclient.safestorage.model + + + + + generate-resources + generate-client-pn-paper-channel + + generate + + + + + https://raw.githubusercontent.com/pagopa/pn-paper-channel/3690259216c94bb7402c9641d0344e222048a191/docs/openapi/api-internal-v1.yaml + + java + webclient + false + false + false + true + + true + false + + @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY) + + + @lombok.Builder(toBuilder = true); + @lombok.AllArgsConstructor + + + + ${project.groupId}.papertracker.generated.openapi.msclient.paperchannel.api + + + ${project.groupId}.papertracker.generated.openapi.msclient.paperchannel.model + + + + + generate-resources + generate-client-pn-external-channel + + generate + + + + + ${project.basedir}/docs/openapi/external-channel/pn-external-channel.yaml + + java + webclient + false + false + false + true + + true + false + + @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY) + + + @lombok.Builder(toBuilder = true); + @lombok.AllArgsConstructor + + + + ${project.groupId}.papertracker.generated.openapi.msclient.externalchannel.api + + + ${project.groupId}.papertracker.generated.openapi.msclient.externalchannel.model + + + + + generate-client-pn-data-vault + + generate + + generate-resources + + https://raw.githubusercontent.com/pagopa/pn-data-vault/f6d07290faf645f0769b5d53901b0123c11a594f/docs/openapi/pn-datavault-api-v1.yaml + java + webclient + false + false + false + true + + true + false + + @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY) + + + @lombok.Builder(toBuilder = true); + @lombok.AllArgsConstructor + + + ${project.groupId}.papertracker.generated.openapi.msclient.pndatavault.model + ${project.groupId}.papertracker.generated.openapi.msclient.pndatavault.api + + + + + + com.sngular + scs-multiapi-maven-plugin + 6.7.3 + + + generate-sources + + asyncapi-generation + + + + + docs/asyncapi/external-datalake-v1.yaml + + OCR_Command + CustomConsumer + DTO + com.sngular.apigenerator.asyncapi.business_model.model.event.consumer + com.sngular.apigenerator.asyncapi.business_model.model.event + + + + docs/asyncapi/internal-datalake-v1.yaml + + OCR_Result + com.sngular.apigenerator.asyncapi.business_model.model.event.producer + com.sngular.apigenerator.asyncapi.business_model.model.event + + + + generated-sources + + + + + + + \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/scripts/aws/cfn/application-dev.env b/jdk_21_maven/cs/rest/pn-paper-tracker/scripts/aws/cfn/application-dev.env new file mode 100644 index 000000000..5be5e3cdd --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/scripts/aws/cfn/application-dev.env @@ -0,0 +1,93 @@ +# Abilita l'OCR per singolo prodotto +# Valori ammessi per prodotto: [RUN, DRY] +# Formato: data_attivazione1;PRODOTTO1:MODALITA1;PRODOTTO2:MODALITA2,data_attivazione2;PRODOTTO1:MODALITA1;PRODOTTO2:MODALITA2 +PN_PAPERTRACKER_ENABLEOCRVALIDATIONFOR=1970-01-01;AR:DRY;890:DRY + +# Abilita l'OCR in modalità RUN per tutte le spedizioni che soddisfano un intervallo temporale specifico +# Valori ammessi: CRON_EXPRESSION, DISABLED +PN_PAPERTRACKER_OCRFILTERTEMPORAL=* * 9-18 * * MON + +# Abilita l'OCR in modalità RUN per tutte le spedizioni che soddisfano un determinato recapitista +# Valori ammessi: [Fulmine, Poste, Sailpost, PostAndService, DISABLED] +# Formato: CommaDelimitedList +PN_PAPERTRACKER_OCRFILTERUNIFIEDDELIVERYDRIVER=Poste + +PN_PAPERTRACKER_ENABLEOCRVALIDATIONFORFILE=PDF + +# StatusCode che non invia a pn-delivery-push ma vengono +# salvati nel tracking +# Formato: CommaDelimitedList +PN_PAPERTRACKER_SAVEANDNOTSENDTODELIVERYPUSH=CON018,CON09A,CON010,CON011,CON012,CON016 + + +PN_PAPERTRACKER_COMPIUTAGIACENZAARDURATION=80s + +# Abilita il troncamento delle date nel calcolo della giacenza +PN_PAPERTRACKER_ENABLETRUNCATEDDATEFORREFINEMENTCHECK=false + +PN_PAPERTRACKER_REFINEMENTDURATION=1m + +# TTL oggetto tracking +PN_PAPERTRACKER_PAPERTRACKINGSTTLDURATION=3650d + +# TTL oggetto errors +PN_PAPERTRACKER_PAPERTRACKINGSERRORSTTLDURATION=3650d + +# Max PC_RETRY prima di restituire errore in modalità DRY-RUN +PN_PAPERTRACKER_MAXPCRETRYMOCK=3 + +# Allegati necessari al processo di perfezionamento della giacenza 890. +# Allegati ammessi: [23L, ARCAD, CAD] +# Formato: data_attivazione1;ALLEGATO1;ALLEGATO2,data_attivazione2;ALLEGATO1;ALLEGATO2 +PN_PAPERTRACKER_REQUIREDATTACHMENTSREFINEMENTSTOCK890=1970-01-01;23L + +# Allegati validati dall'OCR nel processo di perfezionamento della giacenza 890. +# Questi devono essere un sottoinsieme di quelli necessari al perfezionamento. +# Allegati ammessi: [23L, ARCAD] +# Formato: data_attivazione1;ALLEGATO1;ALLEGATO2,data_attivazione2;ALLEGATO1;ALLEGATO2 +PN_PAPERTRACKER_SENDOCRATTACHMENTSREFINEMENTSTOCK890=1970-01-01;23L + +# IMPORTANTE feature flag IN-PROGRESS - PARAMETRO NON UTILIZZATO +# Allegati da inviare all'OCR necessari alla chiusura fascicolo della giacenza 890 +# Allegati ammessi: [ARCAD, Plico] +# Formato: data_attivazione1;ALLEGATO1;ALLEGATO2,data_attivazione2;ALLEGATO1;ALLEGATO2 +PN_PAPERTRACKER_SENDOCRATTACHMENTSFINALVALIDATIONSTOCK890=1970-01-01; + +# Allegati da inviare all'OCR necessari al processo di perfezionamento di tutti +# i prodotti, ad eccezione della giacenza 890. +# Valori ammessi: [AR, Plico, 23L] +# Formato: data_attivazione1;ALLEGATO1;ALLEGATO2,data_attivazione2;ALLEGATO1;ALLEGATO2 +PN_PAPERTRACKER_SENDOCRATTACHMENTSFINALVALIDATION=1970-01-01;Plico;AR;23L + +# Specifica se, per l’evento finale di giacenza che determina +# la chiusura del fascicolo, eventuali errori di validazione +# debbano essere ignorati (registrati come WARNING nella tabella +# degli errori, con relativo PROGRESS nella timeline) oppure se +# debbano bloccare la spedizione, impostando lo stato su KO e +# registrando un ERROR. +# Valori ammessi: [true, false] +# Formato: data_attivazione1;valore1,data_attivazione2;valore2 +PN_PAPERTRACKER_STRICTFINALVALIDATIONSTOCK890=1970-01-01;true + +# Specifica se, per la verifica della deliveryFailureCause, la presenza della stessa +# su tutti gli eventi della sequence debba essere considerata obbligatoria o meno +# Valori ammessi: [true, false] +# Formato: data_attivazione1;valore1,data_attivazione2;valore2 +PN_PAPERTRACKER_STRICTDELIVERYFAILURECAUSE=1970-01-01;false + +# Statuscode interni gestiti +# Formato: CommaDelimitedList +PN_PAPERTRACKER_INTERNALEVENTS=P000,P001,P011,P012,P013 + +# Modalità di processamento per prodotto +# Valori ammessi per prodotto: [RUN, DRY] +# Formato: data_attivazione1;PRODOTTO1:MODALITA1;PRODOTTO2:MODALITA2,data_attivazione2;PRODOTTO1:MODALITA1;PRODOTTO2:MODALITA2 +PN_PAPERTRACKER_PRODUCTSPROCESSINGMODES=1970-01-01;AR:RUN;890:DRY,2026-02-02;AR:RUN;RIR:RUN;890:RUN;RS:RUN;RIS:RUN + +# Categorie degli ERROR da inoltrare al consolidatore quando il contratto sara definito. +# Formato: CommaDelimitedList +PN_PAPERTRACKER_SENDTOCONSOLIDATOREERRORCATEGORIES= + +# Domini con i quali confrontare il senderId per capire se si tratta di un redrive manuale +# Formato: CommaDelimitedList +PN_PAPERTRACKER_REDRIVEENABLEDDOMAINS=@pagopa.it,@external.pagopa.it \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/scripts/aws/cfn/data-quality-dev-cfg.json b/jdk_21_maven/cs/rest/pn-paper-tracker/scripts/aws/cfn/data-quality-dev-cfg.json new file mode 100644 index 000000000..7b5b76777 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/scripts/aws/cfn/data-quality-dev-cfg.json @@ -0,0 +1,6 @@ +{ + "Parameters" : { + "PaperTrackingsGlueTableName": "pn_paper_trackings", + "PaperTrackingsErrorsGlueTableName": "pn_paper_trackings_errors" + } +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/scripts/aws/cfn/data-quality.yml b/jdk_21_maven/cs/rest/pn-paper-tracker/scripts/aws/cfn/data-quality.yml new file mode 100644 index 000000000..bacd7e28b --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/scripts/aws/cfn/data-quality.yml @@ -0,0 +1,170 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Data quality template, creates a nested stack for Glue Table and Crawler + +Parameters: + TemplateBucketBaseUrl: + Type: String + Description: The S3 bucket from which to fetch the templates used by this stack. + + GlueServiceRoleArn: + Type: String + Description: Service role for the Glue Crawler. + + LogsBucketName: + Type: String + Description: Logs bucket name + + GlueDatabaseName: + Type: String + Description: Name of the Glue Database + + PaperTrackingsTableName: + Type: String + Description: DynamoDB table name for paper trackings + + PaperTrackingsGlueTableName: + Type: String + Description: Glue table name for paper trackings + AllowedPattern: ^[a-z_]+$ + ConstraintDescription: Glue table name must contain only lowercase letters and underscores. + Default: pn_paper_trackings + + PaperTrackingsErrorsTableName: + Type: String + Description: DynamoDB table name for paper trackings errors + + PaperTrackingsErrorsGlueTableName: + Type: String + Description: Glue table name for paper trackings errors + AllowedPattern: ^[a-z_]+$ + ConstraintDescription: Glue table name must contain only lowercase letters and underscores. + Default: pn_paper_trackings_errors + +Resources: + PaperTrackingsDataQualityStack: + Type: AWS::CloudFormation::Stack + Properties: + TemplateURL: !Sub ${TemplateBucketBaseUrl}/fragments/cdc-glue.yaml + Parameters: + DynamoTableName: !Ref PaperTrackingsTableName + GlueTableName: !Ref PaperTrackingsGlueTableName + GlueServiceRoleArn: !Ref GlueServiceRoleArn + LogsBucketName: !Ref LogsBucketName + GlueDatabaseName: !Ref GlueDatabaseName + DynamoDBKeysStructure: | + struct< + trackingId:struct + > + DynamoDBNewImageStructure: | + struct< + trackingId:struct, + attemptId:struct, + pcRetry:struct, + productType:struct, + unifiedDeliveryDriver:struct, + events:struct, + iun:struct, + requestTimestamp:struct, + statusCode:struct, + statusDescription:struct, + statusTimestamp:struct, + productType:struct, + deliveryFailureCause:struct, + anonymizedDiscoveredAddressId:struct, + attachments:struct, + documentType:struct, + uri:struct, + date:struct, + sha256:struct + >>>>, + registeredLetterCode:struct, + dryRun:struct, + createdAt:struct, + notificationReworkId:struct + >>>>, + paperStatus:struct, + deliveryFailureCause:struct, + anonymizedDiscoveredAddress:struct, + finalStatusCode:struct, + validatedSequenceTimestamp:struct, + validatedEvents:struct>>, + finalDematFound:struct, + paperDeliveryTimestamp:struct, + actualPaperDeliveryTimestamp:struct, + predictedRefinementType:struct + >>, + validationFlow:struct, + finalEventDematValidationTimestamp:struct, + refinementDematValidationTimestamp:struct, + finalEventBuilderTimestamp:struct, + recag012StatusTimestamp:struct, + ocrRequests:struct, + finalEventId:struct, + attachmentEventId:struct, + responseTimestamp:struct, + requestTimestamp:struct, + uri:struct + >>>> + >>, + validationConfig:struct, + requiredAttachmentsRefinementStock890:struct>>, + sendOcrAttachmentsRefinementStock890:struct>>, + sendOcrAttachmentsFinalValidationStock890:struct>>, + sendOcrAttachmentsFinalValidation:struct>>, + strictFinalValidationStock890:struct, + ocrFilterTemporal:struct, + ocrFilterUnifiedDeliveryDriver:struct>>, + ocrFileTypes:struct>> + >>, + nextRequestIdPcretry:struct, + state:struct, + businessState:struct, + pendingFinalEventId:struct, + createdAt:struct, + updatedAt:struct, + ttl:struct, + notificationReworkRequestTimestamp:struct, + notificationReworkId:struct, + processingMode:struct + > + + PaperTrackingsErrorsDataQualityStack: + Type: AWS::CloudFormation::Stack + Properties: + TemplateURL: !Sub ${TemplateBucketBaseUrl}/fragments/cdc-glue.yaml + Parameters: + DynamoTableName: !Ref PaperTrackingsErrorsTableName + GlueTableName: !Ref PaperTrackingsErrorsGlueTableName + GlueServiceRoleArn: !Ref GlueServiceRoleArn + LogsBucketName: !Ref LogsBucketName + GlueDatabaseName: !Ref GlueDatabaseName + DynamoDBKeysStructure: | + struct< + trackingId:struct, + created:struct + > + DynamoDBNewImageStructure: | + struct< + trackingId:struct, + created:struct, + category:struct, + details:struct, + message:struct, + additionalDetails:struct + >>, + flowThrow:struct, + eventThrow:struct, + eventIdThrow:struct, + productType:struct, + type:struct, + ttl:struct, + unifiedDeliveryDriver:struct, + registeredLetterCode:struct + > \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/scripts/aws/cfn/microservice-dev-cfg.json b/jdk_21_maven/cs/rest/pn-paper-tracker/scripts/aws/cfn/microservice-dev-cfg.json new file mode 100644 index 000000000..51ed39592 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/scripts/aws/cfn/microservice-dev-cfg.json @@ -0,0 +1,23 @@ +{ + "Parameters" : { + "MinTasksNumber": "2", + "MaxTasksNumber": "10", + "AutoscalingThreshold": "1200", + "ConsumedQueueMessagePerMinute": "3300", + "PnCronAnalyzer": "-", + "WireTapLogActivation": "false", + "MountEfs": "false", + "SafeStorageCxId": "pn-cons-000", + "PnOcrInputsQueueARN": "arn:aws:sqs:eu-south-1:830192246553:pn-mock_ocr_inputs", + "PnOcrInputsQueueURL": "https://sqs.eu-south-1.amazonaws.com/830192246553/pn-mock_ocr_inputs", + "PnOcrInputsQueueRegion": "eu-south-1", + "OcrAwsRoleARN": "arn:aws:iam::830192246553:role/pn-external-channels-TaskRole", + "OcrAwsAccountId": "830192246553", + "PaperTrackerExternalChannelUser": "pn-cons-000", + "DataVaultBaseUrl": "http://alb.confidential.pn.internal:8080", + "ExternalChannelsToPaperChannelDryrunQueueName": "pn-external_channel_to_paper_channel_dry_run", + "NotificationReworkInvalidStatusCode": "RECRN006,RECRN013,RECRI005,CON996,RECAG013,RECAG004", + "DisableAllConsumers": "false", + "MetricFormatType": "EMF" + } +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/scripts/aws/cfn/microservice.yml b/jdk_21_maven/cs/rest/pn-paper-tracker/scripts/aws/cfn/microservice.yml new file mode 100644 index 000000000..9b4a9a89c --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/scripts/aws/cfn/microservice.yml @@ -0,0 +1,998 @@ +AWSTemplateFormatVersion: 2010-09-09 +Description: 'Microservice deploy for pn-paper-tracker' + +Parameters: + ProjectName: + Type: String + Description: 'Usually pn can be pnXYZ where XYZ are the feature number, useful to create experimental environments without crash official development environment' + + AlarmSNSTopicArn: + Type: String + Description: ARN of alarm topic + + WebApiDnsName: + Type: String + Description: 'The DNS name used for WEB rest API.' + + CorsAllowedDomains: + Type: String + Description: 'Comma separated list of domains allowed to make cross origin request' + + ContainerImageUri: + Type: String + Description: 'Exact container image URI with full repository and image digest' + + MicroserviceNumber: + Type: Number + Description: 'Disambiguation useful for load balancer rules' + + MicroserviceBucketName: + Type: String + Default: '' + Description: 'Name of the bucket where the microservice files are copied during deploy' + + MicroserviceBucketBaseKey: + Type: String + Default: '' + Description: 'Base key of the microservice in the s3 bucket' + + TemplateBucketBaseUrl: + Type: String + Description: 'The S3 bucket from which to fetch the templates used by this stack.' + + ECSClusterName: + Type: String + Description: 'The name of the ECS cluster where the microservice is going to be deployed' + + SubnetsIds: + Type: String + Description: 'Subnets id list' + + VpcEgressSubnetsIds: + Type: String + Description: 'Egress subnets' + + VpcId: + Type: String + Description: 'VpcId where the microservice is going to be deployed' + + AlbSecurityGroup: + Type: String + Description: "Application load balancer security group" + + EcsDefaultSecurityGroup: + Type: String + Description: 'Default security group required by infrastructure' + + ApplicationLoadBalancerListenerArn: + Type: String + Description: 'Load balancer listener where HTTP endpoints are going to be registered' + + ApplicationLoadBalancerDomain: + Type: String + Description: 'Base URL of the load balancer where the service is going to be reachable' + + NetworkLoadBalancerLink: + Type: String + Description: 'network load balancer link for API-GW' + + LogsKinesisSourceStreamArn: + Type: String + Description: 'Kinesis stream that receives logs' + + Version: + Type: String + Description: 'keep track of used projects commitIds' + + EcsLogGroup: + Type: String + Description: 'ECS log group name' + + WireTapLogActivation: + Type: String + Default: false + Description: Activation of wire logs + AllowedValues: + - true + - false + + MountEfs: + Type: String + Default: "false" + AllowedValues: + - "true" + - "false" + + FargateEFSFileSystemID: + Type: String + Description: "EFS Filesystem" + + # Instance parameters + # 256 (.25 vCPU) - Available memory values: 0.5GB, 1GB, 2GB + # 512 (.5 vCPU) - Available memory values: 1GB, 2GB, 3GB, 4GB + # 1024 (1 vCPU) - Available memory values: 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB + # 2048 (2 vCPU) - Available memory values: Between 4GB and 16GB in 1GB increments + # 4096 (4 vCPU) - Available memory values: Between 8GB and 30GB in 1GB increments + CpuValue: + Type: Number + Default: 1024 + Description: Fargate virtual CPU quantity 1024 equals one vCPU + + # 0.5GB, 1GB, 2GB - Available cpu values: 256 (.25 vCPU) + # 1GB, 2GB, 3GB, 4GB - Available cpu values: 512 (.5 vCPU) + # 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB - Available cpu values: 1024 (1 vCPU) + # Between 4GB and 16GB in 1GB increments - Available cpu values: 2048 (2 vCPU) + # Between 8GB and 30GB in 1GB increments - Available cpu values: 4096 (4 vCPU) + MemoryAmount: + Type: String + Default: 2GB + Description: memory amount reserved to the task pod. + AllowedValues: [ 2GB, 4GB, 6GB, 8GB ] + + JavaToolOptions: + Type: String + Default: '-XX:MaxDirectMemorySize=256m' + Description: JavaToolOptions + + MinTasksNumber: + Default: 1 + Type: Number + Description: minimum autoscaling number of tasks + + MaxTasksNumber: + Default: 1 + Type: Number + Description: maximum autoscaling number of tasks + + PnCronAnalyzer: + Type: String + Default: '-' + Description: Cron for which you send the metric to CloudWatch + + LogAlarmStrategy: + Type: String + Default: 'FATAL' + + # Autoscaling parameters + AutoscalingCheckPeriod: + Default: 60 + Type: Number + Description: minimum autoscaling number of tasks\ + + AutoscalingDataPointN: + Default: 1 + Type: Number + Description: activate when N datapoint on M + + AutoscalingDataPointM: + Default: 1 + Type: Number + Description: activate when N datapoint on M + + AutoscalingThreshold: + Default: 70 + Type: String + + ConsumedQueueMessagePerMinute: + Type: Number + Default: 0 + Description: Weight to assign to queue number of visible messages metric + + HealthCheckTimeout: + Description: health check timeout seconds + Type: Number + Default: 5 + + HealthCheckInterval: + Description: Seconds between two health check + Type: Number + Default: 60 + + HealthyThresholdCount: + Description: | + The number of consecutive health checks successes required before considering + an unhealthy target healthy. + Type: Number + Default: 5 + + UnhealthyThresholdCount: + Description: | + The number of consecutive health check failures required before considering a target unhealthy. + Type: Number + Default: 2 + + ApplicationLoadBalancerMetricsDimensionName: + Type: String + Default: '' + Description: Application load balancer dimension name + + PnOcrOutputsQueueName: + Type: String + Description: 'Name of the SQS queue where OCR outputs are sent' + + PnOcrOutputsQueueARN: + Type: String + Description: 'ARN of the SQS queue where OCR outputs are sent' + + ExternalChannelToPaperTrackerQueueName: + Type: String + Description: 'Name of the SQS queue where external channel messages are sent' + + ExternalChannelToPaperTrackerQueueARN: + Type: String + Description: 'ARN of the SQS queue where external channel messages are sent' + + ExternalChannels2PaperChannelQueueName: + Type: String + Description: 'Pull external-channel messages from this Queue' + + ExternalChannels2PaperChannelQueueARN: + Type: String + Description: 'Pull external-channel messages from this Queue' + + ExternalChannels2PaperChannelQueueURL: + Type: String + Description: 'Pull external-channel messages from this Queue' + + ExternalChannelsToPaperChannelDryrunQueueName: + Type: String + Description: 'Write paper-channel messages to this Queue' + + PaperTrackingsErrorsTableName: + Type: String + Description: 'DynamoDB table name for PaperTrackingsErrors' + + PaperTrackingsErrorsTableArn: + Type: String + Description: 'DynamoDB table ARN for PaperTrackingsErrors' + + PaperTrackingsTableName: + Type: String + Description: 'DynamoDB table name for PaperTrackings' + + PaperTrackingsTableArn: + Type: String + Description: 'DynamoDB table ARN for PaperTrackings' + + PaperTrackerDryRunOutputsTableName: + Type: String + Description: 'DynamoDB table name for PaperTrackerDryRunOutputs' + + PaperTrackerDryRunOutputsTableArn: + Type: String + Description: 'DynamoDB table ARN for PaperTrackerDryRunOutputs' + + NotificationReworkEventsErrorTableName: + Type: String + Description: 'DynamoDB table name for NotificationReworkEventsError' + + NotificationReworkEventsErrorTableArn: + Type: String + Description: 'DynamoDB table ARN for NotificationReworkEventsError' + + ExternalChannelsOutputsQueueName: + Type: String + Description: 'Name of the SQS queue where external channel outputs are sent' + + ExternalChannelsOutputsQueueARN: + Type: String + Description: 'ARN of the SQS queue where external channel outputs are sent' + + SandboxSafeStorageBaseUrl: + Type: String + Description: 'URL to the SafeStorage microservice' + + SafeStorageCxId: + Type: String + Description: 'CxId for the SafeStorage microservice' + + PnOcrInputsQueueARN: + Type: String + Description: 'ARN of the SQS queue where OCR inputs are sent' + + PnOcrInputsQueueURL: + Type: String + Description: 'URL of the SQS queue where OCR inputs are sent' + + PnOcrInputsQueueRegion: + Type: String + Description: 'Region of the SQS queue where OCR inputs are sent' + + PnOcrOutputsQueueURL: + Type: String + Description: 'URL of the SQS queue where OCR outputs are sent' + + OcrAwsRoleARN: + Type: String + Description: 'ARN of the IAM role in the OCR AWS account that can send messages to the PnOcrOutputsQueue' + + OcrAwsAccountId: + Type: String + Description: 'AWS account ID of the OCR service' + + PaperTrackerExternalChannelUser: + Type: String + Description: 'The clientId used by ExternalChannel to send events to PaperTracker' + + DataVaultBaseUrl: + Type: String + Description: 'DataVault BaseUrl' + + ExternalChannelToReworkEventValidationQueueName: + Type: String + Description: 'Name of the SQS queue where external channel rework validation events are sent' + + ExternalChannelToReworkEventValidationQueueARN: + Type: String + Description: 'ARN of the SQS queue where external channel rework validation events are sent' + + ExternalChannelToReworkEventValidationQueueURL: + Type: String + Description: 'URL of the SQS queue where external channel rework validation events are sent' + + NotificationReworkEventsValidationLambdaName: + Type: String + Description: 'Name of the NotificationReworkEventsValidation Lambda.' + + NotificationReworkEventsValidationLambdaBatchSize: + Type: Number + Description: "Batch size of the NotificationReworkEventsValidation Lambda." + Default: 10 + + ApplicativeEnvFileChecksum: + Type: String + Default: '' + Description: 'Applicative environment variable file checksum' + + NotificationReworkInvalidStatusCode: + Type: String + Description: 'Comma separated list of invalid status codes for notification rework events' + + DisableAllConsumers: + Type: String + AllowedValues: [ "true", "false" ] + Default: "false" + + MetricFormatType: + Type: String + Description: 'Metric format type' + Default: 'EMF' + + 890ErrorThreshold: + Type: Number + Description: 'Threshold for 890 errors to trigger the alarm' + Default: 300 + + 890EvaluationPeriods: + Type: Number + Description: 'Number of evaluation periods for 890 errors alarm' + Default: 12 + + 890DatapointsToAlarm: + Type: Number + Description: 'Number of datapoints to alarm for 890 errors alarm' + Default: 1 + + 890TreatMissingData: + Type: String + Description: 'TreatMissingData for 890 errors alarm' + Default: 'ignore' + + ARTreatMissingData: + Type: String + Description: 'TreatMissingData for AR errors alarm' + Default: 'ignore' + + RSTreatMissingData: + Type: String + Description: 'TreatMissingData for RS errors alarm' + Default: 'ignore' + + RIRTreatMissingData: + Type: String + Description: 'TreatMissingData for RIR errors alarm' + Default: 'ignore' + + RISTreatMissingData: + Type: String + Description: 'TreatMissingData for RIS errors alarm' + Default: 'ignore' + + 890WhereClause: + Type: String + Description: 'Where clause to filter 890 errors in the custom metric query' + Default: "errorCategory != 'OCR_VALIDATION'" + + ARErrorThreshold: + Type: Number + Description: 'Threshold for AR errors to trigger the alarm' + Default: 300 + + AREvaluationPeriods: + Type: Number + Description: 'Number of evaluation periods for AR errors alarm' + Default: 12 + + ARDatapointsToAlarm: + Type: Number + Description: 'Number of datapoints to alarm for AR errors alarm' + Default: 1 + + ARWhereClause: + Type: String + Description: 'Where clause to filter AR errors in the custom metric query' + Default: "errorCategory != 'OCR_VALIDATION'" + + RSErrorThreshold: + Type: Number + Description: 'Threshold for RS errors to trigger the alarm' + Default: 300 + + RSEvaluationPeriods: + Type: Number + Description: 'Number of evaluation periods for RS errors alarm' + Default: 12 + + RSDatapointsToAlarm: + Type: Number + Description: 'Number of datapoints to alarm for RS errors alarm' + Default: 1 + + RSWhereClause: + Type: String + Description: 'Where clause to filter RS errors in the custom metric query' + Default: "errorCategory != 'OCR_VALIDATION'" + + RIRErrorThreshold: + Type: Number + Description: 'Threshold for RIR errors to trigger the alarm' + Default: 300 + + RIREvaluationPeriods: + Type: Number + Description: 'Number of evaluation periods for RIR errors alarm' + Default: 12 + + RIRDatapointsToAlarm: + Type: Number + Description: 'Number of datapoints to alarm for RIR errors alarm' + Default: 1 + + RIRWhereClause: + Type: String + Description: 'Where clause to filter RIR errors in the custom metric query' + Default: "errorCategory != 'OCR_VALIDATION'" + + RISErrorThreshold: + Type: Number + Description: 'Threshold for RIS errors to trigger the alarm' + Default: 300 + + RISEvaluationPeriods: + Type: Number + Description: 'Number of evaluation periods for RIS errors alarm' + Default: 12 + + RISDatapointsToAlarm: + Type: Number + Description: 'Number of datapoints to alarm for RIS errors alarm' + Default: 1 + + RISWhereClause: + Type: String + Description: 'Where clause to filter RIS errors in the custom metric query' + Default: "errorCategory != 'OCR_VALIDATION'" + + CpuArchitecture: + Type: String + Default: 'ARM' + Description: CPU architecture for the Fargate tasks + +Resources: + + PaperTrackerMicroservice: + Type: AWS::CloudFormation::Stack + Properties: + TemplateURL: !Sub "${TemplateBucketBaseUrl}/fragments/ecs-service.yaml" + Parameters: + MicroServiceUniqueName: !Sub '${ProjectName}-paper-tracker' + CpuValue: !Ref CpuValue + MemoryAmount: !Ref MemoryAmount + MinTasksNumber: !Ref MinTasksNumber + MaxTasksNumber: !Ref MaxTasksNumber + HealthCheckPath: /actuator/health + HealthCheckTimeout: !Ref HealthCheckTimeout + HealthCheckInterval: !Ref HealthCheckInterval + HealthyThresholdCount: !Ref HealthyThresholdCount + UnhealthyThresholdCount: !Ref UnhealthyThresholdCount + AutoscalingStrategy: CUSTOM + AutoscalingThreshold: !Ref AutoscalingThreshold + AutoscalingCheckPeriod: !Ref AutoscalingCheckPeriod + EfsFilesystem: !Ref FargateEFSFileSystemID + MountEfs: !Ref MountEfs + WireTapLogActivation: !Ref WireTapLogActivation + AutoscalingDataPointN: !Ref AutoscalingDataPointN + AutoscalingDataPointM: !Ref AutoscalingDataPointM + AlarmSNSTopicArn: !Ref AlarmSNSTopicArn + JavaToolOptions: !Ref JavaToolOptions + ContainerImageURI: !Sub '${ContainerImageUri}' + ContainerEnvEntry1: !Sub 'AWS_REGIONCODE=${AWS::Region}' + ContainerEnvEntry2: !Sub 'PN_CRON_ANALYZER=${PnCronAnalyzer}' + ContainerEnvEntry3: !Sub 'WIRE_TAP_LOG=${WireTapLogActivation}' + ContainerEnvEntry4: !Sub 'PN_PAPERTRACKER_DAO_PAPERTRACKERDRYRUNOUTPUTSTABLE=${PaperTrackerDryRunOutputsTableName}' + ContainerEnvEntry5: !Sub 'PN_PAPERTRACKER_DAO_PAPERTRACKINGSERRORSTABLE=${PaperTrackingsErrorsTableName}' + ContainerEnvEntry6: !Sub 'PN_PAPERTRACKER_DAO_PAPERTRACKINGSTABLE=${PaperTrackingsTableName}' + ContainerEnvEntry7: !Sub 'PN_PAPERTRACKER_PAPERCHANNELBASEURL=http://${ApplicationLoadBalancerDomain}:8080' + ContainerEnvEntry9: !Sub 'PN_PAPERTRACKER_TOPICS_EXTERNALCHANNELTOPAPERTRACKERQUEUE=${ExternalChannelToPaperTrackerQueueName}' + ContainerEnvEntry10: !Sub 'PN_PAPERTRACKER_TOPICS_QUEUEOCRINPUTSURL=${PnOcrInputsQueueURL}' + ContainerEnvEntry11: !Sub 'PN_PAPERTRACKER_TOPICS_QUEUEOCRINPUTSREGION=${PnOcrInputsQueueRegion}' + ContainerEnvEntry12: !Sub 'PN_PAPERTRACKER_TOPICS_EXTERNALCHANNELOUTPUTSQUEUE=${ExternalChannelsOutputsQueueName}' + ContainerEnvEntry16: !Sub 'PN_PAPERTRACKER_SAFESTORAGEBASEURL=${SandboxSafeStorageBaseUrl}' + ContainerEnvEntry17: !Sub 'PN_PAPERTRACKER_SAFESTORAGECXID=${SafeStorageCxId}' + ContainerEnvEntry18: !Sub 'PN_PAPERTRACKER_TOPICS_PNOCROUTPUTSQUEUE=${PnOcrOutputsQueueName}' + ContainerEnvEntry19: !Sub 'PN_PAPERTRACKER_DATAVAULTBASEURL=${DataVaultBaseUrl}' + ContainerEnvEntry20: !Sub 'PN_PAPERTRACKER_DISABLEALLCONSUMERS=${DisableAllConsumers}' + ContainerEnvEntry21: !Sub 'PN_PAPERTRACKER_TOPICS_EXTERNALCHANNELTOPAPERCHANNELQUEUE=${ExternalChannels2PaperChannelQueueName}' + ContainerEnvEntry22: !Sub 'PN_PAPERTRACKER_TOPICS_EXTERNALCHANNELTOPAPERCHANNELDRYRUNQUEUE=${ExternalChannelsToPaperChannelDryrunQueueName}' + ContainerEnvEntry23: !Sub 'METRIC_FORMAT_TYPE=${MetricFormatType}' + ContainerEnvEntry24: !Sub 'PN_PAPERTRACKER_EVENTBUS_NAME=arn:aws:events:${AWS::Region}:${AWS::AccountId}:event-bus/${ProjectName}-CoreEventBus' + ApplicativeEnvFileChecksum: !Ref ApplicativeEnvFileChecksum + MappedPaths: '/paper-tracker/*,/paper-tracker-private/*' + ECSClusterName: !Ref ECSClusterName + Subnets: !Ref VpcEgressSubnetsIds + VpcId: !Ref VpcId + AlbSecurityGroup: !Ref AlbSecurityGroup + EcsDefaultSecurityGroup: !Ref EcsDefaultSecurityGroup + LoadBalancerListenerArn: !Ref ApplicationLoadBalancerListenerArn + LoadbalancerRulePriority: !Ref MicroserviceNumber + TaskRoleManagedPolicyArn: !Ref PaperTrackerMicroserviceTaskPolicy + EcsLogGroup: !Ref EcsLogGroup + LogAlarmStrategyV1: !Ref LogAlarmStrategy + CpuArchitecture: !Ref CpuArchitecture + + PnOcrOutputsQueuePolicy: + Type: AWS::SQS::QueuePolicy + Properties: + Queues: + - !Ref PnOcrOutputsQueueURL + PolicyDocument: + Statement: + - Sid: AllowCrossAccountSend + Effect: Allow + Principal: + AWS: !Sub "arn:aws:iam::${OcrAwsAccountId}:root" + Action: + - sqs:SendMessage + Resource: + - !Ref PnOcrOutputsQueueARN + Condition: + StringEquals: + aws:PrincipalArn: !Ref OcrAwsRoleARN + + PaperTrackerMicroserviceCloudWatchDashboard: + Type: AWS::CloudFormation::Stack + Properties: + TemplateURL: !Sub "${TemplateBucketBaseUrl}/fragments/microservice-cloudwatch-dashboard.yaml" + Parameters: + DashboardName: !Sub "${ProjectName}-paper-tracker" + AlarmArns: !Join + - ',' + - - !GetAtt PaperTrackerMicroservice.Outputs.FatalLogsMetricAlarmArn + DynamoDBTableNames: !Join + - ',' + - - !Ref PaperTrackingsTableName + - !Ref PaperTrackingsErrorsTableName + - !Ref PaperTrackerDryRunOutputsTableName + QueueArns: !Join + - ',' + - - !Ref ExternalChannelToPaperTrackerQueueARN + - !Ref PnOcrOutputsQueueARN + LogGroupsNames: !Join + - ',' + - - !Sub '/aws/ecs/${ProjectName}-paper-tracker' + + PaperTrackerMicroserviceTaskPolicy: + Type: AWS::IAM::ManagedPolicy + Properties: + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - dynamodb:GetItem + - dynamodb:PutItem + - dynamodb:UpdateItem + - dynamodb:Query + - dynamodb:BatchGetItem + Resource: + - !Sub "${PaperTrackingsTableArn}" + - !Sub "${PaperTrackerDryRunOutputsTableArn}" + - !Sub "${PaperTrackingsErrorsTableArn}" + - !Sub "${PaperTrackingsTableArn}/*" + - !Sub "${PaperTrackerDryRunOutputsTableArn}/*" + - !Sub "${PaperTrackingsErrorsTableArn}/*" + - Effect: Allow + Action: + - sqs:ChangeMessageVisibility + - sqs:ChangeMessageVisibilityBatch + - sqs:DeleteMessage + - sqs:DeleteMessageBatch + - sqs:GetQueueAttributes + - sqs:GetQueueUrl + - sqs:ReceiveMessage + - sqs:SendMessage + - sqs:SendMessageBatch + Resource: + - !Ref ExternalChannelToPaperTrackerQueueARN + - !Ref PnOcrOutputsQueueARN + - !Ref PnOcrInputsQueueARN + - !Ref ExternalChannelsOutputsQueueARN + - !Ref ExternalChannels2PaperChannelQueueARN + - !Sub 'arn:aws:sqs:${AWS::Region}:${AWS::AccountId}:${ExternalChannelsToPaperChannelDryrunQueueName}' + - Effect: Allow + Action: + - events:PutEvents + Resource: + - !Sub arn:aws:events:${AWS::Region}:${AWS::AccountId}:event-bus/${ProjectName}-CoreEventBus + + ########################################################################## + ### NotificationReworkEventsValidationLambda ### + ########################################################################## + NotificationReworkEventsValidationLambdaSecGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: !Sub '${NotificationReworkEventsValidationLambdaName}-sec-group' + VpcId: !Ref VpcId + + # Lambda function + NotificationReworkEventsValidationLambda: + Type: AWS::Lambda::Function + Properties: + FunctionName: !Ref NotificationReworkEventsValidationLambdaName + Runtime: nodejs22.x + Handler: "index.handler" + Code: + S3Bucket: !Ref MicroserviceBucketName + S3Key: !Sub "${MicroserviceBucketBaseKey}/functions_zip/notificationReworkEventsValidation.zip" + Role: !GetAtt NotificationReworkEventsValidationLambdaRole.Arn + Environment: + Variables: + REGION: !Sub ${AWS::Region} + NOTIFICATION_REWORK_TABLE: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ProjectName}-NotificationReworks + NOTIFICATION_REWORK_EVENTS_ERROR_TABLE: !Ref NotificationReworkEventsErrorTableName + INVALID_STATUS_CODE: !Ref NotificationReworkInvalidStatusCode + EXTERNAL_TO_PAPER_QUEUE_URL: !Ref ExternalChannels2PaperChannelQueueURL + TracingConfig: + Mode: Active + Layers: + - !Sub "arn:aws:lambda:${AWS::Region}:339249233099:layer:LambdaInsightsExtension:13" + Timeout: 10 + + # Lambda function role + NotificationReworkEventsValidationLambdaRole: + Type: AWS::IAM::Role + Properties: + RoleName: !Sub ${ProjectName}-NotificationReworkEventsValidationLambdaRole + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + + # Lambda function IAM policy + NotificationReworkEventsValidationLambdaPolicy: + Type: AWS::IAM::Policy + Properties: + PolicyName: !Sub ${ProjectName}-NotificationReworkEventsValidationLambdaPolicy + Roles: + - !Ref NotificationReworkEventsValidationLambdaRole + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - "dynamodb:updateItem" + - "dynamodb:putItem" + - "dynamodb:getItem" + - "dynamodb:query" + Resource: + - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/pn-NotificationReworks + - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/pn-NotificationReworks/*" + - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/pn-NotificationReworkEventsError + - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/pn-NotificationReworkEventsError/*" + - Effect: Allow + Action: + - sqs:ChangeMessageVisibility + - sqs:GetQueueAttributes + - sqs:GetQueueUrl + - sqs:SendMessage + Resource: !Ref ExternalChannels2PaperChannelQueueARN + - Effect: Allow + Action: + - sqs:ReceiveMessage + - sqs:ChangeMessageVisibility + - sqs:DeleteMessage + - sqs:GetQueueAttributes + - sqs:GetQueueUrl + Resource: !Ref ExternalChannelToReworkEventValidationQueueARN + + # CDC to NotificationReworkEventsValidationLambda + NotificationReworkEventsValidationLambdaSource: + Type: AWS::Lambda::EventSourceMapping + DependsOn: NotificationReworkEventsValidationLambdaPolicy + Properties: + BatchSize: !Sub ${NotificationReworkEventsValidationLambdaBatchSize} + EventSourceArn: !Ref ExternalChannelToReworkEventValidationQueueARN + FunctionName: !Ref NotificationReworkEventsValidationLambda + MaximumBatchingWindowInSeconds: 60 + Enabled: true + + NotificationReworkEventsValidationLambdaAlarms: + Type: AWS::CloudFormation::Stack + Properties: + TemplateURL: !Sub "${TemplateBucketBaseUrl}/fragments/lambda-alarms.yaml" + Parameters: + FunctionName: !Ref NotificationReworkEventsValidationLambda + AlarmSNSTopicArn: !Ref AlarmSNSTopicArn + + #### Alarm for custom metric Autoscaling + AlarmCustomAutoscaling: + Type: "AWS::CloudWatch::Alarm" + Properties: + AlarmName: !Sub "${ProjectName}-paper-tracker-autoscaling-custom" + AlarmDescription: "Trigger scaling based on custom metric" + DatapointsToAlarm: !Ref AutoscalingDataPointN + ComparisonOperator: "GreaterThanOrEqualToThreshold" + Metrics: + - Id: m1 + MetricStat: + Metric: + Dimensions: + - Name: QueueName + Value: !Ref "ExternalChannelToPaperTrackerQueueName" + Namespace: "AWS/SQS" + MetricName: "ApproximateNumberOfMessagesVisible" + Period: !Ref AutoscalingCheckPeriod + Stat: Average + ReturnData: False + - Id: m2 + MetricStat: + Metric: + Dimensions: + - Name: QueueName + Value: !Ref "PnOcrOutputsQueueName" + Namespace: "AWS/SQS" + MetricName: "ApproximateNumberOfMessagesVisible" + Period: !Ref AutoscalingCheckPeriod + Stat: Average + ReturnData: False + - Id: m3 + MetricStat: + Metric: + Dimensions: + - Name: TargetGroup + Value: !GetAtt PaperTrackerMicroservice.Outputs.TargetGroupName + Namespace: "AWS/ApplicationELB" + MetricName: "RequestCountPerTarget" + Period: !Ref AutoscalingCheckPeriod + Stat: Sum + ReturnData: False + - Id: m4 + MetricStat: + Metric: + Dimensions: + - Name: TargetGroup + Value: !GetAtt PaperTrackerMicroservice.Outputs.TargetGroupName + - Name: LoadBalancer + Value: !Ref ApplicationLoadBalancerMetricsDimensionName + Namespace: "AWS/ApplicationELB" + MetricName: "HealthyHostCount" + Period: !Ref AutoscalingCheckPeriod + Stat: Average + ReturnData: False + - Id: code + Expression: !Sub "IF(m4==0, 101, (100*(m1+m2)) / (${ConsumedQueueMessagePerMinute} * m4) )" + Label: !Sub "${ProjectName}-paper-tracker-queue-custom-metric" + ReturnData: False + - Id: http + Expression: !Sub "(100 * m3) / ${AutoscalingThreshold}" + Label: !Sub "${ProjectName}-paper-tracker-http-custom-metric" + ReturnData: False + - Id: e1 + Expression: "IF( code, + "ruleArn": , + "ruleName": , + "ingestionTime": , + "originalEvent": + } + - Id: !Sub '${ProjectName}-CoreEventBus-ExtCh2PaperChannel' + Arn: !Ref ExternalChannelToReworkEventValidationQueueARN + InputPath: $.detail + DeadLetterConfig: + Arn: !Sub arn:aws:sqs:${AWS::Region}:${AWS::AccountId}:pn-CoreEventBus-DLQ + + EventBusEnqueueRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:AssumeRole + Effect: Allow + Principal: + Service: events.amazonaws.com + Version: "2012-10-17" + Policies: + - PolicyName: !Sub ${ProjectName}-paper-tracker-eventBusRuleQueuePolicy + PolicyDocument: + Statement: + - Sid: putEvents + Action: + - sqs:ChangeMessageVisibility + - sqs:ChangeMessageVisibilityBatch + - sqs:GetQueueAttributes + - sqs:GetQueueUrl + - sqs:SendMessage + - sqs:SendMessageBatch + Effect: Allow + Resource: + - !Ref ExternalChannelToReworkEventValidationQueueARN + + ExternalChannelToReworkPolicy: + Type: AWS::SQS::QueuePolicy + Properties: + Queues: + - !Ref ExternalChannelToReworkEventValidationQueueURL + PolicyDocument: + Statement: + - Sid: "SendEventsToDLQ" + Effect: "Allow" + Principal: + Service: + - "events.amazonaws.com" + Action: + - "SQS:SendMessage" + Resource: + - !Ref ExternalChannelToReworkEventValidationQueueARN + + PaperTracker890ErrorsAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmName: !Sub "${ProjectName}-paper-tracker-890Errors-Alarm" + ComparisonOperator: GreaterThanOrEqualToThreshold + Threshold: !Ref 890ErrorThreshold + OKActions: + - !Ref AlarmSNSTopicArn + AlarmActions: + - !Ref AlarmSNSTopicArn + EvaluationPeriods: !Ref 890EvaluationPeriods + DatapointsToAlarm: !Ref 890DatapointsToAlarm + TreatMissingData: !Ref 890TreatMissingData + Metrics: + - Id: q1 + Expression: !Sub | + SELECT SUM(ERROR_COUNT) + FROM SCHEMA("paper-tracker-errors", errorCategory, productType) + WHERE ${890WhereClause} AND productType = '890' + ReturnData: true + Period: 600 + + PaperTrackerRISErrorsAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmName: !Sub "${ProjectName}-paper-tracker-RISErrors-Alarm" + ComparisonOperator: GreaterThanOrEqualToThreshold + Threshold: !Ref RISErrorThreshold + OKActions: + - !Ref AlarmSNSTopicArn + AlarmActions: + - !Ref AlarmSNSTopicArn + EvaluationPeriods: !Ref RISEvaluationPeriods + DatapointsToAlarm: !Ref RISDatapointsToAlarm + TreatMissingData: !Ref RISTreatMissingData + Metrics: + - Id: q1 + Expression: !Sub | + SELECT SUM(ERROR_COUNT) + FROM SCHEMA("paper-tracker-errors", errorCategory, productType) + WHERE ${RISWhereClause} AND productType = 'RIS' + ReturnData: true + Period: 600 + + PaperTrackerARErrorsAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmName: !Sub "${ProjectName}-paper-tracker-ARErrors-Alarm" + ComparisonOperator: GreaterThanOrEqualToThreshold + Threshold: !Ref ARErrorThreshold + OKActions: + - !Ref AlarmSNSTopicArn + AlarmActions: + - !Ref AlarmSNSTopicArn + EvaluationPeriods: !Ref AREvaluationPeriods + DatapointsToAlarm: !Ref ARDatapointsToAlarm + TreatMissingData: !Ref ARTreatMissingData + Metrics: + - Id: q1 + Expression: !Sub | + SELECT SUM(ERROR_COUNT) + FROM SCHEMA("paper-tracker-errors", errorCategory, productType) + WHERE ${ARWhereClause} AND productType = 'AR' + ReturnData: true + Period: 600 + + PaperTrackerRSErrorsAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmName: !Sub "${ProjectName}-paper-tracker-RSErrors-Alarm" + ComparisonOperator: GreaterThanOrEqualToThreshold + Threshold: !Ref RSErrorThreshold + OKActions: + - !Ref AlarmSNSTopicArn + AlarmActions: + - !Ref AlarmSNSTopicArn + EvaluationPeriods: !Ref RSEvaluationPeriods + DatapointsToAlarm: !Ref RSDatapointsToAlarm + TreatMissingData: !Ref RSTreatMissingData + Metrics: + - Id: q1 + Expression: !Sub | + SELECT SUM(ERROR_COUNT) + FROM SCHEMA("paper-tracker-errors", errorCategory, productType) + WHERE ${RSWhereClause} AND productType = 'RS' + ReturnData: true + Period: 600 + + PaperTrackerRIRErrorsAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmName: !Sub "${ProjectName}-paper-tracker-RIRErrors-Alarm" + ComparisonOperator: GreaterThanOrEqualToThreshold + Threshold: !Ref RIRErrorThreshold + OKActions: + - !Ref AlarmSNSTopicArn + AlarmActions: + - !Ref AlarmSNSTopicArn + EvaluationPeriods: !Ref RIREvaluationPeriods + DatapointsToAlarm: !Ref RIRDatapointsToAlarm + TreatMissingData: !Ref RIRTreatMissingData + Metrics: + - Id: q1 + Expression: !Sub | + SELECT SUM(ERROR_COUNT) + FROM SCHEMA("paper-tracker-errors", errorCategory, productType) + WHERE ${RIRWhereClause} AND productType = 'RIR' + ReturnData: true + Period: 600 diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/scripts/aws/cfn/storage.yml b/jdk_21_maven/cs/rest/pn-paper-tracker/scripts/aws/cfn/storage.yml new file mode 100644 index 000000000..2c15d7161 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/scripts/aws/cfn/storage.yml @@ -0,0 +1,251 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Some storage with input and output + +Parameters: + ProjectName: + Type: String + Description: Nome dell'ambiente destinazione + + # Unused but required by CD pipeline + MicroserviceNumber: + Type: Number + Description: An unique number that identify the microservice inside the ECS cluster. + + # Unused but required by CD pipeline + TemplateBucketBaseUrl: + Type: String + Description: URL da cui caricare i frammenti di template di infrastruttura + + Version: + Type: String + Description: 'keep track of used projects commitIds' + + CdcKinesisSourceStreamArn: + Type: String + Description: 'Where to send CDC' + + LogsKinesisSourceStreamArn: + Type: String + Description: 'Where to send Logs' + + LogRetention: + Type: Number + Default: 14 + + AlarmSNSTopicName: + Type: String + +Resources: + PnPaperTrackerLogGroup: + Type: AWS::CloudFormation::Stack + UpdateReplacePolicy: Retain + DeletionPolicy: Retain + Properties: + TemplateURL: !Sub "${TemplateBucketBaseUrl}/fragments/log-group.yaml" + Parameters: + LogGroupName: !Sub '${ProjectName}-paper-tracker' + LogGroupRetention: !Ref LogRetention + LogsKinesisSourceStreamArn: !Ref LogsKinesisSourceStreamArn + + NotificationReworkEventsValidationLambdaLogGroup: + Type: AWS::CloudFormation::Stack + UpdateReplacePolicy: Retain + DeletionPolicy: Retain + Properties: + TemplateURL: !Sub ${TemplateBucketBaseUrl}/fragments/log-group.yaml + Parameters: + LogGroupName: !Sub ${ProjectName}-NotificationReworkEventsValidationLambda + LogGroupRetention: !Ref LogRetention + LogGroupPrefix: /aws/lambda + + PaperTrackingsErrorsTable: + Type: AWS::DynamoDB::Table + UpdateReplacePolicy: Retain + DeletionPolicy: Retain + Properties: + TableName: !Sub '${ProjectName}-PaperTrackingsErrors' + AttributeDefinitions: + - AttributeName: "trackingId" + AttributeType: "S" + - AttributeName: "created" + AttributeType: "S" + KeySchema: + - AttributeName: "trackingId" + KeyType: "HASH" + - AttributeName: "created" + KeyType: "RANGE" + BillingMode: "PAY_PER_REQUEST" + PointInTimeRecoverySpecification: + PointInTimeRecoveryEnabled: true + StreamSpecification: + StreamViewType: NEW_IMAGE + KinesisStreamSpecification: + StreamArn: !Ref CdcKinesisSourceStreamArn + + PaperTrackerDryRunOutputsTable: + Type: AWS::DynamoDB::Table + UpdateReplacePolicy: Retain + DeletionPolicy: Retain + Properties: + TableName: !Sub '${ProjectName}-PaperTrackerDryRunOutputs' + AttributeDefinitions: + - AttributeName: "trackingId" + AttributeType: "S" + - AttributeName: "created" + AttributeType: "S" + KeySchema: + - AttributeName: "trackingId" + KeyType: "HASH" + - AttributeName: "created" + KeyType: "RANGE" + BillingMode: "PAY_PER_REQUEST" + PointInTimeRecoverySpecification: + PointInTimeRecoveryEnabled: true + + PaperTrackingsTable: + Type: AWS::DynamoDB::Table + UpdateReplacePolicy: Retain + DeletionPolicy: Retain + Properties: + TableName: !Sub '${ProjectName}-PaperTrackings' + AttributeDefinitions: + - AttributeName: "trackingId" + AttributeType: "S" + - AttributeName: "attemptId" + AttributeType: "S" + - AttributeName: "pcRetry" + AttributeType: "S" + KeySchema: + - AttributeName: "trackingId" + KeyType: "HASH" + GlobalSecondaryIndexes: + - IndexName: "attemptId-pcRetry-index" + KeySchema: + - AttributeName: "attemptId" + KeyType: "HASH" + - AttributeName: "pcRetry" + KeyType: "RANGE" + Projection: + ProjectionType: "ALL" + TimeToLiveSpecification: + AttributeName: "ttl" + Enabled: true + BillingMode: "PAY_PER_REQUEST" + PointInTimeRecoverySpecification: + PointInTimeRecoveryEnabled: true + StreamSpecification: + StreamViewType: NEW_IMAGE + KinesisStreamSpecification: + StreamArn: !Ref CdcKinesisSourceStreamArn + + NotificationReworkEventsErrorTable: + Type: AWS::DynamoDB::Table + UpdateReplacePolicy: Retain + DeletionPolicy: Retain + Properties: + TableName: !Sub '${ProjectName}-NotificationReworkEventsError' + AttributeDefinitions: + - AttributeName: "iun" + AttributeType: "S" + - AttributeName: "reworkId" + AttributeType: "S" + KeySchema: + - AttributeName: "iun" + KeyType: "HASH" + - AttributeName: "reworkId" + KeyType: "RANGE" + BillingMode: "PAY_PER_REQUEST" + PointInTimeRecoverySpecification: + PointInTimeRecoveryEnabled: true + + PnOcrOutputsQueue: + Type: AWS::CloudFormation::Stack + UpdateReplacePolicy: Retain + DeletionPolicy: Retain + Properties: + TemplateURL: !Sub "${TemplateBucketBaseUrl}/fragments/sqs-queue.yaml" + Parameters: + QueueName: !Sub '${ProjectName}-paper_tracker_ocr_outputs' + AlarmSNSTopicName: !Ref AlarmSNSTopicName + VisibilityTimeout: 60 + DelaySeconds: 1 + + ExternalChannelToPaperTrackerQueue: + Type: AWS::CloudFormation::Stack + UpdateReplacePolicy: Retain + DeletionPolicy: Retain + Properties: + TemplateURL: !Sub "${TemplateBucketBaseUrl}/fragments/sqs-queue.yaml" + Parameters: + QueueName: !Sub '${ProjectName}-external_channel_to_paper_tracker' + AlarmSNSTopicName: !Ref AlarmSNSTopicName + VisibilityTimeout: 60 + DelaySeconds: 1 + + ExternalChannelToReworkEventValidationQueue: + Type: AWS::CloudFormation::Stack + UpdateReplacePolicy: Retain + DeletionPolicy: Retain + Properties: + TemplateURL: !Sub "${TemplateBucketBaseUrl}/fragments/sqs-queue.yaml" + Parameters: + QueueName: !Sub '${ProjectName}-external_channel_to_rework_event_validation' + AlarmSNSTopicName: !Ref AlarmSNSTopicName + VisibilityTimeout: 1800 + DelaySeconds: 1 + +Outputs: + EcsLogGroup: + Value: !GetAtt PnPaperTrackerLogGroup.Outputs.LogGroupName + + # Lambda functions + NotificationReworkEventsValidationLambdaName: + Value: !Sub ${ProjectName}-NotificationReworkEventsValidationLambda + + PaperTrackingsErrorsTableName: + Description: Name of dynamodb table containing trackings errors + Value: !Ref PaperTrackingsErrorsTable + PaperTrackingsErrorsTableArn: + Description: ARN of dynamodb table containing trackings errors + Value: !Sub '${PaperTrackingsErrorsTable.Arn}' + + PaperTrackerDryRunOutputsTableName: + Description: Name of dynamodb table containing trackings outputs + Value: !Ref PaperTrackerDryRunOutputsTable + PaperTrackerDryRunOutputsTableArn: + Description: ARN of dynamodb table containing trackings outputs + Value: !Sub '${PaperTrackerDryRunOutputsTable.Arn}' + + PaperTrackingsTableName: + Description: Name of dynamodb table containing trackings + Value: !Ref PaperTrackingsTable + PaperTrackingsTableArn: + Description: ARN of dynamodb table containing trackings + Value: !Sub '${PaperTrackingsTable.Arn}' + + NotificationReworkEventsErrorTableName: + Description: Name of dynamodb table containing rework events errors + Value: !Ref NotificationReworkEventsErrorTable + + NotificationReworkEventsErrorTableArn: + Description: ARN of dynamodb table containing rework events errors + Value: !Sub '${NotificationReworkEventsErrorTable.Arn}' + + PnOcrOutputsQueueName: + Value: !GetAtt PnOcrOutputsQueue.Outputs.QueueName + PnOcrOutputsQueueARN: + Value: !GetAtt PnOcrOutputsQueue.Outputs.QueueARN + PnOcrOutputsQueueURL: + Value: !GetAtt PnOcrOutputsQueue.Outputs.QueueURL + + ExternalChannelToPaperTrackerQueueName: + Value: !GetAtt ExternalChannelToPaperTrackerQueue.Outputs.QueueName + ExternalChannelToPaperTrackerQueueARN: + Value: !GetAtt ExternalChannelToPaperTrackerQueue.Outputs.QueueARN + + ExternalChannelToReworkEventValidationQueueName: + Value: !GetAtt ExternalChannelToReworkEventValidationQueue.Outputs.QueueName + ExternalChannelToReworkEventValidationQueueARN: + Value: !GetAtt ExternalChannelToReworkEventValidationQueue.Outputs.QueueARN + ExternalChannelToReworkEventValidationQueueURL: + Value: !GetAtt ExternalChannelToReworkEventValidationQueue.Outputs.QueueURL \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/PaperTrackerApplication.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/PaperTrackerApplication.java new file mode 100644 index 000000000..decbc1a7c --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/PaperTrackerApplication.java @@ -0,0 +1,14 @@ +package it.pagopa.pn.papertracker; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class PaperTrackerApplication { + + + public static void main(String[] args) { + SpringApplication.run(PaperTrackerApplication.class, args); + } + +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/PnPaperTrackerConfigs.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/PnPaperTrackerConfigs.java new file mode 100644 index 000000000..3b8713efc --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/PnPaperTrackerConfigs.java @@ -0,0 +1,102 @@ +package it.pagopa.pn.papertracker.config; + +import it.pagopa.pn.commons.conf.SharedAutoConfiguration; +import it.pagopa.pn.papertracker.model.FileType; +import jakarta.annotation.PostConstruct; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +@Configuration +@ConfigurationProperties(prefix = "pn.paper-tracker") +@Data +@Import({SharedAutoConfiguration.class}) +@Slf4j +public class PnPaperTrackerConfigs { + + private Dao dao; + private String paperChannelBaseUrl; + private String dataVaultBaseUrl; + private String safeStorageBaseUrl; + public String safeStorageCxId; + private Duration paperTrackingsTtlDuration; + private Duration paperTrackingsErrorsTtlDuration; + private Topics topics; + private int maxPcRetryMock; + + private List enableOcrValidationFor = new ArrayList<>(); + private List enableOcrValidationForFile = new ArrayList<>(); + private List saveAndNotSendToDeliveryPush = new ArrayList<>(); + private List requiredAttachmentsRefinementStock890 = new ArrayList<>(); + private List sendOcrAttachmentsRefinementStock890 = new ArrayList<>(); + private List sendOcrAttachmentsFinalValidationStock890 = new ArrayList<>(); + private List sendOcrAttachmentsFinalValidation = new ArrayList<>(); + private List strictFinalValidationStock890; + private List strictDeliveryFailureCause; + private List internalEvents = new ArrayList<>(); + private List productsProcessingModes = new ArrayList<>(); + private List redriveEnabledDomains = new ArrayList<>(); + private List sendToConsolidatoreErrorCategories = new ArrayList<>(); + private String ocrFilterTemporal; + private List ocrFilterUnifiedDeliveryDriver = new ArrayList<>(); + + private Duration compiutaGiacenzaArDuration; + private boolean enableTruncatedDateForRefinementCheck; + private Duration refinementDuration; + + private EventBus eventBus; + + @Data + public static class Dao { + private String paperTrackingsErrorsTable; + private String paperTrackerDryRunOutputsTable; + private String paperTrackingsTable; + } + + @Data + public static class Topics { + // Consumer + Producer + private String externalChannelToPaperTrackerQueue; + // Consumer + private String externalChannelToPaperChannelQueue; + private String pnOcrOutputsQueue; + // Producer + private String queueOcrInputsUrl; + private String queueOcrInputsRegion; + private String externalChannelOutputsQueue; + private String externalChannelToPaperChannelDryRunQueue; + } + + @Data + public static class EventBus { + private String name; + private String detailType; + private String source; + } + + @PostConstruct + public void init() { + validateSendOcrAttachmentsFinalValidationStock890(); + + log.info("CONFIGURATIONS: {}", this); + } + + + public void validateSendOcrAttachmentsFinalValidationStock890() { + List expected = List.of("1970-01-01;"); + + if (!expected.equals(sendOcrAttachmentsFinalValidationStock890)) { + throw new IllegalStateException( + "Invalid configuration for sendOcrAttachmentsFinalValidationStock890. " + + "Expected exactly: " + expected + + ", but found: " + sendOcrAttachmentsFinalValidationStock890 + ); + } + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/TrackerConfigUtils.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/TrackerConfigUtils.java new file mode 100644 index 000000000..69a834561 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/TrackerConfigUtils.java @@ -0,0 +1,353 @@ +package it.pagopa.pn.papertracker.config; + +import it.pagopa.pn.papertracker.exception.ConfigNotFound; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.ProcessingMode; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.ProductType; +import it.pagopa.pn.papertracker.model.OcrStatusEnum; +import jakarta.annotation.PostConstruct; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.support.CronExpression; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.time.*; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class TrackerConfigUtils { + + private static final String SEPARATOR = ";"; + private static final String CONFIG_ERROR_CODE = "REQUIRED_CONFIG_NOT_FOUND"; + private static final String OCR_FILTER_DISABLED = "DISABLED"; + + + @Getter + public abstract static class ConfigWithDate { + + protected final LocalDate startConfigurationTime; + protected final List stringConfigs; + + protected ConfigWithDate(String configWithDate) { + var splitted = configWithDate.split(SEPARATOR); + + this.startConfigurationTime = LocalDate.parse(splitted[0]); + this.stringConfigs = List.copyOf( + Arrays.asList(Arrays.copyOfRange(splitted, 1, splitted.length)) + ); + } + + public abstract T getConfig(); + } + + public static final class ListStringConfig extends ConfigWithDate> { + + public ListStringConfig(String configWithDate) { + super(configWithDate); + } + + @Override + public List getConfig() { + return stringConfigs; + } + } + + public static final class BooleanConfig extends ConfigWithDate { + + public BooleanConfig(String configWithDate) { + super(configWithDate); + } + + @Override + public Boolean getConfig() { + if (stringConfigs.isEmpty()) + return Boolean.FALSE; + return Boolean.parseBoolean(stringConfigs.getFirst()); + } + } + + public static final class ActivationModeConfig + extends ConfigWithDate> { + + public ActivationModeConfig(String configWithDate) { + super(configWithDate); + } + + @Override + public Map getConfig() { + return stringConfigs.stream() + .map(pm -> pm.split(":")) + .collect(Collectors.toUnmodifiableMap( + pm -> ProductType.fromValue(pm[0]), + pm -> ProcessingMode.valueOf(pm[1]) + )); + } + } + + public static final class OcrActivationModeConfig + extends ConfigWithDate> { + + public OcrActivationModeConfig(String configWithDate) { + super(configWithDate); + } + + @Override + public Map getConfig() { + return stringConfigs.stream() + .map(pm -> pm.split(":")) + .collect(Collectors.toUnmodifiableMap( + pm -> ProductType.fromValue(pm[0]), + pm -> OcrStatusEnum.valueOf(pm[1]) + )); + } + } + + public record OcrFilterUnifiedDeliveryDriverConfigRecord(List drivers, boolean isDisabled) { + public OcrFilterUnifiedDeliveryDriverConfigRecord(List drivers) { + this(drivers, drivers.stream().anyMatch(OCR_FILTER_DISABLED::equalsIgnoreCase)); + } + } + + public static final class CronTemporalConfig { + + private CronExpression cronExpression; + private boolean isValidConfig; + private boolean isDisabled; + + public CronTemporalConfig(String config) { + initConfig(config); + } + + private void initConfig(String config) { + if (!StringUtils.hasText(config)) { + log.info("Cron configuration is empty or null"); + isValidConfig = false; + return; + } + if (OCR_FILTER_DISABLED.equalsIgnoreCase(config)) { + log.info("Cron configuration is set to DISABLED"); + isDisabled = true; + isValidConfig = true; + return; + } + try { + cronExpression = CronExpression.parse(config); + log.info("Parsed cron expression: {}", this.cronExpression); + isValidConfig = true; + isDisabled = false; + } catch (IllegalArgumentException e) { + log.error("Invalid cron expression: {}", config, e); + isValidConfig = false; + } + } + + public boolean isActive(Instant now) { + if (!isValidConfig || isDisabled || cronExpression == null) { + return false; + } + + ZoneId zoneId = ZoneId.of("Europe/Rome"); + + // Converto l'Instant nel fuso orario corretto + ZonedDateTime currentZdt = now.atZone(zoneId); + + // Tronco al secondo spaccato (i cron non gestiscono i millisecondi) + ZonedDateTime currentSecond = currentZdt.truncatedTo(ChronoUnit.SECONDS); + + // Calcolo la prossima esecuzione partendo da "un secondo fa" + ZonedDateTime nextExecution = cronExpression.next(currentSecond.minusSeconds(1)); + log.debug("Next execution time according to cron: {}", nextExecution); + + // Se la prossima esecuzione rispetto a un secondo fa è proprio adesso, + // significa che il pattern cron include il secondo corrente (è ATTIVO). + return currentSecond.equals(nextExecution); + } + } + + private final List requiredAttachmentsRefinementStock890Configs; + private final List sendOcrAttachmentsRefinementStock890Configs; + private final List sendOcrAttachmentsFinalValidationStock890Configs; + private final List sendOcrAttachmentsFinalValidationConfigs; + private final List strictFinalValidationStock890Config; + private final List strictDeliveryFailureCauseConfig; + private final List productsProcessingModesConfig; + private final List enableOcrValidationForConfig; + private final CronTemporalConfig ocrFilterTemporalConfig; + private final OcrFilterUnifiedDeliveryDriverConfigRecord ocrFilterUnifiedDeliveryDriverConfig; + private final PnPaperTrackerConfigs cfg; + + public TrackerConfigUtils(PnPaperTrackerConfigs cfg) { + this.requiredAttachmentsRefinementStock890Configs = buildListStringConfig(cfg.getRequiredAttachmentsRefinementStock890()); + this.sendOcrAttachmentsRefinementStock890Configs = buildListStringConfig(cfg.getSendOcrAttachmentsRefinementStock890()); + this.sendOcrAttachmentsFinalValidationConfigs = buildListStringConfig(cfg.getSendOcrAttachmentsFinalValidation()); + this.sendOcrAttachmentsFinalValidationStock890Configs = buildListStringConfig(cfg.getSendOcrAttachmentsFinalValidationStock890()); + this.strictFinalValidationStock890Config = buildListBooleanConfig(cfg.getStrictFinalValidationStock890()); + this.strictDeliveryFailureCauseConfig = buildListBooleanConfig(cfg.getStrictDeliveryFailureCause()); + this.productsProcessingModesConfig = buildListActivationModeConfig(cfg.getProductsProcessingModes()); + this.enableOcrValidationForConfig = buildListOcrActivationModeConfig(cfg.getEnableOcrValidationFor()); + this.ocrFilterTemporalConfig = buildOcrFilterTemporalConfig(cfg.getOcrFilterTemporal()); + this.ocrFilterUnifiedDeliveryDriverConfig = new OcrFilterUnifiedDeliveryDriverConfigRecord(cfg.getOcrFilterUnifiedDeliveryDriver()); + this.cfg = cfg; + } + + @PostConstruct + public void init() { + log.info("TrackerConfigUtils PostConstruct initialization started"); + checkIfOcrFilterTemporalIsValid(); + checkIfOcrFilterDriverIsValid(); + } + + private void checkIfOcrFilterTemporalIsValid() { + if (!ocrFilterTemporalConfig.isValidConfig) { + throw new ConfigNotFound( + CONFIG_ERROR_CODE, + "OcrFilterTemporal config not found"); + } + } + + private void checkIfOcrFilterDriverIsValid() { + if (ocrFilterUnifiedDeliveryDriverConfig.drivers().isEmpty()) { + throw new ConfigNotFound( + CONFIG_ERROR_CODE, + "OcrFilterUnifiedDeliveryDriver config not found"); + } + } + + public List buildListStringConfig(List configList) { + return Optional.ofNullable(configList) + .orElse(List.of()) + .stream() + .map(ListStringConfig::new) + .toList(); + } + + public List buildListBooleanConfig(List configList) { + return Optional.ofNullable(configList) + .orElse(List.of()) + .stream() + .map(BooleanConfig::new) + .toList(); + } + + public List buildListActivationModeConfig(List configList) { + return Optional.ofNullable(configList) + .orElse(List.of()) + .stream() + .map(ActivationModeConfig::new) + .toList(); + } + + public List buildListOcrActivationModeConfig(List configList) { + return Optional.ofNullable(configList) + .orElse(List.of()) + .stream() + .map(OcrActivationModeConfig::new) + .toList(); + } + + public CronTemporalConfig buildOcrFilterTemporalConfig(String config) { + return new CronTemporalConfig(config); + } + + public T getActualConfig( + List extends ConfigWithDate> configs, + LocalDate date, + String configName + ) { + return configs.stream() + .sorted(Comparator.comparing( + (ConfigWithDate c) -> c.getStartConfigurationTime() + ).reversed()) + .filter(c -> !date.isBefore(c.getStartConfigurationTime())) + .findFirst() + .map(ConfigWithDate::getConfig) + .orElseThrow(() -> new ConfigNotFound( + CONFIG_ERROR_CODE, + configName + " not found for date: " + date + )); + } + + public List getActualRequiredAttachmentsRefinementStock890(LocalDate date) { + return getActualConfig( + requiredAttachmentsRefinementStock890Configs, + date, + "RequiredAttachmentsRefinementStock890" + ); + } + + public List getActualSendOcrAttachmentsRefinementStock890(LocalDate date) { + return getActualConfig( + sendOcrAttachmentsRefinementStock890Configs, + date, + "SendOcrAttachmentsRefinementStock890" + ); + } + + public List getActualSendOcrAttachmentsFinalValidationStock890(LocalDate date) { + return getActualConfig( + sendOcrAttachmentsFinalValidationStock890Configs, + date, + "SendOcrAttachmentsFinalValidationStock890" + ); + } + + public List getActualSendOcrAttachmentsFinalValidation(LocalDate date) { + return getActualConfig( + sendOcrAttachmentsFinalValidationConfigs, + date, + "SendOcrAttachmentsFinalValidation" + ); + } + + public Boolean getActualStrictFinalValidationStock890(LocalDate date) { + return getActualConfig( + strictFinalValidationStock890Config, + date, + "StrictFinalValidationStock890" + ); + } + + public Boolean getActualStrictDeliveryFailureCause(LocalDate date) { + return getActualConfig( + strictDeliveryFailureCauseConfig, + date, + "StrictDeliveryFailureCause" + ); + } + + public Map getActualProductsProcessingModes(LocalDate date) { + return getActualConfig( + productsProcessingModesConfig, + date, + "ProductsProcessingModes" + ); + } + + public Map getActualEnableOcrValidationFor(LocalDate date) { + return getActualConfig( + enableOcrValidationForConfig, + date, + "EnableOcrValidationFor" + ); + } + + public boolean isOcrFilterTemporalActive(Instant now) { + return ocrFilterTemporalConfig.isActive(now); + } + + public boolean isOcrFilterTemporalDisabled() { + return ocrFilterTemporalConfig.isDisabled; + } + + public boolean isOcrFilterDriverActive(String unifiedDeliveryDriver) { + return ocrFilterUnifiedDeliveryDriverConfig.drivers().contains(unifiedDeliveryDriver); + } + + public boolean isOcrFilterDriverDisabled() { + return ocrFilterUnifiedDeliveryDriverConfig.isDisabled(); + } + +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/msclient/DataVaultClientConfigurator.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/msclient/DataVaultClientConfigurator.java new file mode 100644 index 000000000..244c417d9 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/msclient/DataVaultClientConfigurator.java @@ -0,0 +1,25 @@ +package it.pagopa.pn.papertracker.config.msclient; + +import it.pagopa.pn.commons.pnclients.CommonBaseClient; +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import it.pagopa.pn.papertracker.generated.openapi.msclient.pndatavault.ApiClient; +import it.pagopa.pn.papertracker.generated.openapi.msclient.pndatavault.api.PaperAddressesApi; +import org.jetbrains.annotations.NotNull; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class DataVaultClientConfigurator extends CommonBaseClient { + + @Bean + public PaperAddressesApi paperAddressesApi(PnPaperTrackerConfigs cfg) { + return new PaperAddressesApi(getNewApiClient(cfg)); + } + + @NotNull + private ApiClient getNewApiClient(PnPaperTrackerConfigs cfg) { + ApiClient newApiClient = new ApiClient(initWebClient(ApiClient.buildWebClientBuilder())); + newApiClient.setBasePath(cfg.getDataVaultBaseUrl()); + return newApiClient; + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/msclient/PaperChannelClientConfigurator.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/msclient/PaperChannelClientConfigurator.java new file mode 100644 index 000000000..29a8cf397 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/msclient/PaperChannelClientConfigurator.java @@ -0,0 +1,25 @@ +package it.pagopa.pn.papertracker.config.msclient; + +import it.pagopa.pn.commons.pnclients.CommonBaseClient; +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.ApiClient; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.api.PcRetryApi; +import org.jetbrains.annotations.NotNull; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class PaperChannelClientConfigurator extends CommonBaseClient { + + @Bean + public PcRetryApi pcRetryApi(PnPaperTrackerConfigs cfg) { + return new PcRetryApi(getNewApiClient(cfg)); + } + + @NotNull + private ApiClient getNewApiClient(PnPaperTrackerConfigs cfg) { + ApiClient newApiClient = new ApiClient( initWebClient(ApiClient.buildWebClientBuilder()) ); + newApiClient.setBasePath( cfg.getPaperChannelBaseUrl() ); + return newApiClient; + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/msclient/SafeStorageClientConfigurator.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/msclient/SafeStorageClientConfigurator.java new file mode 100644 index 000000000..0f0d9ae08 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/msclient/SafeStorageClientConfigurator.java @@ -0,0 +1,25 @@ +package it.pagopa.pn.papertracker.config.msclient; + +import it.pagopa.pn.commons.pnclients.CommonBaseClient; +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import it.pagopa.pn.papertracker.generated.openapi.msclient.safestorage.ApiClient; +import it.pagopa.pn.papertracker.generated.openapi.msclient.safestorage.api.FileDownloadApi; +import org.jetbrains.annotations.NotNull; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SafeStorageClientConfigurator extends CommonBaseClient { + + @Bean + public FileDownloadApi getSafeStorageClient (PnPaperTrackerConfigs paperTrackerConfigs){ + return new FileDownloadApi(getNewApiClient(paperTrackerConfigs)); + } + + @NotNull + private ApiClient getNewApiClient(PnPaperTrackerConfigs cfg) { + ApiClient newApiClient = new ApiClient( initWebClient(ApiClient.buildWebClientBuilder()) ); + newApiClient.setBasePath( cfg.getSafeStorageBaseUrl() ); + return newApiClient; + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/AwsConfigsActivation.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/AwsConfigsActivation.java new file mode 100644 index 000000000..a15913a38 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/AwsConfigsActivation.java @@ -0,0 +1,10 @@ +package it.pagopa.pn.papertracker.config.springbootcfg; + +import it.pagopa.pn.commons.configs.aws.AwsConfigs; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties("aws") +public class AwsConfigsActivation extends AwsConfigs { +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/AwsServicesClientsConfigActivation.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/AwsServicesClientsConfigActivation.java new file mode 100644 index 000000000..78e90d87e --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/AwsServicesClientsConfigActivation.java @@ -0,0 +1,14 @@ +package it.pagopa.pn.papertracker.config.springbootcfg; + +import it.pagopa.pn.commons.configs.RuntimeMode; +import it.pagopa.pn.commons.configs.aws.AwsConfigs; +import it.pagopa.pn.commons.configs.aws.AwsServicesClientsConfig; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AwsServicesClientsConfigActivation extends AwsServicesClientsConfig { + + public AwsServicesClientsConfigActivation(AwsConfigs props) { + super(props, RuntimeMode.PROD); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/ClientAspectLoggingActivation.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/ClientAspectLoggingActivation.java new file mode 100644 index 000000000..24abf5810 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/ClientAspectLoggingActivation.java @@ -0,0 +1,8 @@ +package it.pagopa.pn.papertracker.config.springbootcfg; + +import it.pagopa.pn.commons.utils.ClientAspectLogging; +import org.springframework.stereotype.Component; + +@Component +public class ClientAspectLoggingActivation extends ClientAspectLogging { +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/MDCTraceIdWebFilter.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/MDCTraceIdWebFilter.java new file mode 100644 index 000000000..0005b7041 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/MDCTraceIdWebFilter.java @@ -0,0 +1,10 @@ +package it.pagopa.pn.papertracker.config.springbootcfg; + + +import it.pagopa.pn.commons.log.MDCWebFilter; +import org.springframework.stereotype.Component; + +@Component +public class MDCTraceIdWebFilter extends MDCWebFilter { + +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/PnErrorWebExceptionHandlerActivation.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/PnErrorWebExceptionHandlerActivation.java new file mode 100644 index 000000000..43fc25ecb --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/PnErrorWebExceptionHandlerActivation.java @@ -0,0 +1,17 @@ +package it.pagopa.pn.papertracker.config.springbootcfg; + + +import it.pagopa.pn.commons.exceptions.ExceptionHelper; +import it.pagopa.pn.commons.exceptions.PnErrorWebExceptionHandler; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.annotation.Order; + +@Configuration +@Order(-2) +@Import(ExceptionHelper.class) +public class PnErrorWebExceptionHandlerActivation extends PnErrorWebExceptionHandler { + public PnErrorWebExceptionHandlerActivation(ExceptionHelper exceptionHelper) { + super(exceptionHelper); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/PnResponseEntityExceptionHandlerActivation.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/PnResponseEntityExceptionHandlerActivation.java new file mode 100644 index 000000000..bcf522f9f --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/PnResponseEntityExceptionHandlerActivation.java @@ -0,0 +1,14 @@ +package it.pagopa.pn.papertracker.config.springbootcfg; + + +import it.pagopa.pn.commons.exceptions.ExceptionHelper; +import it.pagopa.pn.commons.exceptions.PnResponseEntityExceptionHandler; +import org.springframework.context.annotation.Import; + +@org.springframework.web.bind.annotation.ControllerAdvice +@Import(ExceptionHelper.class) +public class PnResponseEntityExceptionHandlerActivation extends PnResponseEntityExceptionHandler { + public PnResponseEntityExceptionHandlerActivation(ExceptionHelper exceptionHelper) { + super(exceptionHelper); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/ServerAspectLoggingActivation.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/ServerAspectLoggingActivation.java new file mode 100644 index 000000000..e8d1d18fd --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/ServerAspectLoggingActivation.java @@ -0,0 +1,8 @@ +package it.pagopa.pn.papertracker.config.springbootcfg; + +import it.pagopa.pn.commons.utils.ServerAspectLogging; +import org.springframework.stereotype.Component; + +@Component +public class ServerAspectLoggingActivation extends ServerAspectLogging { +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/SpringAnalyzerActivation.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/SpringAnalyzerActivation.java new file mode 100644 index 000000000..a08bf288f --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/SpringAnalyzerActivation.java @@ -0,0 +1,24 @@ +package it.pagopa.pn.papertracker.config.springbootcfg; + +import io.micrometer.core.instrument.MeterRegistry; +import it.pagopa.pn.commons.utils.metrics.SpringAnalyzer; +import it.pagopa.pn.commons.utils.metrics.cloudwatch.CloudWatchMetricHandler; +import lombok.CustomLog; +import org.springframework.boot.actuate.metrics.MetricsEndpoint; +import org.springframework.context.annotation.Import; +import org.springframework.stereotype.Component; + +@Component +@CustomLog +@Import(CloudWatchMetricHandler.class) +public class SpringAnalyzerActivation extends SpringAnalyzer { + + MeterRegistry meterRegistry; + CloudWatchMetricHandler cloudWatchMetricHandler; + + public SpringAnalyzerActivation(CloudWatchMetricHandler cloudWatchMetricHandler, MetricsEndpoint metricsEndpoint, MeterRegistry meterRegistry) { + super(cloudWatchMetricHandler, metricsEndpoint); + this.cloudWatchMetricHandler = cloudWatchMetricHandler; + this.meterRegistry = meterRegistry; + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/SpringAnalyzerClientConfig.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/SpringAnalyzerClientConfig.java new file mode 100644 index 000000000..41b9f9f60 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/SpringAnalyzerClientConfig.java @@ -0,0 +1,7 @@ +package it.pagopa.pn.papertracker.config.springbootcfg; + +import it.pagopa.pn.commons.configs.SpringAnalyzerConfiguration; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SpringAnalyzerClientConfig extends SpringAnalyzerConfiguration {} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/SqsTracingConfig.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/SqsTracingConfig.java new file mode 100644 index 000000000..9304d4101 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/config/springbootcfg/SqsTracingConfig.java @@ -0,0 +1,35 @@ +package it.pagopa.pn.papertracker.config.springbootcfg; + +import io.awspring.cloud.sqs.config.SqsListenerConfigurer; +import io.awspring.cloud.sqs.config.SqsMessageListenerContainerFactory; +import io.awspring.cloud.sqs.operations.SqsTemplate; +import io.micrometer.observation.ObservationRegistry; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.services.sqs.SqsAsyncClient; + +@Configuration +public class SqsTracingConfig { + private static final String DEFAULT_LISTENER_CONTAINER_FACTORY_BEAN_NAME = "tracedMessagesListenerContainerFactory"; + + @Bean + SqsListenerConfigurer configurer() { + return registrar -> registrar.setDefaultListenerContainerFactoryBeanName(DEFAULT_LISTENER_CONTAINER_FACTORY_BEAN_NAME); + } + + @Bean + SqsTemplate sqsTemplate(SqsAsyncClient sqsAsyncClient, ObservationRegistry observationRegistry) { + return SqsTemplate.builder() + .sqsAsyncClient(sqsAsyncClient) + .configure(options -> options.observationRegistry(observationRegistry)) + .build(); + } + + @Bean(name = DEFAULT_LISTENER_CONTAINER_FACTORY_BEAN_NAME) + SqsMessageListenerContainerFactory factory(SqsAsyncClient sqsAsyncClient, ObservationRegistry observationRegistry) { + return SqsMessageListenerContainerFactory.builder() + .sqsAsyncClient(sqsAsyncClient) + .configure(options -> options.observationRegistry(observationRegistry)) + .build(); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/ConfigNotFound.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/ConfigNotFound.java new file mode 100644 index 000000000..a6884ff29 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/ConfigNotFound.java @@ -0,0 +1,11 @@ +package it.pagopa.pn.papertracker.exception; + +import it.pagopa.pn.commons.exceptions.PnInternalException; +import org.springframework.http.HttpStatus; + +public class ConfigNotFound extends PnInternalException { + + public ConfigNotFound(String errorCode, String message) { + super(message, HttpStatus.INTERNAL_SERVER_ERROR.value(), errorCode); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PaperTrackerException.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PaperTrackerException.java new file mode 100644 index 000000000..b67bc8b3e --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PaperTrackerException.java @@ -0,0 +1,12 @@ +package it.pagopa.pn.papertracker.exception; + +public class PaperTrackerException extends RuntimeException { + + public PaperTrackerException(String message, Throwable throwable) { + super(message, throwable); + } + + public PaperTrackerException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PaperTrackerExceptionHandler.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PaperTrackerExceptionHandler.java new file mode 100644 index 000000000..f8b40d58a --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PaperTrackerExceptionHandler.java @@ -0,0 +1,73 @@ +package it.pagopa.pn.papertracker.exception; + +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.*; +import it.pagopa.pn.papertracker.service.PaperTrackerErrorService; +import it.pagopa.pn.papertracker.service.PaperTrackerTrackingService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +import java.util.Objects; + +import static it.pagopa.pn.papertracker.utils.TrackerUtility.setDematValidationTimestamp; +import static it.pagopa.pn.papertracker.utils.TrackerUtility.setNewStatus; + +@Component +@RequiredArgsConstructor +@Slf4j +public class PaperTrackerExceptionHandler { + + private final PaperTrackerErrorService paperTrackerErrorService; + private final PaperTrackerTrackingService paperTrackerTrackingService; + + /** + * Intercetta le eccezioni di tipo {@link PnPaperTrackerValidationException}. + * + * Per ogni eccezione intercettata, effettua una putItem sulla tabella PaperTrackingsError + */ + public Mono handleInternalException(final PnPaperTrackerValidationException ex, Long messageReceiveCount) { + return handleError(ex.getError(), messageReceiveCount, ex); + } + + public Mono handleRetryError(PaperTrackingsErrors paperTrackingsErrors) { + return handleError(paperTrackingsErrors, null, null); + } + + public Mono handleError(PaperTrackingsErrors paperTrackingsErrors, Long messageReceiveCount, PnPaperTrackerValidationException ex) { + boolean isStatusCodeError = Objects.nonNull(paperTrackingsErrors.getDetails()) && ErrorCause.VALUES_NOT_FOUND.equals(paperTrackingsErrors.getDetails().getCause()); + + if (isStatusCodeError) { + if (messageReceiveCount < 5) { + // Retry SQS + return Mono.error(ex); + } + log.error("Max retries reached for status code error, inserting error and updating PaperTrackings state to KO for trackingId: {}", paperTrackingsErrors.getTrackingId()); + } + + // Salva l'errore + return insertErrorAndUpdateTrackingsEntity(paperTrackingsErrors); + } + + private Mono insertErrorAndUpdateTrackingsEntity(PaperTrackingsErrors paperTrackingsErrors){ + return paperTrackerErrorService.insertPaperTrackingsError(paperTrackingsErrors) + .filter(errors -> paperTrackingsErrors.getType().equals(ErrorType.ERROR)) + .doOnDiscard(PaperTrackingsErrors.class, errors -> log.info("Skipped updating PaperTrackings entity for error with type Warning")) + .map(unused -> { + PaperTrackings paperTrackingsToUpdate = new PaperTrackings(); + String statusCode = paperTrackingsErrors.getEventThrow(); + setNewStatus(paperTrackingsToUpdate,statusCode, BusinessState.KO, PaperTrackingsState.KO); + setDematValidationTimestampIfNeeded(paperTrackingsErrors, paperTrackingsToUpdate,statusCode); + return paperTrackingsToUpdate; + }) + .flatMap(paperTrackingsToUpdate -> paperTrackerTrackingService.updatePaperTrackingsStatus(paperTrackingsErrors.getTrackingId(), paperTrackingsToUpdate)) + .doOnError(throwable -> log.error("Error inserting entity into PaperTrackingsErrors: {}", throwable.getMessage(), throwable)); + } + + private static void setDematValidationTimestampIfNeeded(PaperTrackingsErrors paperTrackingsErrors, PaperTrackings paperTrackingsToUpdate, String statusCode) { + if (Objects.nonNull(paperTrackingsErrors.getDetails().getCause()) && paperTrackingsErrors.getDetails().getCause().equals(ErrorCause.OCR_KO)) { + setDematValidationTimestamp(paperTrackingsToUpdate, statusCode); + } + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerBadRequestException.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerBadRequestException.java new file mode 100644 index 000000000..eb8bc5657 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerBadRequestException.java @@ -0,0 +1,13 @@ +package it.pagopa.pn.papertracker.exception; + +import it.pagopa.pn.commons.exceptions.PnRuntimeException; +import org.springframework.http.HttpStatus; + +public class PnPaperTrackerBadRequestException extends PnRuntimeException { + + private static final String DEFAULT_ERROR_CODE = "PN_PAPER_TRACKER_BAD_REQUEST"; + + public PnPaperTrackerBadRequestException(String errorCode, String message) { + super(message, DEFAULT_ERROR_CODE, HttpStatus.BAD_REQUEST.value(), errorCode, null, message); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerConflictException.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerConflictException.java new file mode 100644 index 000000000..1af7a3a81 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerConflictException.java @@ -0,0 +1,11 @@ +package it.pagopa.pn.papertracker.exception; + +import it.pagopa.pn.commons.exceptions.PnInternalException; +import org.springframework.http.HttpStatus; + +public class PnPaperTrackerConflictException extends PnInternalException { + + public PnPaperTrackerConflictException(String errorCode, String message) { + super(message, HttpStatus.CONFLICT.value(), errorCode); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerNotFoundException.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerNotFoundException.java new file mode 100644 index 000000000..ad6eb1f6e --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerNotFoundException.java @@ -0,0 +1,11 @@ +package it.pagopa.pn.papertracker.exception; + +import it.pagopa.pn.commons.exceptions.PnInternalException; +import org.springframework.http.HttpStatus; + +public class PnPaperTrackerNotFoundException extends PnInternalException { + + public PnPaperTrackerNotFoundException(String errorCode, String message) { + super(message, HttpStatus.NOT_FOUND.value(), errorCode); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerValidationException.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerValidationException.java new file mode 100644 index 000000000..def4fbb0b --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerValidationException.java @@ -0,0 +1,15 @@ +package it.pagopa.pn.papertracker.exception; + +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackingsErrors; +import lombok.Getter; + +@Getter +public class PnPaperTrackerValidationException extends RuntimeException { + + private final PaperTrackingsErrors error; + + public PnPaperTrackerValidationException(String message, PaperTrackingsErrors error) { + super(message); + this.error = error; + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperProgressStatusEventMapper.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperProgressStatusEventMapper.java new file mode 100644 index 000000000..57a8fdbaa --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperProgressStatusEventMapper.java @@ -0,0 +1,97 @@ +package it.pagopa.pn.papertracker.mapper; + +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.AttachmentDetails; +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.PaperProgressStatusEvent; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.*; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor(access = AccessLevel.NONE) +public class PaperProgressStatusEventMapper { + + /** + * Rimappa l'oggetto PaperProgressStatusEvent nell'entity PaperTrackings in modo da effettuare l'upsert + * + * @param paperProgressStatusEvent evento da rimappare + * @param anonymizedDiscoveredAddressId l'id dell'indirizzo anonimizzato + * @return PaperTrackings contenente il nuovo evento + */ + public static PaperTrackings toPaperTrackings(String reworkId, PaperProgressStatusEvent paperProgressStatusEvent, + String anonymizedDiscoveredAddressId, + String eventId, boolean dryRunEnabled, + boolean isFinalDemat, boolean isP000event, + boolean isInternalEvent, boolean isRecag012event) { + Instant now = Instant.now(); + PaperTrackings paperTrackings = new PaperTrackings(); + Event event = new Event(); + event.setId(eventId); + event.setIun(paperProgressStatusEvent.getIun()); + if (!CollectionUtils.isEmpty(paperProgressStatusEvent.getAttachments())) { + event.setAttachments(paperProgressStatusEvent.getAttachments().stream() + .map(PaperProgressStatusEventMapper::buildAttachmentFromAttachmentDetail) + .toList()); + }else{ + event.setAttachments(new ArrayList<>()); + } + + if(isFinalDemat) { + PaperStatus paperStatus = new PaperStatus(); + paperStatus.setFinalDematFound(true); + paperTrackings.setPaperStatus(paperStatus); + } + + if (isP000event) { + PaperStatus paperStatus = new PaperStatus(); + paperStatus.setPaperDeliveryTimestamp(paperProgressStatusEvent.getStatusDateTime().toInstant()); + paperTrackings.setPaperStatus(paperStatus); + } + + if (!isInternalEvent){ + event.setRequestTimestamp(paperProgressStatusEvent.getClientRequestTimeStamp().toInstant()); + } + + if(isRecag012event){ + ValidationFlow validationFlow = new ValidationFlow(); + validationFlow.setRecag012StatusTimestamp(now); + paperTrackings.setValidationFlow(validationFlow); + } + + event.setStatusCode(paperProgressStatusEvent.getStatusCode()); + event.setStatusTimestamp(paperProgressStatusEvent.getStatusDateTime().toInstant()); + + event.setDeliveryFailureCause(paperProgressStatusEvent.getDeliveryFailureCause()); + event.setRegisteredLetterCode(paperProgressStatusEvent.getRegisteredLetterCode()); + event.setNotificationReworkId(reworkId); + if(StringUtils.hasText(paperProgressStatusEvent.getProductType())) { + event.setProductType(paperProgressStatusEvent.getProductType()); + } + event.setAnonymizedDiscoveredAddressId(anonymizedDiscoveredAddressId); + event.setDryRun(dryRunEnabled); + event.setStatusDescription(paperProgressStatusEvent.getStatusDescription()); + event.setCreatedAt(now); + event.setPrinter(paperProgressStatusEvent.getPrinter()); + event.setDu(paperProgressStatusEvent.getDu()); + + paperTrackings.setEvents(List.of(event)); + return paperTrackings; + } + + public static Attachment buildAttachmentFromAttachmentDetail(AttachmentDetails attachmentDetails) { + Attachment attachment = new Attachment(); + attachment.setDate(attachmentDetails.getDate().toInstant()); + attachment.setId(attachmentDetails.getId()); + attachment.setDocumentType(attachmentDetails.getDocumentType()); + attachment.setUri(attachmentDetails.getUri()); + attachment.setSha256(attachmentDetails.getSha256()); + attachment.setSourceType(attachmentDetails.getSourceType()); + attachment.setOriginType(attachmentDetails.getOriginType()); + return attachment; + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackerDryRunOutputsMapper.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackerDryRunOutputsMapper.java new file mode 100644 index 000000000..6b3cd82be --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackerDryRunOutputsMapper.java @@ -0,0 +1,56 @@ +package it.pagopa.pn.papertracker.mapper; + +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.AttachmentDetails; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.SendEvent; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.Attachment; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackerDryRunOutputs; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import org.springframework.util.CollectionUtils; + +import java.time.Instant; +import java.util.Objects; + +@RequiredArgsConstructor(access = AccessLevel.NONE) +public class PaperTrackerDryRunOutputsMapper { + + public static PaperTrackerDryRunOutputs dtoToEntity(SendEvent event, String anonymizedDiscoveredAddressId) { + PaperTrackerDryRunOutputs dryRunOutput = new PaperTrackerDryRunOutputs(); + dryRunOutput.setTrackingId(event.getRequestId()); + dryRunOutput.setCreated(Instant.now()); + dryRunOutput.setRegisteredLetterCode(event.getRegisteredLetterCode()); + dryRunOutput.setStatusDetail(event.getStatusDetail()); + dryRunOutput.setStatusDescription(event.getStatusDescription()); + dryRunOutput.setDeliveryFailureCause(event.getDeliveryFailureCause()); + dryRunOutput.setAnonymizedDiscoveredAddressId(anonymizedDiscoveredAddressId); + + if(Objects.nonNull(event.getStatusCode())) { + dryRunOutput.setStatusCode(event.getStatusCode().name()); + } + + if(Objects.nonNull(event.getStatusDateTime())) { + dryRunOutput.setStatusDateTime(event.getStatusDateTime().toString()); + } + + if(!CollectionUtils.isEmpty(event.getAttachments())) { + dryRunOutput.setAttachments((event.getAttachments().stream() + .map(PaperTrackerDryRunOutputsMapper::buildAttachmentForExternalChannelOutputEvent) + .toList())); + } + + if(Objects.nonNull(event.getClientRequestTimeStamp())) { + dryRunOutput.setClientRequestTimestamp(event.getClientRequestTimeStamp().toString()); + } + + return dryRunOutput; + } + + private static Attachment buildAttachmentForExternalChannelOutputEvent(AttachmentDetails attachment) { + Attachment attachmentEntity = new Attachment(); + attachmentEntity.setId(attachment.getId()); + attachmentEntity.setDate(attachment.getDate().toInstant()); + attachmentEntity.setDocumentType(attachment.getDocumentType()); + attachmentEntity.setUri(attachment.getUrl()); + return attachmentEntity; + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackerMapStructMapper.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackerMapStructMapper.java new file mode 100644 index 000000000..4be8c1048 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackerMapStructMapper.java @@ -0,0 +1,75 @@ +package it.pagopa.pn.papertracker.mapper; + +import it.pagopa.pn.papertracker.generated.openapi.server.v1.dto.PaperTrackerOutput; +import it.pagopa.pn.papertracker.generated.openapi.server.v1.dto.Tracking; +import it.pagopa.pn.papertracker.generated.openapi.server.v1.dto.TrackingError; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.BusinessState; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.ErrorType; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackerDryRunOutputs; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackings; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackingsErrors; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackingsState; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import java.time.Instant; + +@Mapper( + componentModel = "spring", + unmappedTargetPolicy = ReportingPolicy.ERROR, + unmappedSourcePolicy = ReportingPolicy.IGNORE +) +public interface PaperTrackerMapStructMapper { + + @Mapping(target = "notificationReworkTimestamp", source = "notificationReworkRequestTimestamp") + Tracking toTracking(PaperTrackings paperTrackings); + + TrackingError toTrackingError(PaperTrackingsErrors paperTrackingsErrors); + + PaperTrackerOutput toDtoPaperTrackerOutput(PaperTrackerDryRunOutputs entity); + + default String map(Instant instant) { + return instant != null ? instant.toString() : null; + } + + default Tracking.StateEnum map(PaperTrackingsState state) { + if (state == null) { + return null; + } + + return switch (state) { + case AWAITING_FINAL_STATUS_CODE, AWAITING_REFINEMENT -> Tracking.StateEnum.AWAITING_REFINEMENT; + case AWAITING_REWORK_EVENTS -> Tracking.StateEnum.AWAITING_REWORK_EVENTS; + case AWAITING_OCR -> Tracking.StateEnum.AWAITING_OCR; + case DONE -> Tracking.StateEnum.DONE; + case KO -> Tracking.StateEnum.KO; + }; + } + + default Tracking.BusinessStateEnum map(BusinessState businessState) { + if (businessState == null) { + return null; + } + + return switch (businessState) { + case AWAITING_FINAL_STATUS_CODE -> Tracking.BusinessStateEnum.AWAITING_FINAL_STATUS_CODE; + case AWAITING_REFINEMENT_OCR -> Tracking.BusinessStateEnum.AWAITING_OCR; + case AWAITING_REWORK_EVENTS -> Tracking.BusinessStateEnum.AWAITING_REWORK_EVENTS; + case AWAITING_OCR -> Tracking.BusinessStateEnum.AWAITING_REFINEMENT_OCR; + case DONE -> Tracking.BusinessStateEnum.DONE; + case KO -> Tracking.BusinessStateEnum.KO; + }; + } + + default TrackingError.TypeEnum map(ErrorType errorType) { + if (errorType == null) { + return null; + } + + return switch (errorType) { + case ERROR -> TrackingError.TypeEnum.ERROR; + case INFO, WARNING -> TrackingError.TypeEnum.WARNING; + }; + } +} + diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackingsErrorsMapper.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackingsErrorsMapper.java new file mode 100644 index 000000000..a7f24d6f3 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackingsErrorsMapper.java @@ -0,0 +1,44 @@ +package it.pagopa.pn.papertracker.mapper; + +import it.pagopa.pn.papertracker.generated.openapi.server.v1.dto.TrackingError; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.*; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; + +import java.time.Instant; +import java.util.Map; + +@RequiredArgsConstructor(access = AccessLevel.NONE) +public class PaperTrackingsErrorsMapper { + + public static PaperTrackingsErrors buildPaperTrackingsError(PaperTrackings paperTrackings, + String statusCode, + ErrorCategory errorCategory, + ErrorCause errorCause, + String errorMessage, + Map additionalDetails, + FlowThrow flowThrow, + ErrorType errorType, + String eventIdThrow) { + return PaperTrackingsErrors.builder() + .trackingId(paperTrackings.getTrackingId()) + .created(Instant.now()) + .errorCategory(errorCategory) + .details(ErrorDetails.builder() + .cause(errorCause) + .message(errorMessage) + .additionalDetails(additionalDetails) + .build()) + .flowThrow(flowThrow) + .eventThrow(statusCode) + .productType(paperTrackings.getProductType()) + .unifiedDeliveryDriver(paperTrackings.getUnifiedDeliveryDriver()) + .registeredLetterCode(paperTrackings.getPaperStatus() != null + ? paperTrackings.getPaperStatus().getRegisteredLetterCode() + : null) + .type(errorType) + .eventIdThrow(eventIdThrow) + .build(); + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackingsMapper.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackingsMapper.java new file mode 100644 index 000000000..4f2e6f699 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackingsMapper.java @@ -0,0 +1,105 @@ +package it.pagopa.pn.papertracker.mapper; + +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import it.pagopa.pn.papertracker.config.TrackerConfigUtils; +import it.pagopa.pn.papertracker.generated.openapi.server.v1.dto.Tracking; +import it.pagopa.pn.papertracker.generated.openapi.server.v1.dto.TrackingCreationRequest; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.*; +import it.pagopa.pn.papertracker.model.FileType; +import it.pagopa.pn.papertracker.model.OcrStatusEnum; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.List; +import java.util.Objects; + +@Slf4j +@RequiredArgsConstructor(access = AccessLevel.NONE) +public class PaperTrackingsMapper { + + public static PaperTrackings toPaperTrackings(TrackingCreationRequest trackingCreationRequest, + TrackerConfigUtils trackerConfigUtils, + PnPaperTrackerConfigs pnPaperTrackerConfigs, + Instant now, + String xOriginClientId) { + ProductType productType = ProductType.fromValue(trackingCreationRequest.getProductType()); + LocalDate localDate = LocalDate.now(ZoneId.of("Europe/Rome")); + PaperTrackings paperTrackings = new PaperTrackings(); + paperTrackings.setTrackingId(String.join(".", trackingCreationRequest.getAttemptId(), trackingCreationRequest.getPcRetry())); + paperTrackings.setUnifiedDeliveryDriver(trackingCreationRequest.getUnifiedDeliveryDriver()); + paperTrackings.setProductType(trackingCreationRequest.getProductType()); + paperTrackings.setState(PaperTrackingsState.AWAITING_REFINEMENT); + paperTrackings.setBusinessState(BusinessState.AWAITING_FINAL_STATUS_CODE); + paperTrackings.setAttemptId(trackingCreationRequest.getAttemptId()); + paperTrackings.setPcRetry(trackingCreationRequest.getPcRetry()); + paperTrackings.setCreatedAt(now); + paperTrackings.setProcessingMode(trackerConfigUtils.getActualProductsProcessingModes(localDate).get(productType)); + paperTrackings.setAnalogRequestClientId(xOriginClientId); + PaperStatus paperStatus = new PaperStatus(); + paperTrackings.setPaperStatus(paperStatus); + ValidationFlow validationFlow = new ValidationFlow(); + validationFlow.setOcrRequests(List.of()); + paperTrackings.setValidationFlow(validationFlow); + ValidationConfig validationConfig = new ValidationConfig(); + validationConfig.setOcrFilterTemporal(pnPaperTrackerConfigs.getOcrFilterTemporal()); + validationConfig.setOcrFilterUnifiedDeliveryDriver(pnPaperTrackerConfigs.getOcrFilterUnifiedDeliveryDriver()); + validationConfig.setOcrEnabled(evaluateIfOcrIsEnabled(trackerConfigUtils, productType, now, trackingCreationRequest, localDate)); + validationConfig.setOcrFileTypes(pnPaperTrackerConfigs.getEnableOcrValidationForFile().stream().map(FileType::getValue).toList()); + validationConfig.setRequiredAttachmentsRefinementStock890(trackerConfigUtils.getActualRequiredAttachmentsRefinementStock890(localDate)); + validationConfig.setSendOcrAttachmentsRefinementStock890(trackerConfigUtils.getActualSendOcrAttachmentsRefinementStock890(localDate)); + validationConfig.setSendOcrAttachmentsFinalValidation(trackerConfigUtils.getActualSendOcrAttachmentsFinalValidation(localDate)); + validationConfig.setSendOcrAttachmentsFinalValidationStock890(trackerConfigUtils.getActualSendOcrAttachmentsFinalValidationStock890(localDate)); + validationConfig.setStrictFinalValidationStock890(trackerConfigUtils.getActualStrictFinalValidationStock890(localDate)); + validationConfig.setStrictDeliveryFailureCause(trackerConfigUtils.getActualStrictDeliveryFailureCause(localDate)); + paperTrackings.setValidationConfig(validationConfig); + return paperTrackings; + } + + private static OcrStatusEnum evaluateIfOcrIsEnabled(TrackerConfigUtils trackerConfigUtils, + ProductType productType, + Instant now, + TrackingCreationRequest trackingCreationRequest, + LocalDate localDate) { + + OcrStatusEnum ocrStatusEnum = trackerConfigUtils.getActualEnableOcrValidationFor(localDate).get(productType); + + if (Objects.isNull(ocrStatusEnum) || OcrStatusEnum.DISABLED.equals(ocrStatusEnum)) { + return OcrStatusEnum.DISABLED; + } + + if (OcrStatusEnum.DRY.equals(ocrStatusEnum)) { + return OcrStatusEnum.DRY; + } + + // Siamo in modalità OCR:RUN + + boolean isTemporalFilterConfigured = !trackerConfigUtils.isOcrFilterTemporalDisabled(); + boolean isUnifiedDeliveryDriverFilterConfigured = !trackerConfigUtils.isOcrFilterDriverDisabled(); + + // Caso 1: entrambi i filtri disattivi + if (!isTemporalFilterConfigured && !isUnifiedDeliveryDriverFilterConfigured) { + log.info("Both OCR filters are disabled. OCR will be enabled in RUN mode"); + return OcrStatusEnum.RUN; + } + + boolean isTemporalFilterActive = isTemporalFilterConfigured && trackerConfigUtils.isOcrFilterTemporalActive(now); + boolean isUnifiedDeliveryDriverFilterActive = isUnifiedDeliveryDriverFilterConfigured && + trackerConfigUtils.isOcrFilterDriverActive(trackingCreationRequest.getUnifiedDeliveryDriver()); + + log.info("Temporal filter is active: {}", isTemporalFilterActive); + log.info("UnifiedDeliveryDriver {} is filtered: {}", trackingCreationRequest.getUnifiedDeliveryDriver(), isUnifiedDeliveryDriverFilterActive); + + // Caso 2: entrambi i filtri sono configurati + if (isTemporalFilterConfigured && isUnifiedDeliveryDriverFilterConfigured) { + return (isTemporalFilterActive && isUnifiedDeliveryDriverFilterActive) ? OcrStatusEnum.RUN : OcrStatusEnum.DRY; + } + + // Caso 3: solo uno dei due filtri è configurato + return (isTemporalFilterActive || isUnifiedDeliveryDriverFilterActive) ? OcrStatusEnum.RUN : OcrStatusEnum.DRY; + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/SendEventMapper.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/SendEventMapper.java new file mode 100644 index 000000000..0c6df62f3 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/SendEventMapper.java @@ -0,0 +1,153 @@ +package it.pagopa.pn.papertracker.mapper; + +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.DiscoveredAddress; +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.PaperProgressStatusEvent; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.AnalogAddress; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.AttachmentDetails; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.SendEvent; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.StatusCodeEnum; +import it.pagopa.pn.papertracker.generated.openapi.msclient.pndatavault.model.PaperAddress; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.Attachment; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.Event; +import it.pagopa.pn.papertracker.model.EventStatusCodeEnum; +import it.pagopa.pn.papertracker.utils.TrackerUtility; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.CollectionUtils; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@RequiredArgsConstructor(access = AccessLevel.NONE) +@Slf4j +public class SendEventMapper { + + /** + * Crea l'evento dalla classe PaperProgressStatusEvent e lo inserisce dentro SendEvent per l'invio a delivery-push. + * Se si tratta di un evento intermedio senza allegati ed è un final demat, non viene inviato a delivery-push. + * + * @param paperProgressStatusEvent evento intermedio ricevuto + * @return SendEvent contenente l'evento rimappato da inviare a delivery-push + */ + public static Flux createSendEventsFromPaperProgressStatusEvent(PaperProgressStatusEvent paperProgressStatusEvent) { + boolean isFinalDemat = TrackerUtility.checkIfIsFinalDemat(paperProgressStatusEvent.getStatusCode()); + return Mono.just(paperProgressStatusEvent) + .flatMapMany(progressEvent -> { + if (!CollectionUtils.isEmpty(progressEvent.getAttachments())) { + return Flux.fromIterable(progressEvent.getAttachments().stream() + .map(attachmentDetails -> buildSendEvent(progressEvent, attachmentDetails)) + .collect(Collectors.toList())); + } else if(!isFinalDemat) + return Flux.just(buildSendEvent(progressEvent, null)); + + log.info("Event with statusCode {} is a final demat without attachments, skipping sending to delivery-push", progressEvent.getStatusCode()); + return Flux.empty(); + } + ); + } + + public static SendEvent buildSendEvent(PaperProgressStatusEvent progressEvent, it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.AttachmentDetails attachmentDetails) { + SendEvent.SendEventBuilder builder = SendEvent.builder() + .requestId(progressEvent.getRequestId()) + .statusCode(StatusCodeEnum.valueOf(EventStatusCodeEnum.valueOf(progressEvent.getStatusCode()).getStatus().name())) + .statusDetail(progressEvent.getStatusCode()) + .statusDescription(progressEvent.getProductType() + " - " + progressEvent.getStatusCode() + " - " + progressEvent.getStatusDescription()) + .deliveryFailureCause(progressEvent.getDeliveryFailureCause()) + .registeredLetterCode(progressEvent.getRegisteredLetterCode()) + .statusDateTime(progressEvent.getStatusDateTime()) + .clientRequestTimeStamp(progressEvent.getClientRequestTimeStamp()); + + if(Objects.nonNull(progressEvent.getDiscoveredAddress())){ + builder.discoveredAddress(toAnalogAddress(progressEvent.getDiscoveredAddress())); + } + if(Objects.nonNull(attachmentDetails)){ + builder.attachments(buildPaperAttachmentDetailsFromExternalChannels(List.of(attachmentDetails))); + } + + return builder.build(); + } + + public static Flux createSendEventsFromEventEntity(String requestId, Event event, StatusCodeEnum statusCode, String statusDetail, OffsetDateTime statusDateTime) { + return Mono.just(event) + .flatMapMany(progressEvent -> { + if (!CollectionUtils.isEmpty(progressEvent.getAttachments())) { + return Flux.fromIterable(progressEvent.getAttachments().stream() + .map(attachmentDetails -> toSendEvent(requestId, event, statusCode, statusDetail, statusDateTime, attachmentDetails)) + .collect(Collectors.toList())); + } + return Flux.just(toSendEvent(requestId, event, statusCode, statusDetail, statusDateTime, null)); + } + ); + } + + public static SendEvent toSendEvent(String requestId, Event event, StatusCodeEnum statusCode, String statusDetail, OffsetDateTime statusDateTime, Attachment attachmentDetails) { + SendEvent.SendEventBuilder builder = SendEvent.builder() + .requestId(requestId) + .statusCode(statusCode) + .statusDetail(statusDetail) + .statusDescription(event.getProductType() + " - " + statusDetail + " - " + event.getStatusDescription()) + .deliveryFailureCause(event.getDeliveryFailureCause()) + .registeredLetterCode(event.getRegisteredLetterCode()) + .statusDateTime(statusDateTime) + .clientRequestTimeStamp(event.getRequestTimestamp().atOffset(ZoneOffset.UTC)); + + if(Objects.nonNull(attachmentDetails)){ + builder.attachments(buildAttachmentDetails(event.getAttachments())); + } + + return builder.build(); + } + + private static List buildAttachmentDetails(List attachments) { + return attachments.stream().map(attachment -> AttachmentDetails.builder() + .documentType(attachment.getDocumentType()) + .date(attachment.getDate().atOffset(ZoneOffset.UTC)) + .id(attachment.getId()) + .url(attachment.getUri()) + .build()).toList(); + } + + private static List buildPaperAttachmentDetailsFromExternalChannels(List attachments) { + return attachments.stream().map(attachment -> AttachmentDetails.builder() + .documentType(attachment.getDocumentType()) + .date(attachment.getDate()) + .id(attachment.getId()) + .url(attachment.getUri()) + .build()).toList(); + } + + public static AnalogAddress toAnalogAddress(DiscoveredAddress discoveredAddress) { + return AnalogAddress.builder() + .address(discoveredAddress.getAddress()) + .addressRow2(discoveredAddress.getAddressRow2()) + .cap(discoveredAddress.getCap()) + .pr(discoveredAddress.getPr()) + .city(discoveredAddress.getCity()) + .city2(discoveredAddress.getCity2()) + .country(discoveredAddress.getCountry()) + .fullname(discoveredAddress.getName()) + .nameRow2(discoveredAddress.getNameRow2()) + .build(); + } + + public static AnalogAddress toAnalogAddress(PaperAddress paperAddress) { + return AnalogAddress.builder() + .address(paperAddress.getAddress()) + .addressRow2(paperAddress.getAddressRow2()) + .cap(paperAddress.getCap()) + .pr(paperAddress.getPr()) + .city(paperAddress.getCity()) + .city2(paperAddress.getCity2()) + .country(paperAddress.getCountry()) + .fullname(paperAddress.getName()) + .nameRow2(paperAddress.getNameRow2()) + .build(); + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/PaperTrackerDryRunOutputsDAO.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/PaperTrackerDryRunOutputsDAO.java new file mode 100644 index 000000000..4b91c4e08 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/PaperTrackerDryRunOutputsDAO.java @@ -0,0 +1,12 @@ +package it.pagopa.pn.papertracker.middleware.dao; + +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackerDryRunOutputs; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public interface PaperTrackerDryRunOutputsDAO { + + Flux retrieveOutputEvents(String requestId); + + Mono insertOutputEvent(PaperTrackerDryRunOutputs paperTrackerDryRunOutputs); +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/PaperTrackingsDAO.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/PaperTrackingsDAO.java new file mode 100644 index 000000000..7b1fe580f --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/PaperTrackingsDAO.java @@ -0,0 +1,25 @@ +package it.pagopa.pn.papertracker.middleware.dao; + +import com.sngular.apigenerator.asyncapi.business_model.model.event.Data; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackings; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; + +public interface PaperTrackingsDAO { + + Mono putIfAbsent(PaperTrackings entity); + + Mono retrieveEntityByTrackingId(String trackingId); + + Flux retrieveEntityByAttemptId(String attemptId, String pcRetry); + + Mono updateItem(String requestId, PaperTrackings paperTrackings); + + Mono updateItemForRework(String trackingId, PaperTrackings paperTrackings); + + Mono updateOcrRequests(Integer ocrRequestIndex, String trackingId, Data.ValidationStatus validationStatus); + + Flux retrieveAllByTrackingIds(List trackingIds); +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/PaperTrackingsErrorsDAO.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/PaperTrackingsErrorsDAO.java new file mode 100644 index 000000000..cf5bd2b19 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/PaperTrackingsErrorsDAO.java @@ -0,0 +1,13 @@ +package it.pagopa.pn.papertracker.middleware.dao; + +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackingsErrors; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public interface PaperTrackingsErrorsDAO { + + Mono insertError(PaperTrackingsErrors entity); + + Flux retrieveErrors(String requestId); + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/AdditionalDetailsConverter.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/AdditionalDetailsConverter.java new file mode 100644 index 000000000..ca73d2b7a --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/AdditionalDetailsConverter.java @@ -0,0 +1,95 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo; + +import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter; +import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType; +import software.amazon.awssdk.enhanced.dynamodb.EnhancedType; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +public class AdditionalDetailsConverter implements AttributeConverter> { + + @Override + public AttributeValue transformFrom(Map value) { + if (Objects.isNull(value)) { + return AttributeValue.builder().nul(true).build(); + } + return AttributeValue.builder().m(value.entrySet().stream() + .collect(java.util.stream.Collectors.toMap( + Map.Entry::getKey, + e -> toAttr(e.getValue()) + ))).build(); + } + + @Override + public Map transformTo(AttributeValue attributeValue) { + if (Boolean.TRUE.equals(attributeValue.nul())) { + return null; + } + if (Objects.isNull(attributeValue.m())) { + return Map.of(); + } + return attributeValue.m().entrySet().stream().collect(java.util.stream.Collectors.toMap( + Map.Entry::getKey, + e -> Optional.ofNullable(fromAttr(e.getValue())).orElse("") + )); + } + + @Override + public AttributeValueType attributeValueType() { + return AttributeValueType.M; + } + + @Override + public EnhancedType> type() { + return EnhancedType.mapOf(EnhancedType.of(String.class), EnhancedType.of(Object.class)); + } + + /* -------- conversioni -------- */ + + private AttributeValue toAttr(Object v) { + return switch (v) { + case String s -> AttributeValue.builder().s(s).build(); + case Number n -> AttributeValue.builder().n(n.toString()).build(); + case Boolean b -> AttributeValue.builder().bool(b).build(); + case Map, ?> m -> AttributeValue.builder().m(m.entrySet().stream() + .collect(Collectors.toMap(e -> e.getKey().toString(), e -> toAttr(e.getValue())))) + .build(); + case List> l -> AttributeValue.builder().l(l.stream().map(this::toAttr).toList()).build(); + case null -> AttributeValue.builder().nul(true).build(); + default -> throw new IllegalArgumentException("Unsupported type in additionalDetails: " + v.getClass()); + }; + } + + private Object fromAttr(AttributeValue attributeValue) { + switch (attributeValue.type()) { + case S -> { + if (Objects.nonNull(attributeValue.s())) return attributeValue.s(); + } + case N -> { + if (Objects.nonNull(attributeValue.n())) return attributeValue.n(); + } + case BOOL -> { + if (Objects.nonNull(attributeValue.bool())) return attributeValue.bool(); + } + case M -> { + if (Objects.nonNull(attributeValue.m())) { + return attributeValue.m().entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> Optional.ofNullable(fromAttr(e.getValue())).orElse(""))); + } + } + case L -> { + if (Objects.nonNull(attributeValue.l())) { + return attributeValue.l().stream() + .map(this::fromAttr) + .toList(); + } + } + } + return null; + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/BaseDao.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/BaseDao.java new file mode 100644 index 000000000..e9da3a8f6 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/BaseDao.java @@ -0,0 +1,184 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo; + +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import software.amazon.awssdk.enhanced.dynamodb.*; +import software.amazon.awssdk.enhanced.dynamodb.model.*; +import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest; +import software.amazon.awssdk.services.dynamodb.model.UpdateItemResponse; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +@Slf4j +public class BaseDao { + private final DynamoDbAsyncTable tableAsync; + private final Class tClass; + private final DynamoDbAsyncClient dynamoDbAsyncClient; + private final DynamoDbEnhancedAsyncClient dynamoDbEnhancedAsyncClient; + + protected BaseDao( + DynamoDbEnhancedAsyncClient dynamoDbEnhancedAsyncClient, + DynamoDbAsyncClient dynamoDbAsyncClient, + String tableName, + Class tClass + ) { + this.tableAsync = dynamoDbEnhancedAsyncClient.table(tableName, TableSchema.fromBean(tClass)); + this.tClass = tClass; + this.dynamoDbAsyncClient = dynamoDbAsyncClient; + this.dynamoDbEnhancedAsyncClient = dynamoDbEnhancedAsyncClient; + } + + protected Mono putItem(T entity) { + return Mono.fromFuture(this.tableAsync.putItem(entity)) + .thenReturn(entity); + } + + public Flux retrieveByRequestId(String pk) { + log.info("retrieve Items from [{}] table, for requestId={}", tableAsync.tableName(), pk); + return Flux.from(tableAsync.query(r -> r.queryConditional( + QueryConditional.keyEqualTo(Key.builder().partitionValue(pk).build())))) + .flatMap(page -> Flux.fromIterable(page.items())) + .doOnError(e -> log.error("Error retrieving items for requestId {}: {}", pk, e.getMessage())); + } + + public Mono getByKey(Key key) { + return Mono.fromFuture(tableAsync.getItem(key)); + } + + public Flux queryByIndex(Key build, String indexName) { + QueryEnhancedRequest request = QueryEnhancedRequest.builder() + .queryConditional(QueryConditional.keyEqualTo(build)) + .build(); + + return Mono.from(tableAsync.index(indexName).query(request)) + .map(Page::items) + .flatMapMany(Flux::fromIterable) + .doOnError(e -> log.error("Error querying index {}: {}", indexName, e.getMessage())); + } + + public Flux retrieveFromIndex(String indexName, QueryConditional queryConditional) { + return Mono.from(tableAsync.index(indexName) + .query(QueryEnhancedRequest.builder().queryConditional(queryConditional).build())) + .map(Page::items) + .flatMapMany(Flux::fromIterable); + } + + public Mono putIfAbsent(String expression, T entity) { + + Expression conditionExpressionPut = Expression.builder() + .expression(expression) + .build(); + + PutItemEnhancedRequest request = PutItemEnhancedRequest.builder(tClass) + .item(entity) + .conditionExpression(conditionExpressionPut) + .build(); + + return Mono.fromFuture(tableAsync.putItem(request)) + .thenReturn(entity); + } + + public Mono updateIfExists(Map key, String updateExpression, Map expressionValues, Map expressionNames, String conditionExpression) { + UpdateItemRequest updateItemRequest = UpdateItemRequest.builder() + .tableName(tableAsync.tableName()) + .key(key) + .updateExpression(updateExpression) + .expressionAttributeValues(expressionValues) + .expressionAttributeNames(expressionNames) + .conditionExpression(conditionExpression) + .returnValues("ALL_NEW") + .build(); + + return Mono.fromFuture(dynamoDbAsyncClient.updateItem(updateItemRequest)); + } + + protected Flux findAllByKeys(List partitionKeys) { + ReadBatch.Builder builder = ReadBatch.builder(tClass) + .mappedTableResource(this.tableAsync); + + partitionKeys.forEach(partitionKey -> + builder.addGetItem(Key.builder().partitionValue(partitionKey).build()) + ); + + BatchGetItemEnhancedRequest request = BatchGetItemEnhancedRequest.builder() + .readBatches(builder.build()) + .build(); + + return Flux.from(dynamoDbEnhancedAsyncClient.batchGetItem(request) + .map(result -> result.resultsForTable(tableAsync))) + .flatMap(Flux::fromIterable); + } + + /** + * Costruisce updateExpression per DynamoDB in base al tipo di attributo fornito. + * Per gli attributi di tipo lista, genera un'espressione per aggiungere alla lista utilizzando {@code list_append} e {@code if_not_exists}. + * Per gli attributi di tipo mappa (oggetto annidato), genera espressioni per aggiornare ciascuna coppia chiave-valore interna. + * Per altri tipi di attributo, genera una semplice espressione di assegnazione. + * + * @param key La chiave dell'attributo da aggiornare + * @param value Il valore dell'attributo da aggiornare + * @param counter Un contatore atomico utilizzato per generare indici univoci per i parametri + * @param names Mappa dei nomi degli attributi per l'espressione DynamoDB + * @param values Mappa dei valori degli attributi per l'espressione DynamoDB + * @return Una lista di espressioni di aggiornamento da utilizzare in una richiesta di update su DynamoDB + */ + protected List buildUpdateExpressions(String key, AttributeValue value, AtomicInteger counter, Map names, Map values) { + List expressions = new ArrayList<>(); + + if(key.equalsIgnoreCase("validationConfig")){ + return expressions; + } + + if (isListAttributeType(value)) { + int idx = counter.getAndIncrement(); + names.put("#k" + idx, key); + values.put(":v" + idx, AttributeValue.builder().l(value.l()).build()); + values.put(":empty" + idx, AttributeValue.builder().l(Collections.emptyList()).build()); + expressions.add("#k" + idx + " = list_append(if_not_exists(" + "#k" + idx + ", " + ":empty" + idx + "), " + ":v" + idx + ")"); + + } else if (isInnerObjectAttributeType(value)) { + value.m().forEach((innerKey, innerVal) -> { + int idx = counter.getAndIncrement(); + if(key.equalsIgnoreCase("paperStatus")){ + names.put("#paperStatus", key); + names.put("#paperStatus_" + idx + "_inner", innerKey); + values.put(":v" + idx, innerVal); + expressions.add("#paperStatus.#paperStatus_" + idx + "_inner" + " = " + ":v" + idx); + } + else if(key.equalsIgnoreCase("validationFlow")){ + names.put("#validationFlow" , key); + names.put("#validationFlow_" + idx + "_inner", innerKey); + values.put(":v" + idx, innerVal); + expressions.add("#validationFlow.#validationFlow_" + idx + "_inner" + " = " + ":v" + idx); + } + else { + names.put("#k" + idx, key); + names.put("#k" + idx + "_inner", innerKey); + values.put(":v" + idx, innerVal); + expressions.add("#k" + idx + "." + "#k" + idx + "_inner" + " = " + ":v" + idx); + } + }); + } else { + if(!key.equalsIgnoreCase("paperStatus") && !key.equalsIgnoreCase("validationFlow")){ + int idx = counter.getAndIncrement(); + names.put("#k" + idx, key); + values.put(":v" + idx, value); + expressions.add("#k" + idx + " = " + ":v" + idx); + } + } + return expressions; + } + + private static boolean isInnerObjectAttributeType(AttributeValue value) { + return value.m() != null && !value.m().isEmpty(); + } + + private boolean isListAttributeType(AttributeValue value) { + return value.l() != null && !value.l().isEmpty(); + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/PaperTrackerDryRunOutputsDAOImpl.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/PaperTrackerDryRunOutputsDAOImpl.java new file mode 100644 index 000000000..d4544c929 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/PaperTrackerDryRunOutputsDAOImpl.java @@ -0,0 +1,35 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo; + +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import it.pagopa.pn.papertracker.middleware.dao.PaperTrackerDryRunOutputsDAO; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackerDryRunOutputs; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient; +import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; + +@Slf4j +@Component +public class PaperTrackerDryRunOutputsDAOImpl extends BaseDao implements PaperTrackerDryRunOutputsDAO { + + public PaperTrackerDryRunOutputsDAOImpl(DynamoDbEnhancedAsyncClient dynamoDbEnhancedClient, PnPaperTrackerConfigs cfg, DynamoDbAsyncClient dynamoDbAsyncClient) { + super(dynamoDbEnhancedClient, + dynamoDbAsyncClient, + cfg.getDao().getPaperTrackerDryRunOutputsTable(), + PaperTrackerDryRunOutputs.class + ); + } + + @Override + public Flux retrieveOutputEvents(String requestId) { + log.info("retrieving output events for requestId={}", requestId); + return retrieveByRequestId(requestId); + } + + @Override + public Mono insertOutputEvent(PaperTrackerDryRunOutputs paperTrackerDryRunOutputs) { + return putItem(paperTrackerDryRunOutputs); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/PaperTrackingsDAOImpl.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/PaperTrackingsDAOImpl.java new file mode 100644 index 000000000..41bf94309 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/PaperTrackingsDAOImpl.java @@ -0,0 +1,206 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo; + +import com.sngular.apigenerator.asyncapi.business_model.model.event.Data; +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import it.pagopa.pn.papertracker.exception.PnPaperTrackerConflictException; +import it.pagopa.pn.papertracker.exception.PnPaperTrackerNotFoundException; +import it.pagopa.pn.papertracker.middleware.dao.PaperTrackingsDAO; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient; +import software.amazon.awssdk.enhanced.dynamodb.Key; +import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException; + +import java.time.Instant; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +import static it.pagopa.pn.commons.abstractions.impl.AbstractDynamoKeyValueStore.ATTRIBUTE_NOT_EXISTS; +import static it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackings.ATTEMPT_ID_PCRETRY_INDEX; + +@Component +@Slf4j +public class PaperTrackingsDAOImpl extends BaseDao implements PaperTrackingsDAO { + + private final String ERROR_CODE_PAPER_TRACKER_DUPLICATED_ITEM = "PN_PAPER_TRACKER_DUPLICATED_ITEM"; + private final String ERROR_CODE_PAPER_TRACKER_NOT_FOUND = "PN_PAPER_TRACKER_NOT_FOUND"; + + private final PnPaperTrackerConfigs pnPaperTrackerConfigs; + + public PaperTrackingsDAOImpl(DynamoDbEnhancedAsyncClient dynamoDbEnhancedClient, PnPaperTrackerConfigs cfg, DynamoDbAsyncClient dynamoDbAsyncClient) { + super(dynamoDbEnhancedClient, + dynamoDbAsyncClient, + cfg.getDao().getPaperTrackingsTable(), + PaperTrackings.class + ); + this.pnPaperTrackerConfigs = cfg; + } + + @Override + public Mono retrieveEntityByTrackingId(String trackingId) { + return getByKey(Key.builder().partitionValue(trackingId).build()); + } + + @Override + public Flux retrieveEntityByAttemptId(String attemptId, String pcRetry) { + Key.Builder key = Key.builder().partitionValue(attemptId); + + if(StringUtils.hasText(pcRetry)){ + key.sortValue(pcRetry); + } + + return queryByIndex(key.build(), ATTEMPT_ID_PCRETRY_INDEX); + } + + @Override + public Mono putIfAbsent(PaperTrackings entity) { + String expression = String.format("%s(%s)", ATTRIBUTE_NOT_EXISTS, PaperTrackings.COL_TRACKING_ID); + entity.setTtl(Instant.now().plus(pnPaperTrackerConfigs.getPaperTrackingsTtlDuration()).getEpochSecond()); + + return putIfAbsent(expression, entity) + .onErrorMap(ConditionalCheckFailedException.class, ex -> { + log.error("Conditional check exception on PaperTrackingsDAOImpl putTrackings trackingId={} exmessage={}", entity.getTrackingId(), ex.getMessage()); + log.warn("Duplicate trackingId detected in putIfAbsent: {}", entity.getTrackingId()); + return new PnPaperTrackerConflictException(ERROR_CODE_PAPER_TRACKER_DUPLICATED_ITEM, String.format("RequestId %s already exists", entity.getTrackingId())); + }); + } + + public Mono updateOcrRequests(Integer ocrRequestIndex, String trackingId, Data.ValidationStatus validationStatus) { + log.debug("updateOcrRequests for item with trackingId: {}", trackingId); + Instant now = Instant.now(); + Map expressionAttributeNames = new HashMap<>(); + Map expressionAttributeValues = new HashMap<>(); + + String updateExpr = "SET #updatedAt = :updatedAt"; + + expressionAttributeNames.put("#updatedAt", PaperTrackings.COL_UPDATED_AT); + expressionAttributeNames.put("#validationFlow", PaperTrackings.COL_VALIDATION_FLOW); + expressionAttributeNames.put("#ocr", ValidationFlow.COL_OCR_REQUESTS); + + expressionAttributeValues.put(":updatedAt", AttributeValue.builder().s(now.toString()).build()); + + if (Objects.nonNull(ocrRequestIndex) && ocrRequestIndex >= 0) { + expressionAttributeNames.put("#responseTimestamp", OcrRequest.COL_RESPONSE_TIMESTAMP); + expressionAttributeValues.put(":responseTimestamp", AttributeValue.builder().s(now.toString()).build()); + expressionAttributeNames.put("#responseStatus", OcrRequest.COL_RESPONSE_STATUS); + expressionAttributeValues.put(":responseStatus", AttributeValue.builder().s(validationStatus.getValue()).build()); + updateExpr += ", #validationFlow.#ocr[" + ocrRequestIndex + "].#responseStatus = :responseStatus, " + + "#validationFlow.#ocr[" + ocrRequestIndex + "].#responseTimestamp = :responseTimestamp"; + } + + String conditionExpression = String.format("%s(%s)", "attribute_exists", PaperTrackings.COL_TRACKING_ID); + return updateIfExists(trackingId, updateExpr, expressionAttributeValues, expressionAttributeNames, conditionExpression); + + } + + @Override + public Mono updateItem(String trackingId, PaperTrackings paperTrackings) { + return updateItem(trackingId, paperTrackings, false); + } + + @Override + public Mono updateItemForRework(String trackingId, PaperTrackings paperTrackings) { + return updateItem(trackingId, paperTrackings, true); + } + + /** + * Aggiorna un elemento PaperTrackings nel database DynamoDB, identificato dal trackingId. + * L'aggiornamento viene eseguito solo se l'elemento esiste (condizione attribute_exists). + * Viene aggiornato anche il campo "updatedAt" con il timestamp corrente. + * + * @param trackingId pk dell'oggetto PaperTrackings da aggiornare + * @param paperTrackings l'oggetto PaperTrackings con i nuovi valori da aggiornare + * @return un Mono contenente l'oggetto PaperTrackings aggiornato + * @throws PnPaperTrackerNotFoundException se l'elemento con il trackingId specificato non esiste + */ + public Mono updateItem(String trackingId, PaperTrackings paperTrackings, boolean resetPaperStatus) { + log.debug("Updating item with trackingId: {}", trackingId); + + Map expressionAttributeNames = new HashMap<>(); + Map expressionAttributeValues = new HashMap<>(); + List updateExpressions = new ArrayList<>(); + + expressionAttributeNames.put("#updatedAt", PaperTrackings.COL_UPDATED_AT); + expressionAttributeValues.put(":updatedAt", AttributeValue.builder().s(Instant.now().toString()).build()); + updateExpressions.add("#updatedAt = :updatedAt"); + + String updateExpr = "SET "; + + Optional.ofNullable(paperTrackings.getValidationFlow()) + .ifPresent(flow -> { + List requests = flow.getOcrRequests(); + if (!CollectionUtils.isEmpty(requests)) { + expressionAttributeNames.put("#validationFlow", PaperTrackings.COL_VALIDATION_FLOW); + expressionAttributeNames.put("#ocr", ValidationFlow.COL_OCR_REQUESTS); + expressionAttributeValues.put(":requests", AttributeValue.builder() + .l(PaperTrackings.paperTrackingsToAttributeValueMap(paperTrackings).get("validationFlow").m().get("ocrRequests").l()).build()); + updateExpressions.add("#validationFlow.#ocr = list_append(#validationFlow.#ocr, :requests) "); + } + + flow.setOcrRequests(null); + }); + + + Map attributeValueMap = new HashMap<>(PaperTrackings.paperTrackingsToAttributeValueMap(paperTrackings)); + + /* + * Reset completo di paperStatus. + * In questo modo DynamoDB sovrascrive l'intero attributo paperStatus + * con una mappa vuota, eliminando tutti i campi precedenti. + */ + if (resetPaperStatus) { + expressionAttributeNames.put("#paperStatus", PaperTrackings.COL_PAPER_STATUS); + expressionAttributeValues.put(":emptyPaperStatus", AttributeValue.builder() + .m(Collections.emptyMap()) + .build()); + + updateExpressions.add("#paperStatus = :emptyPaperStatus"); + attributeValueMap.remove(PaperTrackings.COL_PAPER_STATUS); + } + + AtomicInteger counter = new AtomicInteger(0); + + attributeValueMap.forEach((key, value) -> { + List expressions = buildUpdateExpressions( + key, value, counter, expressionAttributeNames, expressionAttributeValues + ); + updateExpressions.addAll(expressions); + }); + + updateExpr += String.join(", ", updateExpressions); + + String conditionExpression = String.format("%s(%s)", "attribute_exists", PaperTrackings.COL_TRACKING_ID); + + log.debug("updateExpr {}", updateExpr); + log.debug("expressionAttributeValues {}", expressionAttributeValues); + log.debug("expressionAttributeNames {}", expressionAttributeNames); + + return updateIfExists(trackingId, updateExpr, expressionAttributeValues, expressionAttributeNames, conditionExpression); + } + + private Mono updateIfExists(String trackingId, String updateExpr, Map expressionAttributeValues, Map expressionAttributeNames, String conditionExpression) { + return updateIfExists(Map.of(PaperTrackings.COL_TRACKING_ID, AttributeValue.builder().s(trackingId).build()), updateExpr, expressionAttributeValues, expressionAttributeNames, conditionExpression) + .map(updateItemResponse -> PaperTrackings.attributeValueMapToPaperTrackings(updateItemResponse.attributes())) + .doOnError(e -> log.error("Error updating item with trackingId {}: {}", trackingId, e.getMessage())) + .onErrorMap(ConditionalCheckFailedException.class, e -> { + log.info("Item with trackingId {} not found — cannot update", trackingId); + return new PnPaperTrackerNotFoundException( + ERROR_CODE_PAPER_TRACKER_NOT_FOUND, + String.format("PaperTracking with trackingId %s not found", trackingId) + ); + }); + } + + @Override + public Flux retrieveAllByTrackingIds(List trackingIds) { + return findAllByKeys(trackingIds); + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/PaperTrackingsErrorsDAOImpl.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/PaperTrackingsErrorsDAOImpl.java new file mode 100644 index 000000000..8615538eb --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/PaperTrackingsErrorsDAOImpl.java @@ -0,0 +1,53 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo; + +import it.pagopa.pn.commons.log.dto.metrics.GeneralMetric; +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import it.pagopa.pn.papertracker.middleware.dao.PaperTrackingsErrorsDAO; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackingsErrors; +import it.pagopa.pn.papertracker.utils.MetricUtils; +import lombok.CustomLog; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient; +import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; + +import java.time.Instant; +import java.util.List; + +@Component +@CustomLog +public class PaperTrackingsErrorsDAOImpl extends BaseDao implements PaperTrackingsErrorsDAO { + + private final PnPaperTrackerConfigs pnPaperTrackerConfigs; + + public PaperTrackingsErrorsDAOImpl(DynamoDbEnhancedAsyncClient dynamoDbEnhancedClient, PnPaperTrackerConfigs cfg, DynamoDbAsyncClient dynamoDbAsyncClient) { + super(dynamoDbEnhancedClient, + dynamoDbAsyncClient, + cfg.getDao().getPaperTrackingsErrorsTable(), + PaperTrackingsErrors.class + ); + this.pnPaperTrackerConfigs = cfg; + } + + public Mono insertError(PaperTrackingsErrors paperTrackingsErrors) { + String logMessage = "Logging metrics: " + MetricUtils.MetricName.ERROR_COUNT +"; Dimensions: " + MetricUtils.DimensionName.ERROR_CATEGORY.getValue() + ", " + MetricUtils.DimensionName.PRODUCT_TYPE.getValue(); + GeneralMetric errorCountMetric = MetricUtils.generateGeneralMetric( + MetricUtils.MetricName.ERROR_COUNT, + 1, + List.of( + MetricUtils.generateDimension(MetricUtils.DimensionName.ERROR_CATEGORY, paperTrackingsErrors.getErrorCategory().name()), + MetricUtils.generateDimension(MetricUtils.DimensionName.PRODUCT_TYPE, paperTrackingsErrors.getProductType()) + ) + ); + + log.logMetric(List.of(errorCountMetric), logMessage); + + paperTrackingsErrors.setTtl(Instant.now().plus(pnPaperTrackerConfigs.getPaperTrackingsErrorsTtlDuration()).getEpochSecond()); + return putItem(paperTrackingsErrors); + } + + public Flux retrieveErrors(String requestId) { + return retrieveByRequestId(requestId); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/Attachment.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/Attachment.java new file mode 100644 index 000000000..82bc5b32e --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/Attachment.java @@ -0,0 +1,43 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.Data; +import lombok.Getter; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; + +import java.time.Instant; + +@DynamoDbBean +@Data +public class Attachment { + + public static final String COL_ID = "id"; + public static final String COL_DOCUMENT_TYPE = "documentType"; + public static final String COL_URI = "uri"; + public static final String COL_DATE = "date"; + public static final String COL_SHA256 = "sha256"; + public static final String COL_SOURCE_TYPE = "sourceType"; + public static final String COL_ORIGIN_TYPE = "originType"; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_ID)})) + private String id; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DOCUMENT_TYPE)})) + private String documentType; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_URI)})) + private String uri; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DATE)})) + private Instant date; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_SHA256)})) + private String sha256; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_SOURCE_TYPE)})) + private String sourceType; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_ORIGIN_TYPE)})) + private String originType; + +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/BusinessState.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/BusinessState.java new file mode 100644 index 000000000..69258ae87 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/BusinessState.java @@ -0,0 +1,12 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +public enum BusinessState { + AWAITING_FINAL_STATUS_CODE, + AWAITING_REFINEMENT_OCR, + AWAITING_REWORK_EVENTS, + AWAITING_OCR, + DONE, + KO + +} + diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorCategory.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorCategory.java new file mode 100644 index 000000000..6a3b13fd4 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorCategory.java @@ -0,0 +1,36 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.Getter; + +@Getter +public enum ErrorCategory { + + TRACKING_ID_NOT_FOUND("TrackingId non trovato"), + NOT_RETRYABLE_EVENT_ERROR("Evento not retryable ricevuto"), + RENDICONTAZIONE_SCARTATA("Rendicontazione scartata"), + DATE_ERROR("Errore nella validazione delle date della sequenza."), + LAST_EVENT_EXTRACTION_ERROR("Errore nell'estrazione della sequenza dall'ultimo evento."), + REGISTERED_LETTER_CODE_ERROR("Errore nella validazione del registered letter code"), + DELIVERY_FAILURE_CAUSE_ERROR("Errore nella validazione del delivery Failure Cause"), + ATTACHMENTS_ERROR("Errore nella validazione degli allegati della sequenza"), + MAX_RETRY_REACHED_ERROR("Numero massimo di retry raggiunto"), + OCR_VALIDATION("Errore nella validazione OCR"), + DEMAT_EMPTY_EVENT("Errore evento per demat non trovato"), + DEMAT_ATTACHMENT_NUMBER_ERROR("Errore nel numero di attachment trovati per demat"), + DUPLICATED_EVENT("Errore nella validazione della presenza di eventi duplicati"), + INVALID_STATE_FOR_STOCK_890("Stato non valido per giacenza 890"), + INCONSISTENT_STATE("Tracking completato o in attesa validazione OCR"), + /** + * @deprecated Non utilizzare. Campo mantenuto solo per retrocompatibilità. + */ + @Deprecated + STATUS_CODE_ERROR("Errore nella validazione della presenza degli elementi della sequenza."), + REGISTERED_LETTER_CODE_NOT_FOUND("Registered letter code non trovato"); + + private final String value; + + ErrorCategory(String value) { + this.value = value; + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorCause.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorCause.java new file mode 100644 index 000000000..41cdd0c22 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorCause.java @@ -0,0 +1,25 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum ErrorCause { + + GIACENZA_DATE_ERROR("Date di inizio e fine giacenza non coerenti"), + OCR_DUPLICATED_EVENT("Evento di risposta duplicato"), + OCR_DRY_RUN_MODE("Evento di risposta ignorato in quanto attiva la modalità dry-run per l'OCR"), + OCR_KO( "Errore nella validazione dell'OCR"), + OCR_UNSUPPORTED_PRODUCT("Prodotto non supportato per la validazione OCR"), + STOCK_890_REFINEMENT_MISSING("Spedizione 890 non perfezionata"), + STOCK_890_REFINEMENT_ERROR("Errore nel perfezionamento della spedizione 890"), + VALUE_AFTER_REFINEMENT("Evento arrivato dopo la conclusione della spedizione o mentre si stava aspettando l’OCR (DONE o AWAITING_OCR)"), + VALUES_NOT_MATCHING("Errore nella validazione degli allegati della sequenza: mancano degli allegati"), + INVALID_VALUES("Errore nella validazione"), + VALUES_NOT_FOUND("Errore nella validazione dei statusCode della sequenza: non sono presenti tutti gli statusCode previsti dalla macchina a stati"), + SOURCETYPE_FILETYPE_INCOHERENT("Il sourceType e il fileType dell'allegato non sono coerenti"); + + private final String description; + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorDetails.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorDetails.java new file mode 100644 index 000000000..88936d373 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorDetails.java @@ -0,0 +1,26 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import it.pagopa.pn.papertracker.middleware.dao.dynamo.AdditionalDetailsConverter; +import lombok.*; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbConvertedBy; + +import java.util.Map; + +@Setter +@Getter +@DynamoDbBean +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ErrorDetails { + + public static final String COL_CAUSE = "cause"; + public static final String COL_MESSAGE = "message"; + public static final String COL_ADDITIONAL_DETAILS = "additionalDetails"; + + private ErrorCause cause; + private String message; + @Getter(onMethod = @__({ @DynamoDbConvertedBy(AdditionalDetailsConverter.class) })) + private Map additionalDetails; +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorType.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorType.java new file mode 100644 index 000000000..375287ecc --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorType.java @@ -0,0 +1,9 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +public enum ErrorType { + + ERROR, + INFO, + WARNING + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/Event.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/Event.java new file mode 100644 index 000000000..fa1eecbc2 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/Event.java @@ -0,0 +1,83 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.Data; +import lombok.Getter; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbIgnoreNulls; + +import java.time.Instant; +import java.util.List; + +@DynamoDbBean +@Data +public class Event { + + public static final String COL_REQUEST_TIMESTAMP = "requestTimestamp"; + public static final String COL_STATUS_CODE = "statusCode"; + public static final String COL_STATUS_DESCRIPTION = "statusDescription"; + public static final String COL_STATUS_TIMESTAMP = "statusTimestamp"; + public static final String COL_PRODUCT_TYPE = "productType"; + public static final String COL_DELIVERY_FAILURE_CAUSE = "deliveryFailureCause"; + public static final String COL_DISCOVERED_ADDRESS = "discoveredAddress"; + public static final String COL_ANONYMIZED_DISCOVERED_ADDRESS_ID = "anonymizedDiscoveredAddressId"; + public static final String COL_ATTACHMENTS = "attachments"; + public static final String COL_REGISTERED_LETTER_CODE = "registeredLetterCode"; + public static final String COL_ID = "id"; + public static final String COL_DRY_RUN = "dryRun"; + public static final String COL_IUN = "iun"; + public static final String COL_CREATED_AT = "createdAt"; + public static final String COL_REWORK_ID = "reworkId"; + public static final String COL_PRINTER = "printer"; + public static final String COL_DU = "du"; + + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_ID)})) + private String id; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_IUN)})) + private String iun; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REQUEST_TIMESTAMP)})) + private Instant requestTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STATUS_CODE)})) + private String statusCode; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STATUS_DESCRIPTION)})) + private String statusDescription; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STATUS_TIMESTAMP)})) + private Instant statusTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_PRODUCT_TYPE)})) + private String productType; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DELIVERY_FAILURE_CAUSE)})) + private String deliveryFailureCause; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_ANONYMIZED_DISCOVERED_ADDRESS_ID)})) + private String anonymizedDiscoveredAddressId; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_ATTACHMENTS), @DynamoDbIgnoreNulls})) + private List attachments; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REGISTERED_LETTER_CODE)})) + private String registeredLetterCode; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DRY_RUN)})) + private Boolean dryRun; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_CREATED_AT)})) + private Instant createdAt; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REWORK_ID)})) + private String notificationReworkId; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_PRINTER)})) + private String printer; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DU)})) + private String du; + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/FlowThrow.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/FlowThrow.java new file mode 100644 index 000000000..8f7e3590f --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/FlowThrow.java @@ -0,0 +1,15 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +public enum FlowThrow { + + INIT_TRACKING, + NOT_RETRYABLE_EVENT_HANDLER, + DUPLICATED_EVENT_VALIDATION, + CHECK_TRACKING_PRODUCT, + SEQUENCE_VALIDATION, + DEMAT_VALIDATION, + FINAL_EVENT_BUILDING, + CHECK_TRACKING_STATE, + RETRY_PHASE; + +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/OcrRequest.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/OcrRequest.java new file mode 100644 index 000000000..0bf976c34 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/OcrRequest.java @@ -0,0 +1,42 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.Data; +import lombok.Getter; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; + +import java.time.Instant; + +@DynamoDbBean +@Data +public class OcrRequest { + + public static final String COL_REQUEST_TIMESTAMP = "requestTimestamp"; + public static final String COL_RESPONSE_TIMESTAMP = "responseTimestamp"; + public static final String COL_DOCUMENT_TYPE = "documentType"; + public static final String COL_FINAL_EVENT_ID = "finalEventId"; + public static final String COL_ATTACHMENT_EVENT_ID = "attachmentEventId"; + public static final String COL_URI = "uri"; + public static final String COL_RESPONSE_STATUS = "responseStatus"; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DOCUMENT_TYPE)})) + private String documentType; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_FINAL_EVENT_ID)})) + private String finalEventId; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_ATTACHMENT_EVENT_ID)})) + private String attachmentEventId; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_RESPONSE_TIMESTAMP)})) + private Instant responseTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REQUEST_TIMESTAMP)})) + private Instant requestTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_URI)})) + private String uri; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_RESPONSE_STATUS)})) + private String responseStatus; +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperStatus.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperStatus.java new file mode 100644 index 000000000..8b40f09fc --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperStatus.java @@ -0,0 +1,53 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.Data; +import lombok.Getter; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbIgnoreNulls; + +import java.time.Instant; +import java.util.List; + +@DynamoDbBean +@Data +public class PaperStatus { + + public static final String COL_REGISTERED_LETTER_CODE = "registeredLetterCode"; + public static final String COL_DELIVERY_FAILURE_CAUSE = "deliveryFailureCause"; + public static final String COL_ANONYMIZED_DISCOVERED_ADDRESS = "anonymizedDiscoveredAddress"; + public static final String COL_FINAL_STATUS_CODE = "finalStatusCode"; + public static final String COL_VALIDATED_SEQUENCE_TIMESTAMP = "validatedSequenceTimestamp"; + public static final String COL_VALIDATED_EVENTS = "validatedEvents"; + public static final String COL_FINAL_DEMAT_FOUND = "finalDematFound"; + public static final String COL_PAPER_DELIVERY_TIMESTAMP = "paperDeliveryTimestamp"; + public static final String COL_PREDICTED_REFINEMENT_TYPE = "predictedRefinementType"; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REGISTERED_LETTER_CODE)})) + private String registeredLetterCode; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DELIVERY_FAILURE_CAUSE)})) + private String deliveryFailureCause; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_ANONYMIZED_DISCOVERED_ADDRESS)})) + private String anonymizedDiscoveredAddress; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_FINAL_STATUS_CODE)})) + private String finalStatusCode; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_VALIDATED_SEQUENCE_TIMESTAMP)})) + private Instant validatedSequenceTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_VALIDATED_EVENTS), @DynamoDbIgnoreNulls})) + private List validatedEvents; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_FINAL_DEMAT_FOUND)})) + private Boolean finalDematFound; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_PAPER_DELIVERY_TIMESTAMP)})) + private Instant paperDeliveryTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_PREDICTED_REFINEMENT_TYPE)})) + private String predictedRefinementType; + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackerDryRunOutputs.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackerDryRunOutputs.java new file mode 100644 index 000000000..2a0df0c62 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackerDryRunOutputs.java @@ -0,0 +1,61 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.Data; +import lombok.Getter; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; + +import java.time.Instant; +import java.util.List; + +@DynamoDbBean +@Data +public class PaperTrackerDryRunOutputs { + public static final String COL_TRACKING_ID = "trackingId"; + public static final String COL_CREATED = "created"; + public static final String COL_REGISTERED_LETTER_CODE = "registeredLetterCode"; + public static final String COL_STATUS_CODE = "statusCode"; + public static final String COL_STATUS_DETAIL = "statusDetail"; + public static final String COL_STATUS_DESCRIPTION = "statusDescription"; + public static final String COL_STATUS_DATE_TIME = "statusDateTime"; + public static final String COL_DELIVERY_FAILURE_CAUSE = "deliveryFailureCause"; + public static final String COL_ATTACHMENTS = "attachments"; + public static final String COL_DISCOVERED_ADDRESS = "discoveredAddress"; + public static final String COL_CLIENT_REQUEST_TIMESTAMP = "clientRequestTimestamp"; + + @Getter(onMethod = @__({@DynamoDbPartitionKey, @DynamoDbAttribute(COL_TRACKING_ID)})) + private String trackingId; + + @Getter(onMethod = @__({@DynamoDbSortKey, @DynamoDbAttribute(COL_CREATED)})) + private Instant created; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REGISTERED_LETTER_CODE)})) + private String registeredLetterCode; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STATUS_CODE)})) + private String statusCode; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STATUS_DETAIL)})) + private String statusDetail; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STATUS_DESCRIPTION)})) + private String statusDescription; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STATUS_DATE_TIME)})) + private String statusDateTime; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DELIVERY_FAILURE_CAUSE)})) + private String deliveryFailureCause; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_ATTACHMENTS)})) + private List attachments; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DISCOVERED_ADDRESS)})) + private String anonymizedDiscoveredAddressId; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_CLIENT_REQUEST_TIMESTAMP)})) + private String clientRequestTimestamp; + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackings.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackings.java new file mode 100644 index 000000000..0e564239f --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackings.java @@ -0,0 +1,119 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.Data; +import lombok.Getter; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.*; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.Instant; +import java.util.List; +import java.util.Map; + +@DynamoDbBean +@Data +public class PaperTrackings { + + public static final String COL_TRACKING_ID = "trackingId"; + public static final String COL_ATTEMPT_ID = "attemptId"; + public static final String COL_PC_RETRY = "pcRetry"; + public static final String COL_EVENTS = "events"; + public static final String COL_PAPER_STATUS = "paperStatus"; + public static final String COL_VALIDATION_FLOW = "validationFlow"; + public static final String COL_VALIDATION_CONFIG = "validationConfig"; + public static final String COL_PRODUCT_TYPE = "productType"; + public static final String COL_NEXT_REQUEST_ID_PC_RETRY = "nextRequestIdPcretry"; + public static final String COL_UNIFIED_DELIVERY_DRIVER = "unifiedDeliveryDriver"; + public static final String COL_CREATED_AT = "createdAt"; + public static final String COL_UPDATED_AT = "updatedAt"; + public static final String COL_STATE = "state"; + public static final String COL_BUSINESS_STATE = "businessState"; + public static final String COL_TTL = "ttl"; + public static final String COL_RECAG012_STATUS_TIMESTAMP = "recag012StatusTimestamp"; + public static final String COL_PENDING_FINAL_EVENT_ID = "pendingFinalEventId"; + public static final String ATTEMPT_ID_PCRETRY_INDEX = "attemptId-pcRetry-index"; + public static final String COL_REWORK_ID = "reworkId"; + public static final String COL_REWORK_REQUEST_TIMESTAMP = "reworkRequestTimestamp"; + public static final String COL_PROCESSING_MODE = "processingMode"; + public static final String COL_ANALOG_REQUEST_CLIENT_ID = "analogRequestClientId"; + + @Getter(onMethod = @__({@DynamoDbPartitionKey, @DynamoDbAttribute(COL_TRACKING_ID)})) + private String trackingId; + + @Getter(onMethod = @__({@DynamoDbSecondaryPartitionKey(indexNames = ATTEMPT_ID_PCRETRY_INDEX), @DynamoDbAttribute(COL_ATTEMPT_ID)})) + private String attemptId; + + @Getter(onMethod = @__({@DynamoDbSecondarySortKey(indexNames = ATTEMPT_ID_PCRETRY_INDEX), @DynamoDbAttribute(COL_PC_RETRY)})) + private String pcRetry; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_PRODUCT_TYPE)})) + private String productType; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_UNIFIED_DELIVERY_DRIVER)})) + private String unifiedDeliveryDriver; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_EVENTS), @DynamoDbIgnoreNulls})) + private List events; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_PAPER_STATUS), @DynamoDbIgnoreNulls})) + private PaperStatus paperStatus; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_VALIDATION_FLOW), @DynamoDbIgnoreNulls})) + private ValidationFlow validationFlow; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_VALIDATION_CONFIG), @DynamoDbIgnoreNulls})) + private ValidationConfig validationConfig; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_NEXT_REQUEST_ID_PC_RETRY)})) + private String nextRequestIdPcretry; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STATE)})) + private PaperTrackingsState state; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_BUSINESS_STATE)})) + private BusinessState businessState; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_PENDING_FINAL_EVENT_ID)})) + private String pendingFinalEventId; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_CREATED_AT)})) + private Instant createdAt; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_UPDATED_AT)})) + private Instant updatedAt; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_TTL)})) + private Long ttl; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REWORK_REQUEST_TIMESTAMP)})) + private Instant notificationReworkRequestTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REWORK_ID)})) + private String notificationReworkId; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_PROCESSING_MODE)})) + private ProcessingMode processingMode; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_ANALOG_REQUEST_CLIENT_ID)})) + private String analogRequestClientId; + + // Costruito UNA volta sola + private static final TableSchema SCHEMA = + TableSchema.fromBean(PaperTrackings.class); + + /** + * Converte l'entity PaperTrackings in una mappa {@code Map} utilizzata da DynamoDB. + */ + public static Map paperTrackingsToAttributeValueMap(PaperTrackings p) { + return SCHEMA.itemToMap(p, true); + } + + /** + * Converte una mappa {@code Map} utilizzata da DynamoDB in una entity PaperTrackings. + */ + public static PaperTrackings attributeValueMapToPaperTrackings(Map item) { + if (item == null || item.isEmpty()) return null; + return SCHEMA.mapToItem(item); + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackingsErrors.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackingsErrors.java new file mode 100644 index 000000000..5392a5cfb --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackingsErrors.java @@ -0,0 +1,70 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.*; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; + +import java.time.Instant; + +@Setter +@Getter +@ToString +@DynamoDbBean +@EqualsAndHashCode +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class PaperTrackingsErrors { + + public static final String COL_TRACKING_ID = "trackingId"; + public static final String COL_CREATED = "created"; + public static final String COL_CATEGORY = "category"; + public static final String COL_DETAILS = "details"; + public static final String COL_FLOW_THROW = "flowThrow"; + public static final String COL_EVENT_THROW = "eventThrow"; + public static final String COL_EVENT_ID_THROW = "eventIdThrow"; + public static final String COL_PRODUCT_TYPE = "productType"; + public static final String COL_TYPE = "type"; + public static final String COL_TTL = "ttl"; + public static final String COL_UNIFIED_DELIVERY_DRIVER = "unifiedDeliveryDriver"; + public static final String COL_REGISTERED_LETTER_CODE = "registeredLetterCode"; + + @Getter(onMethod = @__({@DynamoDbPartitionKey, @DynamoDbAttribute(COL_TRACKING_ID)})) + private String trackingId; + + @Getter(onMethod = @__({@DynamoDbSortKey, @DynamoDbAttribute(COL_CREATED)})) + private Instant created; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_CATEGORY)})) + private ErrorCategory errorCategory; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DETAILS)})) + private ErrorDetails details; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_FLOW_THROW)})) + private FlowThrow flowThrow; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_EVENT_THROW)})) + private String eventThrow; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_EVENT_ID_THROW)})) + private String eventIdThrow; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_PRODUCT_TYPE)})) + private String productType; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_TYPE)})) + private ErrorType type; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_TTL)})) + private Long ttl; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_UNIFIED_DELIVERY_DRIVER)})) + private String unifiedDeliveryDriver; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REGISTERED_LETTER_CODE)})) + private String registeredLetterCode; + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackingsState.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackingsState.java new file mode 100644 index 000000000..091593be5 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackingsState.java @@ -0,0 +1,10 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +public enum PaperTrackingsState { + AWAITING_FINAL_STATUS_CODE, //mantenuto per retrocompatibilità + AWAITING_REFINEMENT, + AWAITING_REWORK_EVENTS, + AWAITING_OCR, + DONE, + KO +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ProcessingMode.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ProcessingMode.java new file mode 100644 index 000000000..c33f34cd7 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ProcessingMode.java @@ -0,0 +1,6 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +public enum ProcessingMode { + DRY, + RUN +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ProductType.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ProductType.java new file mode 100644 index 000000000..e887d065d --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ProductType.java @@ -0,0 +1,37 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.Getter; + +@Getter +public enum ProductType { + + RS("RS"), + + AR("AR"), + + _890("890"), + + RIR("RIR"), + + RIS("RIS"), + + ALL("ALL"), + + UNKNOWN("UNKNOWN"); + + private final String value; + + ProductType(String value) { + this.value = value; + } + + public static ProductType fromValue(String value) { + for (ProductType type : ProductType.values()) { + if (type.getValue().equals(value)) { + return type; + } + } + throw new IllegalArgumentException("Unknown ProductType: " + value); + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ValidationConfig.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ValidationConfig.java new file mode 100644 index 000000000..b9aaedd04 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ValidationConfig.java @@ -0,0 +1,56 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import it.pagopa.pn.papertracker.model.FileType; +import it.pagopa.pn.papertracker.model.OcrStatusEnum; +import lombok.Data; +import lombok.Getter; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; + +import java.util.List; + +@DynamoDbBean +@Data +public class ValidationConfig { + + public static final String COL_OCR_ENABLED = "ocrEnabled"; + public static final String COL_OCR_FILE_TYPES = "ocrFileTypes"; + public static final String COL_REQUIRED_ATTACHMENTS_REFINEMENT_STOCK_890 = "requiredAttachmentsRefinementStock890"; + public static final String COL_SEND_OCR_REFINEMENT_STOCK_890 = "sendOcrAttachmentsRefinementStock890"; + public static final String COL_SEND_OCR_FINAL_VALIDATION_STOCK_890 = "sendOcrAttachmentsFinalValidationStock890"; + public static final String COL_SEND_OCR_FINAL_VALIDATION = "sendOcrAttachmentsFinalValidation"; + public static final String COL_STRICT_FINAL_VALIDATION_STOCK_890 = "strictFinalValidationStock890"; + public static final String COL_STRICT_DELIVERY_FAILURE_CAUSE = "strictDeliveryFailureCause"; + public static final String COL_OCR_FILTER_TEMPORAL = "ocrFilterTemporal"; + public static final String COL_OCR_FILTER_UNIFIED_DELIVERY_DRIVER = "ocrFilterUnifiedDeliveryDriver"; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_OCR_ENABLED)})) + private OcrStatusEnum ocrEnabled; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_OCR_FILE_TYPES)})) + private List ocrFileTypes; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REQUIRED_ATTACHMENTS_REFINEMENT_STOCK_890)})) + private List requiredAttachmentsRefinementStock890; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_SEND_OCR_REFINEMENT_STOCK_890)})) + private List sendOcrAttachmentsRefinementStock890; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_SEND_OCR_FINAL_VALIDATION_STOCK_890)})) + private List sendOcrAttachmentsFinalValidationStock890; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_SEND_OCR_FINAL_VALIDATION)})) + private List sendOcrAttachmentsFinalValidation; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STRICT_FINAL_VALIDATION_STOCK_890)})) + private Boolean strictFinalValidationStock890; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_OCR_FILTER_TEMPORAL)})) + private String ocrFilterTemporal; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_OCR_FILTER_UNIFIED_DELIVERY_DRIVER)})) + private List ocrFilterUnifiedDeliveryDriver; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STRICT_DELIVERY_FAILURE_CAUSE)})) + private Boolean strictDeliveryFailureCause; +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ValidationFlow.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ValidationFlow.java new file mode 100644 index 000000000..53c04778e --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ValidationFlow.java @@ -0,0 +1,40 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.Data; +import lombok.Getter; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; + +import java.time.Instant; +import java.util.List; + +@DynamoDbBean +@Data +public class ValidationFlow { + + public static final String COL_SEQUENCES_VALIDATION_TIMESTAMP = "sequencesValidationTimestamp"; + public static final String COL_FINAL_EVENT_DEMAT_VALIDATION_TIMESTAMP = "finalEventDematValidationTimestamp"; + public static final String COL_REFINEMENT_DEMAT_VALIDATION_TIMESTAMP = "refinementDematValidationTimestamp"; + public static final String COL_FINAL_EVENT_BUILDER_TIMESTAMP = "finalEventBuilderTimestamp"; + public static final String COL_RECAG012_STATUS_TIMESTAMP = "recag012StatusTimestamp"; + public static final String COL_OCR_REQUESTS = "ocrRequests"; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_SEQUENCES_VALIDATION_TIMESTAMP)})) + private Instant sequencesValidationTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_FINAL_EVENT_DEMAT_VALIDATION_TIMESTAMP)})) + private Instant finalEventDematValidationTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REFINEMENT_DEMAT_VALIDATION_TIMESTAMP)})) + private Instant refinementDematValidationTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_FINAL_EVENT_BUILDER_TIMESTAMP)})) + private Instant finalEventBuilderTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_RECAG012_STATUS_TIMESTAMP)})) + private Instant recag012StatusTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_OCR_REQUESTS)})) + private List ocrRequests; + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/eventBridge/EventBridgePublisher.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/eventBridge/EventBridgePublisher.java new file mode 100644 index 000000000..882a20466 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/eventBridge/EventBridgePublisher.java @@ -0,0 +1,45 @@ +package it.pagopa.pn.papertracker.middleware.eventBridge; + +import com.fasterxml.jackson.databind.ObjectMapper; +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; +import software.amazon.awssdk.services.eventbridge.EventBridgeAsyncClient; +import software.amazon.awssdk.services.eventbridge.model.PutEventsRequest; +import software.amazon.awssdk.services.eventbridge.model.PutEventsRequestEntry; +import software.amazon.awssdk.services.eventbridge.model.PutEventsResponse; + +@Service +@RequiredArgsConstructor +@Slf4j +public class EventBridgePublisher { + + private final EventBridgeAsyncClient client; + private final ObjectMapper objectMapper; + private final PnPaperTrackerConfigs configs; + + public Mono publish(Object event) { + log.info("Sending event to EventBridge"); + return Mono.fromCallable(() -> + objectMapper.writeValueAsString(event)) + .flatMap(detail -> Mono.fromFuture(client.putEvents(createRequest(detail)))) + .doOnError(e -> log.error("EventBridge publish error: {}", e.getMessage())) + .doOnNext(response -> log.info("EventBridge publish response: {}", response.entries())); + } + + private PutEventsRequest createRequest(String detail) { + PutEventsRequestEntry entry = PutEventsRequestEntry.builder() + .eventBusName(configs.getEventBus().getName()) + .source(configs.getEventBus().getSource()) + .detailType(configs.getEventBus().getDetailType()) + .detail(detail) + .build(); + + return PutEventsRequest.builder() + .entries(entry) + .build(); + } + +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/DataVaultClient.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/DataVaultClient.java new file mode 100644 index 000000000..0ead3f9cd --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/DataVaultClient.java @@ -0,0 +1,12 @@ +package it.pagopa.pn.papertracker.middleware.msclient; + +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.DiscoveredAddress; +import it.pagopa.pn.papertracker.generated.openapi.msclient.pndatavault.model.PaperAddress; +import reactor.core.publisher.Mono; + +public interface DataVaultClient { + + Mono anonymizeDiscoveredAddress(String trackingId, DiscoveredAddress discoveredAddress); + + Mono deAnonymizeDiscoveredAddress(String trackingId, String anonymizeDiscoveredAddress); +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/PaperChannelClient.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/PaperChannelClient.java new file mode 100644 index 000000000..27f0df380 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/PaperChannelClient.java @@ -0,0 +1,10 @@ +package it.pagopa.pn.papertracker.middleware.msclient; + +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.PcRetryResponse; +import it.pagopa.pn.papertracker.model.HandlerContext; +import reactor.core.publisher.Mono; + +public interface PaperChannelClient { + + Mono getPcRetry(HandlerContext context, Boolean checkApplyRasterization); +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/SafeStorageClient.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/SafeStorageClient.java new file mode 100644 index 000000000..8e0ddea29 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/SafeStorageClient.java @@ -0,0 +1,8 @@ +package it.pagopa.pn.papertracker.middleware.msclient; + +import reactor.core.publisher.Mono; + +public interface SafeStorageClient { + + Mono getSafeStoragePresignedUrl(String safeStorageUri); +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/impl/DataVaultClientImpl.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/impl/DataVaultClientImpl.java new file mode 100644 index 000000000..101229cbd --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/impl/DataVaultClientImpl.java @@ -0,0 +1,46 @@ +package it.pagopa.pn.papertracker.middleware.msclient.impl; + +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.DiscoveredAddress; +import it.pagopa.pn.papertracker.generated.openapi.msclient.pndatavault.api.PaperAddressesApi; +import it.pagopa.pn.papertracker.generated.openapi.msclient.pndatavault.model.PaperAddress; +import it.pagopa.pn.papertracker.middleware.msclient.DataVaultClient; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +import java.util.UUID; + +@Component +@RequiredArgsConstructor +public class DataVaultClientImpl implements DataVaultClient { + + private final PaperAddressesApi paperAddressesApi; + + @Override + public Mono anonymizeDiscoveredAddress(String trackingId, DiscoveredAddress discoveredAddress) { + String uuid = UUID.randomUUID().toString(); + String PREFIX_ANONYMIZED_ADDRESS_ID = "anonymized_addr_"; + String anonymizedDiscoveredAddressId = PREFIX_ANONYMIZED_ADDRESS_ID + uuid; + return paperAddressesApi.updatePaperAddress(trackingId, anonymizedDiscoveredAddressId, toPaperAddress(discoveredAddress)) + .thenReturn(anonymizedDiscoveredAddressId); + } + + @Override + public Mono deAnonymizeDiscoveredAddress(String trackingId, String anonymizeDiscoveredAddress) { + return paperAddressesApi.getPaperAddressByIds(trackingId, anonymizeDiscoveredAddress) + .switchIfEmpty(Mono.error(new IllegalArgumentException("No address found for the given anonymized ID: " + anonymizeDiscoveredAddress))); + } + + private PaperAddress toPaperAddress(DiscoveredAddress discoveredAddress) { + return new PaperAddress() + .name(discoveredAddress.getName()) + .nameRow2(discoveredAddress.getNameRow2()) + .address(discoveredAddress.getAddress()) + .addressRow2(discoveredAddress.getAddressRow2()) + .cap(discoveredAddress.getCap()) + .city(discoveredAddress.getCity()) + .city2(discoveredAddress.getCity2()) + .pr(discoveredAddress.getPr()) + .country(discoveredAddress.getCountry()); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/impl/PaperChannelClientImpl.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/impl/PaperChannelClientImpl.java new file mode 100644 index 000000000..bc1026405 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/impl/PaperChannelClientImpl.java @@ -0,0 +1,44 @@ +package it.pagopa.pn.papertracker.middleware.msclient.impl; + +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import it.pagopa.pn.papertracker.exception.PnPaperTrackerNotFoundException; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.api.PcRetryApi; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.PcRetryResponse; +import it.pagopa.pn.papertracker.middleware.msclient.PaperChannelClient; +import it.pagopa.pn.papertracker.model.HandlerContext; +import it.pagopa.pn.papertracker.utils.PcRetryUtilsMock; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClientResponseException; +import reactor.core.publisher.Mono; + +@Component +@RequiredArgsConstructor +@Slf4j +public class PaperChannelClientImpl implements PaperChannelClient { + + private final PcRetryApi pcRetryApi; + private final PnPaperTrackerConfigs config; + + @Override + public Mono getPcRetry(HandlerContext context, Boolean checkApplyRasterization) { + if(!context.isDryRunEnabled()){ + return getPcRetryPaperChannel(context.getPaperTrackings().getTrackingId(), checkApplyRasterization); + } + log.debug("giving mock response..."); + return PcRetryUtilsMock.getPcRetryPaperMock(context.getPaperTrackings(), config.getMaxPcRetryMock(), context.getPaperProgressStatusEvent().getStatusCode()); + } + + private Mono getPcRetryPaperChannel(String trackingId, Boolean checkApplyRasterization) { + return pcRetryApi.getPcRetry(trackingId, checkApplyRasterization) + .onErrorResume(throwable -> { + if (throwable instanceof WebClientResponseException webEx && webEx.getStatusCode() == HttpStatus.NOT_FOUND) { + return Mono.error(new PnPaperTrackerNotFoundException("RequestId not found", webEx.getMessage())); + } + return Mono.error(throwable); + }); + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/impl/SafeStorageClientImpl.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/impl/SafeStorageClientImpl.java new file mode 100644 index 000000000..7764f6596 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/impl/SafeStorageClientImpl.java @@ -0,0 +1,59 @@ +package it.pagopa.pn.papertracker.middleware.msclient.impl; + +import it.pagopa.pn.commons.log.PnLogger; +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import it.pagopa.pn.papertracker.exception.PaperTrackerException; +import it.pagopa.pn.papertracker.generated.openapi.msclient.safestorage.api.FileDownloadApi; +import it.pagopa.pn.papertracker.middleware.msclient.SafeStorageClient; +import it.pagopa.pn.papertracker.utils.AttachmentsConfigUtils; +import lombok.CustomLog; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClientResponseException; +import reactor.core.publisher.Mono; +import reactor.util.retry.Retry; + +import java.net.ConnectException; +import java.time.Duration; +import java.util.concurrent.TimeoutException; + +@Component +@RequiredArgsConstructor +@CustomLog +public class SafeStorageClientImpl implements SafeStorageClient { + + private final FileDownloadApi fileDownloadApi; + private final PnPaperTrackerConfigs pnPaperTrackerConfigs; + + /** + * Retrieves the presigned download URL from the SafeStorage. + * + * @param fileKey the object key + * @return Mono containing the presigned URL + */ + public Mono getSafeStoragePresignedUrl(String fileKey) { + final String PN_SAFE_STORAGE_DESCRIPTION = "Safe Storage getFile"; + log.logInvokingAsyncExternalService(PnLogger.EXTERNAL_SERVICES.PN_SAFE_STORAGE, PN_SAFE_STORAGE_DESCRIPTION, null); + String reqFileKey = fileKey; + log.info("Getting file with {} key", fileKey); + fileKey = AttachmentsConfigUtils.cleanFileKey(fileKey); + log.debug("Req params : {}", fileKey); + + return fileDownloadApi.getFile(fileKey, this.pnPaperTrackerConfigs.getSafeStorageCxId(), false) + .retryWhen(Retry.backoff(2, Duration.ofMillis(500)).filter(throwable -> throwable instanceof TimeoutException || throwable instanceof ConnectException)) + .flatMap(response -> { + if(response.getDownload() != null && response.getDownload().getRetryAfter() != null) { + return Mono.error(new PaperTrackerException(String.valueOf(response.getDownload().getRetryAfter()))); + } + if (response.getDownload() == null || response.getDownload().getUrl() == null) { + log.error("File not found in Safe Storage for key: {}", reqFileKey); + return Mono.error(new PaperTrackerException("File not found in Safe Storage")); + } + return Mono.just(response.getDownload().getUrl()); + }) + .onErrorResume(WebClientResponseException.class, ex -> { + log.error(ex.getResponseBodyAsString()); + return Mono.error(ex); + }); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/PnPaperTrackerMiddlewareConfigs.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/PnPaperTrackerMiddlewareConfigs.java new file mode 100644 index 000000000..c2bab40e1 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/PnPaperTrackerMiddlewareConfigs.java @@ -0,0 +1,58 @@ +package it.pagopa.pn.papertracker.middleware.queue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import it.pagopa.pn.papertracker.middleware.queue.model.DeliveryPushEvent; +import it.pagopa.pn.papertracker.middleware.queue.model.ExternalChannelEvent; +import it.pagopa.pn.papertracker.middleware.queue.model.OcrEvent; +import it.pagopa.pn.papertracker.middleware.queue.producer.ExternalChannelOutputsMomProducer; +import it.pagopa.pn.papertracker.middleware.queue.producer.ExternalChannelToPaperChannelDryRunMomProducer; +import it.pagopa.pn.papertracker.middleware.queue.producer.ExternalChannelToPaperTrackerMomProducer; +import it.pagopa.pn.papertracker.middleware.queue.producer.OcrMomProducer; +import lombok.AllArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.sqs.SqsClient; + +@Configuration +@AllArgsConstructor +public class PnPaperTrackerMiddlewareConfigs { + private PnPaperTrackerConfigs pnPaperChannelConfigs; + + @Bean + public OcrMomProducer ocrMomProducer(ObjectMapper objMapper) { + SqsClient sqsClient = SqsClient.builder() + .region(Region.of(this.pnPaperChannelConfigs.getTopics().getQueueOcrInputsRegion())) + .build(); + return new OcrMomProducer(sqsClient, + this.pnPaperChannelConfigs.getTopics().getQueueOcrInputsUrl(), + this.pnPaperChannelConfigs.getTopics().getQueueOcrInputsUrl(), + objMapper, + OcrEvent.class); + } + + @Bean + public ExternalChannelOutputsMomProducer externalChannelOutputsMomProducer(SqsClient sqsClient, ObjectMapper objMapper) { + return new ExternalChannelOutputsMomProducer(sqsClient, + this.pnPaperChannelConfigs.getTopics().getExternalChannelOutputsQueue(), + objMapper, + DeliveryPushEvent.class); + } + + @Bean + public ExternalChannelToPaperTrackerMomProducer externalChannelToPaperTrackerMomProducer(SqsClient sqsClient, ObjectMapper objMapper) { + return new ExternalChannelToPaperTrackerMomProducer(sqsClient, + this.pnPaperChannelConfigs.getTopics().getExternalChannelToPaperTrackerQueue(), + objMapper, + ExternalChannelEvent.class); + } + + @Bean + public ExternalChannelToPaperChannelDryRunMomProducer externalChannelToPaperChannelDryRunMomProducer(SqsClient sqsClient, ObjectMapper objMapper) { + return new ExternalChannelToPaperChannelDryRunMomProducer(sqsClient, + this.pnPaperChannelConfigs.getTopics().getExternalChannelToPaperChannelDryRunQueue(), + objMapper, + ExternalChannelEvent.class); + } +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/PnEventInboundService.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/PnEventInboundService.java new file mode 100644 index 000000000..8b405059a --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/PnEventInboundService.java @@ -0,0 +1,93 @@ +package it.pagopa.pn.papertracker.middleware.queue.consumer; + +import com.sngular.apigenerator.asyncapi.business_model.model.event.OcrDataResultPayload; +import io.awspring.cloud.sqs.annotation.SqsListener; +import io.awspring.cloud.sqs.listener.SqsHeaders; +import it.pagopa.pn.commons.utils.MDCUtils; +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.SingleStatusUpdate; +import it.pagopa.pn.papertracker.middleware.queue.consumer.internal.ExternalChannelHandler; +import it.pagopa.pn.papertracker.middleware.queue.consumer.internal.ExternalChannelSourceEventsHandler; +import it.pagopa.pn.papertracker.middleware.queue.consumer.internal.OcrEventHandler; +import it.pagopa.pn.papertracker.utils.LogUtility; +import lombok.CustomLog; +import lombok.RequiredArgsConstructor; +import org.slf4j.MDC; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.messaging.Message; +import org.springframework.messaging.handler.annotation.Header; +import org.springframework.messaging.handler.annotation.Headers; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +@CustomLog +@Component +@RequiredArgsConstructor +@ConditionalOnProperty( + prefix = "pn.paper-tracker.", + name = "disable-all-consumers", + havingValue = "false" +) +public class PnEventInboundService { + + private final ExternalChannelSourceEventsHandler externalChannelSourceEventsHandler; + private final ExternalChannelHandler externalChannelHandler; + private final OcrEventHandler ocrEventHandler; + private final LogUtility logUtility; + + @SqsListener("${pn.paper-tracker.topics.external-channel-to-paper-channel-queue}") + public void externalChannelSourceConsumer( + @Payload Message message, + @Headers Map headers, + @Header(name = SqsHeaders.SQS_SOURCE_DATA_HEADER) software.amazon.awssdk.services.sqs.model.Message sourceMessage + ) { + processMessage(() -> { + var messageAttributes = sourceMessage.messageAttributes(); + externalChannelSourceEventsHandler.handleExternalChannelMessage(message.getPayload(), messageAttributes); + }, "pn-external_channel_to_paper_channel", message, headers); + } + + @SqsListener("${pn.paper-tracker.topics.external-channel-to-paper-tracker-queue}") + public void externalChannelConsumer( + @Payload Message message, + @Header(name = "dryRun", required = false) Boolean dryRun, + @Header(name = "reworkId", required = false) String reworkId, + @Header(name = "id") String messageId, + @Header(name = SqsHeaders.MessageSystemAttributes.SQS_SENDER_ID, required = false) String senderId, + @Headers Map headers + ) { + processMessage(() -> externalChannelHandler.handleExternalChannelMessage( + message.getPayload(), Boolean.TRUE.equals(dryRun), reworkId, messageId, senderId), + "pn-external_channel_to_paper_tracker", message, headers); + } + + @SqsListener("${pn.paper-tracker.topics.pn-ocr-outputs-queue}") + public void ocrOutputsConsumer( + @Payload Message message, + @Headers Map headers + ) { + processMessage(() -> ocrEventHandler.handleOcrMessage(message.getPayload()), + "pn-ocr_outputs", message, headers); + } + + private void processMessage(Runnable handler, String source, Message> message, Map headers) { + String anonymizedMessage = logUtility.messageToString(message, Set.of("discoveredAddress")); + try { + log.info("Handle message from {} with content {}", source, anonymizedMessage); + setMDCContext(headers); + handler.run(); + } catch (Exception ex) { + log.error("Error processing message from {}: {}", source, ex.getMessage(), ex); + throw ex; + } + } + + private void setMDCContext(Map headers) { + MDCUtils.clearMDCKeys(); + MDC.put(MDCUtils.MDC_PN_CTX_MESSAGE_ID, headers.getOrDefault("id", "").toString()); + MDC.put(MDCUtils.MDC_TRACE_ID_KEY, headers.getOrDefault("AWSTraceHeader", UUID.randomUUID().toString()).toString()); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/ExternalChannelHandler.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/ExternalChannelHandler.java new file mode 100644 index 000000000..dbff4ee52 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/ExternalChannelHandler.java @@ -0,0 +1,92 @@ +package it.pagopa.pn.papertracker.middleware.queue.consumer.internal; + +import it.pagopa.pn.commons.utils.MDCUtils; +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import it.pagopa.pn.papertracker.exception.PaperTrackerExceptionHandler; +import it.pagopa.pn.papertracker.exception.PnPaperTrackerValidationException; +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.SingleStatusUpdate; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.ProductType; +import it.pagopa.pn.papertracker.model.EventStatusCodeEnum; +import it.pagopa.pn.papertracker.model.EventTypeEnum; +import it.pagopa.pn.papertracker.model.HandlerContext; +import it.pagopa.pn.papertracker.service.handler_step.generic.HandlersRegistry; +import it.pagopa.pn.papertracker.utils.TrackerUtility; +import lombok.CustomLog; +import lombok.RequiredArgsConstructor; +import org.slf4j.MDC; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.Objects; +import java.util.Optional; + +import static it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.ProductType.ALL; +import static it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.ProductType.UNKNOWN; + +@Component +@RequiredArgsConstructor +@CustomLog +public class ExternalChannelHandler { + + private final PaperTrackerExceptionHandler paperTrackerExceptionHandler; + private final HandlersRegistry handlersRegistry; // contiene HandlersFactoryAr, HandlersFactoryRir, ecc. + private final PnPaperTrackerConfigs pnPaperTrackerConfigs; + + private static final String HANDLING_EVENT_LOG = "Handling {} event for productType: [{}] and event: [{}]"; + + /** + * Riceve i messaggi provenienti dalla coda pn-external_channel_to_paper_tracker e gestisce, invia l'evento all'handlersRegistry per lo + * smistamento allo specifico handler in base a productType ed eventType + * + * @param payload il SingleStatusUpdate contenente le informazioni da processare + */ + public void handleExternalChannelMessage(SingleStatusUpdate payload, boolean dryRunEnabled, String reworkId, String messageId, String senderId) { + if (Objects.isNull(payload) || Objects.isNull(payload.getAnalogMail())) { + log.error("Received null payload or analogMail in ExternalChannelHandler"); + throw new IllegalArgumentException("Payload or analogMail cannot be null"); + } + + String processName = "processExternalChannelMessage"; + MDC.put(MDCUtils.MDC_PN_CTX_REQUEST_ID, payload.getAnalogMail().getRequestId()); + log.logStartingProcess(processName); + HandlerContext context = initializeContext(payload, dryRunEnabled, reworkId, messageId, senderId); + + var statusCode = payload.getAnalogMail().getStatusCode(); + var productType = resolveProductType(statusCode, payload.getAnalogMail().getProductType()); + var eventType = Optional.ofNullable(EventStatusCodeEnum.fromKey(statusCode)).map(EventStatusCodeEnum::getCodeType).orElse(null); + + logStatusEvent(eventType, productType, statusCode); + + MDCUtils.addMDCToContextAndExecute(handlersRegistry.handleEvent(productType, eventType, context) + .doOnSuccess(unused -> log.logEndingProcess(processName)) + .onErrorResume(PnPaperTrackerValidationException.class, ex -> paperTrackerExceptionHandler.handleInternalException(ex, context.getMessageReceiveCount()))) + .block(); + } + + private HandlerContext initializeContext(SingleStatusUpdate payload, boolean dryRunEnabled, String reworkId, String messageId, String senderId) { + HandlerContext context = new HandlerContext(); + context.setTrackingId(payload.getAnalogMail().getRequestId()); + context.setPaperProgressStatusEvent(payload.getAnalogMail()); + context.setEventId(messageId); + context.setDryRunEnabled(dryRunEnabled); + context.setReworkId(reworkId); + context.setRedrive(TrackerUtility.checkIfIsRedrive(senderId, pnPaperTrackerConfigs.getRedriveEnabledDomains())); + return context; + } + + private ProductType resolveProductType(String statusCode, String payloadProductType) { + ProductType eventProductType = Optional.ofNullable(EventStatusCodeEnum.fromKey(statusCode)) + .map(EventStatusCodeEnum::getProductType) + .orElse(UNKNOWN); + + if (ALL.equals(eventProductType) && StringUtils.hasText(payloadProductType)) { + return ProductType.fromValue(payloadProductType); + } + return eventProductType; + } + + private void logStatusEvent(EventTypeEnum statusEvent, ProductType productType, String statusCode) { + log.info(HANDLING_EVENT_LOG, statusEvent, productType, statusCode); + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/ExternalChannelSourceEventsHandler.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/ExternalChannelSourceEventsHandler.java new file mode 100644 index 000000000..bb48d77d7 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/ExternalChannelSourceEventsHandler.java @@ -0,0 +1,70 @@ +package it.pagopa.pn.papertracker.middleware.queue.consumer.internal; + +import it.pagopa.pn.commons.utils.MDCUtils; +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.SingleStatusUpdate; +import it.pagopa.pn.papertracker.service.SourceQueueProxyService; +import lombok.CustomLog; +import lombok.RequiredArgsConstructor; +import org.slf4j.MDC; +import org.springframework.messaging.Message; +import org.springframework.stereotype.Component; +import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; + +import java.util.Map; +import java.util.Objects; + +/** + * Handler responsabile della gestione degli eventi provenienti da pn-external-channel (pn-ec). + * + * In fase di inizializzazione di una spedizione su pn-paper-tracker, viene determinata e persistita + * la modalità operativa (DRY o RUN) sull'entità della tabella {pn-PaperTrackings. + * + * + * + * Conseguentemente, alla ricezione di un evento da pn-ec, pn-paper-tracker determina la modalità + * operativa recuperando le informazioni di tracking dalla tabella pn-PaperTrackings + * e gestisce ciascun evento in base alla logica definita. + * + * + * + * + * Spedizione non presente su pn-PaperTrackings + * L'evento viene inoltrato direttamente a pn-paper-channel. + * + * + * Spedizione presente su pn-PaperTrackings e modalità DRY + * L'evento viene inoltrato a pn-paper-channel (se non duplicato) e, in parallelo, processato internamente + * da pn-paper-tracker, con persistenza dell'output su tabella a fini di verifica. + * + * + * Spedizione presente su pn-PaperTrackings e modalità RUN + * L'evento viene processato internamente da pn-paper-tracker e l'output + * viene inviato a pn-delivery-push. + * + * + * + * La gestione del flusso operativo effettivo è demandata al {@link SourceQueueProxyService}. + */ +@Component +@RequiredArgsConstructor +@CustomLog +public class ExternalChannelSourceEventsHandler { + private final SourceQueueProxyService sourceQueueProxyService; + + public void handleExternalChannelMessage( + SingleStatusUpdate message, + Map messageAttributes) { + if (Objects.isNull(message) || Objects.isNull(message.getAnalogMail())) { + log.error("Received null payload or analogMail in ExternalChannelHandler"); + throw new IllegalArgumentException("Payload or analogMail cannot be null"); + } + + String processName = "processExternalChannelSourceMessage"; + MDC.put(MDCUtils.MDC_PN_CTX_REQUEST_ID, message.getAnalogMail().getRequestId()); + log.logStartingProcess(processName); + + MDCUtils.addMDCToContextAndExecute(sourceQueueProxyService.handleExternalChannelMessage(message, messageAttributes) + .doOnSuccess(unused -> log.logEndingProcess(processName))) + .block(); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/OcrEventHandler.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/OcrEventHandler.java new file mode 100644 index 000000000..687361643 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/OcrEventHandler.java @@ -0,0 +1,60 @@ +package it.pagopa.pn.papertracker.middleware.queue.consumer.internal; + +import com.sngular.apigenerator.asyncapi.business_model.model.event.OcrDataResultPayload; +import it.pagopa.pn.commons.utils.MDCUtils; +import it.pagopa.pn.papertracker.exception.PaperTrackerExceptionHandler; +import it.pagopa.pn.papertracker.exception.PnPaperTrackerValidationException; +import it.pagopa.pn.papertracker.middleware.dao.PaperTrackingsDAO; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackings; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.ProductType; +import it.pagopa.pn.papertracker.model.EventTypeEnum; +import it.pagopa.pn.papertracker.model.HandlerContext; +import it.pagopa.pn.papertracker.service.handler_step.generic.HandlersRegistry; +import it.pagopa.pn.papertracker.utils.TrackerUtility; +import lombok.CustomLog; +import lombok.RequiredArgsConstructor; +import org.slf4j.MDC; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +import java.util.Objects; + +@Component +@RequiredArgsConstructor +@CustomLog +public class OcrEventHandler { + + private final PaperTrackingsDAO paperTrackingsDAO; + private final PaperTrackerExceptionHandler paperTrackerExceptionHandler; + private final HandlersRegistry handlersRegistry; + + public void handleOcrMessage(OcrDataResultPayload ocrResultMessage) { + if (Objects.isNull(ocrResultMessage) || Objects.isNull(ocrResultMessage.getCommandId())) { + log.error("Received null payload or commandId in OcrResponse handler"); + throw new IllegalArgumentException("Payload or commandId cannot be null"); + } + + String processName = "processOcrResponseMessage"; + MDC.put(MDCUtils.MDC_PN_CTX_REQUEST_ID, ocrResultMessage.getCommandId()); + log.logStartingProcess(processName); + MDCUtils.addMDCToContextAndExecute(paperTrackingsDAO.retrieveEntityByTrackingId(TrackerUtility.getParsedOcrCommandId(ocrResultMessage.getCommandId())[0]) + .flatMap(paperTrackings -> callOcrResponseHandler(paperTrackings, ocrResultMessage)) + .onErrorResume(PnPaperTrackerValidationException.class, e -> paperTrackerExceptionHandler.handleInternalException(e, null)) + .then()) + .block(); + } + + private Mono callOcrResponseHandler(PaperTrackings paperTrackings, OcrDataResultPayload ocrResultMessage) { + HandlerContext context = buildContext(paperTrackings, ocrResultMessage); + return handlersRegistry.handleEvent(ProductType.fromValue(paperTrackings.getProductType()), EventTypeEnum.OCR_RESPONSE_EVENT, context); + } + + private HandlerContext buildContext(PaperTrackings paperTrackings, + OcrDataResultPayload ocrResultMessage) { + HandlerContext handlerContext = new HandlerContext(); + handlerContext.setTrackingId(paperTrackings.getTrackingId()); + handlerContext.setPaperTrackings(paperTrackings); + handlerContext.setOcrDataResultPayload(ocrResultMessage); + return handlerContext; + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/CustomEventHeader.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/CustomEventHeader.java new file mode 100644 index 000000000..7f0436d40 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/CustomEventHeader.java @@ -0,0 +1,22 @@ +package it.pagopa.pn.papertracker.middleware.queue.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import it.pagopa.pn.api.dto.events.GenericEventHeader; +import lombok.*; +import lombok.experimental.SuperBuilder; +import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; + +import java.util.Map; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@EqualsAndHashCode(callSuper = true) +@ToString +@SuperBuilder +public class CustomEventHeader extends GenericEventHeader { + + @JsonProperty("messageAttributes") + private Map messageAttributes; + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/DeliveryPushEvent.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/DeliveryPushEvent.java new file mode 100644 index 000000000..3461506d4 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/DeliveryPushEvent.java @@ -0,0 +1,20 @@ +package it.pagopa.pn.papertracker.middleware.queue.model; + +import it.pagopa.pn.api.dto.events.GenericEvent; +import it.pagopa.pn.api.dto.events.GenericEventHeader; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.PaperChannelUpdate; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Builder(toBuilder = true) +public class DeliveryPushEvent implements GenericEvent { + + private GenericEventHeader header; + private PaperChannelUpdate payload; + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/ExternalChannelEvent.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/ExternalChannelEvent.java new file mode 100644 index 000000000..49d5fe473 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/ExternalChannelEvent.java @@ -0,0 +1,18 @@ +package it.pagopa.pn.papertracker.middleware.queue.model; + +import it.pagopa.pn.api.dto.events.GenericEvent; +import it.pagopa.pn.api.dto.events.GenericEventHeader; +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.SingleStatusUpdate; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.PaperChannelUpdate; +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Builder(toBuilder = true) +public class ExternalChannelEvent implements GenericEvent { + + private CustomEventHeader header; + private SingleStatusUpdate payload; + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/ExternalChannelEventHeader.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/ExternalChannelEventHeader.java new file mode 100644 index 000000000..ed02d376a --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/ExternalChannelEventHeader.java @@ -0,0 +1,20 @@ +package it.pagopa.pn.papertracker.middleware.queue.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import it.pagopa.pn.api.dto.events.GenericEventHeader; +import lombok.*; +import lombok.experimental.SuperBuilder; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@EqualsAndHashCode(callSuper = true) +@ToString +@SuperBuilder +public class ExternalChannelEventHeader extends GenericEventHeader { + + public static final String PN_EVENT_HEADER_DRYRUN = "dryRun"; + @JsonProperty("dryRun") + private Boolean dryRun; + +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/OcrEvent.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/OcrEvent.java new file mode 100644 index 000000000..3de4fbaaf --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/OcrEvent.java @@ -0,0 +1,23 @@ +package it.pagopa.pn.papertracker.middleware.queue.model; + +import com.sngular.apigenerator.asyncapi.business_model.model.event.OcrDataPayloadDTO; +import it.pagopa.pn.api.dto.events.GenericEvent; +import it.pagopa.pn.api.dto.events.GenericEventHeader; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class OcrEvent implements GenericEvent
+ * Per ogni eccezione intercettata, effettua una putItem sulla tabella PaperTrackingsError + */ + public Mono handleInternalException(final PnPaperTrackerValidationException ex, Long messageReceiveCount) { + return handleError(ex.getError(), messageReceiveCount, ex); + } + + public Mono handleRetryError(PaperTrackingsErrors paperTrackingsErrors) { + return handleError(paperTrackingsErrors, null, null); + } + + public Mono handleError(PaperTrackingsErrors paperTrackingsErrors, Long messageReceiveCount, PnPaperTrackerValidationException ex) { + boolean isStatusCodeError = Objects.nonNull(paperTrackingsErrors.getDetails()) && ErrorCause.VALUES_NOT_FOUND.equals(paperTrackingsErrors.getDetails().getCause()); + + if (isStatusCodeError) { + if (messageReceiveCount < 5) { + // Retry SQS + return Mono.error(ex); + } + log.error("Max retries reached for status code error, inserting error and updating PaperTrackings state to KO for trackingId: {}", paperTrackingsErrors.getTrackingId()); + } + + // Salva l'errore + return insertErrorAndUpdateTrackingsEntity(paperTrackingsErrors); + } + + private Mono insertErrorAndUpdateTrackingsEntity(PaperTrackingsErrors paperTrackingsErrors){ + return paperTrackerErrorService.insertPaperTrackingsError(paperTrackingsErrors) + .filter(errors -> paperTrackingsErrors.getType().equals(ErrorType.ERROR)) + .doOnDiscard(PaperTrackingsErrors.class, errors -> log.info("Skipped updating PaperTrackings entity for error with type Warning")) + .map(unused -> { + PaperTrackings paperTrackingsToUpdate = new PaperTrackings(); + String statusCode = paperTrackingsErrors.getEventThrow(); + setNewStatus(paperTrackingsToUpdate,statusCode, BusinessState.KO, PaperTrackingsState.KO); + setDematValidationTimestampIfNeeded(paperTrackingsErrors, paperTrackingsToUpdate,statusCode); + return paperTrackingsToUpdate; + }) + .flatMap(paperTrackingsToUpdate -> paperTrackerTrackingService.updatePaperTrackingsStatus(paperTrackingsErrors.getTrackingId(), paperTrackingsToUpdate)) + .doOnError(throwable -> log.error("Error inserting entity into PaperTrackingsErrors: {}", throwable.getMessage(), throwable)); + } + + private static void setDematValidationTimestampIfNeeded(PaperTrackingsErrors paperTrackingsErrors, PaperTrackings paperTrackingsToUpdate, String statusCode) { + if (Objects.nonNull(paperTrackingsErrors.getDetails().getCause()) && paperTrackingsErrors.getDetails().getCause().equals(ErrorCause.OCR_KO)) { + setDematValidationTimestamp(paperTrackingsToUpdate, statusCode); + } + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerBadRequestException.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerBadRequestException.java new file mode 100644 index 000000000..eb8bc5657 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerBadRequestException.java @@ -0,0 +1,13 @@ +package it.pagopa.pn.papertracker.exception; + +import it.pagopa.pn.commons.exceptions.PnRuntimeException; +import org.springframework.http.HttpStatus; + +public class PnPaperTrackerBadRequestException extends PnRuntimeException { + + private static final String DEFAULT_ERROR_CODE = "PN_PAPER_TRACKER_BAD_REQUEST"; + + public PnPaperTrackerBadRequestException(String errorCode, String message) { + super(message, DEFAULT_ERROR_CODE, HttpStatus.BAD_REQUEST.value(), errorCode, null, message); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerConflictException.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerConflictException.java new file mode 100644 index 000000000..1af7a3a81 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerConflictException.java @@ -0,0 +1,11 @@ +package it.pagopa.pn.papertracker.exception; + +import it.pagopa.pn.commons.exceptions.PnInternalException; +import org.springframework.http.HttpStatus; + +public class PnPaperTrackerConflictException extends PnInternalException { + + public PnPaperTrackerConflictException(String errorCode, String message) { + super(message, HttpStatus.CONFLICT.value(), errorCode); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerNotFoundException.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerNotFoundException.java new file mode 100644 index 000000000..ad6eb1f6e --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerNotFoundException.java @@ -0,0 +1,11 @@ +package it.pagopa.pn.papertracker.exception; + +import it.pagopa.pn.commons.exceptions.PnInternalException; +import org.springframework.http.HttpStatus; + +public class PnPaperTrackerNotFoundException extends PnInternalException { + + public PnPaperTrackerNotFoundException(String errorCode, String message) { + super(message, HttpStatus.NOT_FOUND.value(), errorCode); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerValidationException.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerValidationException.java new file mode 100644 index 000000000..def4fbb0b --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/exception/PnPaperTrackerValidationException.java @@ -0,0 +1,15 @@ +package it.pagopa.pn.papertracker.exception; + +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackingsErrors; +import lombok.Getter; + +@Getter +public class PnPaperTrackerValidationException extends RuntimeException { + + private final PaperTrackingsErrors error; + + public PnPaperTrackerValidationException(String message, PaperTrackingsErrors error) { + super(message); + this.error = error; + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperProgressStatusEventMapper.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperProgressStatusEventMapper.java new file mode 100644 index 000000000..57a8fdbaa --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperProgressStatusEventMapper.java @@ -0,0 +1,97 @@ +package it.pagopa.pn.papertracker.mapper; + +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.AttachmentDetails; +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.PaperProgressStatusEvent; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.*; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor(access = AccessLevel.NONE) +public class PaperProgressStatusEventMapper { + + /** + * Rimappa l'oggetto PaperProgressStatusEvent nell'entity PaperTrackings in modo da effettuare l'upsert + * + * @param paperProgressStatusEvent evento da rimappare + * @param anonymizedDiscoveredAddressId l'id dell'indirizzo anonimizzato + * @return PaperTrackings contenente il nuovo evento + */ + public static PaperTrackings toPaperTrackings(String reworkId, PaperProgressStatusEvent paperProgressStatusEvent, + String anonymizedDiscoveredAddressId, + String eventId, boolean dryRunEnabled, + boolean isFinalDemat, boolean isP000event, + boolean isInternalEvent, boolean isRecag012event) { + Instant now = Instant.now(); + PaperTrackings paperTrackings = new PaperTrackings(); + Event event = new Event(); + event.setId(eventId); + event.setIun(paperProgressStatusEvent.getIun()); + if (!CollectionUtils.isEmpty(paperProgressStatusEvent.getAttachments())) { + event.setAttachments(paperProgressStatusEvent.getAttachments().stream() + .map(PaperProgressStatusEventMapper::buildAttachmentFromAttachmentDetail) + .toList()); + }else{ + event.setAttachments(new ArrayList<>()); + } + + if(isFinalDemat) { + PaperStatus paperStatus = new PaperStatus(); + paperStatus.setFinalDematFound(true); + paperTrackings.setPaperStatus(paperStatus); + } + + if (isP000event) { + PaperStatus paperStatus = new PaperStatus(); + paperStatus.setPaperDeliveryTimestamp(paperProgressStatusEvent.getStatusDateTime().toInstant()); + paperTrackings.setPaperStatus(paperStatus); + } + + if (!isInternalEvent){ + event.setRequestTimestamp(paperProgressStatusEvent.getClientRequestTimeStamp().toInstant()); + } + + if(isRecag012event){ + ValidationFlow validationFlow = new ValidationFlow(); + validationFlow.setRecag012StatusTimestamp(now); + paperTrackings.setValidationFlow(validationFlow); + } + + event.setStatusCode(paperProgressStatusEvent.getStatusCode()); + event.setStatusTimestamp(paperProgressStatusEvent.getStatusDateTime().toInstant()); + + event.setDeliveryFailureCause(paperProgressStatusEvent.getDeliveryFailureCause()); + event.setRegisteredLetterCode(paperProgressStatusEvent.getRegisteredLetterCode()); + event.setNotificationReworkId(reworkId); + if(StringUtils.hasText(paperProgressStatusEvent.getProductType())) { + event.setProductType(paperProgressStatusEvent.getProductType()); + } + event.setAnonymizedDiscoveredAddressId(anonymizedDiscoveredAddressId); + event.setDryRun(dryRunEnabled); + event.setStatusDescription(paperProgressStatusEvent.getStatusDescription()); + event.setCreatedAt(now); + event.setPrinter(paperProgressStatusEvent.getPrinter()); + event.setDu(paperProgressStatusEvent.getDu()); + + paperTrackings.setEvents(List.of(event)); + return paperTrackings; + } + + public static Attachment buildAttachmentFromAttachmentDetail(AttachmentDetails attachmentDetails) { + Attachment attachment = new Attachment(); + attachment.setDate(attachmentDetails.getDate().toInstant()); + attachment.setId(attachmentDetails.getId()); + attachment.setDocumentType(attachmentDetails.getDocumentType()); + attachment.setUri(attachmentDetails.getUri()); + attachment.setSha256(attachmentDetails.getSha256()); + attachment.setSourceType(attachmentDetails.getSourceType()); + attachment.setOriginType(attachmentDetails.getOriginType()); + return attachment; + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackerDryRunOutputsMapper.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackerDryRunOutputsMapper.java new file mode 100644 index 000000000..6b3cd82be --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackerDryRunOutputsMapper.java @@ -0,0 +1,56 @@ +package it.pagopa.pn.papertracker.mapper; + +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.AttachmentDetails; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.SendEvent; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.Attachment; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackerDryRunOutputs; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import org.springframework.util.CollectionUtils; + +import java.time.Instant; +import java.util.Objects; + +@RequiredArgsConstructor(access = AccessLevel.NONE) +public class PaperTrackerDryRunOutputsMapper { + + public static PaperTrackerDryRunOutputs dtoToEntity(SendEvent event, String anonymizedDiscoveredAddressId) { + PaperTrackerDryRunOutputs dryRunOutput = new PaperTrackerDryRunOutputs(); + dryRunOutput.setTrackingId(event.getRequestId()); + dryRunOutput.setCreated(Instant.now()); + dryRunOutput.setRegisteredLetterCode(event.getRegisteredLetterCode()); + dryRunOutput.setStatusDetail(event.getStatusDetail()); + dryRunOutput.setStatusDescription(event.getStatusDescription()); + dryRunOutput.setDeliveryFailureCause(event.getDeliveryFailureCause()); + dryRunOutput.setAnonymizedDiscoveredAddressId(anonymizedDiscoveredAddressId); + + if(Objects.nonNull(event.getStatusCode())) { + dryRunOutput.setStatusCode(event.getStatusCode().name()); + } + + if(Objects.nonNull(event.getStatusDateTime())) { + dryRunOutput.setStatusDateTime(event.getStatusDateTime().toString()); + } + + if(!CollectionUtils.isEmpty(event.getAttachments())) { + dryRunOutput.setAttachments((event.getAttachments().stream() + .map(PaperTrackerDryRunOutputsMapper::buildAttachmentForExternalChannelOutputEvent) + .toList())); + } + + if(Objects.nonNull(event.getClientRequestTimeStamp())) { + dryRunOutput.setClientRequestTimestamp(event.getClientRequestTimeStamp().toString()); + } + + return dryRunOutput; + } + + private static Attachment buildAttachmentForExternalChannelOutputEvent(AttachmentDetails attachment) { + Attachment attachmentEntity = new Attachment(); + attachmentEntity.setId(attachment.getId()); + attachmentEntity.setDate(attachment.getDate().toInstant()); + attachmentEntity.setDocumentType(attachment.getDocumentType()); + attachmentEntity.setUri(attachment.getUrl()); + return attachmentEntity; + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackerMapStructMapper.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackerMapStructMapper.java new file mode 100644 index 000000000..4be8c1048 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackerMapStructMapper.java @@ -0,0 +1,75 @@ +package it.pagopa.pn.papertracker.mapper; + +import it.pagopa.pn.papertracker.generated.openapi.server.v1.dto.PaperTrackerOutput; +import it.pagopa.pn.papertracker.generated.openapi.server.v1.dto.Tracking; +import it.pagopa.pn.papertracker.generated.openapi.server.v1.dto.TrackingError; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.BusinessState; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.ErrorType; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackerDryRunOutputs; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackings; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackingsErrors; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackingsState; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import java.time.Instant; + +@Mapper( + componentModel = "spring", + unmappedTargetPolicy = ReportingPolicy.ERROR, + unmappedSourcePolicy = ReportingPolicy.IGNORE +) +public interface PaperTrackerMapStructMapper { + + @Mapping(target = "notificationReworkTimestamp", source = "notificationReworkRequestTimestamp") + Tracking toTracking(PaperTrackings paperTrackings); + + TrackingError toTrackingError(PaperTrackingsErrors paperTrackingsErrors); + + PaperTrackerOutput toDtoPaperTrackerOutput(PaperTrackerDryRunOutputs entity); + + default String map(Instant instant) { + return instant != null ? instant.toString() : null; + } + + default Tracking.StateEnum map(PaperTrackingsState state) { + if (state == null) { + return null; + } + + return switch (state) { + case AWAITING_FINAL_STATUS_CODE, AWAITING_REFINEMENT -> Tracking.StateEnum.AWAITING_REFINEMENT; + case AWAITING_REWORK_EVENTS -> Tracking.StateEnum.AWAITING_REWORK_EVENTS; + case AWAITING_OCR -> Tracking.StateEnum.AWAITING_OCR; + case DONE -> Tracking.StateEnum.DONE; + case KO -> Tracking.StateEnum.KO; + }; + } + + default Tracking.BusinessStateEnum map(BusinessState businessState) { + if (businessState == null) { + return null; + } + + return switch (businessState) { + case AWAITING_FINAL_STATUS_CODE -> Tracking.BusinessStateEnum.AWAITING_FINAL_STATUS_CODE; + case AWAITING_REFINEMENT_OCR -> Tracking.BusinessStateEnum.AWAITING_OCR; + case AWAITING_REWORK_EVENTS -> Tracking.BusinessStateEnum.AWAITING_REWORK_EVENTS; + case AWAITING_OCR -> Tracking.BusinessStateEnum.AWAITING_REFINEMENT_OCR; + case DONE -> Tracking.BusinessStateEnum.DONE; + case KO -> Tracking.BusinessStateEnum.KO; + }; + } + + default TrackingError.TypeEnum map(ErrorType errorType) { + if (errorType == null) { + return null; + } + + return switch (errorType) { + case ERROR -> TrackingError.TypeEnum.ERROR; + case INFO, WARNING -> TrackingError.TypeEnum.WARNING; + }; + } +} + diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackingsErrorsMapper.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackingsErrorsMapper.java new file mode 100644 index 000000000..a7f24d6f3 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackingsErrorsMapper.java @@ -0,0 +1,44 @@ +package it.pagopa.pn.papertracker.mapper; + +import it.pagopa.pn.papertracker.generated.openapi.server.v1.dto.TrackingError; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.*; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; + +import java.time.Instant; +import java.util.Map; + +@RequiredArgsConstructor(access = AccessLevel.NONE) +public class PaperTrackingsErrorsMapper { + + public static PaperTrackingsErrors buildPaperTrackingsError(PaperTrackings paperTrackings, + String statusCode, + ErrorCategory errorCategory, + ErrorCause errorCause, + String errorMessage, + Map additionalDetails, + FlowThrow flowThrow, + ErrorType errorType, + String eventIdThrow) { + return PaperTrackingsErrors.builder() + .trackingId(paperTrackings.getTrackingId()) + .created(Instant.now()) + .errorCategory(errorCategory) + .details(ErrorDetails.builder() + .cause(errorCause) + .message(errorMessage) + .additionalDetails(additionalDetails) + .build()) + .flowThrow(flowThrow) + .eventThrow(statusCode) + .productType(paperTrackings.getProductType()) + .unifiedDeliveryDriver(paperTrackings.getUnifiedDeliveryDriver()) + .registeredLetterCode(paperTrackings.getPaperStatus() != null + ? paperTrackings.getPaperStatus().getRegisteredLetterCode() + : null) + .type(errorType) + .eventIdThrow(eventIdThrow) + .build(); + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackingsMapper.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackingsMapper.java new file mode 100644 index 000000000..4f2e6f699 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/PaperTrackingsMapper.java @@ -0,0 +1,105 @@ +package it.pagopa.pn.papertracker.mapper; + +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import it.pagopa.pn.papertracker.config.TrackerConfigUtils; +import it.pagopa.pn.papertracker.generated.openapi.server.v1.dto.Tracking; +import it.pagopa.pn.papertracker.generated.openapi.server.v1.dto.TrackingCreationRequest; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.*; +import it.pagopa.pn.papertracker.model.FileType; +import it.pagopa.pn.papertracker.model.OcrStatusEnum; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.List; +import java.util.Objects; + +@Slf4j +@RequiredArgsConstructor(access = AccessLevel.NONE) +public class PaperTrackingsMapper { + + public static PaperTrackings toPaperTrackings(TrackingCreationRequest trackingCreationRequest, + TrackerConfigUtils trackerConfigUtils, + PnPaperTrackerConfigs pnPaperTrackerConfigs, + Instant now, + String xOriginClientId) { + ProductType productType = ProductType.fromValue(trackingCreationRequest.getProductType()); + LocalDate localDate = LocalDate.now(ZoneId.of("Europe/Rome")); + PaperTrackings paperTrackings = new PaperTrackings(); + paperTrackings.setTrackingId(String.join(".", trackingCreationRequest.getAttemptId(), trackingCreationRequest.getPcRetry())); + paperTrackings.setUnifiedDeliveryDriver(trackingCreationRequest.getUnifiedDeliveryDriver()); + paperTrackings.setProductType(trackingCreationRequest.getProductType()); + paperTrackings.setState(PaperTrackingsState.AWAITING_REFINEMENT); + paperTrackings.setBusinessState(BusinessState.AWAITING_FINAL_STATUS_CODE); + paperTrackings.setAttemptId(trackingCreationRequest.getAttemptId()); + paperTrackings.setPcRetry(trackingCreationRequest.getPcRetry()); + paperTrackings.setCreatedAt(now); + paperTrackings.setProcessingMode(trackerConfigUtils.getActualProductsProcessingModes(localDate).get(productType)); + paperTrackings.setAnalogRequestClientId(xOriginClientId); + PaperStatus paperStatus = new PaperStatus(); + paperTrackings.setPaperStatus(paperStatus); + ValidationFlow validationFlow = new ValidationFlow(); + validationFlow.setOcrRequests(List.of()); + paperTrackings.setValidationFlow(validationFlow); + ValidationConfig validationConfig = new ValidationConfig(); + validationConfig.setOcrFilterTemporal(pnPaperTrackerConfigs.getOcrFilterTemporal()); + validationConfig.setOcrFilterUnifiedDeliveryDriver(pnPaperTrackerConfigs.getOcrFilterUnifiedDeliveryDriver()); + validationConfig.setOcrEnabled(evaluateIfOcrIsEnabled(trackerConfigUtils, productType, now, trackingCreationRequest, localDate)); + validationConfig.setOcrFileTypes(pnPaperTrackerConfigs.getEnableOcrValidationForFile().stream().map(FileType::getValue).toList()); + validationConfig.setRequiredAttachmentsRefinementStock890(trackerConfigUtils.getActualRequiredAttachmentsRefinementStock890(localDate)); + validationConfig.setSendOcrAttachmentsRefinementStock890(trackerConfigUtils.getActualSendOcrAttachmentsRefinementStock890(localDate)); + validationConfig.setSendOcrAttachmentsFinalValidation(trackerConfigUtils.getActualSendOcrAttachmentsFinalValidation(localDate)); + validationConfig.setSendOcrAttachmentsFinalValidationStock890(trackerConfigUtils.getActualSendOcrAttachmentsFinalValidationStock890(localDate)); + validationConfig.setStrictFinalValidationStock890(trackerConfigUtils.getActualStrictFinalValidationStock890(localDate)); + validationConfig.setStrictDeliveryFailureCause(trackerConfigUtils.getActualStrictDeliveryFailureCause(localDate)); + paperTrackings.setValidationConfig(validationConfig); + return paperTrackings; + } + + private static OcrStatusEnum evaluateIfOcrIsEnabled(TrackerConfigUtils trackerConfigUtils, + ProductType productType, + Instant now, + TrackingCreationRequest trackingCreationRequest, + LocalDate localDate) { + + OcrStatusEnum ocrStatusEnum = trackerConfigUtils.getActualEnableOcrValidationFor(localDate).get(productType); + + if (Objects.isNull(ocrStatusEnum) || OcrStatusEnum.DISABLED.equals(ocrStatusEnum)) { + return OcrStatusEnum.DISABLED; + } + + if (OcrStatusEnum.DRY.equals(ocrStatusEnum)) { + return OcrStatusEnum.DRY; + } + + // Siamo in modalità OCR:RUN + + boolean isTemporalFilterConfigured = !trackerConfigUtils.isOcrFilterTemporalDisabled(); + boolean isUnifiedDeliveryDriverFilterConfigured = !trackerConfigUtils.isOcrFilterDriverDisabled(); + + // Caso 1: entrambi i filtri disattivi + if (!isTemporalFilterConfigured && !isUnifiedDeliveryDriverFilterConfigured) { + log.info("Both OCR filters are disabled. OCR will be enabled in RUN mode"); + return OcrStatusEnum.RUN; + } + + boolean isTemporalFilterActive = isTemporalFilterConfigured && trackerConfigUtils.isOcrFilterTemporalActive(now); + boolean isUnifiedDeliveryDriverFilterActive = isUnifiedDeliveryDriverFilterConfigured && + trackerConfigUtils.isOcrFilterDriverActive(trackingCreationRequest.getUnifiedDeliveryDriver()); + + log.info("Temporal filter is active: {}", isTemporalFilterActive); + log.info("UnifiedDeliveryDriver {} is filtered: {}", trackingCreationRequest.getUnifiedDeliveryDriver(), isUnifiedDeliveryDriverFilterActive); + + // Caso 2: entrambi i filtri sono configurati + if (isTemporalFilterConfigured && isUnifiedDeliveryDriverFilterConfigured) { + return (isTemporalFilterActive && isUnifiedDeliveryDriverFilterActive) ? OcrStatusEnum.RUN : OcrStatusEnum.DRY; + } + + // Caso 3: solo uno dei due filtri è configurato + return (isTemporalFilterActive || isUnifiedDeliveryDriverFilterActive) ? OcrStatusEnum.RUN : OcrStatusEnum.DRY; + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/SendEventMapper.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/SendEventMapper.java new file mode 100644 index 000000000..0c6df62f3 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/mapper/SendEventMapper.java @@ -0,0 +1,153 @@ +package it.pagopa.pn.papertracker.mapper; + +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.DiscoveredAddress; +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.PaperProgressStatusEvent; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.AnalogAddress; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.AttachmentDetails; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.SendEvent; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.StatusCodeEnum; +import it.pagopa.pn.papertracker.generated.openapi.msclient.pndatavault.model.PaperAddress; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.Attachment; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.Event; +import it.pagopa.pn.papertracker.model.EventStatusCodeEnum; +import it.pagopa.pn.papertracker.utils.TrackerUtility; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.CollectionUtils; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@RequiredArgsConstructor(access = AccessLevel.NONE) +@Slf4j +public class SendEventMapper { + + /** + * Crea l'evento dalla classe PaperProgressStatusEvent e lo inserisce dentro SendEvent per l'invio a delivery-push. + * Se si tratta di un evento intermedio senza allegati ed è un final demat, non viene inviato a delivery-push. + * + * @param paperProgressStatusEvent evento intermedio ricevuto + * @return SendEvent contenente l'evento rimappato da inviare a delivery-push + */ + public static Flux createSendEventsFromPaperProgressStatusEvent(PaperProgressStatusEvent paperProgressStatusEvent) { + boolean isFinalDemat = TrackerUtility.checkIfIsFinalDemat(paperProgressStatusEvent.getStatusCode()); + return Mono.just(paperProgressStatusEvent) + .flatMapMany(progressEvent -> { + if (!CollectionUtils.isEmpty(progressEvent.getAttachments())) { + return Flux.fromIterable(progressEvent.getAttachments().stream() + .map(attachmentDetails -> buildSendEvent(progressEvent, attachmentDetails)) + .collect(Collectors.toList())); + } else if(!isFinalDemat) + return Flux.just(buildSendEvent(progressEvent, null)); + + log.info("Event with statusCode {} is a final demat without attachments, skipping sending to delivery-push", progressEvent.getStatusCode()); + return Flux.empty(); + } + ); + } + + public static SendEvent buildSendEvent(PaperProgressStatusEvent progressEvent, it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.AttachmentDetails attachmentDetails) { + SendEvent.SendEventBuilder builder = SendEvent.builder() + .requestId(progressEvent.getRequestId()) + .statusCode(StatusCodeEnum.valueOf(EventStatusCodeEnum.valueOf(progressEvent.getStatusCode()).getStatus().name())) + .statusDetail(progressEvent.getStatusCode()) + .statusDescription(progressEvent.getProductType() + " - " + progressEvent.getStatusCode() + " - " + progressEvent.getStatusDescription()) + .deliveryFailureCause(progressEvent.getDeliveryFailureCause()) + .registeredLetterCode(progressEvent.getRegisteredLetterCode()) + .statusDateTime(progressEvent.getStatusDateTime()) + .clientRequestTimeStamp(progressEvent.getClientRequestTimeStamp()); + + if(Objects.nonNull(progressEvent.getDiscoveredAddress())){ + builder.discoveredAddress(toAnalogAddress(progressEvent.getDiscoveredAddress())); + } + if(Objects.nonNull(attachmentDetails)){ + builder.attachments(buildPaperAttachmentDetailsFromExternalChannels(List.of(attachmentDetails))); + } + + return builder.build(); + } + + public static Flux createSendEventsFromEventEntity(String requestId, Event event, StatusCodeEnum statusCode, String statusDetail, OffsetDateTime statusDateTime) { + return Mono.just(event) + .flatMapMany(progressEvent -> { + if (!CollectionUtils.isEmpty(progressEvent.getAttachments())) { + return Flux.fromIterable(progressEvent.getAttachments().stream() + .map(attachmentDetails -> toSendEvent(requestId, event, statusCode, statusDetail, statusDateTime, attachmentDetails)) + .collect(Collectors.toList())); + } + return Flux.just(toSendEvent(requestId, event, statusCode, statusDetail, statusDateTime, null)); + } + ); + } + + public static SendEvent toSendEvent(String requestId, Event event, StatusCodeEnum statusCode, String statusDetail, OffsetDateTime statusDateTime, Attachment attachmentDetails) { + SendEvent.SendEventBuilder builder = SendEvent.builder() + .requestId(requestId) + .statusCode(statusCode) + .statusDetail(statusDetail) + .statusDescription(event.getProductType() + " - " + statusDetail + " - " + event.getStatusDescription()) + .deliveryFailureCause(event.getDeliveryFailureCause()) + .registeredLetterCode(event.getRegisteredLetterCode()) + .statusDateTime(statusDateTime) + .clientRequestTimeStamp(event.getRequestTimestamp().atOffset(ZoneOffset.UTC)); + + if(Objects.nonNull(attachmentDetails)){ + builder.attachments(buildAttachmentDetails(event.getAttachments())); + } + + return builder.build(); + } + + private static List buildAttachmentDetails(List attachments) { + return attachments.stream().map(attachment -> AttachmentDetails.builder() + .documentType(attachment.getDocumentType()) + .date(attachment.getDate().atOffset(ZoneOffset.UTC)) + .id(attachment.getId()) + .url(attachment.getUri()) + .build()).toList(); + } + + private static List buildPaperAttachmentDetailsFromExternalChannels(List attachments) { + return attachments.stream().map(attachment -> AttachmentDetails.builder() + .documentType(attachment.getDocumentType()) + .date(attachment.getDate()) + .id(attachment.getId()) + .url(attachment.getUri()) + .build()).toList(); + } + + public static AnalogAddress toAnalogAddress(DiscoveredAddress discoveredAddress) { + return AnalogAddress.builder() + .address(discoveredAddress.getAddress()) + .addressRow2(discoveredAddress.getAddressRow2()) + .cap(discoveredAddress.getCap()) + .pr(discoveredAddress.getPr()) + .city(discoveredAddress.getCity()) + .city2(discoveredAddress.getCity2()) + .country(discoveredAddress.getCountry()) + .fullname(discoveredAddress.getName()) + .nameRow2(discoveredAddress.getNameRow2()) + .build(); + } + + public static AnalogAddress toAnalogAddress(PaperAddress paperAddress) { + return AnalogAddress.builder() + .address(paperAddress.getAddress()) + .addressRow2(paperAddress.getAddressRow2()) + .cap(paperAddress.getCap()) + .pr(paperAddress.getPr()) + .city(paperAddress.getCity()) + .city2(paperAddress.getCity2()) + .country(paperAddress.getCountry()) + .fullname(paperAddress.getName()) + .nameRow2(paperAddress.getNameRow2()) + .build(); + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/PaperTrackerDryRunOutputsDAO.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/PaperTrackerDryRunOutputsDAO.java new file mode 100644 index 000000000..4b91c4e08 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/PaperTrackerDryRunOutputsDAO.java @@ -0,0 +1,12 @@ +package it.pagopa.pn.papertracker.middleware.dao; + +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackerDryRunOutputs; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public interface PaperTrackerDryRunOutputsDAO { + + Flux retrieveOutputEvents(String requestId); + + Mono insertOutputEvent(PaperTrackerDryRunOutputs paperTrackerDryRunOutputs); +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/PaperTrackingsDAO.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/PaperTrackingsDAO.java new file mode 100644 index 000000000..7b1fe580f --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/PaperTrackingsDAO.java @@ -0,0 +1,25 @@ +package it.pagopa.pn.papertracker.middleware.dao; + +import com.sngular.apigenerator.asyncapi.business_model.model.event.Data; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackings; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; + +public interface PaperTrackingsDAO { + + Mono putIfAbsent(PaperTrackings entity); + + Mono retrieveEntityByTrackingId(String trackingId); + + Flux retrieveEntityByAttemptId(String attemptId, String pcRetry); + + Mono updateItem(String requestId, PaperTrackings paperTrackings); + + Mono updateItemForRework(String trackingId, PaperTrackings paperTrackings); + + Mono updateOcrRequests(Integer ocrRequestIndex, String trackingId, Data.ValidationStatus validationStatus); + + Flux retrieveAllByTrackingIds(List trackingIds); +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/PaperTrackingsErrorsDAO.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/PaperTrackingsErrorsDAO.java new file mode 100644 index 000000000..cf5bd2b19 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/PaperTrackingsErrorsDAO.java @@ -0,0 +1,13 @@ +package it.pagopa.pn.papertracker.middleware.dao; + +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackingsErrors; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public interface PaperTrackingsErrorsDAO { + + Mono insertError(PaperTrackingsErrors entity); + + Flux retrieveErrors(String requestId); + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/AdditionalDetailsConverter.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/AdditionalDetailsConverter.java new file mode 100644 index 000000000..ca73d2b7a --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/AdditionalDetailsConverter.java @@ -0,0 +1,95 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo; + +import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter; +import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType; +import software.amazon.awssdk.enhanced.dynamodb.EnhancedType; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +public class AdditionalDetailsConverter implements AttributeConverter> { + + @Override + public AttributeValue transformFrom(Map value) { + if (Objects.isNull(value)) { + return AttributeValue.builder().nul(true).build(); + } + return AttributeValue.builder().m(value.entrySet().stream() + .collect(java.util.stream.Collectors.toMap( + Map.Entry::getKey, + e -> toAttr(e.getValue()) + ))).build(); + } + + @Override + public Map transformTo(AttributeValue attributeValue) { + if (Boolean.TRUE.equals(attributeValue.nul())) { + return null; + } + if (Objects.isNull(attributeValue.m())) { + return Map.of(); + } + return attributeValue.m().entrySet().stream().collect(java.util.stream.Collectors.toMap( + Map.Entry::getKey, + e -> Optional.ofNullable(fromAttr(e.getValue())).orElse("") + )); + } + + @Override + public AttributeValueType attributeValueType() { + return AttributeValueType.M; + } + + @Override + public EnhancedType> type() { + return EnhancedType.mapOf(EnhancedType.of(String.class), EnhancedType.of(Object.class)); + } + + /* -------- conversioni -------- */ + + private AttributeValue toAttr(Object v) { + return switch (v) { + case String s -> AttributeValue.builder().s(s).build(); + case Number n -> AttributeValue.builder().n(n.toString()).build(); + case Boolean b -> AttributeValue.builder().bool(b).build(); + case Map, ?> m -> AttributeValue.builder().m(m.entrySet().stream() + .collect(Collectors.toMap(e -> e.getKey().toString(), e -> toAttr(e.getValue())))) + .build(); + case List> l -> AttributeValue.builder().l(l.stream().map(this::toAttr).toList()).build(); + case null -> AttributeValue.builder().nul(true).build(); + default -> throw new IllegalArgumentException("Unsupported type in additionalDetails: " + v.getClass()); + }; + } + + private Object fromAttr(AttributeValue attributeValue) { + switch (attributeValue.type()) { + case S -> { + if (Objects.nonNull(attributeValue.s())) return attributeValue.s(); + } + case N -> { + if (Objects.nonNull(attributeValue.n())) return attributeValue.n(); + } + case BOOL -> { + if (Objects.nonNull(attributeValue.bool())) return attributeValue.bool(); + } + case M -> { + if (Objects.nonNull(attributeValue.m())) { + return attributeValue.m().entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> Optional.ofNullable(fromAttr(e.getValue())).orElse(""))); + } + } + case L -> { + if (Objects.nonNull(attributeValue.l())) { + return attributeValue.l().stream() + .map(this::fromAttr) + .toList(); + } + } + } + return null; + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/BaseDao.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/BaseDao.java new file mode 100644 index 000000000..e9da3a8f6 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/BaseDao.java @@ -0,0 +1,184 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo; + +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import software.amazon.awssdk.enhanced.dynamodb.*; +import software.amazon.awssdk.enhanced.dynamodb.model.*; +import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest; +import software.amazon.awssdk.services.dynamodb.model.UpdateItemResponse; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +@Slf4j +public class BaseDao { + private final DynamoDbAsyncTable tableAsync; + private final Class tClass; + private final DynamoDbAsyncClient dynamoDbAsyncClient; + private final DynamoDbEnhancedAsyncClient dynamoDbEnhancedAsyncClient; + + protected BaseDao( + DynamoDbEnhancedAsyncClient dynamoDbEnhancedAsyncClient, + DynamoDbAsyncClient dynamoDbAsyncClient, + String tableName, + Class tClass + ) { + this.tableAsync = dynamoDbEnhancedAsyncClient.table(tableName, TableSchema.fromBean(tClass)); + this.tClass = tClass; + this.dynamoDbAsyncClient = dynamoDbAsyncClient; + this.dynamoDbEnhancedAsyncClient = dynamoDbEnhancedAsyncClient; + } + + protected Mono putItem(T entity) { + return Mono.fromFuture(this.tableAsync.putItem(entity)) + .thenReturn(entity); + } + + public Flux retrieveByRequestId(String pk) { + log.info("retrieve Items from [{}] table, for requestId={}", tableAsync.tableName(), pk); + return Flux.from(tableAsync.query(r -> r.queryConditional( + QueryConditional.keyEqualTo(Key.builder().partitionValue(pk).build())))) + .flatMap(page -> Flux.fromIterable(page.items())) + .doOnError(e -> log.error("Error retrieving items for requestId {}: {}", pk, e.getMessage())); + } + + public Mono getByKey(Key key) { + return Mono.fromFuture(tableAsync.getItem(key)); + } + + public Flux queryByIndex(Key build, String indexName) { + QueryEnhancedRequest request = QueryEnhancedRequest.builder() + .queryConditional(QueryConditional.keyEqualTo(build)) + .build(); + + return Mono.from(tableAsync.index(indexName).query(request)) + .map(Page::items) + .flatMapMany(Flux::fromIterable) + .doOnError(e -> log.error("Error querying index {}: {}", indexName, e.getMessage())); + } + + public Flux retrieveFromIndex(String indexName, QueryConditional queryConditional) { + return Mono.from(tableAsync.index(indexName) + .query(QueryEnhancedRequest.builder().queryConditional(queryConditional).build())) + .map(Page::items) + .flatMapMany(Flux::fromIterable); + } + + public Mono putIfAbsent(String expression, T entity) { + + Expression conditionExpressionPut = Expression.builder() + .expression(expression) + .build(); + + PutItemEnhancedRequest request = PutItemEnhancedRequest.builder(tClass) + .item(entity) + .conditionExpression(conditionExpressionPut) + .build(); + + return Mono.fromFuture(tableAsync.putItem(request)) + .thenReturn(entity); + } + + public Mono updateIfExists(Map key, String updateExpression, Map expressionValues, Map expressionNames, String conditionExpression) { + UpdateItemRequest updateItemRequest = UpdateItemRequest.builder() + .tableName(tableAsync.tableName()) + .key(key) + .updateExpression(updateExpression) + .expressionAttributeValues(expressionValues) + .expressionAttributeNames(expressionNames) + .conditionExpression(conditionExpression) + .returnValues("ALL_NEW") + .build(); + + return Mono.fromFuture(dynamoDbAsyncClient.updateItem(updateItemRequest)); + } + + protected Flux findAllByKeys(List partitionKeys) { + ReadBatch.Builder builder = ReadBatch.builder(tClass) + .mappedTableResource(this.tableAsync); + + partitionKeys.forEach(partitionKey -> + builder.addGetItem(Key.builder().partitionValue(partitionKey).build()) + ); + + BatchGetItemEnhancedRequest request = BatchGetItemEnhancedRequest.builder() + .readBatches(builder.build()) + .build(); + + return Flux.from(dynamoDbEnhancedAsyncClient.batchGetItem(request) + .map(result -> result.resultsForTable(tableAsync))) + .flatMap(Flux::fromIterable); + } + + /** + * Costruisce updateExpression per DynamoDB in base al tipo di attributo fornito. + * Per gli attributi di tipo lista, genera un'espressione per aggiungere alla lista utilizzando {@code list_append} e {@code if_not_exists}. + * Per gli attributi di tipo mappa (oggetto annidato), genera espressioni per aggiornare ciascuna coppia chiave-valore interna. + * Per altri tipi di attributo, genera una semplice espressione di assegnazione. + * + * @param key La chiave dell'attributo da aggiornare + * @param value Il valore dell'attributo da aggiornare + * @param counter Un contatore atomico utilizzato per generare indici univoci per i parametri + * @param names Mappa dei nomi degli attributi per l'espressione DynamoDB + * @param values Mappa dei valori degli attributi per l'espressione DynamoDB + * @return Una lista di espressioni di aggiornamento da utilizzare in una richiesta di update su DynamoDB + */ + protected List buildUpdateExpressions(String key, AttributeValue value, AtomicInteger counter, Map names, Map values) { + List expressions = new ArrayList<>(); + + if(key.equalsIgnoreCase("validationConfig")){ + return expressions; + } + + if (isListAttributeType(value)) { + int idx = counter.getAndIncrement(); + names.put("#k" + idx, key); + values.put(":v" + idx, AttributeValue.builder().l(value.l()).build()); + values.put(":empty" + idx, AttributeValue.builder().l(Collections.emptyList()).build()); + expressions.add("#k" + idx + " = list_append(if_not_exists(" + "#k" + idx + ", " + ":empty" + idx + "), " + ":v" + idx + ")"); + + } else if (isInnerObjectAttributeType(value)) { + value.m().forEach((innerKey, innerVal) -> { + int idx = counter.getAndIncrement(); + if(key.equalsIgnoreCase("paperStatus")){ + names.put("#paperStatus", key); + names.put("#paperStatus_" + idx + "_inner", innerKey); + values.put(":v" + idx, innerVal); + expressions.add("#paperStatus.#paperStatus_" + idx + "_inner" + " = " + ":v" + idx); + } + else if(key.equalsIgnoreCase("validationFlow")){ + names.put("#validationFlow" , key); + names.put("#validationFlow_" + idx + "_inner", innerKey); + values.put(":v" + idx, innerVal); + expressions.add("#validationFlow.#validationFlow_" + idx + "_inner" + " = " + ":v" + idx); + } + else { + names.put("#k" + idx, key); + names.put("#k" + idx + "_inner", innerKey); + values.put(":v" + idx, innerVal); + expressions.add("#k" + idx + "." + "#k" + idx + "_inner" + " = " + ":v" + idx); + } + }); + } else { + if(!key.equalsIgnoreCase("paperStatus") && !key.equalsIgnoreCase("validationFlow")){ + int idx = counter.getAndIncrement(); + names.put("#k" + idx, key); + values.put(":v" + idx, value); + expressions.add("#k" + idx + " = " + ":v" + idx); + } + } + return expressions; + } + + private static boolean isInnerObjectAttributeType(AttributeValue value) { + return value.m() != null && !value.m().isEmpty(); + } + + private boolean isListAttributeType(AttributeValue value) { + return value.l() != null && !value.l().isEmpty(); + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/PaperTrackerDryRunOutputsDAOImpl.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/PaperTrackerDryRunOutputsDAOImpl.java new file mode 100644 index 000000000..d4544c929 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/PaperTrackerDryRunOutputsDAOImpl.java @@ -0,0 +1,35 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo; + +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import it.pagopa.pn.papertracker.middleware.dao.PaperTrackerDryRunOutputsDAO; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackerDryRunOutputs; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient; +import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; + +@Slf4j +@Component +public class PaperTrackerDryRunOutputsDAOImpl extends BaseDao implements PaperTrackerDryRunOutputsDAO { + + public PaperTrackerDryRunOutputsDAOImpl(DynamoDbEnhancedAsyncClient dynamoDbEnhancedClient, PnPaperTrackerConfigs cfg, DynamoDbAsyncClient dynamoDbAsyncClient) { + super(dynamoDbEnhancedClient, + dynamoDbAsyncClient, + cfg.getDao().getPaperTrackerDryRunOutputsTable(), + PaperTrackerDryRunOutputs.class + ); + } + + @Override + public Flux retrieveOutputEvents(String requestId) { + log.info("retrieving output events for requestId={}", requestId); + return retrieveByRequestId(requestId); + } + + @Override + public Mono insertOutputEvent(PaperTrackerDryRunOutputs paperTrackerDryRunOutputs) { + return putItem(paperTrackerDryRunOutputs); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/PaperTrackingsDAOImpl.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/PaperTrackingsDAOImpl.java new file mode 100644 index 000000000..41bf94309 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/PaperTrackingsDAOImpl.java @@ -0,0 +1,206 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo; + +import com.sngular.apigenerator.asyncapi.business_model.model.event.Data; +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import it.pagopa.pn.papertracker.exception.PnPaperTrackerConflictException; +import it.pagopa.pn.papertracker.exception.PnPaperTrackerNotFoundException; +import it.pagopa.pn.papertracker.middleware.dao.PaperTrackingsDAO; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient; +import software.amazon.awssdk.enhanced.dynamodb.Key; +import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException; + +import java.time.Instant; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +import static it.pagopa.pn.commons.abstractions.impl.AbstractDynamoKeyValueStore.ATTRIBUTE_NOT_EXISTS; +import static it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackings.ATTEMPT_ID_PCRETRY_INDEX; + +@Component +@Slf4j +public class PaperTrackingsDAOImpl extends BaseDao implements PaperTrackingsDAO { + + private final String ERROR_CODE_PAPER_TRACKER_DUPLICATED_ITEM = "PN_PAPER_TRACKER_DUPLICATED_ITEM"; + private final String ERROR_CODE_PAPER_TRACKER_NOT_FOUND = "PN_PAPER_TRACKER_NOT_FOUND"; + + private final PnPaperTrackerConfigs pnPaperTrackerConfigs; + + public PaperTrackingsDAOImpl(DynamoDbEnhancedAsyncClient dynamoDbEnhancedClient, PnPaperTrackerConfigs cfg, DynamoDbAsyncClient dynamoDbAsyncClient) { + super(dynamoDbEnhancedClient, + dynamoDbAsyncClient, + cfg.getDao().getPaperTrackingsTable(), + PaperTrackings.class + ); + this.pnPaperTrackerConfigs = cfg; + } + + @Override + public Mono retrieveEntityByTrackingId(String trackingId) { + return getByKey(Key.builder().partitionValue(trackingId).build()); + } + + @Override + public Flux retrieveEntityByAttemptId(String attemptId, String pcRetry) { + Key.Builder key = Key.builder().partitionValue(attemptId); + + if(StringUtils.hasText(pcRetry)){ + key.sortValue(pcRetry); + } + + return queryByIndex(key.build(), ATTEMPT_ID_PCRETRY_INDEX); + } + + @Override + public Mono putIfAbsent(PaperTrackings entity) { + String expression = String.format("%s(%s)", ATTRIBUTE_NOT_EXISTS, PaperTrackings.COL_TRACKING_ID); + entity.setTtl(Instant.now().plus(pnPaperTrackerConfigs.getPaperTrackingsTtlDuration()).getEpochSecond()); + + return putIfAbsent(expression, entity) + .onErrorMap(ConditionalCheckFailedException.class, ex -> { + log.error("Conditional check exception on PaperTrackingsDAOImpl putTrackings trackingId={} exmessage={}", entity.getTrackingId(), ex.getMessage()); + log.warn("Duplicate trackingId detected in putIfAbsent: {}", entity.getTrackingId()); + return new PnPaperTrackerConflictException(ERROR_CODE_PAPER_TRACKER_DUPLICATED_ITEM, String.format("RequestId %s already exists", entity.getTrackingId())); + }); + } + + public Mono updateOcrRequests(Integer ocrRequestIndex, String trackingId, Data.ValidationStatus validationStatus) { + log.debug("updateOcrRequests for item with trackingId: {}", trackingId); + Instant now = Instant.now(); + Map expressionAttributeNames = new HashMap<>(); + Map expressionAttributeValues = new HashMap<>(); + + String updateExpr = "SET #updatedAt = :updatedAt"; + + expressionAttributeNames.put("#updatedAt", PaperTrackings.COL_UPDATED_AT); + expressionAttributeNames.put("#validationFlow", PaperTrackings.COL_VALIDATION_FLOW); + expressionAttributeNames.put("#ocr", ValidationFlow.COL_OCR_REQUESTS); + + expressionAttributeValues.put(":updatedAt", AttributeValue.builder().s(now.toString()).build()); + + if (Objects.nonNull(ocrRequestIndex) && ocrRequestIndex >= 0) { + expressionAttributeNames.put("#responseTimestamp", OcrRequest.COL_RESPONSE_TIMESTAMP); + expressionAttributeValues.put(":responseTimestamp", AttributeValue.builder().s(now.toString()).build()); + expressionAttributeNames.put("#responseStatus", OcrRequest.COL_RESPONSE_STATUS); + expressionAttributeValues.put(":responseStatus", AttributeValue.builder().s(validationStatus.getValue()).build()); + updateExpr += ", #validationFlow.#ocr[" + ocrRequestIndex + "].#responseStatus = :responseStatus, " + + "#validationFlow.#ocr[" + ocrRequestIndex + "].#responseTimestamp = :responseTimestamp"; + } + + String conditionExpression = String.format("%s(%s)", "attribute_exists", PaperTrackings.COL_TRACKING_ID); + return updateIfExists(trackingId, updateExpr, expressionAttributeValues, expressionAttributeNames, conditionExpression); + + } + + @Override + public Mono updateItem(String trackingId, PaperTrackings paperTrackings) { + return updateItem(trackingId, paperTrackings, false); + } + + @Override + public Mono updateItemForRework(String trackingId, PaperTrackings paperTrackings) { + return updateItem(trackingId, paperTrackings, true); + } + + /** + * Aggiorna un elemento PaperTrackings nel database DynamoDB, identificato dal trackingId. + * L'aggiornamento viene eseguito solo se l'elemento esiste (condizione attribute_exists). + * Viene aggiornato anche il campo "updatedAt" con il timestamp corrente. + * + * @param trackingId pk dell'oggetto PaperTrackings da aggiornare + * @param paperTrackings l'oggetto PaperTrackings con i nuovi valori da aggiornare + * @return un Mono contenente l'oggetto PaperTrackings aggiornato + * @throws PnPaperTrackerNotFoundException se l'elemento con il trackingId specificato non esiste + */ + public Mono updateItem(String trackingId, PaperTrackings paperTrackings, boolean resetPaperStatus) { + log.debug("Updating item with trackingId: {}", trackingId); + + Map expressionAttributeNames = new HashMap<>(); + Map expressionAttributeValues = new HashMap<>(); + List updateExpressions = new ArrayList<>(); + + expressionAttributeNames.put("#updatedAt", PaperTrackings.COL_UPDATED_AT); + expressionAttributeValues.put(":updatedAt", AttributeValue.builder().s(Instant.now().toString()).build()); + updateExpressions.add("#updatedAt = :updatedAt"); + + String updateExpr = "SET "; + + Optional.ofNullable(paperTrackings.getValidationFlow()) + .ifPresent(flow -> { + List requests = flow.getOcrRequests(); + if (!CollectionUtils.isEmpty(requests)) { + expressionAttributeNames.put("#validationFlow", PaperTrackings.COL_VALIDATION_FLOW); + expressionAttributeNames.put("#ocr", ValidationFlow.COL_OCR_REQUESTS); + expressionAttributeValues.put(":requests", AttributeValue.builder() + .l(PaperTrackings.paperTrackingsToAttributeValueMap(paperTrackings).get("validationFlow").m().get("ocrRequests").l()).build()); + updateExpressions.add("#validationFlow.#ocr = list_append(#validationFlow.#ocr, :requests) "); + } + + flow.setOcrRequests(null); + }); + + + Map attributeValueMap = new HashMap<>(PaperTrackings.paperTrackingsToAttributeValueMap(paperTrackings)); + + /* + * Reset completo di paperStatus. + * In questo modo DynamoDB sovrascrive l'intero attributo paperStatus + * con una mappa vuota, eliminando tutti i campi precedenti. + */ + if (resetPaperStatus) { + expressionAttributeNames.put("#paperStatus", PaperTrackings.COL_PAPER_STATUS); + expressionAttributeValues.put(":emptyPaperStatus", AttributeValue.builder() + .m(Collections.emptyMap()) + .build()); + + updateExpressions.add("#paperStatus = :emptyPaperStatus"); + attributeValueMap.remove(PaperTrackings.COL_PAPER_STATUS); + } + + AtomicInteger counter = new AtomicInteger(0); + + attributeValueMap.forEach((key, value) -> { + List expressions = buildUpdateExpressions( + key, value, counter, expressionAttributeNames, expressionAttributeValues + ); + updateExpressions.addAll(expressions); + }); + + updateExpr += String.join(", ", updateExpressions); + + String conditionExpression = String.format("%s(%s)", "attribute_exists", PaperTrackings.COL_TRACKING_ID); + + log.debug("updateExpr {}", updateExpr); + log.debug("expressionAttributeValues {}", expressionAttributeValues); + log.debug("expressionAttributeNames {}", expressionAttributeNames); + + return updateIfExists(trackingId, updateExpr, expressionAttributeValues, expressionAttributeNames, conditionExpression); + } + + private Mono updateIfExists(String trackingId, String updateExpr, Map expressionAttributeValues, Map expressionAttributeNames, String conditionExpression) { + return updateIfExists(Map.of(PaperTrackings.COL_TRACKING_ID, AttributeValue.builder().s(trackingId).build()), updateExpr, expressionAttributeValues, expressionAttributeNames, conditionExpression) + .map(updateItemResponse -> PaperTrackings.attributeValueMapToPaperTrackings(updateItemResponse.attributes())) + .doOnError(e -> log.error("Error updating item with trackingId {}: {}", trackingId, e.getMessage())) + .onErrorMap(ConditionalCheckFailedException.class, e -> { + log.info("Item with trackingId {} not found — cannot update", trackingId); + return new PnPaperTrackerNotFoundException( + ERROR_CODE_PAPER_TRACKER_NOT_FOUND, + String.format("PaperTracking with trackingId %s not found", trackingId) + ); + }); + } + + @Override + public Flux retrieveAllByTrackingIds(List trackingIds) { + return findAllByKeys(trackingIds); + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/PaperTrackingsErrorsDAOImpl.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/PaperTrackingsErrorsDAOImpl.java new file mode 100644 index 000000000..8615538eb --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/PaperTrackingsErrorsDAOImpl.java @@ -0,0 +1,53 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo; + +import it.pagopa.pn.commons.log.dto.metrics.GeneralMetric; +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import it.pagopa.pn.papertracker.middleware.dao.PaperTrackingsErrorsDAO; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackingsErrors; +import it.pagopa.pn.papertracker.utils.MetricUtils; +import lombok.CustomLog; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient; +import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; + +import java.time.Instant; +import java.util.List; + +@Component +@CustomLog +public class PaperTrackingsErrorsDAOImpl extends BaseDao implements PaperTrackingsErrorsDAO { + + private final PnPaperTrackerConfigs pnPaperTrackerConfigs; + + public PaperTrackingsErrorsDAOImpl(DynamoDbEnhancedAsyncClient dynamoDbEnhancedClient, PnPaperTrackerConfigs cfg, DynamoDbAsyncClient dynamoDbAsyncClient) { + super(dynamoDbEnhancedClient, + dynamoDbAsyncClient, + cfg.getDao().getPaperTrackingsErrorsTable(), + PaperTrackingsErrors.class + ); + this.pnPaperTrackerConfigs = cfg; + } + + public Mono insertError(PaperTrackingsErrors paperTrackingsErrors) { + String logMessage = "Logging metrics: " + MetricUtils.MetricName.ERROR_COUNT +"; Dimensions: " + MetricUtils.DimensionName.ERROR_CATEGORY.getValue() + ", " + MetricUtils.DimensionName.PRODUCT_TYPE.getValue(); + GeneralMetric errorCountMetric = MetricUtils.generateGeneralMetric( + MetricUtils.MetricName.ERROR_COUNT, + 1, + List.of( + MetricUtils.generateDimension(MetricUtils.DimensionName.ERROR_CATEGORY, paperTrackingsErrors.getErrorCategory().name()), + MetricUtils.generateDimension(MetricUtils.DimensionName.PRODUCT_TYPE, paperTrackingsErrors.getProductType()) + ) + ); + + log.logMetric(List.of(errorCountMetric), logMessage); + + paperTrackingsErrors.setTtl(Instant.now().plus(pnPaperTrackerConfigs.getPaperTrackingsErrorsTtlDuration()).getEpochSecond()); + return putItem(paperTrackingsErrors); + } + + public Flux retrieveErrors(String requestId) { + return retrieveByRequestId(requestId); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/Attachment.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/Attachment.java new file mode 100644 index 000000000..82bc5b32e --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/Attachment.java @@ -0,0 +1,43 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.Data; +import lombok.Getter; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; + +import java.time.Instant; + +@DynamoDbBean +@Data +public class Attachment { + + public static final String COL_ID = "id"; + public static final String COL_DOCUMENT_TYPE = "documentType"; + public static final String COL_URI = "uri"; + public static final String COL_DATE = "date"; + public static final String COL_SHA256 = "sha256"; + public static final String COL_SOURCE_TYPE = "sourceType"; + public static final String COL_ORIGIN_TYPE = "originType"; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_ID)})) + private String id; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DOCUMENT_TYPE)})) + private String documentType; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_URI)})) + private String uri; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DATE)})) + private Instant date; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_SHA256)})) + private String sha256; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_SOURCE_TYPE)})) + private String sourceType; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_ORIGIN_TYPE)})) + private String originType; + +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/BusinessState.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/BusinessState.java new file mode 100644 index 000000000..69258ae87 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/BusinessState.java @@ -0,0 +1,12 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +public enum BusinessState { + AWAITING_FINAL_STATUS_CODE, + AWAITING_REFINEMENT_OCR, + AWAITING_REWORK_EVENTS, + AWAITING_OCR, + DONE, + KO + +} + diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorCategory.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorCategory.java new file mode 100644 index 000000000..6a3b13fd4 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorCategory.java @@ -0,0 +1,36 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.Getter; + +@Getter +public enum ErrorCategory { + + TRACKING_ID_NOT_FOUND("TrackingId non trovato"), + NOT_RETRYABLE_EVENT_ERROR("Evento not retryable ricevuto"), + RENDICONTAZIONE_SCARTATA("Rendicontazione scartata"), + DATE_ERROR("Errore nella validazione delle date della sequenza."), + LAST_EVENT_EXTRACTION_ERROR("Errore nell'estrazione della sequenza dall'ultimo evento."), + REGISTERED_LETTER_CODE_ERROR("Errore nella validazione del registered letter code"), + DELIVERY_FAILURE_CAUSE_ERROR("Errore nella validazione del delivery Failure Cause"), + ATTACHMENTS_ERROR("Errore nella validazione degli allegati della sequenza"), + MAX_RETRY_REACHED_ERROR("Numero massimo di retry raggiunto"), + OCR_VALIDATION("Errore nella validazione OCR"), + DEMAT_EMPTY_EVENT("Errore evento per demat non trovato"), + DEMAT_ATTACHMENT_NUMBER_ERROR("Errore nel numero di attachment trovati per demat"), + DUPLICATED_EVENT("Errore nella validazione della presenza di eventi duplicati"), + INVALID_STATE_FOR_STOCK_890("Stato non valido per giacenza 890"), + INCONSISTENT_STATE("Tracking completato o in attesa validazione OCR"), + /** + * @deprecated Non utilizzare. Campo mantenuto solo per retrocompatibilità. + */ + @Deprecated + STATUS_CODE_ERROR("Errore nella validazione della presenza degli elementi della sequenza."), + REGISTERED_LETTER_CODE_NOT_FOUND("Registered letter code non trovato"); + + private final String value; + + ErrorCategory(String value) { + this.value = value; + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorCause.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorCause.java new file mode 100644 index 000000000..41cdd0c22 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorCause.java @@ -0,0 +1,25 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum ErrorCause { + + GIACENZA_DATE_ERROR("Date di inizio e fine giacenza non coerenti"), + OCR_DUPLICATED_EVENT("Evento di risposta duplicato"), + OCR_DRY_RUN_MODE("Evento di risposta ignorato in quanto attiva la modalità dry-run per l'OCR"), + OCR_KO( "Errore nella validazione dell'OCR"), + OCR_UNSUPPORTED_PRODUCT("Prodotto non supportato per la validazione OCR"), + STOCK_890_REFINEMENT_MISSING("Spedizione 890 non perfezionata"), + STOCK_890_REFINEMENT_ERROR("Errore nel perfezionamento della spedizione 890"), + VALUE_AFTER_REFINEMENT("Evento arrivato dopo la conclusione della spedizione o mentre si stava aspettando l’OCR (DONE o AWAITING_OCR)"), + VALUES_NOT_MATCHING("Errore nella validazione degli allegati della sequenza: mancano degli allegati"), + INVALID_VALUES("Errore nella validazione"), + VALUES_NOT_FOUND("Errore nella validazione dei statusCode della sequenza: non sono presenti tutti gli statusCode previsti dalla macchina a stati"), + SOURCETYPE_FILETYPE_INCOHERENT("Il sourceType e il fileType dell'allegato non sono coerenti"); + + private final String description; + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorDetails.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorDetails.java new file mode 100644 index 000000000..88936d373 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorDetails.java @@ -0,0 +1,26 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import it.pagopa.pn.papertracker.middleware.dao.dynamo.AdditionalDetailsConverter; +import lombok.*; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbConvertedBy; + +import java.util.Map; + +@Setter +@Getter +@DynamoDbBean +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ErrorDetails { + + public static final String COL_CAUSE = "cause"; + public static final String COL_MESSAGE = "message"; + public static final String COL_ADDITIONAL_DETAILS = "additionalDetails"; + + private ErrorCause cause; + private String message; + @Getter(onMethod = @__({ @DynamoDbConvertedBy(AdditionalDetailsConverter.class) })) + private Map additionalDetails; +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorType.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorType.java new file mode 100644 index 000000000..375287ecc --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ErrorType.java @@ -0,0 +1,9 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +public enum ErrorType { + + ERROR, + INFO, + WARNING + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/Event.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/Event.java new file mode 100644 index 000000000..fa1eecbc2 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/Event.java @@ -0,0 +1,83 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.Data; +import lombok.Getter; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbIgnoreNulls; + +import java.time.Instant; +import java.util.List; + +@DynamoDbBean +@Data +public class Event { + + public static final String COL_REQUEST_TIMESTAMP = "requestTimestamp"; + public static final String COL_STATUS_CODE = "statusCode"; + public static final String COL_STATUS_DESCRIPTION = "statusDescription"; + public static final String COL_STATUS_TIMESTAMP = "statusTimestamp"; + public static final String COL_PRODUCT_TYPE = "productType"; + public static final String COL_DELIVERY_FAILURE_CAUSE = "deliveryFailureCause"; + public static final String COL_DISCOVERED_ADDRESS = "discoveredAddress"; + public static final String COL_ANONYMIZED_DISCOVERED_ADDRESS_ID = "anonymizedDiscoveredAddressId"; + public static final String COL_ATTACHMENTS = "attachments"; + public static final String COL_REGISTERED_LETTER_CODE = "registeredLetterCode"; + public static final String COL_ID = "id"; + public static final String COL_DRY_RUN = "dryRun"; + public static final String COL_IUN = "iun"; + public static final String COL_CREATED_AT = "createdAt"; + public static final String COL_REWORK_ID = "reworkId"; + public static final String COL_PRINTER = "printer"; + public static final String COL_DU = "du"; + + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_ID)})) + private String id; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_IUN)})) + private String iun; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REQUEST_TIMESTAMP)})) + private Instant requestTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STATUS_CODE)})) + private String statusCode; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STATUS_DESCRIPTION)})) + private String statusDescription; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STATUS_TIMESTAMP)})) + private Instant statusTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_PRODUCT_TYPE)})) + private String productType; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DELIVERY_FAILURE_CAUSE)})) + private String deliveryFailureCause; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_ANONYMIZED_DISCOVERED_ADDRESS_ID)})) + private String anonymizedDiscoveredAddressId; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_ATTACHMENTS), @DynamoDbIgnoreNulls})) + private List attachments; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REGISTERED_LETTER_CODE)})) + private String registeredLetterCode; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DRY_RUN)})) + private Boolean dryRun; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_CREATED_AT)})) + private Instant createdAt; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REWORK_ID)})) + private String notificationReworkId; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_PRINTER)})) + private String printer; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DU)})) + private String du; + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/FlowThrow.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/FlowThrow.java new file mode 100644 index 000000000..8f7e3590f --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/FlowThrow.java @@ -0,0 +1,15 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +public enum FlowThrow { + + INIT_TRACKING, + NOT_RETRYABLE_EVENT_HANDLER, + DUPLICATED_EVENT_VALIDATION, + CHECK_TRACKING_PRODUCT, + SEQUENCE_VALIDATION, + DEMAT_VALIDATION, + FINAL_EVENT_BUILDING, + CHECK_TRACKING_STATE, + RETRY_PHASE; + +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/OcrRequest.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/OcrRequest.java new file mode 100644 index 000000000..0bf976c34 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/OcrRequest.java @@ -0,0 +1,42 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.Data; +import lombok.Getter; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; + +import java.time.Instant; + +@DynamoDbBean +@Data +public class OcrRequest { + + public static final String COL_REQUEST_TIMESTAMP = "requestTimestamp"; + public static final String COL_RESPONSE_TIMESTAMP = "responseTimestamp"; + public static final String COL_DOCUMENT_TYPE = "documentType"; + public static final String COL_FINAL_EVENT_ID = "finalEventId"; + public static final String COL_ATTACHMENT_EVENT_ID = "attachmentEventId"; + public static final String COL_URI = "uri"; + public static final String COL_RESPONSE_STATUS = "responseStatus"; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DOCUMENT_TYPE)})) + private String documentType; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_FINAL_EVENT_ID)})) + private String finalEventId; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_ATTACHMENT_EVENT_ID)})) + private String attachmentEventId; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_RESPONSE_TIMESTAMP)})) + private Instant responseTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REQUEST_TIMESTAMP)})) + private Instant requestTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_URI)})) + private String uri; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_RESPONSE_STATUS)})) + private String responseStatus; +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperStatus.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperStatus.java new file mode 100644 index 000000000..8b40f09fc --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperStatus.java @@ -0,0 +1,53 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.Data; +import lombok.Getter; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbIgnoreNulls; + +import java.time.Instant; +import java.util.List; + +@DynamoDbBean +@Data +public class PaperStatus { + + public static final String COL_REGISTERED_LETTER_CODE = "registeredLetterCode"; + public static final String COL_DELIVERY_FAILURE_CAUSE = "deliveryFailureCause"; + public static final String COL_ANONYMIZED_DISCOVERED_ADDRESS = "anonymizedDiscoveredAddress"; + public static final String COL_FINAL_STATUS_CODE = "finalStatusCode"; + public static final String COL_VALIDATED_SEQUENCE_TIMESTAMP = "validatedSequenceTimestamp"; + public static final String COL_VALIDATED_EVENTS = "validatedEvents"; + public static final String COL_FINAL_DEMAT_FOUND = "finalDematFound"; + public static final String COL_PAPER_DELIVERY_TIMESTAMP = "paperDeliveryTimestamp"; + public static final String COL_PREDICTED_REFINEMENT_TYPE = "predictedRefinementType"; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REGISTERED_LETTER_CODE)})) + private String registeredLetterCode; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DELIVERY_FAILURE_CAUSE)})) + private String deliveryFailureCause; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_ANONYMIZED_DISCOVERED_ADDRESS)})) + private String anonymizedDiscoveredAddress; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_FINAL_STATUS_CODE)})) + private String finalStatusCode; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_VALIDATED_SEQUENCE_TIMESTAMP)})) + private Instant validatedSequenceTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_VALIDATED_EVENTS), @DynamoDbIgnoreNulls})) + private List validatedEvents; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_FINAL_DEMAT_FOUND)})) + private Boolean finalDematFound; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_PAPER_DELIVERY_TIMESTAMP)})) + private Instant paperDeliveryTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_PREDICTED_REFINEMENT_TYPE)})) + private String predictedRefinementType; + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackerDryRunOutputs.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackerDryRunOutputs.java new file mode 100644 index 000000000..2a0df0c62 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackerDryRunOutputs.java @@ -0,0 +1,61 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.Data; +import lombok.Getter; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; + +import java.time.Instant; +import java.util.List; + +@DynamoDbBean +@Data +public class PaperTrackerDryRunOutputs { + public static final String COL_TRACKING_ID = "trackingId"; + public static final String COL_CREATED = "created"; + public static final String COL_REGISTERED_LETTER_CODE = "registeredLetterCode"; + public static final String COL_STATUS_CODE = "statusCode"; + public static final String COL_STATUS_DETAIL = "statusDetail"; + public static final String COL_STATUS_DESCRIPTION = "statusDescription"; + public static final String COL_STATUS_DATE_TIME = "statusDateTime"; + public static final String COL_DELIVERY_FAILURE_CAUSE = "deliveryFailureCause"; + public static final String COL_ATTACHMENTS = "attachments"; + public static final String COL_DISCOVERED_ADDRESS = "discoveredAddress"; + public static final String COL_CLIENT_REQUEST_TIMESTAMP = "clientRequestTimestamp"; + + @Getter(onMethod = @__({@DynamoDbPartitionKey, @DynamoDbAttribute(COL_TRACKING_ID)})) + private String trackingId; + + @Getter(onMethod = @__({@DynamoDbSortKey, @DynamoDbAttribute(COL_CREATED)})) + private Instant created; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REGISTERED_LETTER_CODE)})) + private String registeredLetterCode; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STATUS_CODE)})) + private String statusCode; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STATUS_DETAIL)})) + private String statusDetail; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STATUS_DESCRIPTION)})) + private String statusDescription; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STATUS_DATE_TIME)})) + private String statusDateTime; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DELIVERY_FAILURE_CAUSE)})) + private String deliveryFailureCause; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_ATTACHMENTS)})) + private List attachments; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DISCOVERED_ADDRESS)})) + private String anonymizedDiscoveredAddressId; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_CLIENT_REQUEST_TIMESTAMP)})) + private String clientRequestTimestamp; + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackings.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackings.java new file mode 100644 index 000000000..0e564239f --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackings.java @@ -0,0 +1,119 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.Data; +import lombok.Getter; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.*; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.time.Instant; +import java.util.List; +import java.util.Map; + +@DynamoDbBean +@Data +public class PaperTrackings { + + public static final String COL_TRACKING_ID = "trackingId"; + public static final String COL_ATTEMPT_ID = "attemptId"; + public static final String COL_PC_RETRY = "pcRetry"; + public static final String COL_EVENTS = "events"; + public static final String COL_PAPER_STATUS = "paperStatus"; + public static final String COL_VALIDATION_FLOW = "validationFlow"; + public static final String COL_VALIDATION_CONFIG = "validationConfig"; + public static final String COL_PRODUCT_TYPE = "productType"; + public static final String COL_NEXT_REQUEST_ID_PC_RETRY = "nextRequestIdPcretry"; + public static final String COL_UNIFIED_DELIVERY_DRIVER = "unifiedDeliveryDriver"; + public static final String COL_CREATED_AT = "createdAt"; + public static final String COL_UPDATED_AT = "updatedAt"; + public static final String COL_STATE = "state"; + public static final String COL_BUSINESS_STATE = "businessState"; + public static final String COL_TTL = "ttl"; + public static final String COL_RECAG012_STATUS_TIMESTAMP = "recag012StatusTimestamp"; + public static final String COL_PENDING_FINAL_EVENT_ID = "pendingFinalEventId"; + public static final String ATTEMPT_ID_PCRETRY_INDEX = "attemptId-pcRetry-index"; + public static final String COL_REWORK_ID = "reworkId"; + public static final String COL_REWORK_REQUEST_TIMESTAMP = "reworkRequestTimestamp"; + public static final String COL_PROCESSING_MODE = "processingMode"; + public static final String COL_ANALOG_REQUEST_CLIENT_ID = "analogRequestClientId"; + + @Getter(onMethod = @__({@DynamoDbPartitionKey, @DynamoDbAttribute(COL_TRACKING_ID)})) + private String trackingId; + + @Getter(onMethod = @__({@DynamoDbSecondaryPartitionKey(indexNames = ATTEMPT_ID_PCRETRY_INDEX), @DynamoDbAttribute(COL_ATTEMPT_ID)})) + private String attemptId; + + @Getter(onMethod = @__({@DynamoDbSecondarySortKey(indexNames = ATTEMPT_ID_PCRETRY_INDEX), @DynamoDbAttribute(COL_PC_RETRY)})) + private String pcRetry; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_PRODUCT_TYPE)})) + private String productType; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_UNIFIED_DELIVERY_DRIVER)})) + private String unifiedDeliveryDriver; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_EVENTS), @DynamoDbIgnoreNulls})) + private List events; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_PAPER_STATUS), @DynamoDbIgnoreNulls})) + private PaperStatus paperStatus; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_VALIDATION_FLOW), @DynamoDbIgnoreNulls})) + private ValidationFlow validationFlow; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_VALIDATION_CONFIG), @DynamoDbIgnoreNulls})) + private ValidationConfig validationConfig; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_NEXT_REQUEST_ID_PC_RETRY)})) + private String nextRequestIdPcretry; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STATE)})) + private PaperTrackingsState state; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_BUSINESS_STATE)})) + private BusinessState businessState; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_PENDING_FINAL_EVENT_ID)})) + private String pendingFinalEventId; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_CREATED_AT)})) + private Instant createdAt; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_UPDATED_AT)})) + private Instant updatedAt; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_TTL)})) + private Long ttl; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REWORK_REQUEST_TIMESTAMP)})) + private Instant notificationReworkRequestTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REWORK_ID)})) + private String notificationReworkId; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_PROCESSING_MODE)})) + private ProcessingMode processingMode; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_ANALOG_REQUEST_CLIENT_ID)})) + private String analogRequestClientId; + + // Costruito UNA volta sola + private static final TableSchema SCHEMA = + TableSchema.fromBean(PaperTrackings.class); + + /** + * Converte l'entity PaperTrackings in una mappa {@code Map} utilizzata da DynamoDB. + */ + public static Map paperTrackingsToAttributeValueMap(PaperTrackings p) { + return SCHEMA.itemToMap(p, true); + } + + /** + * Converte una mappa {@code Map} utilizzata da DynamoDB in una entity PaperTrackings. + */ + public static PaperTrackings attributeValueMapToPaperTrackings(Map item) { + if (item == null || item.isEmpty()) return null; + return SCHEMA.mapToItem(item); + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackingsErrors.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackingsErrors.java new file mode 100644 index 000000000..5392a5cfb --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackingsErrors.java @@ -0,0 +1,70 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.*; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; + +import java.time.Instant; + +@Setter +@Getter +@ToString +@DynamoDbBean +@EqualsAndHashCode +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class PaperTrackingsErrors { + + public static final String COL_TRACKING_ID = "trackingId"; + public static final String COL_CREATED = "created"; + public static final String COL_CATEGORY = "category"; + public static final String COL_DETAILS = "details"; + public static final String COL_FLOW_THROW = "flowThrow"; + public static final String COL_EVENT_THROW = "eventThrow"; + public static final String COL_EVENT_ID_THROW = "eventIdThrow"; + public static final String COL_PRODUCT_TYPE = "productType"; + public static final String COL_TYPE = "type"; + public static final String COL_TTL = "ttl"; + public static final String COL_UNIFIED_DELIVERY_DRIVER = "unifiedDeliveryDriver"; + public static final String COL_REGISTERED_LETTER_CODE = "registeredLetterCode"; + + @Getter(onMethod = @__({@DynamoDbPartitionKey, @DynamoDbAttribute(COL_TRACKING_ID)})) + private String trackingId; + + @Getter(onMethod = @__({@DynamoDbSortKey, @DynamoDbAttribute(COL_CREATED)})) + private Instant created; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_CATEGORY)})) + private ErrorCategory errorCategory; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_DETAILS)})) + private ErrorDetails details; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_FLOW_THROW)})) + private FlowThrow flowThrow; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_EVENT_THROW)})) + private String eventThrow; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_EVENT_ID_THROW)})) + private String eventIdThrow; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_PRODUCT_TYPE)})) + private String productType; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_TYPE)})) + private ErrorType type; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_TTL)})) + private Long ttl; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_UNIFIED_DELIVERY_DRIVER)})) + private String unifiedDeliveryDriver; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REGISTERED_LETTER_CODE)})) + private String registeredLetterCode; + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackingsState.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackingsState.java new file mode 100644 index 000000000..091593be5 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/PaperTrackingsState.java @@ -0,0 +1,10 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +public enum PaperTrackingsState { + AWAITING_FINAL_STATUS_CODE, //mantenuto per retrocompatibilità + AWAITING_REFINEMENT, + AWAITING_REWORK_EVENTS, + AWAITING_OCR, + DONE, + KO +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ProcessingMode.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ProcessingMode.java new file mode 100644 index 000000000..c33f34cd7 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ProcessingMode.java @@ -0,0 +1,6 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +public enum ProcessingMode { + DRY, + RUN +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ProductType.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ProductType.java new file mode 100644 index 000000000..e887d065d --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ProductType.java @@ -0,0 +1,37 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.Getter; + +@Getter +public enum ProductType { + + RS("RS"), + + AR("AR"), + + _890("890"), + + RIR("RIR"), + + RIS("RIS"), + + ALL("ALL"), + + UNKNOWN("UNKNOWN"); + + private final String value; + + ProductType(String value) { + this.value = value; + } + + public static ProductType fromValue(String value) { + for (ProductType type : ProductType.values()) { + if (type.getValue().equals(value)) { + return type; + } + } + throw new IllegalArgumentException("Unknown ProductType: " + value); + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ValidationConfig.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ValidationConfig.java new file mode 100644 index 000000000..b9aaedd04 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ValidationConfig.java @@ -0,0 +1,56 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import it.pagopa.pn.papertracker.model.FileType; +import it.pagopa.pn.papertracker.model.OcrStatusEnum; +import lombok.Data; +import lombok.Getter; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; + +import java.util.List; + +@DynamoDbBean +@Data +public class ValidationConfig { + + public static final String COL_OCR_ENABLED = "ocrEnabled"; + public static final String COL_OCR_FILE_TYPES = "ocrFileTypes"; + public static final String COL_REQUIRED_ATTACHMENTS_REFINEMENT_STOCK_890 = "requiredAttachmentsRefinementStock890"; + public static final String COL_SEND_OCR_REFINEMENT_STOCK_890 = "sendOcrAttachmentsRefinementStock890"; + public static final String COL_SEND_OCR_FINAL_VALIDATION_STOCK_890 = "sendOcrAttachmentsFinalValidationStock890"; + public static final String COL_SEND_OCR_FINAL_VALIDATION = "sendOcrAttachmentsFinalValidation"; + public static final String COL_STRICT_FINAL_VALIDATION_STOCK_890 = "strictFinalValidationStock890"; + public static final String COL_STRICT_DELIVERY_FAILURE_CAUSE = "strictDeliveryFailureCause"; + public static final String COL_OCR_FILTER_TEMPORAL = "ocrFilterTemporal"; + public static final String COL_OCR_FILTER_UNIFIED_DELIVERY_DRIVER = "ocrFilterUnifiedDeliveryDriver"; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_OCR_ENABLED)})) + private OcrStatusEnum ocrEnabled; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_OCR_FILE_TYPES)})) + private List ocrFileTypes; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REQUIRED_ATTACHMENTS_REFINEMENT_STOCK_890)})) + private List requiredAttachmentsRefinementStock890; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_SEND_OCR_REFINEMENT_STOCK_890)})) + private List sendOcrAttachmentsRefinementStock890; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_SEND_OCR_FINAL_VALIDATION_STOCK_890)})) + private List sendOcrAttachmentsFinalValidationStock890; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_SEND_OCR_FINAL_VALIDATION)})) + private List sendOcrAttachmentsFinalValidation; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STRICT_FINAL_VALIDATION_STOCK_890)})) + private Boolean strictFinalValidationStock890; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_OCR_FILTER_TEMPORAL)})) + private String ocrFilterTemporal; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_OCR_FILTER_UNIFIED_DELIVERY_DRIVER)})) + private List ocrFilterUnifiedDeliveryDriver; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_STRICT_DELIVERY_FAILURE_CAUSE)})) + private Boolean strictDeliveryFailureCause; +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ValidationFlow.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ValidationFlow.java new file mode 100644 index 000000000..53c04778e --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/dao/dynamo/entity/ValidationFlow.java @@ -0,0 +1,40 @@ +package it.pagopa.pn.papertracker.middleware.dao.dynamo.entity; + +import lombok.Data; +import lombok.Getter; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; + +import java.time.Instant; +import java.util.List; + +@DynamoDbBean +@Data +public class ValidationFlow { + + public static final String COL_SEQUENCES_VALIDATION_TIMESTAMP = "sequencesValidationTimestamp"; + public static final String COL_FINAL_EVENT_DEMAT_VALIDATION_TIMESTAMP = "finalEventDematValidationTimestamp"; + public static final String COL_REFINEMENT_DEMAT_VALIDATION_TIMESTAMP = "refinementDematValidationTimestamp"; + public static final String COL_FINAL_EVENT_BUILDER_TIMESTAMP = "finalEventBuilderTimestamp"; + public static final String COL_RECAG012_STATUS_TIMESTAMP = "recag012StatusTimestamp"; + public static final String COL_OCR_REQUESTS = "ocrRequests"; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_SEQUENCES_VALIDATION_TIMESTAMP)})) + private Instant sequencesValidationTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_FINAL_EVENT_DEMAT_VALIDATION_TIMESTAMP)})) + private Instant finalEventDematValidationTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_REFINEMENT_DEMAT_VALIDATION_TIMESTAMP)})) + private Instant refinementDematValidationTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_FINAL_EVENT_BUILDER_TIMESTAMP)})) + private Instant finalEventBuilderTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_RECAG012_STATUS_TIMESTAMP)})) + private Instant recag012StatusTimestamp; + + @Getter(onMethod = @__({@DynamoDbAttribute(COL_OCR_REQUESTS)})) + private List ocrRequests; + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/eventBridge/EventBridgePublisher.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/eventBridge/EventBridgePublisher.java new file mode 100644 index 000000000..882a20466 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/eventBridge/EventBridgePublisher.java @@ -0,0 +1,45 @@ +package it.pagopa.pn.papertracker.middleware.eventBridge; + +import com.fasterxml.jackson.databind.ObjectMapper; +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; +import software.amazon.awssdk.services.eventbridge.EventBridgeAsyncClient; +import software.amazon.awssdk.services.eventbridge.model.PutEventsRequest; +import software.amazon.awssdk.services.eventbridge.model.PutEventsRequestEntry; +import software.amazon.awssdk.services.eventbridge.model.PutEventsResponse; + +@Service +@RequiredArgsConstructor +@Slf4j +public class EventBridgePublisher { + + private final EventBridgeAsyncClient client; + private final ObjectMapper objectMapper; + private final PnPaperTrackerConfigs configs; + + public Mono publish(Object event) { + log.info("Sending event to EventBridge"); + return Mono.fromCallable(() -> + objectMapper.writeValueAsString(event)) + .flatMap(detail -> Mono.fromFuture(client.putEvents(createRequest(detail)))) + .doOnError(e -> log.error("EventBridge publish error: {}", e.getMessage())) + .doOnNext(response -> log.info("EventBridge publish response: {}", response.entries())); + } + + private PutEventsRequest createRequest(String detail) { + PutEventsRequestEntry entry = PutEventsRequestEntry.builder() + .eventBusName(configs.getEventBus().getName()) + .source(configs.getEventBus().getSource()) + .detailType(configs.getEventBus().getDetailType()) + .detail(detail) + .build(); + + return PutEventsRequest.builder() + .entries(entry) + .build(); + } + +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/DataVaultClient.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/DataVaultClient.java new file mode 100644 index 000000000..0ead3f9cd --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/DataVaultClient.java @@ -0,0 +1,12 @@ +package it.pagopa.pn.papertracker.middleware.msclient; + +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.DiscoveredAddress; +import it.pagopa.pn.papertracker.generated.openapi.msclient.pndatavault.model.PaperAddress; +import reactor.core.publisher.Mono; + +public interface DataVaultClient { + + Mono anonymizeDiscoveredAddress(String trackingId, DiscoveredAddress discoveredAddress); + + Mono deAnonymizeDiscoveredAddress(String trackingId, String anonymizeDiscoveredAddress); +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/PaperChannelClient.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/PaperChannelClient.java new file mode 100644 index 000000000..27f0df380 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/PaperChannelClient.java @@ -0,0 +1,10 @@ +package it.pagopa.pn.papertracker.middleware.msclient; + +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.PcRetryResponse; +import it.pagopa.pn.papertracker.model.HandlerContext; +import reactor.core.publisher.Mono; + +public interface PaperChannelClient { + + Mono getPcRetry(HandlerContext context, Boolean checkApplyRasterization); +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/SafeStorageClient.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/SafeStorageClient.java new file mode 100644 index 000000000..8e0ddea29 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/SafeStorageClient.java @@ -0,0 +1,8 @@ +package it.pagopa.pn.papertracker.middleware.msclient; + +import reactor.core.publisher.Mono; + +public interface SafeStorageClient { + + Mono getSafeStoragePresignedUrl(String safeStorageUri); +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/impl/DataVaultClientImpl.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/impl/DataVaultClientImpl.java new file mode 100644 index 000000000..101229cbd --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/impl/DataVaultClientImpl.java @@ -0,0 +1,46 @@ +package it.pagopa.pn.papertracker.middleware.msclient.impl; + +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.DiscoveredAddress; +import it.pagopa.pn.papertracker.generated.openapi.msclient.pndatavault.api.PaperAddressesApi; +import it.pagopa.pn.papertracker.generated.openapi.msclient.pndatavault.model.PaperAddress; +import it.pagopa.pn.papertracker.middleware.msclient.DataVaultClient; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +import java.util.UUID; + +@Component +@RequiredArgsConstructor +public class DataVaultClientImpl implements DataVaultClient { + + private final PaperAddressesApi paperAddressesApi; + + @Override + public Mono anonymizeDiscoveredAddress(String trackingId, DiscoveredAddress discoveredAddress) { + String uuid = UUID.randomUUID().toString(); + String PREFIX_ANONYMIZED_ADDRESS_ID = "anonymized_addr_"; + String anonymizedDiscoveredAddressId = PREFIX_ANONYMIZED_ADDRESS_ID + uuid; + return paperAddressesApi.updatePaperAddress(trackingId, anonymizedDiscoveredAddressId, toPaperAddress(discoveredAddress)) + .thenReturn(anonymizedDiscoveredAddressId); + } + + @Override + public Mono deAnonymizeDiscoveredAddress(String trackingId, String anonymizeDiscoveredAddress) { + return paperAddressesApi.getPaperAddressByIds(trackingId, anonymizeDiscoveredAddress) + .switchIfEmpty(Mono.error(new IllegalArgumentException("No address found for the given anonymized ID: " + anonymizeDiscoveredAddress))); + } + + private PaperAddress toPaperAddress(DiscoveredAddress discoveredAddress) { + return new PaperAddress() + .name(discoveredAddress.getName()) + .nameRow2(discoveredAddress.getNameRow2()) + .address(discoveredAddress.getAddress()) + .addressRow2(discoveredAddress.getAddressRow2()) + .cap(discoveredAddress.getCap()) + .city(discoveredAddress.getCity()) + .city2(discoveredAddress.getCity2()) + .pr(discoveredAddress.getPr()) + .country(discoveredAddress.getCountry()); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/impl/PaperChannelClientImpl.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/impl/PaperChannelClientImpl.java new file mode 100644 index 000000000..bc1026405 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/impl/PaperChannelClientImpl.java @@ -0,0 +1,44 @@ +package it.pagopa.pn.papertracker.middleware.msclient.impl; + +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import it.pagopa.pn.papertracker.exception.PnPaperTrackerNotFoundException; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.api.PcRetryApi; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.PcRetryResponse; +import it.pagopa.pn.papertracker.middleware.msclient.PaperChannelClient; +import it.pagopa.pn.papertracker.model.HandlerContext; +import it.pagopa.pn.papertracker.utils.PcRetryUtilsMock; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClientResponseException; +import reactor.core.publisher.Mono; + +@Component +@RequiredArgsConstructor +@Slf4j +public class PaperChannelClientImpl implements PaperChannelClient { + + private final PcRetryApi pcRetryApi; + private final PnPaperTrackerConfigs config; + + @Override + public Mono getPcRetry(HandlerContext context, Boolean checkApplyRasterization) { + if(!context.isDryRunEnabled()){ + return getPcRetryPaperChannel(context.getPaperTrackings().getTrackingId(), checkApplyRasterization); + } + log.debug("giving mock response..."); + return PcRetryUtilsMock.getPcRetryPaperMock(context.getPaperTrackings(), config.getMaxPcRetryMock(), context.getPaperProgressStatusEvent().getStatusCode()); + } + + private Mono getPcRetryPaperChannel(String trackingId, Boolean checkApplyRasterization) { + return pcRetryApi.getPcRetry(trackingId, checkApplyRasterization) + .onErrorResume(throwable -> { + if (throwable instanceof WebClientResponseException webEx && webEx.getStatusCode() == HttpStatus.NOT_FOUND) { + return Mono.error(new PnPaperTrackerNotFoundException("RequestId not found", webEx.getMessage())); + } + return Mono.error(throwable); + }); + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/impl/SafeStorageClientImpl.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/impl/SafeStorageClientImpl.java new file mode 100644 index 000000000..7764f6596 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/msclient/impl/SafeStorageClientImpl.java @@ -0,0 +1,59 @@ +package it.pagopa.pn.papertracker.middleware.msclient.impl; + +import it.pagopa.pn.commons.log.PnLogger; +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import it.pagopa.pn.papertracker.exception.PaperTrackerException; +import it.pagopa.pn.papertracker.generated.openapi.msclient.safestorage.api.FileDownloadApi; +import it.pagopa.pn.papertracker.middleware.msclient.SafeStorageClient; +import it.pagopa.pn.papertracker.utils.AttachmentsConfigUtils; +import lombok.CustomLog; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClientResponseException; +import reactor.core.publisher.Mono; +import reactor.util.retry.Retry; + +import java.net.ConnectException; +import java.time.Duration; +import java.util.concurrent.TimeoutException; + +@Component +@RequiredArgsConstructor +@CustomLog +public class SafeStorageClientImpl implements SafeStorageClient { + + private final FileDownloadApi fileDownloadApi; + private final PnPaperTrackerConfigs pnPaperTrackerConfigs; + + /** + * Retrieves the presigned download URL from the SafeStorage. + * + * @param fileKey the object key + * @return Mono containing the presigned URL + */ + public Mono getSafeStoragePresignedUrl(String fileKey) { + final String PN_SAFE_STORAGE_DESCRIPTION = "Safe Storage getFile"; + log.logInvokingAsyncExternalService(PnLogger.EXTERNAL_SERVICES.PN_SAFE_STORAGE, PN_SAFE_STORAGE_DESCRIPTION, null); + String reqFileKey = fileKey; + log.info("Getting file with {} key", fileKey); + fileKey = AttachmentsConfigUtils.cleanFileKey(fileKey); + log.debug("Req params : {}", fileKey); + + return fileDownloadApi.getFile(fileKey, this.pnPaperTrackerConfigs.getSafeStorageCxId(), false) + .retryWhen(Retry.backoff(2, Duration.ofMillis(500)).filter(throwable -> throwable instanceof TimeoutException || throwable instanceof ConnectException)) + .flatMap(response -> { + if(response.getDownload() != null && response.getDownload().getRetryAfter() != null) { + return Mono.error(new PaperTrackerException(String.valueOf(response.getDownload().getRetryAfter()))); + } + if (response.getDownload() == null || response.getDownload().getUrl() == null) { + log.error("File not found in Safe Storage for key: {}", reqFileKey); + return Mono.error(new PaperTrackerException("File not found in Safe Storage")); + } + return Mono.just(response.getDownload().getUrl()); + }) + .onErrorResume(WebClientResponseException.class, ex -> { + log.error(ex.getResponseBodyAsString()); + return Mono.error(ex); + }); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/PnPaperTrackerMiddlewareConfigs.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/PnPaperTrackerMiddlewareConfigs.java new file mode 100644 index 000000000..c2bab40e1 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/PnPaperTrackerMiddlewareConfigs.java @@ -0,0 +1,58 @@ +package it.pagopa.pn.papertracker.middleware.queue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import it.pagopa.pn.papertracker.middleware.queue.model.DeliveryPushEvent; +import it.pagopa.pn.papertracker.middleware.queue.model.ExternalChannelEvent; +import it.pagopa.pn.papertracker.middleware.queue.model.OcrEvent; +import it.pagopa.pn.papertracker.middleware.queue.producer.ExternalChannelOutputsMomProducer; +import it.pagopa.pn.papertracker.middleware.queue.producer.ExternalChannelToPaperChannelDryRunMomProducer; +import it.pagopa.pn.papertracker.middleware.queue.producer.ExternalChannelToPaperTrackerMomProducer; +import it.pagopa.pn.papertracker.middleware.queue.producer.OcrMomProducer; +import lombok.AllArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.sqs.SqsClient; + +@Configuration +@AllArgsConstructor +public class PnPaperTrackerMiddlewareConfigs { + private PnPaperTrackerConfigs pnPaperChannelConfigs; + + @Bean + public OcrMomProducer ocrMomProducer(ObjectMapper objMapper) { + SqsClient sqsClient = SqsClient.builder() + .region(Region.of(this.pnPaperChannelConfigs.getTopics().getQueueOcrInputsRegion())) + .build(); + return new OcrMomProducer(sqsClient, + this.pnPaperChannelConfigs.getTopics().getQueueOcrInputsUrl(), + this.pnPaperChannelConfigs.getTopics().getQueueOcrInputsUrl(), + objMapper, + OcrEvent.class); + } + + @Bean + public ExternalChannelOutputsMomProducer externalChannelOutputsMomProducer(SqsClient sqsClient, ObjectMapper objMapper) { + return new ExternalChannelOutputsMomProducer(sqsClient, + this.pnPaperChannelConfigs.getTopics().getExternalChannelOutputsQueue(), + objMapper, + DeliveryPushEvent.class); + } + + @Bean + public ExternalChannelToPaperTrackerMomProducer externalChannelToPaperTrackerMomProducer(SqsClient sqsClient, ObjectMapper objMapper) { + return new ExternalChannelToPaperTrackerMomProducer(sqsClient, + this.pnPaperChannelConfigs.getTopics().getExternalChannelToPaperTrackerQueue(), + objMapper, + ExternalChannelEvent.class); + } + + @Bean + public ExternalChannelToPaperChannelDryRunMomProducer externalChannelToPaperChannelDryRunMomProducer(SqsClient sqsClient, ObjectMapper objMapper) { + return new ExternalChannelToPaperChannelDryRunMomProducer(sqsClient, + this.pnPaperChannelConfigs.getTopics().getExternalChannelToPaperChannelDryRunQueue(), + objMapper, + ExternalChannelEvent.class); + } +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/PnEventInboundService.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/PnEventInboundService.java new file mode 100644 index 000000000..8b405059a --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/PnEventInboundService.java @@ -0,0 +1,93 @@ +package it.pagopa.pn.papertracker.middleware.queue.consumer; + +import com.sngular.apigenerator.asyncapi.business_model.model.event.OcrDataResultPayload; +import io.awspring.cloud.sqs.annotation.SqsListener; +import io.awspring.cloud.sqs.listener.SqsHeaders; +import it.pagopa.pn.commons.utils.MDCUtils; +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.SingleStatusUpdate; +import it.pagopa.pn.papertracker.middleware.queue.consumer.internal.ExternalChannelHandler; +import it.pagopa.pn.papertracker.middleware.queue.consumer.internal.ExternalChannelSourceEventsHandler; +import it.pagopa.pn.papertracker.middleware.queue.consumer.internal.OcrEventHandler; +import it.pagopa.pn.papertracker.utils.LogUtility; +import lombok.CustomLog; +import lombok.RequiredArgsConstructor; +import org.slf4j.MDC; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.messaging.Message; +import org.springframework.messaging.handler.annotation.Header; +import org.springframework.messaging.handler.annotation.Headers; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +@CustomLog +@Component +@RequiredArgsConstructor +@ConditionalOnProperty( + prefix = "pn.paper-tracker.", + name = "disable-all-consumers", + havingValue = "false" +) +public class PnEventInboundService { + + private final ExternalChannelSourceEventsHandler externalChannelSourceEventsHandler; + private final ExternalChannelHandler externalChannelHandler; + private final OcrEventHandler ocrEventHandler; + private final LogUtility logUtility; + + @SqsListener("${pn.paper-tracker.topics.external-channel-to-paper-channel-queue}") + public void externalChannelSourceConsumer( + @Payload Message message, + @Headers Map headers, + @Header(name = SqsHeaders.SQS_SOURCE_DATA_HEADER) software.amazon.awssdk.services.sqs.model.Message sourceMessage + ) { + processMessage(() -> { + var messageAttributes = sourceMessage.messageAttributes(); + externalChannelSourceEventsHandler.handleExternalChannelMessage(message.getPayload(), messageAttributes); + }, "pn-external_channel_to_paper_channel", message, headers); + } + + @SqsListener("${pn.paper-tracker.topics.external-channel-to-paper-tracker-queue}") + public void externalChannelConsumer( + @Payload Message message, + @Header(name = "dryRun", required = false) Boolean dryRun, + @Header(name = "reworkId", required = false) String reworkId, + @Header(name = "id") String messageId, + @Header(name = SqsHeaders.MessageSystemAttributes.SQS_SENDER_ID, required = false) String senderId, + @Headers Map headers + ) { + processMessage(() -> externalChannelHandler.handleExternalChannelMessage( + message.getPayload(), Boolean.TRUE.equals(dryRun), reworkId, messageId, senderId), + "pn-external_channel_to_paper_tracker", message, headers); + } + + @SqsListener("${pn.paper-tracker.topics.pn-ocr-outputs-queue}") + public void ocrOutputsConsumer( + @Payload Message message, + @Headers Map headers + ) { + processMessage(() -> ocrEventHandler.handleOcrMessage(message.getPayload()), + "pn-ocr_outputs", message, headers); + } + + private void processMessage(Runnable handler, String source, Message> message, Map headers) { + String anonymizedMessage = logUtility.messageToString(message, Set.of("discoveredAddress")); + try { + log.info("Handle message from {} with content {}", source, anonymizedMessage); + setMDCContext(headers); + handler.run(); + } catch (Exception ex) { + log.error("Error processing message from {}: {}", source, ex.getMessage(), ex); + throw ex; + } + } + + private void setMDCContext(Map headers) { + MDCUtils.clearMDCKeys(); + MDC.put(MDCUtils.MDC_PN_CTX_MESSAGE_ID, headers.getOrDefault("id", "").toString()); + MDC.put(MDCUtils.MDC_TRACE_ID_KEY, headers.getOrDefault("AWSTraceHeader", UUID.randomUUID().toString()).toString()); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/ExternalChannelHandler.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/ExternalChannelHandler.java new file mode 100644 index 000000000..dbff4ee52 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/ExternalChannelHandler.java @@ -0,0 +1,92 @@ +package it.pagopa.pn.papertracker.middleware.queue.consumer.internal; + +import it.pagopa.pn.commons.utils.MDCUtils; +import it.pagopa.pn.papertracker.config.PnPaperTrackerConfigs; +import it.pagopa.pn.papertracker.exception.PaperTrackerExceptionHandler; +import it.pagopa.pn.papertracker.exception.PnPaperTrackerValidationException; +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.SingleStatusUpdate; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.ProductType; +import it.pagopa.pn.papertracker.model.EventStatusCodeEnum; +import it.pagopa.pn.papertracker.model.EventTypeEnum; +import it.pagopa.pn.papertracker.model.HandlerContext; +import it.pagopa.pn.papertracker.service.handler_step.generic.HandlersRegistry; +import it.pagopa.pn.papertracker.utils.TrackerUtility; +import lombok.CustomLog; +import lombok.RequiredArgsConstructor; +import org.slf4j.MDC; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.Objects; +import java.util.Optional; + +import static it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.ProductType.ALL; +import static it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.ProductType.UNKNOWN; + +@Component +@RequiredArgsConstructor +@CustomLog +public class ExternalChannelHandler { + + private final PaperTrackerExceptionHandler paperTrackerExceptionHandler; + private final HandlersRegistry handlersRegistry; // contiene HandlersFactoryAr, HandlersFactoryRir, ecc. + private final PnPaperTrackerConfigs pnPaperTrackerConfigs; + + private static final String HANDLING_EVENT_LOG = "Handling {} event for productType: [{}] and event: [{}]"; + + /** + * Riceve i messaggi provenienti dalla coda pn-external_channel_to_paper_tracker e gestisce, invia l'evento all'handlersRegistry per lo + * smistamento allo specifico handler in base a productType ed eventType + * + * @param payload il SingleStatusUpdate contenente le informazioni da processare + */ + public void handleExternalChannelMessage(SingleStatusUpdate payload, boolean dryRunEnabled, String reworkId, String messageId, String senderId) { + if (Objects.isNull(payload) || Objects.isNull(payload.getAnalogMail())) { + log.error("Received null payload or analogMail in ExternalChannelHandler"); + throw new IllegalArgumentException("Payload or analogMail cannot be null"); + } + + String processName = "processExternalChannelMessage"; + MDC.put(MDCUtils.MDC_PN_CTX_REQUEST_ID, payload.getAnalogMail().getRequestId()); + log.logStartingProcess(processName); + HandlerContext context = initializeContext(payload, dryRunEnabled, reworkId, messageId, senderId); + + var statusCode = payload.getAnalogMail().getStatusCode(); + var productType = resolveProductType(statusCode, payload.getAnalogMail().getProductType()); + var eventType = Optional.ofNullable(EventStatusCodeEnum.fromKey(statusCode)).map(EventStatusCodeEnum::getCodeType).orElse(null); + + logStatusEvent(eventType, productType, statusCode); + + MDCUtils.addMDCToContextAndExecute(handlersRegistry.handleEvent(productType, eventType, context) + .doOnSuccess(unused -> log.logEndingProcess(processName)) + .onErrorResume(PnPaperTrackerValidationException.class, ex -> paperTrackerExceptionHandler.handleInternalException(ex, context.getMessageReceiveCount()))) + .block(); + } + + private HandlerContext initializeContext(SingleStatusUpdate payload, boolean dryRunEnabled, String reworkId, String messageId, String senderId) { + HandlerContext context = new HandlerContext(); + context.setTrackingId(payload.getAnalogMail().getRequestId()); + context.setPaperProgressStatusEvent(payload.getAnalogMail()); + context.setEventId(messageId); + context.setDryRunEnabled(dryRunEnabled); + context.setReworkId(reworkId); + context.setRedrive(TrackerUtility.checkIfIsRedrive(senderId, pnPaperTrackerConfigs.getRedriveEnabledDomains())); + return context; + } + + private ProductType resolveProductType(String statusCode, String payloadProductType) { + ProductType eventProductType = Optional.ofNullable(EventStatusCodeEnum.fromKey(statusCode)) + .map(EventStatusCodeEnum::getProductType) + .orElse(UNKNOWN); + + if (ALL.equals(eventProductType) && StringUtils.hasText(payloadProductType)) { + return ProductType.fromValue(payloadProductType); + } + return eventProductType; + } + + private void logStatusEvent(EventTypeEnum statusEvent, ProductType productType, String statusCode) { + log.info(HANDLING_EVENT_LOG, statusEvent, productType, statusCode); + } + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/ExternalChannelSourceEventsHandler.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/ExternalChannelSourceEventsHandler.java new file mode 100644 index 000000000..bb48d77d7 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/ExternalChannelSourceEventsHandler.java @@ -0,0 +1,70 @@ +package it.pagopa.pn.papertracker.middleware.queue.consumer.internal; + +import it.pagopa.pn.commons.utils.MDCUtils; +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.SingleStatusUpdate; +import it.pagopa.pn.papertracker.service.SourceQueueProxyService; +import lombok.CustomLog; +import lombok.RequiredArgsConstructor; +import org.slf4j.MDC; +import org.springframework.messaging.Message; +import org.springframework.stereotype.Component; +import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; + +import java.util.Map; +import java.util.Objects; + +/** + * Handler responsabile della gestione degli eventi provenienti da pn-external-channel (pn-ec). + * + * In fase di inizializzazione di una spedizione su pn-paper-tracker, viene determinata e persistita + * la modalità operativa (DRY o RUN) sull'entità della tabella {pn-PaperTrackings. + * + * + * + * Conseguentemente, alla ricezione di un evento da pn-ec, pn-paper-tracker determina la modalità + * operativa recuperando le informazioni di tracking dalla tabella pn-PaperTrackings + * e gestisce ciascun evento in base alla logica definita. + * + * + * + * + * Spedizione non presente su pn-PaperTrackings + * L'evento viene inoltrato direttamente a pn-paper-channel. + * + * + * Spedizione presente su pn-PaperTrackings e modalità DRY + * L'evento viene inoltrato a pn-paper-channel (se non duplicato) e, in parallelo, processato internamente + * da pn-paper-tracker, con persistenza dell'output su tabella a fini di verifica. + * + * + * Spedizione presente su pn-PaperTrackings e modalità RUN + * L'evento viene processato internamente da pn-paper-tracker e l'output + * viene inviato a pn-delivery-push. + * + * + * + * La gestione del flusso operativo effettivo è demandata al {@link SourceQueueProxyService}. + */ +@Component +@RequiredArgsConstructor +@CustomLog +public class ExternalChannelSourceEventsHandler { + private final SourceQueueProxyService sourceQueueProxyService; + + public void handleExternalChannelMessage( + SingleStatusUpdate message, + Map messageAttributes) { + if (Objects.isNull(message) || Objects.isNull(message.getAnalogMail())) { + log.error("Received null payload or analogMail in ExternalChannelHandler"); + throw new IllegalArgumentException("Payload or analogMail cannot be null"); + } + + String processName = "processExternalChannelSourceMessage"; + MDC.put(MDCUtils.MDC_PN_CTX_REQUEST_ID, message.getAnalogMail().getRequestId()); + log.logStartingProcess(processName); + + MDCUtils.addMDCToContextAndExecute(sourceQueueProxyService.handleExternalChannelMessage(message, messageAttributes) + .doOnSuccess(unused -> log.logEndingProcess(processName))) + .block(); + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/OcrEventHandler.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/OcrEventHandler.java new file mode 100644 index 000000000..687361643 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/consumer/internal/OcrEventHandler.java @@ -0,0 +1,60 @@ +package it.pagopa.pn.papertracker.middleware.queue.consumer.internal; + +import com.sngular.apigenerator.asyncapi.business_model.model.event.OcrDataResultPayload; +import it.pagopa.pn.commons.utils.MDCUtils; +import it.pagopa.pn.papertracker.exception.PaperTrackerExceptionHandler; +import it.pagopa.pn.papertracker.exception.PnPaperTrackerValidationException; +import it.pagopa.pn.papertracker.middleware.dao.PaperTrackingsDAO; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.PaperTrackings; +import it.pagopa.pn.papertracker.middleware.dao.dynamo.entity.ProductType; +import it.pagopa.pn.papertracker.model.EventTypeEnum; +import it.pagopa.pn.papertracker.model.HandlerContext; +import it.pagopa.pn.papertracker.service.handler_step.generic.HandlersRegistry; +import it.pagopa.pn.papertracker.utils.TrackerUtility; +import lombok.CustomLog; +import lombok.RequiredArgsConstructor; +import org.slf4j.MDC; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +import java.util.Objects; + +@Component +@RequiredArgsConstructor +@CustomLog +public class OcrEventHandler { + + private final PaperTrackingsDAO paperTrackingsDAO; + private final PaperTrackerExceptionHandler paperTrackerExceptionHandler; + private final HandlersRegistry handlersRegistry; + + public void handleOcrMessage(OcrDataResultPayload ocrResultMessage) { + if (Objects.isNull(ocrResultMessage) || Objects.isNull(ocrResultMessage.getCommandId())) { + log.error("Received null payload or commandId in OcrResponse handler"); + throw new IllegalArgumentException("Payload or commandId cannot be null"); + } + + String processName = "processOcrResponseMessage"; + MDC.put(MDCUtils.MDC_PN_CTX_REQUEST_ID, ocrResultMessage.getCommandId()); + log.logStartingProcess(processName); + MDCUtils.addMDCToContextAndExecute(paperTrackingsDAO.retrieveEntityByTrackingId(TrackerUtility.getParsedOcrCommandId(ocrResultMessage.getCommandId())[0]) + .flatMap(paperTrackings -> callOcrResponseHandler(paperTrackings, ocrResultMessage)) + .onErrorResume(PnPaperTrackerValidationException.class, e -> paperTrackerExceptionHandler.handleInternalException(e, null)) + .then()) + .block(); + } + + private Mono callOcrResponseHandler(PaperTrackings paperTrackings, OcrDataResultPayload ocrResultMessage) { + HandlerContext context = buildContext(paperTrackings, ocrResultMessage); + return handlersRegistry.handleEvent(ProductType.fromValue(paperTrackings.getProductType()), EventTypeEnum.OCR_RESPONSE_EVENT, context); + } + + private HandlerContext buildContext(PaperTrackings paperTrackings, + OcrDataResultPayload ocrResultMessage) { + HandlerContext handlerContext = new HandlerContext(); + handlerContext.setTrackingId(paperTrackings.getTrackingId()); + handlerContext.setPaperTrackings(paperTrackings); + handlerContext.setOcrDataResultPayload(ocrResultMessage); + return handlerContext; + } +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/CustomEventHeader.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/CustomEventHeader.java new file mode 100644 index 000000000..7f0436d40 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/CustomEventHeader.java @@ -0,0 +1,22 @@ +package it.pagopa.pn.papertracker.middleware.queue.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import it.pagopa.pn.api.dto.events.GenericEventHeader; +import lombok.*; +import lombok.experimental.SuperBuilder; +import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; + +import java.util.Map; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@EqualsAndHashCode(callSuper = true) +@ToString +@SuperBuilder +public class CustomEventHeader extends GenericEventHeader { + + @JsonProperty("messageAttributes") + private Map messageAttributes; + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/DeliveryPushEvent.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/DeliveryPushEvent.java new file mode 100644 index 000000000..3461506d4 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/DeliveryPushEvent.java @@ -0,0 +1,20 @@ +package it.pagopa.pn.papertracker.middleware.queue.model; + +import it.pagopa.pn.api.dto.events.GenericEvent; +import it.pagopa.pn.api.dto.events.GenericEventHeader; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.PaperChannelUpdate; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Builder(toBuilder = true) +public class DeliveryPushEvent implements GenericEvent { + + private GenericEventHeader header; + private PaperChannelUpdate payload; + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/ExternalChannelEvent.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/ExternalChannelEvent.java new file mode 100644 index 000000000..49d5fe473 --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/ExternalChannelEvent.java @@ -0,0 +1,18 @@ +package it.pagopa.pn.papertracker.middleware.queue.model; + +import it.pagopa.pn.api.dto.events.GenericEvent; +import it.pagopa.pn.api.dto.events.GenericEventHeader; +import it.pagopa.pn.papertracker.generated.openapi.msclient.externalchannel.model.SingleStatusUpdate; +import it.pagopa.pn.papertracker.generated.openapi.msclient.paperchannel.model.PaperChannelUpdate; +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Builder(toBuilder = true) +public class ExternalChannelEvent implements GenericEvent { + + private CustomEventHeader header; + private SingleStatusUpdate payload; + +} diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/ExternalChannelEventHeader.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/ExternalChannelEventHeader.java new file mode 100644 index 000000000..ed02d376a --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/ExternalChannelEventHeader.java @@ -0,0 +1,20 @@ +package it.pagopa.pn.papertracker.middleware.queue.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import it.pagopa.pn.api.dto.events.GenericEventHeader; +import lombok.*; +import lombok.experimental.SuperBuilder; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@EqualsAndHashCode(callSuper = true) +@ToString +@SuperBuilder +public class ExternalChannelEventHeader extends GenericEventHeader { + + public static final String PN_EVENT_HEADER_DRYRUN = "dryRun"; + @JsonProperty("dryRun") + private Boolean dryRun; + +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/OcrEvent.java b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/OcrEvent.java new file mode 100644 index 000000000..3de4fbaaf --- /dev/null +++ b/jdk_21_maven/cs/rest/pn-paper-tracker/src/main/java/it/pagopa/pn/papertracker/middleware/queue/model/OcrEvent.java @@ -0,0 +1,23 @@ +package it.pagopa.pn.papertracker.middleware.queue.model; + +import com.sngular.apigenerator.asyncapi.business_model.model.event.OcrDataPayloadDTO; +import it.pagopa.pn.api.dto.events.GenericEvent; +import it.pagopa.pn.api.dto.events.GenericEventHeader; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class OcrEvent implements GenericEvent
+ * In fase di inizializzazione di una spedizione su pn-paper-tracker, viene determinata e persistita + * la modalità operativa (DRY o RUN) sull'entità della tabella {pn-PaperTrackings. + *
+ * Conseguentemente, alla ricezione di un evento da pn-ec, pn-paper-tracker determina la modalità + * operativa recuperando le informazioni di tracking dalla tabella pn-PaperTrackings + * e gestisce ciascun evento in base alla logica definita. + *