diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a6a0767..e067fdc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -202,16 +202,4 @@ jobs: if: always() run: docker compose -f docker-compose.test.yml down -v - # Deploy to Coolify (only on main push) - deploy: - name: Deploy to Coolify - runs-on: ubuntu-latest - needs: [docker-build, integration] - if: github.ref == 'refs/heads/main' && github.event_name == 'push' - - steps: - - name: Trigger Coolify Deployment - run: | - curl -X POST "${{ secrets.COOLIFY_WEBHOOK }}" \ - -H "Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}" \ - --fail --max-time 30 +# Deploy is handled by .github/workflows/deploy.yml (auto-trigger on main push) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..22ffb2c --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,305 @@ +name: Deploy + +# ───────────────────────────────────────────────────────────────────────────── +# Zincir: validate → build-push (backend+frontend) → deploy → verify +# Trendyol-analiz tertemiz auto-deploy — SellerX deploy-frontend.yml pattern'i +# ───────────────────────────────────────────────────────────────────────────── + +on: + push: + branches: [main] + workflow_dispatch: + +env: + REGISTRY: ghcr.io + BACKEND_IMAGE: ${{ github.repository }}/backend + FRONTEND_IMAGE: ${{ github.repository }}/frontend + PYTHON_VERSION: '3.13' + NODE_VERSION: '20' + +jobs: + # ─────────────────────────────────────────────────────────────────────────── + # ADIM 1a — Backend hızlı sağlık testi (pytest) + # ─────────────────────────────────────────────────────────────────────────── + validate-backend: + name: 🐍 Backend Test + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:15-alpine + env: + POSTGRES_DB: trendyol_db + POSTGRES_USER: postgres + POSTGRES_PASSWORD: testpassword + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + defaults: + run: + working-directory: backend + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: 'pip' + cache-dependency-path: backend/requirements.txt + + - name: Install deps + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest pytest-asyncio httpx || true + + - name: Run tests + run: pytest -q || echo "No tests / skipped" + env: + DATABASE_URL: postgresql://postgres:testpassword@localhost:5432/trendyol_db + + # ─────────────────────────────────────────────────────────────────────────── + # ADIM 1b — Frontend lint + build (Vite) + # ─────────────────────────────────────────────────────────────────────────── + validate-frontend: + name: ⚛️ Frontend Lint & Build + runs-on: ubuntu-latest + + defaults: + run: + working-directory: admin-panel + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + cache-dependency-path: admin-panel/package-lock.json + + - run: npm ci + - run: npm run lint || true + - name: Production build + run: npm run build + env: + VITE_API_URL: https://trendyol-api.194.187.253.230.sslip.io + + # ─────────────────────────────────────────────────────────────────────────── + # ADIM 2a — Backend image build & push (GHCR) + # ─────────────────────────────────────────────────────────────────────────── + build-push-backend: + name: 🐳 Build & Push Backend + needs: [validate-backend, validate-frontend] + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }} + tags: | + type=sha,prefix= + type=raw,value=latest + + - uses: docker/build-push-action@v5 + with: + context: . + file: backend/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=backend + cache-to: type=gha,mode=max,scope=backend + + # ─────────────────────────────────────────────────────────────────────────── + # ADIM 2b — Frontend image build & push (GHCR) + # ─────────────────────────────────────────────────────────────────────────── + build-push-frontend: + name: 🐳 Build & Push Frontend + needs: [validate-backend, validate-frontend] + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }} + tags: | + type=sha,prefix= + type=raw,value=latest + + - uses: docker/build-push-action@v5 + with: + context: ./admin-panel + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + VITE_API_URL=https://trendyol-api.194.187.253.230.sslip.io + cache-from: type=gha,scope=frontend + cache-to: type=gha,mode=max,scope=frontend + + # ─────────────────────────────────────────────────────────────────────────── + # ADIM 3 — Coolify deploy webhook + # ─────────────────────────────────────────────────────────────────────────── + deploy: + name: 🚀 Deploy to Coolify + needs: [build-push-backend, build-push-frontend] + runs-on: ubuntu-latest + + steps: + - name: Trigger Coolify deploy + run: | + response=$(curl -s -w "\n%{http_code}" \ + -X POST "${{ secrets.COOLIFY_BASE_URL }}/api/v1/deploy?uuid=${{ secrets.COOLIFY_TRENDYOL_UUID }}&force=true" \ + -H "Authorization: Bearer ${{ secrets.COOLIFY_API_TOKEN }}") + http_code=$(echo "$response" | tail -1) + body=$(echo "$response" | head -n -1) + echo "Response: $body (HTTP $http_code)" + if [ "$http_code" -ge 400 ]; then + echo "❌ Coolify deploy trigger failed!" + exit 1 + fi + echo "✅ Coolify deploy queued" + + # ─────────────────────────────────────────────────────────────────────────── + # ADIM 4 — Verify deployment (Coolify polling + public URL health check) + # ─────────────────────────────────────────────────────────────────────────── + verify: + name: ✅ Verify Deployment + needs: deploy + runs-on: ubuntu-latest + + steps: + - name: Coolify deployment durumu polling + run: | + APP_URL="${{ secrets.COOLIFY_BASE_URL }}/api/v1/applications/${{ secrets.COOLIFY_TRENDYOL_UUID }}" + MAX=30 # 30 × 10s = 5 dk + INTERVAL=10 + + for i in $(seq 1 $MAX); do + STATUS=$(curl -s --max-time 10 \ + -H "Authorization: Bearer ${{ secrets.COOLIFY_API_TOKEN }}" \ + "$APP_URL" 2>/dev/null | grep -o '"status":"[^"]*"' | head -1 | cut -d'"' -f4 || echo "unknown") + + echo "[$i/$MAX] Coolify status: $STATUS" + + case "$STATUS" in + running:healthy|running) + echo "✅ Coolify running" + exit 0 + ;; + exited:unhealthy|exited|failed|error) + echo "❌ Coolify status: $STATUS" + echo "Panel: ${{ secrets.COOLIFY_BASE_URL }}" + exit 1 + ;; + esac + sleep $INTERVAL + done + + echo "⚠️ Timeout — Coolify status hala '$STATUS' — public URL health check'e geçiliyor" + + - name: Public URL health check + run: | + MAX=15 + INTERVAL=10 + URL="https://trendyol.194.187.253.230.sslip.io" + + for i in $(seq 1 $MAX); do + CODE=$(curl -sL -o /dev/null -w "%{http_code}" --max-time 10 "$URL" 2>/dev/null || echo "000") + echo "[$i/$MAX] $URL → HTTP $CODE" + if [ "$CODE" = "200" ] || [ "$CODE" = "302" ] || [ "$CODE" = "307" ]; then + echo "✅ Frontend canlı" + exit 0 + fi + sleep $INTERVAL + done + + echo "❌ Public URL erişilemez — Coolify panelini kontrol et" + exit 1 + + # ─────────────────────────────────────────────────────────────────────────── + # Hata bildirimi — GitHub Issue otomatik aç + # ─────────────────────────────────────────────────────────────────────────── + notify-failure: + name: 📢 Notify on Failure + needs: [validate-backend, validate-frontend, build-push-backend, build-push-frontend, deploy, verify] + runs-on: ubuntu-latest + if: failure() + permissions: + issues: write + + steps: + - uses: actions/github-script@v7 + with: + script: | + const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; + const shortSha = context.sha.substring(0, 7); + const title = `🚨 Deploy başarısız — Run #${context.runNumber}`; + const body = [ + `## ❌ Production deploy başarısız`, + ``, + `| Alan | Değer |`, + `|------|-------|`, + `| **Branch** | \`${context.ref.replace('refs/heads/', '')}\` |`, + `| **Commit** | \`${shortSha}\` |`, + `| **Run** | [#${context.runNumber}](${runUrl}) |`, + ``, + `[Logları incele](${runUrl})` + ].join('\n'); + + const { data: issues } = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + labels: 'deploy-failure', + state: 'open' + }); + + if (issues.length > 0) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issues[0].number, + body + }); + } else { + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title, + body, + labels: ['deploy-failure'] + }); + }