name: Shortlist CI/CD on: push: branches: [main, develop] pull_request: branches: [main] env: PYTHON_VERSION: "3.12" NODE_VERSION: "20" REGISTRY: ghcr.io IMAGE_PREFIX: ${{ github.repository_owner }}/shortlist jobs: # ── Backend Lint + Test ──────────────────────────────────── backend: name: Backend CI runs-on: ubuntu-latest defaults: run: working-directory: ./backend steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: pip cache-dependency-path: backend/requirements.txt - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt - name: Lint with flake8 run: | flake8 app/ --max-line-length=120 --statistics --count - name: Type check with pylint (errors only) run: | pylint app/ --errors-only --disable=E0401 - name: Run tests env: SECRET_KEY: ci-test-secret-key-not-for-production SUPABASE_URL: https://test.supabase.co SUPABASE_ANON_KEY: eyJhbGciOiJIUzI1NiJ9.test-anon SUPABASE_SERVICE_KEY: eyJhbGciOiJIUzI1NiJ9.test-service SUPABASE_JWT_SECRET: ci-jwt-secret GROQ_API_KEY: gsk_test_ci_key ENVIRONMENT: testing ALLOWED_ORIGINS: http://localhost:3000 run: | pytest -v --cov=app --cov-report=xml --tb=short - name: Upload coverage if: always() uses: actions/upload-artifact@v4 with: name: backend-coverage path: backend/coverage.xml # ── Frontend Lint + Build ────────────────────────────────── frontend: name: Frontend CI runs-on: ubuntu-latest defaults: run: working-directory: ./frontend steps: - uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: npm cache-dependency-path: frontend/package-lock.json - name: Install dependencies run: npm ci - name: Lint run: npm run lint - name: Type check run: npx tsc --noEmit - name: Build env: NEXT_PUBLIC_API_URL: http://localhost:8000 NEXT_PUBLIC_SUPABASE_URL: https://test.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY: test-anon-key run: npm run build # ── Docker Build + Push ──────────────────────────────────── docker: name: Docker Build & Push runs-on: ubuntu-latest needs: [backend, frontend] if: github.event_name == 'push' && github.ref == 'refs/heads/main' permissions: contents: read packages: write steps: - uses: actions/checkout@v4 - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build and push backend image uses: docker/build-push-action@v5 with: context: ./backend push: true tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-backend:latest ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-backend:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max - name: Build and push frontend image uses: docker/build-push-action@v5 with: context: ./frontend push: true tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-frontend:latest ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-frontend:${{ github.sha }} build-args: | NEXT_PUBLIC_API_URL=${{ vars.NEXT_PUBLIC_API_URL }} NEXT_PUBLIC_SUPABASE_URL=${{ vars.NEXT_PUBLIC_SUPABASE_URL }} NEXT_PUBLIC_SUPABASE_ANON_KEY=${{ vars.NEXT_PUBLIC_SUPABASE_ANON_KEY }} cache-from: type=gha cache-to: type=gha,mode=max # ── Deploy (main branch only) ────────────────────────────── deploy: name: Deploy to Production runs-on: ubuntu-latest needs: [docker] if: github.event_name == 'push' && github.ref == 'refs/heads/main' environment: production steps: - uses: actions/checkout@v4 - name: Deploy notification run: | echo "🚀 Deploying Shortlist" echo " Commit: ${{ github.sha }}" echo " Backend: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-backend:${{ github.sha }}" echo " Frontend: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-frontend:${{ github.sha }}" # ── Option A: SSH deploy to VPS ── # Uncomment and configure for your server: # # - name: Deploy via SSH # uses: appleboy/ssh-action@v1 # with: # host: ${{ secrets.DEPLOY_HOST }} # username: ${{ secrets.DEPLOY_USER }} # key: ${{ secrets.DEPLOY_SSH_KEY }} # script: | # cd /opt/shortlist # docker compose -f docker-compose.prod.yml pull # docker compose -f docker-compose.prod.yml up -d --remove-orphans # docker image prune -f # ── Option B: Railway / Render / Fly.io ── # Configure platform-specific deployment here - name: Post-deploy health check run: | echo "Deployment pipeline complete." echo "Verify health: curl https://api.yourdomain.com/health/deep"