Servizi backend¶
Il backend di Jinbocho è composto da quattro microservizi FastAPI. Tre 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│ │ ai-service │
│ :8001 │ │ :8002 │ │ :8003 │
│ (Privato) │ │ (Privato) │ │ (Privato) │
└──────┬───────┘ └──────┬───────┘ └──────────────┘
│ │
┌──────┴───────┐ ┌──────┴──────┐
│ auth_db │ │ catalog_db │
│ (PostgreSQL) │ │ (PostgreSQL) │
└──────────────┘ └──────────────┘
Ogni servizio ha il proprio database. I servizi non condividono mai un database e comunicano solo via HTTP attraverso le regole di routing del gateway.
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 e gli account utente
- Gestire l’assegnazione dei ruoli (Admin, Editor, Viewer)
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 |
GET |
/v1/families/me |
Bearer | Ottieni la famiglia corrente |
PATCH |
/v1/families/me |
Bearer (Admin) | Aggiorna la famiglia |
GET |
/v1/users/ |
Bearer | Elenca i membri della famiglia |
POST |
/v1/users/ |
Bearer (Admin) | Crea un utente |
PATCH |
/v1/users/{id} |
Bearer (Admin) | Aggiorna utente / cambia ruolo |
DELETE |
/v1/users/{id} |
Bearer (Admin) | Rimuovi un utente |
GET |
/health |
— | Health check |
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 |
ACCESS_TOKEN_EXPIRE_MINUTES |
— | 30 |
Durata del token di accesso |
REFRESH_TOKEN_EXPIRE_DAYS |
— | 30 |
Durata del refresh token |
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.
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 revoca
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)
- Gestire i libri posseduti (copie che collegano un record a uno scaffale + stato di lettura)
- Ricerca ISBN tramite Open Library (primario) e Google Books (fallback), con cache locale
- Ricerca libri, log storico/audit, esportazione (CSV/JSON)
- Mappa visiva della libreria
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 stanza) / crea librerie |
GET/PATCH/DELETE |
/v1/bookcases/{id} |
Bearer | CRUD libreria |
GET/POST |
/v1/sections/ |
Bearer | Sezioni per libreria |
GET/POST |
/v1/shelves/ |
Bearer | Scaffali per sezione |
GET/POST |
/v1/bibliographic-records/ |
Bearer | Record bibliografici (cerca con ?q=, filtra con ?genre=<codice>) |
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 la presentazione del libro |
PUT |
/v1/bibliographic-records/{id}/incipit |
Bearer (Admin/Editor) | Imposta la presentazione (testo manuale o generato da AI) |
POST |
/v1/records/isbn-lookup |
Bearer | Ricerca metadati ISBN |
GET/POST |
/v1/books/ |
Bearer | Libri posseduti (lista con limit/offset) |
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/map/bookcases/{id} |
Bearer | Dati mappa visiva della libreria |
GET |
/v1/export/ |
Bearer (Admin) | Esporta biblioteca in CSV o JSON |
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://... |
AUTH_SERVICE_URL |
✅ | — | URL interno di auth-service |
JWT_SECRET_KEY |
✅ | — | Deve corrispondere a auth + gateway |
JWT_ALGORITHM |
— | HS256 |
Algoritmo di firma |
GOOGLE_BOOKS_API_KEY |
— | — | Ricerca ISBN di fallback (chiave gratuita, 100 req/giorno) |
OPEN_LIBRARY_URL |
— | https://openlibrary.org |
URL base Open Library |
GOOGLE_BOOKS_URL |
— | https://www.googleapis.com |
URL base Google Books |
ISBN_CACHE_TTL_DAYS |
— | 30 |
Giorni di cache dei metadati ISBN in locale |
DEBUG |
— | false |
Logging delle query SQL |
Flusso di ricerca ISBN¶
Richiesta /v1/records/isbn-lookup?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 non trovato"
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 AUTH_SERVICE_URL
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)owned_books— Copie fisiche che collegano un record a uno scaffale + stato di lettura + posizioneisbn_cache— Risultati di ricerca ISBN in cache (con TTL)audit_log— Storico dei movimenti dei libri e dei cambi di stato
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
- Aggregazione delle risposte (pattern BFF)
Tutti gli endpoint sono montati sotto /v1 e replicati dai servizi interni.
Tabella di routing¶
| Percorso Gateway | Inoltrato a |
|---|---|
/v1/auth/* |
auth-service:8001/v1/auth/* |
/v1/families/* |
auth-service:8001/v1/families/* |
/v1/users/* |
auth-service:8001/v1/users/* |
/v1/rooms/* |
catalog-service:8002/v1/rooms/* |
/v1/bookcases/* |
catalog-service:8002/v1/bookcases/* |
/v1/sections/* |
catalog-service:8002/v1/sections/* |
/v1/shelves/* |
catalog-service:8002/v1/shelves/* |
/v1/records/* |
catalog-service:8002/v1/records/* |
/v1/books/* |
catalog-service:8002/v1/books/* |
/v1/map/* |
catalog-service:8002/v1/map/* |
/v1/export/* |
catalog-service:8002/v1/export/* |
/health |
locale |
Variabili d’ambiente¶
| Variabile | Obbligatoria | Default | Descrizione |
|---|---|---|---|
JWT_SECRET_KEY |
✅ | — | Deve corrispondere a auth + catalog |
JWT_ALGORITHM |
— | HS256 |
Algoritmo di firma |
AUTH_SERVICE_URL |
✅ | — | URL interno di auth-service |
CATALOG_SERVICE_URL |
✅ | — | URL interno di catalog-service |
AI_SERVICE_URL |
— | — | URL interno di ai-service (ometti se non distribuito) |
CORS_ORIGINS |
✅ | — | Array JSON delle origini consentite, es. ["https://jinbocho-fe.onrender.com"] |
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 il docker-compose usa ["*"] — 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
ai-service (porta 8003) — Opzionale¶
Repository: jinbocho-ai-v1
Responsabilità¶
- Presentazione del libro (incipit) — genera una breve presentazione senza spoiler di un libro a partire da titolo, autore e genere, così i lettori possono decidere cosa leggere dopo.
- Suggerimenti di tag automatici (scaffold)
- Suggerimenti di deduplicazione (scaffold)
- Raccomandazioni di lettura (futuro)
LLM collegabile — disabilitato per default¶
Il layer AI è opzionale e disabilitato per default. Con LLM_ENABLED=false (il default) il servizio è comunque attivo e ogni endpoint AI restituisce un risultato vuoto ({"text": null}) — non genera mai errori e non richiede nessuna chiave API. La funzione di presentazione del libro degrada in modo graceful: il catalog serve comunque la descrizione editoriale gratuita, e solo il pulsante “Genera con AI” è inattivo.
Il client è compatibile con OpenAI, quindi puoi puntarlo a qualsiasi provider tramite LLM_BASE_URL / LLM_MODEL / LLM_API_KEY:
| Provider | LLM_BASE_URL |
Note |
|---|---|---|
| Groq | https://api.groq.com/openai/v1 |
Livello gratuito — es. llama-3.3-70b-versatile |
| OpenAI | https://api.openai.com/v1 |
Pay-as-you-go — es. gpt-4o-mini |
| Google Gemini | https://generativelanguage.googleapis.com/v1beta/openai |
Endpoint compatibile OpenAI |
| Ollama (locale) | http://localhost:11434/v1 |
Self-hosted, nessuna chiave |
Endpoint principali¶
| Metodo | Percorso | Auth | Descrizione |
|---|---|---|---|
POST |
/v1/suggestions/incipit |
(interno) | Genera una presentazione; restituisce {"text": null} quando l’LLM è disabilitato |
POST |
/v1/suggestions/tags |
(interno) | Suggerimenti di tag (scaffold) |
GET |
/health |
— | Health check |
Il gateway fa il proxy di /v1/ai/{path} → ai-service /v1/suggestions/{path}, quindi il frontend chiama POST /v1/ai/incipit.
Dove viene salvata la presentazione
Le presentazioni generate o modificate manualmente vengono salvate dal catalog-service sul
record bibliografico (incipit, incipit_source, incipit_generated_at) tramite
PUT /v1/bibliographic-records/{id}/incipit. Per questa funzione l’ai-service è stateless —
il catalog non lo chiama mai nel percorso di scrittura (i servizi rimangono disaccoppiati).
Quando distribuirlo¶
Il servizio AI è opzionale. Puoi saltarlo completamente: le presentazioni dei libri funzionano ancora dalla descrizione editoriale gratuita del catalog. Distribuiscilo solo quando vuoi presentazioni generate dall’AI — e anche in quel caso non costa nulla se lo punti a un livello gratuito (Groq) o a un Ollama locale.
Variabili d’ambiente¶
| Variabile | Obbligatoria | Default | Descrizione |
|---|---|---|---|
DATABASE_URL |
✅ | — | postgresql+asyncpg://... punta a ai_db |
CATALOG_SERVICE_URL |
✅ | — | URL interno di catalog-service |
LLM_ENABLED |
— | false |
Interruttore principale. false → nessuna chiamata AI, nessuna chiave necessaria |
LLM_BASE_URL |
— | https://api.openai.com/v1 |
Endpoint compatibile OpenAI |
LLM_MODEL |
— | gpt-4o-mini |
Nome del modello |
LLM_API_KEY |
— | — | Chiave API del provider (richiesta solo quando LLM_ENABLED=true) |
DEBUG |
— | false |
Logging delle query SQL |
Avvio in locale (senza Docker)¶
cd jinbocho-ai-v1
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # lascia LLM_ENABLED=false, o configura Groq / OpenAI / Ollama
uvicorn app.main:app --reload --host 0.0.0.0 --port 8003
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