From 803487ef576a4396ea0c739e85f6a7285c6f934e Mon Sep 17 00:00:00 2001 From: erict875 Date: Mon, 1 Sep 2025 16:52:59 +0100 Subject: [PATCH 1/2] feat: add Actuator, expose health/info, update env handling; install curl in Dockerfile for HTTP healthchecks --- cashu-gateway-rest/Dockerfile | 5 ++++- cashu-gateway-rest/pom.xml | 4 ++++ .../src/main/resources/application.properties | 12 ++++++++---- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/cashu-gateway-rest/Dockerfile b/cashu-gateway-rest/Dockerfile index e6f373c..6bb68d6 100644 --- a/cashu-gateway-rest/Dockerfile +++ b/cashu-gateway-rest/Dockerfile @@ -7,9 +7,12 @@ RUN mvn -q package -DskipTests # Runtime stage FROM eclipse-temurin:21-jre WORKDIR /app +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl \ + && rm -rf /var/lib/apt/lists/* ENV CASHU_GATEWAY_PORT=8080 ENV VAULT_BASE_URL=http://localhost:${CASHU_GATEWAY_PORT} COPY --from=build /build/target/cashu-gateway-rest-*.jar /app/app.jar RUN chown -R /app EXPOSE ${CASHU_GATEWAY_PORT} -ENTRYPOINT ["java","-jar","/app/app.jar"] \ No newline at end of file +ENTRYPOINT ["java","-jar","/app/app.jar"] diff --git a/cashu-gateway-rest/pom.xml b/cashu-gateway-rest/pom.xml index d264166..51f664d 100644 --- a/cashu-gateway-rest/pom.xml +++ b/cashu-gateway-rest/pom.xml @@ -80,6 +80,10 @@ org.projectlombok lombok + + org.springframework.boot + spring-boot-starter-actuator + diff --git a/cashu-gateway-rest/src/main/resources/application.properties b/cashu-gateway-rest/src/main/resources/application.properties index 9053e18..692a44c 100644 --- a/cashu-gateway-rest/src/main/resources/application.properties +++ b/cashu-gateway-rest/src/main/resources/application.properties @@ -1,10 +1,14 @@ spring.application.name=cashu-gateway-spring -server.port=${cashu_gateway_port:8080} +server.port= #spring.datasource.url=jdbc:postgresql://dbserver/gateway -spring.datasource.url=jdbc:postgresql://localhost:5432/cashu-gateway +spring.datasource.url= spring.datasource.driverClassName=org.postgresql.Driver -spring.datasource.username=postgres -spring.datasource.password=password +spring.datasource.username= +spring.datasource.password= spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect +server.address= +management.endpoints.web.exposure.include=health,info +management.endpoint.health.probes.enabled=true +management.health.defaults.enabled=true From 76ea9ca0bbe86c1c97f950e079274b1563429ac0 Mon Sep 17 00:00:00 2001 From: erict875 Date: Mon, 1 Sep 2025 17:23:16 +0100 Subject: [PATCH 2/2] chore: consolidate workflows; remove `CodeQL` and `PR Quality Gate`, add `Qodana` workflow for code analysis --- .github/workflows/code-quality.yml | 28 ++++++ .github/workflows/codeql-analysis.yml | 29 ------ .github/workflows/pr-quality-gate.yml | 138 -------------------------- 3 files changed, 28 insertions(+), 167 deletions(-) create mode 100644 .github/workflows/code-quality.yml delete mode 100644 .github/workflows/codeql-analysis.yml delete mode 100644 .github/workflows/pr-quality-gate.yml diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml new file mode 100644 index 0000000..c4335be --- /dev/null +++ b/.github/workflows/code-quality.yml @@ -0,0 +1,28 @@ +name: Qodana +on: + workflow_dispatch: + pull_request: + push: + branches: # Specify your branches here + - main # The 'main' branch + - 'releases/*' # The release branches + +jobs: + qodana: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + checks: write + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit + fetch-depth: 0 # a full history is required for pull request analysis + - name: 'Qodana Scan' + uses: JetBrains/qodana-action@v2025.1 + with: + pr-mode: false + env: + QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} + QODANA_ENDPOINT: 'https://qodana.cloud' \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index f6f50ba..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: CodeQL - -# Disable GitHub's default CodeQL setup in the repository settings to avoid -# "advanced configuration" errors when using this workflow. - -on: - push: - branches: [main, develop] - pull_request: - branches: [main, develop] - -jobs: - analyze: - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - steps: - - uses: actions/checkout@v5 - - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: '21' - - uses: github/codeql-action/init@v3 - with: - languages: 'java' - - uses: github/codeql-action/autobuild@v3 - - uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/pr-quality-gate.yml b/.github/workflows/pr-quality-gate.yml deleted file mode 100644 index c398028..0000000 --- a/.github/workflows/pr-quality-gate.yml +++ /dev/null @@ -1,138 +0,0 @@ -name: PR Quality Gate - -on: - pull_request: - branches: [develop] - types: [opened, edited, synchronize, ready_for_review] - -permissions: - issues: write - pull-requests: write - -jobs: - review: - if: ${{ github.actor != 'dependabot[bot]' }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Verify Copilot instructions - run: test -s .github/copilot-instructions.md - - name: Run expertise standard checks - uses: actions/github-script@v7 - with: - script: | - const pr = context.payload.pull_request; - const owner = context.repo.owner; - const repo = context.repo.repo; - // Fetch fresh PR data + files - const { data: prData } = await github.rest.pulls.get({ - owner, repo, pull_number: pr.number - }); - // Sum changed lines (additions + deletions) - const totalChanged = prData.additions + prData.deletions; - // Pull files for basic heuristics (e.g., tests touched?) - const files = await github.paginate( - github.rest.pulls.listFiles, { owner, repo, pull_number: pr.number } - ); - const commits = await github.paginate( - github.rest.pulls.listCommits, { owner, repo, pull_number: pr.number } - ); - const extsCode = ['.js','.ts','.tsx','.jsx','.py','.rb','.go','.rs','.java','.kt','.cs','.php','.c','.cc','.cpp','.m','.mm','.swift','.scala','.sh','.yml','.yaml','.json','.toml']; - const extsTests = ['.spec.','.test.','/tests/','/__tests__/']; - const codeTouched = files.some(f => - extsCode.some(ext => f.filename.includes(ext))); - const testsTouched = files.some(f => - extsTests.some(tok => f.filename.includes(tok))); - // 1) Scope ≤ 300 lines (from GitHub blog checklist) - const scopeOK = totalChanged <= 300; - // 2) Title and commits follow type: description (verb + object) - const title = prData.title.trim(); - const types = ['feat','fix','docs','refactor','test','chore','ci','build','perf','style']; - const naming = `^(${types.join('|')}):\\s+[A-Z][^\\s]*\\s+.+`; - const titleOK = new RegExp(naming).test(title); - const commitsOK = commits.every(c => new RegExp(naming).test(c.commit.message.split('\\n')[0])); - // 3) Description “why now?” + links to issue - const body = (prData.body || '').trim(); - const hasIssueLink = /#[0-9]+|https?:\/\/github\.com\/.+\/issues\/[0-9]+/i.test(body); - const mentionsWhy = /\bwhy\b|\bbecause\b|\brationale\b|\bcontext\b/i.test(body); - const descOK = body.length >= 50 && (mentionsWhy || hasIssueLink); - // 4) BREAKING change highlighted - const breakingFlagPresent = /\*\*?BREAKING\*\*?|⚠️\s*BREAKING|BREAKING CHANGE/i.test(title) || /\*\*?BREAKING\*\*?|⚠️\s*BREAKING|BREAKING CHANGE/i.test(body); - // Heuristic: if "breaking" appears anywhere, require emphasis flag; otherwise pass. - const containsBreakingWord = /\bbreaking\b/i.test(title) || /\bbreaking\b/i.test(body); - const breakingOK = containsBreakingWord ? breakingFlagPresent : true; - // 5) Request specific feedback - const feedbackOK = /\b(feedback|review focus|please focus|looking for|need input)\b/i.test(body); - // Soft hint: if code changed but no tests changed, nudge (not blocking per article) - const testsHint = codeTouched && !testsTouched; - // Build result table - function row(name, ok, hint='') { - const status = ok ? '✅' : '❌'; - const extra = hint ? ` — ${hint}` : ''; - return `| ${status} | ${name}${extra} |`; - } - const report = [ - `### PR Quality Gate — AI-Era Expertise Standard`, - `This automated review checks your PR against the five items GitHub recommends for high-quality, human-in-the-loop reviews.`, - ``, - `| Pass | Check |`, - `|:----:|:------|`, - row(`Scope ≤ 300 changed lines (current: ${totalChanged})`, scopeOK, scopeOK ? '' : 'Consider splitting into smaller PRs (stacking).'), - row(`Title and commits use type: description (verb + object)`, titleOK && commitsOK), - row(`Description answers "why now?" and links an issue`, descOK, hasIssueLink ? '' : 'Add a linked issue (#123) or URL.'), - row(`Highlight breaking changes with **BREAKING** or ⚠️ BREAKING`, breakingOK, containsBreakingWord && !breakingFlagPresent ? 'Add explicit BREAKING flag.' : ''), - row(`Request specific feedback (e.g., "Concurrency strategy OK?")`, feedbackOK), - ``, - testsHint ? `> ℹ️ Heads-up: Code changed but tests weren’t touched. The blog suggests reviewers read tests first—consider adding or updating tests for clarity.` : ``, - ``, - `_This gate is derived from GitHub’s “Why developer expertise matters more than ever in the age of AI.”_` - ].filter(Boolean).join('\n'); - // Determine blocking result (fail if any required check fails) - const failures = []; - if (!scopeOK) failures.push('Scope > 300 lines'); - if (!titleOK || !commitsOK) failures.push('Naming format invalid'); - if (!descOK) failures.push('Description lacks why/issue link'); - if (!breakingOK) failures.push('Missing explicit BREAKING flag'); - if (!feedbackOK) failures.push('No specific feedback requested'); - const sameRepo = pr.head.repo.full_name === `${owner}/${repo}`; - if (sameRepo) { - try { - // Upsert a single sticky comment - const bot = (await github.rest.users.getAuthenticated()).data.login; - const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number: pr.number }); - const existing = comments.find(c => c.user?.login === bot && /PR Quality Gate — AI-Era/.test(c.body || '')); - if (existing) { - await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body: report }); - } else { - await github.rest.issues.createComment({ owner, repo, issue_number: pr.number, body: report }); - } - // Add labels for visibility - const addLabel = async (name) => { - await github.rest.issues.addLabels({ owner, repo, issue_number: pr.number, labels: [name] }); - }; - const removeLabel = async (name) => { - await github.rest.issues.removeLabel({ owner, repo, issue_number: pr.number, name }); - }; - if (failures.length) { - await addLabel('needs-quality-fixes'); - } else { - await removeLabel('needs-quality-fixes'); - await addLabel('quality-checked'); - } - } catch (error) { - if (error.message && error.message.includes('Resource not accessible by integration')) { - core.warning('Skipping comment and label updates due to insufficient permissions.'); - } else { - throw error; - } - } - } else { - core.warning('PR originates from a fork; skipping comment and label updates.'); - } - // Fail the job if there are blocking issues - if (failures.length) { - core.setFailed('PR failed the expertise standard: ' + failures.join(', ')); - } else { - core.info('PR passes the expertise standard.'); - } -