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.');
- }
-
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