Servizi backend¶
Il backend di Jinbocho è composto da tre microservizi FastAPI. Due sono Servizi Privati (solo uso interno); uno è l'API Gateway pubblico.
Architettura in sintesi¶
┌─────────────────────────────────┐
Client (Browser) │ API Gateway :8000 (PUBBLICO) │
──────────────────► │ Validazione JWT · CORS · Proxy │
└───────────┼─────────────────┘
│ HTTP interno
┌─────────────┴─────────────┐
│ │
┌──────────────┐ ┌──────────────────┐
│ auth-service │ │ catalog-service │
│ :8001 │ │ :8002 │
│ (Privato) │ │ (Privato) │
└──────┼───────┘ └──────┼────────────┘
│ │
┌──────┴──────┐ ┌───────┴──────┐
│ auth_db │ │ catalog_db │
│ (PostgreSQL) │ │ (PostgreSQL) │
└──────────────┘ └──────────────┘
Ogni servizio ha il proprio database. I servizi non condividono mai un database. catalog-service valida i JWT localmente (non richiama auth-service).
auth-service (porta 8001)¶
Repository: jinbocho-auth-v1
Responsabilità¶
- Registrare le famiglie e il primo utente admin
- Autenticare gli utenti (email + password)
- Emettere e ruotare i token JWT di accesso e refresh
- Gestire i metadati della famiglia, incluso il flusso irreversibile di cancellazione completa dell'account
- Invitare, gestire ed esportare/importare gli account utente; gestire l'assegnazione dei ruoli (Admin, Editor, Viewer)
- Reset della password via email (SMTP), con fallback su console in sviluppo
Endpoint principali¶
| Metodo | Percorso | Auth | Descrizione |
|---|---|---|---|
POST |
/v1/auth/register |
— | Crea famiglia + primo admin |
POST |
/v1/auth/login |
— | Ottieni token di accesso + refresh |
POST |
/v1/auth/refresh |
— | Ruota il refresh token |
POST |
/v1/auth/logout |
Bearer | Revoca il refresh token |
POST |
/v1/auth/forgot-password |
— | Invia un'email di reset password (o la logga su console in sviluppo) |
POST |
/v1/auth/reset-password |
— | Consuma il token di reset e imposta una nuova password |
GET |
/v1/families/{family_id} |
Bearer | Ottieni informazioni sulla famiglia (qualsiasi membro) |
PATCH |
/v1/families/{family_id} |
Bearer (Admin) | Aggiorna le informazioni della famiglia |
POST |
/v1/families/{family_id}/confirm-deletion |
Bearer (Admin) | Verifica password + nome famiglia prima della cancellazione irreversibile sotto |
DELETE |
/v1/families/{family_id} |
Bearer (Admin) | Cancella permanentemente la famiglia e tutti gli utenti, in cascata su refresh + reset token |
GET |
/v1/users/me |
Bearer | Ottieni l'utente autenticato corrente |
PATCH |
/v1/users/me |
Bearer | Aggiorna il proprio nome / obiettivo di lettura |
GET |
/v1/users/ |
Bearer | Elenca i membri della famiglia |
POST |
/v1/users/ |
Bearer (Admin) | Invita un nuovo utente (invia un'email di invito; password non ancora impostata) |
PATCH |
/v1/users/{id} |
Bearer (Admin) | Aggiorna utente / cambia ruolo |
DELETE |
/v1/users/{id} |
Bearer (Admin) | Rimuovi un utente |
GET |
/v1/users/export |
Bearer (Admin) | Esporta l'identità della famiglia + l'elenco dei membri per backup |
POST |
/v1/users/import |
Bearer (Admin) | Ripristina gli utenti da un export di backup nella famiglia corrente |
GET |
/health |
— | Health check |
La cancellazione dell'account coinvolge due servizi
La cancellazione completa dell'account coinvolge sia auth-service che catalog-service. Il frontend chiama
POST /v1/families/{id}/confirm-deletion (verifica le credenziali), poi
DELETE /v1/catalog/account (elimina i dati di location/catalogo, vedi sotto), poi
DELETE /v1/families/{id} (elimina la famiglia e i suoi utenti) — in quest'ordine.
Variabili d'ambiente¶
| Variabile | Obbligatoria | Default | Descrizione |
|---|---|---|---|
DATABASE_URL |
✅ | — | postgresql+asyncpg://... |
JWT_SECRET_KEY |
✅ | — | Segreto condiviso — deve corrispondere a catalog + gateway |
JWT_ALGORITHM |
— | HS256 |
Algoritmo di firma |
JWT_ISSUER |
— | jinbocho-auth |
Claim issuer del token (iss) |
JWT_AUDIENCE |
— | jinbocho |
Claim audience del token (aud) |
ACCESS_TOKEN_EXPIRE_MINUTES |
— | 30 |
Durata del token di accesso |
REFRESH_TOKEN_EXPIRE_DAYS |
— | 30 |
Durata del refresh token |
PASSWORD_RESET_EXPIRE_MINUTES |
— | 15 |
Durata del token di reset password |
FRONTEND_BASE_URL |
— | http://localhost:5173 |
Usato per costruire il link di reset password inviato via email |
SMTP_HOST |
— | (vuoto) | Server SMTP per l'invio email. Lascia vuoto per loggare le email su console invece di inviarle (sviluppo) |
SMTP_PORT |
— | 587 |
Porta SMTP |
SMTP_USER |
— | (vuoto) | Username di autenticazione SMTP |
SMTP_PASSWORD |
— | (vuoto) | Password di autenticazione SMTP |
EMAIL_FROM |
— | noreply@jinbocho.local |
Indirizzo "From" sulle email in uscita |
DEBUG |
— | false |
Abilita il logging delle query SQL |
Payload del token JWT¶
I token emessi dall'auth-service contengono:
{
"sub": "user-uuid",
"email": "alice@example.com",
"family_id": "family-uuid",
"role": "admin",
"exp": 1234567890,
"iss": "jinbocho-auth",
"aud": "jinbocho"
}
Sia catalog-service che il gateway validano questo token usando il JWT_SECRET_KEY condiviso — nessuno dei due richiama auth-service per farlo.
Avvio in locale (senza Docker)¶
cd jinbocho-auth-v1
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # modifica DATABASE_URL per Postgres locale
uvicorn app.main:app --reload --host 0.0.0.0 --port 8001
Swagger UI: http://localhost:8001/docs
Esecuzione dei test¶
cd jinbocho-auth-v1
source .venv/bin/activate
pytest tests/ -v
# Solo unit test (senza DB):
pytest tests/unit/ -v
# Test di integrazione (richiede Postgres in esecuzione):
pytest tests/integration/ -v
Schema del database¶
Tabelle gestite tramite migrazioni Alembic (applicate automaticamente all'avvio):
families— Account famiglia (nome, id)users— Account utente (email, hashed_password, ruolo, family_id)refresh_tokens— Refresh token emessi con supporto alla revocapassword_reset_tokens— Token di reset password / invito emessi, con scadenza
catalog-service (porta 8002)¶
Repository: jinbocho-catalog-v1
Responsabilità¶
- Gestire la gerarchia fisica delle posizioni: stanze → librerie → sezioni → scaffali
- Gestire i record bibliografici (titolo, autore, ISBN, editore, copertina, presentazione "incipit" assistita da AI)
- Gestire i libri posseduti (copie che collegano un record a uno scaffale + stato di lettura), tracciamento di lettori multipli (
reads) e prestiti tra membri della famiglia (loans) - Ricerca ISBN online tramite Open Library (primario) e Google Books (fallback), con cache locale
- Ricerca libri, log storico/audit, esportazione CSV/JSON, export/import completo della libreria per i backup
- Mappa visiva della libreria
- Cancellazione completa dei dati account in stile GDPR (la sua metà del flusso cross-service descritto sotto auth-service)
Endpoint principali¶
| Metodo | Percorso | Auth | Descrizione |
|---|---|---|---|
GET/POST |
/v1/rooms/ |
Bearer | Elenca / crea stanze |
GET/PATCH/DELETE |
/v1/rooms/{id} |
Bearer | CRUD stanza |
GET/POST |
/v1/bookcases/ |
Bearer | Elenca (filtra per room_id) / crea librerie |
GET/PATCH/DELETE |
/v1/bookcases/{id} |
Bearer | CRUD libreria |
GET/POST |
/v1/sections/ |
Bearer | Elenca (filtra per bookcase_id) / crea sezioni |
GET/PATCH/DELETE |
/v1/sections/{id} |
Bearer | CRUD sezione |
GET/POST |
/v1/shelves/ |
Bearer | Elenca (filtra per section_id) / crea scaffali |
GET/PATCH/DELETE |
/v1/shelves/{id} |
Bearer | CRUD scaffale |
GET/POST |
/v1/bibliographic-records/ |
Bearer | Cerca record (?q=) / crea record |
GET/PATCH/DELETE |
/v1/bibliographic-records/{id} |
Bearer | CRUD record |
GET |
/v1/bibliographic-records/genres |
Bearer | Generi normalizzati distinti nella biblioteca di famiglia, con conteggi |
GET |
/v1/bibliographic-records/{id}/incipit |
Bearer | Ottieni o genera pigramente la presentazione del libro |
PUT |
/v1/bibliographic-records/{id}/incipit |
Bearer | Imposta la presentazione (testo manuale o generato da AI) |
GET |
/v1/ingestion/isbn/{isbn} |
Bearer | Ricerca metadati ISBN (cache → Open Library → Google Books) |
GET |
/v1/ingestion/search |
Bearer | Cerca libri online per titolo/autore |
POST |
/v1/ingestion/bulk-lookup |
Bearer | Ricerca ISBN massiva |
GET/POST |
/v1/books/ |
Bearer | Libri posseduti (lista con limit/offset) |
GET |
/v1/books/reads |
Bearer | Elenca tutte le letture della famiglia |
GET |
/v1/books/loans/active |
Bearer | Elenca tutti i prestiti attivi della famiglia |
GET/PATCH/DELETE |
/v1/books/{id} |
Bearer | CRUD libro posseduto |
POST |
/v1/books/{id}/position |
Bearer | Aggiorna posizione sullo scaffale (query param) |
POST |
/v1/books/{id}/reading-status |
Bearer | Aggiorna stato di lettura (query param) |
GET |
/v1/books/{id}/history |
Bearer | Ottieni la storia del libro |
GET/POST |
/v1/books/{id}/reads |
Bearer | Elenca i lettori di un libro / segna un membro come avente letto il libro |
DELETE |
/v1/books/{id}/reads/{user_id} |
Bearer | Rimuovi la marcatura di lettura di un membro |
GET/POST |
/v1/books/{id}/loans |
Bearer | Elenca lo storico prestiti di un libro / prestalo a un membro della famiglia |
POST |
/v1/books/{id}/loans/return |
Bearer | Segna il prestito attivo come restituito |
GET |
/v1/map/bookcase/{id} |
Bearer | Dati mappa visiva della libreria |
GET |
/v1/export/books.csv |
Bearer (Admin) | Esporta i libri posseduti come CSV |
GET |
/v1/export/books.json |
Bearer (Admin) | Esporta i libri posseduti come JSON |
GET |
/v1/export/full |
Bearer (Admin) | Backup completo della libreria: location, record, libri, prestiti, letture, storico |
POST |
/v1/import/full |
Bearer (Admin) | Ripristina un backup completo della libreria prodotto da /v1/export/full |
POST |
/v1/members/removed |
Bearer (Admin) | Salva uno snapshot di nome/email/ruolo di un membro della famiglia appena prima che auth-service lo elimini definitivamente |
DELETE |
/v1/account/ |
Bearer (Admin) | Metà lato catalog-service della cancellazione completa dell'account (vedi sezione auth-service) |
GET |
/health |
— | Health check |
Query param, non corpo JSON
POST /v1/books/{id}/position e POST /v1/books/{id}/reading-status leggono i
parametri dalla query string, non da un corpo JSON. Costruisci gli URL di conseguenza:
POST /v1/books/abc/reading-status?reading_status=read
POST /v1/books/abc/position?section_id=x&shelf_id=y&position=3
Variabili d'ambiente¶
| Variabile | Obbligatoria | Default | Descrizione |
|---|---|---|---|
DATABASE_URL |
✅ | — | postgresql+asyncpg://... |
JWT_SECRET_KEY |
✅ | — | Deve corrispondere a auth + gateway |
JWT_ALGORITHM |
— | HS256 |
Algoritmo di firma |
JWT_ISSUER |
— | jinbocho-auth |
Deve corrispondere all'issuer dei token di auth-service |
JWT_AUDIENCE |
— | jinbocho |
Deve corrispondere all'audience dei token di auth-service |
GOOGLE_BOOKS_API_KEY |
— | (vuoto) | Ricerca ISBN di fallback; senza chiave la quota è condivisa/limitata |
OPEN_LIBRARY_URL |
— | https://openlibrary.org |
URL base Open Library |
GOOGLE_BOOKS_URL |
— | https://www.googleapis.com/books/v1 |
URL base Google Books |
ISBN_CACHE_TTL_DAYS |
— | 30 |
Giorni di cache dei metadati ISBN in locale |
DEBUG |
— | false |
Logging delle query SQL |
Nessuna chiamata di ritorno verso auth-service
Versioni precedenti di questo servizio chiamavano auth-service per validare i token. Ora
valida i JWT localmente usando JWT_SECRET_KEY / JWT_ISSUER / JWT_AUDIENCE condivisi —
non esiste un'impostazione AUTH_SERVICE_URL, anche se ne è ancora presente una (inutilizzata)
nel .env.example del repository.
Flusso di ricerca ISBN¶
Richiesta /v1/ingestion/isbn/9788845292613
│
├─► Cache locale nel DB? → risponde immediatamente
│
├─► Open Library → recupera metadati (gratuito, nessuna chiave)
│ Trovato? → salva in cache → risponde
│
└─► Google Books → recupera metadati (richiede chiave API)
Trovato? → salva in cache → risponde
Non trovato? → 404 "ISBN not found"
Avvio in locale (senza Docker)¶
cd jinbocho-catalog-v1
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # imposta DATABASE_URL e JWT_SECRET_KEY
uvicorn app.main:app --reload --host 0.0.0.0 --port 8002
Swagger UI: http://localhost:8002/docs
Esecuzione dei test¶
cd jinbocho-catalog-v1
source .venv/bin/activate
pytest tests/ -v
Schema del database¶
Tabelle gestite tramite migrazioni Alembic:
rooms— Stanze fisiche (per famiglia)bookcases— Librerie in una stanzasections— Colonne verticali in una libreriashelves— Ripiani orizzontali in una sezionebibliographic_records— Metadati del libro (titolo, autore, ISBN, editore, cover_url, incipit)owned_books— Copie che collegano un record a uno scaffale + stato di lettura + posizionebook_reads— Quali membri della famiglia hanno letto quale libro possedutobook_loans— Storico prestiti (chi ha preso in prestito, date di prestito/restituzione) per libro possedutoisbn_cache— Risultati di ricerca ISBN in cache (con TTL)audit_log— Storico dei movimenti dei libri e dei cambi di statoremoved_members— Snapshot di nome/email/ruolo degli utenti eliminati, per continuità di export/import
api-gateway (porta 8000)¶
Repository: jinbocho-api-gateway-v1
Responsabilità¶
- Unico punto di ingresso pubblico per tutte le richieste del client
- Validazione JWT al confine (verifica il token prima di fare il proxy)
- Applicazione della policy CORS
- Routing delle richieste ai servizi interni, controllato da
JINBOCHO_FEATURES
Tutti gli endpoint sono montati sotto /v1 e proxati ai servizi interni. A differenza di uno specchio 1:1, il gateway raggruppa gli endpoint di catalog-service in due prefissi pubblici — /v1/catalog/* per libri/record/ingestion/export/import/account/members, e /v1/location/* per stanze/librerie/sezioni/scaffali — anche se entrambi sono serviti dallo stesso processo catalog-service.
Tabella di routing¶
| Percorso Gateway | Inoltrato a | Note |
|---|---|---|
/v1/auth/* |
auth-service:8001/v1/auth/* |
|
/v1/families/* |
auth-service:8001/v1/families/* |
|
/v1/users/* |
auth-service:8001/v1/users/* |
|
/v1/catalog/* |
catalog-service:8002/v1/* |
Record, libri, ingestion, export, import, members, account |
/v1/location/* |
catalog-service:8002/v1/* |
Stanze, librerie, sezioni, scaffali |
/health |
locale |
Quindi, ad esempio, le chiamate bibliographic-records del frontend vanno a /v1/catalog/bibliographic-records, e le chiamate rooms vanno a /v1/location/rooms — non direttamente a /v1/records o /v1/rooms.
Variabili d'ambiente¶
| Variabile | Obbligatoria | Default | Descrizione |
|---|---|---|---|
JWT_SECRET_KEY |
✅ | — | Deve corrispondere a auth + catalog |
JWT_ALGORITHM |
— | HS256 |
Algoritmo di firma |
AUTH_SERVICE_URL |
— | http://auth-service:8001 |
URL interno di auth-service |
CATALOG_SERVICE_URL |
— | http://catalog-service:8002 |
URL interno di catalog-service |
CORS_ORIGINS |
— | ["*"] |
Array JSON delle origini consentite, es. ["https://jinbocho-fe.onrender.com"] |
JINBOCHO_FEATURES |
— | catalog,auth |
Moduli abilitati separati da virgola |
DEBUG |
— | false |
Modalità debug FastAPI + logging dettagliato |
CORS in produzione
Non usare mai ["*"] in produzione. Imposta CORS_ORIGINS all'URL esatto del frontend.
In sviluppo locale l'ambiente docker-compose usa ["*"] — questo è accettabile.
Avvio in locale (senza Docker)¶
cd jinbocho-api-gateway-v1
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
Swagger UI: http://localhost:8000/docs
Qualità del codice — tutti i servizi¶
Esegui prima di ogni commit:
# Type checking (strict)
python -m mypy app --strict
# Linting + auto-fix
ruff check app tests
ruff check --fix app tests
# Test
pytest tests/ -v