Refactor API endpoints and fix product reducer import path #1582
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
| # SonarQube Analysis Pipeline | |
| # Runs code quality and security analysis on every PR and push to main branches | |
| name: SonarQube Analysis | |
| on: | |
| push: | |
| branches: ['develop', 'master'] | |
| pull_request: | |
| branches: ['develop', 'master'] | |
| workflow_dispatch: | |
| inputs: | |
| branch: | |
| description: 'Branch to analyze (must be develop or master)' | |
| required: false | |
| default: 'develop' | |
| type: choice | |
| options: | |
| - develop | |
| - master | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| # SonarQube Community Build 25.x requires the scanner to run on Java 17+ | |
| JAVA_VERSION: '21' | |
| NODE_VERSION: '18.x' | |
| SONAR_HOST_URL: 'https://sonarqube.datainn.io' | |
| jobs: | |
| sonarqube: | |
| name: Build and Analyze | |
| runs-on: k3s-simpleaccounts-runners | |
| timeout-minutes: 45 | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 | |
| with: | |
| fetch-depth: 0 # Full history for accurate blame information | |
| - name: Determine whether to run SonarQube | |
| id: sonar | |
| env: | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN || secrets.SONARQUBE_TOKEN }} | |
| run: | | |
| if [ "${{ github.event_name }}" = "pull_request" ] && [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then | |
| echo "run=false" >> "$GITHUB_OUTPUT" | |
| echo "::notice::Fork PR detected; SonarQube analysis is skipped because GitHub Actions secrets are not available to forks." | |
| exit 0 | |
| fi | |
| if [ "${{ github.event_name }}" = "pull_request" ] && [ -z "$SONAR_TOKEN" ]; then | |
| echo "run=false" >> "$GITHUB_OUTPUT" | |
| echo "::notice::SONAR_TOKEN is not available in this PR context (e.g., Dependabot); skipping SonarQube analysis." | |
| exit 0 | |
| fi | |
| echo "run=true" >> "$GITHUB_OUTPUT" | |
| - name: Set up JDK | |
| if: steps.sonar.outputs.run == 'true' | |
| uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v4 | |
| with: | |
| java-version: ${{ env.JAVA_VERSION }} | |
| distribution: 'temurin' | |
| cache: maven | |
| - name: Set up Node.js | |
| if: steps.sonar.outputs.run == 'true' | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Cache SonarQube packages | |
| if: steps.sonar.outputs.run == 'true' | |
| uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4 | |
| with: | |
| path: ~/.sonar/cache | |
| key: ${{ runner.os }}-sonar | |
| restore-keys: ${{ runner.os }}-sonar | |
| - name: Cache Maven packages | |
| if: steps.sonar.outputs.run == 'true' | |
| uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4 | |
| with: | |
| path: ~/.m2 | |
| key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} | |
| restore-keys: ${{ runner.os }}-m2 | |
| - name: Cache Node dependencies | |
| if: steps.sonar.outputs.run == 'true' | |
| uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4 | |
| with: | |
| path: | | |
| node_modules | |
| apps/frontend/node_modules | |
| key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} | |
| - name: Install Node dependencies | |
| if: steps.sonar.outputs.run == 'true' | |
| run: | | |
| npm ci --legacy-peer-deps | |
| cd apps/frontend && npm ci --legacy-peer-deps | |
| # Frontend: Test and Generate Coverage (don't fail on thresholds, just report to SonarQube) | |
| - name: Test Frontend with Coverage | |
| if: steps.sonar.outputs.run == 'true' | |
| working-directory: apps/frontend | |
| run: npm test -- --coverage --watchAll=false --coverageThreshold='{}' || true | |
| env: | |
| CI: true | |
| NODE_OPTIONS: --openssl-legacy-provider | |
| # Backend: Build, Test, and Analyze with SonarQube | |
| - name: Build and Test Backend | |
| if: steps.sonar.outputs.run == 'true' | |
| working-directory: apps/backend | |
| env: | |
| SPRING_PROFILES_ACTIVE: test | |
| run: ./mvnw -B test -Dspring.profiles.active=test || true | |
| continue-on-error: true | |
| - name: Generate Coverage Report | |
| if: steps.sonar.outputs.run == 'true' | |
| working-directory: apps/backend | |
| run: ./mvnw jacoco:report || true | |
| continue-on-error: true | |
| # Import SSL certificate and run SonarQube Analysis | |
| - name: Import SonarQube SSL Certificate | |
| if: steps.sonar.outputs.run == 'true' | |
| continue-on-error: true | |
| run: | | |
| SONAR_HOST=$(echo "${{ secrets.SONAR_HOST_URL || env.SONAR_HOST_URL }}" | sed -E 's#^https?://##;s#/.*##') | |
| if [ -z "$SONAR_HOST" ]; then | |
| echo "::warning::Unable to resolve SonarQube host from SONAR_HOST_URL." | |
| exit 0 | |
| fi | |
| # Fetch and import the SSL certificate (non-blocking) | |
| echo | openssl s_client -servername "$SONAR_HOST" \ | |
| -connect "$SONAR_HOST:443" 2>/dev/null | \ | |
| openssl x509 -out /tmp/sonar.crt || echo "::warning::Could not fetch SSL certificate" | |
| if [ -f /tmp/sonar.crt ]; then | |
| sudo keytool -import -trustcacerts -keystore $JAVA_HOME/lib/security/cacerts \ | |
| -storepass changeit -noprompt -alias sonarqube -file /tmp/sonar.crt || true | |
| fi | |
| # Run SonarQube Analysis (Community Edition - no branch/PR support) | |
| - name: Run SonarQube Analysis | |
| if: steps.sonar.outputs.run == 'true' | |
| continue-on-error: true | |
| working-directory: apps/backend | |
| env: | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN || secrets.SONARQUBE_TOKEN }} | |
| SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL || env.SONAR_HOST_URL }} | |
| run: | | |
| if [ -z "$SONAR_TOKEN" ]; then | |
| echo "::warning::SONAR_TOKEN is not set (configure repo secret SONAR_TOKEN)." | |
| exit 0 | |
| fi | |
| if [ -z "$SONAR_HOST_URL" ]; then | |
| echo "::warning::SONAR_HOST_URL is not set (configure repo secret SONAR_HOST_URL or workflow env)." | |
| exit 0 | |
| fi | |
| mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \ | |
| -Dsonar.projectKey=SimpleAccounts_SimpleAccounts-UAE_f0046086-4810-411a-9ca7-6017268b2eb9 \ | |
| -Dsonar.projectName="SimpleAccounts-UAE" \ | |
| -Dsonar.host.url="$SONAR_HOST_URL" \ | |
| -Dsonar.token=$SONAR_TOKEN \ | |
| -Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml \ | |
| -Dsonar.javascript.lcov.reportPaths=../../apps/frontend/coverage/lcov.info \ | |
| -Dsonar.scanner.skipJreProvisioning=true \ | |
| -Dsonar.scanner.os=linux \ | |
| -Dsonar.scanner.arch=x64 \ | |
| -Dsonar.scanner.truststorePath=${JAVA_HOME}/lib/security/cacerts \ | |
| -Dsonar.scanner.truststorePassword=changeit \ | |
| -Dsonar.qualitygate.wait=true \ | |
| -Dsonar.qualitygate.timeout=300 \ | |
| -DskipTests=true | |
| # Check Quality Gate status - fails the workflow if quality gate fails | |
| - name: Check Quality Gate Status | |
| if: steps.sonar.outputs.run == 'true' && always() | |
| continue-on-error: true | |
| env: | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN || secrets.SONARQUBE_TOKEN }} | |
| SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL || env.SONAR_HOST_URL }} | |
| run: | | |
| echo "Checking Quality Gate status..." | |
| sleep 15 | |
| if [ -z "$SONAR_TOKEN" ]; then | |
| echo "::warning::SONAR_TOKEN is not set (configure repo secret SONAR_TOKEN)." | |
| exit 0 | |
| fi | |
| if [ -z "$SONAR_HOST_URL" ]; then | |
| echo "::warning::SONAR_HOST_URL is not set (configure repo secret SONAR_HOST_URL or workflow env)." | |
| exit 0 | |
| fi | |
| PROJECT_KEY="SimpleAccounts_SimpleAccounts-UAE_f0046086-4810-411a-9ca7-6017268b2eb9" | |
| API_URL="$SONAR_HOST_URL/api/qualitygates/project_status?projectKey=${PROJECT_KEY}" | |
| DASHBOARD_URL="$SONAR_HOST_URL/dashboard?id=${PROJECT_KEY}" | |
| # Add retry logic to handle transient network/SSL issues | |
| RESPONSE=$(curl -sS --retry 5 --retry-delay 5 --retry-connrefused --connect-timeout 30 --max-time 60 -u "$SONAR_TOKEN:" "$API_URL" 2>&1) #gitleaks:allow | |
| STATUS=$(echo "$RESPONSE" | jq -r '.projectStatus.status // empty' 2>/dev/null) | |
| echo "Quality Gate Status: $STATUS" | |
| echo "Dashboard: $DASHBOARD_URL" | |
| if [ -z "$STATUS" ]; then | |
| echo "" | |
| echo "::warning::Unable to determine Quality Gate status from SonarQube response (connection issue)." | |
| echo "$RESPONSE" | |
| exit 0 | |
| fi | |
| if [ "$STATUS" != "OK" ]; then | |
| echo "" | |
| echo "::error::Quality Gate FAILED!" | |
| echo "Failed conditions:" | |
| echo "$RESPONSE" | jq -r '.projectStatus.conditions[] | select(.status != "OK") | " - \(.metricKey): \(.actualValue) (threshold: \(.errorThreshold))"' | |
| exit 1 | |
| fi | |
| echo "::notice::Quality Gate PASSED!" |