diff --git a/.dockerignore b/.dockerignore index cd967fc..59c3614 100644 --- a/.dockerignore +++ b/.dockerignore @@ -18,7 +18,7 @@ **/Dockerfile* **/node_modules **/npm-debug.log -**/obj +**/obj **/secrets.dev.yaml **/values.dev.yaml LICENSE diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..c58c5c7 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,99 @@ +name: Deploy Webzine + +on: + push: + branches: + - main + - dev + +jobs: + # ───────────────────────────────────────────── + # COMPILATION — commun aux deux branches + # ───────────────────────────────────────────── + build: + name: Build & Push Docker Image + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + # Le tag d'image dépend de la branche : + # main → webzine:latest + - name: Set image tag + id: vars + run: | + echo "IMAGE_TAG=latest" >> $GITHUB_OUTPUT + echo "ENV_LABEL=production" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + # Connexion au registry Gitea intégré + - name: Log in to Gitea Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ vars.REGISTRY_URL }} + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + # Construction et publication de l'image Docker + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Webzine.WebApplication/Dockerfile + push: true + tags: ${{ vars.REGISTRY_URL }}/webzine/webzine:${{ steps.vars.outputs.IMAGE_TAG }} + cache-from: type=registry,ref=${{ vars.REGISTRY_URL }}/webzine/webzine:buildcache-${{ steps.vars.outputs.IMAGE_TAG }} + cache-to: type=registry,ref=${{ vars.REGISTRY_URL }}/webzine/webzine:buildcache-${{ steps.vars.outputs.IMAGE_TAG }},mode=max + + outputs: + image_tag: ${{ steps.vars.outputs.IMAGE_TAG }} + env_label: ${{ steps.vars.outputs.ENV_LABEL }} + + # ───────────────────────────────────────────── + # DÉPLOIEMENT — Serveur de PRODUCTION (branche main) + # ───────────────────────────────────────────── + deploy-production: + name: Deploy to Production + needs: build + runs-on: ubuntu-latest + + steps: + - name: Deploy via SSH to PRODUCTION server + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.PROD_HOST }} + username: ${{ secrets.PROD_USER }} + key: ${{ secrets.PROD_SSH_KEY }} + port: ${{ secrets.PROD_SSH_PORT || 22 }} + script: | + set -e + + echo "=== [PROD] Récupération de l'image ===" + docker login ${{ vars.REGISTRY_URL }} \ + -u ${{ secrets.REGISTRY_USERNAME }} \ + -p ${{ secrets.REGISTRY_PASSWORD }} + + docker pull ${{ vars.REGISTRY_URL }}/webzine/webzine:latest + + echo "=== [PROD] Arrêt de l'ancien conteneur ===" + docker stop webzine-prod 2>/dev/null || true + docker rm webzine-prod 2>/dev/null || true + + echo "=== [PROD] Démarrage du nouveau conteneur ===" + docker run -d \ + --name webzine-prod \ + --restart unless-stopped \ + -p 80:8080 \ + -p 443:8081 \ + -v /opt/webzine/prod/data:/app/Data \ + -v /opt/webzine/prod/logs:/Logs \ + -e ASPNETCORE_ENVIRONMENT=Production \ + ${{ vars.REGISTRY_URL }}/webzine/webzine:latest + + echo "=== [PROD] Nettoyage des anciennes images ===" + docker image prune -f + + echo "=== [PROD] Déploiement terminé ===" \ No newline at end of file diff --git a/.gitea/workflows/pr-endpoint-check.yml b/.gitea/workflows/pr-endpoint-check.yml new file mode 100644 index 0000000..c67fab3 --- /dev/null +++ b/.gitea/workflows/pr-endpoint-check.yml @@ -0,0 +1,132 @@ +name: PR Endpoint Performance Check + +on: + pull_request: + branches: + - main + - master + - develop + +jobs: + endpoint-performance-check: + name: Test All Endpoints (< 1s) + runs-on: ubuntu-latest + + steps: + # ───────────────────────────────────────────── + # Récupération du code source + # ───────────────────────────────────────────── + - name: Checkout PR branch + uses: actions/checkout@v4 + + # ───────────────────────────────────────────── + # Installation de .NET 10 + # ───────────────────────────────────────────── + - name: Setup .NET 10 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "10.0.x" + + # ───────────────────────────────────────────── + # Restauration des dépendances et compilation + # ───────────────────────────────────────────── + - name: Restore dependencies + run: dotnet restore Webzine.sln + + - name: Build solution + run: dotnet build Webzine.sln --no-restore --configuration Release + + # ───────────────────────────────────────────── + # Exécution des tests unitaires (entités) + # ───────────────────────────────────────────── + - name: Run unit tests + run: | + dotnet test Webzine.Entity.Tests/Webzine.Entity.Tests.csproj \ + --no-build \ + --configuration Release \ + --logger "console;verbosity=normal" + + # ───────────────────────────────────────────── + # Démarrage de l'application web en arrière-plan + # ───────────────────────────────────────────── + - name: Start Webzine application + run: | + dotnet run \ + --project Webzine.WebApplication/Webzine.WebApplication.csproj \ + --configuration Release \ + --no-build \ + -- --urls "http://localhost:5038" & + + echo "Attente du démarrage de l'application..." + timeout 60 bash -c ' + until curl -sf http://localhost:5038 > /dev/null 2>&1; do + sleep 1 + done + ' + echo "Application prête !" + + # ───────────────────────────────────────────── + # Exécution des tests de performance des endpoints + # ───────────────────────────────────────────── + - name: Test endpoint response times + id: perf_test + run: | + chmod +x scripts/test-endpoints.sh + bash scripts/test-endpoints.sh http://localhost:5038 1000 || true + FAIL_COUNT=$(grep -c "^\[FAIL\]\|^\[SLOW\]" /tmp/webzine_endpoint_report.txt || echo 0) + echo "failed=$FAIL_COUNT" >> "$GITHUB_OUTPUT" + + # ───────────────────────────────────────────── + # Publication du rapport en commentaire de PR + # ───────────────────────────────────────────── + - name: Post performance report as PR comment + if: always() + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + GITEA_SERVER_URL: ${{ gitea.server_url }} + REPO: ${{ gitea.repository }} + PR_NUMBER: ${{ gitea.event.pull_request.number }} + run: | + REPORT_CONTENT=$(cat /tmp/webzine_endpoint_report.txt 2>/dev/null || echo "Aucun rapport généré.") + FAILED_COUNT="${{ steps.perf_test.outputs.failed }}" + + if [ "${FAILED_COUNT:-0}" -gt 0 ]; then + HEADER="## ❌ Vérification des performances ÉCHOUÉE" + INTRO="${FAILED_COUNT} endpoint(s) ont dépassé 1 seconde ou retourné une erreur serveur." + else + HEADER="## ✅ Vérification des performances RÉUSSIE" + INTRO="Tous les endpoints ont répondu en moins d'une seconde." + fi + + BODY=$(cat < Seuil : **1000ms** | Vérifié par le workflow **PR Endpoint Performance**. + EOF + ) + + curl -s -X POST \ + -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/json" \ + -d "$(jq -n --arg body "$BODY" '{body: $body}')" \ + "$GITEA_SERVER_URL/api/v1/repos/$REPO/issues/$PR_NUMBER/comments" + + # ───────────────────────────────────────────── + # Blocage de la PR si un endpoint a échoué + # ───────────────────────────────────────────── + - name: Enforce performance gate + run: | + FAILED="${{ steps.perf_test.outputs.failed }}" + if [ "${FAILED:-0}" -gt 0 ]; then + echo "❌ PR REJETÉE : ${FAILED} endpoint(s) n'ont pas respecté le seuil d'une seconde." + echo " Corrigez les endpoints lents ou en erreur avant de fusionner." + exit 1 + else + echo "✅ Tous les endpoints ont passé le contrôle de performance." + fi \ No newline at end of file diff --git a/Webzine.Entity/Fixtures/ArtisteFactory.cs b/Webzine.Entity/Fixtures/ArtisteFactory.cs index d8ecb26..44f297a 100644 --- a/Webzine.Entity/Fixtures/ArtisteFactory.cs +++ b/Webzine.Entity/Fixtures/ArtisteFactory.cs @@ -49,5 +49,20 @@ namespace Webzine.Entity.Fixtures return artisteFaker.Generate(); } + + /// + /// Permet de retourner une liste d'artiste pour seeder la base + /// de données. + /// + /// + /// Liste d'artiste. + public static List CreerListeArtiste(int nombre) + { + Faker faker = new Faker("fr") + .RuleFor(a => a.Nom, f => f.Person.FullName) + .RuleFor(a => a.Biographie, f => f.Lorem.Paragraph(2)); + + return faker.Generate(nombre); + } } } \ No newline at end of file diff --git a/Webzine.Entity/Fixtures/CommentaireFactory.cs b/Webzine.Entity/Fixtures/CommentaireFactory.cs new file mode 100644 index 0000000..790cfc2 --- /dev/null +++ b/Webzine.Entity/Fixtures/CommentaireFactory.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) PlaceholderCompany. All rights reserved. +// + +using Bogus; + +namespace Webzine.Entity.Fixtures +{ + public class CommentaireFactory + { + /// + /// Seeder pour la base de données. + /// + /// Titre. + /// Min. + /// Max. + /// Liste de commentaire. + public static List CreerListeCommentaire(Titre titre, int min = 0, int max = 5) + { + Random random = new Random(); + int count = random.Next(min, max + 1); + + Faker faker = new Faker("fr") + .RuleFor(c => c.Auteur, f => f.Internet.UserName()) + .RuleFor(c => c.Contenu, f => f.Lorem.Sentences(2)) + .RuleFor(c => c.DateCreation, f => f.Date.Recent(60)) + .RuleFor(c => c.Titre, _ => titre) + .RuleFor(c => c.IdTitre, _ => titre.IdTitre); + + return faker.Generate(count); + } + } +} diff --git a/Webzine.Entity/Fixtures/SeedDataLocal.cs b/Webzine.Entity/Fixtures/SeedDataLocal.cs index bd2479e..544b730 100644 --- a/Webzine.Entity/Fixtures/SeedDataLocal.cs +++ b/Webzine.Entity/Fixtures/SeedDataLocal.cs @@ -1,6 +1,14 @@ -namespace Webzine.Entity.Fixtures; +// +// Copyright (c) PlaceholderCompany. All rights reserved. +// -public class SeedDataLocal -{ +namespace Webzine.Entity.Fixtures +{ + public class SeedDataLocal + { + public SeedDataLocal() + { + } + } } \ No newline at end of file diff --git a/Webzine.Entity/Fixtures/StyleFactory.cs b/Webzine.Entity/Fixtures/StyleFactory.cs new file mode 100644 index 0000000..039f6f7 --- /dev/null +++ b/Webzine.Entity/Fixtures/StyleFactory.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) PlaceholderCompany. All rights reserved. +// + +namespace Webzine.Entity.Fixtures +{ + using Webzine.Entity; + public class StyleFactory + { + /// + /// Générer une liste de style pour seeder la base + /// de données. + /// + /// Liste de style. + public static List