fix: Properly handle 0 vs None for likes/comments and improve usernam… #204
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI/CD Pipeline | |
| # Testing Railway deployment with new Project Token | |
| permissions: | |
| contents: read | |
| deployments: write | |
| actions: read | |
| on: | |
| push: | |
| branches: [main] | |
| # Only deploy on main branch, and only if source code changes | |
| # Using paths (not paths-ignore) to explicitly include only relevant files | |
| paths: | |
| - 'apps/frontend/**' | |
| - 'apps/backend/**' | |
| - '.github/workflows/**' | |
| - 'package.json' | |
| - 'pnpm-lock.yaml' | |
| - 'requirements.txt' | |
| - 'Dockerfile' | |
| - 'start.sh' | |
| pull_request: | |
| branches: [main] | |
| paths: | |
| - 'apps/frontend/**' | |
| - 'apps/backend/**' | |
| # Allow manual deployment via workflow_dispatch | |
| workflow_dispatch: | |
| jobs: | |
| # Nettoyer TOUS les anciens déploiements avant de créer les nouveaux | |
| cleanup-deployments: | |
| runs-on: ubuntu-latest | |
| if: github.ref == 'refs/heads/main' | |
| permissions: | |
| contents: read | |
| deployments: write | |
| steps: | |
| - name: Cleanup all old deployments | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| try { | |
| console.log('🧹 Nettoyage de tous les anciens déploiements...'); | |
| // Récupérer TOUS les déploiements (tous environnements) | |
| const allDeployments = await github.rest.repos.listDeployments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| per_page: 100 | |
| }); | |
| console.log(`📦 ${allDeployments.data.length} déploiement(s) trouvé(s) au total`); | |
| // Supprimer TOUS les anciens déploiements avant de créer les nouveaux | |
| // On va créer 2 nouveaux déploiements (1 Production + 1 Railway), donc on supprime tout | |
| console.log(`🗑️ Suppression de TOUS les ${allDeployments.data.length} ancien(s) déploiement(s) avant de créer les nouveaux`); | |
| for (const deployment of allDeployments.data) { | |
| try { | |
| // Marquer comme inactive avant de supprimer | |
| await github.rest.repos.createDeploymentStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: deployment.id, | |
| state: 'inactive' | |
| }); | |
| await github.rest.repos.deleteDeployment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: deployment.id | |
| }); | |
| console.log(`🗑️ Déploiement #${deployment.id} (${deployment.environment}) supprimé`); | |
| } catch (error) { | |
| console.log(`⚠️ Erreur lors de la suppression du déploiement #${deployment.id}: ${error.message}`); | |
| } | |
| } | |
| console.log('✅ Tous les anciens déploiements supprimés - les nouveaux seront créés par les jobs frontend/backend'); | |
| console.log('✅ Nettoyage terminé'); | |
| } catch (error) { | |
| console.error('❌ Erreur lors du nettoyage:', error.message); | |
| // Ne pas faire échouer le workflow si le nettoyage échoue | |
| } | |
| # Check if deployment is needed | |
| check-changes: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dorny/paths-filter@v2 | |
| id: filter | |
| with: | |
| filters: | | |
| frontend: | |
| - 'apps/frontend/**' | |
| backend: | |
| - 'apps/backend/**' | |
| - 'start.sh' | |
| - 'Dockerfile' | |
| - 'requirements.txt' | |
| frontend: | |
| runs-on: ubuntu-latest | |
| needs: [check-changes, cleanup-deployments] | |
| if: always() && github.ref == 'refs/heads/main' | |
| outputs: | |
| vercel-deploy-outcome: ${{ steps.vercel-deploy.outcome }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '18' | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v2 | |
| with: | |
| version: 8 | |
| - name: Install dependencies | |
| working-directory: ./apps/frontend | |
| run: pnpm install --frozen-lockfile | |
| - name: Type check | |
| working-directory: ./apps/frontend | |
| run: pnpm type-check | |
| - name: Build | |
| working-directory: ./apps/frontend | |
| run: pnpm build | |
| id: build | |
| - name: Deploy to Vercel (production only) | |
| if: github.ref == 'refs/heads/main' | |
| id: vercel-deploy | |
| run: | | |
| echo "📦 Current directory: $(pwd)" | |
| echo "📦 Listing apps/frontend:" | |
| ls -la apps/frontend/ | head -20 | |
| echo "📦 Checking if dist folder exists:" | |
| ls -la apps/frontend/dist/ 2>/dev/null || echo "⚠️ dist folder not found!" | |
| echo "📦 Deploying to Vercel..." | |
| npx -y vercel@latest --prod --token ${{ secrets.VERCEL_TOKEN }} --yes | |
| env: | |
| VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} | |
| VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} | |
| - name: Create GitHub Deployment (Vercel) | |
| if: github.ref == 'refs/heads/main' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| try { | |
| // Vérifier si le déploiement Vercel a réussi | |
| const vercelOutcome = '${{ steps.vercel-deploy.outcome }}'; | |
| const vercelSuccess = vercelOutcome === 'success'; | |
| const deployState = vercelSuccess ? 'success' : (vercelOutcome === 'failure' ? 'failure' : 'pending'); | |
| console.log(`🔍 État du déploiement Vercel: ${vercelOutcome || 'not executed'}`); | |
| console.log(`📦 Création du GitHub Deployment avec statut: ${deployState}`); | |
| // Le nettoyage a déjà été fait par le job cleanup-deployments | |
| // Créer directement le nouveau déploiement | |
| console.log('✨ Création du nouveau déploiement Production...'); | |
| const newDeployment = await github.rest.repos.createDeployment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: context.sha, | |
| environment: 'Production', | |
| auto_merge: false, | |
| required_contexts: [], | |
| description: `Deployed via GitHub Actions - ${context.sha.substring(0, 7)}` | |
| }); | |
| console.log(`✅ Déploiement #${newDeployment.data.id} créé avec succès`); | |
| await github.rest.repos.createDeploymentStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: newDeployment.data.id, | |
| state: deployState, | |
| environment_url: 'https://www.veyl.io', | |
| description: vercelSuccess ? 'Deployment successful' : (vercelOutcome === 'failure' ? 'Deployment failed' : 'Deployment in progress') | |
| }); | |
| console.log(`🎉 Statut de déploiement mis à jour: ${deployState}`); | |
| } catch (error) { | |
| console.error('❌ Erreur lors de la création du GitHub Deployment:', error); | |
| console.error('Détails:', JSON.stringify(error, null, 2)); | |
| throw error; | |
| } | |
| backend: | |
| runs-on: ubuntu-latest | |
| needs: [check-changes, cleanup-deployments] | |
| if: always() && github.ref == 'refs/heads/main' | |
| outputs: | |
| railway-deploy-outcome: ${{ steps.railway-deploy.outcome }} | |
| defaults: | |
| run: | |
| working-directory: ./apps/backend | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Python | |
| uses: actions/setup-python@v4 | |
| with: | |
| python-version: '3.11' | |
| - name: Install dependencies | |
| run: pip install -r requirements.txt | |
| - name: Run tests (if available) | |
| continue-on-error: true | |
| run: | | |
| if [ -f "test_*.py" ] || [ -d "tests" ]; then | |
| python -m pytest || echo "Tests failed, continuing..." | |
| else | |
| echo "No tests found, skipping..." | |
| fi | |
| - name: Deploy to Railway (production only) | |
| if: github.ref == 'refs/heads/main' | |
| id: railway-deploy | |
| continue-on-error: true | |
| run: | | |
| # Railway CLI utilise RAILWAY_TOKEN depuis la variable d'environnement | |
| echo "📦 Current directory: $(pwd)" | |
| echo "📦 Deploying to Railway..." | |
| # Railway.toml est à la racine, donc on déploie depuis la racine | |
| cd ${{ github.workspace }} | |
| echo "📦 Working directory: $(pwd)" | |
| echo "📦 Checking railway.toml:" | |
| cat railway.toml || echo "⚠️ railway.toml not found" | |
| # Utiliser railway up sans --path-as-root car railway.toml définit déjà le buildPath | |
| npx -y @railway/cli@latest up --service veyl.io --detach && { | |
| echo "✅ Déploiement réussi avec service 'veyl.io'" | |
| exit 0 | |
| } | |
| echo "❌ Railway CLI failed. Check if RAILWAY_TOKEN is un Project Token valide." | |
| exit 1 | |
| env: | |
| RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} | |
| - name: Create GitHub Deployment (Railway) | |
| if: github.ref == 'refs/heads/main' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| try { | |
| const envName = 'veyl.io (veyl-production / production)'; | |
| const railwayOutcome = '${{ steps.railway-deploy.outcome }}'; | |
| const railwaySuccess = railwayOutcome === 'success'; | |
| const deployState = railwaySuccess ? 'success' : (railwayOutcome === 'failure' ? 'failure' : 'pending'); | |
| console.log(`🔍 État du déploiement Railway: ${railwayOutcome || 'not executed'}`); | |
| console.log(`📦 Création du GitHub Deployment avec statut: ${deployState}`); | |
| // Le nettoyage a déjà été fait par le job cleanup-deployments | |
| // Créer directement le nouveau déploiement | |
| console.log(`✨ Création du nouveau déploiement Railway (${envName})...`); | |
| const newDeployment = await github.rest.repos.createDeployment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: context.sha, | |
| environment: envName, | |
| auto_merge: false, | |
| required_contexts: [], | |
| description: `Deployed via GitHub Actions - ${context.sha.substring(0, 7)}` | |
| }); | |
| console.log(`✅ Déploiement #${newDeployment.data.id} créé avec succès`); | |
| await github.rest.repos.createDeploymentStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: newDeployment.data.id, | |
| state: deployState, | |
| environment_url: 'https://api.veyl.io', | |
| description: railwaySuccess ? 'Deployment successful' : (railwayOutcome === 'failure' ? 'Deployment failed' : 'Deployment in progress') | |
| }); | |
| console.log(`🎉 Statut de déploiement mis à jour: ${deployState}`); | |
| } catch (error) { | |
| console.error('❌ Erreur lors de la création du GitHub Deployment:', error); | |
| console.error('Détails:', JSON.stringify(error, null, 2)); | |
| throw error; | |
| } | |
| # Job final : s'assurer que les 2 déploiements existent toujours | |
| # Crée les déploiements manquants si les jobs frontend/backend ont été skippés | |
| ensure-deployments: | |
| runs-on: ubuntu-latest | |
| needs: [check-changes, cleanup-deployments, frontend, backend] | |
| if: always() && github.ref == 'refs/heads/main' | |
| permissions: | |
| contents: read | |
| deployments: write | |
| steps: | |
| - name: Ensure both deployments exist | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| try { | |
| console.log('🔍 Vérification finale - s\'assurer qu\'il y a exactement 2 déploiements...'); | |
| // Récupérer TOUS les déploiements | |
| const allDeployments = await github.rest.repos.listDeployments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| per_page: 100 | |
| }); | |
| const prodEnv = 'Production'; | |
| const railwayEnv = 'veyl.io (veyl-production / production)'; | |
| // Séparer par environnement | |
| const prodDeployments = allDeployments.data.filter(d => d.environment === prodEnv) | |
| .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); | |
| const railwayDeployments = allDeployments.data.filter(d => d.environment === railwayEnv) | |
| .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); | |
| console.log(`📦 Déploiements trouvés: ${prodDeployments.length} Production, ${railwayDeployments.length} Railway`); | |
| // Garder seulement le plus récent de chaque environnement, supprimer les autres | |
| const toKeep = new Set(); | |
| if (prodDeployments.length > 0) { | |
| toKeep.add(prodDeployments[0].id); | |
| // Supprimer les doublons Production | |
| for (let i = 1; i < prodDeployments.length; i++) { | |
| try { | |
| await github.rest.repos.createDeploymentStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: prodDeployments[i].id, | |
| state: 'inactive' | |
| }); | |
| await github.rest.repos.deleteDeployment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: prodDeployments[i].id | |
| }); | |
| console.log(`🗑️ Déploiement Production #${prodDeployments[i].id} (doublon) supprimé`); | |
| } catch (error) { | |
| console.log(`⚠️ Erreur suppression doublon Production #${prodDeployments[i].id}: ${error.message}`); | |
| } | |
| } | |
| } | |
| if (railwayDeployments.length > 0) { | |
| toKeep.add(railwayDeployments[0].id); | |
| // Supprimer les doublons Railway | |
| for (let i = 1; i < railwayDeployments.length; i++) { | |
| try { | |
| await github.rest.repos.createDeploymentStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: railwayDeployments[i].id, | |
| state: 'inactive' | |
| }); | |
| await github.rest.repos.deleteDeployment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: railwayDeployments[i].id | |
| }); | |
| console.log(`🗑️ Déploiement Railway #${railwayDeployments[i].id} (doublon) supprimé`); | |
| } catch (error) { | |
| console.log(`⚠️ Erreur suppression doublon Railway #${railwayDeployments[i].id}: ${error.message}`); | |
| } | |
| } | |
| } | |
| // Créer les déploiements manquants seulement s'ils n'existent pas | |
| if (prodDeployments.length === 0) { | |
| console.log('📦 Création du déploiement Production manquant...'); | |
| const prodDeployment = await github.rest.repos.createDeployment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: context.sha, | |
| environment: prodEnv, | |
| auto_merge: false, | |
| required_contexts: [], | |
| description: `Deployed via GitHub Actions - ${context.sha.substring(0, 7)} (no frontend changes)` | |
| }); | |
| await github.rest.repos.createDeploymentStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: prodDeployment.data.id, | |
| state: 'success', | |
| environment_url: 'https://www.veyl.io', | |
| description: 'No frontend changes in this commit' | |
| }); | |
| console.log('✅ Déploiement Production créé'); | |
| } | |
| if (railwayDeployments.length === 0) { | |
| console.log('📦 Création du déploiement Railway manquant...'); | |
| const railwayDeployment = await github.rest.repos.createDeployment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: context.sha, | |
| environment: railwayEnv, | |
| auto_merge: false, | |
| required_contexts: [], | |
| description: `Deployed via GitHub Actions - ${context.sha.substring(0, 7)} (no backend changes)` | |
| }); | |
| await github.rest.repos.createDeploymentStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: railwayDeployment.data.id, | |
| state: 'success', | |
| environment_url: 'https://api.veyl.io', | |
| description: 'No backend changes in this commit' | |
| }); | |
| console.log('✅ Déploiement Railway créé'); | |
| } | |
| console.log('🎉 Vérification terminée - exactement 2 déploiements garantis (1 Production + 1 Railway)'); | |
| } catch (error) { | |
| console.error('❌ Erreur lors de la vérification:', error.message); | |
| // Ne pas faire échouer le workflow | |
| } |