GapFill Flowee
This commit is contained in:
@@ -0,0 +1,538 @@
|
||||
# Integrazione Aeterna – Archiviazione Sostitutiva PecHub
|
||||
|
||||
Documento tecnico sull'integrazione di PecHub con Aeterna,
|
||||
piattaforma di conservazione digitale conforme E-ARK gestita da Idra Informatica.
|
||||
|
||||
---
|
||||
|
||||
## Indice
|
||||
|
||||
1. [Panoramica del provider](#1-panoramica-del-provider)
|
||||
2. [Architettura Aeterna](#2-architettura-aeterna)
|
||||
3. [Autenticazione JWT](#3-autenticazione-jwt)
|
||||
4. [Ingest – Upload SIP](#4-ingest--upload-sip)
|
||||
5. [Formato SIP BagIt (RFC 8493)](#5-formato-sip-bagit-rfc-8493)
|
||||
6. [Polling stato ingest](#6-polling-stato-ingest)
|
||||
7. [Disseminazione (DIP)](#7-disseminazione-dip)
|
||||
8. [Mapping stati Aeterna → PecHub](#8-mapping-stati-aeterna--pechub)
|
||||
9. [Configurazione in PecHub](#9-configurazione-in-pechub)
|
||||
10. [Esempi curl completi](#10-esempi-curl-completi)
|
||||
11. [Note operative](#11-note-operative)
|
||||
|
||||
---
|
||||
|
||||
## 1. Panoramica del provider
|
||||
|
||||
| Campo | Valore |
|
||||
|-------------------|-----------------------------------------------|
|
||||
| Provider | Idra Informatica srl |
|
||||
| Piattaforma | Aeterna v0.1.0 |
|
||||
| URL applicazione | https://aeterna.idrainformatica.it |
|
||||
| Endpoint API | https://api.aeterna.idrainformatica.it |
|
||||
| Documentazione | https://api.aeterna.idrainformatica.it/docs |
|
||||
| OpenAPI JSON | https://api.aeterna.idrainformatica.it/openapi.json |
|
||||
| Standard | E-ARK CSIP 2.1.0, BagIt RFC 8493, PREMIS 3.0, METS 1.12 |
|
||||
|
||||
### Credenziali tenant PecHub
|
||||
|
||||
| Campo | Valore |
|
||||
|-------------|---------------------------------|
|
||||
| Org. name | pechub |
|
||||
| Tenant slug | pechub |
|
||||
| Username | matteo@idrainformatica.it |
|
||||
| Password | (cifrata nel DB PecHub) |
|
||||
| Tenant ID | 366d3d51-b25d-46fc-8f9c-9bc28d902620 |
|
||||
|
||||
---
|
||||
|
||||
## 2. Architettura Aeterna
|
||||
|
||||
Aeterna e' una piattaforma multi-tenant FastAPI (Python).
|
||||
Ogni organizzazione (tenant) ha:
|
||||
- Container MinIO dedicato per lo storage isolato
|
||||
- Collection Apache Solr dedicata per la ricerca full-text
|
||||
- RBAC (Role-Based Access Control) per-tenant
|
||||
|
||||
Il ciclo di vita di un documento archiviale in Aeterna:
|
||||
|
||||
```
|
||||
SIP (upload) → AIP (ingest pipeline) → DIP (disseminazione)
|
||||
| | |
|
||||
BagIt ZIP PREMIS events ZIP scaricabile
|
||||
multipart METS 1.12 generato
|
||||
form-data validazione E-ARK
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Autenticazione JWT
|
||||
|
||||
Aeterna usa JWT Bearer token, NON HTTP Basic.
|
||||
Il token ha durata 3600 secondi (1 ora). Esiste un refresh token.
|
||||
|
||||
### Login
|
||||
|
||||
```
|
||||
POST /api/v1/auth/login
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
Body:
|
||||
```json
|
||||
{
|
||||
"email": "matteo@idrainformatica.it",
|
||||
"password": "...",
|
||||
"tenant_slug": "pechub"
|
||||
}
|
||||
```
|
||||
|
||||
Risposta 200:
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIs...",
|
||||
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
|
||||
"token_type": "bearer",
|
||||
"expires_in": 3600,
|
||||
"user": {
|
||||
"id": "e3cac60b-d942-4590-94fe-932c0e14e836",
|
||||
"email": "matteo@idrainformatica.it",
|
||||
"full_name": "Matteo Giustini",
|
||||
"is_platform_admin": false,
|
||||
"tenant_id": "366d3d51-b25d-46fc-8f9c-9bc28d902620",
|
||||
"permissions": [
|
||||
"ingest:submit", "ingest:manage", "packages:read",
|
||||
"packages:create", "dissemination:read", "dissemination:download",
|
||||
"preservation:manage", "audit:read", "settings:manage", ...
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Utilizzo del token nelle richieste successive:
|
||||
```
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
|
||||
```
|
||||
|
||||
### Refresh token
|
||||
|
||||
```
|
||||
POST /api/v1/auth/refresh
|
||||
Content-Type: application/json
|
||||
|
||||
{"refresh_token": "eyJhbGciOiJIUzI1NiIs..."}
|
||||
```
|
||||
|
||||
### Verifica identita'
|
||||
|
||||
```
|
||||
GET /api/v1/auth/me
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Ingest – Upload SIP
|
||||
|
||||
L'endpoint di ingest accetta pacchetti SIP in due formati:
|
||||
- **E-ARK CSIP v2.2.0** (ZIP con METS.xml in root)
|
||||
- **BagIt RFC 8493** (ZIP con bagit.txt + data/) — **formato scelto per PecHub**
|
||||
|
||||
### Endpoint
|
||||
|
||||
```
|
||||
POST /api/v1/ingest/upload
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: multipart/form-data
|
||||
```
|
||||
|
||||
### Campi form-data
|
||||
|
||||
| Campo | Tipo | Obbligatorio | Descrizione |
|
||||
|---------------------|--------|:------------:|--------------------------------------|
|
||||
| `file` | file | si | ZIP SIP (E-ARK CSIP o BagIt) |
|
||||
| `title` | string | si | Titolo del pacchetto |
|
||||
| `description` | string | no | Descrizione |
|
||||
| `creator` | string | no | Nome del produttore |
|
||||
| `submission_agreement` | string | no | ID o URL dell'accordo di versamento |
|
||||
| `ead3_file` | file | no | EAD3 finding aid XML (GAP-09) |
|
||||
| `eac_cpf_file` | file | no | EAC-CPF authority record XML |
|
||||
|
||||
### Risposta 202 Accepted
|
||||
|
||||
```json
|
||||
{
|
||||
"package_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"pid": "urn:pechub:aip:a1b2c3d4",
|
||||
"status": "UPLOADED",
|
||||
"task_id": "celery-task-uuid",
|
||||
"message": "SIP uploaded successfully. Processing started.",
|
||||
"submitted_at": "2026-06-18T09:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
Il `package_id` e' l'identificatore da usare per polling e DIP.
|
||||
|
||||
---
|
||||
|
||||
## 5. Formato SIP BagIt (RFC 8493)
|
||||
|
||||
PecHub costruisce pacchetti BagIt in memoria (`build_bagit_sip` in `conservatore_client.py`).
|
||||
|
||||
### Struttura ZIP generata
|
||||
|
||||
```
|
||||
pechub-pec-{message_id}/
|
||||
bagit.txt # Dichiarazione BagIt (obbligatorio)
|
||||
bag-info.txt # Metadati descrittivi (opzionale)
|
||||
manifest-sha256.txt # Checksum SHA-256 dei file in data/
|
||||
data/
|
||||
{message_id}.eml # Messaggio PEC grezzo
|
||||
```
|
||||
|
||||
### Contenuto bagit.txt
|
||||
|
||||
```
|
||||
BagIt-Version: 1.0
|
||||
Tag-File-Character-Encoding: UTF-8
|
||||
```
|
||||
|
||||
### Contenuto bag-info.txt (esempio)
|
||||
|
||||
```
|
||||
Bag-Software-Agent: PecHub Archival Module
|
||||
Bagging-Date: 2026-06-18
|
||||
External-Identifier: a1b2c3d4-e5f6-7890-abcd-ef1234567890
|
||||
Source-Organization: PecHub
|
||||
Description: PEC oggetto del messaggio (max 500 char)
|
||||
Contact-Email: mittente@pec.it
|
||||
External-Description: PEC a destinatario@pec.it
|
||||
Bag-Group-Identifier: 2026-06-18
|
||||
```
|
||||
|
||||
### Contenuto manifest-sha256.txt
|
||||
|
||||
```
|
||||
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 data/a1b2c3d4-e5f6.eml
|
||||
```
|
||||
|
||||
### Rilevamento automatico BagIt da Aeterna
|
||||
|
||||
Aeterna rileva automaticamente il formato BagIt dalla presenza di `bagit.txt`
|
||||
nella root del bag all'interno dello ZIP. Non e' necessario specificare il formato.
|
||||
La pipeline verifica i checksum del manifest prima dell'ingest.
|
||||
|
||||
---
|
||||
|
||||
## 6. Polling stato ingest
|
||||
|
||||
Dopo l'upload, la pipeline di Aeterna processa il pacchetto in modo asincrono.
|
||||
|
||||
### Endpoint status
|
||||
|
||||
```
|
||||
GET /api/v1/ingest/{package_id}/status
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
### Risposta
|
||||
|
||||
```json
|
||||
{
|
||||
"package_id": "a1b2c3d4-...",
|
||||
"pid": "urn:pechub:aip:a1b2c3d4",
|
||||
"status": "PROCESSING",
|
||||
"task_id": "celery-task-uuid",
|
||||
"pipeline_stage": "format_identification",
|
||||
"progress_pct": 40,
|
||||
"steps": [
|
||||
{"name": "validation", "status": "completed", ...},
|
||||
{"name": "format_identification", "status": "running", ...},
|
||||
{"name": "virus_scan", "status": "pending", ...}
|
||||
],
|
||||
"is_valid": null,
|
||||
"validation_errors": 0,
|
||||
"error_message": null,
|
||||
"submitted_at": "2026-06-18T09:00:00Z",
|
||||
"completed_at": null
|
||||
}
|
||||
```
|
||||
|
||||
### Stati possibili
|
||||
|
||||
| Status Aeterna | Significato | Stato PecHub |
|
||||
|----------------|------------------------------------------|--------------|
|
||||
| `UPLOADED` | SIP ricevuto, elaborazione non iniziata | `uploading` |
|
||||
| `VALIDATING` | Validazione E-ARK/BagIt in corso | `uploading` |
|
||||
| `PROCESSING` | Pipeline ingest in corso | `uploading` |
|
||||
| `INGESTING` | AIP in fase di creazione | `uploading` |
|
||||
| `ACTIVE` | AIP attivo, conservazione completata | `confirmed` |
|
||||
| `FAILED` | Pipeline fallita con errori | `failed` |
|
||||
| `REJECTED` | Pacchetto non conforme agli standard | `rejected` |
|
||||
| `DELETED` | Soft-delete | `rejected` |
|
||||
|
||||
### Report completo
|
||||
|
||||
```
|
||||
GET /api/v1/ingest/{package_id}/report
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Disseminazione (DIP)
|
||||
|
||||
### Richiesta generazione DIP
|
||||
|
||||
```
|
||||
POST /api/v1/packages/{package_id}/disseminate
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
|
||||
{"note": "Richiesto da PecHub Archival Module"}
|
||||
```
|
||||
|
||||
Risposta 202:
|
||||
```json
|
||||
{
|
||||
"id": "dip-uuid",
|
||||
"pid": "urn:pechub:dip:...",
|
||||
"status": "PROCESSING",
|
||||
"size_bytes": null,
|
||||
"is_available": false
|
||||
}
|
||||
```
|
||||
|
||||
### Verifica stato DIP
|
||||
|
||||
```
|
||||
GET /api/v1/packages/{package_id}/dip
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
Quando `is_available: true`, il DIP e' scaricabile.
|
||||
|
||||
### Download DIP
|
||||
|
||||
```
|
||||
GET /api/v1/packages/{package_id}/dip/download
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
Risposta: stream ZIP del DIP.
|
||||
|
||||
---
|
||||
|
||||
## 8. Mapping stati Aeterna → PecHub
|
||||
|
||||
```python
|
||||
_STATUS_MAP = {
|
||||
"UPLOADED": "uploading",
|
||||
"VALIDATING": "uploading",
|
||||
"PROCESSING": "uploading",
|
||||
"INGESTING": "uploading",
|
||||
"ACTIVE": "confirmed", # conservazione completata
|
||||
"FAILED": "failed",
|
||||
"REJECTED": "rejected",
|
||||
"DELETED": "rejected",
|
||||
}
|
||||
```
|
||||
|
||||
Il `versamento_id` in PecHub corrisponde al `package_id` di Aeterna (UUID v4).
|
||||
|
||||
---
|
||||
|
||||
## 9. Configurazione in PecHub
|
||||
|
||||
### Impostazioni tenant
|
||||
|
||||
Dalla pagina **Impostazioni → Archiviazione Sostitutiva**:
|
||||
|
||||
| Campo | Valore per Aeterna |
|
||||
|-------------------------|---------------------------------------------|
|
||||
| Modalita' | Produzione |
|
||||
| Identificativo conservatore | `aeterna` |
|
||||
| URL endpoint API | `https://api.aeterna.idrainformatica.it` |
|
||||
| Tenant Slug | `pechub` |
|
||||
| Username | `matteo@idrainformatica.it` |
|
||||
| Password | (da fornire, viene cifrata AES-256-GCM) |
|
||||
|
||||
### Riconoscimento automatico del provider
|
||||
|
||||
Il factory `ConservatoreClient.from_tenant_credentials()` rileva Aeterna se:
|
||||
- `conservatore_id == "aeterna"` (case-insensitive), OPPURE
|
||||
- `"aeterna"` e' presente nell'URL endpoint, OPPURE
|
||||
- `"idrainformatica"` e' presente nell'URL endpoint
|
||||
|
||||
Se rilevato come Aeterna, usa `AeternaConservatoreClient` (JWT + BagIt).
|
||||
Altrimenti usa `ProductionConservatoreClient` (HTTP Basic, standard AgID legacy).
|
||||
|
||||
### Test connessione
|
||||
|
||||
Dopo aver configurato e salvato le impostazioni, usare il pulsante
|
||||
**"Testa connessione"** in Impostazioni → Archiviazione Sostitutiva.
|
||||
|
||||
Oppure via API:
|
||||
```
|
||||
POST /api/v1/settings/test-conservatore
|
||||
Authorization: Bearer <pechub-token>
|
||||
```
|
||||
|
||||
Risposta:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Connessione ad Aeterna riuscita (utente: matteo@idrainformatica.it)",
|
||||
"latency_ms": 342,
|
||||
"provider_info": {
|
||||
"platform": "Aeterna",
|
||||
"tenant_slug": "pechub",
|
||||
"user_email": "matteo@idrainformatica.it",
|
||||
"permissions_count": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Esempi curl completi
|
||||
|
||||
### Login
|
||||
|
||||
```bash
|
||||
TOKEN=$(curl -s -X POST https://api.aeterna.idrainformatica.it/api/v1/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email":"matteo@idrainformatica.it","password":"...","tenant_slug":"pechub"}' \
|
||||
| jq -r .access_token)
|
||||
echo "Token: $TOKEN"
|
||||
```
|
||||
|
||||
### Upload SIP BagIt
|
||||
|
||||
```bash
|
||||
# Crea un BagIt minimo di test
|
||||
mkdir -p /tmp/testbag/data
|
||||
echo "Hello PEC" > /tmp/testbag/data/test.eml
|
||||
SHA=$(sha256sum /tmp/testbag/data/test.eml | awk '{print $1}')
|
||||
|
||||
cat > /tmp/testbag/bagit.txt <<EOF
|
||||
BagIt-Version: 1.0
|
||||
Tag-File-Character-Encoding: UTF-8
|
||||
EOF
|
||||
|
||||
cat > /tmp/testbag/manifest-sha256.txt <<EOF
|
||||
$SHA data/test.eml
|
||||
EOF
|
||||
|
||||
# ZIP del bag
|
||||
cd /tmp && zip -r testbag.zip testbag/
|
||||
|
||||
# Upload
|
||||
curl -s -X POST https://api.aeterna.idrainformatica.it/api/v1/ingest/upload \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-F "file=@/tmp/testbag.zip;type=application/zip" \
|
||||
-F "title=Test PEC da PecHub" \
|
||||
-F "description=Messaggio di test" \
|
||||
-F "creator=PecHub" \
|
||||
| jq .
|
||||
```
|
||||
|
||||
### Polling status
|
||||
|
||||
```bash
|
||||
PACKAGE_ID="a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
|
||||
curl -s https://api.aeterna.idrainformatica.it/api/v1/ingest/$PACKAGE_ID/status \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
| jq '{status:.status, stage:.pipeline_stage, pct:.progress_pct}'
|
||||
```
|
||||
|
||||
### Lista pacchetti
|
||||
|
||||
```bash
|
||||
curl -s "https://api.aeterna.idrainformatica.it/api/v1/packages?limit=10" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
| jq '.items[] | {id:.id, title:.title, status:.status}'
|
||||
```
|
||||
|
||||
### Report ingest
|
||||
|
||||
```bash
|
||||
curl -s https://api.aeterna.idrainformatica.it/api/v1/ingest/$PACKAGE_ID/report \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
| jq .
|
||||
```
|
||||
|
||||
### Richiesta DIP
|
||||
|
||||
```bash
|
||||
curl -s -X POST \
|
||||
"https://api.aeterna.idrainformatica.it/api/v1/packages/$PACKAGE_ID/disseminate" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"note":"Richiesto da PecHub"}' \
|
||||
| jq .
|
||||
```
|
||||
|
||||
### Download DIP
|
||||
|
||||
```bash
|
||||
curl -L -o /tmp/dip.zip \
|
||||
"https://api.aeterna.idrainformatica.it/api/v1/packages/$PACKAGE_ID/dip/download" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Note operative
|
||||
|
||||
### Dimensione pacchetti
|
||||
|
||||
Non ci sono limiti documentati. I file EML di PEC sono tipicamente 10-500 KB.
|
||||
Il timeout HTTP nel client e' impostato a 120 secondi per l'upload.
|
||||
|
||||
### Concorrenza
|
||||
|
||||
Ogni `AeternaConservatoreClient` gestisce il token JWT in memoria.
|
||||
Se il worker usa piu' istanze concorrenti, ogni istanza effettua il suo login.
|
||||
Il token dura 3600s, il rinnovo automatico avviene 60s prima della scadenza.
|
||||
|
||||
### Retry policy
|
||||
|
||||
In caso di fallimento dell'upload, il batch `ArchivalBatch` in PecHub
|
||||
ha `max_attempts=3` e un `next_retry_at` con back-off esponenziale.
|
||||
|
||||
### Standard di riferimento
|
||||
|
||||
- **E-ARK CSIP v2.1.0** — Common Specification for Information Packages
|
||||
- **E-ARK SIP/AIP/DIP** — Submission/Archival/Dissemination Information Package
|
||||
- **BagIt RFC 8493** — formato file standard per trasferimento dati
|
||||
- **PREMIS 3.0** — metadati di preservazione
|
||||
- **METS 1.12** — Metadata Encoding and Transmission Standard
|
||||
- **Dublin Core / EAD** — metadati descrittivi
|
||||
|
||||
### Codice sorgente rilevante
|
||||
|
||||
| File | Descrizione |
|
||||
|------|-------------|
|
||||
| `worker/app/archival/conservatore_client.py` | Client completo con AeternaConservatoreClient, BagIt builder, factory |
|
||||
| `worker/scripts/test_aeterna_transmission.py` | Script di test standalone |
|
||||
| `backend/app/api/v1/settings.py` | Endpoint test-conservatore |
|
||||
| `backend/app/models/tenant_settings.py` | Modello DB con conservatore_tenant_slug |
|
||||
| `backend/app/services/tenant_settings_service.py` | Servizio credenziali |
|
||||
| `backend/alembic/versions/0020_add_conservatore_tenant_slug.py` | Migrazione DB |
|
||||
| `frontend/src/pages/Settings/SettingsPage.tsx` | UI configurazione conservatore |
|
||||
| `frontend/src/api/settings.api.ts` | Client API frontend |
|
||||
|
||||
### Esecuzione script di test
|
||||
|
||||
```bash
|
||||
# Dal server, dentro il container worker
|
||||
docker exec -it pechub-worker-1 \
|
||||
python /app/scripts/test_aeterna_transmission.py
|
||||
|
||||
# I risultati vengono salvati in /tmp/aeterna_test_results.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Documento generato il 2026-06-18. Versione Aeterna: 0.1.0.*
|
||||
Reference in New Issue
Block a user