# Fichier: python_cheats/cheatsheets/kong_gateway.txt
# Cheatsheet Kong Gateway - Guide Ultra-Détaillé pour Grands Débutants


[OK] CONCEPTS FONDAMENTAUX (EXPLICATIONS TRÈS DÉTAILLÉES)

# === QU'EST-CE QUE KONG GATEWAY ? ===

# Imagine que tu as créé plusieurs APIs (microservices) :
# - API utilisateurs : http://localhost:3001/users
# - API produits : http://localhost:3002/products
# - API paiements : http://localhost:3003/payments

# Problèmes sans Kong:
# 1. Chaque API doit gérer son authentification
# 2. Pas de limite de taux (rate limiting) pour éviter les abus
# 3. Logs dispersés sur plusieurs services
# 4. Pas de cache centralisé
# 5. Pas de transformation des requêtes
# 6. Difficile de monitorer toutes les APIs en un seul endroit

# KONG GATEWAY = Porte d'entrée unique pour toutes tes APIs!
# = API Gateway qui se place DEVANT tes APIs
# = Centralise l'authentification, le rate limiting, les logs, etc.

# Analogie:
# Sans Kong = Plusieurs portes d'entrée (une par API) avec chacune son propre garde
# Avec Kong = UNE porte principale avec UN garde qui redirige vers les bonnes APIs

# === VOCABULAIRE KONG (TRÈS IMPORTANT!) ===

# SERVICE (Backend API)
# = Une API backend que tu veux exposer via Kong
# = Exemple: Service "users-api" pointant vers http://localhost:3001
# = Kong redirige les requêtes vers ce service

# ROUTE (Chemin d'accès)
# = Un chemin URL pour accéder à un service via Kong
# = Exemple: /api/users redirige vers le service "users-api"
# = Les clients appellent Kong, pas directement ton API

# PLUGIN (Fonctionnalité)
# = Un module qui ajoute une fonctionnalité à Kong
# = Exemples:
#   - key-auth: Authentification par clé API
#   - rate-limiting: Limiter le nombre de requêtes
#   - cors: Activer CORS pour le frontend
#   - jwt: Authentification JWT
#   - request-transformer: Modifier les requêtes avant envoi
#   - prometheus: Métriques pour monitoring

# CONSUMER (Client/Utilisateur)
# = Un utilisateur ou application qui consomme tes APIs via Kong
# = Tu peux associer des credentials (clés API, JWT) à un consumer
# = Permet de tracer qui fait quoi

# UPSTREAM (Load Balancing)
# = Un groupe de serveurs backend pour un service
# = Kong peut faire du load balancing entre plusieurs instances
# = Exemple: 3 instances de ton API users pour répartir la charge

# ADMIN API
# = API REST de Kong pour la configuration
# = Port par défaut: 8001
# = Tu l'utilises pour créer services, routes, plugins, etc.

# PROXY API
# = L'API publique par laquelle les clients accèdent
# = Port par défaut: 8000 (HTTP) et 8443 (HTTPS)
# = C'est ce que tes clients/frontend appellent


# === COMMENT ÇA MARCHE? (FLUX COMPLET) ===

# 1. Client envoie une requête:
#    GET https://api.monapp.com/users/123

# 2. Kong Gateway reçoit la requête sur le port 8000

# 3. Kong vérifie la ROUTE:
#    - Trouve la route /users
#    - Associe au service "users-api"

# 4. Kong applique les PLUGINS dans l'ordre:
#    a. Authentification (key-auth) -> vérifie la clé API
#    b. Rate limiting -> vérifie si le client n'a pas dépassé la limite
#    c. Request transformer -> modifie les headers si besoin
#    d. Cache -> vérifie si la réponse est en cache

# 5. Si tout est OK, Kong transmet la requête au SERVICE:
#    - Envoie GET http://localhost:3001/users/123

# 6. Le service backend répond:
#    - Status 200, JSON: {"id": 123, "name": "John"}

# 7. Kong applique les plugins de réponse:
#    - Cache la réponse
#    - Ajoute des headers CORS
#    - Log la requête

# 8. Kong renvoie la réponse au client

# === POURQUOI UTILISER KONG? ===

# Avantages:
# 1. CENTRALISATION: Un seul point d'entrée pour toutes les APIs
# 2. SÉCURITÉ: Authentification, rate limiting, IP whitelist
# 3. MONITORING: Logs centralisés, métriques Prometheus
# 4. TRANSFORMATION: Modifier requêtes/réponses à la volée
# 5. CACHE: Accélérer les réponses
# 6. LOAD BALANCING: Répartir la charge sur plusieurs serveurs
# 7. PLUGINS: Extensible avec des fonctionnalités custom


[OK] INSTALLATION SUPER DÉTAILLÉE

# === MÉTHODE 1: DOCKER (RECOMMANDÉ POUR DÉBUTANTS) ===

# Pourquoi Docker?
# - Installation la plus simple
# - Pas de conflits avec ton système
# - Facile à supprimer
# - Identique sur tous les OS (Windows, macOS, Linux)

# === ÉTAPE 1: INSTALLER DOCKER ===

# Télécharge Docker Desktop selon ton OS:
# Windows/macOS: https://www.docker.com/products/docker-desktop
# Linux: sudo apt install docker.io docker-compose

# Vérifie l'installation:
docker --version
# Doit afficher: Docker version 24.x.x ou plus

docker-compose --version
# Doit afficher: docker-compose version 2.x.x ou plus


# === ÉTAPE 2: CRÉER LA STRUCTURE DU PROJET ===

# Créer un dossier pour Kong:
mkdir kong-gateway && cd kong-gateway

# Structure finale:
# kong-gateway/
# ├── docker-compose.yml
# ├── kong.yml (configuration déclarative)
# └── .env (variables d'environnement)


# === ÉTAPE 3: FICHIER docker-compose.yml ===

# Créer le fichier docker-compose.yml:
cat > docker-compose.yml << 'EOF'
version: '3.9'

services:
  # Base de données PostgreSQL pour Kong
  kong-database:
    image: postgres:15
    container_name: kong-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: kong
      POSTGRES_DB: kong
      POSTGRES_PASSWORD: kongpass
    volumes:
      - kong_data:/var/lib/postgresql/data
    networks:
      - kong-net
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "kong"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Migration de la base de données
  kong-migration:
    image: kong/kong-gateway:3.5
    container_name: kong-migration
    command: kong migrations bootstrap
    environment:
      KONG_DATABASE: postgres
      KONG_PG_HOST: kong-database
      KONG_PG_USER: kong
      KONG_PG_PASSWORD: kongpass
      KONG_PG_DATABASE: kong
    depends_on:
      kong-database:
        condition: service_healthy
    networks:
      - kong-net
    restart: on-failure

  # Kong Gateway
  kong:
    image: kong/kong-gateway:3.5
    container_name: kong-gateway
    restart: unless-stopped
    environment:
      KONG_DATABASE: postgres
      KONG_PG_HOST: kong-database
      KONG_PG_USER: kong
      KONG_PG_PASSWORD: kongpass
      KONG_PG_DATABASE: kong
      KONG_PROXY_ACCESS_LOG: /dev/stdout
      KONG_ADMIN_ACCESS_LOG: /dev/stdout
      KONG_PROXY_ERROR_LOG: /dev/stderr
      KONG_ADMIN_ERROR_LOG: /dev/stderr
      KONG_ADMIN_LISTEN: 0.0.0.0:8001
      KONG_ADMIN_GUI_URL: http://localhost:8002
    depends_on:
      kong-database:
        condition: service_healthy
      kong-migration:
        condition: service_completed_successfully
    ports:
      - "8000:8000"  # Proxy HTTP
      - "8443:8443"  # Proxy HTTPS
      - "8001:8001"  # Admin API
      - "8002:8002"  # Kong Manager (GUI)
    networks:
      - kong-net
    healthcheck:
      test: ["CMD", "kong", "health"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Konga - Interface graphique pour gérer Kong
  konga:
    image: pantsel/konga:latest
    container_name: konga
    restart: unless-stopped
    environment:
      NODE_ENV: production
      DB_ADAPTER: postgres
      DB_HOST: kong-database
      DB_USER: kong
      DB_PASSWORD: kongpass
      DB_DATABASE: konga
    depends_on:
      kong-database:
        condition: service_healthy
    ports:
      - "1337:1337"
    networks:
      - kong-net

networks:
  kong-net:
    driver: bridge

volumes:
  kong_data:
EOF

# Explications ligne par ligne:

# SERVICES:

# 1. kong-database (PostgreSQL):
# - Image officielle PostgreSQL version 15
# - Utilisateur: kong, mot de passe: kongpass
# - Volume pour persistance des données
# - Healthcheck pour vérifier que la DB est prête

# 2. kong-migration:
# - Lance les migrations de la base de données
# - S'exécute UNE FOIS au démarrage
# - Crée les tables nécessaires pour Kong
# - depends_on: attend que la DB soit healthy

# 3. kong (Gateway principal):
# - Image officielle Kong Gateway 3.5
# - Ports exposés:
#   * 8000: Proxy HTTP (requêtes clients)
#   * 8443: Proxy HTTPS
#   * 8001: Admin API (configuration)
#   * 8002: Kong Manager GUI
# - Logs envoyés vers stdout/stderr pour Docker
# - Healthcheck pour vérifier que Kong fonctionne

# 4. konga (Interface graphique):
# - Alternative à Kong Manager
# - Plus facile pour les débutants
# - Port 1337
# - Utilise la même DB PostgreSQL

# NETWORKS:
# - Réseau Docker interne (kong-net)
# - Permet aux containers de communiquer entre eux

# VOLUMES:
# - kong_data: Persistance de la base de données
# - Les données survivent même si tu redémarres Docker


# === ÉTAPE 4: LANCER KONG ===

# Démarrer tous les services:
docker-compose up -d

# Explications:
# docker-compose up = démarre les services
# -d = détaché (background)

# Vérifier que tout fonctionne:
docker-compose ps

# Doit afficher:
# NAME              STATUS         PORTS
# kong-gateway      Up (healthy)   0.0.0.0:8000->8000/tcp, ...
# kong-postgres     Up (healthy)   5432/tcp
# konga             Up             0.0.0.0:1337->1337/tcp

# Voir les logs:
docker-compose logs -f kong

# Arrêter: Ctrl+C

# Pour arrêter complètement:
docker-compose down

# Pour arrêter ET supprimer les données:
docker-compose down -v


# === ÉTAPE 5: VÉRIFIER L'INSTALLATION ===

# Tester l'Admin API:
curl -i http://localhost:8001/

# Doit retourner du JSON avec la config de Kong

# Tester le Proxy:
curl -i http://localhost:8000/

# Doit retourner 404 (normal, pas de routes configurées encore)

# Accéder à Kong Manager (GUI):
# Ouvre un navigateur: http://localhost:8002

# Accéder à Konga (GUI alternative):
# Ouvre un navigateur: http://localhost:1337
# Première connexion: créer un compte admin


# === MÉTHODE 2: INSTALLATION NATIVE (LINUX) ===

# Sur Ubuntu/Debian:

# 1. Télécharger le package:
curl -Lo kong-3.5.0.deb "https://download.konghq.com/gateway-3.x-ubuntu-$(lsb_release -sc)/pool/all/k/kong/kong_3.5.0_amd64.deb"

# 2. Installer:
sudo apt install -y ./kong-3.5.0.deb

# 3. Installer PostgreSQL:
sudo apt install -y postgresql postgresql-contrib

# 4. Créer la base de données:
sudo -u postgres psql -c "CREATE USER kong WITH PASSWORD 'kongpass';"
sudo -u postgres psql -c "CREATE DATABASE kong OWNER kong;"

# 5. Configurer Kong:
sudo cp /etc/kong/kong.conf.default /etc/kong/kong.conf

# Éditer /etc/kong/kong.conf:
# Chercher et modifier ces lignes:
# database = postgres
# pg_host = 127.0.0.1
# pg_port = 5432
# pg_user = kong
# pg_password = kongpass
# pg_database = kong

# 6. Lancer les migrations:
sudo kong migrations bootstrap -c /etc/kong/kong.conf

# 7. Démarrer Kong:
sudo kong start -c /etc/kong/kong.conf

# 8. Vérifier:
curl -i http://localhost:8001/


# === MÉTHODE 3: DOCKER SANS DOCKER-COMPOSE ===

# Si tu n'as pas docker-compose:

# 1. Réseau Docker:
docker network create kong-net

# 2. Base de données:
docker run -d --name kong-database \
  --network=kong-net \
  -e POSTGRES_USER=kong \
  -e POSTGRES_DB=kong \
  -e POSTGRES_PASSWORD=kongpass \
  postgres:15

# 3. Attendre que la DB soit prête (30 secondes)
sleep 30

# 4. Migrations:
docker run --rm --network=kong-net \
  -e KONG_DATABASE=postgres \
  -e KONG_PG_HOST=kong-database \
  -e KONG_PG_USER=kong \
  -e KONG_PG_PASSWORD=kongpass \
  kong/kong-gateway:3.5 kong migrations bootstrap

# 5. Lancer Kong:
docker run -d --name kong-gateway \
  --network=kong-net \
  -e KONG_DATABASE=postgres \
  -e KONG_PG_HOST=kong-database \
  -e KONG_PG_USER=kong \
  -e KONG_PG_PASSWORD=kongpass \
  -e KONG_ADMIN_LISTEN=0.0.0.0:8001 \
  -p 8000:8000 \
  -p 8443:8443 \
  -p 8001:8001 \
  kong/kong-gateway:3.5

# 6. Vérifier:
docker ps
curl -i http://localhost:8001/


[OK] STRUCTURE KONG (EXPLICATIONS TRÈS DÉTAILLÉES)

# === HIÉRARCHIE DES OBJETS KONG ===

# Kong organise la configuration en plusieurs objets liés:

# 1. SERVICE (Backend)
#    └── 2. ROUTE (Chemin d'accès)
#         └── 3. PLUGIN (Fonctionnalités)
#    └── 4. UPSTREAM (Load balancing)
#         └── 5. TARGET (Serveurs backend)

# 6. CONSUMER (Client/Utilisateur)
#    └── 7. CREDENTIAL (Clé API, JWT, etc)

# === EXEMPLE CONCRET ===

# Tu as une API backend "users" qui tourne sur http://localhost:3001

# Étape 1: Créer un SERVICE
# = Dire à Kong: "J'ai une API à http://localhost:3001"

# Étape 2: Créer une ROUTE
# = Dire à Kong: "Quand quelqu'un appelle /api/users, envoie vers le service users"

# Étape 3: Ajouter des PLUGINS
# = Dire à Kong: "Sur cette route, active l'authentification et le rate limiting"

# Étape 4: Créer un CONSUMER
# = Dire à Kong: "J'ai un client nommé 'mobile-app'"

# Étape 5: Créer une CREDENTIAL
# = Dire à Kong: "Le client 'mobile-app' a la clé API abc123"

# Maintenant:
# - Le client envoie: GET http://localhost:8000/api/users/123 avec header "apikey: abc123"
# - Kong vérifie la clé
# - Kong limite le nombre de requêtes
# - Kong transmet vers http://localhost:3001/users/123
# - Le backend répond
# - Kong retourne la réponse au client


[OK] CONFIGURATION VIA ADMIN API (EXEMPLES PRATIQUES)

# === CRÉER UN SERVICE ===

# Qu'est-ce qu'un service?
# = Une API backend que tu veux exposer via Kong

# Exemple: API utilisateurs sur http://localhost:3001

# Via curl:
curl -i -X POST http://localhost:8001/services \
  --data name=users-service \
  --data url=http://host.docker.internal:3001

# Explications des paramètres:
# name = Nom du service (identifiant unique)
# url = URL complète du backend
#   - host.docker.internal = localhost depuis un container Docker
#   - Ou utilise l'IP réelle si l'API est sur un autre serveur

# Réponse (JSON):
{
  "id": "a1b2c3d4-...",
  "name": "users-service",
  "protocol": "http",
  "host": "host.docker.internal",
  "port": 3001,
  "path": null,
  "created_at": 1234567890
}

# Via interface Konga:
# 1. Ouvre http://localhost:1337
# 2. Onglet "Services"
# 3. Bouton "Add New Service"
# 4. Remplis:
#    - Name: users-service
#    - URL: http://host.docker.internal:3001
# 5. Clique "Submit"


# === CRÉER UNE ROUTE ===

# Qu'est-ce qu'une route?
# = Un chemin URL pour accéder à un service via Kong

# Exemple: Route /api/users vers le service users-service

# Via curl:
curl -i -X POST http://localhost:8001/services/users-service/routes \
  --data name=users-route \
  --data paths[]=/api/users

# Explications:
# services/users-service/routes = Créer une route pour le service users-service
# name = Nom de la route
# paths[] = Chemins URL qui matchent cette route
#   - /api/users = match /api/users, /api/users/123, etc.
#   - Plusieurs chemins possibles: paths[]=/api/users paths[]=/v1/users

# Réponse:
{
  "id": "e5f6g7h8-...",
  "name": "users-route",
  "paths": ["/api/users"],
  "service": {"id": "a1b2c3d4-..."},
  "created_at": 1234567890
}

# Via Konga:
# 1. Onglet "Services" -> clique sur "users-service"
# 2. Onglet "Routes" -> "Add Route"
# 3. Remplis:
#    - Name: users-route
#    - Paths: /api/users
# 4. Submit


# === TESTER LA CONFIGURATION ===

# Sans Kong (directement vers le backend):
curl http://localhost:3001/users

# Avec Kong (via le proxy):
curl http://localhost:8000/api/users

# Les deux doivent retourner la même réponse!
# Kong a redirigé /api/users vers http://localhost:3001/users


# === CRÉER UN SERVICE AVEC PLUSIEURS ROUTES ===

# Exemple: Service produits avec plusieurs chemins

# 1. Créer le service:
curl -i -X POST http://localhost:8001/services \
  --data name=products-service \
  --data url=http://host.docker.internal:3002

# 2. Créer plusieurs routes:

# Route GET /products (liste)
curl -i -X POST http://localhost:8001/services/products-service/routes \
  --data name=products-list \
  --data paths[]=/api/products \
  --data methods[]=GET

# Route POST /products (créer)
curl -i -X POST http://localhost:8001/services/products-service/routes \
  --data name=products-create \
  --data paths[]=/api/products \
  --data methods[]=POST

# Route GET /products/:id (détail)
curl -i -X POST http://localhost:8001/services/products-service/routes \
  --data name=products-detail \
  --data paths[]=/api/products/(\d+) \
  --data methods[]=GET

# Explications:
# methods[] = Filtrer par méthode HTTP (GET, POST, PUT, DELETE)
# (\d+) = Expression régulière pour matcher un ID numérique


# === VOIR TOUS LES SERVICES ===

curl http://localhost:8001/services

# Réponse (liste de tous les services):
{
  "data": [
    {"id": "...", "name": "users-service", ...},
    {"id": "...", "name": "products-service", ...}
  ],
  "next": null
}


# === VOIR TOUTES LES ROUTES ===

curl http://localhost:8001/routes

# Ou les routes d'un service spécifique:
curl http://localhost:8001/services/users-service/routes


# === METTRE À JOUR UN SERVICE ===

# Exemple: Changer l'URL du backend

curl -i -X PATCH http://localhost:8001/services/users-service \
  --data url=http://host.docker.internal:4001

# PATCH = modification partielle (seulement les champs fournis)
# PUT = remplacement complet


# === SUPPRIMER UN SERVICE ===

curl -i -X DELETE http://localhost:8001/services/users-service

# ATTENTION: Supprime aussi toutes les routes associées!


# === SUPPRIMER UNE ROUTE ===

curl -i -X DELETE http://localhost:8001/routes/users-route

# Ou par ID:
curl -i -X DELETE http://localhost:8001/routes/e5f6g7h8-...


[OK] PLUGINS ESSENTIELS (EXPLICATIONS DÉTAILLÉES)

# === QU'EST-CE QU'UN PLUGIN? ===

# Un plugin = Module qui ajoute une fonctionnalité à Kong
# Peut être activé:
# - Globalement (toutes les routes)
# - Sur un service spécifique
# - Sur une route spécifique
# - Sur un consumer spécifique

# === PLUGIN 1: KEY AUTHENTICATION (CLÉS API) ===

# Pourquoi?
# - Empêcher l'accès non autorisé à tes APIs
# - Chaque client a une clé unique
# - Facile à révoquer

# Étape 1: Activer le plugin sur une route

curl -i -X POST http://localhost:8001/routes/users-route/plugins \
  --data name=key-auth \
  --data config.key_names[]=apikey

# Explications:
# routes/users-route/plugins = Activer sur la route users-route
# name=key-auth = Plugin d'authentification par clé
# config.key_names[]=apikey = Nom du header qui contiendra la clé
#   - Le client devra envoyer: Header "apikey: <clé>"
#   - Autres noms possibles: X-API-KEY, Authorization, etc.

# Étape 2: Tester SANS clé (doit échouer)

curl http://localhost:8000/api/users

# Réponse:
{
  "message": "No API key found in request"
}

# Étape 3: Créer un consumer (client)

curl -i -X POST http://localhost:8001/consumers \
  --data username=mobile-app

# Explications:
# username = Identifiant du client (unique)
# Tu peux aussi utiliser custom_id au lieu de username

# Réponse:
{
  "id": "consumer-123",
  "username": "mobile-app",
  "created_at": 1234567890
}

# Étape 4: Créer une clé API pour ce consumer

curl -i -X POST http://localhost:8001/consumers/mobile-app/key-auth \
  --data key=my-secret-api-key-123

# Explications:
# consumers/mobile-app/key-auth = Créer une clé pour le consumer mobile-app
# key = La clé API (si omis, Kong génère une clé aléatoire)

# Réponse:
{
  "id": "key-456",
  "consumer": {"id": "consumer-123"},
  "key": "my-secret-api-key-123",
  "created_at": 1234567890
}

# Étape 5: Tester AVEC la clé (doit fonctionner)

curl -H "apikey: my-secret-api-key-123" http://localhost:8000/api/users

# Réponse: Les données de l'API!

# Kong a:
# 1. Vérifié que la clé existe
# 2. Identifié le consumer (mobile-app)
# 3. Transmis la requête au backend
# 4. Ajouté des headers pour que le backend sache qui appelle:
#    - X-Consumer-ID: consumer-123
#    - X-Consumer-Username: mobile-app


# === PLUGIN 2: RATE LIMITING (LIMITE DE TAUX) ===

# Pourquoi?
# - Empêcher les abus (trop de requêtes)
# - Protéger ton backend contre la surcharge
# - Limiter par consumer, IP, ou globalement

# Activer le plugin:

curl -i -X POST http://localhost:8001/routes/users-route/plugins \
  --data name=rate-limiting \
  --data config.minute=10 \
  --data config.hour=100 \
  --data config.policy=local

# Explications:
# config.minute=10 = Max 10 requêtes par minute
# config.hour=100 = Max 100 requêtes par heure
# config.policy=local = Stockage en mémoire (simple)
#   - Autres options: redis, cluster (pour plusieurs instances Kong)

# Tester:

# Requête 1-10: OK
for i in {1..10}; do
  curl -H "apikey: my-secret-api-key-123" http://localhost:8000/api/users
done

# Requête 11: Rate limit dépassé
curl -H "apikey: my-secret-api-key-123" http://localhost:8000/api/users

# Réponse:
{
  "message": "API rate limit exceeded"
}

# Headers de la réponse:
# X-RateLimit-Limit-Minute: 10
# X-RateLimit-Remaining-Minute: 0
# X-RateLimit-Reset: 1234567890


# === PLUGIN 3: CORS (CROSS-ORIGIN RESOURCE SHARING) ===

# Pourquoi?
# - Permettre aux frontends (React, Vue, Angular) d'appeler tes APIs
# - Contrôler quels domaines peuvent accéder

# Activer le plugin:

curl -i -X POST http://localhost:8001/routes/users-route/plugins \
  --data name=cors \
  --data config.origins=http://localhost:3000 \
  --data config.origins=https://monapp.com \
  --data config.methods=GET,POST,PUT,DELETE \
  --data config.headers=Accept,Content-Type,apikey \
  --data config.credentials=true

# Explications:
# config.origins = Domaines autorisés
#   - * = tous les domaines (dangereux en prod!)
# config.methods = Méthodes HTTP autorisées
# config.headers = Headers autorisés
# config.credentials=true = Autoriser les cookies/credentials

# Tester depuis un frontend:

// JavaScript (fetch)
fetch('http://localhost:8000/api/users', {
  method: 'GET',
  headers: {
    'apikey': 'my-secret-api-key-123'
  }
})
.then(res => res.json())
.then(data => console.log(data));

# Sans CORS plugin: Erreur CORS dans la console
# Avec CORS plugin: Fonctionne!


# === PLUGIN 4: REQUEST TRANSFORMER (TRANSFORMATION) ===

# Pourquoi?
# - Modifier les requêtes avant envoi au backend
# - Ajouter/supprimer headers
# - Modifier le body

# Exemple: Ajouter un header "X-Powered-By: Kong"

curl -i -X POST http://localhost:8001/routes/users-route/plugins \
  --data name=request-transformer \
  --data config.add.headers=X-Powered-By:Kong \
  --data config.add.headers=X-Request-ID:$(uuidgen)

# Explications:
# config.add.headers = Ajouter des headers
# $(uuidgen) = Générer un UUID unique pour tracer la requête

# Le backend recevra:
# GET /users/123
# X-Powered-By: Kong
# X-Request-ID: abc-def-123-456


# === PLUGIN 5: RESPONSE TRANSFORMER ===

# Modifier les réponses avant renvoi au client

curl -i -X POST http://localhost:8001/routes/users-route/plugins \
  --data name=response-transformer \
  --data config.add.headers=X-API-Version:1.0 \
  --data config.remove.headers=X-Internal-Secret

# Le client recevra:
# X-API-Version: 1.0
# (Sans X-Internal-Secret)


# === PLUGIN 6: JWT (JSON WEB TOKEN) ===

# Authentification via JWT au lieu de clés API

# Étape 1: Activer le plugin

curl -i -X POST http://localhost:8001/routes/users-route/plugins \
  --data name=jwt

# Étape 2: Créer un consumer

curl -i -X POST http://localhost:8001/consumers \
  --data username=jwt-user

# Étape 3: Créer un JWT credential

curl -i -X POST http://localhost:8001/consumers/jwt-user/jwt \
  --data key=my-jwt-key \
  --data secret=my-jwt-secret

# Réponse:
{
  "id": "jwt-123",
  "consumer": {"id": "consumer-456"},
  "key": "my-jwt-key",
  "secret": "my-jwt-secret",
  "algorithm": "HS256"
}

# Étape 4: Générer un JWT token

# En Python:
import jwt
import time

payload = {
    'iss': 'my-jwt-key',  # Issuer (key du credential)
    'exp': int(time.time()) + 3600  # Expire dans 1h
}
token = jwt.encode(payload, 'my-jwt-secret', algorithm='HS256')
print(token)

# Étape 5: Tester avec le token

curl -H "Authorization: Bearer <token>" http://localhost:8000/api/users


# === PLUGIN 7: IP RESTRICTION ===

# Limiter l'accès par adresse IP

# Whitelist (autoriser seulement ces IPs):
curl -i -X POST http://localhost:8001/routes/users-route/plugins \
  --data name=ip-restriction \
  --data config.allow=192.168.1.100 \
  --data config.allow=10.0.0.0/8

# Blacklist (bloquer ces IPs):
curl -i -X POST http://localhost:8001/routes/users-route/plugins \
  --data name=ip-restriction \
  --data config.deny=123.45.67.89


# === PLUGIN 8: REQUEST SIZE LIMITING ===

# Limiter la taille des requêtes (éviter les gros uploads)

curl -i -X POST http://localhost:8001/routes/users-route/plugins \
  --data name=request-size-limiting \
  --data config.allowed_payload_size=10

# Explications:
# config.allowed_payload_size=10 = Max 10 MB


# === PLUGIN 9: PROXY CACHE ===

# Mettre en cache les réponses pour accélérer

curl -i -X POST http://localhost:8001/routes/users-route/plugins \
  --data name=proxy-cache \
  --data config.strategy=memory \
  --data config.content_type="application/json" \
  --data config.cache_ttl=300

# Explications:
# config.strategy=memory = Stockage en mémoire
# config.cache_ttl=300 = Cache valide 300 secondes (5 min)

# Première requête: Miss (pas en cache)
# Requêtes suivantes: Hit (depuis le cache)

# Headers de la réponse:
# X-Cache-Status: Hit (ou Miss)


# === PLUGIN 10: PROMETHEUS (MÉTRIQUES) ===

# Exporter des métriques pour monitoring

curl -i -X POST http://localhost:8001/plugins \
  --data name=prometheus

# Activer globalement (tous les services/routes)

# Accéder aux métriques:
curl http://localhost:8001/metrics

# Réponse (format Prometheus):
# kong_http_requests_total{service="users-service",route="users-route"} 1234
# kong_latency_ms_sum{service="users-service"} 5678
# ...


# === VOIR TOUS LES PLUGINS ACTIFS ===

curl http://localhost:8001/plugins

# Ou pour une route spécifique:
curl http://localhost:8001/routes/users-route/plugins


# === DÉSACTIVER UN PLUGIN ===

curl -i -X DELETE http://localhost:8001/plugins/<plugin-id>


# === ORDRE D'EXÉCUTION DES PLUGINS ===

# Kong applique les plugins dans un ordre précis:

# PHASE 1: ACCÈS (avant transmission au backend)
# 1. ip-restriction
# 2. key-auth ou jwt
# 3. rate-limiting
# 4. request-size-limiting

# PHASE 2: TRANSFORMATION (avant transmission)
# 5. cors
# 6. request-transformer

# PHASE 3: BACKEND
# - Kong transmet la requête au backend

# PHASE 4: RÉPONSE (avant renvoi au client)
# 7. response-transformer
# 8. proxy-cache

# PHASE 5: LOGS
# 9. prometheus


[OK] LOAD BALANCING (RÉPARTITION DE CHARGE)

# === POURQUOI LE LOAD BALANCING? ===

# Imagine tu as 3 instances de ton API users:
# - http://server1.com:3001
# - http://server2.com:3001
# - http://server3.com:3001

# Problème: Comment répartir les requêtes entre les 3?
# Solution: Kong Upstream + Targets!

# === CRÉER UN UPSTREAM ===

# Upstream = Groupe de serveurs backend

curl -i -X POST http://localhost:8001/upstreams \
  --data name=users-upstream \
  --data algorithm=round-robin

# Explications:
# name = Nom de l'upstream
# algorithm = Algorithme de répartition:
#   - round-robin: À tour de rôle (défaut)
#   - least-connections: Serveur avec le moins de connexions
#   - consistent-hashing: Hash de l'IP client (sticky)

# Réponse:
{
  "id": "upstream-123",
  "name": "users-upstream",
  "algorithm": "round-robin",
  "created_at": 1234567890
}


# === AJOUTER DES TARGETS (SERVEURS) ===

# Target = Un serveur backend dans l'upstream

# Server 1:
curl -i -X POST http://localhost:8001/upstreams/users-upstream/targets \
  --data target=server1.com:3001 \
  --data weight=100

# Server 2:
curl -i -X POST http://localhost:8001/upstreams/users-upstream/targets \
  --data target=server2.com:3001 \
  --data weight=100

# Server 3:
curl -i -X POST http://localhost:8001/upstreams/users-upstream/targets \
  --data target=server3.com:3001 \
  --data weight=50

# Explications:
# target = Adresse du serveur (host:port)
# weight = Poids (importance)
#   - Server 1 et 2: weight=100
#   - Server 3: weight=50
#   - Ratio: 100:100:50 = 2:2:1
#   - Server 3 recevra moitié moins de requêtes


# === ASSOCIER L'UPSTREAM À UN SERVICE ===

# Méthode 1: Créer un nouveau service avec upstream

curl -i -X POST http://localhost:8001/services \
  --data name=users-lb-service \
  --data host=users-upstream

# Explications:
# host=users-upstream = Pointe vers l'upstream (pas une URL directe)

# Méthode 2: Modifier un service existant

curl -i -X PATCH http://localhost:8001/services/users-service \
  --data host=users-upstream


# === TESTER LE LOAD BALANCING ===

# Faire plusieurs requêtes:
for i in {1..6}; do
  curl -H "apikey: my-secret-api-key-123" http://localhost:8000/api/users
  sleep 1
done

# Résultat (round-robin):
# Requête 1 -> server1.com:3001
# Requête 2 -> server2.com:3001
# Requête 3 -> server3.com:3001
# Requête 4 -> server1.com:3001
# Requête 5 -> server2.com:3001
# Requête 6 -> server3.com:3001

# Avec weight 100:100:50:
# Requête 1 -> server1
# Requête 2 -> server2
# Requête 3 -> server1
# Requête 4 -> server2
# Requête 5 -> server3
# Requête 6 -> server1
# ...


# === HEALTH CHECKS (VÉRIFICATION DE SANTÉ) ===

# Kong peut vérifier automatiquement si les serveurs sont en ligne

curl -i -X POST http://localhost:8001/upstreams/users-upstream \
  --data healthchecks.active.healthy.interval=10 \
  --data healthchecks.active.unhealthy.interval=20 \
  --data healthchecks.active.http_path=/health

# Explications:
# healthchecks.active.healthy.interval=10
#   - Vérifier tous les 10 secondes si un serveur sain est toujours sain

# healthchecks.active.unhealthy.interval=20
#   - Vérifier tous les 20 secondes si un serveur down est revenu

# healthchecks.active.http_path=/health
#   - Endpoint à appeler pour le health check
#   - Le serveur backend doit répondre 200 OK sur /health

# Résultat:
# Si server2 tombe (500 ou timeout):
# - Kong marque server2 comme "unhealthy"
# - Les requêtes vont seulement vers server1 et server3
# - Quand server2 revient, Kong le réactive


# === VOIR L'ÉTAT DES TARGETS ===

curl http://localhost:8001/upstreams/users-upstream/health

# Réponse:
{
  "total": 3,
  "data": [
    {"target": "server1.com:3001", "health": "HEALTHY", "weight": 100},
    {"target": "server2.com:3001", "health": "UNHEALTHY", "weight": 100},
    {"target": "server3.com:3001", "health": "HEALTHY", "weight": 50}
  ]
}


# === SUPPRIMER UN TARGET ===

# Marquer comme inactif (soft delete):
curl -i -X DELETE http://localhost:8001/upstreams/users-upstream/targets/<target-id>

# Ou modifier le weight à 0:
curl -i -X POST http://localhost:8001/upstreams/users-upstream/targets \
  --data target=server2.com:3001 \
  --data weight=0


[OK] CONFIGURATION DÉCLARATIVE (FICHIER kong.yml)

# === QU'EST-CE QUE LA CONFIGURATION DÉCLARATIVE? ===

# Problème avec l'Admin API:
# - Commandes curl une par une
# - Pas de versioning
# - Difficile à reproduire

# Solution: Fichier kong.yml!
# = Toute la configuration dans UN fichier YAML
# = Versionnable avec git
# = Facile à déployer sur plusieurs environnements

# === EXEMPLE DE FICHIER kong.yml ===

cat > kong.yml << 'EOF'
_format_version: "3.0"

# === SERVICES ===
services:
  - name: users-service
    url: http://host.docker.internal:3001
    routes:
      - name: users-route
        paths:
          - /api/users
        methods:
          - GET
          - POST
        plugins:
          - name: key-auth
            config:
              key_names:
                - apikey
          - name: rate-limiting
            config:
              minute: 10
              hour: 100
              policy: local
          - name: cors
            config:
              origins:
                - http://localhost:3000
              methods:
                - GET
                - POST
                - PUT
                - DELETE
              credentials: true

  - name: products-service
    url: http://host.docker.internal:3002
    routes:
      - name: products-route
        paths:
          - /api/products
        plugins:
          - name: key-auth
            config:
              key_names:
                - apikey

# === UPSTREAMS ===
upstreams:
  - name: users-upstream
    algorithm: round-robin
    targets:
      - target: server1.com:3001
        weight: 100
      - target: server2.com:3001
        weight: 100
      - target: server3.com:3001
        weight: 50

# === CONSUMERS ===
consumers:
  - username: mobile-app
    keyauth_credentials:
      - key: mobile-secret-key-123
  - username: web-app
    keyauth_credentials:
      - key: web-secret-key-456

# === PLUGINS GLOBAUX ===
plugins:
  - name: prometheus
  - name: request-id
    config:
      header_name: X-Request-ID
EOF

# Explications:

# _format_version: "3.0" = Version du format Kong

# services: Liste des services
#   - name: Nom du service
#   - url: URL du backend
#   - routes: Routes associées
#     - paths: Chemins URL
#     - methods: Méthodes HTTP
#     - plugins: Plugins activés sur cette route

# upstreams: Load balancing
#   - targets: Serveurs backend avec leurs poids

# consumers: Clients de l'API
#   - keyauth_credentials: Clés API

# plugins: Plugins globaux (tous les services)


# === CHARGER LA CONFIGURATION ===

# Méthode 1: Via docker-compose

# Modifier docker-compose.yml:
# kong:
#   ...
#   volumes:
#     - ./kong.yml:/usr/local/kong/declarative/kong.yml:ro
#   environment:
#     ...
#     KONG_DATABASE: "off"  # Mode DB-less
#     KONG_DECLARATIVE_CONFIG: /usr/local/kong/declarative/kong.yml

# Redémarrer:
docker-compose restart kong

# Méthode 2: Via Admin API (mode DB)

curl -i -X POST http://localhost:8001/config \
  --form config=@kong.yml

# Méthode 3: Via CLI

kong config db_import kong.yml


# === AVANTAGES DE LA CONFIG DÉCLARATIVE ===

# 1. VERSIONING avec git:
git add kong.yml
git commit -m "Add rate limiting to users API"
git push

# 2. ENVIRONNEMENTS multiples:
# kong-dev.yml (développement)
# kong-staging.yml (staging)
# kong-prod.yml (production)

# 3. REPRODUCTIBLE:
# Facile de recréer la config sur un nouveau serveur

# 4. LISIBLE:
# Toute la config en un seul endroit

# 5. VALIDATION:
# Kong vérifie la syntaxe avant application


# === EXPORTER LA CONFIGURATION ACTUELLE ===

# Si tu as configuré via Admin API et veux exporter:

curl http://localhost:8001/config > kong-export.yml

# Ou avec deck (outil Kong):
deck dump -o kong.yml


[OK] MONITORING & LOGS (SURVEILLANCE)

# === LOGS KONG ===

# Voir les logs en temps réel:
docker-compose logs -f kong

# Logs d'accès:
# 192.168.1.100 - - [15/Jan/2024:19:10:45 +0000] "GET /api/users HTTP/1.1" 200 1234

# Logs d'erreur:
# [error] 12345#0: *67890 connect() failed (111: Connection refused)

# Explications:
# 192.168.1.100 = IP du client
# GET /api/users = Requête
# 200 = Status code
# 1234 = Taille de la réponse (bytes)


# === PLUGIN FILE-LOG ===

# Enregistrer les logs dans un fichier

curl -i -X POST http://localhost:8001/plugins \
  --data name=file-log \
  --data config.path=/tmp/kong-access.log

# Tous les logs seront écrits dans /tmp/kong-access.log

# Format JSON:
{
  "started_at": 1234567890,
  "client_ip": "192.168.1.100",
  "latencies": {
    "request": 123,
    "kong": 45,
    "proxy": 78
  },
  "request": {
    "method": "GET",
    "uri": "/api/users/123",
    "headers": {
      "apikey": "***"
    }
  },
  "response": {
    "status": 200,
    "size": 1234
  },
  "service": {"name": "users-service"},
  "route": {"name": "users-route"},
  "consumer": {"username": "mobile-app"}
}


# === PLUGIN HTTP-LOG ===

# Envoyer les logs vers un endpoint HTTP

curl -i -X POST http://localhost:8001/plugins \
  --data name=http-log \
  --data config.http_endpoint=http://logs.monapp.com/api/logs

# Kong enverra un POST avec le JSON des logs


# === PLUGIN SYSLOG ===

# Envoyer les logs vers un serveur syslog

curl -i -X POST http://localhost:8001/plugins \
  --data name=syslog \
  --data config.log_level=info \
  --data config.facility=user


# === MÉTRIQUES PROMETHEUS ===

# Activer le plugin Prometheus (déjà vu plus haut):

curl -i -X POST http://localhost:8001/plugins \
  --data name=prometheus

# Accéder aux métriques:
curl http://localhost:8001/metrics

# Exemples de métriques:

# Nombre total de requêtes:
kong_http_requests_total{service="users-service",route="users-route",code="200"} 12345

# Latence moyenne (ms):
kong_latency_ms_sum{service="users-service"} 5678
kong_latency_ms_count{service="users-service"} 100

# Bande passante (bytes):
kong_bandwidth_bytes{type="egress",service="users-service"} 1234567

# Connexions actives:
kong_nginx_connections_total{state="active"} 42


# === VISUALISER AVEC GRAFANA ===

# Docker Compose avec Prometheus + Grafana:

cat >> docker-compose.yml << 'EOF'
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    ports:
      - "9090:9090"
    networks:
      - kong-net

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana_data:/var/lib/grafana
    networks:
      - kong-net

volumes:
  prometheus_data:
  grafana_data:
EOF

# Fichier prometheus.yml:

cat > prometheus.yml << 'EOF'
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'kong'
    static_configs:
      - targets: ['kong:8001']
    metrics_path: /metrics
EOF

# Redémarrer:
docker-compose up -d

# Accéder à Grafana: http://localhost:3000
# Login: admin / admin

# Ajouter Kong comme source de données:
# 1. Configuration -> Data Sources -> Add data source
# 2. Choisir Prometheus
# 3. URL: http://prometheus:9090
# 4. Save & Test

# Importer un dashboard Kong:
# 1. Dashboards -> Import
# 2. ID: 7424 (Kong official dashboard)
# 3. Choisir la source Prometheus
# 4. Import

# Tu verras:
# - Nombre de requêtes par seconde
# - Latence moyenne
# - Taux d'erreur
# - Bande passante
# - Graphiques en temps réel


# === PLUGIN DATADOG ===

# Intégration avec Datadog (monitoring SaaS)

curl -i -X POST http://localhost:8001/plugins \
  --data name=datadog \
  --data config.host=datadog-agent \
  --data config.port=8125


# === PLUGIN ZIPKIN (TRACING) ===

# Tracer les requêtes à travers plusieurs services

curl -i -X POST http://localhost:8001/plugins \
  --data name=zipkin \
  --data config.http_endpoint=http://zipkin:9411/api/v2/spans


[OK] SÉCURITÉ AVANCÉE

# === HTTPS / TLS ===

# Activer HTTPS sur le proxy Kong

# Méthode 1: Certificat auto-signé (dev)

# Générer un certificat:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout kong.key \
  -out kong.crt \
  -subj "/CN=localhost"

# Ajouter à Kong:
curl -i -X POST http://localhost:8001/certificates \
  --form cert=@kong.crt \
  --form key=@kong.key \
  --form snis=localhost

# Explications:
# snis = Server Name Indication (domaine)
# Plusieurs certificats possibles pour différents domaines

# Tester:
curl -k https://localhost:8443/api/users

# -k = ignore les erreurs de certificat (auto-signé)


# Méthode 2: Let's Encrypt (prod)

# Installer certbot dans le container Kong:
docker exec -it kong-gateway bash

# Générer le certificat:
certbot certonly --standalone -d api.monapp.com

# Certificats créés dans:
# /etc/letsencrypt/live/api.monapp.com/fullchain.pem
# /etc/letsencrypt/live/api.monapp.com/privkey.pem

# Ajouter à Kong:
curl -i -X POST http://localhost:8001/certificates \
  --form cert=@/etc/letsencrypt/live/api.monapp.com/fullchain.pem \
  --form key=@/etc/letsencrypt/live/api.monapp.com/privkey.pem \
  --form snis=api.monapp.com


# === MUTUAL TLS (mTLS) ===

# Le client doit aussi fournir un certificat

# Activer le plugin:
curl -i -X POST http://localhost:8001/routes/users-route/plugins \
  --data name=mtls-auth

# Créer un certificat client:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout client.key \
  -out client.crt \
  -subj "/CN=mobile-app"

# Ajouter le certificat client à Kong:
curl -i -X POST http://localhost:8001/consumers/mobile-app/mtls-auth \
  --form cert=@client.crt

# Tester:
curl --cert client.crt --key client.key https://localhost:8443/api/users


# === OAUTH 2.0 ===

# Authentification OAuth2

# Activer le plugin:
curl -i -X POST http://localhost:8001/routes/users-route/plugins \
  --data name=oauth2 \
  --data config.scopes=email,profile \
  --data config.mandatory_scope=true

# Créer une application OAuth:
curl -i -X POST http://localhost:8001/consumers/mobile-app/oauth2 \
  --data name=mobile-app-client \
  --data client_id=my-client-id \
  --data client_secret=my-client-secret \
  --data redirect_uris=http://localhost:3000/callback

# Flow OAuth:

# 1. Rediriger vers l'endpoint d'autorisation:
http://localhost:8000/oauth2/authorize?response_type=code&client_id=my-client-id&scope=email,profile

# 2. Utilisateur se connecte et autorise

# 3. Recevoir le code d'autorisation:
http://localhost:3000/callback?code=abc123

# 4. Échanger le code contre un access token:
curl -X POST http://localhost:8000/oauth2/token \
  --data grant_type=authorization_code \
  --data client_id=my-client-id \
  --data client_secret=my-client-secret \
  --data code=abc123

# Réponse:
{
  "access_token": "xyz789",
  "token_type": "bearer",
  "expires_in": 3600
}

# 5. Utiliser l'access token:
curl -H "Authorization: Bearer xyz789" http://localhost:8000/api/users


# === BOT DETECTION ===

# Détecter et bloquer les bots

curl -i -X POST http://localhost:8001/plugins \
  --data name=bot-detection \
  --data config.allow=googlebot,bingbot \
  --data config.deny=baidu


# === ACL (ACCESS CONTROL LIST) ===

# Contrôler l'accès par groupes

# Créer des groupes:
curl -i -X POST http://localhost:8001/consumers/mobile-app/acls \
  --data group=premium

curl -i -X POST http://localhost:8001/consumers/web-app/acls \
  --data group=free

# Activer le plugin:
curl -i -X POST http://localhost:8001/routes/users-route/plugins \
  --data name=acl \
  --data config.allow=premium

# Résultat:
# mobile-app (premium) -> Accès OK
# web-app (free) -> Accès refusé


[OK] EXEMPLE COMPLET: APPLICATION COMPLÈTE

# === CONTEXTE ===

# Tu développes une app de e-commerce avec:
# - Frontend React: http://localhost:3000
# - API Users: http://localhost:4001
# - API Products: http://localhost:4002
# - API Orders: http://localhost:4003

# Objectifs:
# 1. Centraliser toutes les APIs via Kong
# 2. Authentification par clé API
# 3. Rate limiting pour éviter les abus
# 4. CORS pour le frontend
# 5. Load balancing pour l'API Products (3 instances)
# 6. Monitoring avec Prometheus


# === ÉTAPE 1: DÉMARRER LES APIs BACKEND ===

# API Users (Flask):
cat > api_users.py << 'EOF'
from flask import Flask, jsonify
app = Flask(__name__)

@app.route('/users', methods=['GET'])
def get_users():
    return jsonify([
        {"id": 1, "name": "Alice"},
        {"id": 2, "name": "Bob"}
    ])

@app.route('/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    return jsonify({"id": user_id, "name": f"User {user_id}"})

if __name__ == '__main__':
    app.run(port=4001)
EOF

python api_users.py &

# API Products (Flask):
cat > api_products.py << 'EOF'
from flask import Flask, jsonify
app = Flask(__name__)

@app.route('/products', methods=['GET'])
def get_products():
    return jsonify([
        {"id": 1, "name": "Laptop", "price": 999},
        {"id": 2, "name": "Phone", "price": 699}
    ])

if __name__ == '__main__':
    app.run(port=4002)
EOF

python api_products.py &

# API Orders (Flask):
cat > api_orders.py << 'EOF'
from flask import Flask, jsonify
app = Flask(__name__)

@app.route('/orders', methods=['GET'])
def get_orders():
    return jsonify([
        {"id": 1, "user_id": 1, "total": 1698}
    ])

if __name__ == '__main__':
    app.run(port=4003)
EOF

python api_orders.py &


# === ÉTAPE 2: CONFIGURER KONG (kong.yml) ===

cat > kong.yml << 'EOF'
_format_version: "3.0"

# === SERVICES & ROUTES ===

services:
  # Service Users
  - name: users-service
    url: http://host.docker.internal:4001
    routes:
      - name: users-route
        paths:
          - /api/users
        methods:
          - GET
          - POST
        strip_path: true
        plugins:
          - name: key-auth
            config:
              key_names:
                - apikey
          - name: rate-limiting
            config:
              minute: 20
              hour: 1000
              policy: local
          - name: cors
            config:
              origins:
                - http://localhost:3000
              methods:
                - GET
                - POST
                - PUT
                - DELETE
              credentials: true

  # Service Products
  - name: products-service
    host: products-upstream  # Load balancing
    routes:
      - name: products-route
        paths:
          - /api/products
        strip_path: true
        plugins:
          - name: key-auth
            config:
              key_names:
                - apikey
          - name: proxy-cache
            config:
              strategy: memory
              cache_ttl: 300

  # Service Orders
  - name: orders-service
    url: http://host.docker.internal:4003
    routes:
      - name: orders-route
        paths:
          - /api/orders
        strip_path: true
        plugins:
          - name: key-auth
            config:
              key_names:
                - apikey
          - name: rate-limiting
            config:
              minute: 10
              hour: 500

# === UPSTREAMS (Load Balancing) ===

upstreams:
  - name: products-upstream
    algorithm: round-robin
    targets:
      - target: host.docker.internal:4002
        weight: 100
      # Ajouter d'autres instances si disponibles:
      # - target: host.docker.internal:4012
      #   weight: 100
      # - target: host.docker.internal:4022
      #   weight: 100

# === CONSUMERS ===

consumers:
  # Frontend React
  - username: react-frontend
    keyauth_credentials:
      - key: frontend-key-abc123
    plugins:
      - name: rate-limiting
        config:
          minute: 100
          hour: 5000

  # Application mobile
  - username: mobile-app
    keyauth_credentials:
      - key: mobile-key-xyz789
    plugins:
      - name: rate-limiting
        config:
          minute: 50
          hour: 2000

  # Admin backend
  - username: admin-backend
    keyauth_credentials:
      - key: admin-key-secret456
    # Pas de rate limiting pour l'admin

# === PLUGINS GLOBAUX ===

plugins:
  # Monitoring
  - name: prometheus

  # Request ID pour tracer
  - name: request-id
    config:
      header_name: X-Request-ID

  # Logs
  - name: file-log
    config:
      path: /tmp/kong-access.log
EOF


# === ÉTAPE 3: CHARGER LA CONFIGURATION ===

# Via docker-compose:
docker-compose restart kong

# Ou via Admin API:
curl -i -X POST http://localhost:8001/config \
  --form config=@kong.yml


# === ÉTAPE 4: TESTER LES APIs ===

# Sans Kong (direct):
curl http://localhost:4001/users
curl http://localhost:4002/products
curl http://localhost:4003/orders

# Avec Kong (via proxy):

# 1. Users API:
curl -H "apikey: frontend-key-abc123" http://localhost:8000/api/users

# Réponse:
[
  {"id": 1, "name": "Alice"},
  {"id": 2, "name": "Bob"}
]

# 2. Products API (avec cache):
curl -H "apikey: frontend-key-abc123" http://localhost:8000/api/products

# Première requête: X-Cache-Status: Miss
# Deuxième requête: X-Cache-Status: Hit

# 3. Orders API:
curl -H "apikey: frontend-key-abc123" http://localhost:8000/api/orders


# === ÉTAPE 5: FRONTEND REACT ===

// src/api.js
const API_URL = 'http://localhost:8000/api';
const API_KEY = 'frontend-key-abc123';

async function fetchUsers() {
  const response = await fetch(`${API_URL}/users`, {
    headers: {
      'apikey': API_KEY
    }
  });
  return response.json();
}

async function fetchProducts() {
  const response = await fetch(`${API_URL}/products`, {
    headers: {
      'apikey': API_KEY
    }
  });
  return response.json();
}

async function fetchOrders() {
  const response = await fetch(`${API_URL}/orders`, {
    headers: {
      'apikey': API_KEY
    }
  });
  return response.json();
}

export { fetchUsers, fetchProducts, fetchOrders };

// src/App.js
import React, { useEffect, useState } from 'react';
import { fetchUsers, fetchProducts, fetchOrders } from './api';

function App() {
  const [users, setUsers] = useState([]);
  const [products, setProducts] = useState([]);
  const [orders, setOrders] = useState([]);

  useEffect(() => {
    fetchUsers().then(setUsers);
    fetchProducts().then(setProducts);
    fetchOrders().then(setOrders);
  }, []);

  return (
    <div>
      <h1>E-commerce App</h1>
      
      <h2>Users</h2>
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>

      <h2>Products</h2>
      <ul>
        {products.map(product => (
          <li key={product.id}>
            {product.name} - ${product.price}
          </li>
        ))}
      </ul>

      <h2>Orders</h2>
      <ul>
        {orders.map(order => (
          <li key={order.id}>
            Order #{order.id} - Total: ${order.total}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;


# === ÉTAPE 6: MONITORING ===

# Accéder aux métriques:
curl http://localhost:8001/metrics

# Exemples de métriques:

# Requêtes par service:
kong_http_requests_total{service="users-service"} 1234
kong_http_requests_total{service="products-service"} 5678

# Latence:
kong_latency_ms_sum{service="users-service"} 12345
kong_latency_ms_count{service="users-service"} 100

# Bande passante:
kong_bandwidth_bytes{service="products-service"} 987654

# Avec Grafana (voir section précédente):
# Dashboard affiche les graphiques en temps réel


# === ÉTAPE 7: LOGS ===

# Voir les logs:
docker-compose logs -f kong

# Exemple de log:
{
  "started_at": 1234567890,
  "client_ip": "192.168.1.100",
  "request": {
    "method": "GET",
    "uri": "/api/users",
    "headers": {
      "apikey": "***"
    }
  },
  "response": {
    "status": 200,
    "size": 234
  },
  "service": {"name": "users-service"},
  "consumer": {"username": "react-frontend"},
  "latencies": {
    "request": 123,
    "kong": 45,
    "proxy": 78
  }
}


[OK] DÉPLOIEMENT EN PRODUCTION

# === DOCKER COMPOSE POUR PRODUCTION ===

cat > docker-compose-prod.yml << 'EOF'
version: '3.9'

services:
  kong-database:
    image: postgres:15
    restart: always
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - kong_data:/var/lib/postgresql/data
    networks:
      - kong-net

  kong-migration:
    image: kong/kong-gateway:3.5
    command: kong migrations bootstrap
    environment:
      KONG_DATABASE: postgres
      KONG_PG_HOST: kong-database
      KONG_PG_USER: ${POSTGRES_USER}
      KONG_PG_PASSWORD: ${POSTGRES_PASSWORD}
      KONG_PG_DATABASE: ${POSTGRES_DB}
    depends_on:
      - kong-database
    networks:
      - kong-net
    restart: on-failure

  kong:
    image: kong/kong-gateway:3.5
    restart: always
    environment:
      KONG_DATABASE: postgres
      KONG_PG_HOST: kong-database
      KONG_PG_USER: ${POSTGRES_USER}
      KONG_PG_PASSWORD: ${POSTGRES_PASSWORD}
      KONG_PG_DATABASE: ${POSTGRES_DB}
      KONG_PROXY_ACCESS_LOG: /dev/stdout
      KONG_ADMIN_ACCESS_LOG: /dev/stdout
      KONG_PROXY_ERROR_LOG: /dev/stderr
      KONG_ADMIN_ERROR_LOG: /dev/stderr
      KONG_ADMIN_LISTEN: 127.0.0.1:8001  # Admin API seulement en local
      KONG_REAL_IP_HEADER: X-Forwarded-For
      KONG_TRUSTED_IPS: 0.0.0.0/0
    depends_on:
      - kong-database
      - kong-migration
    ports:
      - "80:8000"
      - "443:8443"
    networks:
      - kong-net
    volumes:
      - ./kong.yml:/usr/local/kong/declarative/kong.yml:ro

  # Nginx reverse proxy
  nginx:
    image: nginx:latest
    restart: always
    ports:
      - "8001:8001"  # Admin API via Nginx avec auth
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    networks:
      - kong-net

networks:
  kong-net:
    driver: bridge

volumes:
  kong_data:
EOF

# Fichier .env:
cat > .env << 'EOF'
POSTGRES_USER=kong_prod
POSTGRES_DB=kong_prod
POSTGRES_PASSWORD=super-secret-password-change-this
EOF

# Fichier nginx.conf (protection Admin API):
cat > nginx.conf << 'EOF'
events {
  worker_connections 1024;
}

http {
  upstream kong-admin {
    server kong:8001;
  }

  server {
    listen 8001 ssl;
    server_name admin.monapp.com;

    ssl_certificate /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;

    # Authentification HTTP Basic
    auth_basic "Kong Admin API";
    auth_basic_user_file /etc/nginx/.htpasswd;

    location / {
      proxy_pass http://kong-admin;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
    }
  }
}
EOF

# Créer le fichier .htpasswd:
htpasswd -c .htpasswd admin

# Démarrer en production:
docker-compose -f docker-compose-prod.yml up -d


# === DÉPLOIEMENT SUR AWS / AZURE / GCP ===

# 1. Préparer les ressources:
# - Base de données PostgreSQL managée (RDS, Azure DB, Cloud SQL)
# - Load balancer (ALB, Azure LB, GCP LB)
# - Instances EC2 / VM pour Kong

# 2. Variables d'environnement:
export KONG_DATABASE=postgres
export KONG_PG_HOST=db.example.com
export KONG_PG_USER=kong
export KONG_PG_PASSWORD=secret
export KONG_PG_DATABASE=kong

# 3. Lancer Kong:
docker run -d \
  --name kong \
  -e KONG_DATABASE=$KONG_DATABASE \
  -e KONG_PG_HOST=$KONG_PG_HOST \
  -e KONG_PG_USER=$KONG_PG_USER \
  -e KONG_PG_PASSWORD=$KONG_PG_PASSWORD \
  -p 8000:8000 \
  -p 8443:8443 \
  kong/kong-gateway:3.5

# 4. Configurer le load balancer:
# - Forwarding rule: 80 -> 8000 (Kong HTTP)
# - Forwarding rule: 443 -> 8443 (Kong HTTPS)
# - Health check: GET /status (port 8001)


# === KUBERNETES (K8S) ===

# Kong propose un Ingress Controller pour Kubernetes

# Installation via Helm:
helm repo add kong https://charts.konghq.com
helm repo update

helm install kong kong/kong \
  --set ingressController.enabled=true \
  --set postgresql.enabled=true \
  --namespace kong \
  --create-namespace

# Vérifier:
kubectl get pods -n kong

# Exemple d'Ingress:
cat > ingress.yml << 'EOF'
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: users-ingress
  annotations:
    konghq.com/strip-path: "true"
    konghq.com/plugins: key-auth,rate-limiting
spec:
  ingressClassName: kong
  rules:
    - host: api.monapp.com
      http:
        paths:
          - path: /api/users
            pathType: Prefix
            backend:
              service:
                name: users-service
                port:
                  number: 4001
EOF

kubectl apply -f ingress.yml


[OK] DÉPANNAGE (TROUBLESHOOTING)

# === PROBLÈME: Kong ne démarre pas ===

# Vérifier les logs:
docker-compose logs kong

# Erreur courante: "database not ready"
# Solution: Attendre que PostgreSQL soit prêt
# Vérifier:
docker-compose ps kong-database

# Si unhealthy:
docker-compose restart kong-database
docker-compose restart kong


# === PROBLÈME: 404 Not Found ===

# Cause: Route non configurée

# Vérifier les routes:
curl http://localhost:8001/routes

# Si vide: Créer des routes!

# Vérifier les services:
curl http://localhost:8001/services


# === PROBLÈME: 401 Unauthorized ===

# Cause: Plugin key-auth activé mais pas de clé fournie

# Tester sans authentification:
curl -i http://localhost:8000/api/users

# Réponse:
HTTP/1.1 401 Unauthorized
{"message": "No API key found in request"}

# Solution: Fournir la clé:
curl -H "apikey: frontend-key-abc123" http://localhost:8000/api/users


# === PROBLÈME: 429 Too Many Requests ===

# Cause: Rate limit dépassé

# Vérifier la config du plugin:
curl http://localhost:8001/plugins | grep rate-limiting

# Solution: Attendre ou augmenter la limite


# === PROBLÈME: 502 Bad Gateway ===

# Cause: Le backend ne répond pas

# Vérifier que le backend fonctionne:
curl http://localhost:4001/users

# Si erreur: Relancer le backend

# Vérifier la config du service:
curl http://localhost:8001/services/users-service

# Vérifier l'URL:
# "url": "http://host.docker.internal:4001"


# === PROBLÈME: CORS errors ===

# Erreur dans la console du navigateur:
# "Access to fetch at 'http://localhost:8000/api/users' from origin 'http://localhost:3000' has been blocked by CORS policy"

# Cause: Plugin CORS pas activé ou mal configuré

# Vérifier le plugin:
curl http://localhost:8001/plugins | grep cors

# Si absent: Activer le plugin
curl -i -X POST http://localhost:8001/routes/users-route/plugins \
  --data name=cors \
  --data config.origins=http://localhost:3000 \
  --data config.credentials=true


# === PROBLÈME: Lenteur / Timeouts ===

# Vérifier les latences:
curl http://localhost:8001/status

# Réponse:
{
  "database": {"reachable": true},
  "server": {"connections_active": 5}
}

# Augmenter les timeouts:
curl -i -X PATCH http://localhost:8001/services/users-service \
  --data connect_timeout=60000 \
  --data read_timeout=60000 \
  --data write_timeout=60000

# Timeouts en millisecondes


# === PROBLÈME: Admin API inaccessible ===

# Vérifier que le port est exposé:
docker ps | grep kong

# Doit afficher: 0.0.0.0:8001->8001/tcp

# Tester:
curl http://localhost:8001/

# Si erreur "Connection refused":
# Vérifier la config KONG_ADMIN_LISTEN

# Dans docker-compose.yml:
# KONG_ADMIN_LISTEN: 0.0.0.0:8001


[OK] RESSOURCES & LIENS

# Documentation officielle Kong:
# https://docs.konghq.com/

# Guides spécifiques:
# Getting Started: https://docs.konghq.com/gateway/latest/get-started/
# Plugins: https://docs.konghq.com/hub/
# Configuration: https://docs.konghq.com/gateway/latest/reference/configuration/

# Kong Hub (plugins):
# https://docs.konghq.com/hub/

# Konga (GUI):
# https://github.com/pantsel/konga

# Deck (CLI tool):
# https://docs.konghq.com/deck/

# Kong Ingress Controller (K8s):
# https://docs.konghq.com/kubernetes-ingress-controller/

# Forum d'aide:
# https://discuss.konghq.com/

# GitHub:
# https://github.com/Kong/kong

# Blog:
# https://konghq.com/blog


[OK] RÉSUMÉ DES COMMANDES ESSENTIELLES

# === DOCKER ===

# Démarrer Kong:
docker-compose up -d

# Arrêter Kong:
docker-compose down

# Voir les logs:
docker-compose logs -f kong

# Redémarrer Kong:
docker-compose restart kong


# === ADMIN API ===

# Créer un service:
curl -i -X POST http://localhost:8001/services \
  --data name=my-service \
  --data url=http://backend:8080

# Créer une route:
curl -i -X POST http://localhost:8001/services/my-service/routes \
  --data name=my-route \
  --data paths[]=/api/path

# Activer un plugin:
curl -i -X POST http://localhost:8001/routes/my-route/plugins \
  --data name=key-auth

# Créer un consumer:
curl -i -X POST http://localhost:8001/consumers \
  --data username=my-consumer

# Créer une clé API:
curl -i -X POST http://localhost:8001/consumers/my-consumer/key-auth \
  --data key=my-api-key

# Lister tous les services:
curl http://localhost:8001/services

# Lister toutes les routes:
curl http://localhost:8001/routes

# Lister tous les plugins:
curl http://localhost:8001/plugins

# Supprimer un service:
curl -i -X DELETE http://localhost:8001/services/my-service


# === PROXY API ===

# Appeler une API via Kong:
curl -H "apikey: my-api-key" http://localhost:8000/api/path

# Avec JWT:
curl -H "Authorization: Bearer <token>" http://localhost:8000/api/path


# === MONITORING ===

# Métriques Prometheus:
curl http://localhost:8001/metrics

# Status Kong:
curl http://localhost:8001/status


# === DECK (CLI TOOL) ===

# Exporter la config:
deck dump -o kong.yml

# Appliquer la config:
deck sync -s kong.yml

# Valider la config:
deck validate -s kong.yml

# Diff entre local et Kong:
deck diff -s kong.yml


[OK] CHECKLISTE FINALE AVANT PRODUCTION

# [OK] Base de données PostgreSQL configurée?
docker-compose ps kong-database
# Doit afficher: Up (healthy)

# [OK] Migrations appliquées?
docker-compose logs kong-migration
# Doit afficher: "migrations bootstrap completed"

# [OK] Kong démarré?
curl -i http://localhost:8001/
# Doit retourner du JSON

# [OK] Services configurés?
curl http://localhost:8001/services | jq '.data[].name'

# [OK] Routes configurées?
curl http://localhost:8001/routes | jq '.data[].paths'

# [OK] Plugins activés?
curl http://localhost:8001/plugins | jq '.data[].name'

# [OK] Authentification testée?
curl -H "apikey: <clé>" http://localhost:8000/api/path

# [OK] HTTPS configuré?
curl -k https://localhost:8443/api/path

# [OK] Monitoring activé (Prometheus)?
curl http://localhost:8001/metrics

# [OK] Logs accessibles?
docker-compose logs -f kong

# [OK] Rate limiting testé?
for i in {1..11}; do curl -H "apikey: <clé>" http://localhost:8000/api/path; done

# Si OUI à tous: TU PEUX DÉPLOYER EN PRODUCTION!


# APPLICATION MICROSERVICES COMPLÈTE - FLASK + REACT + KONG GATEWAY
# Guide Ultra-Détaillé pour Grands Débutants

# ============================================================================
# PARTIE 1: INTRODUCTION AUX MICROSERVICES (POUR GRANDS DÉBUTANTS)
# ============================================================================

# === QU'EST-CE QU'UN MICROSERVICE? ===

# AVANT (Application Monolithique):
# Tu as UNE SEULE application qui fait tout:
# 
# monolithic-app/
# ├── users.py          (gestion utilisateurs)
# ├── products.py       (gestion produits)
# ├── orders.py         (gestion commandes)
# ├── payments.py       (gestion paiements)
# └── notifications.py  (envoi emails/SMS)
#
# Problèmes:
# 1. Si une partie crash, TOUT crash
# 2. Difficile à scaler (si products est lent, tu dois scaler TOUTE l'app)
# 3. Déploiement complexe (chaque modification redéploie TOUT)
# 4. Équipes bloquées (tout le monde travaille sur la même codebase)
# 5. Technologies figées (tout doit être dans le même langage)

# APRÈS (Architecture Microservices):
# Tu as PLUSIEURS applications indépendantes:
#
# users-service/         -> Port 4001 (Python Flask)
# products-service/      -> Port 4002 (Python Flask)
# orders-service/        -> Port 4003 (Node.js Express)
# payments-service/      -> Port 4004 (Python Flask)
# notifications-service/ -> Port 4005 (Go)
#
# Avantages:
# 1. Si payments crash, les autres continuent de fonctionner
# 2. Scale indépendamment (3 instances de products, 1 de users)
# 3. Déploiement indépendant (modifier products sans toucher users)
# 4. Équipes autonomes (équipe A sur users, équipe B sur products)
# 5. Liberté technologique (Python pour users, Node.js pour orders)

# MAIS... NOUVEAU PROBLÈME:
# Comment le frontend (React) appelle-t-il 5 APIs différentes?
# - http://users-api.com:4001
# - http://products-api.com:4002
# - http://orders-api.com:4003
# - etc.

# SOLUTION: KONG GATEWAY!
# Kong = Porte d'entrée unique pour tous les microservices
#
# Frontend React -> Kong (port 8000) -> Redirige vers le bon service
#
# Le frontend appelle:
# - http://api.monapp.com/users     -> Kong redirige vers users-service:4001
# - http://api.monapp.com/products  -> Kong redirige vers products-service:4002
# - http://api.monapp.com/orders    -> Kong redirige vers orders-service:4003

# Kong ajoute aussi:
# - Authentification (qui peut appeler?)
# - Rate limiting (combien de requêtes max?)
# - Monitoring (combien de requêtes reçues?)
# - Cache (accélérer les réponses)
# - Load balancing (répartir sur plusieurs instances)


# ============================================================================
# PARTIE 2: NOTRE APPLICATION E-COMMERCE
# ============================================================================

# === VUE D'ENSEMBLE ===

# Nous allons créer une application e-commerce complète avec:
#
# MICROSERVICES BACKEND:
# 1. users-service       -> Gestion utilisateurs (inscription, login, profil)
# 2. products-service    -> Catalogue produits (liste, détails, recherche)
# 3. orders-service      -> Gestion commandes (créer, lister, statut)
# 4. payments-service    -> Traitement paiements (Stripe simulation)
# 5. notifications-service -> Envoi emails/SMS (confirmation commandes)
#
# FRONTEND:
# 6. react-app          -> Interface utilisateur (web)
#
# INFRASTRUCTURE:
# 7. kong-gateway       -> API Gateway
# 8. postgresql         -> Base de données Kong
# 9. prometheus         -> Monitoring métriques
# 10. grafana           -> Dashboards visuels

# === ARCHITECTURE ===

#                         ┌─────────────────┐
#                         │   React App     │
#                         │  (Frontend)     │
#                         │   Port 3000     │
#                         └────────┬────────┘
#                                  │
#                         HTTP (apikey header)
#                                  │
#                         ┌────────[BLACK_DOWN-POINTING_TRIANGLE]────────┐
#                         │  Kong Gateway   │
#                         │  Port 8000      │
#                         │  (API Gateway)  │
#                         └────────┬────────┘
#                                  │
#              ┌───────────────────┼───────────────────┬─────────────┐
#              │                   │                   │             │
#     ┌────────[BLACK_DOWN-POINTING_TRIANGLE]────────┐ ┌───────[BLACK_DOWN-POINTING_TRIANGLE]───────┐ ┌────────[BLACK_DOWN-POINTING_TRIANGLE]────────┐ ┌──[BLACK_DOWN-POINTING_TRIANGLE]──────┐
#     │ users-service   │ │products-service│ │ orders-service  │ │payments │
#     │   Port 4001     │ │   Port 4002    │ │   Port 4003     │ │  4004   │
#     └─────────────────┘ └────────────────┘ └─────────────────┘ └─────────┘
#              │                                        │
#              │                                        │
#              └────────────────┬───────────────────────┘
#                               │
#                      ┌────────[BLACK_DOWN-POINTING_TRIANGLE]────────┐
#                      │ notifications   │
#                      │   Port 4005     │
#                      └─────────────────┘

# === FONCTIONNALITÉS KONG EXPLORÉES ===

# Cette application utilisera TOUTES les fonctionnalités principales de Kong:
#
# 1. [OK] Services & Routes (base)
# 2. [OK] Key Authentication (clés API)
# 3. [OK] JWT Authentication (tokens JWT)
# 4. [OK] Rate Limiting (limite de requêtes)
# 5. [OK] CORS (accès frontend)
# 6. [OK] Request Transformer (modification requêtes)
# 7. [OK] Response Transformer (modification réponses)
# 8. [OK] Proxy Cache (cache des réponses)
# 9. [OK] Load Balancing (répartition de charge)
# 10. [OK] IP Restriction (whitelist/blacklist)
# 11. [OK] ACL (contrôle d'accès par groupe)
# 12. [OK] OAuth2 (authentification OAuth)
# 13. [OK] Prometheus (métriques)
# 14. [OK] File/HTTP Log (logs)
# 15. [OK] Bot Detection (détection bots)
# 16. [OK] Request Size Limiting (limite taille)
# 17. [OK] Health Checks (vérification santé services)


# ============================================================================
# PARTIE 3: STRUCTURE DU PROJET
# ============================================================================

# === ARBORESCENCE COMPLÈTE ===

# ecommerce-microservices/
# │
# ├── docker-compose.yml              # Orchestration de tous les services
# ├── kong.yml                        # Configuration Kong déclarative
# ├── .env                            # Variables d'environnement
# │
# ├── services/                       # Tous les microservices backend
# │   ├── users/
# │   │   ├── app.py                  # API Users
# │   │   ├── models.py               # Modèles SQLAlchemy
# │   │   ├── requirements.txt        # Dépendances Python
# │   │   ├── Dockerfile
# │   │   └── database.db             # SQLite local
# │   │
# │   ├── products/
# │   │   ├── app.py
# │   │   ├── models.py
# │   │   ├── requirements.txt
# │   │   ├── Dockerfile
# │   │   └── database.db
# │   │
# │   ├── orders/
# │   │   ├── app.py
# │   │   ├── models.py
# │   │   ├── requirements.txt
# │   │   ├── Dockerfile
# │   │   └── database.db
# │   │
# │   ├── payments/
# │   │   ├── app.py
# │   │   ├── requirements.txt
# │   │   ├── Dockerfile
# │   │   └── stripe_simulator.py
# │   │
# │   └── notifications/
# │       ├── app.py
# │       ├── requirements.txt
# │       ├── Dockerfile
# │       └── email_templates/
# │           ├── order_confirmation.html
# │           └── payment_success.html
# │
# ├── frontend/                       # Application React
# │   ├── public/
# │   │   └── index.html
# │   ├── src/
# │   │   ├── App.js
# │   │   ├── api/
# │   │   │   ├── config.js
# │   │   │   ├── users.js
# │   │   │   ├── products.js
# │   │   │   └── orders.js
# │   │   ├── components/
# │   │   │   ├── Login.js
# │   │   │   ├── ProductList.js
# │   │   │   ├── Cart.js
# │   │   │   └── OrderHistory.js
# │   │   └── index.js
# │   ├── package.json
# │   └── Dockerfile
# │
# ├── monitoring/                     # Monitoring & logs
# │   ├── prometheus.yml
# │   └── grafana/
# │       └── dashboards/
# │           └── kong-dashboard.json
# │
# └── scripts/                        # Scripts utiles
#     ├── setup.sh                    # Installation complète
#     ├── test-apis.sh                # Tests des APIs
#     └── seed-data.sh                # Données de test


# ============================================================================
# PARTIE 4: INSTALLATION & CONFIGURATION
# ============================================================================

# === ÉTAPE 1: CRÉER LA STRUCTURE ===

# Créer le dossier principal:
mkdir ecommerce-microservices && cd ecommerce-microservices

# Créer la structure:
mkdir -p services/{users,products,orders,payments,notifications}
mkdir -p frontend/src/{api,components}
mkdir -p monitoring/grafana/dashboards
mkdir -p scripts


# === ÉTAPE 2: DOCKER-COMPOSE.YML ===

cat > docker-compose.yml << 'DOCKERCOMPOSE'
version: '3.9'

services:
  # ===== BASE DE DONNÉES KONG =====
  kong-database:
    image: postgres:15
    container_name: kong-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: kong
      POSTGRES_DB: kong
      POSTGRES_PASSWORD: ${KONG_DB_PASSWORD:-kongpass}
    volumes:
      - kong_data:/var/lib/postgresql/data
    networks:
      - kong-net
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "kong"]
      interval: 10s
      timeout: 5s
      retries: 5

  # ===== MIGRATION KONG =====
  kong-migration:
    image: kong/kong-gateway:3.5
    container_name: kong-migration
    command: kong migrations bootstrap
    environment:
      KONG_DATABASE: postgres
      KONG_PG_HOST: kong-database
      KONG_PG_USER: kong
      KONG_PG_PASSWORD: ${KONG_DB_PASSWORD:-kongpass}
      KONG_PG_DATABASE: kong
    depends_on:
      kong-database:
        condition: service_healthy
    networks:
      - kong-net
    restart: on-failure

  # ===== KONG GATEWAY =====
  kong:
    image: kong/kong-gateway:3.5
    container_name: kong-gateway
    restart: unless-stopped
    environment:
      KONG_DATABASE: postgres
      KONG_PG_HOST: kong-database
      KONG_PG_USER: kong
      KONG_PG_PASSWORD: ${KONG_DB_PASSWORD:-kongpass}
      KONG_PG_DATABASE: kong
      KONG_PROXY_ACCESS_LOG: /dev/stdout
      KONG_ADMIN_ACCESS_LOG: /dev/stdout
      KONG_PROXY_ERROR_LOG: /dev/stderr
      KONG_ADMIN_ERROR_LOG: /dev/stderr
      KONG_ADMIN_LISTEN: 0.0.0.0:8001
      KONG_ADMIN_GUI_URL: http://localhost:8002
    depends_on:
      kong-database:
        condition: service_healthy
      kong-migration:
        condition: service_completed_successfully
    ports:
      - "8000:8000"  # Proxy HTTP
      - "8443:8443"  # Proxy HTTPS
      - "8001:8001"  # Admin API
      - "8002:8002"  # Kong Manager GUI
    networks:
      - kong-net
    healthcheck:
      test: ["CMD", "kong", "health"]
      interval: 10s
      timeout: 5s
      retries: 5

  # ===== KONGA (GUI) =====
  konga:
    image: pantsel/konga:latest
    container_name: konga
    restart: unless-stopped
    environment:
      NODE_ENV: production
      DB_ADAPTER: postgres
      DB_HOST: kong-database
      DB_USER: kong
      DB_PASSWORD: ${KONG_DB_PASSWORD:-kongpass}
      DB_DATABASE: konga
    depends_on:
      kong-database:
        condition: service_healthy
    ports:
      - "1337:1337"
    networks:
      - kong-net

  # ===== MICROSERVICE: USERS =====
  users-service:
    build: ./services/users
    container_name: users-service
    restart: unless-stopped
    environment:
      - SERVICE_NAME=users-service
      - SERVICE_PORT=4001
      - DATABASE_URL=sqlite:///database.db
    ports:
      - "4001:4001"
    networks:
      - kong-net
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4001/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  # ===== MICROSERVICE: PRODUCTS =====
  products-service:
    build: ./services/products
    container_name: products-service
    restart: unless-stopped
    environment:
      - SERVICE_NAME=products-service
      - SERVICE_PORT=4002
      - DATABASE_URL=sqlite:///database.db
    ports:
      - "4002:4002"
      - "4012:4002"  # Instance 2 pour load balancing
      - "4022:4002"  # Instance 3 pour load balancing
    networks:
      - kong-net
    deploy:
      replicas: 3  # 3 instances pour le load balancing
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4002/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  # ===== MICROSERVICE: ORDERS =====
  orders-service:
    build: ./services/orders
    container_name: orders-service
    restart: unless-stopped
    environment:
      - SERVICE_NAME=orders-service
      - SERVICE_PORT=4003
      - DATABASE_URL=sqlite:///database.db
      - PAYMENTS_SERVICE_URL=http://payments-service:4004
      - NOTIFICATIONS_SERVICE_URL=http://notifications-service:4005
    ports:
      - "4003:4003"
    networks:
      - kong-net
    depends_on:
      - payments-service
      - notifications-service
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4003/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  # ===== MICROSERVICE: PAYMENTS =====
  payments-service:
    build: ./services/payments
    container_name: payments-service
    restart: unless-stopped
    environment:
      - SERVICE_NAME=payments-service
      - SERVICE_PORT=4004
      - STRIPE_API_KEY=${STRIPE_API_KEY:-sk_test_fake}
    ports:
      - "4004:4004"
    networks:
      - kong-net
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4004/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  # ===== MICROSERVICE: NOTIFICATIONS =====
  notifications-service:
    build: ./services/notifications
    container_name: notifications-service
    restart: unless-stopped
    environment:
      - SERVICE_NAME=notifications-service
      - SERVICE_PORT=4005
      - SMTP_HOST=${SMTP_HOST:-smtp.gmail.com}
      - SMTP_PORT=${SMTP_PORT:-587}
      - SMTP_USER=${SMTP_USER:-noreply@example.com}
      - SMTP_PASSWORD=${SMTP_PASSWORD:-password}
    ports:
      - "4005:4005"
    networks:
      - kong-net
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4005/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  # ===== FRONTEND REACT =====
  react-app:
    build: ./frontend
    container_name: react-app
    restart: unless-stopped
    environment:
      - REACT_APP_API_URL=http://localhost:8000
      - REACT_APP_API_KEY=frontend-key-abc123xyz
    ports:
      - "3000:3000"
    networks:
      - kong-net
    depends_on:
      - kong

  # ===== PROMETHEUS (MÉTRIQUES) =====
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    restart: unless-stopped
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
    ports:
      - "9090:9090"
    networks:
      - kong-net

  # ===== GRAFANA (DASHBOARDS) =====
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    restart: unless-stopped
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD:-admin}
      - GF_INSTALL_PLUGINS=grafana-piechart-panel
    volumes:
      - grafana_data:/var/lib/grafana
      - ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards
    ports:
      - "3001:3000"
    networks:
      - kong-net
    depends_on:
      - prometheus

networks:
  kong-net:
    driver: bridge

volumes:
  kong_data:
  prometheus_data:
  grafana_data:
DOCKERCOMPOSE


# === ÉTAPE 3: FICHIER .ENV ===

cat > .env << 'ENVFILE'
# Kong Database
KONG_DB_PASSWORD=super-secret-kong-password

# Stripe (simulation)
STRIPE_API_KEY=sk_test_fake_key_for_testing

# Email SMTP
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=noreply@ecommerce-app.com
SMTP_PASSWORD=your-email-password

# Grafana
GRAFANA_PASSWORD=admin123
ENVFILE


# ============================================================================
# PARTIE 5: MICROSERVICE 1 - USERS SERVICE
# ============================================================================

# === SERVICE USERS: GESTION DES UTILISATEURS ===

# Fonctionnalités:
# - Inscription (POST /users)
# - Login (POST /auth/login)
# - Profil utilisateur (GET /users/{id})
# - Mise à jour profil (PUT /users/{id})
# - Liste utilisateurs (GET /users) - Admin uniquement

# === app.py ===

cat > services/users/app.py << 'USERSAPP'
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
from werkzeug.security import generate_password_hash, check_password_hash
import jwt
import datetime
import os
from functools import wraps

# ===== CONFIGURATION =====
app = Flask(__name__)
CORS(app)

# Configuration de la base de données
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'sqlite:///database.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = 'super-secret-key-change-in-production'

db = SQLAlchemy(app)


# ===== MODÈLES =====

class User(db.Model):
    """Modèle Utilisateur"""
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password_hash = db.Column(db.String(255), nullable=False)
    first_name = db.Column(db.String(50))
    last_name = db.Column(db.String(50))
    phone = db.Column(db.String(20))
    address = db.Column(db.Text)
    role = db.Column(db.String(20), default='user')  # user, admin
    created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)

    def set_password(self, password):
        """Hasher le mot de passe"""
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        """Vérifier le mot de passe"""
        return check_password_hash(self.password_hash, password)

    def to_dict(self, include_sensitive=False):
        """Convertir en dictionnaire"""
        data = {
            'id': self.id,
            'username': self.username,
            'email': self.email,
            'first_name': self.first_name,
            'last_name': self.last_name,
            'phone': self.phone,
            'address': self.address,
            'role': self.role,
            'created_at': self.created_at.isoformat() if self.created_at else None,
            'updated_at': self.updated_at.isoformat() if self.updated_at else None
        }
        if include_sensitive:
            data['password_hash'] = self.password_hash
        return data


# ===== DÉCORATEURS D'AUTHENTIFICATION =====

def token_required(f):
    """Décorateur pour vérifier le JWT token"""
    @wraps(f)
    def decorated(*args, **kwargs):
        token = None
        
        # Le token peut venir de différentes sources
        if 'Authorization' in request.headers:
            auth_header = request.headers['Authorization']
            try:
                token = auth_header.split(" ")[1]  # "Bearer <token>"
            except IndexError:
                return jsonify({'error': 'Token format invalide'}), 401
        
        if not token:
            return jsonify({'error': 'Token manquant'}), 401

        try:
            data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"])
            current_user = User.query.get(data['user_id'])
            if not current_user:
                return jsonify({'error': 'Utilisateur introuvable'}), 401
        except jwt.ExpiredSignatureError:
            return jsonify({'error': 'Token expiré'}), 401
        except jwt.InvalidTokenError:
            return jsonify({'error': 'Token invalide'}), 401

        return f(current_user, *args, **kwargs)
    return decorated


def admin_required(f):
    """Décorateur pour vérifier que l'utilisateur est admin"""
    @wraps(f)
    @token_required
    def decorated(current_user, *args, **kwargs):
        if current_user.role != 'admin':
            return jsonify({'error': 'Accès refusé. Droits admin requis.'}), 403
        return f(current_user, *args, **kwargs)
    return decorated


# ===== ROUTES: SANTÉ & INFO =====

@app.route('/health', methods=['GET'])
def health():
    """Endpoint de santé pour Kong health checks"""
    return jsonify({
        'status': 'healthy',
        'service': 'users-service',
        'timestamp': datetime.datetime.utcnow().isoformat()
    }), 200


@app.route('/info', methods=['GET'])
def info():
    """Informations sur le service"""
    return jsonify({
        'service': 'users-service',
        'version': '1.0.0',
        'endpoints': {
            'POST /users': 'Créer un utilisateur',
            'POST /auth/login': 'Se connecter',
            'GET /users': 'Liste des utilisateurs (admin)',
            'GET /users/<id>': 'Détails utilisateur',
            'PUT /users/<id>': 'Modifier utilisateur',
            'DELETE /users/<id>': 'Supprimer utilisateur (admin)'
        }
    }), 200


# ===== ROUTES: AUTHENTIFICATION =====

@app.route('/users', methods=['POST'])
def create_user():
    """
    Créer un nouvel utilisateur (inscription)
    
    Body JSON:
    {
        "username": "john_doe",
        "email": "john@example.com",
        "password": "securepassword123",
        "first_name": "John",
        "last_name": "Doe",
        "phone": "+1234567890",
        "address": "123 Main St, City"
    }
    """
    data = request.get_json()

    # Validation des champs requis
    required_fields = ['username', 'email', 'password']
    for field in required_fields:
        if field not in data:
            return jsonify({'error': f'Champ requis manquant: {field}'}), 400

    # Vérifier si l'utilisateur existe déjà
    if User.query.filter_by(username=data['username']).first():
        return jsonify({'error': 'Username déjà utilisé'}), 409
    
    if User.query.filter_by(email=data['email']).first():
        return jsonify({'error': 'Email déjà utilisé'}), 409

    # Créer le nouvel utilisateur
    user = User(
        username=data['username'],
        email=data['email'],
        first_name=data.get('first_name'),
        last_name=data.get('last_name'),
        phone=data.get('phone'),
        address=data.get('address'),
        role='user'  # Par défaut, rôle user
    )
    user.set_password(data['password'])

    db.session.add(user)
    db.session.commit()

    # Générer un JWT token
    token = jwt.encode({
        'user_id': user.id,
        'username': user.username,
        'role': user.role,
        'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=24)
    }, app.config['SECRET_KEY'], algorithm="HS256")

    return jsonify({
        'message': 'Utilisateur créé avec succès',
        'user': user.to_dict(),
        'token': token
    }), 201


@app.route('/auth/login', methods=['POST'])
def login():
    """
    Se connecter
    
    Body JSON:
    {
        "username": "john_doe",
        "password": "securepassword123"
    }
    """
    data = request.get_json()

    if not data or not data.get('username') or not data.get('password'):
        return jsonify({'error': 'Username et password requis'}), 400

    user = User.query.filter_by(username=data['username']).first()

    if not user or not user.check_password(data['password']):
        return jsonify({'error': 'Identifiants invalides'}), 401

    # Générer un JWT token
    token = jwt.encode({
        'user_id': user.id,
        'username': user.username,
        'role': user.role,
        'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=24)
    }, app.config['SECRET_KEY'], algorithm="HS256")

    return jsonify({
        'message': 'Connexion réussie',
        'user': user.to_dict(),
        'token': token
    }), 200


# ===== ROUTES: GESTION UTILISATEURS =====

@app.route('/users', methods=['GET'])
@admin_required
def get_users(current_user):
    """
    Liste de tous les utilisateurs (admin uniquement)
    
    Query params:
    - page: numéro de page (défaut: 1)
    - per_page: résultats par page (défaut: 10)
    - role: filtrer par rôle (user, admin)
    """
    page = request.args.get('page', 1, type=int)
    per_page = request.args.get('per_page', 10, type=int)
    role_filter = request.args.get('role')

    query = User.query

    if role_filter:
        query = query.filter_by(role=role_filter)

    pagination = query.paginate(page=page, per_page=per_page, error_out=False)

    return jsonify({
        'users': [user.to_dict() for user in pagination.items],
        'total': pagination.total,
        'page': pagination.page,
        'pages': pagination.pages,
        'per_page': pagination.per_page
    }), 200


@app.route('/users/<int:user_id>', methods=['GET'])
@token_required
def get_user(current_user, user_id):
    """
    Obtenir les détails d'un utilisateur
    
    Un utilisateur peut voir son propre profil.
    Un admin peut voir n'importe quel profil.
    """
    user = User.query.get(user_id)

    if not user:
        return jsonify({'error': 'Utilisateur introuvable'}), 404

    # Vérifier les permissions
    if current_user.id != user_id and current_user.role != 'admin':
        return jsonify({'error': 'Accès refusé'}), 403

    return jsonify({'user': user.to_dict()}), 200


@app.route('/users/<int:user_id>', methods=['PUT'])
@token_required
def update_user(current_user, user_id):
    """
    Modifier un utilisateur
    
    Body JSON (tous les champs sont optionnels):
    {
        "first_name": "Jane",
        "last_name": "Smith",
        "phone": "+9876543210",
        "address": "456 New St, City"
    }
    """
    user = User.query.get(user_id)

    if not user:
        return jsonify({'error': 'Utilisateur introuvable'}), 404

    # Vérifier les permissions
    if current_user.id != user_id and current_user.role != 'admin':
        return jsonify({'error': 'Accès refusé'}), 403

    data = request.get_json()

    # Mettre à jour les champs fournis
    if 'first_name' in data:
        user.first_name = data['first_name']
    if 'last_name' in data:
        user.last_name = data['last_name']
    if 'phone' in data:
        user.phone = data['phone']
    if 'address' in data:
        user.address = data['address']
    
    # Seul un admin peut changer le rôle
    if 'role' in data and current_user.role == 'admin':
        user.role = data['role']

    db.session.commit()

    return jsonify({
        'message': 'Utilisateur mis à jour',
        'user': user.to_dict()
    }), 200


@app.route('/users/<int:user_id>', methods=['DELETE'])
@admin_required
def delete_user(current_user, user_id):
    """
    Supprimer un utilisateur (admin uniquement)
    """
    user = User.query.get(user_id)

    if not user:
        return jsonify({'error': 'Utilisateur introuvable'}), 404

    db.session.delete(user)
    db.session.commit()

    return jsonify({'message': 'Utilisateur supprimé'}), 200


# ===== GESTION DES ERREURS =====

@app.errorhandler(404)
def not_found(error):
    return jsonify({'error': 'Resource not found'}), 404


@app.errorhandler(500)
def internal_error(error):
    db.session.rollback()
    return jsonify({'error': 'Internal server error'}), 500


# ===== INITIALISATION =====

with app.app_context():
    db.create_all()
    
    # Créer un utilisateur admin par défaut si aucun n'existe
    if not User.query.filter_by(role='admin').first():
        admin = User(
            username='admin',
            email='admin@ecommerce.com',
            first_name='Admin',
            last_name='User',
            role='admin'
        )
        admin.set_password('admin123')
        db.session.add(admin)
        db.session.commit()
        print("[OK] Admin user created: admin / admin123")


# ===== LANCEMENT =====

if __name__ == '__main__':
    app.run(
        host='0.0.0.0',
        port=int(os.getenv('SERVICE_PORT', 4001)),
        debug=True
    )
USERSAPP


# === requirements.txt ===

cat > services/users/requirements.txt << 'USERSREQ'
Flask==3.0.0
Flask-SQLAlchemy==3.1.1
Flask-CORS==4.0.0
PyJWT==2.8.0
Werkzeug==3.0.1
USERSREQ


# === Dockerfile ===

cat > services/users/Dockerfile << 'USERSDOCKER'
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 4001

CMD ["python", "app.py"]
USERSDOCKER


# ============================================================================
# PARTIE 6: MICROSERVICE 2 - PRODUCTS SERVICE
# ============================================================================

# === SERVICE PRODUCTS: GESTION DU CATALOGUE ===

# Fonctionnalités:
# - Liste produits (GET /products)
# - Détails produit (GET /products/{id})
# - Recherche (GET /products/search?q=...)
# - Créer produit (POST /products) - Admin
# - Modifier produit (PUT /products/{id}) - Admin
# - Supprimer produit (DELETE /products/{id}) - Admin

cat > services/products/app.py << 'PRODUCTSAPP'
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
import datetime
import os

# ===== CONFIGURATION =====
app = Flask(__name__)
CORS(app)

app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'sqlite:///database.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)


# ===== MODÈLES =====

class Product(db.Model):
    """Modèle Produit"""
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(200), nullable=False)
    description = db.Column(db.Text)
    price = db.Column(db.Float, nullable=False)
    category = db.Column(db.String(100))
    stock = db.Column(db.Integer, default=0)
    image_url = db.Column(db.String(500))
    is_active = db.Column(db.Boolean, default=True)
    created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)

    def to_dict(self):
        """Convertir en dictionnaire"""
        return {
            'id': self.id,
            'name': self.name,
            'description': self.description,
            'price': self.price,
            'category': self.category,
            'stock': self.stock,
            'image_url': self.image_url,
            'is_active': self.is_active,
            'created_at': self.created_at.isoformat() if self.created_at else None,
            'updated_at': self.updated_at.isoformat() if self.updated_at else None
        }


# ===== ROUTES: SANTÉ & INFO =====

@app.route('/health', methods=['GET'])
def health():
    """Endpoint de santé"""
    return jsonify({
        'status': 'healthy',
        'service': 'products-service',
        'timestamp': datetime.datetime.utcnow().isoformat()
    }), 200


@app.route('/info', methods=['GET'])
def info():
    """Informations sur le service"""
    return jsonify({
        'service': 'products-service',
        'version': '1.0.0',
        'endpoints': {
            'GET /products': 'Liste des produits',
            'GET /products/<id>': 'Détails produit',
            'GET /products/search': 'Recherche produits',
            'POST /products': 'Créer produit (admin)',
            'PUT /products/<id>': 'Modifier produit (admin)',
            'DELETE /products/<id>': 'Supprimer produit (admin)'
        }
    }), 200


# ===== ROUTES: PRODUITS =====

@app.route('/products', methods=['GET'])
def get_products():
    """
    Liste de tous les produits
    
    Query params:
    - page: numéro de page (défaut: 1)
    - per_page: résultats par page (défaut: 20)
    - category: filtrer par catégorie
    - active_only: seulement les produits actifs (défaut: true)
    """
    page = request.args.get('page', 1, type=int)
    per_page = request.args.get('per_page', 20, type=int)
    category = request.args.get('category')
    active_only = request.args.get('active_only', 'true').lower() == 'true'

    query = Product.query

    if active_only:
        query = query.filter_by(is_active=True)

    if category:
        query = query.filter_by(category=category)

    pagination = query.order_by(Product.created_at.desc()).paginate(
        page=page, per_page=per_page, error_out=False
    )

    return jsonify({
        'products': [product.to_dict() for product in pagination.items],
        'total': pagination.total,
        'page': pagination.page,
        'pages': pagination.pages,
        'per_page': pagination.per_page
    }), 200


@app.route('/products/<int:product_id>', methods=['GET'])
def get_product(product_id):
    """Obtenir les détails d'un produit"""
    product = Product.query.get(product_id)

    if not product:
        return jsonify({'error': 'Produit introuvable'}), 404

    return jsonify({'product': product.to_dict()}), 200


@app.route('/products/search', methods=['GET'])
def search_products():
    """
    Rechercher des produits
    
    Query params:
    - q: terme de recherche
    - min_price: prix minimum
    - max_price: prix maximum
    - category: catégorie
    """
    search_term = request.args.get('q', '')
    min_price = request.args.get('min_price', type=float)
    max_price = request.args.get('max_price', type=float)
    category = request.args.get('category')

    query = Product.query.filter_by(is_active=True)

    if search_term:
        query = query.filter(
            db.or_(
                Product.name.ilike(f'%{search_term}%'),
                Product.description.ilike(f'%{search_term}%')
            )
        )

    if min_price is not None:
        query = query.filter(Product.price >= min_price)

    if max_price is not None:
        query = query.filter(Product.price <= max_price)

    if category:
        query = query.filter_by(category=category)

    products = query.all()

    return jsonify({
        'products': [product.to_dict() for product in products],
        'count': len(products)
    }), 200


@app.route('/products', methods=['POST'])
def create_product():
    """
    Créer un nouveau produit (admin)
    
    Body JSON:
    {
        "name": "iPhone 15 Pro",
        "description": "Latest iPhone model",
        "price": 999.99,
        "category": "Electronics",
        "stock": 50,
        "image_url": "https://example.com/iphone.jpg"
    }
    """
    data = request.get_json()

    # Validation
    required_fields = ['name', 'price']
    for field in required_fields:
        if field not in data:
            return jsonify({'error': f'Champ requis manquant: {field}'}), 400

    product = Product(
        name=data['name'],
        description=data.get('description'),
        price=data['price'],
        category=data.get('category'),
        stock=data.get('stock', 0),
        image_url=data.get('image_url'),
        is_active=data.get('is_active', True)
    )

    db.session.add(product)
    db.session.commit()

    return jsonify({
        'message': 'Produit créé',
        'product': product.to_dict()
    }), 201


@app.route('/products/<int:product_id>', methods=['PUT'])
def update_product(product_id):
    """Modifier un produit (admin)"""
    product = Product.query.get(product_id)

    if not product:
        return jsonify({'error': 'Produit introuvable'}), 404

    data = request.get_json()

    # Mettre à jour les champs fournis
    if 'name' in data:
        product.name = data['name']
    if 'description' in data:
        product.description = data['description']
    if 'price' in data:
        product.price = data['price']
    if 'category' in data:
        product.category = data['category']
    if 'stock' in data:
        product.stock = data['stock']
    if 'image_url' in data:
        product.image_url = data['image_url']
    if 'is_active' in data:
        product.is_active = data['is_active']

    db.session.commit()

    return jsonify({
        'message': 'Produit mis à jour',
        'product': product.to_dict()
    }), 200


@app.route('/products/<int:product_id>', methods=['DELETE'])
def delete_product(product_id):
    """Supprimer un produit (admin)"""
    product = Product.query.get(product_id)

    if not product:
        return jsonify({'error': 'Produit introuvable'}), 404

    db.session.delete(product)
    db.session.commit()

    return jsonify({'message': 'Produit supprimé'}), 200


@app.route('/categories', methods=['GET'])
def get_categories():
    """Liste de toutes les catégories"""
    categories = db.session.query(Product.category).distinct().all()
    return jsonify({
        'categories': [cat[0] for cat in categories if cat[0]]
    }), 200


# ===== INITIALISATION =====

with app.app_context():
    db.create_all()
    
    # Créer des produits de test si aucun n'existe
    if Product.query.count() == 0:
        sample_products = [
            Product(name='iPhone 15 Pro', description='Latest iPhone', price=999.99, category='Electronics', stock=50, image_url='https://via.placeholder.com/300x300?text=iPhone'),
            Product(name='MacBook Pro 16"', description='Powerful laptop', price=2499.99, category='Electronics', stock=30, image_url='https://via.placeholder.com/300x300?text=MacBook'),
            Product(name='AirPods Pro', description='Wireless earbuds', price=249.99, category='Electronics', stock=100, image_url='https://via.placeholder.com/300x300?text=AirPods'),
            Product(name='Nike Air Max', description='Running shoes', price=129.99, category='Shoes', stock=75, image_url='https://via.placeholder.com/300x300?text=Nike'),
            Product(name='Adidas Ultraboost', description='Comfortable shoes', price=179.99, category='Shoes', stock=60, image_url='https://via.placeholder.com/300x300?text=Adidas'),
        ]
        db.session.add_all(sample_products)
        db.session.commit()
        print("[OK] Sample products created")


# ===== LANCEMENT =====

if __name__ == '__main__':
    app.run(
        host='0.0.0.0',
        port=int(os.getenv('SERVICE_PORT', 4002)),
        debug=True
    )
PRODUCTSAPP


# === requirements.txt & Dockerfile (identiques à users) ===

cat > services/products/requirements.txt << 'PRODREQ'
Flask==3.0.0
Flask-SQLAlchemy==3.1.1
Flask-CORS==4.0.0
PRODREQ

cat > services/products/Dockerfile << 'PRODDOCKER'
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 4002
CMD ["python", "app.py"]
PRODDOCKER


# ============================================================================
# PARTIE 7: MICROSERVICE 3 - ORDERS SERVICE
# ============================================================================

# Ce service communique avec les autres services (inter-service communication)

cat > services/orders/app.py << 'ORDERSAPP'
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
import datetime
import os
import requests

# ===== CONFIGURATION =====
app = Flask(__name__)
CORS(app)

app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'sqlite:///database.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

# URLs des autres services
PAYMENTS_SERVICE_URL = os.getenv('PAYMENTS_SERVICE_URL', 'http://payments-service:4004')
NOTIFICATIONS_SERVICE_URL = os.getenv('NOTIFICATIONS_SERVICE_URL', 'http://notifications-service:4005')


# ===== MODÈLES =====

class Order(db.Model):
    """Modèle Commande"""
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, nullable=False)
    total_amount = db.Column(db.Float, nullable=False)
    status = db.Column(db.String(50), default='pending')  # pending, paid, shipped, delivered, cancelled
    payment_id = db.Column(db.String(100))
    shipping_address = db.Column(db.Text)
    created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)

    items = db.relationship('OrderItem', backref='order', lazy=True, cascade='all, delete-orphan')

    def to_dict(self):
        return {
            'id': self.id,
            'user_id': self.user_id,
            'total_amount': self.total_amount,
            'status': self.status,
            'payment_id': self.payment_id,
            'shipping_address': self.shipping_address,
            'items': [item.to_dict() for item in self.items],
            'created_at': self.created_at.isoformat() if self.created_at else None,
            'updated_at': self.updated_at.isoformat() if self.updated_at else None
        }


class OrderItem(db.Model):
    """Modèle Item de commande"""
    id = db.Column(db.Integer, primary_key=True)
    order_id = db.Column(db.Integer, db.ForeignKey('order.id'), nullable=False)
    product_id = db.Column(db.Integer, nullable=False)
    product_name = db.Column(db.String(200))
    quantity = db.Column(db.Integer, nullable=False)
    price = db.Column(db.Float, nullable=False)

    def to_dict(self):
        return {
            'id': self.id,
            'product_id': self.product_id,
            'product_name': self.product_name,
            'quantity': self.quantity,
            'price': self.price,
            'subtotal': self.quantity * self.price
        }


# ===== ROUTES: SANTÉ & INFO =====

@app.route('/health', methods=['GET'])
def health():
    return jsonify({
        'status': 'healthy',
        'service': 'orders-service',
        'timestamp': datetime.datetime.utcnow().isoformat()
    }), 200


# ===== ROUTES: COMMANDES =====

@app.route('/orders', methods=['GET'])
def get_orders():
    """
    Liste des commandes
    
    Query params:
    - user_id: filtrer par utilisateur
    - status: filtrer par statut
    """
    user_id = request.args.get('user_id', type=int)
    status_filter = request.args.get('status')

    query = Order.query

    if user_id:
        query = query.filter_by(user_id=user_id)

    if status_filter:
        query = query.filter_by(status=status_filter)

    orders = query.order_by(Order.created_at.desc()).all()

    return jsonify({
        'orders': [order.to_dict() for order in orders],
        'count': len(orders)
    }), 200


@app.route('/orders/<int:order_id>', methods=['GET'])
def get_order(order_id):
    """Détails d'une commande"""
    order = Order.query.get(order_id)

    if not order:
        return jsonify({'error': 'Commande introuvable'}), 404

    return jsonify({'order': order.to_dict()}), 200


@app.route('/orders', methods=['POST'])
def create_order():
    """
    Créer une nouvelle commande
    
    Body JSON:
    {
        "user_id": 1,
        "items": [
            {"product_id": 1, "product_name": "iPhone", "quantity": 1, "price": 999.99},
            {"product_id": 2, "product_name": "AirPods", "quantity": 2, "price": 249.99}
        ],
        "shipping_address": "123 Main St, City, Country"
    }
    """
    data = request.get_json()

    # Validation
    required_fields = ['user_id', 'items', 'shipping_address']
    for field in required_fields:
        if field not in data:
            return jsonify({'error': f'Champ requis manquant: {field}'}), 400

    if not data['items']:
        return jsonify({'error': 'La commande doit contenir au moins un article'}), 400

    # Calculer le total
    total_amount = sum(item['quantity'] * item['price'] for item in data['items'])

    # Créer la commande
    order = Order(
        user_id=data['user_id'],
        total_amount=total_amount,
        shipping_address=data['shipping_address'],
        status='pending'
    )

    # Ajouter les items
    for item_data in data['items']:
        item = OrderItem(
            product_id=item_data['product_id'],
            product_name=item_data['product_name'],
            quantity=item_data['quantity'],
            price=item_data['price']
        )
        order.items.append(item)

    db.session.add(order)
    db.session.commit()

    # COMMUNICATION INTER-SERVICE: Envoyer notification
    try:
        notification_data = {
            'type': 'order_created',
            'user_id': order.user_id,
            'order_id': order.id,
            'total_amount': order.total_amount
        }
        requests.post(
            f'{NOTIFICATIONS_SERVICE_URL}/notifications',
            json=notification_data,
            timeout=5
        )
    except Exception as e:
        print(f"[ATTENTION] Failed to send notification: {e}")

    return jsonify({
        'message': 'Commande créée',
        'order': order.to_dict()
    }), 201


@app.route('/orders/<int:order_id>/pay', methods=['POST'])
def pay_order(order_id):
    """
    Payer une commande
    
    Body JSON:
    {
        "payment_method": "card",
        "card_number": "4242424242424242",
        "card_exp": "12/25",
        "card_cvc": "123"
    }
    """
    order = Order.query.get(order_id)

    if not order:
        return jsonify({'error': 'Commande introuvable'}), 404

    if order.status != 'pending':
        return jsonify({'error': f'Commande déjà {order.status}'}), 400

    data = request.get_json()

    # COMMUNICATION INTER-SERVICE: Traiter le paiement
    try:
        payment_response = requests.post(
            f'{PAYMENTS_SERVICE_URL}/payments',
            json={
                'order_id': order.id,
                'amount': order.total_amount,
                'payment_method': data.get('payment_method', 'card'),
                'card_number': data.get('card_number'),
                'card_exp': data.get('card_exp'),
                'card_cvc': data.get('card_cvc')
            },
            timeout=10
        )

        payment_data = payment_response.json()

        if payment_response.status_code == 200 and payment_data.get('status') == 'success':
            # Paiement réussi
            order.status = 'paid'
            order.payment_id = payment_data.get('payment_id')
            db.session.commit()

            # Envoyer notification de paiement réussi
            try:
                requests.post(
                    f'{NOTIFICATIONS_SERVICE_URL}/notifications',
                    json={
                        'type': 'payment_success',
                        'user_id': order.user_id,
                        'order_id': order.id,
                        'payment_id': order.payment_id
                    },
                    timeout=5
                )
            except Exception as e:
                print(f"[ATTENTION] Failed to send notification: {e}")

            return jsonify({
                'message': 'Paiement réussi',
                'order': order.to_dict(),
                'payment': payment_data
            }), 200
        else:
            # Paiement échoué
            return jsonify({
                'error': 'Paiement échoué',
                'details': payment_data.get('error', 'Unknown error')
            }), 400

    except requests.exceptions.RequestException as e:
        return jsonify({
            'error': 'Service de paiement indisponible',
            'details': str(e)
        }), 503


@app.route('/orders/<int:order_id>/cancel', methods=['POST'])
def cancel_order(order_id):
    """Annuler une commande"""
    order = Order.query.get(order_id)

    if not order:
        return jsonify({'error': 'Commande introuvable'}), 404

    if order.status in ['shipped', 'delivered']:
        return jsonify({'error': 'Impossible d\'annuler une commande déjà expédiée'}), 400

    order.status = 'cancelled'
    db.session.commit()

    return jsonify({
        'message': 'Commande annulée',
        'order': order.to_dict()
    }), 200


# ===== INITIALISATION =====

with app.app_context():
    db.create_all()
    print("[OK] Orders database initialized")


# ===== LANCEMENT =====

if __name__ == '__main__':
    app.run(
        host='0.0.0.0',
        port=int(os.getenv('SERVICE_PORT', 4003)),
        debug=True
    )
ORDERSAPP


cat > services/orders/requirements.txt << 'ORDREQ'
Flask==3.0.0
Flask-SQLAlchemy==3.1.1
Flask-CORS==4.0.0
requests==2.31.0
ORDREQ

cat > services/orders/Dockerfile << 'ORDDOCKER'
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 4003
CMD ["python", "app.py"]
ORDDOCKER


# ============================================================================
# PARTIE 8: MICROSERVICE 4 - PAYMENTS SERVICE
# ============================================================================

cat > services/payments/app.py << 'PAYMENTSAPP'
from flask import Flask, request, jsonify
from flask_cors import CORS
import datetime
import os
import random
import string

# ===== CONFIGURATION =====
app = Flask(__name__)
CORS(app)

STRIPE_API_KEY = os.getenv('STRIPE_API_KEY', 'sk_test_fake')


# ===== SIMULATION STRIPE =====

def generate_payment_id():
    """Générer un ID de paiement simulé"""
    return 'pi_' + ''.join(random.choices(string.ascii_lowercase + string.digits, k=24))


def simulate_payment(amount, payment_method, card_number=None):
    """
    Simuler un paiement Stripe
    
    Règles de simulation:
    - Carte 4242424242424242 -> succès
    - Carte 4000000000000002 -> décliné
    - Autres cartes -> succès aléatoire (80% de chances)
    """
    # Simuler un délai de traitement
    import time
    time.sleep(1)

    # Vérifier la carte
    if card_number == '4242424242424242':
        # Carte de test Stripe -> toujours succès
        return {
            'status': 'success',
            'payment_id': generate_payment_id(),
            'amount': amount,
            'currency': 'usd',
            'message': 'Paiement réussi'
        }
    elif card_number == '4000000000000002':
        # Carte déclinée
        return {
            'status': 'failed',
            'error': 'Carte déclinée',
            'decline_code': 'insufficient_funds'
        }
    else:
        # Succès aléatoire (80%)
        if random.random() < 0.8:
            return {
                'status': 'success',
                'payment_id': generate_payment_id(),
                'amount': amount,
                'currency': 'usd',
                'message': 'Paiement réussi'
            }
        else:
            return {
                'status': 'failed',
                'error': 'Erreur de traitement',
                'decline_code': 'processing_error'
            }


# ===== ROUTES =====

@app.route('/health', methods=['GET'])
def health():
    return jsonify({
        'status': 'healthy',
        'service': 'payments-service',
        'timestamp': datetime.datetime.utcnow().isoformat()
    }), 200


@app.route('/payments', methods=['POST'])
def process_payment():
    """
    Traiter un paiement
    
    Body JSON:
    {
        "order_id": 123,
        "amount": 999.99,
        "payment_method": "card",
        "card_number": "4242424242424242",
        "card_exp": "12/25",
        "card_cvc": "123"
    }
    """
    data = request.get_json()

    # Validation
    required_fields = ['order_id', 'amount', 'payment_method']
    for field in required_fields:
        if field not in data:
            return jsonify({'error': f'Champ requis manquant: {field}'}), 400

    if data['payment_method'] == 'card':
        if not data.get('card_number'):
            return jsonify({'error': 'Numéro de carte requis'}), 400

    # Simuler le paiement
    result = simulate_payment(
        amount=data['amount'],
        payment_method=data['payment_method'],
        card_number=data.get('card_number')
    )

    if result['status'] == 'success':
        return jsonify(result), 200
    else:
        return jsonify(result), 400


@app.route('/payments/<payment_id>', methods=['GET'])
def get_payment(payment_id):
    """Obtenir les détails d'un paiement (simulation)"""
    return jsonify({
        'payment_id': payment_id,
        'status': 'completed',
        'amount': 999.99,
        'currency': 'usd',
        'created_at': datetime.datetime.utcnow().isoformat()
    }), 200


@app.route('/payments/<payment_id>/refund', methods=['POST'])
def refund_payment(payment_id):
    """Rembourser un paiement (simulation)"""
    return jsonify({
        'refund_id': 're_' + ''.join(random.choices(string.ascii_lowercase + string.digits, k=24)),
        'payment_id': payment_id,
        'status': 'succeeded',
        'amount': 999.99,
        'message': 'Remboursement traité'
    }), 200


# ===== LANCEMENT =====

if __name__ == '__main__':
    app.run(
        host='0.0.0.0',
        port=int(os.getenv('SERVICE_PORT', 4004)),
        debug=True
    )
PAYMENTSAPP


cat > services/payments/requirements.txt << 'PAYREQ'
Flask==3.0.0
Flask-CORS==4.0.0
PAYREQ

cat > services/payments/Dockerfile << 'PAYDOCKER'
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 4004
CMD ["python", "app.py"]
PAYDOCKER


# ============================================================================
# PARTIE 9: MICROSERVICE 5 - NOTIFICATIONS SERVICE
# ============================================================================

cat > services/notifications/app.py << 'NOTIFAPP'
from flask import Flask, request, jsonify
from flask_cors import CORS
import datetime
import os

# ===== CONFIGURATION =====
app = Flask(__name__)
CORS(app)

SMTP_HOST = os.getenv('SMTP_HOST', 'smtp.gmail.com')
SMTP_PORT = int(os.getenv('SMTP_PORT', 587))
SMTP_USER = os.getenv('SMTP_USER', 'noreply@example.com')
SMTP_PASSWORD = os.getenv('SMTP_PASSWORD', 'password')


# ===== SIMULATION EMAIL =====

def send_email_simulation(to_email, subject, body):
    """
    Simuler l'envoi d'un email
    En production, utiliser smtplib ou un service comme SendGrid
    """
    print(f"""
    [EMAIL] EMAIL SENT
    To: {to_email}
    Subject: {subject}
    Body: {body}
    """)
    return True


# ===== ROUTES =====

@app.route('/health', methods=['GET'])
def health():
    return jsonify({
        'status': 'healthy',
        'service': 'notifications-service',
        'timestamp': datetime.datetime.utcnow().isoformat()
    }), 200


@app.route('/notifications', methods=['POST'])
def send_notification():
    """
    Envoyer une notification
    
    Body JSON:
    {
        "type": "order_created" | "payment_success" | "order_shipped",
        "user_id": 1,
        "order_id": 123,
        "email": "user@example.com",
        "data": {...}
    }
    """
    data = request.get_json()

    notification_type = data.get('type')
    user_id = data.get('user_id')
    email = data.get('email', f'user{user_id}@example.com')

    if notification_type == 'order_created':
        subject = '[BRAVO] Commande créée avec succès'
        body = f"""
        Bonjour,
        
        Votre commande #{data.get('order_id')} a été créée avec succès.
        Montant total: ${data.get('total_amount', 0):.2f}
        
        Merci de votre confiance!
        """
        send_email_simulation(email, subject, body)

    elif notification_type == 'payment_success':
        subject = '[OK] Paiement confirmé'
        body = f"""
        Bonjour,
        
        Votre paiement pour la commande #{data.get('order_id')} a été confirmé.
        ID de paiement: {data.get('payment_id')}
        
        Votre commande sera bientôt expédiée!
        """
        send_email_simulation(email, subject, body)

    elif notification_type == 'order_shipped':
        subject = '[PACKAGE] Commande expédiée'
        body = f"""
        Bonjour,
        
        Votre commande #{data.get('order_id')} a été expédiée!
        Numéro de suivi: {data.get('tracking_number')}
        
        Livraison estimée: {data.get('estimated_delivery')}
        """
        send_email_simulation(email, subject, body)

    return jsonify({
        'message': 'Notification envoyée',
        'type': notification_type,
        'recipient': email
    }), 200


# ===== LANCEMENT =====

if __name__ == '__main__':
    app.run(
        host='0.0.0.0',
        port=int(os.getenv('SERVICE_PORT', 4005)),
        debug=True
    )
NOTIFAPP


cat > services/notifications/requirements.txt << 'NOTREQ'
Flask==3.0.0
Flask-CORS==4.0.0
NOTREQ

cat > services/notifications/Dockerfile << 'NOTDOCKER'
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 4005
CMD ["python", "app.py"]
NOTDOCKER


# ============================================================================
# PARTIE 10: CONFIGURATION KONG (kong.yml)
# ============================================================================

# Ce fichier configure TOUTES les fonctionnalités de Kong!

cat > kong.yml << 'KONGYML'
_format_version: "3.0"

# ============================================================================
# PARTIE A: SERVICES & ROUTES
# ============================================================================

services:
  # ===== SERVICE: USERS =====
  - name: users-service
    url: http://users-service:4001
    retries: 3
    connect_timeout: 60000
    write_timeout: 60000
    read_timeout: 60000
    
    routes:
      # Route publique: Inscription
      - name: users-register
        paths:
          - /api/users
        methods:
          - POST
        strip_path: false
        plugins:
          - name: cors
            config:
              origins:
                - http://localhost:3000
                - http://react-app:3000
              methods:
                - GET
                - POST
                - PUT
                - DELETE
                - OPTIONS
              headers:
                - Accept
                - Content-Type
                - Authorization
                - apikey
              credentials: true
          - name: rate-limiting
            config:
              minute: 5  # Max 5 inscriptions par minute par IP
              policy: local
          - name: request-size-limiting
            config:
              allowed_payload_size: 1  # Max 1 MB

      # Route publique: Login
      - name: users-login
        paths:
          - /api/auth/login
        methods:
          - POST
        strip_path: false
        plugins:
          - name: cors
            config:
              origins:
                - http://localhost:3000
              methods:
                - POST
              credentials: true
          - name: rate-limiting
            config:
              minute: 10  # Max 10 tentatives de login par minute
              policy: local

      # Routes protégées: Profil utilisateur
      - name: users-protected
        paths:
          - /api/users/~
        methods:
          - GET
          - PUT
          - DELETE
        strip_path: false
        plugins:
          - name: key-auth
            config:
              key_names:
                - apikey
          - name: cors
            config:
              origins:
                - http://localhost:3000
              credentials: true
          - name: rate-limiting
            config:
              minute: 60
              hour: 1000
              policy: local
          - name: request-transformer
            config:
              add:
                headers:
                  - X-Service-Name:users-service
                  - X-Request-Time:$(date)
          - name: response-transformer
            config:
              add:
                headers:
                  - X-Response-Time:$(date)
                  - X-Powered-By:Kong

  # ===== SERVICE: PRODUCTS (avec Load Balancing) =====
  - name: products-service
    host: products-upstream  # Pointe vers l'upstream
    port: 4002
    protocol: http
    
    routes:
      - name: products-list
        paths:
          - /api/products
        methods:
          - GET
        strip_path: false
        plugins:
          # Cache pour la liste des produits (5 minutes)
          - name: proxy-cache
            config:
              strategy: memory
              content_type:
                - application/json
              cache_ttl: 300
              cache_control: false
          - name: cors
            config:
              origins:
                - "*"  # Public pour le catalogue
              methods:
                - GET
              credentials: false
          - name: rate-limiting
            config:
              minute: 100
              hour: 5000
              policy: local

      - name: products-search
        paths:
          - /api/products/search
        methods:
          - GET
        strip_path: false
        plugins:
          - name: proxy-cache
            config:
              strategy: memory
              cache_ttl: 60  # Cache court pour la recherche
          - name: rate-limiting
            config:
              minute: 50
              policy: local

      - name: products-details
        paths:
          - /api/products/\d+
        methods:
          - GET
        strip_path: false
        plugins:
          - name: proxy-cache
            config:
              strategy: memory
              cache_ttl: 600  # Cache 10 minutes pour les détails

      # Routes admin (création, modification, suppression)
      - name: products-admin
        paths:
          - /api/products
          - /api/products/\d+
        methods:
          - POST
          - PUT
          - DELETE
        strip_path: false
        plugins:
          - name: key-auth
            config:
              key_names:
                - apikey
          - name: acl
            config:
              allow:
                - admin  # Seulement le groupe admin
          - name: rate-limiting
            config:
              minute: 30
              policy: local

  # ===== SERVICE: ORDERS =====
  - name: orders-service
    url: http://orders-service:4003
    
    routes:
      - name: orders-all
        paths:
          - /api/orders
        methods:
          - GET
          - POST
        strip_path: false
        plugins:
          - name: key-auth
            config:
              key_names:
                - apikey
          - name: cors
            config:
              origins:
                - http://localhost:3000
              credentials: true
          - name: rate-limiting
            config:
              minute: 20  # Limiter les créations de commandes
              hour: 100
              policy: local
          - name: request-transformer
            config:
              add:
                headers:
                  - X-Service-Name:orders-service
                  - X-Consumer-ID:$(kong.consumer.id)
                  - X-Consumer-Username:$(kong.consumer.username)
          # Log toutes les commandes
          - name: file-log
            config:
              path: /tmp/orders.log

      - name: orders-single
        paths:
          - /api/orders/\d+
        methods:
          - GET
        strip_path: false
        plugins:
          - name: key-auth
            config:
              key_names:
                - apikey
          - name: rate-limiting
            config:
              minute: 60
              policy: local

      - name: orders-pay
        paths:
          - /api/orders/\d+/pay
        methods:
          - POST
        strip_path: false
        plugins:
          - name: key-auth
            config:
              key_names:
                - apikey
          - name: rate-limiting
            config:
              minute: 5  # Limiter les tentatives de paiement
              policy: local
          - name: request-size-limiting
            config:
              allowed_payload_size: 1

  # ===== SERVICE: PAYMENTS (Interne uniquement) =====
  - name: payments-service
    url: http://payments-service:4004
    
    routes:
      - name: payments-internal
        paths:
          - /api/payments
        methods:
          - POST
          - GET
        strip_path: false
        plugins:
          # Restriction IP: Seulement depuis le réseau interne
          - name: ip-restriction
            config:
              allow:
                - 172.18.0.0/16  # Réseau Docker kong-net
          - name: rate-limiting
            config:
              minute: 30
              policy: local

  # ===== SERVICE: NOTIFICATIONS (Interne uniquement) =====
  - name: notifications-service
    url: http://notifications-service:4005
    
    routes:
      - name: notifications-internal
        paths:
          - /api/notifications
        methods:
          - POST
        strip_path: false
        plugins:
          - name: ip-restriction
            config:
              allow:
                - 172.18.0.0/16

# ============================================================================
# PARTIE B: UPSTREAMS (Load Balancing)
# ============================================================================

upstreams:
  - name: products-upstream
    algorithm: round-robin
    slots: 10000
    healthchecks:
      active:
        type: http
        http_path: /health
        healthy:
          interval: 10
          successes: 2
        unhealthy:
          interval: 5
          http_failures: 3
          timeouts: 3
    targets:
      - target: products-service:4002
        weight: 100
      # Si tu as plusieurs instances:
      # - target: products-service-2:4002
      #   weight: 100
      # - target: products-service-3:4002
      #   weight: 50

# ============================================================================
# PARTIE C: CONSUMERS (Clients)
# ============================================================================

consumers:
  # ===== FRONTEND REACT =====
  - username: react-frontend
    custom_id: react-frontend
    keyauth_credentials:
      - key: frontend-key-abc123xyz
    acls:
      - group: user
    plugins:
      - name: rate-limiting
        config:
          minute: 100
          hour: 5000
          policy: local

  # ===== APPLICATION MOBILE =====
  - username: mobile-app
    custom_id: mobile-app
    keyauth_credentials:
      - key: mobile-key-xyz789abc
    acls:
      - group: user
    plugins:
      - name: rate-limiting
        config:
          minute: 60
          hour: 3000
          policy: local

  # ===== ADMIN BACKEND =====
  - username: admin-backend
    custom_id: admin-backend
    keyauth_credentials:
      - key: admin-key-super-secret-123
    acls:
      - group: admin
    # Pas de rate limiting pour l'admin

  # ===== BOT DE TEST =====
  - username: test-bot
    custom_id: test-bot
    keyauth_credentials:
      - key: test-bot-key-456
    acls:
      - group: user
    plugins:
      - name: rate-limiting
        config:
          minute: 10
          hour: 100
          policy: local

# ============================================================================
# PARTIE D: PLUGINS GLOBAUX
# ============================================================================

plugins:
  # ===== MONITORING: PROMETHEUS =====
  - name: prometheus
    config:
      status_code_metrics: true
      latency_metrics: true
      bandwidth_metrics: true
      upstream_health_metrics: true

  # ===== LOGS: FILE LOG =====
  - name: file-log
    config:
      path: /tmp/kong-access.log
      reopen: true

  # ===== REQUEST ID (traçabilité) =====
  - name: correlation-id
    config:
      header_name: X-Request-ID
      generator: uuid
      echo_downstream: true

  # ===== BOT DETECTION =====
  - name: bot-detection
    config:
      allow:
        - googlebot
        - bingbot
        - facebookexternalhit
      deny:
        - baidu

  # ===== GLOBAL CORS (fallback) =====
  - name: cors
    config:
      origins:
        - http://localhost:3000
        - http://react-app:3000
      methods:
        - GET
        - POST
        - PUT
        - DELETE
        - OPTIONS
      headers:
        - Accept
        - Content-Type
        - Authorization
        - apikey
      credentials: true
      max_age: 3600

  # ===== RESPONSE TRANSFORMER =====
  - name: response-transformer
    config:
      add:
        headers:
          - X-Kong-Gateway:Kong/3.5
          - X-Response-Time:$(date)
KONGYML


# ============================================================================
# PARTIE 11: MONITORING (Prometheus & Grafana)
# ============================================================================

cat > monitoring/prometheus.yml << 'PROMYML'
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  # Kong metrics
  - job_name: 'kong'
    static_configs:
      - targets: ['kong:8001']
    metrics_path: /metrics

  # Services metrics (si ils exportent des métriques)
  - job_name: 'users-service'
    static_configs:
      - targets: ['users-service:4001']
    metrics_path: /metrics

  - job_name: 'products-service'
    static_configs:
      - targets: ['products-service:4002']
    metrics_path: /metrics

  - job_name: 'orders-service'
    static_configs:
      - targets: ['orders-service:4003']
    metrics_path: /metrics
PROMYML


# ============================================================================
# PARTIE 12: FRONTEND REACT
# ============================================================================

# Cette section serait trop longue, voici les fichiers clés:

mkdir -p frontend/src/{api,components}

# Configuration API
cat > frontend/src/api/config.js << 'REACTCONFIG'
export const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
export const API_KEY = process.env.REACT_APP_API_KEY || 'frontend-key-abc123xyz';

export const apiRequest = async (endpoint, options = {}) => {
  const defaultOptions = {
    headers: {
      'Content-Type': 'application/json',
      'apikey': API_KEY,
      ...options.headers,
    },
  };

  const response = await fetch(`${API_URL}${endpoint}`, {
    ...options,
    ...defaultOptions,
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.error || 'Une erreur est survenue');
  }

  return response.json();
};
REACTCONFIG


# ============================================================================
# PARTIE 13: SCRIPTS DE DÉMARRAGE ET TESTS
# ============================================================================

cat > scripts/setup.sh << 'SETUPSH'
#!/bin/bash

echo "[RAPIDE] Installation de l'application E-commerce Microservices"
echo ""

# Vérifier Docker
if ! command -v docker &> /dev/null; then
    echo "[X] Docker n'est pas installé"
    exit 1
fi

if ! command -v docker-compose &> /dev/null; then
    echo "[X] Docker Compose n'est pas installé"
    exit 1
fi

echo "[OK] Docker et Docker Compose trouvés"
echo ""

# Créer les réseaux
echo "[RESEAU] Création du réseau Docker..."
docker network create kong-net 2>/dev/null || true

# Démarrer l'infrastructure
echo "[CONSTRUCTION]  Démarrage de l'infrastructure..."
docker-compose up -d kong-database kong-migration

echo "[HOURGLASS_WITH_FLOWING_SAND] Attente de la base de données..."
sleep 10

# Démarrer Kong
echo "[SORTIE] Démarrage de Kong Gateway..."
docker-compose up -d kong konga

echo "[HOURGLASS_WITH_FLOWING_SAND] Attente de Kong..."
sleep 10

# Charger la configuration Kong
echo "[CONFIG]  Configuration de Kong..."
curl -i -X POST http://localhost:8001/config \
  --form config=@kong.yml

# Démarrer les microservices
echo "[OUTIL] Démarrage des microservices..."
docker-compose up -d users-service products-service orders-service payments-service notifications-service

echo "[HOURGLASS_WITH_FLOWING_SAND] Attente des services..."
sleep 10

# Démarrer le frontend
echo "[DESIGN] Démarrage du frontend..."
docker-compose up -d react-app

# Démarrer le monitoring
echo "[GRAPHIQUE] Démarrage du monitoring..."
docker-compose up -d prometheus grafana

echo ""
echo "[OK] Installation terminée!"
echo ""
echo "[WEB] URLs:"
echo "  - Frontend React:    http://localhost:3000"
echo "  - Kong Gateway:      http://localhost:8000"
echo "  - Kong Admin API:    http://localhost:8001"
echo "  - Kong Manager:      http://localhost:8002"
echo "  - Konga GUI:         http://localhost:1337"
echo "  - Prometheus:        http://localhost:9090"
echo "  - Grafana:           http://localhost:3001"
echo ""
echo "[CLE] Credentials:"
echo "  - Admin user:        admin / admin123"
echo "  - API Key:           frontend-key-abc123xyz"
echo "  - Grafana:           admin / admin"
echo ""
SETUPSH

chmod +x scripts/setup.sh


cat > scripts/test-apis.sh << 'TESTSH'
#!/bin/bash

API_URL="http://localhost:8000"
API_KEY="frontend-key-abc123xyz"

echo "[TEST] Test des APIs via Kong Gateway"
echo ""

# Test 1: Santé de Kong
echo "1⃣ Test santé de Kong..."
curl -s http://localhost:8001/ | jq -r '.version'
echo ""

# Test 2: Inscription
echo "2⃣ Test inscription utilisateur..."
curl -X POST "$API_URL/api/users" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "testuser",
    "email": "test@example.com",
    "password": "password123",
    "first_name": "Test",
    "last_name": "User"
  }' | jq '.'
echo ""

# Test 3: Login
echo "3⃣ Test login..."
TOKEN=$(curl -s -X POST "$API_URL/api/auth/login" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "testuser",
    "password": "password123"
  }' | jq -r '.token')
echo "Token: $TOKEN"
echo ""

# Test 4: Liste produits
echo "4⃣ Test liste produits..."
curl -s "$API_URL/api/products" | jq '.products | length'
echo ""

# Test 5: Recherche produits
echo "5⃣ Test recherche produits..."
curl -s "$API_URL/api/products/search?q=iphone" | jq '.count'
echo ""

# Test 6: Créer une commande
echo "6⃣ Test création commande..."
curl -s -X POST "$API_URL/api/orders" \
  -H "Content-Type: application/json" \
  -H "apikey: $API_KEY" \
  -d '{
    "user_id": 1,
    "items": [
      {"product_id": 1, "product_name": "iPhone", "quantity": 1, "price": 999.99}
    ],
    "shipping_address": "123 Test St"
  }' | jq '.'
echo ""

# Test 7: Métriques
echo "7⃣ Test métriques Prometheus..."
curl -s http://localhost:8001/metrics | grep kong_http_requests_total
echo ""

echo "[OK] Tests terminés!"
TESTSH

chmod +x scripts/test-apis.sh


# ============================================================================
# PARTIE 14: DOCUMENTATION D'UTILISATION
# ============================================================================

cat > README.md << 'README'
# [SHOPPING_TROLLEY] E-commerce Microservices avec Kong Gateway

Application e-commerce complète démontrant une architecture microservices avec Kong API Gateway.

## [CONSTRUCTION] Architecture

### Services Backend (Flask)
- **users-service** (port 4001): Gestion utilisateurs et authentification
- **products-service** (port 4002): Catalogue produits
- **orders-service** (port 4003): Gestion commandes
- **payments-service** (port 4004): Traitement paiements
- **notifications-service** (port 4005): Envoi notifications

### Frontend
- **react-app** (port 3000): Interface utilisateur

### Infrastructure
- **kong** (ports 8000, 8001, 8002): API Gateway
- **postgresql**: Base de données Kong
- **prometheus** (port 9090): Métriques
- **grafana** (port 3001): Dashboards

## [RAPIDE] Installation

### Prérequis
- Docker
- Docker Compose
- 8 GB RAM minimum

### Démarrage rapide

```bash
# 1. Cloner le repo
git clone <repo-url>
cd ecommerce-microservices

# 2. Lancer l'installation complète
./scripts/setup.sh

# 3. Vérifier que tout fonctionne
./scripts/test-apis.sh
```

### Démarrage manuel

```bash
# Démarrer tous les services
docker-compose up -d

# Voir les logs
docker-compose logs -f

# Arrêter
docker-compose down
```

## [GUIDE] Utilisation

### Frontend React
Ouvrir http://localhost:3000

### API via Kong
Toutes les requêtes passent par Kong sur le port 8000:

```bash
# Liste des produits (public)
curl http://localhost:8000/api/products

# Inscription
curl -X POST http://localhost:8000/api/users \
  -H "Content-Type: application/json" \
  -d '{"username": "john", "email": "john@example.com", "password": "pass123"}'

# Login
curl -X POST http://localhost:8000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username": "john", "password": "pass123"}'

# Créer une commande (nécessite API key)
curl -X POST http://localhost:8000/api/orders \
  -H "Content-Type: application/json" \
  -H "apikey: frontend-key-abc123xyz" \
  -d '{
    "user_id": 1,
    "items": [{"product_id": 1, "product_name": "iPhone", "quantity": 1, "price": 999.99}],
    "shipping_address": "123 Main St"
  }'
```

### API Keys disponibles
- Frontend: `frontend-key-abc123xyz`
- Mobile: `mobile-key-xyz789abc`
- Admin: `admin-key-super-secret-123`

## [OUTIL] Configuration Kong

La configuration Kong est dans `kong.yml` et inclut:

### Fonctionnalités démontrées
- [OK] Services & Routes
- [OK] Key Authentication
- [OK] Rate Limiting
- [OK] CORS
- [OK] Proxy Cache
- [OK] Load Balancing
- [OK] Request/Response Transformation
- [OK] IP Restriction
- [OK] ACL (Access Control Lists)
- [OK] Health Checks
- [OK] File/HTTP Logging
- [OK] Prometheus Metrics
- [OK] Bot Detection

### Exemples de configuration

#### Rate Limiting
```yaml
- name: rate-limiting
  config:
    minute: 60
    hour: 1000
    policy: local
```

#### Cache
```yaml
- name: proxy-cache
  config:
    strategy: memory
    cache_ttl: 300
```

#### ACL
```yaml
- name: acl
  config:
    allow:
      - admin
```

## [GRAPHIQUE] Monitoring

### Prometheus
- URL: http://localhost:9090
- Métriques Kong: http://localhost:8001/metrics

### Grafana
- URL: http://localhost:3001
- Login: admin / admin
- Dashboard Kong pré-configuré

### Métriques disponibles
- `kong_http_requests_total`: Nombre de requêtes
- `kong_latency_ms`: Latence
- `kong_bandwidth_bytes`: Bande passante
- `kong_nginx_connections_total`: Connexions actives

## [TEST] Tests

### Tests automatiques
```bash
./scripts/test-apis.sh
```

### Tests manuels
```bash
# Santé de Kong
curl http://localhost:8001/

# Liste des services
curl http://localhost:8001/services

# Liste des routes
curl http://localhost:8001/routes

# Liste des consumers
curl http://localhost:8001/consumers

# Métriques
curl http://localhost:8001/metrics
```

## [BUG] Dépannage

### Kong ne démarre pas
```bash
# Vérifier les logs
docker-compose logs kong

# Redémarrer
docker-compose restart kong-database kong-migration kong
```

### 404 Not Found
```bash
# Vérifier les routes
curl http://localhost:8001/routes

# Recharger la configuration
curl -X POST http://localhost:8001/config --form config=@kong.yml
```

### 401 Unauthorized
```bash
# Vérifier que l'API key est correcte
# Header: apikey: frontend-key-abc123xyz
```

## [DOCS] Documentation

- [Kong Gateway](https://docs.konghq.com/)
- [Flask](https://flask.palletsprojects.com/)
- [React](https://react.dev/)
- [Docker](https://docs.docker.com/)

## [FICHIER] Licence

MIT
README


# ============================================================================
# COMMANDES FINALES
# ============================================================================

echo ""
echo "[OK] Tous les fichiers ont été créés!"
echo ""
echo "[DOSSIER] Structure du projet:"
tree -L 3 ecommerce-microservices || find ecommerce-microservices -type f
echo ""
echo "[RAPIDE] Pour démarrer l'application:"
echo ""
echo "  cd ecommerce-microservices"
echo "  ./scripts/setup.sh"
echo ""
echo "[GUIDE] Consultez le README.md pour plus d'informations"
echo ""