feat: implemented phase 5 of dashboard plan #5
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
| # ============================================================================= | |
| # Continuous Integration and Deployment Pipeline | |
| # Event Management Portal - Enhanced CI/CD with TypeScript Migration Support | |
| # ============================================================================= | |
| name: CI/CD Pipeline | |
| on: | |
| push: | |
| branches: [main, migration/typescript-monorepo, develop] | |
| paths-ignore: | |
| - '**.md' | |
| - 'docs/**' | |
| - 'documentation/**' | |
| - '.gitignore' | |
| - 'LICENSE' | |
| - 'CODE_OF_CONDUCT.md' | |
| pull_request: | |
| branches: [main, migration/typescript-monorepo, develop] | |
| paths-ignore: | |
| - '**.md' | |
| - 'docs/**' | |
| - 'documentation/**' | |
| - '.gitignore' | |
| - 'LICENSE' | |
| - 'CODE_OF_CONDUCT.md' | |
| # Enhanced permissions for security and package management | |
| permissions: | |
| contents: read | |
| security-events: write | |
| actions: read | |
| checks: write | |
| pull-requests: write | |
| packages: write | |
| issues: read | |
| statuses: write | |
| deployments: write | |
| env: | |
| NODE_VERSION: '20' | |
| PNPM_VERSION: '10.18.3' | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: event-management-portal | |
| TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} | |
| TURBO_TEAM: ${{ vars.TURBO_TEAM }} | |
| # Concurrency configuration to prevent duplicate runs | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # ============================================================================ | |
| # Code Quality & Testing | |
| # ============================================================================ | |
| lint-and-test: | |
| name: Lint & Test | |
| runs-on: ubuntu-latest | |
| services: | |
| mongodb: | |
| image: mongo:6.0 | |
| env: | |
| MONGO_INITDB_ROOT_USERNAME: root | |
| MONGO_INITDB_ROOT_PASSWORD: password | |
| MONGO_INITDB_DATABASE: event_management_test | |
| ports: | |
| - 27017:27017 | |
| options: >- | |
| --health-cmd "mongosh --eval 'db.adminCommand({ping: 1})'" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 3 | |
| redis: | |
| image: redis:7-alpine | |
| ports: | |
| - 6379:6379 | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 3 | |
| strategy: | |
| matrix: | |
| package: [frontend, backend, shared] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v2 | |
| with: | |
| version: ${{ env.PNPM_VERSION }} | |
| - name: Get pnpm store directory | |
| shell: bash | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV | |
| - name: Setup pnpm cache | |
| uses: actions/cache@v3 | |
| with: | |
| path: ${{ env.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Environment validation | |
| run: | | |
| cp .env.example .env.test | |
| node scripts/validate-env.js test || echo "Environment validation skipped in CI" | |
| - name: Lint code | |
| run: pnpm turbo run lint --filter=${{ matrix.package }} | |
| - name: Type check | |
| run: pnpm turbo run type-check --filter=${{ matrix.package }} | |
| - name: Run unit tests | |
| run: pnpm turbo run test:unit --filter=${{ matrix.package }} | |
| env: | |
| NODE_ENV: test | |
| MONGODB_URI: mongodb://root:password@localhost:27017/event_management_test?authSource=admin | |
| REDIS_URL: redis://localhost:6379 | |
| JWT_SECRET: test-secret-key-not-for-production | |
| CLOUDINARY_CLOUD_NAME: test-cloud | |
| CLOUDINARY_API_KEY: test-key | |
| CLOUDINARY_API_SECRET: test-secret | |
| - name: Generate test coverage | |
| run: pnpm turbo run test:coverage --filter=${{ matrix.package }} | |
| continue-on-error: true | |
| - name: Upload coverage reports | |
| uses: codecov/codecov-action@v3 | |
| with: | |
| file: ./packages/${{ matrix.package }}/coverage/lcov.info | |
| flags: ${{ matrix.package }} | |
| name: ${{ matrix.package }}-coverage | |
| fail_ci_if_error: false | |
| # ============================================================================ | |
| # Security Scanning | |
| # ============================================================================ | |
| security-scan: | |
| name: Security Scan | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'push' || github.event_name == 'pull_request' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v2 | |
| with: | |
| version: ${{ env.PNPM_VERSION }} | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Run security audit | |
| run: pnpm audit --audit-level moderate | |
| continue-on-error: true | |
| - name: Run Snyk security scan | |
| uses: snyk/actions/node@master | |
| continue-on-error: true | |
| env: | |
| SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} | |
| with: | |
| args: --severity-threshold=high | |
| - name: CodeQL Analysis | |
| uses: github/codeql-action/init@v2 | |
| with: | |
| languages: javascript | |
| - name: Perform CodeQL Analysis | |
| uses: github/codeql-action/analyze@v2 | |
| # ============================================================================ | |
| # Build & Package | |
| # ============================================================================ | |
| build: | |
| name: Build Application | |
| runs-on: ubuntu-latest | |
| needs: [lint-and-test, security-scan] | |
| if: github.event_name == 'push' | |
| outputs: | |
| frontend-image: ${{ steps.meta-frontend.outputs.tags }} | |
| backend-image: ${{ steps.meta-backend.outputs.tags }} | |
| frontend-digest: ${{ steps.build-frontend.outputs.digest }} | |
| backend-digest: ${{ steps.build-backend.outputs.digest }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v2 | |
| with: | |
| version: ${{ env.PNPM_VERSION }} | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build packages | |
| run: pnpm turbo run build | |
| env: | |
| NODE_ENV: production | |
| - name: Log in to 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: Extract frontend metadata | |
| id: meta-frontend | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ github.repository }}-frontend | |
| tags: | | |
| type=ref,event=branch | |
| type=ref,event=pr | |
| type=sha,prefix={{branch}}- | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| - name: Extract backend metadata | |
| id: meta-backend | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ github.repository }}-backend | |
| tags: | | |
| type=ref,event=branch | |
| type=ref,event=pr | |
| type=sha,prefix={{branch}}- | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| - name: Build and push Frontend image | |
| id: build-frontend | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| file: packages/frontend/Dockerfile | |
| push: true | |
| tags: ${{ steps.meta-frontend.outputs.tags }} | |
| labels: ${{ steps.meta-frontend.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| target: production | |
| build-args: | | |
| NODE_ENV=production | |
| NEXT_PUBLIC_APP_URL=${{ vars.NEXT_PUBLIC_APP_URL || 'https://localhost' }} | |
| NEXT_PUBLIC_API_URL=${{ vars.NEXT_PUBLIC_API_URL || 'https://localhost/api/v1' }} | |
| - name: Build and push Backend image | |
| id: build-backend | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| file: packages/backend/Dockerfile | |
| push: true | |
| tags: ${{ steps.meta-backend.outputs.tags }} | |
| labels: ${{ steps.meta-backend.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| target: production | |
| # ============================================================================ | |
| # Integration Testing | |
| # ============================================================================ | |
| integration-test: | |
| name: Integration Tests | |
| runs-on: ubuntu-latest | |
| needs: [build] | |
| if: github.event_name == 'push' | |
| services: | |
| mongodb: | |
| image: mongo:6.0 | |
| env: | |
| MONGO_INITDB_ROOT_USERNAME: root | |
| MONGO_INITDB_ROOT_PASSWORD: password | |
| MONGO_INITDB_DATABASE: event_management_test | |
| ports: | |
| - 27017:27017 | |
| options: >- | |
| --health-cmd "mongosh --eval 'db.adminCommand({ping: 1})'" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 3 | |
| redis: | |
| image: redis:7-alpine | |
| ports: | |
| - 6379:6379 | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 3 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v2 | |
| with: | |
| version: ${{ env.PNPM_VERSION }} | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Run integration tests | |
| run: pnpm turbo run test:integration | |
| env: | |
| NODE_ENV: test | |
| MONGODB_URI: mongodb://root:password@localhost:27017/event_management_test?authSource=admin | |
| REDIS_URL: redis://localhost:6379 | |
| JWT_SECRET: test-secret-key-not-for-production | |
| CLOUDINARY_CLOUD_NAME: test-cloud | |
| CLOUDINARY_API_KEY: test-key | |
| CLOUDINARY_API_SECRET: test-secret | |
| - name: Upload integration test results | |
| uses: actions/upload-artifact@v3 | |
| if: always() | |
| with: | |
| name: integration-test-results | |
| path: | | |
| packages/*/test-results/ | |
| packages/*/coverage/ | |
| # ============================================================================ | |
| # Deploy to Staging | |
| # ============================================================================ | |
| deploy-staging: | |
| name: Deploy to Staging | |
| runs-on: ubuntu-latest | |
| needs: [build, integration-test] | |
| if: github.ref == 'refs/heads/develop' && github.event_name == 'push' | |
| environment: | |
| name: staging | |
| url: https://staging.eventmanagement.com | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Deploy to staging server | |
| uses: appleboy/[email protected] | |
| with: | |
| host: ${{ secrets.STAGING_HOST }} | |
| username: ${{ secrets.STAGING_USER }} | |
| key: ${{ secrets.STAGING_SSH_KEY }} | |
| script: | | |
| cd /opt/event-management-portal | |
| git pull origin develop | |
| docker-compose -f docker-compose.prod.yml pull | |
| docker-compose -f docker-compose.prod.yml up -d --remove-orphans | |
| docker system prune -f | |
| - name: Run staging health checks | |
| run: | | |
| sleep 30 | |
| curl -f https://staging.eventmanagement.com/health || exit 1 | |
| - name: Notify deployment | |
| uses: 8398a7/action-slack@v3 | |
| with: | |
| status: ${{ job.status }} | |
| channel: '#deployments' | |
| webhook_url: ${{ secrets.SLACK_WEBHOOK }} | |
| # ============================================================================ | |
| # Deploy to Production | |
| # ============================================================================ | |
| deploy-production: | |
| name: Deploy to Production | |
| runs-on: ubuntu-latest | |
| needs: [build, integration-test] | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| environment: | |
| name: production | |
| url: https://eventmanagement.com | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Create deployment | |
| uses: actions/github-script@v6 | |
| id: create-deployment | |
| with: | |
| script: | | |
| const { data: deployment } = await github.rest.repos.createDeployment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: context.sha, | |
| environment: 'production', | |
| description: 'Production deployment', | |
| auto_merge: false, | |
| required_contexts: [] | |
| }); | |
| return deployment.id; | |
| - name: Deploy to production server | |
| uses: appleboy/[email protected] | |
| with: | |
| host: ${{ secrets.PRODUCTION_HOST }} | |
| username: ${{ secrets.PRODUCTION_USER }} | |
| key: ${{ secrets.PRODUCTION_SSH_KEY }} | |
| script: | | |
| cd /opt/event-management-portal | |
| # Backup before deployment | |
| ./scripts/backup.sh | |
| # Pull latest code | |
| git pull origin main | |
| # Deploy with zero-downtime | |
| docker-compose -f docker-compose.prod.yml pull | |
| docker-compose -f docker-compose.prod.yml up -d --remove-orphans | |
| # Cleanup | |
| docker system prune -f | |
| - name: Run production health checks | |
| run: | | |
| sleep 60 | |
| curl -f https://eventmanagement.com/health || exit 1 | |
| curl -f https://eventmanagement.com/api/v1/health || exit 1 | |
| - name: Update deployment status - Success | |
| if: success() | |
| uses: actions/github-script@v6 | |
| with: | |
| script: | | |
| await github.rest.repos.createDeploymentStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: '${{ steps.create-deployment.outputs.result }}', | |
| state: 'success', | |
| environment_url: 'https://eventmanagement.com', | |
| description: 'Deployment succeeded' | |
| }); | |
| - name: Update deployment status - Failure | |
| if: failure() | |
| uses: actions/github-script@v6 | |
| with: | |
| script: | | |
| await github.rest.repos.createDeploymentStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: '${{ steps.create-deployment.outputs.result }}', | |
| state: 'failure', | |
| description: 'Deployment failed' | |
| }); | |
| - name: Notify production deployment | |
| uses: 8398a7/action-slack@v3 | |
| with: | |
| status: ${{ job.status }} | |
| channel: '#production' | |
| webhook_url: ${{ secrets.SLACK_WEBHOOK }} | |
| message: | | |
| 🚀 Production Deployment ${{ job.status }} | |
| Branch: ${{ github.ref }} | |
| Commit: ${{ github.sha }} | |
| Author: ${{ github.actor }} | |
| # ============================================================================ | |
| # Cleanup | |
| # ============================================================================ | |
| cleanup: | |
| name: Cleanup | |
| runs-on: ubuntu-latest | |
| needs: [deploy-staging, deploy-production] | |
| if: always() | |
| steps: | |
| - name: Cleanup old images | |
| uses: actions/delete-package-versions@v4 | |
| with: | |
| package-name: event-management-portal-frontend | |
| package-type: container | |
| min-versions-to-keep: 10 | |
| - name: Cleanup old images | |
| uses: actions/delete-package-versions@v4 | |
| with: | |
| package-name: event-management-portal-backend | |
| package-type: container | |
| min-versions-to-keep: 10 |