#01 — rgz-api
PLANIFIÉ
Priorité: 🔴 CRITIQUE · Type: TYPE A · Conteneur: rgz-api · Code: app/main.py
Dépendances: #04 rgz-db, #05 rgz-redis
Description
rgz-api est le hub central de toute la plateforme RGZ. C'est le point de convergence de tous les échanges entre services : le portail captif, le dashboard admin, FreeRADIUS, les tâches Celery, et les outils terrain y font tous appel. Aucun service ne communique directement avec la base de données sans passer par l'API.
L'API est construite avec FastAPI (Python 3.11), ce qui offre une validation automatique des entrées/sorties via Pydantic, une documentation OpenAPI auto-générée accessible sur /docs, et des performances asynchrones natives. Le schéma de données suit strictement les contrats définis dans API_Contracts_RGZ.md : snake_case partout, UUIDs v4, dates ISO8601 UTC, préfixe /api/v1/.
La logique métier est découplée dans app/services/ (billing, fraud, onboarding, etc.) tandis que les endpoints REST sont dans app/api/v1/endpoints/. Cette séparation permet aux tests unitaires d'exercer les services indépendamment du framework HTTP. SQLAlchemy gère l'ORM avec PostgreSQL, et le client Redis est injecté via la dépendance FastAPI get_redis() définie dans app/deps.py.
Le même Dockerfile (docker/api/Dockerfile) est partagé avec rgz-beat (outil #10). L'image embarque Tesseract OCR (pour l'outil #11 inscription-ocr), WeasyPrint (pour les factures PDF #24), et le client SSH (pour les opérations CPE #33). Le conteneur écoute sur le port 8000 en interne ; Traefik gère le TLS et l'exposition publique sur api-rgz.duckdns.org.
Architecture Interne
Requête entrante
│
▼
FastAPI app (app/main.py)
│
├── CORS middleware (whitelist explicite — SEC-11, LL#2)
├── Audit middleware (app/middleware/audit.py — outil #48)
├── Rate limiting (via Redis — SEC-14)
│
▼
Router /api/v1/ (app/api/v1/router.py)
│
├── /auth → endpoints/auth.py (#12 OTP, auth flow)
├── /portal → endpoints/portal.py (#11 OCR, #13 devices, #14 forfaits)
├── /payments → endpoints/payments.py (#15 KKiaPay, #22 refunds)
├── /billing → endpoints/billing.py (#19 facturation, #20 vouchers)
├── /reports → endpoints/reports.py (#24 PDF, #47 ARCEP)
├── /resellers → endpoints/resellers.py (#56 onboarding)
├── /network → endpoints/network.py (#31 VLAN, #29 fair-use)
├── /compliance → endpoints/compliance.py (#46 APDP, #48 audit)
├── /rma → endpoints/rma.py (#57 RMA, #58 incidents)
├── /radius → endpoints/radius.py (REST module FreeRADIUS)
└── /health → { status: "ok" }
│
▼
Services (app/services/) ←→ DB (SQLAlchemy + PostgreSQL)
←→ Cache (Redis 7)Configuration
Variables d'environnement
| Variable | Exemple | Description |
|---|---|---|
DATABASE_URL | postgresql+asyncpg://rgz:pass@rgz-db:5432/rgz | URL connexion PostgreSQL |
REDIS_URL | redis://:pass@rgz-redis:6379/0 | URL connexion Redis |
SECRET_KEY | changeme-64-chars-random | Clé HMAC JWT / OTP |
CORS_ORIGINS | https://admin-rgz.duckdns.org,https://access-rgz.duckdns.org | Whitelist CORS (jamais *) |
LOG_LEVEL | INFO | Niveau de logs (DEBUG/INFO/WARNING/ERROR) |
KKIAPAY_SECRET | ... | Clé secrète KKiaPay pour vérification webhook |
LETEXTO_API_KEY | ... | Clé API Letexto SMS |
RADIUS_SECRET | ... | Secret partagé FreeRADIUS ↔ API |
ENVIRONMENT | production | Contrôle comportements dev/prod |
Fichiers de configuration
app/
├── main.py # Point d'entrée FastAPI, lifespan, CORS, middleware
├── config.py # Settings Pydantic BaseSettings (lecture .env)
├── database.py # Engine SQLAlchemy async + session factory
├── deps.py # Dépendances FastAPI: get_db(), get_redis(), get_current_user()
└── api/v1/
└── router.py # include_router() de tous les endpointsEndpoints API
Structure de réponse standardisée
// Liste
{ "items": [...], "total": 42, "page": 1, "pages": 5 }
// Erreur
{ "error": { "code": "ERR_XXXX", "message": "...", "details": {} } }Endpoints principaux
| Méthode | Route | Code HTTP | Description |
|---|---|---|---|
GET | /api/v1/health | 200 | Healthcheck applicatif |
POST | /api/v1/auth/otp/request | 201 | Demande OTP par MSISDN |
POST | /api/v1/auth/otp/verify | 200 | Vérifie OTP → session token |
POST | /api/v1/portal/register | 201 | Inscription abonné (avec OCR) |
GET | /api/v1/portal/forfaits | 200 | Liste forfaits actifs |
POST | /api/v1/payments/initiate | 201 | Initier paiement KKiaPay |
POST | /api/v1/payments/webhook | 200 | Webhook confirmation KKiaPay |
GET | /api/v1/resellers/{id}/stats | 200 | Stats revendeur (dashboard #51) |
POST | /api/v1/radius/authorize | 200 | Autorisation RADIUS (FreeRADIUS REST) |
POST | /api/v1/radius/accounting | 200 | Accounting RADIUS |
Exemple curl — healthcheck
curl -s https://api-rgz.duckdns.org/api/v1/health | jq .
# { "status": "ok", "version": "1.0.0", "db": "connected", "redis": "connected" }Exemple curl — demande OTP
curl -s -X POST https://api-rgz.duckdns.org/api/v1/auth/otp/request \
-H "Content-Type: application/json" \
-d '{"msisdn": "+22997123456", "nas_id": "access_kossou"}' | jq .
# { "otp_token": "...", "expires_in": 300 }Redis Keys
| Clé | Type | TTL | Usage |
|---|---|---|---|
rgz:session:{subscriber_id} | String | session_duration | Token de session actif |
rgz:otp:{phone} | String | 300s | Code OTP en cours |
rgz:rate:{phone} | String | 60s | Compteur rate limiting OTP |
rgz:devices:{subscriber_id} | Set | — | MACs connues de l'abonné |
rgz:voucher:used:{code} | String | 86400s | Anti-replay vouchers |
Healthcheck
# Healthcheck Docker (dans docker-compose.core.yml)
# LL#43 : utiliser 127.0.0.1 JAMAIS localhost (IPv6 ::1 dans Docker)
curl --fail http://127.0.0.1:8000/api/v1/health
# Depuis l'hôte
curl https://api-rgz.duckdns.org/api/v1/health
# Depuis un autre conteneur sur rgz-net
docker exec rgz-db curl -s http://rgz-api:8000/api/v1/healthSécurité
| Règle | Implémentation |
|---|---|
| SEC-01 IDOR | if current_user.reseller_id != resource.owner_id: raise HTTPException(403) dans chaque endpoint |
| SEC-02 Race condition | Redis WATCH/MULTI/EXEC pour la consommation de vouchers |
| SEC-03 Webhook | Vérification header x-kkiapay-secret + idempotency key en DB |
| SEC-04 Montants | Entiers FCFA uniquement — jamais float pour les paiements |
| SEC-06 OTP | Lié au subscriber_id UUID, comparaison via hmac.compare_digest() |
| SEC-10 JWT | RS256, expiration 15min, refresh 7j, révocation via jti blacklist Redis |
| SEC-11 CORS | Whitelist explicite dans CORS_ORIGINS, SameSite=Strict cookies |
| LL#2 | CORS entre services configuré, jamais wildcard en production |
# app/main.py — CORS correct
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=settings.CORS_ORIGINS, # liste explicite, jamais ["*"]
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)Commandes Utiles
# Démarrage avec rebuild
docker compose -f /home/claude-dev/RGZ/docker-compose.core.yml up -d --build rgz-api
# Logs temps réel
docker logs rgz-api -f --tail=100
# Status healthcheck
docker inspect rgz-api --format='{}'
# Restart sans rebuild
docker restart rgz-api
# Accéder au shell
docker exec -it rgz-api bash
# Tester la connexion DB depuis l'API
docker exec rgz-api python -c "from app.database import engine; print('DB OK')"
# Tester Redis depuis l'API
docker exec rgz-api python -c "import redis; r=redis.from_url('redis://:pass@rgz-redis:6379/0'); print(r.ping())"
# Voir la documentation OpenAPI
curl -s https://api-rgz.duckdns.org/docs
# Ou en JSON : curl -s https://api-rgz.duckdns.org/openapi.json | jq .Implémentation TODO
- [x] Dockerfile créé (
docker/api/Dockerfile) - [x] Service défini dans
docker-compose.core.yml - [x] Variables d'env dans
.env.example - [ ]
app/main.py— FastAPI app avec lifespan, CORS, middleware - [ ]
app/config.py— Pydantic BaseSettings - [ ]
app/database.py— SQLAlchemy async engine - [ ]
app/deps.py— Dépendances FastAPI - [ ]
app/models/— Modèles SQLAlchemy (subscriber, reseller, billing, monitoring) - [ ]
app/schemas/— Schémas Pydantic input/output - [ ]
app/api/v1/router.py— Aggregation des routers - [ ]
app/api/v1/endpoints/— Tous les endpoints (12 fichiers) - [ ]
app/services/— Business logic découplée - [ ]
app/middleware/audit.py— Audit trail (#48) - [ ] Tests unitaires
app/tests/ - [ ] Tests d'intégration (prepare-tests Phase 2)
Dernière mise à jour: 2026-02-21