═══════════════════════════════════════════════════════════════════════════════
[LIVRE] NEXUS REPOSITORY MANAGER - CAS PRATIQUE RÉEL
    ÉQUIPE DE 3 DÉVELOPPEURS : APPLICATION PYTHON + JAVA
═══════════════════════════════════════════════════════════════════════════════

[OBJECTIF] Section bonus ultra-détaillée : Simulation grandeur nature
[UTILISATEURS] Équipe : Alice (Backend Java), Bob (Backend Python), Charlie (DevOps/Intégration)
[CONSTRUCTION] Projet : API de gestion de produits (microservice Python + bibliothèque Java)
[CALENDRIER] Version : Janvier 2026

═══════════════════════════════════════════════════════════════════════════════
[DOCS] TABLE DES MATIÈRES
═══════════════════════════════════════════════════════════════════════════════

  SECTION A : CONTEXTE ET ARCHITECTURE DU PROJET
  SECTION B : MISE EN PLACE NEXUS PAR CHARLIE (DevOps)
  SECTION C : TRAVAIL D'ALICE (Développeuse Java)
  SECTION D : TRAVAIL DE BOB (Développeur Python)
  SECTION E : COLLABORATION ET INTÉGRATION ENTRE LES TROIS
  SECTION F : SCÉNARIOS DU QUOTIDIEN (Situations réelles)
  SECTION G : TROUBLESHOOTING VÉCU PAR L'ÉQUIPE
  SECTION H : BILAN ET BONNES PRATIQUES APPRISES

═══════════════════════════════════════════════════════════════════════════════
SECTION A : CONTEXTE ET ARCHITECTURE DU PROJET
═══════════════════════════════════════════════════════════════════════════════

────────────────────────────────────────────────────────────────────────────
A.1 - PRÉSENTATION DE L'ÉQUIPE
────────────────────────────────────────────────────────────────────────────

┌─────────────────────────────────────────────────────────────────────────┐
│  ALICE  │ Développeuse Senior Java                                      │
│         │ Responsable : Bibliothèque de validation partagée             │
│         │ Stack : Java 17, Spring Boot 3, Maven                         │
│         │ PC : MacOS, IntelliJ IDEA                                     │
├─────────────────────────────────────────────────────────────────────────┤
│  BOB    │ Développeur Python                                            │
│         │ Responsable : API REST FastAPI                                │
│         │ Stack : Python 3.11, FastAPI, pip/pyproject.toml              │
│         │ PC : Ubuntu 22.04, VS Code                                    │
├─────────────────────────────────────────────────────────────────────────┤
│  CHARLIE│ DevOps / Développeur Intégration                              │
│         │ Responsable : Nexus, CI/CD, Infrastructure                    │
│         │ Stack : Docker, GitHub Actions, Bash                          │
│         │ PC : Ubuntu 22.04, Terminal                                   │
└─────────────────────────────────────────────────────────────────────────┘


────────────────────────────────────────────────────────────────────────
A.2 - ARCHITECTURE DU PROJET "PRODUCTMANAGER"
────────────────────────────────────────────────────────────────────────

Le projet "ProductManager" est une application de gestion de produits
composée de deux parties distinctes qui se partagent du code via Nexus :

┌────────────────────────────────────────────────────────────────────────┐
│                      PRODUCTMANAGER SYSTEM                             │
│                                                                        │
│  ┌─────────────────────┐        ┌─────────────────────────────────┐   │
│  │  product-validator  │        │      product-api (Python)        │   │
│  │  (Java Library)     │        │                                  │   │
│  │  par Alice          │[BLACK_LEFT-POINTING_POINTER]───────│  Utilise product-validator       │   │
│  │                     │        │  via Nexus PyPI (wrapper Python) │   │
│  │  - Validation SKU   │        │  par Bob                        │   │
│  │  - Validation prix  │        │                                  │   │
│  │  - Règles métier    │        │  Endpoints REST :               │   │
│  │  Publié sur Nexus   │        │  POST /products                 │   │
│  │  (maven + pypi)     │        │  GET  /products/{id}            │   │
│  └─────────────────────┘        │  PUT  /products/{id}            │   │
│                                 └─────────────────────────────────┘   │
│                                                                        │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                    NEXUS REPOSITORY (Charlie)                   │   │
│  │  maven-releases     │  pypi-hosted  │  raw-scripts             │   │
│  │  maven-snapshots    │  pypi-proxy   │  docker-hosted           │   │
│  └─────────────────────────────────────────────────────────────────┘   │
└────────────────────────────────────────────────────────────────────────┘


────────────────────────────────────────────────────────────────────────
A.3 - POURQUOI NEXUS DANS CE CONTEXTE ?
────────────────────────────────────────────────────────────────────────

PROBLÈME SANS NEXUS :
─────────────────────

Alice développe product-validator.jar
Bob a besoin de ce code en Python
-> Solution actuelle (mauvaise) :
  1. Alice envoie le JAR par Slack à Bob
  2. Bob ne peut pas utiliser un JAR dans Python
  3. Bob copie le code de validation à la main
  4. Deux codebases désynchronisées
  5. Quand Alice corrige un bug, Bob ne sait pas

SOLUTION AVEC NEXUS :
─────────────────────

1. Alice publie product-validator:1.0.0 sur Nexus (maven-releases)
2. Alice publie aussi un wrapper Python sur Nexus (pypi-hosted)
   -> product-validator-py:1.0.0
3. Bob installe via pip install product-validator-py
4. Charlie configure CI/CD pour automatiser tout ça
5. Tout le monde utilise la même version, tracée, versionnée


════════════════════════════════════════════════════════════════════════
SECTION B : MISE EN PLACE NEXUS PAR CHARLIE (DevOps)
════════════════════════════════════════════════════════════════════════

────────────────────────────────────────────────────────────────────────
B.1 - INSTALLATION NEXUS PAR CHARLIE
────────────────────────────────────────────────────────────────────────

Charlie installe Nexus sur le serveur de l'équipe (IP: 192.168.1.50).

# Sur le serveur Ubuntu 22.04 de l'équipe

# Étape 1 : Installer Docker
sudo apt update
sudo apt install docker.io docker-compose -y
sudo usermod -aG docker charlie
newgrp docker

# Étape 2 : Créer dossier de travail
mkdir -p /opt/nexus/{data,backup}
sudo chown -R 200:200 /opt/nexus/data

# Étape 3 : Créer docker-compose.yml
cat > /opt/nexus/docker-compose.yml << 'EOF'
version: '3.8'

services:
  nexus:
    image: sonatype/nexus3:latest
    container_name: nexus
    restart: unless-stopped
    ports:
      - "8081:8081"    # Web UI + Maven + npm + PyPI
      - "8082:8082"    # Docker Hosted Registry
      - "8083:8083"    # Docker Proxy (Docker Hub cache)
    volumes:
      - /opt/nexus/data:/nexus-data
    environment:
      - INSTALL4J_ADD_VM_PARAMS=-Xms2g -Xmx2g -XX:MaxDirectMemorySize=2g
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8081/service/rest/v1/status"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 120s
EOF

# Étape 4 : Démarrer Nexus
cd /opt/nexus
docker-compose up -d

# Étape 5 : Attendre démarrage (2-3 minutes)
echo "[HOURGLASS_WITH_FLOWING_SAND] Attente du démarrage de Nexus..."
sleep 120

# Étape 6 : Récupérer mot de passe admin initial
docker exec nexus cat /nexus-data/admin.password
# -> Ex: 3a4b5c6d-7e8f-9a0b-c1d2-e3f4a5b6c7d8

# Étape 7 : Accéder à http://192.168.1.50:8081
# Login : admin
# Password : (celui récupéré ci-dessus)
# -> Changer password : Admin@ProductManager2024!


────────────────────────────────────────────────────────────────────────
B.2 - CRÉATION DES REPOSITORIES PAR CHARLIE
────────────────────────────────────────────────────────────────────────

Charlie crée tous les repositories nécessaires pour l'équipe.

════════════════════════
B.2.1 - REPOSITORIES MAVEN (Pour Alice)
════════════════════════

Charlie crée via l'interface Nexus :

── REPOSITORY 1 : maven-releases (hosted) ──
  Name               : maven-releases
  Version policy     : Release
  Deployment policy  : Disable redeploy     <- Immuabilité des releases
  Blob store         : default
  -> URL : http://192.168.1.50:8081/repository/maven-releases/

── REPOSITORY 2 : maven-snapshots (hosted) ──
  Name               : maven-snapshots
  Version policy     : Snapshot
  Deployment policy  : Allow redeploy       <- Dev en cours
  Cleanup policy     : delete-old-snapshots (>30 jours)
  Blob store         : default
  -> URL : http://192.168.1.50:8081/repository/maven-snapshots/

── REPOSITORY 3 : maven-central-proxy (proxy) ──
  Name               : maven-central-proxy
  Remote storage     : https://repo1.maven.org/maven2/
  Maximum component age : -1 (illimité pour les releases)
  Maximum metadata age  : 1440 (24h)
  Negative cache enabled: true
  -> URL : http://192.168.1.50:8081/repository/maven-central-proxy/

── REPOSITORY 4 : maven-public (group) ──
  Name               : maven-public
  Member repositories:
    1. maven-releases      (vos artifacts d'abord)
    2. maven-snapshots
    3. maven-central-proxy (fallback internet)
  -> URL : http://192.168.1.50:8081/repository/maven-public/


════════════════════════
B.2.2 - REPOSITORIES PyPI (Pour Bob)
════════════════════════

── REPOSITORY 5 : pypi-hosted (hosted) ──
  Name               : pypi-hosted
  Deployment policy  : Allow redeploy
  -> URL : http://192.168.1.50:8081/repository/pypi-hosted/

── REPOSITORY 6 : pypi-proxy (proxy) ──
  Name               : pypi-proxy
  Remote storage     : https://pypi.org
  Maximum component age : -1
  Maximum metadata age  : 1440
  -> URL : http://192.168.1.50:8081/repository/pypi-proxy/

── REPOSITORY 7 : pypi-all (group) ──
  Name               : pypi-all
  Members            :
    1. pypi-hosted   (packages internes en priorité)
    2. pypi-proxy    (PyPI.org en fallback)
  -> URL : http://192.168.1.50:8081/repository/pypi-all/


════════════════════════
B.2.3 - REPOSITORIES DOCKER (Pour l'équipe)
════════════════════════

── REPOSITORY 8 : docker-hosted (hosted) ──
  Name               : docker-hosted
  HTTP Port          : 8082
  Allow anonymous pull: true (réseau interne seulement)
  -> URL : http://192.168.1.50:8082/

── REPOSITORY 9 : docker-proxy (proxy) ──
  Name               : docker-proxy
  HTTP Port          : 8083
  Remote storage     : https://registry-1.docker.io
  Docker Index       : Use Docker Hub
  -> URL : http://192.168.1.50:8083/

── REPOSITORY 10 : raw-scripts (hosted) ──
  Name               : raw-scripts
  Content Disposition: Attachment
  -> URL : http://192.168.1.50:8081/repository/raw-scripts/


────────────────────────────────────────────────────────────────────────
B.3 - CRÉATION DES UTILISATEURS PAR CHARLIE
────────────────────────────────────────────────────────────────────────

Charlie crée des comptes dédiés pour chaque développeur.
Principe : Moindre privilège.

════════════════════════
B.3.1 - CRÉER LES RÔLES
════════════════════════

── RÔLE : java-developer-role ──
  ID               : java-developer-role
  Description      : Développeur Java - Alice
  Privileges :
    [x] nx-search-read
    [x] nx-repository-view-maven2-*-read
    [x] nx-repository-view-maven2-*-browse
    [x] nx-repository-view-maven2-maven-snapshots-add
    [x] nx-repository-view-maven2-maven-snapshots-edit
    [x] nx-repository-view-maven2-maven-snapshots-delete
    [x] nx-repository-view-maven2-maven-releases-add
    [x] nx-repository-view-pypi-pypi-hosted-add   <- Pour publier wrapper Python
    [x] nx-repository-view-pypi-*-read
    [x] nx-repository-view-pypi-*-browse

── RÔLE : python-developer-role ──
  ID               : python-developer-role
  Description      : Développeur Python - Bob
  Privileges :
    [x] nx-search-read
    [x] nx-repository-view-pypi-*-read
    [x] nx-repository-view-pypi-*-browse
    [x] nx-repository-view-pypi-pypi-hosted-add
    [x] nx-repository-view-pypi-pypi-hosted-edit
    [x] nx-repository-view-maven2-*-read     <- Pour consulter les libs Java
    [x] nx-repository-view-maven2-*-browse
    [x] nx-repository-view-docker-*-read
    [x] nx-repository-view-docker-*-browse
    [x] nx-repository-view-docker-docker-hosted-add

════════════════════════
B.3.2 - CRÉER LES UTILISATEURS
════════════════════════

── UTILISATEUR : alice ──
  ID           : alice
  First Name   : Alice
  Last Name    : Martin
  Email        : alice@productmanager.local
  Status       : Active
  Roles        : java-developer-role
  Password     : AliceSecure@2024!

── UTILISATEUR : bob ──
  ID           : bob
  First Name   : Bob
  Last Name    : Dupont
  Email        : bob@productmanager.local
  Status       : Active
  Roles        : python-developer-role
  Password     : BobSecure@2024!

── UTILISATEUR : ci-runner ──
  ID           : ci-runner
  First Name   : CI
  Last Name    : Runner
  Email        : ci@productmanager.local
  Status       : Active
  Roles        : java-developer-role + python-developer-role
  Password     : (généré aléatoirement)
  -> Token API : Générer via User Token pour GitHub Actions


────────────────────────────────────────────────────────────────────────
B.4 - CONFIGURATION INSECURE REGISTRY DOCKER (Charlie)
────────────────────────────────────────────────────────────────────────

Sur chaque machine de l'équipe (alice, bob, charlie) :

# /etc/docker/daemon.json
{
  "insecure-registries": [
    "192.168.1.50:8082",
    "192.168.1.50:8083"
  ],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

# Redémarrer Docker sur chaque poste
sudo systemctl daemon-reload
sudo systemctl restart docker

# Vérifier
docker info | grep -A5 "Insecure Registries"


────────────────────────────────────────────────────────────────────────
B.5 - DÉSACTIVATION ANONYMOUS ACCESS (Charlie)
────────────────────────────────────────────────────────────────────────

En production interne, Charlie désactive l'accès anonyme :

# Nexus UI -> Security -> Anonymous Access
# -> Décocher "Allow anonymous users to access the server"
# -> Save

# Vérifier que l'accès est bloqué sans credentials :
curl http://192.168.1.50:8081/repository/maven-public/
# Expected: 401 Unauthorized

# Vérifier que l'accès fonctionne avec credentials :
curl -u alice:AliceSecure@2024! http://192.168.1.50:8081/repository/maven-public/
# Expected: 200 OK


════════════════════════════════════════════════════════════════════════
SECTION C : TRAVAIL D'ALICE (Développeuse Java)
════════════════════════════════════════════════════════════════════════

Alice développe la bibliothèque "product-validator" qui contient
les règles métier de validation des produits. Cette bibliothèque
est utilisée par Bob via un wrapper Python.

────────────────────────────────────────────────────────────────────────
C.1 - CONFIGURATION MAVEN D'ALICE
────────────────────────────────────────────────────────────────────────

════════════════════════
C.1.1 - settings.xml (Fichier global Maven d'Alice)
════════════════════════

# Sur le poste d'Alice : ~/.m2/settings.xml

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
            http://maven.apache.org/xsd/settings-1.0.0.xsd">

  <!-- ====================================================== -->
  <!--  SERVEURS : Credentials pour chaque repository Nexus  -->
  <!-- ====================================================== -->
  <servers>

    <!-- Pour TÉLÉCHARGER depuis maven-public (group) -->
    <server>
      <id>nexus-public</id>
      <username>alice</username>
      <password>AliceSecure@2024!</password>
      <!--
        [IDEE] NOTE : En CI/CD, utiliser variables d'environnement :
        <username>${env.NEXUS_USERNAME}</username>
        <password>${env.NEXUS_PASSWORD}</password>
      -->
    </server>

    <!-- Pour DEPLOYER vers maven-releases -->
    <server>
      <id>nexus-releases</id>
      <username>alice</username>
      <password>AliceSecure@2024!</password>
    </server>

    <!-- Pour DEPLOYER vers maven-snapshots -->
    <server>
      <id>nexus-snapshots</id>
      <username>alice</username>
      <password>AliceSecure@2024!</password>
    </server>

  </servers>

  <!-- ====================================================== -->
  <!--  MIRROR : Tout le trafic Maven -> Nexus                -->
  <!-- ====================================================== -->
  <mirrors>
    <mirror>
      <id>nexus-mirror</id>
      <name>Nexus Repository Manager - Team Mirror</name>
      <url>http://192.168.1.50:8081/repository/maven-public/</url>
      <!--
        <mirrorOf>*</mirrorOf>
        Cela signifie : "Pour TOUS les repositories, utiliser Nexus"
        Maven Central, Spring, Google -> Tout passe par notre Nexus
        Nexus proxifie et met en cache pour nous.
      -->
      <mirrorOf>*</mirrorOf>
    </mirror>
  </mirrors>

  <!-- ====================================================== -->
  <!--  PROFILS : Configuration des repositories             -->
  <!-- ====================================================== -->
  <profiles>
    <profile>
      <id>nexus</id>
      <repositories>
        <repository>
          <id>nexus-public</id>
          <name>Nexus Repository Manager</name>
          <url>http://192.168.1.50:8081/repository/maven-public/</url>
          <releases>
            <enabled>true</enabled>
            <updatePolicy>never</updatePolicy>
            <!--
              [IDEE] updatePolicy=never pour les releases :
              Une release ne change jamais -> Pas besoin de revérifier
              Améliore performances Maven
            -->
          </releases>
          <snapshots>
            <enabled>true</enabled>
            <updatePolicy>always</updatePolicy>
            <!--
              [IDEE] updatePolicy=always pour les snapshots :
              Les snapshots changent souvent -> Toujours vérifier
              -SNAPSHOT peut être rebuild par quelqu'un d'autre
            -->
          </snapshots>
        </repository>
      </repositories>
      <pluginRepositories>
        <pluginRepository>
          <id>nexus-public</id>
          <url>http://192.168.1.50:8081/repository/maven-public/</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>true</enabled></snapshots>
        </pluginRepository>
      </pluginRepositories>
    </profile>
  </profiles>

  <!-- Activer le profil par défaut -->
  <activeProfiles>
    <activeProfile>nexus</activeProfile>
  </activeProfiles>

</settings>


════════════════════════
C.1.2 - VÉRIFICATION DE LA CONFIG ALICE
════════════════════════

# Alice vérifie que Maven utilise bien Nexus :
mvn help:effective-settings

# Elle crée un projet test et vérifie les downloads :
mvn archetype:generate \
    -DgroupId=com.productmanager \
    -DartifactId=test-nexus \
    -DarchetypeArtifactId=maven-archetype-quickstart \
    -DinteractiveMode=false

cd test-nexus
mvn clean install -X 2>&1 | grep "Downloading from nexus"

# Sortie attendue :
# [DEBUG] Downloading from nexus-mirror: http://192.168.1.50:8081/repository/maven-public/...
# [OK] Maven télécharge bien depuis Nexus !


────────────────────────────────────────────────────────────────────────
C.2 - STRUCTURE DU PROJET JAVA D'ALICE
────────────────────────────────────────────────────────────────────────

product-validator/
├── pom.xml                              <- Configuration Maven principale
├── src/
│   ├── main/
│   │   └── java/
│   │       └── com/
│   │           └── productmanager/
│   │               └── validator/
│   │                   ├── ProductValidator.java    <- Classe principale
│   │                   ├── ValidationResult.java    <- Objet résultat
│   │                   ├── ValidationRule.java      <- Interface règle
│   │                   └── rules/
│   │                       ├── SkuValidationRule.java
│   │                       ├── PriceValidationRule.java
│   │                       └── StockValidationRule.java
│   └── test/
│       └── java/
│           └── com/
│               └── productmanager/
│                   └── validator/
│                       └── ProductValidatorTest.java
└── README.md


────────────────────────────────────────────────────────────────────────
C.3 - LE pom.xml D'ALICE (Complet et commenté)
────────────────────────────────────────────────────────────────────────

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
           http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <!-- ====================================================== -->
  <!--  IDENTITÉ DE L'ARTIFACT                               -->
  <!-- ====================================================== -->
  <groupId>com.productmanager</groupId>
  <artifactId>product-validator</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <!--
    [IDEE] Pendant le développement : 1.0.0-SNAPSHOT
    Avant la release : Alice changera en 1.0.0
    La version détermine vers quel repository Maven déploie :
    - *-SNAPSHOT -> maven-snapshots
    - Sans SNAPSHOT -> maven-releases
  -->
  <packaging>jar</packaging>

  <name>Product Validator Library</name>
  <description>
    Bibliothèque de validation des produits pour ProductManager.
    Utilisée par le service Java ET le service Python (via wrapper).
  </description>

  <!-- ====================================================== -->
  <!--  CONFIGURATION DE L'ENCODAGE ET DES PROPRIÉTÉS       -->
  <!-- ====================================================== -->
  <properties>
    <java.version>17</java.version>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <junit.version>5.10.0</junit.version>
    <jackson.version>2.15.3</jackson.version>
  </properties>

  <!-- ====================================================== -->
  <!--  DÉPENDANCES                                          -->
  <!-- ====================================================== -->
  <dependencies>

    <!-- Jackson : Pour sérialisation JSON des résultats de validation -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>${jackson.version}</version>
      <!--
        [IDEE] Maven télécharge jackson-databind depuis Nexus :
        1. Cherche dans maven-releases (hosted) -> Pas trouvé
        2. Cherche dans maven-snapshots (hosted) -> Pas trouvé
        3. Cherche dans maven-central-proxy -> Pas en cache
        4. Nexus télécharge depuis Maven Central et met en cache
        5. Renvoie à Maven d'Alice
        -> Prochaine fois : Servi depuis le cache Nexus (rapide!)
      -->
    </dependency>

    <!-- Commons Lang : Utilitaires String -->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.14.0</version>
    </dependency>

    <!-- ──────────────────────────────────────────── -->
    <!-- DÉPENDANCES DE TEST (scope=test uniquement)  -->
    <!-- ──────────────────────────────────────────── -->

    <!-- JUnit 5 -->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>

    <!-- AssertJ : Assertions lisibles -->
    <dependency>
      <groupId>org.assertj</groupId>
      <artifactId>assertj-core</artifactId>
      <version>3.24.2</version>
      <scope>test</scope>
    </dependency>

  </dependencies>

  <!-- ====================================================== -->
  <!--  DISTRIBUTION MANAGEMENT : Où publier dans Nexus      -->
  <!-- ====================================================== -->
  <distributionManagement>
    <!--
      [IDEE] Maven sélectionne automatiquement selon la version :
      - <version>x.y.z-SNAPSHOT</version> -> snapshotRepository
      - <version>x.y.z</version>           -> repository
    -->
    <repository>
      <id>nexus-releases</id>
      <!--
        Cet <id> DOIT correspondre au <id> dans settings.xml <servers>
        C'est ainsi que Maven trouve les credentials
      -->
      <name>Nexus Releases Repository</name>
      <url>http://192.168.1.50:8081/repository/maven-releases/</url>
    </repository>
    <snapshotRepository>
      <id>nexus-snapshots</id>
      <name>Nexus Snapshots Repository</name>
      <url>http://192.168.1.50:8081/repository/maven-snapshots/</url>
    </snapshotRepository>
  </distributionManagement>

  <!-- ====================================================== -->
  <!--  BUILD : Plugins Maven                                -->
  <!-- ====================================================== -->
  <build>
    <plugins>

      <!-- Compilateur Java 17 -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.11.0</version>
        <configuration>
          <release>17</release>
        </configuration>
      </plugin>

      <!-- Tests : JUnit 5 support -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.2.2</version>
      </plugin>

      <!-- Plugin Sources : Génère product-validator-1.0.0-sources.jar -->
      <!--
        [IDEE] Important ! Permet à Bob de voir le code source
        dans son IDE même en utilisant le JAR compilé.
        Nexus stockera 3 fichiers :
        - product-validator-1.0.0.jar          (compilé)
        - product-validator-1.0.0-sources.jar  (sources)
        - product-validator-1.0.0-javadoc.jar  (docs)
      -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-source-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <id>attach-sources</id>
            <goals>
              <goal>jar-no-fork</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

      <!-- Plugin Javadoc -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-javadoc-plugin</artifactId>
        <version>3.6.3</version>
        <executions>
          <execution>
            <id>attach-javadocs</id>
            <goals>
              <goal>jar</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

    </plugins>
  </build>

</project>


────────────────────────────────────────────────────────────────────────
C.4 - LE CODE JAVA D'ALICE
────────────────────────────────────────────────────────────────────────

════════════════════════
C.4.1 - ProductValidator.java (Classe principale)
════════════════════════

# src/main/java/com/productmanager/validator/ProductValidator.java

package com.productmanager.validator;

import com.productmanager.validator.rules.PriceValidationRule;
import com.productmanager.validator.rules.SkuValidationRule;
import com.productmanager.validator.rules.StockValidationRule;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Point d'entrée principal de la bibliothèque de validation.
 *
 * <p>Exemple d'utilisation :</p>
 * <pre>
 *   ProductValidator validator = new ProductValidator();
 *   ValidationResult result = validator.validate(productData);
 *   if (result.isValid()) {
 *     // Produit valide
 *   }
 * </pre>
 *
 * @version 1.0.0
 * @author Alice Martin
 */
public class ProductValidator {

    private final List<ValidationRule> rules;

    public ProductValidator() {
        this.rules = new ArrayList<>();
        // Règles par défaut
        this.rules.add(new SkuValidationRule());
        this.rules.add(new PriceValidationRule());
        this.rules.add(new StockValidationRule());
    }

    /**
     * Valide un produit selon toutes les règles configurées.
     *
     * @param productData Map contenant les données du produit :
     *                    - "sku"   : String (obligatoire, format PROD-XXXX)
     *                    - "price" : Double (obligatoire, >0)
     *                    - "stock" : Integer (obligatoire, >=0)
     *                    - "name"  : String (obligatoire, non vide)
     * @return ValidationResult avec isValid() et liste d'erreurs
     */
    public ValidationResult validate(Map<String, Object> productData) {
        List<String> errors = new ArrayList<>();

        for (ValidationRule rule : rules) {
            List<String> ruleErrors = rule.validate(productData);
            errors.addAll(ruleErrors);
        }

        return new ValidationResult(errors.isEmpty(), errors);
    }

    /**
     * Valide uniquement le SKU d'un produit.
     *
     * @param sku Le SKU à valider
     * @return true si le SKU est valide
     */
    public boolean isValidSku(String sku) {
        return sku != null && sku.matches("^PROD-[A-Z0-9]{4,8}$");
    }

    /**
     * Valide uniquement le prix d'un produit.
     *
     * @param price Le prix à valider
     * @return true si le prix est valide
     */
    public boolean isValidPrice(double price) {
        return price > 0 && price <= 999999.99;
    }
}


════════════════════════
C.4.2 - ValidationResult.java
════════════════════════

# src/main/java/com/productmanager/validator/ValidationResult.java

package com.productmanager.validator;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Collections;
import java.util.List;

/**
 * Résultat d'une validation de produit.
 * Sérialisable en JSON pour communication inter-services.
 */
public class ValidationResult {

    @JsonProperty("valid")
    private final boolean valid;

    @JsonProperty("errors")
    private final List<String> errors;

    @JsonProperty("error_count")
    private final int errorCount;

    public ValidationResult(boolean valid, List<String> errors) {
        this.valid = valid;
        this.errors = Collections.unmodifiableList(errors);
        this.errorCount = errors.size();
    }

    public boolean isValid() { return valid; }

    public List<String> getErrors() { return errors; }

    public int getErrorCount() { return errorCount; }

    public String getFirstError() {
        return errors.isEmpty() ? null : errors.get(0);
    }

    @Override
    public String toString() {
        if (valid) {
            return "ValidationResult{VALID}";
        }
        return "ValidationResult{INVALID, errors=" + errors + "}";
    }
}


════════════════════════
C.4.3 - SkuValidationRule.java
════════════════════════

# src/main/java/com/productmanager/validator/rules/SkuValidationRule.java

package com.productmanager.validator.rules;

import com.productmanager.validator.ValidationRule;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Règle de validation du SKU (Stock Keeping Unit).
 * Format attendu : PROD-[A-Z0-9]{4,8}
 * Exemples valides : PROD-ABC1, PROD-XY12Z, PROD-ABCD1234
 * Exemples invalides : ABC-123, PROD-ab, prod-ABC1
 */
public class SkuValidationRule implements ValidationRule {

    private static final String SKU_PATTERN = "^PROD-[A-Z0-9]{4,8}$";
    private static final String FIELD_NAME = "sku";

    @Override
    public List<String> validate(Map<String, Object> productData) {
        List<String> errors = new ArrayList<>();

        Object skuObj = productData.get(FIELD_NAME);

        if (skuObj == null) {
            errors.add("Le champ 'sku' est obligatoire");
            return errors;
        }

        String sku = skuObj.toString().trim();

        if (sku.isEmpty()) {
            errors.add("Le SKU ne peut pas être vide");
        } else if (!sku.matches(SKU_PATTERN)) {
            errors.add(
                String.format(
                    "SKU invalide : '%s'. Format attendu : PROD-[A-Z0-9]{4,8} " +
                    "(ex: PROD-ABC1, PROD-XY12Z)",
                    sku
                )
            );
        }

        return errors;
    }
}


════════════════════════
C.4.4 - PriceValidationRule.java
════════════════════════

# src/main/java/com/productmanager/validator/rules/PriceValidationRule.java

package com.productmanager.validator.rules;

import com.productmanager.validator.ValidationRule;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Règle de validation du prix.
 * Le prix doit être positif et ne pas dépasser 999 999.99
 */
public class PriceValidationRule implements ValidationRule {

    private static final double MAX_PRICE = 999_999.99;
    private static final String FIELD_NAME = "price";

    @Override
    public List<String> validate(Map<String, Object> productData) {
        List<String> errors = new ArrayList<>();

        Object priceObj = productData.get(FIELD_NAME);

        if (priceObj == null) {
            errors.add("Le champ 'price' est obligatoire");
            return errors;
        }

        try {
            double price = Double.parseDouble(priceObj.toString());

            if (price <= 0) {
                errors.add(
                    String.format("Le prix doit être positif. Valeur reçue : %.2f", price)
                );
            } else if (price > MAX_PRICE) {
                errors.add(
                    String.format(
                        "Le prix dépasse le maximum autorisé (%.2f). Valeur reçue : %.2f",
                        MAX_PRICE, price
                    )
                );
            }

            // Vérification de la précision (maximum 2 décimales)
            String priceStr = priceObj.toString();
            if (priceStr.contains(".") && priceStr.split("\\.")[1].length() > 2) {
                errors.add(
                    "Le prix ne peut pas avoir plus de 2 décimales. Valeur reçue : " + priceStr
                );
            }

        } catch (NumberFormatException e) {
            errors.add("Le prix doit être un nombre. Valeur reçue : " + priceObj);
        }

        return errors;
    }
}


════════════════════════
C.4.5 - Tests JUnit d'Alice
════════════════════════

# src/test/java/com/productmanager/validator/ProductValidatorTest.java

package com.productmanager.validator;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.*;

@DisplayName("Tests de ProductValidator")
class ProductValidatorTest {

    private ProductValidator validator;
    private Map<String, Object> validProduct;

    @BeforeEach
    void setUp() {
        validator = new ProductValidator();
        validProduct = new HashMap<>();
        validProduct.put("sku", "PROD-ABC1");
        validProduct.put("price", 29.99);
        validProduct.put("stock", 100);
        validProduct.put("name", "Produit Test");
    }

    @Test
    @DisplayName("Un produit valide doit passer la validation")
    void testValidProduct() {
        ValidationResult result = validator.validate(validProduct);

        assertThat(result.isValid()).isTrue();
        assertThat(result.getErrors()).isEmpty();
        assertThat(result.getErrorCount()).isZero();
    }

    @ParameterizedTest
    @DisplayName("SKUs valides")
    @ValueSource(strings = {"PROD-ABC1", "PROD-XY12", "PROD-ABCD1234", "PROD-A0B1"})
    void testValidSkus(String sku) {
        assertThat(validator.isValidSku(sku)).isTrue();
    }

    @ParameterizedTest
    @DisplayName("SKUs invalides")
    @ValueSource(strings = {"abc-123", "PROD-ab", "PROD-", "prod-ABC1", "PRODABC1"})
    void testInvalidSkus(String sku) {
        assertThat(validator.isValidSku(sku)).isFalse();
    }

    @Test
    @DisplayName("Prix négatif -> Erreur de validation")
    void testNegativePrice() {
        validProduct.put("price", -5.00);
        ValidationResult result = validator.validate(validProduct);

        assertThat(result.isValid()).isFalse();
        assertThat(result.getErrors()).anyMatch(e -> e.contains("prix doit être positif"));
    }

    @Test
    @DisplayName("SKU manquant -> Erreur de validation")
    void testMissingSku() {
        validProduct.remove("sku");
        ValidationResult result = validator.validate(validProduct);

        assertThat(result.isValid()).isFalse();
        assertThat(result.getFirstError()).contains("'sku' est obligatoire");
    }
}


────────────────────────────────────────────────────────────────────────
C.5 - CYCLE DE DÉVELOPPEMENT D'ALICE
────────────────────────────────────────────────────────────────────────

════════════════════════
C.5.1 - Phase Développement (SNAPSHOT)
════════════════════════

# Alice travaille sur la version SNAPSHOT
# pom.xml : <version>1.0.0-SNAPSHOT</version>

# 1. Coder et tester localement
mvn clean test

# Sortie :
# [INFO] -------------------------------------------------------
# [INFO]  T E S T S
# [INFO] -------------------------------------------------------
# [INFO] Running com.productmanager.validator.ProductValidatorTest
# [INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0
# [INFO] BUILD SUCCESS

# 2. Déployer le SNAPSHOT vers Nexus
mvn clean deploy

# Sortie détaillée :
# [INFO] --- maven-deploy-plugin:3.1.1:deploy (default-deploy) @ product-validator ---
# [INFO] Uploading to nexus-snapshots: http://192.168.1.50:8081/repository/maven-snapshots/com/productmanager/product-validator/1.0.0-SNAPSHOT/product-validator-1.0.0-20240115.103045-1.jar
# [INFO] Uploaded to nexus-snapshots: http://192.168.1.50:8081/repository/maven-snapshots/com/productmanager/product-validator/1.0.0-SNAPSHOT/product-validator-1.0.0-20240115.103045-1.jar (15 kB at 1.2 MB/s)
# [INFO] Uploading to nexus-snapshots: http://192.168.1.50:8081/repository/maven-snapshots/com/productmanager/product-validator/1.0.0-SNAPSHOT/product-validator-1.0.0-20240115.103045-1-sources.jar
# [INFO] Uploaded: ... (sources)
# [INFO] BUILD SUCCESS

# 3. Vérifier dans Nexus UI :
# Browse -> maven-snapshots -> com/productmanager/product-validator/1.0.0-SNAPSHOT/
# -> Files présents :
#   product-validator-1.0.0-20240115.103045-1.jar
#   product-validator-1.0.0-20240115.103045-1-sources.jar
#   product-validator-1.0.0-20240115.103045-1-javadoc.jar
#   product-validator-1.0.0-20240115.103045-1.pom
#   maven-metadata.xml (pointe vers la dernière version)

# 4. Bob peut maintenant utiliser le SNAPSHOT :
# (Voir Section D - Côté Bob)


════════════════════════
C.5.2 - Préparer la Release 1.0.0
════════════════════════

Quand Alice est prête pour la release officielle :

# Méthode 1 : Manuellement (simple)

# a. Changer la version dans pom.xml
# De : <version>1.0.0-SNAPSHOT</version>
# À  : <version>1.0.0</version>

# b. Commiter le changement
git add pom.xml
git commit -m "release: prepare version 1.0.0"
git tag -a v1.0.0 -m "Release version 1.0.0"

# c. Déployer la release
mvn clean deploy

# Sortie :
# [INFO] Uploading to nexus-releases: http://192.168.1.50:8081/repository/maven-releases/com/productmanager/product-validator/1.0.0/product-validator-1.0.0.jar
# [INFO] Uploaded to nexus-releases: ... (15 kB at 1.5 MB/s)
# [INFO] BUILD SUCCESS

# d. Vérifier dans Nexus UI :
# Browse -> maven-releases -> com/productmanager/product-validator/1.0.0/
# -> Files :
#   product-validator-1.0.0.jar      <- RELEASE OFFICIELLE
#   product-validator-1.0.0-sources.jar
#   product-validator-1.0.0-javadoc.jar
#   product-validator-1.0.0.pom

# e. Passer en version SNAPSHOT suivante pour la suite du développement
# <version>1.1.0-SNAPSHOT</version>
git add pom.xml
git commit -m "dev: start working on version 1.1.0-SNAPSHOT"

# Méthode 2 : Via Maven Release Plugin (automatique)
mvn release:prepare -Dtag=v1.0.0 -DreleaseVersion=1.0.0 -DdevelopmentVersion=1.1.0-SNAPSHOT
mvn release:perform


════════════════════════
C.5.3 - Ce qu'Alice voit dans Nexus après
════════════════════════

Dans Browse -> maven-releases :
  com/productmanager/product-validator/
  └── 1.0.0/
      ├── product-validator-1.0.0.jar          (15.2 KB)
      ├── product-validator-1.0.0.jar.md5      (hash MD5)
      ├── product-validator-1.0.0.jar.sha1     (hash SHA1)
      ├── product-validator-1.0.0-sources.jar  (8.4 KB)
      ├── product-validator-1.0.0-javadoc.jar  (22.1 KB)
      └── product-validator-1.0.0.pom          (3.8 KB)

Dans Browse -> maven-snapshots :
  com/productmanager/product-validator/
  └── 1.0.0-SNAPSHOT/
      ├── product-validator-1.0.0-20240115.103045-1.jar   (ancien)
      ├── product-validator-1.0.0-20240115.143022-2.jar   (moins ancien)
      ├── product-validator-1.0.0-20240116.090011-3.jar   (plus récent)
      └── maven-metadata.xml (contient la référence au -3 = latest)


════════════════════════════════════════════════════════════════════════
SECTION D : TRAVAIL DE BOB (Développeur Python)
════════════════════════════════════════════════════════════════════════

Bob développe l'API REST Python qui expose les fonctionnalités de validation.
Il utilise la bibliothèque d'Alice via un wrapper Python qu'Alice a aussi publié.

────────────────────────────────────────────────────────────────────────
D.1 - CONFIGURATION PIP DE BOB
────────────────────────────────────────────────────────────────────────

════════════════════════
D.1.1 - Fichier pip.conf global de Bob
════════════════════════

# Sur le poste de Bob : ~/.pip/pip.conf
# (Windows : %APPDATA%\pip\pip.ini)

[global]
# Rediriger tous les téléchargements vers Nexus pypi-all
index-url = http://192.168.1.50:8081/repository/pypi-all/simple
# Nexus pypi-all = Group qui agrège pypi-hosted + pypi-proxy
# pip ira chercher dans nos packages internes EN PREMIER
# puis dans PyPI.org via le proxy si non trouvé localement

# Autoriser le domaine en HTTP (environnement de développement interne)
# En production : Utiliser HTTPS et retirer trusted-host
trusted-host = 192.168.1.50

# Timeout étendu pour les gros packages (numpy, tensorflow, etc.)
timeout = 120

# Afficher la progression du téléchargement
progress-bar = on

[install]
# Reporter les erreurs lors de l'installation
# (utile pour le debug)
report = /tmp/pip-report.json


════════════════════════
D.1.2 - Vérification de la config Bob
════════════════════════

# Bob vérifie que pip utilise bien Nexus :
pip config list

# Sortie attendue :
# global.index-url='http://192.168.1.50:8081/repository/pypi-all/simple'
# global.trusted-host='192.168.1.50'

# Test en installant un package courant
pip install requests

# Les logs montrent :
# Looking in indexes: http://192.168.1.50:8081/repository/pypi-all/simple
# Collecting requests
#   Downloading http://192.168.1.50:8081/repository/pypi-all/.../requests-2.31.0-py3-none-any.whl
# [OK] Téléchargement depuis Nexus !

# Vérifier dans Nexus UI que le package est mis en cache :
# Browse -> pypi-proxy -> Components -> requests


────────────────────────────────────────────────────────────────────────
D.2 - STRUCTURE DU PROJET PYTHON DE BOB
────────────────────────────────────────────────────────────────────────

product-api/
├── pyproject.toml                    <- Configuration moderne Python
├── README.md
├── .python-version                   <- Version Python requise (3.11)
├── requirements.txt                  <- Dépendances figées (pour déploiement)
├── requirements-dev.txt              <- Dépendances dev/test
├── src/
│   └── product_api/
│       ├── __init__.py
│       ├── main.py                   <- Point d'entrée FastAPI
│       ├── models.py                 <- Pydantic models
│       ├── routes/
│       │   ├── __init__.py
│       │   └── products.py           <- Routes /products
│       └── services/
│           ├── __init__.py
│           └── validation_service.py  <- Utilise product-validator-py
└── tests/
    ├── __init__.py
    ├── conftest.py
    └── test_products.py


────────────────────────────────────────────────────────────────────────
D.3 - LE pyproject.toml DE BOB (Complet et commenté)
────────────────────────────────────────────────────────────────────────

# product-api/pyproject.toml

[build-system]
requires = ["setuptools>=68.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "product-api"
version = "1.0.0"
description = "API REST de gestion des produits - ProductManager"
readme = "README.md"
license = {text = "Proprietary"}
authors = [
    {name = "Bob Dupont", email = "bob@productmanager.local"}
]
requires-python = ">=3.11"

# ====================================================
#  DÉPENDANCES PRINCIPALES
# ====================================================
dependencies = [
    # Framework API
    "fastapi>=0.104.0,<0.200.0",
    "uvicorn[standard]>=0.24.0",

    # Validation et sérialisation
    "pydantic>=2.0.0",
    "pydantic-settings>=2.0.0",

    # Bibliothèque de validation d'Alice
    # Installée depuis Nexus pypi-hosted
    # Version exacte pour reproductibilité
    "product-validator-py==1.0.0",
    #
    # [IDEE] Comment cette dépendance est résolue par pip :
    # 1. pip interroge Nexus pypi-all (group)
    # 2. Nexus cherche dans pypi-hosted -> TROUVÉ! (publié par Alice)
    # 3. pip télécharge product_validator_py-1.0.0-py3-none-any.whl
    # 4. Installation locale
    #
    # Nexus pypi-hosted prend la priorité sur pypi-proxy
    # Donc nos packages internes sont toujours préférés

    # HTTP client pour tests internes
    "httpx>=0.25.0",

    # Base de données (SQLite pour le dev)
    "sqlalchemy>=2.0.0",
    "aiosqlite>=0.19.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.4.0",
    "pytest-asyncio>=0.21.0",
    "pytest-cov>=4.1.0",
    "httpx>=0.25.0",
    "black>=23.0.0",
    "ruff>=0.1.0",
    "mypy>=1.5.0",
]

[project.scripts]
product-api = "product_api.main:app"

[tool.setuptools.packages.find]
where = ["src"]

[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"

[tool.black]
line-length = 88
target-version = ["py311"]

[tool.ruff]
line-length = 88
target-version = "py311"


────────────────────────────────────────────────────────────────────────
D.4 - LE CODE PYTHON DE BOB
────────────────────────────────────────────────────────────────────────

════════════════════════
D.4.1 - models.py (Modèles Pydantic)
════════════════════════

# src/product_api/models.py

from pydantic import BaseModel, Field, field_validator
from typing import Optional
from decimal import Decimal
import re

class ProductCreate(BaseModel):
    """
    Modèle pour la création d'un produit.
    Validation Pydantic côté entrée HTTP.
    La validation métier est déléguée à product-validator-py.
    """
    sku: str = Field(
        ...,
        description="SKU du produit au format PROD-XXXX",
        example="PROD-ABC1"
    )
    name: str = Field(
        ...,
        min_length=1,
        max_length=200,
        description="Nom du produit",
        example="Widget Super Deluxe"
    )
    price: Decimal = Field(
        ...,
        gt=0,
        le=999999.99,
        decimal_places=2,
        description="Prix en euros (2 décimales max)",
        example=29.99
    )
    stock: int = Field(
        ...,
        ge=0,
        description="Quantité en stock",
        example=100
    )
    category: Optional[str] = Field(
        None,
        description="Catégorie du produit",
        example="Electronics"
    )

    @field_validator("sku")
    @classmethod
    def validate_sku_format(cls, v: str) -> str:
        """Validation Pydantic du format SKU (validation préliminaire)."""
        if not re.match(r'^PROD-[A-Z0-9]{4,8}$', v):
            raise ValueError(
                f"Format SKU invalide: '{v}'. "
                "Format attendu: PROD-[A-Z0-9]{{4,8}}"
            )
        return v


class ProductResponse(BaseModel):
    """Modèle de réponse pour un produit."""
    id: int
    sku: str
    name: str
    price: Decimal
    stock: int
    category: Optional[str]
    is_validated: bool = True


class ValidationError(BaseModel):
    """Détail d'une erreur de validation."""
    field: Optional[str] = None
    message: str


class ValidationResponse(BaseModel):
    """Réponse de validation d'un produit."""
    valid: bool
    errors: list[ValidationError] = []
    error_count: int = 0


════════════════════════
D.4.2 - validation_service.py (Intégration avec la lib d'Alice)
════════════════════════

# src/product_api/services/validation_service.py

"""
Service de validation qui utilise product-validator-py.

product-validator-py est un wrapper Python autour de la bibliothèque
Java développée par Alice (product-validator).

Ce wrapper est publié par Alice sur Nexus pypi-hosted et installé
par Bob via : pip install product-validator-py
"""

from product_validator_py import ProductValidator, ValidationResult
from product_api.models import ProductCreate, ValidationResponse, ValidationError
import logging

logger = logging.getLogger(__name__)


class ValidationService:
    """
    Service encapsulant la logique de validation.
    Utilise la bibliothèque partagée product-validator-py.
    """

    def __init__(self):
        # Instanciation du validateur partagé
        # product-validator-py est installé depuis Nexus pypi-hosted
        self._validator = ProductValidator()
        logger.info(
            f"ValidationService initialisé avec product-validator-py "
            f"v{self._validator.version}"
        )

    def validate_product(self, product: ProductCreate) -> ValidationResponse:
        """
        Valide un produit en utilisant les règles métier partagées.

        Les règles sont définies UNE SEULE FOIS par Alice en Java,
        exposées via le wrapper Python.
        Cela garantit la cohérence entre le service Java et l'API Python.
        """
        # Construire le dictionnaire attendu par le validateur
        product_data = {
            "sku": product.sku,
            "price": float(product.price),
            "stock": product.stock,
            "name": product.name,
        }

        # Appel de la validation (même logique que côté Java)
        result: ValidationResult = self._validator.validate(product_data)

        if result.is_valid:
            logger.debug(f"Produit {product.sku} validé avec succès")
            return ValidationResponse(valid=True)
        else:
            logger.warning(
                f"Produit {product.sku} invalide : {result.errors}"
            )
            validation_errors = [
                ValidationError(message=error)
                for error in result.errors
            ]
            return ValidationResponse(
                valid=False,
                errors=validation_errors,
                error_count=len(validation_errors)
            )

    def is_valid_sku(self, sku: str) -> bool:
        """Délègue la validation SKU à la bibliothèque partagée."""
        return self._validator.is_valid_sku(sku)


════════════════════════
D.4.3 - routes/products.py (Routes FastAPI)
════════════════════════

# src/product_api/routes/products.py

from fastapi import APIRouter, HTTPException, Depends, status
from fastapi.responses import JSONResponse
from product_api.models import (
    ProductCreate,
    ProductResponse,
    ValidationResponse
)
from product_api.services.validation_service import ValidationService
import logging

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/products", tags=["products"])


def get_validation_service() -> ValidationService:
    """Dependency injection du service de validation."""
    return ValidationService()


@router.post(
    "/",
    response_model=ProductResponse,
    status_code=status.HTTP_201_CREATED,
    summary="Créer un nouveau produit",
    description="Crée un produit après validation complète via product-validator-py"
)
async def create_product(
    product: ProductCreate,
    validation_svc: ValidationService = Depends(get_validation_service)
):
    """
    Endpoint de création de produit.

    Flux de traitement :
    1. Pydantic valide le format HTTP (types, formats)
    2. ValidationService valide les règles métier (via product-validator-py)
    3. Si valide -> Sauvegarde en base
    4. Si invalide -> 422 avec détails des erreurs
    """
    # Validation métier via la bibliothèque partagée d'Alice
    validation = validation_svc.validate_product(product)

    if not validation.valid:
        logger.warning(
            f"Tentative de création d'un produit invalide : {product.sku}"
        )
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail={
                "message": "Données produit invalides",
                "errors": [e.dict() for e in validation.errors]
            }
        )

    # TODO: Sauvegarder en base de données
    # Pour l'exemple, on renvoie un objet fictif
    logger.info(f"Produit créé avec succès : {product.sku}")

    return ProductResponse(
        id=1,
        sku=product.sku,
        name=product.name,
        price=product.price,
        stock=product.stock,
        category=product.category,
        is_validated=True
    )


@router.post(
    "/validate",
    response_model=ValidationResponse,
    summary="Valider un produit sans le créer"
)
async def validate_product(
    product: ProductCreate,
    validation_svc: ValidationService = Depends(get_validation_service)
):
    """
    Endpoint de validation seule.
    Utile pour les clients qui veulent valider avant d'envoyer.
    """
    return validation_svc.validate_product(product)


════════════════════════
D.4.4 - main.py (Application FastAPI)
════════════════════════

# src/product_api/main.py

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from product_api.routes.products import router as products_router
import logging

# Configuration du logging
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)

app = FastAPI(
    title="Product Manager API",
    description="""
    ## API de gestion des produits

    Cette API utilise **product-validator-py** (publié sur Nexus) pour
    la validation des règles métier. La bibliothèque de validation est
    développée en Java par Alice et wrappée en Python.

    Cela garantit que les règles métier sont identiques quel que soit
    le service qui les utilise.
    """,
    version="1.0.0",
    docs_url="/docs",
    redoc_url="/redoc"
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(products_router)

@app.get("/health", tags=["system"])
async def health_check():
    """Health check endpoint."""
    return {
        "status": "healthy",
        "service": "product-api",
        "version": "1.0.0"
    }

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000, reload=True)


────────────────────────────────────────────────────────────────────────
D.5 - SETUP ENVIRONNEMENT PYTHON DE BOB
────────────────────────────────────────────────────────────────────────

════════════════════════
D.5.1 - Création de l'environnement virtuel
════════════════════════

# Bob crée son environnement de développement

# 1. Créer le projet
mkdir product-api && cd product-api

# 2. Créer environnement virtuel (isolé du système)
python3.11 -m venv venv

# 3. Activer l'environnement
source venv/bin/activate      # Linux/Mac
# venv\Scripts\activate       # Windows

# 4. Vérifier que pip utilise bien la bonne version
which pip
# -> /home/bob/product-api/venv/bin/pip

# 5. Installer les dépendances (depuis Nexus via pip.conf)
pip install -e ".[dev]"

# Sortie attendue :
# Looking in indexes: http://192.168.1.50:8081/repository/pypi-all/simple
# Obtaining file:///home/bob/product-api
# Collecting fastapi>=0.104.0
#   Downloading http://192.168.1.50:8081/.../fastapi-0.104.1-py3-none-any.whl (92 kB)
# Collecting product-validator-py==1.0.0
#   Downloading http://192.168.1.50:8081/.../product_validator_py-1.0.0-py3-none-any.whl (12 kB)
#   <- Installé depuis pypi-HOSTED (le package d'Alice !)
# ...
# Successfully installed fastapi-0.104.1 product-validator-py-1.0.0 ...

# 6. Générer requirements.txt figé (pour le déploiement)
pip freeze > requirements.txt

# requirements.txt contient les versions exactes de tout :
# fastapi==0.104.1
# product-validator-py==1.0.0
# uvicorn==0.24.0
# pydantic==2.5.0
# ...


════════════════════════
D.5.2 - Lancer l'API en développement
════════════════════════

# Activer le venv si pas déjà fait
source venv/bin/activate

# Lancer le serveur de développement
uvicorn product_api.main:app --reload --host 0.0.0.0 --port 8000

# Sortie :
# INFO:     Will watch for changes in these directories: ['/home/bob/product-api']
# INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
# INFO:     ValidationService initialisé avec product-validator-py v1.0.0

# Tester l'API
curl -X POST http://localhost:8000/products/ \
  -H "Content-Type: application/json" \
  -d '{
    "sku": "PROD-ABC1",
    "name": "Widget Super Deluxe",
    "price": 29.99,
    "stock": 100,
    "category": "Electronics"
  }'

# Réponse :
# {
#   "id": 1,
#   "sku": "PROD-ABC1",
#   "name": "Widget Super Deluxe",
#   "price": 29.99,
#   "stock": 100,
#   "category": "Electronics",
#   "is_validated": true
# }

# Tester avec un SKU invalide
curl -X POST http://localhost:8000/products/ \
  -H "Content-Type: application/json" \
  -d '{
    "sku": "invalid-sku",
    "name": "Produit test",
    "price": 10.00,
    "stock": 5
  }'

# Réponse :
# HTTP 422 Unprocessable Entity
# {
#   "detail": {
#     "message": "Données produit invalides",
#     "errors": [
#       {"message": "SKU invalide : 'invalid-sku'. Format attendu : PROD-[A-Z0-9]{4,8}"}
#     ]
#   }
# }

# Documentation interactive disponible :
# http://localhost:8000/docs    (Swagger UI)
# http://localhost:8000/redoc  (ReDoc)


════════════════════════
D.5.3 - Publication d'un package Python par Bob
════════════════════════

Bob développe aussi des utilitaires Python réutilisables.
Il les publie sur Nexus pour que l'équipe puisse les utiliser.

# Exemple : Publier product-api-client sur Nexus
# (Client Python pour l'API, réutilisable dans d'autres services)

# Structure du package :
product-api-client/
├── pyproject.toml
└── src/
    └── product_api_client/
        ├── __init__.py
        └── client.py

# pyproject.toml du client :
[project]
name = "product-api-client"
version = "1.0.0"
description = "Client Python pour l'API ProductManager"
dependencies = ["httpx>=0.25.0", "pydantic>=2.0.0"]

# Configuration de publication vers Nexus
[tool.twine]
repository-url = "http://192.168.1.50:8081/repository/pypi-hosted/"
skip-existing = true

# ~/.pypirc de Bob
[distutils]
index-servers =
    nexus

[nexus]
repository = http://192.168.1.50:8081/repository/pypi-hosted/
username = bob
password = BobSecure@2024!

# Builder le package
pip install build
python -m build

# Sortie :
# Successfully built product_api_client-1.0.0.tar.gz
# Successfully built product_api_client-1.0.0-py3-none-any.whl

# Publier vers Nexus
pip install twine
twine upload -r nexus dist/*

# Sortie :
# Uploading distributions to http://192.168.1.50:8081/repository/pypi-hosted/
# Uploading product_api_client-1.0.0-py3-none-any.whl
# 100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8.2/8.2 kB • 00:00
# Uploading product_api_client-1.0.0.tar.gz
# 100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.1/6.1 kB • 00:00

# [OK] Vérifier dans Nexus :
# Browse -> pypi-hosted -> product-api-client / 1.0.0


════════════════════════════════════════════════════════════════════════
SECTION E : COLLABORATION ET INTÉGRATION ENTRE LES TROIS
════════════════════════════════════════════════════════════════════════

────────────────────────────────────────────────────────────────────────
E.1 - LE WRAPPER PYTHON D'ALICE (product-validator-py)
────────────────────────────────────────────────────────────────────────

Alice crée un wrapper Python de sa bibliothèque Java.
Ce wrapper est publié sur Nexus et utilisé par Bob.

Structure :
product-validator-py/
├── pyproject.toml
├── README.md
└── src/
    └── product_validator_py/
        ├── __init__.py
        └── validator.py       <- Wrapper Python pur (réimplémentation)


════════════════════════
E.1.1 - validator.py (Implémentation Python des règles Java)
════════════════════════

# src/product_validator_py/validator.py

"""
Wrapper Python de la bibliothèque Java product-validator.

Cette classe réimplémente en Python PUR les règles de validation
définies dans la bibliothèque Java d'Alice.

IMPORTANT : Les règles doivent rester synchronisées !
Quand Alice modifie une règle en Java -> Alice met à jour ce fichier.
-> Publier une nouvelle version sur Nexus (ex: 1.1.0)
-> Bob mettra à jour sa dépendance

Versions :
- product-validator (Java) : 1.0.0
- product-validator-py (Python) : 1.0.0  <- Correspondance
"""

import re
from dataclasses import dataclass, field
from typing import Any

__version__ = "1.0.0"


@dataclass
class ValidationResult:
    """
    Résultat de validation, équivalent à
    com.productmanager.validator.ValidationResult en Java.
    """
    is_valid: bool
    errors: list[str] = field(default_factory=list)
    error_count: int = 0

    def __post_init__(self):
        self.error_count = len(self.errors)

    @property
    def first_error(self) -> str | None:
        """Premier message d'erreur ou None."""
        return self.errors[0] if self.errors else None

    def __str__(self) -> str:
        if self.is_valid:
            return "ValidationResult{VALID}"
        return f"ValidationResult{{INVALID, errors={self.errors}}}"


class ProductValidator:
    """
    Validateur de produits - équivalent Python de
    com.productmanager.validator.ProductValidator.

    Exemple d'usage :
        validator = ProductValidator()
        result = validator.validate({
            "sku": "PROD-ABC1",
            "price": 29.99,
            "stock": 100,
            "name": "Widget"
        })
        if result.is_valid:
            print("Produit valide!")
    """

    # Format SKU : PROD- suivi de 4 à 8 caractères alphanumériques majuscules
    _SKU_PATTERN = re.compile(r'^PROD-[A-Z0-9]{4,8}$')

    # Prix maximum
    _MAX_PRICE = 999_999.99

    @property
    def version(self) -> str:
        """Version du validateur (correspond à la version Java)."""
        return __version__

    def validate(self, product_data: dict[str, Any]) -> ValidationResult:
        """
        Valide un produit selon toutes les règles métier.

        Args:
            product_data: Dictionnaire avec :
                - "sku"   : str  (obligatoire)
                - "price" : float (obligatoire)
                - "stock" : int   (obligatoire)
                - "name"  : str   (obligatoire)

        Returns:
            ValidationResult avec is_valid et liste d'erreurs
        """
        errors = []

        # Règle 1 : Validation SKU
        errors.extend(self._validate_sku(product_data))

        # Règle 2 : Validation prix
        errors.extend(self._validate_price(product_data))

        # Règle 3 : Validation stock
        errors.extend(self._validate_stock(product_data))

        # Règle 4 : Validation nom
        errors.extend(self._validate_name(product_data))

        return ValidationResult(is_valid=len(errors) == 0, errors=errors)

    def is_valid_sku(self, sku: str) -> bool:
        """
        Valide uniquement le format d'un SKU.
        Équivalent à ProductValidator.isValidSku() en Java.
        """
        return sku is not None and bool(self._SKU_PATTERN.match(sku))

    def is_valid_price(self, price: float) -> bool:
        """
        Valide uniquement un prix.
        Équivalent à ProductValidator.isValidPrice() en Java.
        """
        return 0 < price <= self._MAX_PRICE

    def _validate_sku(self, data: dict) -> list[str]:
        """Règle de validation SKU (doit rester synchronisée avec Java)."""
        errors = []
        sku = data.get("sku")

        if sku is None:
            errors.append("Le champ 'sku' est obligatoire")
        elif not str(sku).strip():
            errors.append("Le SKU ne peut pas être vide")
        elif not self._SKU_PATTERN.match(str(sku).strip()):
            errors.append(
                f"SKU invalide : '{sku}'. "
                "Format attendu : PROD-[A-Z0-9]{4,8} "
                "(ex: PROD-ABC1, PROD-XY12Z)"
            )

        return errors

    def _validate_price(self, data: dict) -> list[str]:
        """Règle de validation prix (doit rester synchronisée avec Java)."""
        errors = []
        price = data.get("price")

        if price is None:
            errors.append("Le champ 'price' est obligatoire")
            return errors

        try:
            price_float = float(price)
            if price_float <= 0:
                errors.append(
                    f"Le prix doit être positif. Valeur reçue : {price_float:.2f}"
                )
            elif price_float > self._MAX_PRICE:
                errors.append(
                    f"Le prix dépasse le maximum autorisé ({self._MAX_PRICE:.2f}). "
                    f"Valeur reçue : {price_float:.2f}"
                )

            # Vérification décimales
            price_str = str(price)
            if '.' in price_str and len(price_str.split('.')[1]) > 2:
                errors.append(
                    f"Le prix ne peut pas avoir plus de 2 décimales. "
                    f"Valeur reçue : {price}"
                )
        except (ValueError, TypeError):
            errors.append(f"Le prix doit être un nombre. Valeur reçue : {price}")

        return errors

    def _validate_stock(self, data: dict) -> list[str]:
        """Règle de validation stock."""
        errors = []
        stock = data.get("stock")

        if stock is None:
            errors.append("Le champ 'stock' est obligatoire")
        else:
            try:
                stock_int = int(stock)
                if stock_int < 0:
                    errors.append(
                        f"Le stock ne peut pas être négatif. Valeur reçue : {stock_int}"
                    )
            except (ValueError, TypeError):
                errors.append(f"Le stock doit être un entier. Valeur reçue : {stock}")

        return errors

    def _validate_name(self, data: dict) -> list[str]:
        """Règle de validation du nom."""
        errors = []
        name = data.get("name")

        if name is None:
            errors.append("Le champ 'name' est obligatoire")
        elif not str(name).strip():
            errors.append("Le nom du produit ne peut pas être vide")
        elif len(str(name)) > 200:
            errors.append(
                f"Le nom dépasse 200 caractères. Longueur actuelle : {len(str(name))}"
            )

        return errors


════════════════════════
E.1.2 - Publication du wrapper par Alice
════════════════════════

# pyproject.toml de product-validator-py
[build-system]
requires = ["setuptools>=68.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "product-validator-py"
version = "1.0.0"
description = "Wrapper Python de product-validator (bibliothèque Java)"
requires-python = ">=3.11"
dependencies = []   # Aucune dépendance externe !

# ~/.pypirc d'Alice (pour publier sur Nexus)
[distutils]
index-servers = nexus
[nexus]
repository = http://192.168.1.50:8081/repository/pypi-hosted/
username = alice
password = AliceSecure@2024!

# Build et publication par Alice
cd product-validator-py
python -m build
twine upload -r nexus dist/*

# [OK] Disponible pour Bob via :
# pip install product-validator-py==1.0.0


────────────────────────────────────────────────────────────────────────
E.2 - CI/CD PAR CHARLIE (GitHub Actions)
────────────────────────────────────────────────────────────────────────

Charlie configure les pipelines CI/CD pour automatiser :
- Les tests à chaque push
- Les déploiements vers Nexus à chaque merge

════════════════════════
E.2.1 - Pipeline Java (product-validator)
════════════════════════

# .github/workflows/java-ci.yml

name: Java CI - product-validator

on:
  push:
    branches: [ main, develop ]
    paths:
      - 'product-validator/**'
  pull_request:
    branches: [ main ]
    paths:
      - 'product-validator/**'

jobs:
  test:
    name: Tests Java
    runs-on: ubuntu-latest

    steps:
      - name: Checkout du code
        uses: actions/checkout@v4

      - name: Installation Java 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
          cache: 'maven'

      # Configurer Maven pour utiliser Nexus
      # Les credentials sont dans les GitHub Secrets du repo
      - name: Configuration Maven -> Nexus
        run: |
          mkdir -p ~/.m2
          cat > ~/.m2/settings.xml << EOF
          <settings>
            <servers>
              <server>
                <id>nexus-releases</id>
                <username>${{ secrets.NEXUS_USERNAME }}</username>
                <password>${{ secrets.NEXUS_PASSWORD }}</password>
              </server>
              <server>
                <id>nexus-snapshots</id>
                <username>${{ secrets.NEXUS_USERNAME }}</username>
                <password>${{ secrets.NEXUS_PASSWORD }}</password>
              </server>
              <server>
                <id>nexus-public</id>
                <username>${{ secrets.NEXUS_USERNAME }}</username>
                <password>${{ secrets.NEXUS_PASSWORD }}</password>
              </server>
            </servers>
            <mirrors>
              <mirror>
                <id>nexus-mirror</id>
                <url>http://192.168.1.50:8081/repository/maven-public/</url>
                <mirrorOf>*</mirrorOf>
              </mirror>
            </mirrors>
          </settings>
          EOF

      - name: Tests Maven
        working-directory: product-validator
        run: mvn clean test

      - name: Rapport de tests
        uses: dorny/test-reporter@v1
        if: always()
        with:
          name: Tests JUnit
          path: product-validator/target/surefire-reports/*.xml
          reporter: java-junit

  deploy-snapshot:
    name: Déploiement SNAPSHOT -> Nexus
    runs-on: ubuntu-latest
    needs: test
    # Seulement sur la branche develop (pas main)
    if: github.ref == 'refs/heads/develop'

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
          cache: 'maven'

      - name: Configuration Maven
        run: |
          # (même config que ci-dessus)
          mkdir -p ~/.m2
          # ...

      - name: Deploy SNAPSHOT vers Nexus
        working-directory: product-validator
        run: |
          # S'assurer que c'est bien une version SNAPSHOT
          VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
          if [[ "$VERSION" != *"-SNAPSHOT" ]]; then
            echo "Erreur: version $VERSION n'est pas un SNAPSHOT"
            exit 1
          fi
          mvn clean deploy -DskipTests

      - name: Notification Slack
        uses: rtCamp/action-slack-notify@v2
        env:
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
          SLACK_MESSAGE: |
            [OK] product-validator SNAPSHOT déployé sur Nexus
            Version: ${{ steps.version.outputs.version }}
            Par: ${{ github.actor }}

  deploy-release:
    name: Déploiement RELEASE -> Nexus
    runs-on: ubuntu-latest
    needs: test
    # Seulement sur la branche main ET si c'est un tag
    if: github.ref == 'refs/heads/main' && startsWith(github.ref, 'refs/tags/v')

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Deploy RELEASE vers Nexus
        working-directory: product-validator
        run: |
          VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
          if [[ "$VERSION" == *"-SNAPSHOT" ]]; then
            echo "Erreur: Impossible de release une version SNAPSHOT"
            exit 1
          fi
          mvn clean deploy -DskipTests
          echo "[OK] Release $VERSION déployée sur Nexus maven-releases"

      - name: Créer GitHub Release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: Release ${{ github.ref }}
          body: "Déployé sur Nexus maven-releases"


════════════════════════
E.2.2 - Pipeline Python (product-api)
════════════════════════

# .github/workflows/python-ci.yml

name: Python CI - product-api

on:
  push:
    branches: [ main, develop ]
    paths:
      - 'product-api/**'
  pull_request:
    branches: [ main ]
    paths:
      - 'product-api/**'

jobs:
  test:
    name: Tests Python
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Installation Python 3.11
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
          cache: 'pip'

      # Configurer pip pour utiliser Nexus
      - name: Configuration pip -> Nexus
        run: |
          mkdir -p ~/.pip
          cat > ~/.pip/pip.conf << EOF
          [global]
          index-url = http://${{ secrets.NEXUS_USERNAME }}:${{ secrets.NEXUS_PASSWORD }}@192.168.1.50:8081/repository/pypi-all/simple
          trusted-host = 192.168.1.50
          timeout = 120
          EOF
          #
          # IMPORTANT :
          # index-url avec credentials embarqués dans l'URL
          # Alternative sécurisée : Utiliser keyring ou .netrc

      - name: Installation des dépendances
        working-directory: product-api
        run: |
          pip install -e ".[dev]"
          # pip télécharge depuis Nexus pypi-all (group)
          # - product-validator-py depuis pypi-hosted (package interne)
          # - fastapi, pydantic, etc. depuis pypi-proxy (cache PyPI.org)

      - name: Linting avec Ruff
        working-directory: product-api
        run: ruff check src/ tests/

      - name: Formatage avec Black
        working-directory: product-api
        run: black --check src/ tests/

      - name: Typage avec mypy
        working-directory: product-api
        run: mypy src/

      - name: Tests avec pytest
        working-directory: product-api
        run: |
          pytest tests/ \
            --cov=product_api \
            --cov-report=xml \
            --cov-report=term-missing \
            -v

      - name: Upload couverture
        uses: codecov/codecov-action@v3

  build-docker:
    name: Build image Docker
    runs-on: ubuntu-latest
    needs: test

    steps:
      - uses: actions/checkout@v4

      # Autoriser le registry HTTP interne
      - name: Configurer Docker insecure registry
        run: |
          cat > /etc/docker/daemon.json << EOF
          {"insecure-registries": ["192.168.1.50:8082"]}
          EOF
          sudo systemctl daemon-reload
          sudo systemctl restart docker

      - name: Login Docker Nexus
        run: |
          docker login 192.168.1.50:8082 \
            -u ${{ secrets.NEXUS_USERNAME }} \
            -p ${{ secrets.NEXUS_PASSWORD }}

      - name: Build image Docker
        working-directory: product-api
        run: |
          VERSION=$(cat pyproject.toml | grep '^version' | cut -d'"' -f2)
          docker build \
            --build-arg NEXUS_URL=http://192.168.1.50:8081/repository/pypi-all/simple \
            --build-arg NEXUS_USER=${{ secrets.NEXUS_USERNAME }} \
            --build-arg NEXUS_PASS=${{ secrets.NEXUS_PASSWORD }} \
            -t 192.168.1.50:8082/product-api:${VERSION} \
            -t 192.168.1.50:8082/product-api:latest \
            .

      - name: Push vers Nexus Docker Registry
        run: |
          VERSION=$(cat product-api/pyproject.toml | grep '^version' | cut -d'"' -f2)
          docker push 192.168.1.50:8082/product-api:${VERSION}
          docker push 192.168.1.50:8082/product-api:latest
          echo "[OK] Image Docker poussée vers Nexus"


════════════════════════
E.2.3 - Dockerfile de l'API Python (avec Nexus)
════════════════════════

# product-api/Dockerfile

# Stage 1 : Build
# Utilise l'image Python depuis le cache Docker Nexus
FROM 192.168.1.50:8083/python:3.11-slim AS builder
# ^ 192.168.1.50:8083 = docker-proxy (cache Docker Hub)
# python:3.11-slim sera téléchargé depuis Docker Hub UNE SEULE FOIS
# et ensuite servi depuis le cache Nexus

WORKDIR /build

# Arguments pour Nexus (passés au build)
ARG NEXUS_URL
ARG NEXUS_USER
ARG NEXUS_PASS

# Configurer pip pour utiliser Nexus pendant le build Docker
# IMPORTANT : Pas de credentials dans l'image finale !
RUN pip config set global.index-url http://${NEXUS_USER}:${NEXUS_PASS}@192.168.1.50:8081/repository/pypi-all/simple && \
    pip config set global.trusted-host 192.168.1.50

# Copier et installer dépendances
COPY pyproject.toml .
RUN pip install --no-cache-dir build

COPY src/ src/
RUN pip install --no-cache-dir ".[dev]"

# Stage 2 : Image finale (légère, sans credentials Nexus)
FROM 192.168.1.50:8083/python:3.11-slim AS runtime

WORKDIR /app

# Copier les packages installés depuis le stage builder
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
COPY src/ src/

# Variables d'environnement de l'application
ENV PYTHONPATH=/app/src
ENV PORT=8000
ENV LOG_LEVEL=info

# Exposition du port
EXPOSE 8000

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

# Commande de démarrage
CMD ["uvicorn", "product_api.main:app", \
     "--host", "0.0.0.0", \
     "--port", "8000", \
     "--workers", "4"]


════════════════════════════════════════════════════════════════════════
SECTION F : SCÉNARIOS DU QUOTIDIEN
════════════════════════════════════════════════════════════════════════

Voici des situations réelles vécues par l'équipe et comment Nexus
intervient dans chaque cas.

────────────────────────────────────────────────────────────────────────
F.1 - SCÉNARIO 1 : Alice publie une correction de bug
────────────────────────────────────────────────────────────────────────

SITUATION :
Bob remonte un bug : la validation du prix accepte les prix avec
3 décimales (ex: 10.999) alors qu'elle ne devrait pas.
Alice doit corriger et republier.

WORKFLOW :
──────────

# Alice corrige le bug dans PriceValidationRule.java
# Le bug : La vérification des décimales ne fonctionnait pas pour les Doubles

# 1. Alice code la correction dans PriceValidationRule.java

# 2. Alice met à jour la version SNAPSHOT (développement de 1.0.1)
# pom.xml : <version>1.0.1-SNAPSHOT</version>

# 3. Alice run les tests (y compris le test qui reproduit le bug)
mvn clean test

# 4. Alice déploie le SNAPSHOT
mvn clean deploy

# Le SNAPSHOT 1.0.1-SNAPSHOT est dans maven-snapshots
# Bob peut déjà tester si besoin

# 5. Alice corrige AUSSI le wrapper Python product-validator-py
# validator.py : méthode _validate_price()
# pyproject.toml : version = "1.0.1"

# 6. Alice build et publie le wrapper Python
python -m build
twine upload -r nexus dist/*

# -> product-validator-py-1.0.1 disponible sur Nexus pypi-hosted

# 7. Alice prévient Bob dans Slack :
# "J'ai corrigé le bug du prix. product-validator-py 1.0.1 est dispo sur Nexus.
#  Mets à jour ta dépendance : product-validator-py==1.0.1"

# 8. Bob met à jour dans pyproject.toml :
# "product-validator-py==1.0.1"
# pip install -e .
# -> Nexus télécharge la nouvelle version depuis pypi-hosted

# 9. Bob vérifie que le bug est corrigé dans son API
uvicorn product_api.main:app --reload
curl -X POST http://localhost:8000/products/ \
  -H "Content-Type: application/json" \
  -d '{"sku": "PROD-ABC1", "name": "Test", "price": 10.999, "stock": 5}'
# Expected : 422 avec message sur les décimales [OK]

# 10. Alice fait la release officielle
# pom.xml : <version>1.0.1</version>
mvn clean deploy

# La release 1.0.1 est dans maven-releases
# Immuable, versionnée, traçable dans Nexus


────────────────────────────────────────────────────────────────────────
F.2 - SCÉNARIO 2 : Bob installe des dépendances lourdes (numpy, pandas)
────────────────────────────────────────────────────────────────────────

SITUATION :
Bob doit ajouter des statistiques à l'API. Il a besoin de numpy et pandas.
Sans Nexus : 300+ MB à télécharger à chaque build CI.
Avec Nexus : Téléchargé UNE FOIS, mis en cache, servi en local ensuite.

WORKFLOW :
──────────

# 1. Bob ajoute les dépendances dans pyproject.toml
[project]
dependencies = [
    ...
    "numpy>=1.26.0",
    "pandas>=2.1.0",
]

# 2. Première installation (sur le poste de Bob)
pip install -e .
# pip télécharge depuis PyPI.org via le proxy Nexus
# Le téléchargement prend 2-3 minutes (300+ MB depuis internet)

# 3. Nexus met en cache numpy et pandas dans pypi-proxy

# 4. Deuxième installation (sur le poste de Charlie ou en CI)
pip install -e .
# <- pip télécharge depuis Nexus pypi-proxy (cache local)
# -> 10x plus rapide (réseau interne vs internet)

# Vérifier dans Nexus UI :
# Browse -> pypi-proxy -> Components
# numpy / 1.26.2   (en cache)
# pandas / 2.1.4   (en cache)

# En CI, économies :
# Avant Nexus : 5 minutes à chaque build (téléchargement internet)
# Après Nexus : 30 secondes (téléchargement réseau interne)


────────────────────────────────────────────────────────────────────────
F.3 - SCÉNARIO 3 : Charlie configure le cleanup automatique
────────────────────────────────────────────────────────────────────────

SITUATION :
Après 3 mois, maven-snapshots est plein de vieilles versions SNAPSHOT.
Charlie configure le cleanup automatique.

WORKFLOW :
──────────

# 1. Charlie crée une Cleanup Policy dans Nexus UI

# System -> Cleanup Policies -> Create Cleanup Policy
# Name               : cleanup-old-snapshots
# Format             : maven2
# Cleanup Criteria   :
#   Published Before : 30 days
#   Asset Name Matcher: .*-SNAPSHOT.*

# 2. Charlie applique la policy au repository
# Repositories -> maven-snapshots -> Edit
# Cleanup Policies : [x] cleanup-old-snapshots
# Save

# 3. Charlie crée une Cleanup Policy pour PyPI
# Name               : cleanup-old-pypi-snapshots
# Format             : pypi
# Published Before   : 30 days

# Applique à pypi-hosted
# Repositories -> pypi-hosted -> Edit
# Cleanup Policies : [x] cleanup-old-pypi-snapshots

# 4. Charlie vérifie les tasks automatiques
# System -> Tasks -> Voir "Repository - Cleanup repositories using their associated policies"
# -> Exécution automatique quotidienne à 1h du matin

# 5. Charlie exécute manuellement pour voir l'effet
# System -> Tasks -> "Repository - Delete orphaned snapshot artifacts"
# -> Run

# 6. Compacter le blob store pour libérer l'espace réel
# System -> Tasks -> "Admin - Compact blob store"
# Blob store : default -> Run

# Résultat :
# Avant : 45 GB utilisés
# Après : 28 GB utilisés
# -> 17 GB libérés en nettoyant les snapshots obsolètes !


────────────────────────────────────────────────────────────────────────
F.4 - SCÉNARIO 4 : L'internet tombe pendant un sprint
────────────────────────────────────────────────────────────────────────

SITUATION :
Un vendredi après-midi, la connexion internet de l'équipe est coupée.
Sprint critique, les builds doivent continuer.

AVEC NEXUS :
──────────

# Alice continue à builder normalement
mvn clean install
# Maven cherche les dépendances dans Nexus
# Nexus a tout en cache (maven-central-proxy)
# [OK] Build réussi même sans internet !

# Bob continue aussi
pip install -r requirements.txt
# pip télécharge depuis pypi-proxy (tout en cache)
# [OK] Installation réussie même sans internet !

# Les packages internes (product-validator-py, product-api-client)
# sont dans pypi-hosted -> Pas besoin d'internet du tout

# Docker pulls depuis docker-proxy (cache Docker Hub)
docker pull 192.168.1.50:8083/python:3.11-slim
# [OK] L'image est en cache dans Nexus

SANS NEXUS (simulation) :
────────────────────────
# mvn clean install -> BLOQUÉ sur téléchargement Maven Central [X]
# pip install requests -> timeout sur PyPI.org [X]
# Toute l'équipe est bloquée [X]


────────────────────────────────────────────────────────────────────────
F.5 - SCÉNARIO 5 : Onboarding d'un 4ème développeur (Diana)
────────────────────────────────────────────────────────────────────────

SITUATION :
Diana rejoint l'équipe. Charlie lui crée un compte Nexus et lui envoie
un guide d'onboarding. En 30 minutes, Diana est opérationnelle.

WORKFLOW ONBOARDING :
─────────────────────

# 1. Charlie crée le compte Diana dans Nexus
# Security -> Users -> Create local user
# ID       : diana
# Role     : java-developer-role + python-developer-role
# Password : TemporaryPass@2024! (Diana devra changer)

# 2. Charlie envoie un email d'onboarding avec :
# - URL Nexus : http://192.168.1.50:8081
# - Credentials temporaires
# - Instructions ci-dessous

# ──────────────────────────────────────────────
# GUIDE ONBOARDING NEXUS - Diana
# ──────────────────────────────────────────────

# ÉTAPE 1 : Configurer Maven (si Java)
mkdir -p ~/.m2
cat > ~/.m2/settings.xml << 'EOF'
<settings>
  <servers>
    <server><id>nexus-releases</id><username>diana</username><password>TemporaryPass@2024!</password></server>
    <server><id>nexus-snapshots</id><username>diana</username><password>TemporaryPass@2024!</password></server>
    <server><id>nexus-public</id><username>diana</username><password>TemporaryPass@2024!</password></server>
  </servers>
  <mirrors>
    <mirror>
      <id>nexus</id>
      <url>http://192.168.1.50:8081/repository/maven-public/</url>
      <mirrorOf>*</mirrorOf>
    </mirror>
  </mirrors>
</settings>
EOF

# ÉTAPE 2 : Configurer pip (si Python)
mkdir -p ~/.pip
cat > ~/.pip/pip.conf << 'EOF'
[global]
index-url = http://192.168.1.50:8081/repository/pypi-all/simple
trusted-host = 192.168.1.50
EOF

# ÉTAPE 3 : Configurer npm (si JavaScript)
cat >> ~/.npmrc << 'EOF'
registry=http://192.168.1.50:8081/repository/npm-group/
always-auth=true
_auth=GENERER_AVEC_echo -n "diana:TemporaryPass@2024!" | base64
EOF

# ÉTAPE 4 : Configurer Docker
sudo tee /etc/docker/daemon.json << 'EOF'
{"insecure-registries": ["192.168.1.50:8082", "192.168.1.50:8083"]}
EOF
sudo systemctl restart docker

# ÉTAPE 5 : Tester que tout fonctionne
mvn archetype:generate -DgroupId=com.test -DartifactId=test -DinteractiveMode=false
cd test && mvn clean install
# [OK] Si ça marche -> Maven est configuré

pip install requests
python -c "import requests; print('pip OK')"
# [OK] Si ça marche -> pip est configuré

docker pull 192.168.1.50:8083/nginx:alpine
# [OK] Si ça marche -> Docker est configuré

# 3. Diana peut cloner les projets et commencer à travailler immédiatement !


════════════════════════════════════════════════════════════════════════
SECTION G : TROUBLESHOOTING VÉCU PAR L'ÉQUIPE
════════════════════════════════════════════════════════════════════════

────────────────────────────────────────────────────────────────────────
G.1 - PROBLÈME : Alice ne peut pas déployer (401 Unauthorized)
────────────────────────────────────────────────────────────────────────

SYMPTÔME :
mvn clean deploy
# [ERROR] Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin...
# [ERROR] Return code is: 401, ReasonPhrase: Unauthorized.

DIAGNOSTIC :
──────────

# 1. Vérifier que l'<id> dans pom.xml correspond à settings.xml
# pom.xml
<distributionManagement>
  <repository>
    <id>nexus-releases</id>   <- CET ID
    ...
  </repository>
</distributionManagement>

# settings.xml
<server>
  <id>nexus-releases</id>    <- DOIT ÊTRE IDENTIQUE
  <username>alice</username>
  <password>AliceSecure@2024!</password>
</server>

# 2. Vérifier le mot de passe
curl -u alice:AliceSecure@2024! http://192.168.1.50:8081/service/rest/v1/status
# Si 200 -> Credentials OK
# Si 401 -> Mauvais mot de passe

# 3. Vérifier les permissions
# Nexus UI -> Security -> Users -> alice -> Voir rôles
# Le rôle java-developer-role doit inclure :
# nx-repository-view-maven2-maven-releases-add

SOLUTION :
─────────
# Charlie vérifie les permissions dans Nexus UI
# et corrige le rôle si nécessaire.
# Ou Alice s'est trompée de mot de passe -> Réinitialiser.


────────────────────────────────────────────────────────────────────────
G.2 - PROBLÈME : Bob ne trouve pas product-validator-py (404)
────────────────────────────────────────────────────────────────────────

SYMPTÔME :
pip install product-validator-py==1.0.0
# ERROR: Could not find a version that satisfies the requirement product-validator-py==1.0.0
# ERROR: No matching distribution found for product-validator-py==1.0.0

DIAGNOSTIC :
──────────

# 1. Vérifier que pip utilise bien Nexus
pip config list
# Doit montrer index-url = http://192.168.1.50:8081/...

# 2. Vérifier que le package existe dans Nexus
# Nexus UI -> Browse -> pypi-hosted -> Components
# -> product-validator-py présent ?

# 3. Vérifier via l'API Nexus
curl -u bob:BobSecure@2024! \
  "http://192.168.1.50:8081/service/rest/v1/search?repository=pypi-hosted&name=product-validator-py"
# Si résultat vide -> Alice n'a pas encore publié !

# 4. Tester l'accès à l'index pip
curl http://192.168.1.50:8081/repository/pypi-all/simple/product-validator-py/
# Doit retourner la liste des versions disponibles

SOLUTION :
─────────
# Alice n'avait pas encore publié le wrapper Python.
# Alice publie : twine upload -r nexus dist/*
# Bob réessaie : pip install product-validator-py==1.0.0


────────────────────────────────────────────────────────────────────────
G.3 - PROBLÈME : Docker pull échoue (SSL error)
────────────────────────────────────────────────────────────────────────

SYMPTÔME :
docker pull 192.168.1.50:8083/nginx:alpine
# Error response from daemon: Get "https://192.168.1.50:8083/v2/":
# http: server gave HTTP response to HTTPS client

DIAGNOSTIC :
──────────
# Docker tente HTTPS par défaut même pour 192.168.1.50:8083
# Solution : Configurer les "insecure-registries"

SOLUTION :
─────────

# Sur le poste concerné :
sudo nano /etc/docker/daemon.json

# Ajouter :
{
  "insecure-registries": [
    "192.168.1.50:8082",
    "192.168.1.50:8083"
  ]
}

# Redémarrer Docker
sudo systemctl daemon-reload
sudo systemctl restart docker

# Retester
docker pull 192.168.1.50:8083/nginx:alpine
# [OK] Fonctionne maintenant


────────────────────────────────────────────────────────────────────────
G.4 - PROBLÈME : Maven utilise encore Maven Central (pas Nexus)
────────────────────────────────────────────────────────────────────────

SYMPTÔME :
mvn clean install
# [INFO] Downloading from central: https://repo.maven.apache.org/maven2/...
# (téléchargement depuis internet au lieu de Nexus)

DIAGNOSTIC :
──────────
# Le mirror n'est pas correctement configuré
# OU un pom.xml parent override le mirror

# Vérifier la config effective Maven
mvn help:effective-settings | grep -A5 "mirror"

SOLUTION :
─────────

# S'assurer que settings.xml contient :
<mirrors>
  <mirror>
    <id>nexus-mirror</id>
    <url>http://192.168.1.50:8081/repository/maven-public/</url>
    <mirrorOf>*</mirrorOf>   <- L'astérisque est crucial
  </mirror>
</mirrors>

# Si un pom.xml parent définit un repository qui bypasse le mirror :
# Ajouter dans le pom.xml du projet :
<repositories>
  <!-- Forcer l'utilisation du mirror en supprimant les autres repos -->
</repositories>

# Ou forcer avec -s :
mvn clean install -s ~/.m2/settings.xml


────────────────────────────────────────────────────────────────────────
G.5 - PROBLÈME : La version SNAPSHOT de Bob est obsolète
────────────────────────────────────────────────────────────────────────

SITUATION :
Alice a publié une nouvelle version de product-validator-py SNAPSHOT.
Bob continue à utiliser l'ancienne version.

SYMPTÔME :
# Bob utilise encore product-validator-py-1.1.0.dev20240115
# Alice a publié product-validator-py-1.1.0.dev20240116 (avec le fix)

SOLUTION :
─────────

# Bob force la mise à jour du package
pip install --upgrade product-validator-py==1.1.0.dev0

# Ou pour les SNAPSHOTs Python (dev releases) :
pip install --pre product-validator-py

# Vérifier la version installée
pip show product-validator-py
# Name: product-validator-py
# Version: 1.1.0.dev20240116
# [OK] Bonne version !

# Pour les SNAPSHOTs Maven (côté Alice/Charlie) :
mvn clean install -U   # -U force la mise à jour des snapshots


════════════════════════════════════════════════════════════════════════
SECTION H : BILAN ET BONNES PRATIQUES APPRISES
════════════════════════════════════════════════════════════════════════

────────────────────────────────────────────────────────────────────────
H.1 - CE QUE NEXUS A APPORTÉ À L'ÉQUIPE
────────────────────────────────────────────────────────────────────────

════════════════════════
AVANT NEXUS (problèmes)
════════════════════════

[X] Alice envoyait le JAR par Slack à Bob -> Bob l'intégrait manuellement
[X] Deux versions différentes de la logique de validation coexistaient
[X] 5-10 minutes de téléchargement à chaque build CI
[X] Builds impossibles si internet en panne
[X] Pas de traçabilité des versions utilisées
[X] Onboarding d'un nouveau développeur : 1-2 jours de configuration

════════════════════════
APRÈS NEXUS (gains)
════════════════════════

[OK] Alice publie sur Nexus -> Bob installe via pip -> Toujours synchronisé
[OK] Une seule source de vérité pour les règles de validation
[OK] 10-30 secondes de téléchargement (cache local Nexus)
[OK] Builds indépendants d'internet
[OK] Chaque version tracée, horodatée, immuable (releases)
[OK] Onboarding nouveau dev : 30 minutes


────────────────────────────────────────────────────────────────────────
H.2 - MÉTRIQUES CONCRÈTES
────────────────────────────────────────────────────────────────────────

TEMPS DE BUILD CI (moyen)
  Avant  : 7 minutes 34 secondes  (téléchargement Internet)
  Après  : 52 secondes            (téléchargement Nexus local)
  Gain   : -88%

BANDE PASSANTE (hebdomadaire)
  Avant  : 4.2 GB      (5 devs × 3 builds/jour × 280 MB dépendances)
  Après  : 280 MB      (premier téléchargement uniquement)
  Gain   : -93%

TAUX DE SUCCÈS BUILDS CI
  Avant  : 87%         (13% échouaient pour rate limits / timeouts PyPI / Maven Central)
  Après  : 99.8%       (Seuls pannes infra interne)

TEMPS RÉSOLUTION BUG PARTAGÉ
  Avant  : 2-3 jours   (partage manuel, intégration manuelle par Bob)
  Après  : < 1 heure   (Alice publie -> Bob installe la nouvelle version)


────────────────────────────────────────────────────────────────────────
H.3 - BONNES PRATIQUES APPRISES
────────────────────────────────────────────────────────────────────────

════════════════════════
POUR ALICE (Java Developer)
════════════════════════

[OK] TOUJOURS développer en -SNAPSHOT et ne passer en release qu'au bon moment
[OK] NE JAMAIS modifier une release déjà publiée -> Incrémenter la version
[OK] Publier sources ET javadoc (les collègues en ont besoin)
[OK] Maintenir STRICTEMENT la synchronisation entre le Java et le wrapper Python
[OK] Documenter les breaking changes dans le CHANGELOG
[OK] Tester le wrapper Python avant de publier (éviter que Bob soit bloqué)

════════════════════════
POUR BOB (Python Developer)
════════════════════════

[OK] Toujours utiliser un virtualenv par projet
[OK] Toujours figer les versions dans requirements.txt pour la production
[OK] Utiliser == pour les packages internes (critiques)
[OK] Utiliser >= pour les packages publics (flexibilité)
[OK] Vérifier dans Nexus UI avant de signaler un bug "package introuvable"
[OK] Ne jamais faire pip install sans venv activé

════════════════════════
POUR CHARLIE (DevOps)
════════════════════════

[OK] TOUJOURS utiliser des comptes dédiés (jamais partager les credentials admin)
[OK] Principle of least privilege pour chaque développeur
[OK] Cleanup policies configurées dès le début (avant que le disque sature)
[OK] Backups automatisés quotidiens testés mensuellement
[OK] Documentation d'onboarding tenue à jour
[OK] Utiliser des tokens API en CI/CD (jamais les vrais passwords)
[OK] Monitoring Nexus (espace disque, mémoire, uptime)

════════════════════════
POUR TOUTE L'ÉQUIPE
════════════════════════

[OK] Semantic Versioning strictement respecté (MAJOR.MINOR.PATCH)
[OK] Changelog maintenu à chaque release
[OK] Pull Requests obligatoires avant merge sur main
[OK] Les releases n'arrivent qu'après merge sur main + tag Git
[OK] Jamais de credentials dans le code source (Git)
[OK] Credentials dans GitHub Secrets pour CI/CD


────────────────────────────────────────────────────────────────────────
H.4 - RÉCAPITULATIF DES URLS NEXUS DE L'ÉQUIPE
────────────────────────────────────────────────────────────────────────

Interface Web Nexus     : http://192.168.1.50:8081

Repositories Maven :
  maven-public          : http://192.168.1.50:8081/repository/maven-public/
  maven-releases        : http://192.168.1.50:8081/repository/maven-releases/
  maven-snapshots       : http://192.168.1.50:8081/repository/maven-snapshots/
  maven-central-proxy   : http://192.168.1.50:8081/repository/maven-central-proxy/

Repositories PyPI :
  pypi-all              : http://192.168.1.50:8081/repository/pypi-all/simple
  pypi-hosted           : http://192.168.1.50:8081/repository/pypi-hosted/
  pypi-proxy            : http://192.168.1.50:8081/repository/pypi-proxy/

Repositories Docker :
  docker-hosted         : http://192.168.1.50:8082/
  docker-proxy          : http://192.168.1.50:8083/

Repositories Raw :
  raw-scripts           : http://192.168.1.50:8081/repository/raw-scripts/


────────────────────────────────────────────────────────────────────────
H.5 - COMMANDES RAPIDES DE RÉFÉRENCE
────────────────────────────────────────────────────────────────────────

## JAVA (Alice) ##

# Build et test
mvn clean test

# Déployer snapshot vers Nexus
mvn clean deploy

# Voir version actuelle
mvn help:evaluate -Dexpression=project.version -q -DforceStdout

# Forcer maj des snapshots
mvn clean install -U

# Debug Maven (voir toutes les requêtes vers Nexus)
mvn clean install -X 2>&1 | grep "nexus\|Downloading\|Downloaded"


## PYTHON (Bob) ##

# Créer venv et installer dépendances depuis Nexus
python3 -m venv venv && source venv/bin/activate
pip install -e ".[dev]"

# Installer package interne spécifique
pip install product-validator-py==1.0.0

# Forcer mise à jour d'un package
pip install --upgrade product-validator-py

# Publier sur Nexus
python -m build && twine upload -r nexus dist/*

# Vérifier quelle version est installée
pip show product-validator-py


## DOCKER (Charlie) ##

# Pull depuis Docker Hub via cache Nexus
docker pull 192.168.1.50:8083/nginx:alpine

# Login au registry privé
docker login 192.168.1.50:8082

# Push image vers Nexus
docker tag product-api:1.0.0 192.168.1.50:8082/product-api:1.0.0
docker push 192.168.1.50:8082/product-api:1.0.0

# Pull image privée depuis Nexus
docker pull 192.168.1.50:8082/product-api:latest


## NEXUS ADMIN (Charlie) ##

# Vérifier statut Nexus
curl -u admin:pass http://192.168.1.50:8081/service/rest/v1/status

# Lister tous les repositories
curl -u admin:pass http://192.168.1.50:8081/service/rest/v1/repositories

# Chercher un artifact
curl -u admin:pass "http://192.168.1.50:8081/service/rest/v1/search?name=product-validator"

# Voir les logs en temps réel
docker logs -f nexus | grep -E "ERROR|WARN"

# Relancer Nexus
cd /opt/nexus && docker-compose restart nexus


═══════════════════════════════════════════════════════════════════════════
* FIN DE LA SECTION - CAS PRATIQUE ÉQUIPE DE 3 DÉVELOPPEURS *
═══════════════════════════════════════════════════════════════════════════

Résumé de ce cas pratique :

  [UTILISATEUR] 3 développeurs : Alice (Java), Bob (Python), Charlie (DevOps)
  [CONSTRUCTION] 1 application : product-validator (Java) + product-api (Python)
  [LIEN] 1 lien fort  : product-validator-py (wrapper publié via Nexus)
  [PACKAGE] 10 repositories Nexus configurés et utilisés quotidiennement
  [RAPIDE] Gains mesurables : -88% temps de build, -93% bande passante

  La clé du succès : Nexus comme source unique de vérité pour tous les
  artifacts, quel que soit le langage ou l'outil utilisé par chaque
  développeur de l'équipe.

═══════════════════════════════════════════════════════════════════════════