539 lines
15 KiB
Markdown
539 lines
15 KiB
Markdown
# 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.*
|