diff --git a/.github/workflows/database-migration.yml b/.github/workflows/database-migration.yml new file mode 100644 index 0000000..c00b635 --- /dev/null +++ b/.github/workflows/database-migration.yml @@ -0,0 +1,248 @@ +name: Database Migration + +on: + push: + branches: [master, develop] + paths: + - "prisma/migrations/**" + - "prisma/schema.prisma" + workflow_dispatch: + inputs: + environment: + description: "Environment to migrate" + required: true + default: "staging" + type: choice + options: + - staging + - production + migration_type: + description: "Migration type" + required: true + default: "deploy" + type: choice + options: + - deploy + - reset + - status + +jobs: + # Validate migration files + validate-migrations: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Validate Prisma schema + run: npx prisma validate + + - name: Check for migration conflicts + run: | + # Check if there are any pending migrations + npx prisma migrate status --schema=./prisma/schema.prisma + + - name: Generate Prisma client + run: npx prisma generate + + # Backup database before migration + backup-database: + runs-on: ubuntu-latest + needs: validate-migrations + if: github.event.inputs.environment == 'production' || github.ref == 'refs/heads/master' + environment: ${{ github.event.inputs.environment || 'production' }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Create database backup + run: | + TIMESTAMP=$(date +%Y%m%d_%H%M%S) + BACKUP_FILE="worknow_backup_${TIMESTAMP}.sql" + + # Create backup using pg_dump + PGPASSWORD="${{ secrets.DATABASE_PASSWORD }}" pg_dump \ + -h "${{ secrets.DATABASE_HOST }}" \ + -U "${{ secrets.DATABASE_USER }}" \ + -d "${{ secrets.DATABASE_NAME }}" \ + -f "$BACKUP_FILE" + + # Upload backup to S3 or similar storage + aws s3 cp "$BACKUP_FILE" "s3://${{ secrets.BACKUP_BUCKET }}/database-backups/$BACKUP_FILE" + + echo "BACKUP_FILE=$BACKUP_FILE" >> $GITHUB_ENV + + - name: Send backup completion email + uses: dawidd6/action-send-mail@v3 + with: + server_address: smtp.gmail.com + server_port: 587 + username: ${{ secrets.EMAIL_USERNAME }} + password: ${{ secrets.EMAIL_PASSWORD }} + subject: "WorkNow Database Backup Completed" + to: peterbaikov12@gmail.com + from: ${{ secrets.EMAIL_USERNAME }} + body: | + ✅ WorkNow Database Backup Completed! + + Backup File: ${{ env.BACKUP_FILE }} + Environment: ${{ github.event.inputs.environment || 'production' }} + Branch: ${{ github.ref_name }} + Commit: ${{ github.sha }} + Backed up at: $(date) + + The database backup has been successfully created and uploaded to S3. + + Best regards, + WorkNow CI/CD System + + # Run migrations on staging + migrate-staging: + runs-on: ubuntu-latest + needs: validate-migrations + if: github.event.inputs.environment == 'staging' || github.ref == 'refs/heads/develop' + environment: staging + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Run staging migrations + run: | + if [ "${{ github.event.inputs.migration_type }}" = "reset" ]; then + npx prisma migrate reset --force + elif [ "${{ github.event.inputs.migration_type }}" = "status" ]; then + npx prisma migrate status + else + npx prisma migrate deploy + fi + env: + DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }} + + - name: Verify staging migration + run: | + npx prisma db push --accept-data-loss + npx prisma generate + env: + DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }} + + # Run migrations on production + migrate-production: + runs-on: ubuntu-latest + needs: [validate-migrations, backup-database] + if: github.event.inputs.environment == 'production' || github.ref == 'refs/heads/master' + environment: production + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Run production migrations + run: | + # Always use deploy for production (never reset) + npx prisma migrate deploy + env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} + + - name: Verify production migration + run: | + npx prisma db push --accept-data-loss + npx prisma generate + env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} + + - name: Run data integrity checks + run: | + # Add custom data integrity checks here + node -e " + const { PrismaClient } = require('@prisma/client'); + const prisma = new PrismaClient(); + + // Check critical tables have data + Promise.all([ + prisma.user.count(), + prisma.job.count(), + prisma.category.count(), + prisma.city.count() + ]).then(([users, jobs, categories, cities]) => { + console.log('Data integrity check:'); + console.log('Users:', users); + console.log('Jobs:', jobs); + console.log('Categories:', categories); + console.log('Cities:', cities); + + if (users === 0 || categories === 0 || cities === 0) { + console.error('Critical data missing!'); + process.exit(1); + } + + console.log('Data integrity check passed ✅'); + prisma.$disconnect(); + }).catch(err => { + console.error('Data integrity check failed:', err); + process.exit(1); + }); + " + env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} + + # Notify migration completion + notify-completion: + runs-on: ubuntu-latest + needs: [migrate-staging, migrate-production] + if: always() + + steps: + - name: Send migration completion email + uses: dawidd6/action-send-mail@v3 + with: + server_address: smtp.gmail.com + server_port: 587 + username: ${{ secrets.EMAIL_USERNAME }} + password: ${{ secrets.EMAIL_PASSWORD }} + subject: "WorkNow Database Migration Completed" + to: peterbaikov12@gmail.com + from: ${{ secrets.EMAIL_USERNAME }} + body: | + ✅ WorkNow Database Migration Completed! + + Environment: ${{ github.event.inputs.environment || 'auto-detected' }} + Migration Type: ${{ github.event.inputs.migration_type || 'deploy' }} + Branch: ${{ github.ref_name }} + Commit: ${{ github.sha }} + Completed at: $(date) + + The database migration has been completed successfully. + Data integrity checks passed. + + Best regards, + WorkNow CI/CD System diff --git a/.github/workflows/deploy-frontend-aws.yml b/.github/workflows/deploy-frontend-aws.yml new file mode 100644 index 0000000..614c3fd --- /dev/null +++ b/.github/workflows/deploy-frontend-aws.yml @@ -0,0 +1,133 @@ +name: Deploy Frontend to AWS S3 (Manual Only) - Updated + +on: + workflow_dispatch: + inputs: + environment: + description: "Environment to deploy to" + required: true + default: "production" + type: choice + options: + - production + - staging + confirm_deployment: + description: "Type 'DEPLOY' to confirm deployment" + required: true + type: string + +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + - name: Build application + run: npm run build + + deploy-frontend: + runs-on: ubuntu-latest + needs: build-and-test + environment: ${{ github.event.inputs.environment }} + if: github.event.inputs.confirm_deployment == 'DEPLOY' + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Build frontend for production + run: | + VITE_API_URL=https://api.worknow.co.il \ + VITE_CLERK_PUBLISHABLE_KEY=${{ secrets.VITE_CLERK_PUBLISHABLE_KEY }} \ + CLERK_SECRET_KEY=${{ secrets.CLERK_SECRET_KEY }} \ + npm run build + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + - name: Navigate to client directory + run: cd apps/client + + - name: Deploy to AWS S3 + run: | + cd apps/client + aws s3 sync dist/ s3://worknow-frontend --delete + + - name: Send email notification + uses: dawidd6/action-send-mail@v3 + with: + server_address: smtp.gmail.com + server_port: 587 + username: ${{ secrets.EMAIL_USERNAME }} + password: ${{ secrets.EMAIL_PASSWORD }} + subject: "WorkNow Frontend Deployment Success" + to: peterbaikov12@gmail.com + from: ${{ secrets.EMAIL_USERNAME }} + body: | + 🚀 WorkNow Frontend Deployment Successful! + + Environment: ${{ github.event.inputs.environment }} + Commit: ${{ github.sha }} + Branch: ${{ github.ref }} + Deployed at: $(date) + + The frontend has been successfully deployed to AWS S3. + + Best regards, + WorkNow CI/CD System + + rollback: + runs-on: ubuntu-latest + needs: deploy-frontend + if: failure() + environment: ${{ github.event.inputs.environment }} + steps: + - name: Send failure email notification + uses: dawidd6/action-send-mail@v3 + with: + server_address: smtp.gmail.com + server_port: 587 + username: ${{ secrets.EMAIL_USERNAME }} + password: ${{ secrets.EMAIL_PASSWORD }} + subject: "WorkNow Frontend Deployment Failed" + to: peterbaikov12@gmail.com + from: ${{ secrets.EMAIL_USERNAME }} + body: | + ❌ WorkNow Frontend Deployment Failed! + + Environment: ${{ github.event.inputs.environment }} + Commit: ${{ github.sha }} + Branch: ${{ github.ref }} + Failed at: $(date) + + The frontend deployment has failed. Manual intervention required. + + Please check the GitHub Actions logs for more details. + + Best regards, + WorkNow CI/CD System diff --git a/.github/workflows/deploy-frontend-manual.yml b/.github/workflows/deploy-frontend-manual.yml new file mode 100644 index 0000000..7eab3d4 --- /dev/null +++ b/.github/workflows/deploy-frontend-manual.yml @@ -0,0 +1,98 @@ +name: Deploy Frontend to AWS S3 (Manual) + +on: + workflow_dispatch: + inputs: + environment: + description: "Environment to deploy to" + required: true + default: "production" + type: choice + options: + - production + - staging + confirm_deployment: + description: "Type 'DEPLOY' to confirm deployment" + required: true + type: string + +env: + NODE_VERSION: "20" + +jobs: + # Deploy frontend to AWS S3 + deploy-frontend: + runs-on: ubuntu-latest + environment: ${{ github.event.inputs.environment }} + if: github.event.inputs.confirm_deployment == 'DEPLOY' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Build frontend for production + run: | + cd apps/client + VITE_API_URL=https://api.worknow.co.il \ + VITE_CLERK_PUBLISHABLE_KEY=pk_live_Y2xlcmsud29ya25vdy5jby5pbCQ \ + npm run build + env: + CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }} + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + - name: Deploy to S3 + run: | + cd apps/client + aws s3 sync dist/ s3://worknow-frontend --delete + + - name: Invalidate CloudFront cache + run: | + aws cloudfront create-invalidation \ + --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \ + --paths "/*" + + - name: Deployment Summary + run: | + echo "🚀 Frontend deployment completed successfully!" + echo "📦 Built from commit: ${{ github.sha }}" + echo "🌍 Environment: ${{ github.event.inputs.environment }}" + echo "📅 Deployed at: $(date)" + echo "🔗 S3 Bucket: s3://worknow-frontend" + + - name: Send email notification + uses: dawidd6/action-send-mail@v3 + with: + server_address: smtp.gmail.com + server_port: 587 + username: ${{ secrets.EMAIL_USERNAME }} + password: ${{ secrets.EMAIL_PASSWORD }} + subject: "WorkNow Frontend Deployment Success" + to: peterbaikov12@gmail.com + from: ${{ secrets.EMAIL_USERNAME }} + body: | + 🚀 WorkNow Frontend Deployment Successful! + + Environment: ${{ github.event.inputs.environment }} + Commit: ${{ github.sha }} + Branch: ${{ github.ref }} + Deployed at: $(date) + + The frontend has been successfully deployed to AWS S3. + + Best regards, + WorkNow CI/CD System diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml new file mode 100644 index 0000000..f604931 --- /dev/null +++ b/.github/workflows/deploy-production.yml @@ -0,0 +1,202 @@ +name: Deploy to Production + +on: + push: + branches: [master] + tags: ["v*"] + workflow_dispatch: + inputs: + environment: + description: "Environment to deploy to" + required: true + default: "production" + type: choice + options: + - production + - staging + +env: + REGISTRY: ghcr.io + IMAGE_NAME: worknow-s-r-o/worknow + +jobs: + # Build and test + build-and-test: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + outputs: + image-tag: ${{ steps.meta.outputs.tags }} + image-digest: ${{ steps.build.outputs.digest }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr,prefix=pr- + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha,prefix=sha- + + - name: Build and push Docker image + id: build + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile.prod + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + platforms: linux/amd64,linux/arm64 + + # Database migration + migrate-database: + runs-on: ubuntu-latest + needs: build-and-test + environment: production + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Run database migrations + run: npx prisma migrate deploy + env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} + + - name: Verify database connection + run: npx prisma db push --accept-data-loss + env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} + + # Deploy to production + deploy: + runs-on: ubuntu-latest + needs: [build-and-test, migrate-database] + environment: production + if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Deploy to production server + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.PRODUCTION_HOST }} + username: ${{ secrets.PRODUCTION_USER }} + key: ${{ secrets.PRODUCTION_SSH_KEY }} + script: | + # Pull latest image + docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.build-and-test.outputs.image-tag }} + + # Update docker-compose with new image + export IMAGE_TAG=${{ needs.build-and-test.outputs.image-tag }} + envsubst < docker-compose.prod.yml > docker-compose.prod.current.yml + + # Deploy with zero downtime + docker-compose -f docker-compose.prod.current.yml up -d --no-deps worknow + + # Clean up old images + docker image prune -f + + - name: Health check + run: | + sleep 30 + curl -f ${{ secrets.PRODUCTION_URL }}/api/health || exit 1 + + - name: Send deployment success email + uses: dawidd6/action-send-mail@v3 + with: + server_address: smtp.gmail.com + server_port: 587 + username: ${{ secrets.EMAIL_USERNAME }} + password: ${{ secrets.EMAIL_PASSWORD }} + subject: "WorkNow Production Deployment Success" + to: peterbaikov12@gmail.com + from: ${{ secrets.EMAIL_USERNAME }} + body: | + 🚀 WorkNow Production Deployment Successful! + + Environment: Production + Commit: ${{ github.sha }} + Branch: ${{ github.ref }} + Deployed at: $(date) + Image Tag: ${{ needs.build-and-test.outputs.image-tag }} + + The production deployment has been completed successfully. + Health check passed. + + Best regards, + WorkNow CI/CD System + + # Rollback job (manual trigger) + rollback: + runs-on: ubuntu-latest + if: failure() && github.event_name == 'workflow_dispatch' + environment: production + + steps: + - name: Rollback to previous version + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.PRODUCTION_HOST }} + username: ${{ secrets.PRODUCTION_USER }} + key: ${{ secrets.PRODUCTION_SSH_KEY }} + script: | + # Rollback to previous working image + docker-compose -f docker-compose.prod.yml down + docker-compose -f docker-compose.prod.yml up -d + + - name: Send rollback email + uses: dawidd6/action-send-mail@v3 + with: + server_address: smtp.gmail.com + server_port: 587 + username: ${{ secrets.EMAIL_USERNAME }} + password: ${{ secrets.EMAIL_PASSWORD }} + subject: "WorkNow Production Rollback Completed" + to: peterbaikov12@gmail.com + from: ${{ secrets.EMAIL_USERNAME }} + body: | + ⚠️ WorkNow Production Rollback Completed! + + Environment: Production + Commit: ${{ github.sha }} + Branch: ${{ github.ref }} + Rolled back at: $(date) + + The production deployment has been rolled back to the previous version. + Please check the system status. + + Best regards, + WorkNow CI/CD System diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml new file mode 100644 index 0000000..1b8a530 --- /dev/null +++ b/.github/workflows/deploy-staging.yml @@ -0,0 +1,182 @@ +name: Deploy to Staging + +on: + push: + branches: [develop, staging] + pull_request: + branches: [master] + types: [opened, synchronize, reopened] + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: worknow-s-r-o/worknow + +jobs: + # Build staging image + build-staging: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + outputs: + image-tag: ${{ steps.meta.outputs.tags }} + pr-number: ${{ steps.pr-meta.outputs.number }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract PR metadata + id: pr-meta + if: github.event_name == 'pull_request' + run: | + echo "number=${{ github.event.number }}" >> $GITHUB_OUTPUT + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr,prefix=pr- + type=sha,prefix=sha- + + - name: Build and push staging image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile.prod + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + platforms: linux/amd64 + + # Deploy to staging (DISABLED - No staging server configured) + # deploy-staging: + # runs-on: ubuntu-latest + # needs: build-staging + # environment: staging + # if: github.event_name == 'pull_request' || github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/staging' + # + # steps: + # - name: Checkout code + # uses: actions/checkout@v4 + # + # - name: Deploy to staging server + # uses: appleboy/ssh-action@v1.0.3 + # with: + # host: ${{ secrets.STAGING_HOST }} + # username: ${{ secrets.STAGING_USER }} + # key: ${{ secrets.STAGING_SSH_KEY }} + # script: | + # # Create staging environment + # export IMAGE_TAG=${{ needs.build-staging.outputs.image-tag }} + # export PR_NUMBER=${{ needs.build-staging.outputs.pr-number }} + # + # # For PR deployments, create isolated environment + # if [ ! -z "$PR_NUMBER" ]; then + # export COMPOSE_PROJECT_NAME="worknow-pr-$PR_NUMBER" + # export STAGING_PORT=$((8000 + PR_NUMBER)) + # else + # export COMPOSE_PROJECT_NAME="worknow-staging" + # export STAGING_PORT=8000 + # fi + # + # # Update environment variables for staging + # cat > .env.staging << EOF + # NODE_ENV=staging + # DATABASE_URL=${{ secrets.STAGING_DATABASE_URL }} + # REDIS_URL=${{ secrets.STAGING_REDIS_URL }} + # CLERK_SECRET_KEY=${{ secrets.STAGING_CLERK_SECRET_KEY }} + # WEBHOOK_SECRET=${{ secrets.STAGING_WEBHOOK_SECRET }} + # STRIPE_SECRET_KEY=${{ secrets.STAGING_STRIPE_SECRET_KEY }} + # EMAIL_USER=${{ secrets.STAGING_EMAIL_USER }} + # EMAIL_PASS=${{ secrets.STAGING_EMAIL_PASS }} + # VITE_CLERK_PUBLISHABLE_KEY=${{ secrets.STAGING_VITE_CLERK_PUBLISHABLE_KEY }} + # VITE_API_URL=${{ secrets.STAGING_VITE_API_URL }} + # VITE_STRIPE_PUBLISHABLE_KEY=${{ secrets.STAGING_VITE_STRIPE_PUBLISHABLE_KEY }} + # PORT=$STAGING_PORT + # EOF + # + # # Deploy staging environment + # docker-compose -f docker/docker-compose.staging.yml --env-file .env.staging up -d + # + # - name: Run staging tests + # run: | + # sleep 30 + # STAGING_URL="http://${{ secrets.STAGING_HOST }}:$STAGING_PORT" + # curl -f $STAGING_URL/api/health || exit 1 + # + # # Run smoke tests + # npm run test:staging -- --baseURL=$STAGING_URL + # + # - name: Comment PR with staging URL + # if: github.event_name == 'pull_request' + # uses: actions/github-script@v7 + # with: + # script: | + # const prNumber = ${{ needs.build-staging.outputs.pr-number }}; + # const stagingPort = 8000 + prNumber; + # const stagingUrl = `http://${{ secrets.STAGING_HOST }}:${stagingPort}`; + # + # github.rest.issues.createComment({ + # issue_number: prNumber, + # owner: context.repo.owner, + # repo: context.repo.repo, + # body: `🚀 **Staging deployment ready!** + # + # **Staging URL:** ${stagingUrl} + # + # **What's deployed:** + # - Branch: \`${context.payload.pull_request.head.ref}\` + # - Commit: \`${context.payload.pull_request.head.sha.substring(0, 7)}\` + # - Image: \`${{ needs.build-staging.outputs.image-tag }}\` + # + # **Test the deployment:** + # - [ ] Health check: ${stagingUrl}/api/health + # - [ ] Frontend: ${stagingUrl} + # - [ ] Authentication flow + # - [ ] Job creation/editing + # - [ ] Payment flow (test mode) + # + # This staging environment will be automatically cleaned up when the PR is closed.` + # }); + + # Cleanup PR environments (DISABLED - No staging server configured) + # cleanup-pr: + # runs-on: ubuntu-latest + # if: github.event_name == 'pull_request' && github.event.action == 'closed' + # environment: staging + + # steps: + # - name: Cleanup PR environment + # uses: appleboy/ssh-action@v1.0.3 + # with: + # host: ${{ secrets.STAGING_HOST }} + # username: ${{ secrets.STAGING_USER }} + # key: ${{ secrets.STAGING_SSH_KEY }} + # script: | + # export PR_NUMBER=${{ github.event.number }} + # export COMPOSE_PROJECT_NAME="worknow-pr-$PR_NUMBER" + # + # # Stop and remove PR environment + # docker-compose -f docker/docker-compose.staging.yml down -v + # + # # Clean up images + # docker image prune -f + # + # echo "✅ PR environment cleaned up successfully!" diff --git a/.github/workflows/docker-optimized.yml b/.github/workflows/docker-optimized.yml new file mode 100644 index 0000000..3bec790 --- /dev/null +++ b/.github/workflows/docker-optimized.yml @@ -0,0 +1,220 @@ +name: Optimized Docker Build + +on: + push: + branches: [master, develop] + pull_request: + branches: [master] + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: worknow-s-r-o/worknow + +jobs: + # Build optimized Docker images + build-images: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + strategy: + matrix: + target: [frontend, backend, production] + + outputs: + frontend-digest: ${{ steps.frontend.outputs.digest }} + backend-digest: ${{ steps.backend.outputs.digest }} + production-digest: ${{ steps.production.outputs.digest }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: | + image=moby/buildkit:v0.12.5 + network=host + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr,prefix=pr- + type=semver,pattern={{version}} + type=sha,prefix=sha- + type=raw,value=${{ github.sha }} + + # Build frontend image + - name: Build frontend image + id: frontend + if: matrix.target == 'frontend' + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile.frontend + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=frontend + # cache-to: type=gha,mode=max,scope=frontend # Disabled due to GitHub Actions Cache service issues + platforms: linux/amd64,linux/arm64 + build-args: | + BUILDKIT_INLINE_CACHE=1 + + # Build backend image + - name: Build backend image + id: backend + if: matrix.target == 'backend' + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile.backend + 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 # Disabled due to GitHub Actions Cache service issues + platforms: linux/amd64,linux/arm64 + build-args: | + BUILDKIT_INLINE_CACHE=1 + + # Build production image + - name: Build production image + id: production + if: matrix.target == 'production' + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile.prod + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: | + type=gha,scope=production + type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:cache + # cache-to: type=gha,mode=max,scope=production # Disabled due to GitHub Actions Cache service issues + platforms: linux/amd64,linux/arm64 + build-args: | + BUILDKIT_INLINE_CACHE=1 + FRONTEND_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-frontend:${{ steps.meta.outputs.tags }} + BACKEND_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-backend:${{ steps.meta.outputs.tags }} + + # Security scan + security-scan: + runs-on: ubuntu-latest + needs: build-images + if: github.event_name == 'pull_request' || github.ref == 'refs/heads/master' + + steps: + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} + format: "sarif" + output: "trivy-results.sarif" + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: "trivy-results.sarif" + + # Image optimization analysis + image-analysis: + runs-on: ubuntu-latest + needs: build-images + + steps: + - name: Analyze image size + run: | + # Pull and analyze the built image + docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} + + # Get image size + IMAGE_SIZE=$(docker images ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} --format "table {{.Size}}") + echo "Image size: $IMAGE_SIZE" + + # Analyze layers + docker history ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} --no-trunc + + # Check for large files + docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ + wagoodman/dive:latest ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \ + --ci --lowestEfficiency 0.8 || echo "Dive analysis completed" + + # Performance testing + performance-test: + runs-on: ubuntu-latest + needs: build-images + if: always() && needs.build-images.result == 'success' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Run container performance tests + run: | + # Start the container + docker run -d --name worknow-test \ + -p 3001:3001 \ + -e NODE_ENV=test \ + -e DATABASE_URL="postgresql://test:test@localhost:5432/test" \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} + + # Wait for container to start + sleep 30 + + # Run basic performance tests + curl -w "@curl-format.txt" -o /dev/null -s "http://localhost:3001/api/health" || echo "Health check failed" + + # Memory usage + docker stats worknow-test --no-stream --format "table {{.MemUsage}}" + + # CPU usage + docker stats worknow-test --no-stream --format "table {{.CPUPerc}}" + + # Cleanup + docker stop worknow-test + docker rm worknow-test + + - name: Create curl format file + run: | + cat > curl-format.txt << EOF + time_namelookup: %{time_namelookup}\n + time_connect: %{time_connect}\n + time_appconnect: %{time_appconnect}\n + time_pretransfer: %{time_pretransfer}\n + time_redirect: %{time_redirect}\n + time_starttransfer: %{time_starttransfer}\n + ----------\n + time_total: %{time_total}\n + EOF + + # Cleanup old images + cleanup: + runs-on: ubuntu-latest + needs: [build-images, security-scan, image-analysis] + if: always() + + steps: + - name: Clean up old images + run: | + # Keep only the last 10 images + docker images ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} --format "table {{.Repository}}:{{.Tag}}" | \ + tail -n +11 | \ + xargs -r docker rmi || echo "No old images to clean up" diff --git a/.github/workflows/docker-simple.yml b/.github/workflows/docker-simple.yml new file mode 100644 index 0000000..25f6e37 --- /dev/null +++ b/.github/workflows/docker-simple.yml @@ -0,0 +1,73 @@ +name: Simplified Docker Build + +on: + push: + branches: [master, develop] + pull_request: + branches: [master] + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: worknow-s-r-o/worknow + +jobs: + # Build Docker image + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr,prefix=pr- + type=sha,prefix=sha- + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile.prod + push: true # Re-enable push after adding labels and creating package + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + platforms: linux/amd64 + + # Security scan (disabled temporarily - requires pushed image) + # security-scan: + # runs-on: ubuntu-latest + # needs: build + # if: github.event_name == 'pull_request' || github.ref == 'refs/heads/master' + # steps: + # - name: Run Trivy vulnerability scanner + # uses: aquasecurity/trivy-action@master + # with: + # image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} + # format: "sarif" + # output: "trivy-results.sarif" + # - name: Upload Trivy scan results to GitHub Security tab + # uses: github/codeql-action/upload-sarif@v3 + # with: + # sarif_file: "trivy-results.sarif" diff --git a/.github/workflows/monitoring.yml b/.github/workflows/monitoring.yml new file mode 100644 index 0000000..a469df3 --- /dev/null +++ b/.github/workflows/monitoring.yml @@ -0,0 +1,250 @@ +name: Monitoring and Health Checks + +on: + schedule: + # Run health checks every 5 minutes + - cron: "*/5 * * * *" + workflow_dispatch: + inputs: + environment: + description: "Environment to monitor" + required: true + default: "production" + type: choice + options: + - production + - staging + +jobs: + # Health check production + health-check-production: + runs-on: ubuntu-latest + if: github.event.inputs.environment == 'production' || github.event.schedule + + steps: + - name: Check production health + run: | + PRODUCTION_URL="${{ secrets.PRODUCTION_URL }}" + + # Basic health check + HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$PRODUCTION_URL/api/health") + + if [ "$HTTP_STATUS" != "200" ]; then + echo "❌ Production health check failed (HTTP $HTTP_STATUS)" + exit 1 + fi + + # Response time check + RESPONSE_TIME=$(curl -s -o /dev/null -w "%{time_total}" "$PRODUCTION_URL/api/health") + RESPONSE_TIME_MS=$(echo "$RESPONSE_TIME * 1000" | bc) + + if (( $(echo "$RESPONSE_TIME_MS > 5000" | bc -l) )); then + echo "⚠️ Production response time is slow: ${RESPONSE_TIME_MS}ms" + else + echo "✅ Production health check passed (${RESPONSE_TIME_MS}ms)" + fi + + # Database connectivity check + curl -f "$PRODUCTION_URL/api/health/database" || { + echo "❌ Database connectivity check failed" + exit 1 + } + + # Redis connectivity check + curl -f "$PRODUCTION_URL/api/health/redis" || { + echo "❌ Redis connectivity check failed" + exit 1 + } + + - name: Alert on failure + if: failure() + run: | + echo "🚨 Production Health Check Failed!" + echo "Environment: Production" + echo "Time: $(date)" + echo "Branch: ${{ github.ref_name }}" + echo "Please check the production environment immediately!" + + # Health check staging + health-check-staging: + runs-on: ubuntu-latest + if: github.event.inputs.environment == 'staging' + + steps: + - name: Check staging health + run: | + STAGING_URL="${{ secrets.STAGING_URL }}" + + # Basic health check + HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$STAGING_URL/api/health") + + if [ "$HTTP_STATUS" != "200" ]; then + echo "❌ Staging health check failed (HTTP $HTTP_STATUS)" + exit 1 + fi + + echo "✅ Staging health check passed" + + # Performance monitoring + performance-monitoring: + runs-on: ubuntu-latest + if: github.event.schedule + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Run performance tests + run: | + # Run basic performance tests + npm run test:performance || echo "Performance tests not configured" + + - name: Check application metrics + run: | + PRODUCTION_URL="${{ secrets.PRODUCTION_URL }}" + + # Get application metrics + METRICS=$(curl -s "$PRODUCTION_URL/api/metrics" || echo "{}") + + # Parse and check key metrics + echo "Application metrics:" + echo "$METRICS" + + # Check if metrics are within acceptable ranges + # Add your specific metric checks here + + # Security monitoring + security-monitoring: + runs-on: ubuntu-latest + if: github.event.schedule + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run security audit + run: | + npm audit --audit-level=moderate + + - name: Check for known vulnerabilities + run: | + npm audit --audit-level=high --json > audit-results.json + + # Check if there are high or critical vulnerabilities + HIGH_VULNS=$(cat audit-results.json | jq '.metadata.vulnerabilities.high // 0') + CRITICAL_VULNS=$(cat audit-results.json | jq '.metadata.vulnerabilities.critical // 0') + + if [ "$HIGH_VULNS" -gt 0 ] || [ "$CRITICAL_VULNS" -gt 0 ]; then + echo "⚠️ Security vulnerabilities found:" + echo "High: $HIGH_VULNS, Critical: $CRITICAL_VULNS" + + # Send alert + curl -X POST -H 'Content-type: application/json' \ + --data "{\"text\":\"⚠️ Security Alert: $HIGH_VULNS high, $CRITICAL_VULNS critical vulnerabilities found in WorkNow\"}" \ + "${{ secrets.SLACK_WEBHOOK_URL }}" + else + echo "✅ No critical security vulnerabilities found" + fi + + # Resource monitoring + resource-monitoring: + runs-on: ubuntu-latest + if: github.event.schedule + + steps: + - name: Check server resources + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.PRODUCTION_HOST }} + username: ${{ secrets.PRODUCTION_USER }} + key: ${{ secrets.PRODUCTION_SSH_KEY }} + script: | + # Check disk usage + DISK_USAGE=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//') + if [ "$DISK_USAGE" -gt 80 ]; then + echo "⚠️ Disk usage is high: ${DISK_USAGE}%" + fi + + # Check memory usage + MEMORY_USAGE=$(free | awk 'NR==2{printf "%.0f", $3*100/$2}') + if [ "$MEMORY_USAGE" -gt 80 ]; then + echo "⚠️ Memory usage is high: ${MEMORY_USAGE}%" + fi + + # Check CPU load + CPU_LOAD=$(uptime | awk -F'load average:' '{print $2}' | awk '{print $1}' | sed 's/,//') + if (( $(echo "$CPU_LOAD > 2.0" | bc -l) )); then + echo "⚠️ CPU load is high: $CPU_LOAD" + fi + + # Check Docker container status + docker ps --format "table {{.Names}}\t{{.Status}}" | grep worknow + + echo "✅ Resource monitoring completed" + + # Log analysis + log-analysis: + runs-on: ubuntu-latest + if: github.event.schedule + + steps: + - name: Analyze application logs + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.PRODUCTION_HOST }} + username: ${{ secrets.PRODUCTION_USER }} + key: ${{ secrets.PRODUCTION_SSH_KEY }} + script: | + # Check for error patterns in logs + ERROR_COUNT=$(docker logs worknow-app --since=1h 2>&1 | grep -i error | wc -l) + + if [ "$ERROR_COUNT" -gt 10 ]; then + echo "⚠️ High error count in last hour: $ERROR_COUNT" + + # Get recent errors + echo "Recent errors:" + docker logs worknow-app --since=1h 2>&1 | grep -i error | tail -5 + else + echo "✅ Error count is normal: $ERROR_COUNT" + fi + + # Check for specific error patterns + docker logs worknow-app --since=1h 2>&1 | grep -E "(timeout|connection refused|database error)" || echo "No critical errors found" + + # Notify monitoring summary + notify-summary: + runs-on: ubuntu-latest + needs: + [ + health-check-production, + performance-monitoring, + security-monitoring, + resource-monitoring, + log-analysis, + ] + if: always() && github.event.schedule + + steps: + - name: Send monitoring summary + run: | + echo "📊 WorkNow Monitoring Summary" + echo "Health Check: ${{ needs.health-check-production.result }}" + echo "Performance: ${{ needs.performance-monitoring.result }}" + echo "Security: ${{ needs.security-monitoring.result }}" + echo "Resources: ${{ needs.resource-monitoring.result }}" + echo "Logs: ${{ needs.log-analysis.result }}" + echo "Time: $(date)" + if [ "${{ job.status }}" == "success" ]; then + echo "✅ All systems operational" + else + echo "⚠️ Some issues detected - check individual jobs" + fi diff --git a/apps/client/src/App.jsx b/apps/client/src/App.jsx index 6c82592..bb1270f 100644 --- a/apps/client/src/App.jsx +++ b/apps/client/src/App.jsx @@ -306,3 +306,5 @@ const App = () => { }; export default App; +// Test CI pipeline +// CI/CD fix test - Sun Sep 14 17:13:28 IDT 2025 diff --git a/docker/Dockerfile.backend b/docker/Dockerfile.backend new file mode 100644 index 0000000..cfd1a4c --- /dev/null +++ b/docker/Dockerfile.backend @@ -0,0 +1,61 @@ +# Backend-only Dockerfile for WorkNow +# Optimized for Node.js/Express API + +FROM node:20-alpine AS backend-builder + +WORKDIR /app + +# Copy package files +COPY package*.json ./ +COPY apps/api/package*.json ./apps/api/ + +# Copy Prisma schema +COPY prisma/ ./prisma/ + +# Install dependencies +RUN npm ci + +# Copy backend source +COPY apps/api/ ./apps/api/ +COPY libs/ ./libs/ + +# Generate Prisma client +RUN npx prisma generate + +# Production stage +FROM node:20-alpine AS production + +# Install necessary packages +RUN apk add --no-cache \ + dumb-init \ + curl \ + && rm -rf /var/cache/apk/* + +# Create app user +RUN addgroup -g 1001 -S nodejs && \ + adduser -S worknow -u 1001 + +WORKDIR /app + +# Copy built backend +COPY --from=backend-builder --chown=worknow:nodejs /app/node_modules ./node_modules +COPY --from=backend-builder --chown=worknow:nodejs /app/apps/api ./apps/api +COPY --from=backend-builder --chown=worknow:nodejs /app/prisma ./prisma +COPY --from=backend-builder --chown=worknow:nodejs /app/libs ./libs +COPY --from=backend-builder --chown=worknow:nodejs /app/package*.json ./ + +# Switch to non-root user +USER worknow + +# Expose port +EXPOSE 3001 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:3001/api/health || exit 1 + +# Use dumb-init to handle signals properly +ENTRYPOINT ["dumb-init", "--"] + +# Start the application +CMD ["node", "apps/api/index.js"] diff --git a/docker/Dockerfile.frontend b/docker/Dockerfile.frontend new file mode 100644 index 0000000..f8990f1 --- /dev/null +++ b/docker/Dockerfile.frontend @@ -0,0 +1,49 @@ +# Frontend-only Dockerfile for WorkNow +# Optimized for building React frontend + +FROM node:20-alpine AS frontend-builder + +WORKDIR /app + +# Copy package files +COPY package*.json ./ +COPY apps/client/package*.json ./apps/client/ +COPY vite.config.js ./ +COPY tailwind.config.js ./ +COPY postcss.config.js ./ + +# Copy Prisma schema for postinstall script +COPY prisma/ ./prisma/ + +# Install dependencies +RUN npm ci + +# Copy frontend source +COPY apps/client/ ./apps/client/ +COPY public/ ./public/ +COPY libs/ ./libs/ + +# Set build environment +ENV NODE_ENV=production +ENV DOCKER_BUILD=true + +# Build frontend +RUN npm run build + +# Production stage +FROM nginx:alpine AS production + +# Copy built frontend +COPY --from=frontend-builder /app/apps/client/dist /usr/share/nginx/html + +# Copy nginx configuration +COPY docker/nginx/frontend.conf /etc/nginx/conf.d/default.conf + +# Expose port +EXPOSE 80 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost/ || exit 1 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/docker/Dockerfile.prod b/docker/Dockerfile.prod index 4fffb23..b98420e 100644 --- a/docker/Dockerfile.prod +++ b/docker/Dockerfile.prod @@ -55,6 +55,11 @@ RUN npx prisma generate # Stage 3: Production runtime FROM node:20-alpine AS production +# Add metadata labels for GitHub Container Registry +LABEL org.opencontainers.image.source=https://github.com/WorkNow-S-R-O/worknow +LABEL org.opencontainers.image.description="WorkNow is a platform that helps employers find employees and job seekers find work. It allows users to post job listings, browse job offers, and contact employers." +LABEL org.opencontainers.image.licenses=MIT + # Install necessary packages RUN apk add --no-cache \ dumb-init \ diff --git a/docker/docker-compose.staging.yml b/docker/docker-compose.staging.yml new file mode 100644 index 0000000..33ecc9c --- /dev/null +++ b/docker/docker-compose.staging.yml @@ -0,0 +1,108 @@ +# Docker Compose Staging Configuration + +services: + # PostgreSQL Database for Staging + postgres-staging: + image: postgres:15-alpine + container_name: worknow-postgres-staging-${COMPOSE_PROJECT_NAME:-staging} + restart: unless-stopped + environment: + POSTGRES_DB: worknow_staging + POSTGRES_USER: worknow_staging + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-staging_password} + volumes: + - postgres_staging_data:/var/lib/postgresql/data + ports: + - "${POSTGRES_PORT:-5433}:5432" + networks: + - worknow-staging-network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U worknow_staging -d worknow_staging"] + interval: 30s + timeout: 10s + retries: 3 + + # Redis for Staging + redis-staging: + image: redis:7-alpine + container_name: worknow-redis-staging-${COMPOSE_PROJECT_NAME:-staging} + restart: unless-stopped + command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-staging_redis_password} + volumes: + - redis_staging_data:/data + ports: + - "${REDIS_PORT:-6380}:6379" + networks: + - worknow-staging-network + healthcheck: + test: ["CMD", "redis-cli", "--raw", "incr", "ping"] + interval: 30s + timeout: 10s + retries: 3 + + # WorkNow Staging Application + worknow-staging: + image: ${REGISTRY:-ghcr.io}/${IMAGE_NAME:-worknow}:${IMAGE_TAG:-latest} + container_name: worknow-app-staging-${COMPOSE_PROJECT_NAME:-staging} + restart: unless-stopped + environment: + # Database + DATABASE_URL: postgresql://worknow_staging:${POSTGRES_PASSWORD:-staging_password}@postgres-staging:5432/worknow_staging + + # Node environment + NODE_ENV: staging + PORT: ${PORT:-8000} + + # Clerk Authentication (staging keys) + CLERK_SECRET_KEY: ${CLERK_SECRET_KEY} + WEBHOOK_SECRET: ${WEBHOOK_SECRET} + + # Stripe (test mode) + STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY} + STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET} + + # Email (staging/test email) + EMAIL_USER: ${EMAIL_USER} + EMAIL_PASS: ${EMAIL_PASS} + + # Redis + REDIS_URL: redis://:${REDIS_PASSWORD:-staging_redis_password}@redis-staging:6379 + + # Frontend Environment Variables + VITE_CLERK_PUBLISHABLE_KEY: ${VITE_CLERK_PUBLISHABLE_KEY} + VITE_API_URL: ${VITE_API_URL} + VITE_STRIPE_PUBLISHABLE_KEY: ${VITE_STRIPE_PUBLISHABLE_KEY} + + # Staging specific + STAGING_MODE: true + LOG_LEVEL: debug + ports: + - "${PORT:-8000}:${PORT:-8000}" + depends_on: + postgres-staging: + condition: service_healthy + redis-staging: + condition: service_healthy + networks: + - worknow-staging-network + healthcheck: + test: + [ + "CMD", + "node", + "-e", + "require('http').get('http://localhost:${PORT:-8000}/api/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })", + ] + interval: 30s + timeout: 10s + retries: 3 + +volumes: + postgres_staging_data: + driver: local + redis_staging_data: + driver: local + +networks: + worknow-staging-network: + driver: bridge diff --git a/docker/nginx/frontend.conf b/docker/nginx/frontend.conf new file mode 100644 index 0000000..5a6bce6 --- /dev/null +++ b/docker/nginx/frontend.conf @@ -0,0 +1,58 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied expired no-cache no-store private must-revalidate auth; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/javascript + application/xml+rss + application/json; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; + + # Handle client-side routing + location / { + try_files $uri $uri/ /index.html; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # API proxy (if needed) + location /api/ { + proxy_pass http://backend:3001; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } +} diff --git a/fix-cicd.sh b/fix-cicd.sh new file mode 100755 index 0000000..d7e1999 --- /dev/null +++ b/fix-cicd.sh @@ -0,0 +1,124 @@ +#!/bin/bash + +# Quick CI/CD Fix Script +# This script helps fix common CI/CD issues + +set -e + +echo "🔧 WorkNow CI/CD Fix Script" +echo "============================" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +print_status() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Check if we're in a git repository +if [ ! -d ".git" ]; then + print_error "Not in a git repository. Please run this script from the project root." + exit 1 +fi + +print_status "Fixing CI/CD pipeline issues..." + +# 1. Test local builds first +print_status "1. Testing local Docker builds..." + +if docker build -f docker/Dockerfile.frontend -t worknow-frontend:test .; then + print_success "Frontend Docker build successful" +else + print_error "Frontend Docker build failed" + exit 1 +fi + +if docker build -f docker/Dockerfile.backend -t worknow-backend:test .; then + print_success "Backend Docker build successful" +else + print_error "Backend Docker build failed" + exit 1 +fi + +if docker build -f docker/Dockerfile.prod -t worknow:test .; then + print_success "Production Docker build successful" +else + print_error "Production Docker build failed" + exit 1 +fi + +# 2. Test application locally +print_status "2. Testing application locally..." + +if npm ci; then + print_success "Dependencies installed" +else + print_error "Failed to install dependencies" + exit 1 +fi + +if npm run build; then + print_success "Application build successful" +else + print_error "Application build failed" + exit 1 +fi + +# 3. Create a test commit to trigger workflows +print_status "3. Creating test commit to trigger workflows..." + +# Create a small change +echo "// CI/CD fix test - $(date)" >> apps/client/src/App.jsx + +# Commit the changes +git add . +git commit -m "fix: test CI/CD pipeline fixes" || print_warning "No changes to commit" + +# Push to trigger workflows +git push origin HEAD || print_warning "Failed to push (may need to set upstream)" + +print_success "Test commit created and pushed" + +# 4. Check workflow status +if command -v gh &> /dev/null; then + print_status "4. Checking workflow status..." + + if gh auth status &> /dev/null; then + print_status "Recent workflow runs:" + gh run list --limit 5 + + print_status "Available workflows:" + gh workflow list + else + print_warning "GitHub CLI not authenticated. Run: gh auth login" + fi +else + print_warning "GitHub CLI not installed. Install from: https://cli.github.com/" +fi + +print_success "CI/CD fix script completed!" +print_status "Check GitHub Actions tab to see if workflows are now working." +print_status "If issues persist, check the workflow logs for specific error messages." + +echo "" +print_status "Next steps:" +echo "1. Go to GitHub → Actions tab" +echo "2. Check if workflows are running successfully" +echo "3. If still failing, check the logs for specific errors" +echo "4. The simplified docker-simple.yml workflow should work better" diff --git a/package.json b/package.json index b513409..cd2b87b 100755 --- a/package.json +++ b/package.json @@ -1,129 +1,147 @@ { - "name": "job-searcher", - "private": true, - "version": "0.0.1", - "type": "module", - "url": "https://github.com/MostOfLuck/job-listing", - "scripts": { - "dev": "concurrently \"vite --config vite.config.js\" \"node apps/api/index.js\"", - "build": "vite build --config vite.config.js", - "build:server": "npm run build && npm run start", - "test": "vitest", - "test:ui": "vitest --ui", - "test:coverage": "vitest run --coverage", - "test:run": "vitest run", - "preview": "vite preview", - "start": "node apps/api/index.js", - "lint": "biome .", - "prepare": "husky install", - "release": "standard-version", - "changelog": "auto-changelog --commit-limit false --unreleased", - "prisma:push": "prisma db push", - "prisma:studio": "prisma studio", - "prisma:seed": "node prisma/seed.js", - "postinstall": "prisma generate", - "setup": "node setup-env.js" - }, - "lint-staged": { - "*.{js,jsx,ts,tsx}": [ - "biome --fix" - ] - }, - "dependencies": { - "@clerk/clerk-react": "^5.20.4", - "@clerk/localizations": "^3.9.4", - "@clerk/themes": "^2.2.16", - "@faker-js/faker": "^9.5.1", - "@hookform/resolvers": "^4.1.0", - "@popperjs/core": "^2.11.8", - "@prisma/client": "^6.1.0", - "@stripe/react-stripe-js": "^3.1.1", - "@stripe/stripe-js": "^5.6.0", - "@supabase/supabase-js": "^2.47.12", - "@vitest/coverage-v8": "^3.2.4", - "aws-sdk": "^2.1692.0", - "axios": "^1.8.1", - "bad-words": "^4.0.0", - "bad-words-next": "^3.1.1", - "body-parser": "^1.20.3", - "bootstrap": "^5.3.7", - "bootstrap-icons": "^1.13.1", - "cheerio": "^1.0.0", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "cors": "^2.8.5", - "date-fns": "^4.1.0", - "dotenv": "^16.4.7", - "export-to-csv": "^1.4.0", - "express": "^4.21.2", - "gtag": "^1.0.1", - "intlayer": "^5.8.1", - "ioredis": "^5.6.1", - "multer": "^2.0.2", - "node-cron": "^3.0.3", - "nodemailer": "^6.10.0", - "nprogress": "^0.2.0", - "openai": "^5.10.2", - "postgres": "^3.4.5", - "prop-types": "^15.8.1", - "puppeteer": "^24.3.0", - "react": "^18.3.1", - "react-bootstrap": "^2.10.10", - "react-bootstrap-icons": "^1.11.6", - "react-dom": "^18.3.1", - "react-helmet": "^6.1.0", - "react-helmet-async": "^2.0.5", - "react-hook-form": "^7.54.2", - "react-hot-toast": "^2.5.2", - "react-intlayer": "^5.8.1", - "react-loading-skeleton": "^3.5.0", - "react-router-dom": "^7.0.2", - "react-select": "^5.10.0", - "react-toastify": "^11.0.3", - "redis": "^5.6.1", - "resend": "^3.5.0", - "string-similarity": "^4.0.4", - "stripe": "^17.6.0", - "svix": "^1.45.1", - "tailwind-merge": "^3.0.1", - "tailwindcss-animate": "^1.0.7", - "uuid": "^11.1.0", - "winston": "^3.17.0", - "zod": "^3.24.2", - "zustand": "^5.0.2" - }, - "devDependencies": { - "@biomejs/biome": "2.2.3", - "@testing-library/jest-dom": "^6.6.4", - "@testing-library/react": "^16.2.0", - "@testing-library/user-event": "^14.6.1", - "@types/express": "^5.0.0", - "@types/node": "^22.15.32", - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", - "@vitejs/plugin-react": "^4.3.4", - "@vitest/ui": "^3.2.4", - "auto-changelog": "^2.5.0", - "autoprefixer": "^10.4.20", - "babel-jest": "^29.7.0", - "concurrently": "^9.1.2", - "globals": "^15.12.0", - "husky": "^9.1.7", - "jsdom": "^26.1.0", - "lint-staged": "^15.5.2", - "postcss": "^8.4.49", - "prisma": "^6.1.0", - "release-it": "^17.11.0", - "sass": "^1.83.0", - "standard-version": "^9.5.0", - "supabase": "^2.9.6", - "supertest": "^7.1.4", - "tailwindcss": "^3.4.16", - "ts-node": "^10.9.2", - "tsx": "^4.19.2", - "typescript": "^5.7.3", - "vite": "^6.0.1", - "vite-intlayer": "^5.8.1", - "vitest": "^3.2.4" - } + "name": "job-searcher", + "private": true, + "version": "0.0.1", + "type": "module", + "url": "https://github.com/MostOfLuck/job-listing", + "scripts": { + "dev": "concurrently \"vite --config vite.config.js\" \"node apps/api/index.js\"", + "build": "vite build --config vite.config.js", + "build:server": "npm run build && npm run start", + "test": "vitest", + "test:ui": "vitest --ui", + "test:coverage": "vitest run --coverage", + "test:run": "vitest run", + "test:staging": "vitest run --config vitest.staging.config.js", + "test:performance": "vitest run --config vitest.performance.config.js", + "preview": "vite preview", + "start": "node apps/api/index.js", + "start:prod": "NODE_ENV=production node apps/api/index.js", + "lint": "biome .", + "lint:fix": "biome --fix .", + "prepare": "husky install", + "release": "standard-version", + "changelog": "auto-changelog --commit-limit false --unreleased", + "prisma:push": "prisma db push", + "prisma:migrate": "prisma migrate deploy", + "prisma:migrate:dev": "prisma migrate dev", + "prisma:migrate:reset": "prisma migrate reset", + "prisma:studio": "prisma studio", + "prisma:seed": "node prisma/seed.js", + "prisma:generate": "prisma generate", + "postinstall": "prisma generate", + "setup": "node setup-env.js", + "docker:build": "docker build -f docker/Dockerfile.prod -t worknow:latest .", + "docker:build:frontend": "docker build -f docker/Dockerfile.frontend -t worknow-frontend:latest .", + "docker:build:backend": "docker build -f docker/Dockerfile.backend -t worknow-backend:latest .", + "docker:run": "docker-compose -f docker/docker-compose.prod.yml up -d", + "docker:run:staging": "docker-compose -f docker/docker-compose.staging.yml up -d", + "docker:stop": "docker-compose -f docker/docker-compose.prod.yml down", + "docker:logs": "docker-compose -f docker/docker-compose.prod.yml logs -f", + "health:check": "curl -f http://localhost:3001/api/health || exit 1", + "db:backup": "pg_dump $DATABASE_URL > backup_$(date +%Y%m%d_%H%M%S).sql", + "db:restore": "psql $DATABASE_URL < $1" + }, + "lint-staged": { + "*.{js,jsx,ts,tsx}": [ + "biome --fix" + ] + }, + "dependencies": { + "@clerk/clerk-react": "^5.20.4", + "@clerk/localizations": "^3.9.4", + "@clerk/themes": "^2.2.16", + "@faker-js/faker": "^9.5.1", + "@hookform/resolvers": "^4.1.0", + "@popperjs/core": "^2.11.8", + "@prisma/client": "^6.1.0", + "@stripe/react-stripe-js": "^3.1.1", + "@stripe/stripe-js": "^5.6.0", + "@supabase/supabase-js": "^2.47.12", + "@vitest/coverage-v8": "^3.2.4", + "aws-sdk": "^2.1692.0", + "axios": "^1.8.1", + "bad-words": "^4.0.0", + "bad-words-next": "^3.1.1", + "body-parser": "^1.20.3", + "bootstrap": "^5.3.7", + "bootstrap-icons": "^1.13.1", + "cheerio": "^1.0.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cors": "^2.8.5", + "date-fns": "^4.1.0", + "dotenv": "^16.4.7", + "export-to-csv": "^1.4.0", + "express": "^4.21.2", + "gtag": "^1.0.1", + "intlayer": "^5.8.1", + "ioredis": "^5.6.1", + "multer": "^2.0.2", + "node-cron": "^3.0.3", + "nodemailer": "^6.10.0", + "nprogress": "^0.2.0", + "openai": "^5.10.2", + "postgres": "^3.4.5", + "prop-types": "^15.8.1", + "puppeteer": "^24.3.0", + "react": "^18.3.1", + "react-bootstrap": "^2.10.10", + "react-bootstrap-icons": "^1.11.6", + "react-dom": "^18.3.1", + "react-helmet": "^6.1.0", + "react-helmet-async": "^2.0.5", + "react-hook-form": "^7.54.2", + "react-hot-toast": "^2.5.2", + "react-intlayer": "^5.8.1", + "react-loading-skeleton": "^3.5.0", + "react-router-dom": "^7.0.2", + "react-select": "^5.10.0", + "react-toastify": "^11.0.3", + "redis": "^5.6.1", + "resend": "^3.5.0", + "string-similarity": "^4.0.4", + "stripe": "^17.6.0", + "svix": "^1.45.1", + "tailwind-merge": "^3.0.1", + "tailwindcss-animate": "^1.0.7", + "uuid": "^11.1.0", + "winston": "^3.17.0", + "zod": "^3.24.2", + "zustand": "^5.0.2" + }, + "devDependencies": { + "@biomejs/biome": "2.2.3", + "@testing-library/jest-dom": "^6.6.4", + "@testing-library/react": "^16.2.0", + "@testing-library/user-event": "^14.6.1", + "@types/express": "^5.0.0", + "@types/node": "^22.15.32", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "@vitest/ui": "^3.2.4", + "auto-changelog": "^2.5.0", + "autoprefixer": "^10.4.20", + "babel-jest": "^29.7.0", + "concurrently": "^9.1.2", + "globals": "^15.12.0", + "husky": "^9.1.7", + "jsdom": "^26.1.0", + "lint-staged": "^15.5.2", + "postcss": "^8.4.49", + "prisma": "^6.1.0", + "release-it": "^17.11.0", + "sass": "^1.83.0", + "standard-version": "^9.5.0", + "supabase": "^2.9.6", + "supertest": "^7.1.4", + "tailwindcss": "^3.4.16", + "ts-node": "^10.9.2", + "tsx": "^4.19.2", + "typescript": "^5.7.3", + "vite": "^6.0.1", + "vite-intlayer": "^5.8.1", + "vitest": "^3.2.4" + } } diff --git a/test-cicd.sh b/test-cicd.sh new file mode 100755 index 0000000..052ee1a --- /dev/null +++ b/test-cicd.sh @@ -0,0 +1,330 @@ +#!/bin/bash + +# WorkNow CI/CD Pipeline Test Script +# This script helps you test the CI/CD pipeline step by step + +set -e + +echo "🚀 WorkNow CI/CD Pipeline Testing" +echo "==================================" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_status() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Check if we're in a git repository +if [ ! -d ".git" ]; then + print_error "Not in a git repository. Please run this script from the project root." + exit 1 +fi + +# Check if GitHub CLI is installed +if ! command -v gh &> /dev/null; then + print_warning "GitHub CLI (gh) is not installed. Some tests will be skipped." + print_status "Install GitHub CLI: https://cli.github.com/" +fi + +# Function to test local Docker builds +test_docker_builds() { + print_status "Testing Docker builds locally..." + + # Test frontend build + print_status "Building frontend Docker image..." + if npm run docker:build:frontend; then + print_success "Frontend Docker build successful" + else + print_error "Frontend Docker build failed" + return 1 + fi + + # Test backend build + print_status "Building backend Docker image..." + if npm run docker:build:backend; then + print_success "Backend Docker build successful" + else + print_error "Backend Docker build failed" + return 1 + fi + + # Test full production build + print_status "Building production Docker image..." + if npm run docker:build; then + print_success "Production Docker build successful" + else + print_error "Production Docker build failed" + return 1 + fi +} + +# Function to test local application +test_local_app() { + print_status "Testing local application..." + + # Install dependencies + print_status "Installing dependencies..." + if npm ci; then + print_success "Dependencies installed successfully" + else + print_error "Failed to install dependencies" + return 1 + fi + + # Run tests + print_status "Running tests..." + if npm test; then + print_success "Tests passed successfully" + else + print_error "Tests failed" + return 1 + fi + + # Run linting + print_status "Running linting..." + if npm run lint; then + print_success "Linting passed successfully" + else + print_warning "Linting issues found (this is expected for testing)" + fi + + # Build application + print_status "Building application..." + if npm run build; then + print_success "Application build successful" + else + print_error "Application build failed" + return 1 + fi +} + +# Function to test GitHub workflows +test_github_workflows() { + if ! command -v gh &> /dev/null; then + print_warning "Skipping GitHub workflow tests (gh CLI not available)" + return 0 + fi + + print_status "Testing GitHub workflows..." + + # Check if we're authenticated + if ! gh auth status &> /dev/null; then + print_warning "Not authenticated with GitHub CLI. Please run: gh auth login" + return 1 + fi + + # List available workflows + print_status "Available workflows:" + gh workflow list + + # Test workflow syntax + print_status "Checking workflow syntax..." + for workflow in .github/workflows/*.yml; do + if [ -f "$workflow" ]; then + print_status "Checking $workflow..." + if gh workflow view "$(basename "$workflow" .yml)" &> /dev/null; then + print_success "Workflow syntax OK: $(basename "$workflow")" + else + print_error "Workflow syntax error: $(basename "$workflow")" + fi + fi + done +} + +# Function to create test branch and trigger CI +trigger_ci_test() { + if ! command -v gh &> /dev/null; then + print_warning "Skipping CI trigger test (gh CLI not available)" + return 0 + fi + + print_status "Creating test branch to trigger CI..." + + # Create test branch + TEST_BRANCH="test-cicd-$(date +%Y%m%d-%H%M%S)" + git checkout -b "$TEST_BRANCH" + + # Make a small change + echo "// Test CI pipeline - $(date)" >> apps/client/src/App.jsx + + # Commit and push + git add . + git commit -m "test: trigger CI pipeline" + git push origin "$TEST_BRANCH" + + print_success "Test branch created: $TEST_BRANCH" + print_status "Check GitHub Actions tab to see the CI pipeline running" + + # Create PR + print_status "Creating pull request..." + gh pr create --title "Test CI Pipeline" --body "Testing the CI/CD pipeline - $(date)" + + print_success "Pull request created. Check GitHub Actions for workflow execution." +} + +# Function to test staging environment +test_staging_environment() { + print_status "Testing staging environment setup..." + + # Check if staging docker-compose file exists + if [ -f "docker/docker-compose.staging.yml" ]; then + print_success "Staging Docker Compose file exists" + else + print_error "Staging Docker Compose file not found" + return 1 + fi + + # Check if staging Dockerfile exists + if [ -f "docker/Dockerfile.frontend" ] && [ -f "docker/Dockerfile.backend" ]; then + print_success "Staging Dockerfiles exist" + else + print_error "Staging Dockerfiles not found" + return 1 + fi +} + +# Function to check secrets configuration +check_secrets() { + print_status "Checking secrets configuration..." + + if ! command -v gh &> /dev/null; then + print_warning "Skipping secrets check (gh CLI not available)" + return 0 + fi + + # List repository secrets (this will show if any secrets are configured) + print_status "Repository secrets:" + gh secret list 2>/dev/null || print_warning "No secrets configured or insufficient permissions" + + print_status "Environment secrets:" + gh api repos/:owner/:repo/environments 2>/dev/null || print_warning "No environments configured" +} + +# Main test function +run_tests() { + echo "" + print_status "Starting CI/CD pipeline tests..." + echo "" + + # Test 1: Local application + print_status "=== Test 1: Local Application ===" + if test_local_app; then + print_success "Local application tests passed" + else + print_error "Local application tests failed" + return 1 + fi + echo "" + + # Test 2: Docker builds + print_status "=== Test 2: Docker Builds ===" + if test_docker_builds; then + print_success "Docker build tests passed" + else + print_error "Docker build tests failed" + return 1 + fi + echo "" + + # Test 3: Staging environment + print_status "=== Test 3: Staging Environment ===" + if test_staging_environment; then + print_success "Staging environment tests passed" + else + print_error "Staging environment tests failed" + return 1 + fi + echo "" + + # Test 4: GitHub workflows + print_status "=== Test 4: GitHub Workflows ===" + if test_github_workflows; then + print_success "GitHub workflow tests passed" + else + print_warning "GitHub workflow tests had issues" + fi + echo "" + + # Test 5: Secrets configuration + print_status "=== Test 5: Secrets Configuration ===" + check_secrets + echo "" + + print_success "All basic tests completed!" + echo "" + + # Ask if user wants to trigger CI + read -p "Do you want to trigger a CI test by creating a test branch and PR? (y/n): " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + trigger_ci_test + fi +} + +# Help function +show_help() { + echo "WorkNow CI/CD Pipeline Test Script" + echo "" + echo "Usage: $0 [option]" + echo "" + echo "Options:" + echo " --help, -h Show this help message" + echo " --local Run only local tests (no GitHub integration)" + echo " --docker Run only Docker build tests" + echo " --ci Trigger CI test (create test branch and PR)" + echo " --all Run all tests (default)" + echo "" + echo "Examples:" + echo " $0 # Run all tests" + echo " $0 --local # Run only local tests" + echo " $0 --docker # Run only Docker tests" + echo " $0 --ci # Trigger CI test" +} + +# Parse command line arguments +case "${1:-}" in + --help|-h) + show_help + exit 0 + ;; + --local) + test_local_app + ;; + --docker) + test_docker_builds + ;; + --ci) + trigger_ci_test + ;; + --all|"") + run_tests + ;; + *) + print_error "Unknown option: $1" + show_help + exit 1 + ;; +esac + +echo "" +print_success "Test script completed!" +print_status "Check the GitHub Actions tab in your repository to see workflow execution." +print_status "For detailed testing instructions, see: .github/TESTING_GUIDE.md"