{"id":39729719,"url":"https://github.com/link-it/govpay-common","last_synced_at":"2026-01-18T11:00:07.745Z","repository":{"id":326102197,"uuid":"1103968104","full_name":"link-it/govpay-common","owner":"link-it","description":"Funzionalità comuni ai progetti GovPay","archived":false,"fork":false,"pushed_at":"2025-11-27T14:38:25.000Z","size":78,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-28T20:58:55.468Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/link-it.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-25T15:17:26.000Z","updated_at":"2025-11-27T14:38:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/link-it/govpay-common","commit_stats":null,"previous_names":["link-it/govpay-common"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/link-it/govpay-common","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/link-it%2Fgovpay-common","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/link-it%2Fgovpay-common/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/link-it%2Fgovpay-common/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/link-it%2Fgovpay-common/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/link-it","download_url":"https://codeload.github.com/link-it/govpay-common/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/link-it%2Fgovpay-common/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28535156,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-18T10:13:46.436Z","status":"ssl_error","status_checked_at":"2026-01-18T10:13:11.045Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-18T11:00:04.876Z","updated_at":"2026-01-18T11:00:07.713Z","avatar_url":"https://github.com/link-it.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# GovPay Client Commons\n\nLibreria Spring Boot per la gestione dinamica di RestTemplate configurabili a partire dalla tabella `connettori` di GovPay.\n\n## Caratteristiche\n\n- **Configurazione da Database**: Carica le configurazioni dei connettori dalla tabella `connettori` di GovPay (struttura EAV)\n- **Cache Intelligente**: Sistema di caching per ottimizzare le performance\n- **Supporto Multi-Database**: PostgreSQL, MySQL, Oracle, SQL Server, HSQLDB\n- **Autenticazione Multipla**: Supporto per vari tipi di autenticazione:\n  - HTTP Basic Authentication\n  - SSL/TLS con certificati client\n  - OAuth2 Client Credentials\n  - API Key\n  - Custom HTTP Headers\n  - Nessuna autenticazione\n\n## Struttura della Tabella Connettori\n\nLa tabella `connettori` utilizza un approccio EAV (Entity-Attribute-Value):\n\n```sql\nCREATE TABLE connettori (\n    id BIGINT PRIMARY KEY,\n    cod_connettore VARCHAR(255) NOT NULL,\n    cod_proprieta VARCHAR(255) NOT NULL,\n    valore VARCHAR(255) NOT NULL,\n    UNIQUE (cod_connettore, cod_proprieta)\n);\n```\n\n## Proprietà Supportate\n\n| Proprietà | Descrizione |\n|-----------|-------------|\n| `URL` | URL base del servizio |\n| `TIPOAUTENTICAZIONE` | Tipo di autenticazione (NONE, HTTPBasic, SSL, API_KEY, HTTP_HEADER, OAUTH2_CLIENT_CREDENTIALS) |\n| `HTTPUSER` | Username per HTTP Basic Auth |\n| `HTTPPASSW` | Password per HTTP Basic Auth |\n| `SSLKSLOCATION` | Path del keystore |\n| `SSLKSPASSWD` | Password del keystore |\n| `SSLKSTYPE` | Tipo di keystore (JKS, PKCS12) |\n| `SSLPKEYPASSWD` | Password della chiave privata |\n| `SSLTSLOCATION` | Path del truststore |\n| `SSLTSPASSWD` | Password del truststore |\n| `SSLTSTYPE` | Tipo di truststore |\n| `HTTP_HEADER_AUTH_HEADER_NAME` | Nome dell'header personalizzato |\n| `HTTP_HEADER_AUTH_HEADER_VALUE` | Valore dell'header personalizzato |\n| `API_KEY_AUTH_API_KEY_NAME` | Nome dell'API Key |\n| `API_KEY_AUTH_API_ID_NAME` | ID dell'API |\n| `OAUTH2_CLIENT_CREDENTIALS_CLIENT_ID_NAME` | Client ID OAuth2 |\n| `OAUTH2_CLIENT_CREDENTIALS_CLIENT_SECRET_NAME` | Client Secret OAuth2 |\n| `OAUTH2_CLIENT_CREDENTIALS_URL_TOKEN_ENDPOINT_NAME` | URL token endpoint OAuth2 |\n| `OAUTH2_CLIENT_CREDENTIALS_SCOPE_NAME` | Scope OAuth2 |\n| `SUBSCRIPTION_KEY_VALUE` | Subscription Key per Azure APIM (header: Ocp-Apim-Subscription-Key) |\n| `X-CUSTOM-HEADER-NAME-N` | Nome di un custom header generico (N = indice numerico) |\n| `X-CUSTOM-HEADER-VALUE-N` | Valore del custom header corrispondente (stesso indice N) |\n| `ABILITATO` | Flag di abilitazione (true/false) |\n| `CONNECTION_TIMEOUT` | Timeout di connessione in millisecondi |\n| `READ_TIMEOUT` | Timeout di lettura in millisecondi |\n\n## Installazione\n\nAggiungi la dipendenza al tuo `pom.xml`:\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eit.govpay\u003c/groupId\u003e\n    \u003cartifactId\u003egovpay-client-commons\u003c/artifactId\u003e\n    \u003cversion\u003e1.0.0-SNAPSHOT\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## Configurazione\n\n### application.yml\n\n```yaml\nspring:\n  datasource:\n    url: jdbc:postgresql://localhost:5432/govpay\n    username: govpay\n    password: govpay\n    driver-class-name: org.postgresql.Driver\n\n  jpa:\n    hibernate:\n      ddl-auto: validate\n    properties:\n      hibernate:\n        dialect: org.hibernate.dialect.PostgreSQLDialect\n\ngovpay:\n  client:\n    cache:\n      # Abilita/disabilita il caching delle configurazioni\n      # Default: false (disabilitato)\n      enabled: false\n```\n\n### Abilitazione Auto-Configuration\n\nLa libreria si auto-configura automaticamente. Assicurati che lo scan delle componenti includa il package:\n\n```java\n@SpringBootApplication\n@ComponentScan(basePackages = {\"it.govpay.client.commons\", \"com.tuoprogetto\"})\npublic class Application {\n    public static void main(String[] args) {\n        SpringApplication.run(Application.class, args);\n    }\n}\n```\n\n## Utilizzo\n\n### Chiamate Sincrone con RestTemplate\n\n```java\n@Service\npublic class MioServizio {\n\n    @Autowired\n    private ConnettoreService connettoreService;\n\n    public void chiamataEsempio() {\n        // Ottieni il RestTemplate configurato per il connettore\n        RestTemplate restTemplate = connettoreService.getRestTemplate(\"COD_CONNETTORE\");\n\n        // Usa il RestTemplate per le chiamate REST\n        ResponseEntity\u003cString\u003e response = restTemplate.getForEntity(\"/api/endpoint\", String.class);\n        System.out.println(response.getBody());\n    }\n}\n```\n\n### Chiamate Asincrone con CompletableFuture\n\nPer batch ad alto volume o chiamate parallele, usa `AsyncRestTemplateWrapper`:\n\n```java\n@Service\npublic class MyPivotBatchService {\n\n    @Autowired\n    private ConnettoreService connettoreService;\n\n    @Scheduled(cron = \"0 0 2 * * *\")\n    public void batchMyPivot() {\n        // Ottieni il wrapper asincrono\n        AsyncRestTemplateWrapper asyncClient =\n            connettoreService.getAsyncRestTemplate(\"MYPIVOT_CLIENT\");\n\n        // GET asincrono\n        CompletableFuture\u003cResponseEntity\u003cFlussiDTO\u003e\u003e future =\n            asyncClient.getForEntityAsync(\"/api/flussi\", FlussiDTO.class);\n\n        future.thenAccept(response -\u003e {\n            log.info(\"Ricevuti {} flussi\", response.getBody().size());\n            processaFlussi(response.getBody());\n        });\n\n        // POST asincrono\n        CompletableFuture\u003cResponseEntity\u003cRispostaDTO\u003e\u003e postFuture =\n            asyncClient.postForEntityAsync(\"/api/posizioni\", request, RispostaDTO.class);\n\n        postFuture.thenAccept(response -\u003e\n            log.info(\"Posizione creata: {}\", response.getBody())\n        );\n    }\n}\n```\n\n### Chiamate Parallele Multiple\n\n```java\n@Service\npublic class RendicontazioneBatchService {\n\n    @Autowired\n    private ConnettoreService connettoreService;\n\n    @Scheduled(cron = \"0 0 3 * * *\")\n    public void elaboraRendicontazioni() {\n        AsyncRestTemplateWrapper asyncClient =\n            connettoreService.getAsyncRestTemplate(\"ENTE_CREDITORE\");\n\n        List\u003cString\u003e codiciEnte = Arrays.asList(\"E001\", \"E002\", \"E003\", \"E004\", \"E005\");\n\n        // Crea una lista di future per chiamate parallele\n        List\u003cCompletableFuture\u003cResponseEntity\u003cRendicontazioneDTO\u003e\u003e\u003e futures =\n            codiciEnte.stream()\n                .map(codice -\u003e asyncClient.getForEntityAsync(\n                    \"/api/rendicontazioni/\" + codice,\n                    RendicontazioneDTO.class\n                ))\n                .toList();\n\n        // Attendi il completamento di tutte le chiamate\n        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))\n            .thenRun(() -\u003e {\n                List\u003cRendicontazioneDTO\u003e results = futures.stream()\n                    .map(CompletableFuture::join)\n                    .map(ResponseEntity::getBody)\n                    .toList();\n\n                log.info(\"Elaborate {} rendicontazioni\", results.size());\n                saveToDatabase(results);\n            });\n    }\n}\n```\n\n### Gestione Errori Asincrona\n\n```java\nasyncClient.getForEntityAsync(\"/api/data\", DataDTO.class)\n    .exceptionally(ex -\u003e {\n        log.error(\"Errore nella chiamata: {}\", ex.getMessage());\n        return ResponseEntity.status(500).body(new DataDTO()); // Fallback\n    })\n    .thenAccept(response -\u003e {\n        if (response.getStatusCode().is2xxSuccessful()) {\n            processData(response.getBody());\n        }\n    });\n```\n\n### Configurazione Thread Pool Asincrono\n\nIl thread pool per le operazioni asincrone è configurabile tramite properties:\n\n```yaml\ngovpay:\n  client:\n    async:\n      core-pool-size: 10       # Thread sempre attivi (default: 10)\n      max-pool-size: 50        # Thread massimi (default: 50)\n      queue-capacity: 100      # Task in coda (default: 100)\n      thread-name-prefix: async-http-  # Prefisso nome thread\n```\n\n**Dimensionamento suggerito:**\n- Batch low-volume (\u003c 50 chiamate): core=5, max=20\n- Batch medium-volume (50-200 chiamate): core=10, max=50 (default)\n- Batch high-volume (\u003e 200 chiamate): core=20, max=100\n\n### Gestione della Cache\n\nLa cache è **disabilitata di default** per garantire che le modifiche alla configurazione nel database siano immediatamente effettive.\n\n#### Abilitazione Cache\n\nPer abilitare la cache e migliorare le performance:\n\n```yaml\ngovpay:\n  client:\n    cache:\n      enabled: true\n```\n\n#### Operazioni sulla Cache\n\n```java\n// Verifica se la cache è abilitata\nboolean enabled = connettoreService.isCacheEnabled();\n\n// Invalida la cache per un connettore specifico\nconnettoreService.invalidateCache(\"COD_CONNETTORE\");\n\n// Ricarica un connettore specifico\nconnettoreService.reloadConnettore(\"COD_CONNETTORE\");\n\n// Refresh completo della cache\nconnettoreService.refreshCache();\n\n// Verifica dimensione cache\nint size = connettoreService.getCacheSize();\n\n// Verifica se un connettore è in cache\nboolean inCache = connettoreService.isInCache(\"COD_CONNETTORE\");\n```\n\n**Note:**\n- Se la cache è disabilitata, tutte le operazioni di gestione cache vengono ignorate\n- Con cache disabilitata, ogni chiamata a `getRestTemplate()` carica la configurazione dal database\n- Con cache abilitata, le configurazioni vengono precaricate all'avvio e mantenute in memoria\n- Ricorda di invalidare la cache dopo aver modificato le configurazioni nel database\n\n## Esempio di Configurazione nel Database\n\n### Connettore con HTTP Basic Auth\n\n```sql\nINSERT INTO connettori (cod_connettore, cod_proprieta, valore) VALUES\n('SERVIZIO_BASIC', 'URL', 'https://api.example.com'),\n('SERVIZIO_BASIC', 'TIPOAUTENTICAZIONE', 'HTTPBasic'),\n('SERVIZIO_BASIC', 'HTTPUSER', 'username'),\n('SERVIZIO_BASIC', 'HTTPPASSW', 'password'),\n('SERVIZIO_BASIC', 'ABILITATO', 'true'),\n('SERVIZIO_BASIC', 'CONNECTION_TIMEOUT', '5000'),\n('SERVIZIO_BASIC', 'READ_TIMEOUT', '30000');\n```\n\n### Connettore con API Key\n\n```sql\nINSERT INTO connettori (cod_connettore, cod_proprieta, valore) VALUES\n('SERVIZIO_APIKEY', 'URL', 'https://api.withkey.com'),\n('SERVIZIO_APIKEY', 'TIPOAUTENTICAZIONE', 'API_KEY'),\n('SERVIZIO_APIKEY', 'API_KEY_AUTH_API_KEY_NAME', 'your-api-key'),\n('SERVIZIO_APIKEY', 'API_KEY_AUTH_API_ID_NAME', 'X-API-Key'),\n('SERVIZIO_APIKEY', 'ABILITATO', 'true');\n```\n\n### Connettore con OAuth2\n\n```sql\nINSERT INTO connettori (cod_connettore, cod_proprieta, valore) VALUES\n('SERVIZIO_OAUTH2', 'URL', 'https://api.oauth.com'),\n('SERVIZIO_OAUTH2', 'TIPOAUTENTICAZIONE', 'OAUTH2_CLIENT_CREDENTIALS'),\n('SERVIZIO_OAUTH2', 'OAUTH2_CLIENT_CREDENTIALS_CLIENT_ID_NAME', 'client-id'),\n('SERVIZIO_OAUTH2', 'OAUTH2_CLIENT_CREDENTIALS_CLIENT_SECRET_NAME', 'client-secret'),\n('SERVIZIO_OAUTH2', 'OAUTH2_CLIENT_CREDENTIALS_URL_TOKEN_ENDPOINT_NAME', 'https://auth.oauth.com/token'),\n('SERVIZIO_OAUTH2', 'OAUTH2_CLIENT_CREDENTIALS_SCOPE_NAME', 'read write'),\n('SERVIZIO_OAUTH2', 'ABILITATO', 'true');\n```\n\n### Connettore con Azure APIM Subscription Key\n\n```sql\nINSERT INTO connettori (cod_connettore, cod_proprieta, valore) VALUES\n('SERVIZIO_AZURE', 'URL', 'https://myapi.azure-api.net'),\n('SERVIZIO_AZURE', 'TIPOAUTENTICAZIONE', 'NONE'),\n('SERVIZIO_AZURE', 'SUBSCRIPTION_KEY_VALUE', 'your-subscription-key-here'),\n('SERVIZIO_AZURE', 'ABILITATO', 'true');\n```\n\nQuesto configurerà automaticamente l'header `Ocp-Apim-Subscription-Key` per le chiamate verso Azure API Management.\n\n### Connettore con Custom Headers Generici\n\nIl sistema supporta l'aggiunta di header HTTP personalizzati utilizzando coppie di proprietà con pattern indicizzato:\n\n```sql\nINSERT INTO connettori (cod_connettore, cod_proprieta, valore) VALUES\n('SERVIZIO_CUSTOM_HEADERS', 'URL', 'https://api.example.com'),\n('SERVIZIO_CUSTOM_HEADERS', 'TIPOAUTENTICAZIONE', 'NONE'),\n-- Custom header 1: API versioning\n('SERVIZIO_CUSTOM_HEADERS', 'X-CUSTOM-HEADER-NAME-1', 'X-Api-Version'),\n('SERVIZIO_CUSTOM_HEADERS', 'X-CUSTOM-HEADER-VALUE-1', '2.0'),\n-- Custom header 2: Tracing\n('SERVIZIO_CUSTOM_HEADERS', 'X-CUSTOM-HEADER-NAME-2', 'X-Trace-Id'),\n('SERVIZIO_CUSTOM_HEADERS', 'X-CUSTOM-HEADER-VALUE-2', 'govpay-trace'),\n-- Custom header 3: Client identification\n('SERVIZIO_CUSTOM_HEADERS', 'X-CUSTOM-HEADER-NAME-3', 'X-Client-Name'),\n('SERVIZIO_CUSTOM_HEADERS', 'X-CUSTOM-HEADER-VALUE-3', 'GovPay'),\n('SERVIZIO_CUSTOM_HEADERS', 'ABILITATO', 'true');\n```\n\n**Caratteristiche:**\n- Gli indici (N) devono essere uguali per nome e valore dello stesso header\n- Non c'è limite al numero di custom headers (usa indici sequenziali: 1, 2, 3, ...)\n- Gli header personalizzati sono applicati a tutte le chiamate HTTP del connettore\n- I custom headers sono compatibili con tutti i tipi di autenticazione\n- Possono essere combinati con Subscription Key di Azure APIM\n\n**Esempio combinato (API Key + Custom Headers):**\n```sql\nINSERT INTO connettori (cod_connettore, cod_proprieta, valore) VALUES\n('SERVIZIO_COMBINATO', 'URL', 'https://api.partner.com'),\n('SERVIZIO_COMBINATO', 'TIPOAUTENTICAZIONE', 'API_KEY'),\n('SERVIZIO_COMBINATO', 'API_KEY_AUTH_API_KEY_NAME', 'secret-api-key-123'),\n('SERVIZIO_COMBINATO', 'API_KEY_AUTH_API_ID_NAME', 'X-API-Key'),\n-- Custom headers aggiuntivi\n('SERVIZIO_COMBINATO', 'X-CUSTOM-HEADER-NAME-1', 'X-Partner-Id'),\n('SERVIZIO_COMBINATO', 'X-CUSTOM-HEADER-VALUE-1', 'GOVPAY_001'),\n('SERVIZIO_COMBINATO', 'X-CUSTOM-HEADER-NAME-2', 'X-Request-Source'),\n('SERVIZIO_COMBINATO', 'X-CUSTOM-HEADER-VALUE-2', 'GovPay-Backend'),\n('SERVIZIO_COMBINATO', 'ABILITATO', 'true');\n```\n\nQuesto configurerà:\n- Header `X-API-Key: secret-api-key-123` (autenticazione)\n- Header `X-Partner-Id: GOVPAY_001` (custom)\n- Header `X-Request-Source: GovPay-Backend` (custom)\n\n## Logging\n\nConfigura il livello di log in `application.yml`:\n\n```yaml\nlogging:\n  level:\n    it.govpay.client.commons: DEBUG\n```\n\n## Architettura\n\n### Componenti Principali\n\n- **ConnettoreEntity**: Entity JPA che mappa la tabella `connettori` (struttura EAV)\n- **Connettore**: Model di dominio che rappresenta un connettore con tutte le sue proprietà\n- **ConnettoreConverter**: Converte da EAV (lista di entity) a model di dominio\n- **ConnettoreEntityRepository**: Repository per accedere alla tabella\n- **RestTemplateFactory**: Factory per creare RestTemplate configurati\n- **ConnettoreService**: Service principale con gestione cache e lifecycle\n\n### Test Suite\n\n- **68 test automatici** con copertura ~85%\n- Unit test: ConnettoreConverter, RestTemplateFactory\n- Integration test: ConnettoreService (cache on/off)\n- End-to-end test con H2 in-memory database\n- 9 connettori di test pre-configurati\n\n## Quality Assurance\n\nLa libreria include strumenti di Quality Assurance integrati per garantire sicurezza e qualità del codice.\n\n### Code Coverage con Jacoco\n\nJacoco è configurato per generare report di code coverage durante l'esecuzione dei test.\n\n```bash\n# Esegui test con coverage\nmvn clean test\n\n# Report disponibile in: target/site/jacoco/index.html\n```\n\n**Report generati:**\n- `target/site/jacoco/index.html` - Report HTML navigabile\n- `target/site/jacoco/jacoco.xml` - Report XML per SonarQube\n\n### OWASP Dependency Check\n\nPlugin per rilevare vulnerabilità note (CVE) nelle dipendenze del progetto.\n\n```bash\n# Esegui analisi vulnerabilità durante verify\nmvn clean verify\n\n# Esegui solo dependency check\nmvn dependency-check:aggregate\n\n# Report disponibile in: target/dependency-check-report.html\n```\n\n**Configurazione:**\n- `failBuildOnCVSS: 0` - Build fallisce per qualsiasi vulnerabilità rilevata\n- `autoUpdate: true` - Aggiorna automaticamente il database CVE\n- `nvdApiDelay: 120000` - Delay 2 minuti tra richieste NVD API\n\n**Per disabilitare il check durante sviluppo:**\n```bash\nmvn clean verify -Dowasp=none\n```\n\n**Per modificare la soglia di fallimento:**\n```xml\n\u003c!-- pom.xml --\u003e\n\u003cowasp.plugin.failBuildOnCVSS\u003e7\u003c/owasp.plugin.failBuildOnCVSS\u003e\n```\n- `0` = Fallisce per qualsiasi vulnerabilità\n- `7` = Fallisce solo per vulnerabilità HIGH/CRITICAL (CVSS ≥ 7.0)\n- `11` = Non fallisce mai (max CVSS è 10.0)\n\n## Compatibilità\n\n- Java 21+\n- Spring Boot 3.5.6+\n- Apache Tomcat 10.1.48+ (CVE-2025-61795 fixed)\n- Database supportati: PostgreSQL, MySQL, Oracle, SQL Server, HSQLDB\n\n## Licenza\n\nCopyright (c) 2025 - Licenza GNU GPL v3\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flink-it%2Fgovpay-common","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flink-it%2Fgovpay-common","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flink-it%2Fgovpay-common/lists"}