Skip to content

fix: Properly handle 0 vs None for likes/comments and improve usernam… #204

fix: Properly handle 0 vs None for likes/comments and improve usernam…

fix: Properly handle 0 vs None for likes/comments and improve usernam… #204

Workflow file for this run

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
}