From d6ceefdeda533ad4706f39da198adb2e4ab54120 Mon Sep 17 00:00:00 2001 From: andil Date: Tue, 4 Aug 2026 11:45:04 +0000 Subject: [PATCH 01/14] chore: dockerize app and automate database initialization --- backend/.dockerignore | 7 +++ backend/Dockerfile | 15 ++++++ backend/package.json | 3 +- backend/src/scripts/initDatabase.js | 55 +++++++++++++++++++ docker-compose.dev.yml | 84 +++++++++++++++++++++++++++++ frontend/.dockerignore | 7 +++ frontend/Dockerfile | 22 ++++++++ frontend/nginx.conf | 11 ++++ frontend/package-lock.json | 39 +++++++++----- 9 files changed, 229 insertions(+), 14 deletions(-) create mode 100644 backend/.dockerignore create mode 100644 backend/Dockerfile create mode 100644 backend/src/scripts/initDatabase.js create mode 100644 docker-compose.dev.yml create mode 100644 frontend/.dockerignore create mode 100644 frontend/Dockerfile create mode 100644 frontend/nginx.conf diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..7e5a332 --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,7 @@ +node_modules +npm-debug.log +.env +coverage +.git +.gitignore +README.md diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..771b161 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,15 @@ +FROM node:22-alpine + +WORKDIR /app + +COPY package*.json ./ +RUN npm ci + +COPY prisma ./prisma +RUN npx prisma generate + +COPY . . + +EXPOSE 3000 + +CMD ["npm", "start"] diff --git a/backend/package.json b/backend/package.json index abc8476..5a7af54 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,7 +8,8 @@ "generate:tickets": "node src/scripts/generateTickets.js", "test": "jest --runInBand", "test:watch": "jest --watch", - "test:coverage": "jest --coverage" + "test:coverage": "jest --coverage", + "init:db": "node src/scripts/initDatabase.js" }, "keywords": [], "author": "", diff --git a/backend/src/scripts/initDatabase.js b/backend/src/scripts/initDatabase.js new file mode 100644 index 0000000..3d7645c --- /dev/null +++ b/backend/src/scripts/initDatabase.js @@ -0,0 +1,55 @@ +const { execSync } = require("child_process"); +const prisma = require("../config/prisma"); + +async function main() { + let lotCount = await prisma.lot.count(); + let ticketCount = await prisma.ticket.count(); + + console.log(`Lots présents : ${lotCount}`); + console.log(`Tickets présents : ${ticketCount}`); + + if (lotCount === 0) { + console.log("Aucun lot trouvé : exécution du seed..."); + execSync("npx prisma db seed", { + stdio: "inherit", + env: process.env, + }); + + lotCount = await prisma.lot.count(); + console.log(`Lots présents après seed : ${lotCount}`); + } else { + console.log("Les lots existent déjà : seed ignoré."); + } + + if (lotCount !== 5) { + throw new Error( + `Initialisation interrompue : 5 lots attendus, ${lotCount} trouvé(s).` + ); + } + + ticketCount = await prisma.ticket.count(); + + if (ticketCount === 0) { + console.log("Aucun ticket trouvé : génération des tickets..."); + execSync("npm run generate:tickets", { + stdio: "inherit", + env: process.env, + }); + + ticketCount = await prisma.ticket.count(); + console.log(`Tickets présents après génération : ${ticketCount}`); + } else { + console.log("Les tickets existent déjà : génération ignorée."); + } + + console.log("Initialisation de la base terminée."); +} + +main() + .catch((error) => { + console.error("Erreur pendant l'initialisation de la base :", error); + process.exitCode = 1; + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..8df08a2 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,84 @@ +services: + postgres: + image: postgres:16-alpine + container_name: tiptop-postgres-dev + restart: unless-stopped + + environment: + POSTGRES_USER: tiptop + POSTGRES_PASSWORD: tiptop_dev_password + POSTGRES_DB: the_tip_top_db + + volumes: + - tiptop_postgres_dev_data:/var/lib/postgresql/data + + healthcheck: + test: ["CMD-SHELL", "pg_isready -U tiptop -d the_tip_top_db"] + interval: 10s + timeout: 5s + retries: 5 + + networks: + - tiptop-dev + + backend: + build: + context: ./backend + dockerfile: Dockerfile + + container_name: tiptop-backend-dev + restart: unless-stopped + + depends_on: + postgres: + condition: service_healthy + + environment: + PORT: 5000 + DATABASE_URL: postgresql://tiptop:tiptop_dev_password@postgres:5432/the_tip_top_db + JWT_SECRET: dev_secret_change_me + APP_URL: http://localhost:5000 + + MAIL_HOST: smtp.gmail.com + MAIL_PORT: 587 + MAIL_USER: "" + MAIL_PASS: "" + MAIL_FROM: "The Tip Top " + + ports: + - "5001:5000" + + command: > + sh -c " + npx prisma migrate deploy && + npm run init:db && + npm start + " + + networks: + - tiptop-dev + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + args: + VITE_API_URL: http://localhost:5001 + + container_name: tiptop-frontend-dev + restart: unless-stopped + + depends_on: + - backend + + ports: + - "5173:80" + + networks: + - tiptop-dev + +volumes: + tiptop_postgres_dev_data: + +networks: + tiptop-dev: diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..cf4ed03 --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,7 @@ +node_modules +dist +npm-debug.log +.env +.git +.gitignore +README.md diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..f5e98df --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,22 @@ +FROM node:22-alpine AS build + +WORKDIR /app + +COPY package*.json ./ +RUN npm ci + +COPY . . + +ARG VITE_API_URL +ENV VITE_API_URL=${VITE_API_URL} + +RUN npm run build + + +FROM nginx:1.27-alpine + +COPY --from=build /app/dist /usr/share/nginx/html + +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..3aa17e6 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,11 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a814f40..2bf4f85 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -57,7 +57,6 @@ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -267,10 +266,33 @@ "node": ">=6.9.0" } }, + "node_modules/@emnapi/core": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz", + "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", + "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", "dev": true, "license": "MIT", "optional": true, @@ -1159,7 +1181,6 @@ "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1206,7 +1227,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1297,7 +1317,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -1460,7 +1479,6 @@ "integrity": "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -2379,7 +2397,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -2441,7 +2458,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -2451,7 +2467,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -2692,7 +2707,6 @@ "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -2817,7 +2831,6 @@ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } From fbeeb518a0cfb5ef64331a101700234731ae8b56 Mon Sep 17 00:00:00 2001 From: Andil Date: Tue, 4 Aug 2026 11:56:13 +0000 Subject: [PATCH 02/14] ci: add Jenkins pipeline --- Jenkinsfile | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..4898b4b --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,119 @@ +pipeline { + agent any + + options { + disableConcurrentBuilds() + timestamps() + } + + environment { + CI_NETWORK = "tiptop-ci-${BUILD_NUMBER}" + CI_DATABASE = "tiptop-ci-db-${BUILD_NUMBER}" + DATABASE_URL = "postgresql://tiptop_ci:tiptop_ci_password@${CI_DATABASE}:5432/the_tip_top_ci" + } + + stages { + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Backend - Tests') { + steps { + sh ''' + docker network create "${CI_NETWORK}" + + docker run -d \ + --name "${CI_DATABASE}" \ + --network "${CI_NETWORK}" \ + -e POSTGRES_USER=tiptop_ci \ + -e POSTGRES_PASSWORD=tiptop_ci_password \ + -e POSTGRES_DB=the_tip_top_ci \ + postgres:16-alpine + + echo "Attente de PostgreSQL..." + + for i in $(seq 1 30); do + if docker exec "${CI_DATABASE}" \ + pg_isready -U tiptop_ci -d the_tip_top_ci; then + echo "PostgreSQL est prêt." + break + fi + + if [ "$i" -eq 30 ]; then + echo "PostgreSQL ne répond pas." + exit 1 + fi + + sleep 2 + done + + docker run --rm \ + --network "${CI_NETWORK}" \ + --user "$(id -u):$(id -g)" \ + -e HOME=/tmp \ + -e DATABASE_URL="${DATABASE_URL}" \ + -e JWT_SECRET=jenkins_ci_secret \ + -e NODE_ENV=test \ + -v "${WORKSPACE}/backend:/app" \ + -w /app \ + node:22-alpine \ + sh -c " + npm ci && + npx prisma generate && + npx prisma migrate deploy && + npm test + " + ''' + } + } + + stage('Frontend - Lint and Build') { + steps { + sh ''' + docker run --rm \ + --user "$(id -u):$(id -g)" \ + -e HOME=/tmp \ + -v "${WORKSPACE}/frontend:/app" \ + -w /app \ + node:22-alpine \ + sh -c " + npm ci && + npm run lint && + npm run build + " + ''' + } + } + + stage('Docker Build') { + steps { + sh ''' + docker compose \ + -f docker-compose.dev.yml \ + build + ''' + } + } + } + + post { + always { + sh ''' + docker rm -f "${CI_DATABASE}" 2>/dev/null || true + docker network rm "${CI_NETWORK}" 2>/dev/null || true + ''' + + deleteDir() + } + + success { + echo 'Pipeline CI réussi.' + } + + failure { + echo 'Pipeline CI en échec. Consulte la Console Output.' + } + } +} From f7f8afd484a540ae6399947c30567282ac152a13 Mon Sep 17 00:00:00 2001 From: Andil Date: Tue, 4 Aug 2026 12:01:34 +0000 Subject: [PATCH 03/14] fix: use host Jenkins workspace in Docker CI --- Jenkinsfile | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 4898b4b..624a678 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -4,12 +4,17 @@ pipeline { options { disableConcurrentBuilds() timestamps() + skipDefaultCheckout(true) } environment { CI_NETWORK = "tiptop-ci-${BUILD_NUMBER}" CI_DATABASE = "tiptop-ci-db-${BUILD_NUMBER}" + DATABASE_URL = "postgresql://tiptop_ci:tiptop_ci_password@${CI_DATABASE}:5432/the_tip_top_ci" + + HOST_JENKINS_HOME = "/opt/furious-ducks/data/jenkins" + HOST_WORKSPACE = "${HOST_JENKINS_HOME}/workspace/${JOB_NAME}" } stages { @@ -19,6 +24,21 @@ pipeline { } } + stage('Vérification du workspace') { + steps { + sh ''' + echo "Workspace Jenkins : ${WORKSPACE}" + echo "Workspace hôte : ${HOST_WORKSPACE}" + + ls -lah backend + test -f backend/package.json + test -f backend/package-lock.json + test -f frontend/package.json + test -f frontend/package-lock.json + ''' + } + } + stage('Backend - Tests') { steps { sh ''' @@ -43,6 +63,7 @@ pipeline { if [ "$i" -eq 30 ]; then echo "PostgreSQL ne répond pas." + docker logs "${CI_DATABASE}" exit 1 fi @@ -56,7 +77,7 @@ pipeline { -e DATABASE_URL="${DATABASE_URL}" \ -e JWT_SECRET=jenkins_ci_secret \ -e NODE_ENV=test \ - -v "${WORKSPACE}/backend:/app" \ + -v "${HOST_WORKSPACE}/backend:/app" \ -w /app \ node:22-alpine \ sh -c " @@ -75,7 +96,7 @@ pipeline { docker run --rm \ --user "$(id -u):$(id -g)" \ -e HOME=/tmp \ - -v "${WORKSPACE}/frontend:/app" \ + -v "${HOST_WORKSPACE}/frontend:/app" \ -w /app \ node:22-alpine \ sh -c " @@ -90,6 +111,8 @@ pipeline { stage('Docker Build') { steps { sh ''' + cd "${WORKSPACE}" + docker compose \ -f docker-compose.dev.yml \ build @@ -104,8 +127,6 @@ pipeline { docker rm -f "${CI_DATABASE}" 2>/dev/null || true docker network rm "${CI_NETWORK}" 2>/dev/null || true ''' - - deleteDir() } success { From 4b04bf48e967baaf48b0c374ed913ac00d947bc0 Mon Sep 17 00:00:00 2001 From: Andil Date: Tue, 4 Aug 2026 12:04:25 +0000 Subject: [PATCH 04/14] fix: share Jenkins workspace with CI containers --- Jenkinsfile | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 624a678..b63fe07 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -10,11 +10,7 @@ pipeline { environment { CI_NETWORK = "tiptop-ci-${BUILD_NUMBER}" CI_DATABASE = "tiptop-ci-db-${BUILD_NUMBER}" - DATABASE_URL = "postgresql://tiptop_ci:tiptop_ci_password@${CI_DATABASE}:5432/the_tip_top_ci" - - HOST_JENKINS_HOME = "/opt/furious-ducks/data/jenkins" - HOST_WORKSPACE = "${HOST_JENKINS_HOME}/workspace/${JOB_NAME}" } stages { @@ -28,9 +24,10 @@ pipeline { steps { sh ''' echo "Workspace Jenkins : ${WORKSPACE}" - echo "Workspace hôte : ${HOST_WORKSPACE}" ls -lah backend + ls -lah frontend + test -f backend/package.json test -f backend/package-lock.json test -f frontend/package.json @@ -72,13 +69,13 @@ pipeline { docker run --rm \ --network "${CI_NETWORK}" \ + --volumes-from furious-jenkins \ --user "$(id -u):$(id -g)" \ -e HOME=/tmp \ -e DATABASE_URL="${DATABASE_URL}" \ -e JWT_SECRET=jenkins_ci_secret \ -e NODE_ENV=test \ - -v "${HOST_WORKSPACE}/backend:/app" \ - -w /app \ + -w "${WORKSPACE}/backend" \ node:22-alpine \ sh -c " npm ci && @@ -94,10 +91,10 @@ pipeline { steps { sh ''' docker run --rm \ + --volumes-from furious-jenkins \ --user "$(id -u):$(id -g)" \ -e HOME=/tmp \ - -v "${HOST_WORKSPACE}/frontend:/app" \ - -w /app \ + -w "${WORKSPACE}/frontend" \ node:22-alpine \ sh -c " npm ci && @@ -111,8 +108,6 @@ pipeline { stage('Docker Build') { steps { sh ''' - cd "${WORKSPACE}" - docker compose \ -f docker-compose.dev.yml \ build From 95aae8a63a4d96dad7016bb583e7a67716877dce Mon Sep 17 00:00:00 2001 From: Andil Date: Tue, 4 Aug 2026 13:58:08 +0000 Subject: [PATCH 05/14] ci: publish Docker images and deploy development environment --- Jenkinsfile | 87 ++++++++++++++++++++++++++++++----- backend/Dockerfile | 2 +- deploy/dev/docker-compose.yml | 74 +++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 13 deletions(-) create mode 100644 deploy/dev/docker-compose.yml diff --git a/Jenkinsfile b/Jenkinsfile index b63fe07..978797b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -10,7 +10,14 @@ pipeline { environment { CI_NETWORK = "tiptop-ci-${BUILD_NUMBER}" CI_DATABASE = "tiptop-ci-db-${BUILD_NUMBER}" + DATABASE_URL = "postgresql://tiptop_ci:tiptop_ci_password@${CI_DATABASE}:5432/the_tip_top_ci" + + REGISTRY = "localhost:5000" + IMAGE_TAG = "${BUILD_NUMBER}" + + BACKEND_IMAGE = "${REGISTRY}/the-tip-top/backend" + FRONTEND_IMAGE = "${REGISTRY}/the-tip-top/frontend" } stages { @@ -23,15 +30,14 @@ pipeline { stage('Vérification du workspace') { steps { sh ''' - echo "Workspace Jenkins : ${WORKSPACE}" - - ls -lah backend - ls -lah frontend - test -f backend/package.json test -f backend/package-lock.json test -f frontend/package.json test -f frontend/package-lock.json + test -f deploy/dev/docker-compose.yml + + echo "Commit : ${GIT_COMMIT}" + echo "Tag Docker : ${IMAGE_TAG}" ''' } } @@ -59,7 +65,6 @@ pipeline { fi if [ "$i" -eq 30 ]; then - echo "PostgreSQL ne répond pas." docker logs "${CI_DATABASE}" exit 1 fi @@ -72,6 +77,7 @@ pipeline { --volumes-from furious-jenkins \ --user "$(id -u):$(id -g)" \ -e HOME=/tmp \ + -e NPM_CONFIG_CACHE=/tmp/.npm \ -e DATABASE_URL="${DATABASE_URL}" \ -e JWT_SECRET=jenkins_ci_secret \ -e NODE_ENV=test \ @@ -87,13 +93,14 @@ pipeline { } } - stage('Frontend - Lint and Build') { + stage('Frontend - Lint et Build') { steps { sh ''' docker run --rm \ --volumes-from furious-jenkins \ --user "$(id -u):$(id -g)" \ -e HOME=/tmp \ + -e NPM_CONFIG_CACHE=/tmp/.npm \ -w "${WORKSPACE}/frontend" \ node:22-alpine \ sh -c " @@ -105,12 +112,68 @@ pipeline { } } - stage('Docker Build') { + stage('Construction des images Docker') { steps { sh ''' + docker build \ + -t "${BACKEND_IMAGE}:${IMAGE_TAG}" \ + -t "${BACKEND_IMAGE}:latest" \ + backend + + docker build \ + --build-arg VITE_API_URL=http://192.168.1.23:5001 \ + -t "${FRONTEND_IMAGE}:${IMAGE_TAG}" \ + -t "${FRONTEND_IMAGE}:latest" \ + frontend + ''' + } + } + + stage('Publication dans le Registry') { + steps { + sh ''' + docker push "${BACKEND_IMAGE}:${IMAGE_TAG}" + docker push "${BACKEND_IMAGE}:latest" + + docker push "${FRONTEND_IMAGE}:${IMAGE_TAG}" + docker push "${FRONTEND_IMAGE}:latest" + ''' + } + } + + stage('Déploiement DEV') { + steps { + sh ''' + REGISTRY="${REGISTRY}" \ + IMAGE_TAG="${IMAGE_TAG}" \ docker compose \ - -f docker-compose.dev.yml \ - build + -p tiptop-dev \ + -f deploy/dev/docker-compose.yml \ + up -d \ + --pull always \ + --remove-orphans + + echo "Attente du backend DEV..." + + for i in $(seq 1 30); do + if curl -fsS http://localhost:5001/ >/dev/null; then + echo "Backend DEV opérationnel." + break + fi + + if [ "$i" -eq 30 ]; then + docker compose \ + -p tiptop-dev \ + -f deploy/dev/docker-compose.yml \ + logs --tail=100 + exit 1 + fi + + sleep 5 + done + + curl -fsS http://localhost:5173/ >/dev/null + echo "Frontend DEV opérationnel." ''' } } @@ -125,11 +188,11 @@ pipeline { } success { - echo 'Pipeline CI réussi.' + echo "Pipeline CI/CD réussi. Version DEV déployée : ${IMAGE_TAG}" } failure { - echo 'Pipeline CI en échec. Consulte la Console Output.' + echo "Pipeline CI/CD en échec. Consulte la Console Output." } } } diff --git a/backend/Dockerfile b/backend/Dockerfile index 771b161..f09f7a9 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -10,6 +10,6 @@ RUN npx prisma generate COPY . . -EXPOSE 3000 +EXPOSE 5000 CMD ["npm", "start"] diff --git a/deploy/dev/docker-compose.yml b/deploy/dev/docker-compose.yml new file mode 100644 index 0000000..95a170f --- /dev/null +++ b/deploy/dev/docker-compose.yml @@ -0,0 +1,74 @@ +services: + postgres: + image: postgres:16-alpine + restart: unless-stopped + + environment: + POSTGRES_USER: tiptop + POSTGRES_PASSWORD: tiptop_dev_password + POSTGRES_DB: the_tip_top_db + + volumes: + - tiptop_postgres_dev_data:/var/lib/postgresql/data + + healthcheck: + test: ["CMD-SHELL", "pg_isready -U tiptop -d the_tip_top_db"] + interval: 10s + timeout: 5s + retries: 10 + + networks: + - tiptop-dev + + backend: + image: ${REGISTRY}/the-tip-top/backend:${IMAGE_TAG} + restart: unless-stopped + + depends_on: + postgres: + condition: service_healthy + + environment: + PORT: 5000 + DATABASE_URL: postgresql://tiptop:tiptop_dev_password@postgres:5432/the_tip_top_db + JWT_SECRET: dev_secret_change_me + APP_URL: http://192.168.1.23:5001 + MAIL_HOST: smtp.gmail.com + MAIL_PORT: 587 + MAIL_USER: "" + MAIL_PASS: "" + MAIL_FROM: "The Tip Top " + + command: > + sh -c " + npx prisma migrate deploy && + npm run init:db && + npm start + " + + ports: + - "5001:5000" + + networks: + - tiptop-dev + + frontend: + image: ${REGISTRY}/the-tip-top/frontend:${IMAGE_TAG} + restart: unless-stopped + + depends_on: + - backend + + ports: + - "5173:80" + + networks: + - tiptop-dev + +volumes: + tiptop_postgres_dev_data: + name: the-tip-top_tiptop_postgres_dev_data + +networks: + tiptop-dev: + name: tiptop-dev From bbab18351befc07935dbd90e2e92793f1774ea78 Mon Sep 17 00:00:00 2001 From: Andil Date: Tue, 4 Aug 2026 14:21:26 +0000 Subject: [PATCH 06/14] fix: rebuild CI/CD pipeline with persistent npm cache --- Jenkinsfile | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 978797b..8d4d8c2 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -36,7 +36,6 @@ pipeline { test -f frontend/package-lock.json test -f deploy/dev/docker-compose.yml - echo "Commit : ${GIT_COMMIT}" echo "Tag Docker : ${IMAGE_TAG}" ''' } @@ -75,16 +74,22 @@ pipeline { docker run --rm \ --network "${CI_NETWORK}" \ --volumes-from furious-jenkins \ + -v tiptop-npm-cache-backend:/npm-cache \ --user "$(id -u):$(id -g)" \ -e HOME=/tmp \ - -e NPM_CONFIG_CACHE=/tmp/.npm \ + -e NODE_OPTIONS=--dns-result-order=ipv4first \ + -e NPM_CONFIG_CACHE=/npm-cache \ + -e NPM_CONFIG_FETCH_RETRIES=5 \ + -e NPM_CONFIG_FETCH_RETRY_MINTIMEOUT=20000 \ + -e NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT=120000 \ + -e NPM_CONFIG_FETCH_TIMEOUT=600000 \ -e DATABASE_URL="${DATABASE_URL}" \ -e JWT_SECRET=jenkins_ci_secret \ -e NODE_ENV=test \ -w "${WORKSPACE}/backend" \ node:22-alpine \ sh -c " - npm ci && + npm ci --prefer-offline --no-audit --no-fund && npx prisma generate && npx prisma migrate deploy && npm test @@ -98,13 +103,19 @@ pipeline { sh ''' docker run --rm \ --volumes-from furious-jenkins \ + -v tiptop-npm-cache-frontend:/npm-cache \ --user "$(id -u):$(id -g)" \ -e HOME=/tmp \ - -e NPM_CONFIG_CACHE=/tmp/.npm \ + -e NODE_OPTIONS=--dns-result-order=ipv4first \ + -e NPM_CONFIG_CACHE=/npm-cache \ + -e NPM_CONFIG_FETCH_RETRIES=5 \ + -e NPM_CONFIG_FETCH_RETRY_MINTIMEOUT=20000 \ + -e NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT=120000 \ + -e NPM_CONFIG_FETCH_TIMEOUT=600000 \ -w "${WORKSPACE}/frontend" \ node:22-alpine \ sh -c " - npm ci && + npm ci --prefer-offline --no-audit --no-fund && npm run lint && npm run build " From 0d6835645704663b120d5f09b41c78f2fe516bab Mon Sep 17 00:00:00 2001 From: Andil Date: Tue, 4 Aug 2026 15:01:28 +0000 Subject: [PATCH 07/14] fix: verify DEV services through Docker network --- Jenkinsfile | 24 +++++++++++++++++++++--- deploy/dev/docker-compose.yml | 1 + 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 8d4d8c2..ecd3c7b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -152,7 +152,7 @@ pipeline { } } - stage('Déploiement DEV') { + stage('Déploiement DEV') { steps { sh ''' REGISTRY="${REGISTRY}" \ @@ -167,23 +167,41 @@ pipeline { echo "Attente du backend DEV..." for i in $(seq 1 30); do - if curl -fsS http://localhost:5001/ >/dev/null; then + if docker run --rm \ + --network tiptop-dev \ + curlimages/curl:8.12.1 \ + -fsS http://backend:5000/ >/dev/null; then + echo "Backend DEV opérationnel." break fi if [ "$i" -eq 30 ]; then + echo "Le backend DEV ne répond pas." + + docker compose \ + -p tiptop-dev \ + -f deploy/dev/docker-compose.yml \ + ps + docker compose \ -p tiptop-dev \ -f deploy/dev/docker-compose.yml \ logs --tail=100 + exit 1 fi sleep 5 done - curl -fsS http://localhost:5173/ >/dev/null + echo "Vérification du frontend DEV..." + + docker run --rm \ + --network tiptop-dev \ + curlimages/curl:8.12.1 \ + -fsS http://frontend/ >/dev/null + echo "Frontend DEV opérationnel." ''' } diff --git a/deploy/dev/docker-compose.yml b/deploy/dev/docker-compose.yml index 95a170f..8602afa 100644 --- a/deploy/dev/docker-compose.yml +++ b/deploy/dev/docker-compose.yml @@ -67,6 +67,7 @@ services: volumes: tiptop_postgres_dev_data: + external: true name: the-tip-top_tiptop_postgres_dev_data networks: From 92dfcbe61e8ed3a55e3a01baedd1dd4eb173f311 Mon Sep 17 00:00:00 2001 From: Andil Date: Tue, 4 Aug 2026 16:01:02 +0000 Subject: [PATCH 08/14] feat: add preproduction environment --- deploy/preprod/docker-compose.yml | 79 +++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 deploy/preprod/docker-compose.yml diff --git a/deploy/preprod/docker-compose.yml b/deploy/preprod/docker-compose.yml new file mode 100644 index 0000000..b61eef5 --- /dev/null +++ b/deploy/preprod/docker-compose.yml @@ -0,0 +1,79 @@ +services: + postgres: + image: postgres:16-alpine + restart: unless-stopped + + environment: + POSTGRES_USER: tiptop_preprod + POSTGRES_PASSWORD: tiptop_preprod_password + POSTGRES_DB: the_tip_top_preprod + + volumes: + - tiptop_preprod_postgres_data:/var/lib/postgresql/data + + healthcheck: + test: + [ + "CMD-SHELL", + "pg_isready -U tiptop_preprod -d the_tip_top_preprod" + ] + interval: 10s + timeout: 5s + retries: 10 + + networks: + - tiptop-preprod + + backend: + image: ${REGISTRY}/the-tip-top/backend:${IMAGE_TAG} + restart: unless-stopped + + depends_on: + postgres: + condition: service_healthy + + environment: + PORT: 5000 + NODE_ENV: preproduction + DATABASE_URL: postgresql://tiptop_preprod:tiptop_preprod_password@postgres:5432/the_tip_top_preprod + JWT_SECRET: preprod_secret_change_me + APP_URL: http://192.168.1.23:5002 + MAIL_HOST: smtp.gmail.com + MAIL_PORT: 587 + MAIL_USER: "" + MAIL_PASS: "" + MAIL_FROM: "The Tip Top Preprod " + + command: > + sh -c " + npx prisma migrate deploy && + npm run init:db && + npm start + " + + ports: + - "5002:5000" + + networks: + - tiptop-preprod + + frontend: + image: ${REGISTRY}/the-tip-top/frontend:${IMAGE_TAG} + restart: unless-stopped + + depends_on: + - backend + + ports: + - "5174:80" + + networks: + - tiptop-preprod + +volumes: + tiptop_preprod_postgres_data: + name: tiptop_preprod_postgres_data + +networks: + tiptop-preprod: + name: tiptop-preprod From ac702c8835e69429031751e0a2313e654ef85224 Mon Sep 17 00:00:00 2001 From: Andil Date: Tue, 4 Aug 2026 16:13:53 +0000 Subject: [PATCH 09/14] feat: route branches to dev and preproduction --- Jenkinsfile | 117 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 84 insertions(+), 33 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index ecd3c7b..1434d2b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,16 +8,15 @@ pipeline { } environment { - CI_NETWORK = "tiptop-ci-${BUILD_NUMBER}" - CI_DATABASE = "tiptop-ci-db-${BUILD_NUMBER}" - - DATABASE_URL = "postgresql://tiptop_ci:tiptop_ci_password@${CI_DATABASE}:5432/the_tip_top_ci" - REGISTRY = "localhost:5000" - IMAGE_TAG = "${BUILD_NUMBER}" BACKEND_IMAGE = "${REGISTRY}/the-tip-top/backend" FRONTEND_IMAGE = "${REGISTRY}/the-tip-top/frontend" + + CI_NETWORK = "tiptop-ci-${JOB_BASE_NAME}-${BUILD_NUMBER}" + CI_DATABASE = "tiptop-ci-db-${JOB_BASE_NAME}-${BUILD_NUMBER}" + + DATABASE_URL = "postgresql://tiptop_ci:tiptop_ci_password@${CI_DATABASE}:5432/the_tip_top_ci" } stages { @@ -27,6 +26,42 @@ pipeline { } } + stage('Préparer environnement') { + steps { + script { + env.IMAGE_TAG = "${env.BRANCH_NAME}-${env.BUILD_NUMBER}" + + if (env.BRANCH_NAME == 'main') { + env.DEPLOY_ENABLED = 'true' + env.DEPLOY_ENV = 'DEV' + env.COMPOSE_FILE = 'deploy/dev/docker-compose.yml' + env.COMPOSE_PROJECT = 'tiptop-dev' + env.DEPLOY_NETWORK = 'tiptop-dev' + env.FRONTEND_API_URL = 'http://192.168.1.23:5001' + } else if (env.BRANCH_NAME == 'develop') { + env.DEPLOY_ENABLED = 'true' + env.DEPLOY_ENV = 'PREPROD' + env.COMPOSE_FILE = 'deploy/preprod/docker-compose.yml' + env.COMPOSE_PROJECT = 'tiptop-preprod' + env.DEPLOY_NETWORK = 'tiptop-preprod' + env.FRONTEND_API_URL = 'http://192.168.1.23:5002' + } else { + env.DEPLOY_ENABLED = 'false' + env.DEPLOY_ENV = 'CI uniquement' + env.COMPOSE_FILE = '' + env.COMPOSE_PROJECT = '' + env.DEPLOY_NETWORK = '' + env.FRONTEND_API_URL = '' + } + + echo "Branche : ${env.BRANCH_NAME}" + echo "Tag Docker : ${env.IMAGE_TAG}" + echo "Environnement : ${env.DEPLOY_ENV}" + echo "Déploiement activé : ${env.DEPLOY_ENABLED}" + } + } + } + stage('Vérification du workspace') { steps { sh ''' @@ -34,9 +69,10 @@ pipeline { test -f backend/package-lock.json test -f frontend/package.json test -f frontend/package-lock.json - test -f deploy/dev/docker-compose.yml - echo "Tag Docker : ${IMAGE_TAG}" + if [ "${DEPLOY_ENABLED}" = "true" ]; then + test -f "${COMPOSE_FILE}" + fi ''' } } @@ -54,12 +90,12 @@ pipeline { -e POSTGRES_DB=the_tip_top_ci \ postgres:16-alpine - echo "Attente de PostgreSQL..." + echo "Attente de PostgreSQL CI..." for i in $(seq 1 30); do if docker exec "${CI_DATABASE}" \ pg_isready -U tiptop_ci -d the_tip_top_ci; then - echo "PostgreSQL est prêt." + echo "PostgreSQL CI est prêt." break fi @@ -124,70 +160,85 @@ pipeline { } stage('Construction des images Docker') { + when { + expression { + env.DEPLOY_ENABLED == 'true' + } + } + steps { sh ''' docker build \ -t "${BACKEND_IMAGE}:${IMAGE_TAG}" \ - -t "${BACKEND_IMAGE}:latest" \ backend docker build \ - --build-arg VITE_API_URL=http://192.168.1.23:5001 \ + --build-arg VITE_API_URL="${FRONTEND_API_URL}" \ -t "${FRONTEND_IMAGE}:${IMAGE_TAG}" \ - -t "${FRONTEND_IMAGE}:latest" \ frontend ''' } } stage('Publication dans le Registry') { + when { + expression { + env.DEPLOY_ENABLED == 'true' + } + } + steps { sh ''' docker push "${BACKEND_IMAGE}:${IMAGE_TAG}" - docker push "${BACKEND_IMAGE}:latest" - docker push "${FRONTEND_IMAGE}:${IMAGE_TAG}" - docker push "${FRONTEND_IMAGE}:latest" ''' } } - stage('Déploiement DEV') { + stage('Déploiement') { + when { + expression { + env.DEPLOY_ENABLED == 'true' + } + } + steps { sh ''' + echo "Déploiement de ${BRANCH_NAME} vers ${DEPLOY_ENV}" + REGISTRY="${REGISTRY}" \ IMAGE_TAG="${IMAGE_TAG}" \ docker compose \ - -p tiptop-dev \ - -f deploy/dev/docker-compose.yml \ + -p "${COMPOSE_PROJECT}" \ + -f "${COMPOSE_FILE}" \ up -d \ --pull always \ --remove-orphans - echo "Attente du backend DEV..." + echo "Attente du backend ${DEPLOY_ENV}..." for i in $(seq 1 30); do if docker run --rm \ - --network tiptop-dev \ + --network "${DEPLOY_NETWORK}" \ curlimages/curl:8.12.1 \ -fsS http://backend:5000/ >/dev/null; then - echo "Backend DEV opérationnel." + echo "Backend ${DEPLOY_ENV} opérationnel." break fi if [ "$i" -eq 30 ]; then - echo "Le backend DEV ne répond pas." + echo "Le backend ${DEPLOY_ENV} ne répond pas." docker compose \ - -p tiptop-dev \ - -f deploy/dev/docker-compose.yml \ + -p "${COMPOSE_PROJECT}" \ + -f "${COMPOSE_FILE}" \ ps docker compose \ - -p tiptop-dev \ - -f deploy/dev/docker-compose.yml \ - logs --tail=100 + -p "${COMPOSE_PROJECT}" \ + -f "${COMPOSE_FILE}" \ + logs --tail=150 exit 1 fi @@ -195,14 +246,14 @@ pipeline { sleep 5 done - echo "Vérification du frontend DEV..." + echo "Vérification du frontend ${DEPLOY_ENV}..." docker run --rm \ - --network tiptop-dev \ + --network "${DEPLOY_NETWORK}" \ curlimages/curl:8.12.1 \ -fsS http://frontend/ >/dev/null - echo "Frontend DEV opérationnel." + echo "Frontend ${DEPLOY_ENV} opérationnel." ''' } } @@ -217,11 +268,11 @@ pipeline { } success { - echo "Pipeline CI/CD réussi. Version DEV déployée : ${IMAGE_TAG}" + echo "Pipeline réussi pour ${BRANCH_NAME}. Environnement : ${DEPLOY_ENV}. Version : ${IMAGE_TAG}" } failure { - echo "Pipeline CI/CD en échec. Consulte la Console Output." + echo "Pipeline en échec pour ${BRANCH_NAME}. Consulte la Console Output." } } } From 466655f52d4ae3341aee1597114c3e8f139c56fb Mon Sep 17 00:00:00 2001 From: Andil Date: Tue, 4 Aug 2026 17:54:09 +0000 Subject: [PATCH 10/14] feat: add production deployment flow --- Jenkinsfile | 31 +++++++++---- deploy/prod/docker-compose.yml | 80 ++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 deploy/prod/docker-compose.yml diff --git a/Jenkinsfile b/Jenkinsfile index 1434d2b..5063b6b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -45,6 +45,13 @@ pipeline { env.COMPOSE_PROJECT = 'tiptop-preprod' env.DEPLOY_NETWORK = 'tiptop-preprod' env.FRONTEND_API_URL = 'http://192.168.1.23:5002' + } else if (env.BRANCH_NAME.startsWith('release-')) { + env.DEPLOY_ENABLED = 'true' + env.DEPLOY_ENV = 'PROD' + env.COMPOSE_FILE = 'deploy/prod/docker-compose.yml' + env.COMPOSE_PROJECT = 'tiptop-prod' + env.DEPLOY_NETWORK = 'tiptop-prod' + env.FRONTEND_API_URL = 'http://192.168.1.23:5003' } else { env.DEPLOY_ENABLED = 'false' env.DEPLOY_ENV = 'CI uniquement' @@ -195,6 +202,21 @@ pipeline { } } + stage('Validation production') { + when { + expression { + env.DEPLOY_ENV == 'PROD' + } + } + + steps { + timeout(time: 15, unit: 'MINUTES') { + input message: "Déployer ${IMAGE_TAG} en production ?", + ok: "Déployer" + } + } + } + stage('Déploiement') { when { expression { @@ -228,13 +250,6 @@ pipeline { fi if [ "$i" -eq 30 ]; then - echo "Le backend ${DEPLOY_ENV} ne répond pas." - - docker compose \ - -p "${COMPOSE_PROJECT}" \ - -f "${COMPOSE_FILE}" \ - ps - docker compose \ -p "${COMPOSE_PROJECT}" \ -f "${COMPOSE_FILE}" \ @@ -246,8 +261,6 @@ pipeline { sleep 5 done - echo "Vérification du frontend ${DEPLOY_ENV}..." - docker run --rm \ --network "${DEPLOY_NETWORK}" \ curlimages/curl:8.12.1 \ diff --git a/deploy/prod/docker-compose.yml b/deploy/prod/docker-compose.yml new file mode 100644 index 0000000..beeda63 --- /dev/null +++ b/deploy/prod/docker-compose.yml @@ -0,0 +1,80 @@ +services: + postgres: + image: postgres:16-alpine + restart: unless-stopped + + environment: + POSTGRES_USER: tiptop_prod + POSTGRES_PASSWORD: tiptop_prod_password + POSTGRES_DB: the_tip_top_prod + + volumes: + - tiptop_prod_postgres_data:/var/lib/postgresql/data + + healthcheck: + test: + [ + "CMD-SHELL", + "pg_isready -U tiptop_prod -d the_tip_top_prod" + ] + interval: 10s + timeout: 5s + retries: 10 + + networks: + - tiptop-prod + + backend: + image: ${REGISTRY}/the-tip-top/backend:${IMAGE_TAG} + restart: unless-stopped + + depends_on: + postgres: + condition: service_healthy + + environment: + PORT: 5000 + NODE_ENV: production + DATABASE_URL: postgresql://tiptop_prod:tiptop_prod_password@postgres:5432/the_tip_top_prod + JWT_SECRET: prod_secret_change_me + APP_URL: http://192.168.1.23:5003 + + MAIL_HOST: smtp.gmail.com + MAIL_PORT: 587 + MAIL_USER: "" + MAIL_PASS: "" + MAIL_FROM: "The Tip Top " + + command: > + sh -c " + npx prisma migrate deploy && + npm run init:db && + npm start + " + + ports: + - "5003:5000" + + networks: + - tiptop-prod + + frontend: + image: ${REGISTRY}/the-tip-top/frontend:${IMAGE_TAG} + restart: unless-stopped + + depends_on: + - backend + + ports: + - "5175:80" + + networks: + - tiptop-prod + +volumes: + tiptop_prod_postgres_data: + name: tiptop_prod_postgres_data + +networks: + tiptop-prod: + name: tiptop-prod From 803a78cd7118607c202f2a84394223c4c7d885c7 Mon Sep 17 00:00:00 2001 From: Andil Date: Tue, 4 Aug 2026 20:43:14 +0000 Subject: [PATCH 11/14] refactor: align branches with dev preprod and production --- Jenkinsfile | 75 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 5063b6b..e413def 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -31,30 +31,49 @@ pipeline { script { env.IMAGE_TAG = "${env.BRANCH_NAME}-${env.BUILD_NUMBER}" - if (env.BRANCH_NAME == 'main') { + /* + * Stratégie de branches : + * + * feature/* -> CI uniquement + * develop -> DEV automatique + * release-* -> PREPROD automatique + * main -> PROD avec validation manuelle + */ + + if (env.BRANCH_NAME == 'develop') { env.DEPLOY_ENABLED = 'true' env.DEPLOY_ENV = 'DEV' + env.COMPOSE_FILE = 'deploy/dev/docker-compose.yml' env.COMPOSE_PROJECT = 'tiptop-dev' env.DEPLOY_NETWORK = 'tiptop-dev' + env.FRONTEND_API_URL = 'http://192.168.1.23:5001' - } else if (env.BRANCH_NAME == 'develop') { + + } else if (env.BRANCH_NAME.startsWith('release-')) { env.DEPLOY_ENABLED = 'true' env.DEPLOY_ENV = 'PREPROD' + env.COMPOSE_FILE = 'deploy/preprod/docker-compose.yml' env.COMPOSE_PROJECT = 'tiptop-preprod' env.DEPLOY_NETWORK = 'tiptop-preprod' + env.FRONTEND_API_URL = 'http://192.168.1.23:5002' - } else if (env.BRANCH_NAME.startsWith('release-')) { + + } else if (env.BRANCH_NAME == 'main') { env.DEPLOY_ENABLED = 'true' env.DEPLOY_ENV = 'PROD' + env.COMPOSE_FILE = 'deploy/prod/docker-compose.yml' env.COMPOSE_PROJECT = 'tiptop-prod' env.DEPLOY_NETWORK = 'tiptop-prod' + env.FRONTEND_API_URL = 'http://192.168.1.23:5003' + } else { env.DEPLOY_ENABLED = 'false' env.DEPLOY_ENV = 'CI uniquement' + env.COMPOSE_FILE = '' env.COMPOSE_PROJECT = '' env.DEPLOY_NETWORK = '' @@ -101,12 +120,16 @@ pipeline { for i in $(seq 1 30); do if docker exec "${CI_DATABASE}" \ - pg_isready -U tiptop_ci -d the_tip_top_ci; then + pg_isready \ + -U tiptop_ci \ + -d the_tip_top_ci; then + echo "PostgreSQL CI est prêt." break fi if [ "$i" -eq 30 ]; then + echo "PostgreSQL CI ne répond pas." docker logs "${CI_DATABASE}" exit 1 fi @@ -211,8 +234,10 @@ pipeline { steps { timeout(time: 15, unit: 'MINUTES') { - input message: "Déployer ${IMAGE_TAG} en production ?", - ok: "Déployer" + input( + message: "Déployer ${IMAGE_TAG} en production ?", + ok: "Déployer" + ) } } } @@ -227,6 +252,9 @@ pipeline { steps { sh ''' echo "Déploiement de ${BRANCH_NAME} vers ${DEPLOY_ENV}" + echo "Image backend : ${BACKEND_IMAGE}:${IMAGE_TAG}" + echo "Image frontend : ${FRONTEND_IMAGE}:${IMAGE_TAG}" + echo "Compose : ${COMPOSE_FILE}" REGISTRY="${REGISTRY}" \ IMAGE_TAG="${IMAGE_TAG}" \ @@ -243,17 +271,27 @@ pipeline { if docker run --rm \ --network "${DEPLOY_NETWORK}" \ curlimages/curl:8.12.1 \ - -fsS http://backend:5000/ >/dev/null; then + -fsS \ + http://backend:5000/ \ + >/dev/null; then echo "Backend ${DEPLOY_ENV} opérationnel." break fi if [ "$i" -eq 30 ]; then + echo "Le backend ${DEPLOY_ENV} ne répond pas." + docker compose \ -p "${COMPOSE_PROJECT}" \ -f "${COMPOSE_FILE}" \ - logs --tail=150 + ps + + docker compose \ + -p "${COMPOSE_PROJECT}" \ + -f "${COMPOSE_FILE}" \ + logs \ + --tail=150 exit 1 fi @@ -261,10 +299,14 @@ pipeline { sleep 5 done + echo "Vérification du frontend ${DEPLOY_ENV}..." + docker run --rm \ --network "${DEPLOY_NETWORK}" \ curlimages/curl:8.12.1 \ - -fsS http://frontend/ >/dev/null + -fsS \ + http://frontend/ \ + >/dev/null echo "Frontend ${DEPLOY_ENV} opérationnel." ''' @@ -281,11 +323,22 @@ pipeline { } success { - echo "Pipeline réussi pour ${BRANCH_NAME}. Environnement : ${DEPLOY_ENV}. Version : ${IMAGE_TAG}" + echo """ + Pipeline réussi. + + Branche : ${BRANCH_NAME} + Environnement : ${DEPLOY_ENV} + Version : ${IMAGE_TAG} + """ } failure { - echo "Pipeline en échec pour ${BRANCH_NAME}. Consulte la Console Output." + echo """ + Pipeline en échec. + + Branche : ${BRANCH_NAME} + Environnement : ${DEPLOY_ENV} + """ } } } From d57b26c8c8663aa8787d339152cf34b6ef89ee33 Mon Sep 17 00:00:00 2001 From: Andil Date: Sat, 8 Aug 2026 00:24:43 +0000 Subject: [PATCH 12/14] test: generate JUnit reports for Jenkins --- backend/jest.config.js | 29 +++++++++++++--------- backend/package-lock.json | 51 +++++++++++++++++++++++++++++++++++++++ backend/package.json | 1 + 3 files changed, 70 insertions(+), 11 deletions(-) diff --git a/backend/jest.config.js b/backend/jest.config.js index 5296fb1..b803d50 100644 --- a/backend/jest.config.js +++ b/backend/jest.config.js @@ -1,26 +1,33 @@ module.exports = { testEnvironment: "node", - // Dossier des tests testMatch: ["**/tests/**/*.test.js"], - // Nettoyage des mocks entre les tests clearMocks: true, - // Timeout (utile pour Prisma / DB) testTimeout: 10000, - // Affichage des logs de test - verbose: true, + verbose: true, - // Couverture de code (optionnel mais bon pour soutenance) collectCoverage: true, coverageDirectory: "coverage", + coverageReporters: [ + "text", + "lcov", + "html" + ], - // Fichiers à ignorer - testPathIgnorePatterns: ["/node_modules/"], + reporters: [ + "default", + [ + "jest-junit", + { + outputDirectory: "./test-results", + outputName: "junit.xml" + } + ] + ], - // Configuration pour Jest avec Supertest - //setupFilesAfterEnv: ["/tests/setup.js"] -}; \ No newline at end of file + testPathIgnorePatterns: ["/node_modules/"] +}; diff --git a/backend/package-lock.json b/backend/package-lock.json index 40a1584..3d2455a 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -25,6 +25,7 @@ "cross-env": "^10.1.0", "dotenv": "^17.4.2", "jest": "^30.4.2", + "jest-junit": "^17.0.0", "nodemon": "^3.1.14", "supertest": "^7.2.2" } @@ -4457,6 +4458,22 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/jest-junit": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-17.0.0.tgz", + "integrity": "sha512-RYWCkq4j59gUXj5DsgbIE7xFBZzu1gtibPhyjSjMmGaOTLnqlXhg7x9zuGCwgbCuMAyoyvk0Mi8wSrRR5uOeLA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^14.0.0", + "xml": "^1.0.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/jest-leak-detector": { "version": "30.4.1", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz", @@ -5375,6 +5392,19 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -6836,6 +6866,20 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uuid": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz", + "integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -6942,6 +6986,13 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "dev": true, + "license": "MIT" + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/backend/package.json b/backend/package.json index 5a7af54..7d0fd04 100644 --- a/backend/package.json +++ b/backend/package.json @@ -32,6 +32,7 @@ "cross-env": "^10.1.0", "dotenv": "^17.4.2", "jest": "^30.4.2", + "jest-junit": "^17.0.0", "nodemon": "^3.1.14", "supertest": "^7.2.2" }, From 100b75a05ac01bf9b729287df0e9c4da7a4b108d Mon Sep 17 00:00:00 2001 From: Andil Date: Sat, 8 Aug 2026 01:07:31 +0000 Subject: [PATCH 13/14] ci: publish JUnit and coverage reports --- Jenkinsfile | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index e413def..5c175cf 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -162,7 +162,22 @@ pipeline { " ''' } - } + + post { + always { + junit( + testResults: 'backend/test-results/*.xml', + allowEmptyResults: true + ) + + archiveArtifacts( + artifacts: 'backend/coverage/**', + fingerprint: true, + allowEmptyArchive: true + ) + } + } + } stage('Frontend - Lint et Build') { steps { From b15d8458c8f41673260b6857c365933cbeb488bf Mon Sep 17 00:00:00 2001 From: Andil Date: Mon, 10 Aug 2026 19:23:52 +0000 Subject: [PATCH 14/14] refactor: centralize server IP configuration --- Jenkinsfile | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 5c175cf..d57f215 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -9,6 +9,7 @@ pipeline { environment { REGISTRY = "localhost:5000" + SERVER_IP = "192.168.1.95" BACKEND_IMAGE = "${REGISTRY}/the-tip-top/backend" FRONTEND_IMAGE = "${REGISTRY}/the-tip-top/frontend" @@ -48,7 +49,7 @@ pipeline { env.COMPOSE_PROJECT = 'tiptop-dev' env.DEPLOY_NETWORK = 'tiptop-dev' - env.FRONTEND_API_URL = 'http://192.168.1.23:5001' + env.FRONTEND_API_URL = "http://${SERVER_IP}:5001" } else if (env.BRANCH_NAME.startsWith('release-')) { env.DEPLOY_ENABLED = 'true' @@ -58,7 +59,7 @@ pipeline { env.COMPOSE_PROJECT = 'tiptop-preprod' env.DEPLOY_NETWORK = 'tiptop-preprod' - env.FRONTEND_API_URL = 'http://192.168.1.23:5002' + env.FRONTEND_API_URL = "http://${SERVER_IP}:5002" } else if (env.BRANCH_NAME == 'main') { env.DEPLOY_ENABLED = 'true' @@ -68,7 +69,7 @@ pipeline { env.COMPOSE_PROJECT = 'tiptop-prod' env.DEPLOY_NETWORK = 'tiptop-prod' - env.FRONTEND_API_URL = 'http://192.168.1.23:5003' + env.FRONTEND_API_URL = "http://${SERVER_IP}:5003" } else { env.DEPLOY_ENABLED = 'false' @@ -84,6 +85,8 @@ pipeline { echo "Tag Docker : ${env.IMAGE_TAG}" echo "Environnement : ${env.DEPLOY_ENV}" echo "Déploiement activé : ${env.DEPLOY_ENABLED}" + echo "Serveur : ${env.SERVER_IP}" + echo "API frontend : ${env.FRONTEND_API_URL}" } } } @@ -163,21 +166,21 @@ pipeline { ''' } - post { - always { - junit( - testResults: 'backend/test-results/*.xml', - allowEmptyResults: true - ) + post { + always { + junit( + testResults: 'backend/test-results/*.xml', + allowEmptyResults: true + ) - archiveArtifacts( - artifacts: 'backend/coverage/**', - fingerprint: true, - allowEmptyArchive: true - ) + archiveArtifacts( + artifacts: 'backend/coverage/**', + fingerprint: true, + allowEmptyArchive: true + ) + } } - } - } + } stage('Frontend - Lint et Build') { steps {