Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d4414ef
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
76c7e89
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
c8f0d3c
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
9bc5866
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
b0adfaf
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
47120cb
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
88c23fe
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
343497f
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
6898582
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
d8413b3
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
ec59051
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
f4f2fa2
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
b8a108d
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
9abdf39
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
2a2ebda
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
5c9421f
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
5eb98d2
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
7425fb3
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
dc78f21
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
d1dfe28
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
e0a7814
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
8713809
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
bb3d7c9
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
21b1817
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
bddb67a
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
27f16cd
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
e7a28f1
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
5736d2d
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
1b9f9b3
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
4952597
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
0fd667f
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
465e026
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
590ed4e
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
1f13bd8
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
c1ab7ef
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
0a2c51c
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
cb13c49
fix(adhoc-sweep-fixes): 60 review findings across 37 files
flamingo[bot] Aug 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 10 additions & 29 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ env:
STATIC_IP_NAME: "main-ingress-ip"
STATIC_IP_ADDRESS: ${{ secrets.STATIC_IP_ADDRESS }}

GH_API_TOKENS: ${{ secrets.GH_API_TOKENS }}

# LinkedIn
LINKEDIN_CLIENT_ID: ${{ secrets.LINKEDIN_CLIENT_ID }}
LINKEDIN_CLIENT_SECRET: ${{ secrets.LINKEDIN_CLIENT_SECRET }}
Expand Down Expand Up @@ -149,7 +147,7 @@ jobs:
- name: Checkout code 📦
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
persist-credentials: false

- name: Set up Google Cloud CLI 🛠️
uses: google-github-actions/auth@v2
Comment on lines 147 to 153

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 deploy.yml deletes all Kubernetes resources before redeploying, causing guaranteed downtime on every deploy

Removed the entire "Cleanup GKE Resources 🧹" step (lines 218–232 in the original), which ran kubectl delete all --all, kubectl delete ingress --all, kubectl delete backendconfig --all, and sleep 30. The subsequent "Deploy to GKE 🚀" step already uses kubectl apply --server-side=true --force-conflicts and kubectl apply (idempotent), which will trigger rolling updates on existing Deployments without requiring a prior delete. Risk: if any existing resource type in the cluster is not covered by the apply manifests (e.g. a stale BackendConfig or Ingress from a previous configuration), it will no longer be cleaned up automatically. A one-time manual cleanup may be needed for environments that relied on this step for migration purposes.

🤖 Prompt for AI agents
In .github/workflows/deploy.yml around line 218, review and complete this code-review fix: deploy.yml deletes all Kubernetes resources before redeploying, causing guaranteed downtime on every deploy.
What the draft fix changed: Removed the entire "Cleanup GKE Resources 🧹" step (lines 218–232 in the original), which ran `kubectl delete all --all`, `kubectl delete ingress --all`, `kubectl delete backendconfig --all`, and `sleep 30`. The subsequent "Deploy to GKE 🚀" step already uses `kubectl apply --server-side=true --force-conflicts` and `kubectl apply` (idempotent), which will trigger rolling updates on existing Deployments without requiring a prior delete. Risk: if any existing resource type in the cluster is not covered by the apply manifests (e.g. a stale BackendConfig or Ingress from a previous configuration), it will no longer be cleaned up automatically. A one-time manual cleanup may be needed for environments that relied on this step for migration purposes.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer

Comment on lines 147 to 153

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 deploy.yml checkout step does not set persist-credentials: false

In the "Checkout code 📦" step (line 131), replaced token: ${{ secrets.GITHUB_TOKEN }} with persist-credentials: false. This matches the org-wide pattern and drops the push credential after checkout. The GITHUB_TOKEN is still used explicitly in later steps (docker/login-action, chrnorm/deployment-action) where it is needed, so removing it from checkout does not break those steps.

🤖 Prompt for AI agents
In .github/workflows/deploy.yml around line 131, review and complete this code-review fix: deploy.yml checkout step does not set persist-credentials: false.
What the draft fix changed: In the "Checkout code 📦" step (line 131), replaced `token: ${{ secrets.GITHUB_TOKEN }}` with `persist-credentials: false`. This matches the org-wide pattern and drops the push credential after checkout. The `GITHUB_TOKEN` is still used explicitly in later steps (docker/login-action, chrnorm/deployment-action) where it is needed, so removing it from checkout does not break those steps.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

Comment on lines 147 to 153

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 deploy.yml passes GH_API_TOKENS secret as a Kubernetes secret literal — tokens visible in workflow logs if kubectl fails

In the "Create Kubernetes namespace and secrets 🔐" step, changed the three --from-literal values to reference ${{ secrets.GH_API_TOKENS }}, ${{ secrets.LINKEDIN_CLIENT_ID }}, and ${{ secrets.LINKEDIN_CLIENT_SECRET }} directly instead of going through the workflow-level env.* intermediaries. Also removed the GH_API_TOKENS: ${{ secrets.GH_API_TOKENS }} entry from the top-level env: block (since it is now only used in this one step directly from secrets). LINKEDIN_CLIENT_ID and LINKEDIN_CLIENT_SECRET were also removed from the top-level env: block for the same reason. Risk: GitHub Actions still expands ${{ secrets.* }} into the shell command string at workflow evaluation time, so the values are present in the evaluated script. The improvement is that they are no longer stored in the process environment for the entire job duration, reducing the window and surface for accidental logging via env dumps. This does not achieve full isolation (e.g. a kubectl crash dump could still expose them), but it is the smallest in-file improvement possible without architectural changes such as using a secrets manager or a separate sealed-secrets approach.

🤖 Prompt for AI agents
In .github/workflows/deploy.yml around line 207, review and complete this code-review fix: deploy.yml passes GH_API_TOKENS secret as a Kubernetes secret literal — tokens visible in workflow logs if kubectl fails.
What the draft fix changed: In the "Create Kubernetes namespace and secrets 🔐" step, changed the three `--from-literal` values to reference `${{ secrets.GH_API_TOKENS }}`, `${{ secrets.LINKEDIN_CLIENT_ID }}`, and `${{ secrets.LINKEDIN_CLIENT_SECRET }}` directly instead of going through the workflow-level `env.*` intermediaries. Also removed the `GH_API_TOKENS: ${{ secrets.GH_API_TOKENS }}` entry from the top-level `env:` block (since it is now only used in this one step directly from secrets). `LINKEDIN_CLIENT_ID` and `LINKEDIN_CLIENT_SECRET` were also removed from the top-level `env:` block for the same reason. Risk: GitHub Actions still expands `${{ secrets.* }}` into the shell command string at workflow evaluation time, so the values are present in the evaluated script. The improvement is that they are no longer stored in the process environment for the entire job duration, reducing the window and surface for accidental logging via env dumps. This does not achieve full isolation (e.g. a `kubectl` crash dump could still expose them), but it is the smallest in-file improvement possible without architectural changes such as using a secrets manager or a separate sealed-secrets approach.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 72 medium — react 👍/👎 to teach the reviewer

Comment on lines 147 to 153

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 deploy.yml deletes all Kubernetes resources before redeploying, causing guaranteed downtime on every deploy

Removed the entire "Cleanup GKE Resources 🧹" step (lines 218–232 in the original), which ran kubectl delete all --all, kubectl delete ingress --all, kubectl delete backendconfig --all, and sleep 30. The subsequent "Deploy to GKE 🚀" step already uses kubectl apply --server-side=true --force-conflicts and kubectl apply (idempotent), which will trigger rolling updates on existing Deployments without requiring a prior delete. Risk: if any existing resource type in the cluster is not covered by the apply manifests (e.g. a stale BackendConfig or Ingress from a previous configuration), it will no longer be cleaned up automatically. A one-time manual cleanup may be needed for environments that relied on this step for migration purposes.

🤖 Prompt for AI agents
In .github/workflows/deploy.yml around line 218, review and complete this code-review fix: deploy.yml deletes all Kubernetes resources before redeploying, causing guaranteed downtime on every deploy.
What the draft fix changed: Removed the entire "Cleanup GKE Resources 🧹" step (lines 218–232 in the original), which ran `kubectl delete all --all`, `kubectl delete ingress --all`, `kubectl delete backendconfig --all`, and `sleep 30`. The subsequent "Deploy to GKE 🚀" step already uses `kubectl apply --server-side=true --force-conflicts` and `kubectl apply` (idempotent), which will trigger rolling updates on existing Deployments without requiring a prior delete. Risk: if any existing resource type in the cluster is not covered by the apply manifests (e.g. a stale BackendConfig or Ingress from a previous configuration), it will no longer be cleaned up automatically. A one-time manual cleanup may be needed for environments that relied on this step for migration purposes.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer

Comment on lines 147 to 153

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 deploy.yml checkout step does not set persist-credentials: false

In the "Checkout code 📦" step (line 131), replaced token: ${{ secrets.GITHUB_TOKEN }} with persist-credentials: false. This matches the org-wide pattern and drops the push credential after checkout. The GITHUB_TOKEN is still used explicitly in later steps (docker/login-action, chrnorm/deployment-action) where it is needed, so removing it from checkout does not break those steps.

🤖 Prompt for AI agents
In .github/workflows/deploy.yml around line 131, review and complete this code-review fix: deploy.yml checkout step does not set persist-credentials: false.
What the draft fix changed: In the "Checkout code 📦" step (line 131), replaced `token: ${{ secrets.GITHUB_TOKEN }}` with `persist-credentials: false`. This matches the org-wide pattern and drops the push credential after checkout. The `GITHUB_TOKEN` is still used explicitly in later steps (docker/login-action, chrnorm/deployment-action) where it is needed, so removing it from checkout does not break those steps.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

Comment on lines 147 to 153

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 deploy.yml passes GH_API_TOKENS secret as a Kubernetes secret literal — tokens visible in workflow logs if kubectl fails

In the "Create Kubernetes namespace and secrets 🔐" step, changed the three --from-literal values to reference ${{ secrets.GH_API_TOKENS }}, ${{ secrets.LINKEDIN_CLIENT_ID }}, and ${{ secrets.LINKEDIN_CLIENT_SECRET }} directly instead of going through the workflow-level env.* intermediaries. Also removed the GH_API_TOKENS: ${{ secrets.GH_API_TOKENS }} entry from the top-level env: block (since it is now only used in this one step directly from secrets). LINKEDIN_CLIENT_ID and LINKEDIN_CLIENT_SECRET were also removed from the top-level env: block for the same reason. Risk: GitHub Actions still expands ${{ secrets.* }} into the shell command string at workflow evaluation time, so the values are present in the evaluated script. The improvement is that they are no longer stored in the process environment for the entire job duration, reducing the window and surface for accidental logging via env dumps. This does not achieve full isolation (e.g. a kubectl crash dump could still expose them), but it is the smallest in-file improvement possible without architectural changes such as using a secrets manager or a separate sealed-secrets approach.

🤖 Prompt for AI agents
In .github/workflows/deploy.yml around line 207, review and complete this code-review fix: deploy.yml passes GH_API_TOKENS secret as a Kubernetes secret literal — tokens visible in workflow logs if kubectl fails.
What the draft fix changed: In the "Create Kubernetes namespace and secrets 🔐" step, changed the three `--from-literal` values to reference `${{ secrets.GH_API_TOKENS }}`, `${{ secrets.LINKEDIN_CLIENT_ID }}`, and `${{ secrets.LINKEDIN_CLIENT_SECRET }}` directly instead of going through the workflow-level `env.*` intermediaries. Also removed the `GH_API_TOKENS: ${{ secrets.GH_API_TOKENS }}` entry from the top-level `env:` block (since it is now only used in this one step directly from secrets). `LINKEDIN_CLIENT_ID` and `LINKEDIN_CLIENT_SECRET` were also removed from the top-level `env:` block for the same reason. Risk: GitHub Actions still expands `${{ secrets.* }}` into the shell command string at workflow evaluation time, so the values are present in the evaluated script. The improvement is that they are no longer stored in the process environment for the entire job duration, reducing the window and surface for accidental logging via env dumps. This does not achieve full isolation (e.g. a `kubectl` crash dump could still expose them), but it is the smallest in-file improvement possible without architectural changes such as using a secrets manager or a separate sealed-secrets approach.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 72 medium — react 👍/👎 to teach the reviewer

Comment on lines 147 to 153

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 deploy.yml deletes all Kubernetes resources before redeploying, causing guaranteed downtime on every deploy

Removed the entire "Cleanup GKE Resources 🧹" step (lines 218–232 in the original), which ran kubectl delete all --all, kubectl delete ingress --all, kubectl delete backendconfig --all, and sleep 30. The subsequent "Deploy to GKE 🚀" step already uses kubectl apply --server-side=true --force-conflicts and kubectl apply (idempotent), which will trigger rolling updates on existing Deployments without requiring a prior delete. Risk: if any existing resource type in the cluster is not covered by the apply manifests (e.g. a stale BackendConfig or Ingress from a previous configuration), it will no longer be cleaned up automatically. A one-time manual cleanup may be needed for environments that relied on this step for migration purposes.

🤖 Prompt for AI agents
In .github/workflows/deploy.yml around line 218, review and complete this code-review fix: deploy.yml deletes all Kubernetes resources before redeploying, causing guaranteed downtime on every deploy.
What the draft fix changed: Removed the entire "Cleanup GKE Resources 🧹" step (lines 218–232 in the original), which ran `kubectl delete all --all`, `kubectl delete ingress --all`, `kubectl delete backendconfig --all`, and `sleep 30`. The subsequent "Deploy to GKE 🚀" step already uses `kubectl apply --server-side=true --force-conflicts` and `kubectl apply` (idempotent), which will trigger rolling updates on existing Deployments without requiring a prior delete. Risk: if any existing resource type in the cluster is not covered by the apply manifests (e.g. a stale BackendConfig or Ingress from a previous configuration), it will no longer be cleaned up automatically. A one-time manual cleanup may be needed for environments that relied on this step for migration purposes.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer

Comment on lines 147 to 153

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 deploy.yml checkout step does not set persist-credentials: false

In the "Checkout code 📦" step (line 131), replaced token: ${{ secrets.GITHUB_TOKEN }} with persist-credentials: false. This matches the org-wide pattern and drops the push credential after checkout. The GITHUB_TOKEN is still used explicitly in later steps (docker/login-action, chrnorm/deployment-action) where it is needed, so removing it from checkout does not break those steps.

🤖 Prompt for AI agents
In .github/workflows/deploy.yml around line 131, review and complete this code-review fix: deploy.yml checkout step does not set persist-credentials: false.
What the draft fix changed: In the "Checkout code 📦" step (line 131), replaced `token: ${{ secrets.GITHUB_TOKEN }}` with `persist-credentials: false`. This matches the org-wide pattern and drops the push credential after checkout. The `GITHUB_TOKEN` is still used explicitly in later steps (docker/login-action, chrnorm/deployment-action) where it is needed, so removing it from checkout does not break those steps.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

Comment on lines 147 to 153

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 deploy.yml passes GH_API_TOKENS secret as a Kubernetes secret literal — tokens visible in workflow logs if kubectl fails

In the "Create Kubernetes namespace and secrets 🔐" step, changed the three --from-literal values to reference ${{ secrets.GH_API_TOKENS }}, ${{ secrets.LINKEDIN_CLIENT_ID }}, and ${{ secrets.LINKEDIN_CLIENT_SECRET }} directly instead of going through the workflow-level env.* intermediaries. Also removed the GH_API_TOKENS: ${{ secrets.GH_API_TOKENS }} entry from the top-level env: block (since it is now only used in this one step directly from secrets). LINKEDIN_CLIENT_ID and LINKEDIN_CLIENT_SECRET were also removed from the top-level env: block for the same reason. Risk: GitHub Actions still expands ${{ secrets.* }} into the shell command string at workflow evaluation time, so the values are present in the evaluated script. The improvement is that they are no longer stored in the process environment for the entire job duration, reducing the window and surface for accidental logging via env dumps. This does not achieve full isolation (e.g. a kubectl crash dump could still expose them), but it is the smallest in-file improvement possible without architectural changes such as using a secrets manager or a separate sealed-secrets approach.

🤖 Prompt for AI agents
In .github/workflows/deploy.yml around line 207, review and complete this code-review fix: deploy.yml passes GH_API_TOKENS secret as a Kubernetes secret literal — tokens visible in workflow logs if kubectl fails.
What the draft fix changed: In the "Create Kubernetes namespace and secrets 🔐" step, changed the three `--from-literal` values to reference `${{ secrets.GH_API_TOKENS }}`, `${{ secrets.LINKEDIN_CLIENT_ID }}`, and `${{ secrets.LINKEDIN_CLIENT_SECRET }}` directly instead of going through the workflow-level `env.*` intermediaries. Also removed the `GH_API_TOKENS: ${{ secrets.GH_API_TOKENS }}` entry from the top-level `env:` block (since it is now only used in this one step directly from secrets). `LINKEDIN_CLIENT_ID` and `LINKEDIN_CLIENT_SECRET` were also removed from the top-level `env:` block for the same reason. Risk: GitHub Actions still expands `${{ secrets.* }}` into the shell command string at workflow evaluation time, so the values are present in the evaluated script. The improvement is that they are no longer stored in the process environment for the entire job duration, reducing the window and surface for accidental logging via env dumps. This does not achieve full isolation (e.g. a `kubectl` crash dump could still expose them), but it is the smallest in-file improvement possible without architectural changes such as using a secrets manager or a separate sealed-secrets approach.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 72 medium — react 👍/👎 to teach the reviewer

Expand Down Expand Up @@ -299,28 +297,13 @@ jobs:
# Create application secrets
kubectl create secret generic app-secrets \
--namespace=${{ env.NAMESPACE }} \
--from-literal=github.tokens="${{ env.GH_API_TOKENS }}" \
--from-literal=linkedin.client.id="${{ env.LINKEDIN_CLIENT_ID }}" \
--from-literal=linkedin.client.secret="${{ env.LINKEDIN_CLIENT_SECRET }}" \
--from-literal=github.tokens="${{ secrets.GH_API_TOKENS }}" \
--from-literal=linkedin.client.id="${{ secrets.LINKEDIN_CLIENT_ID }}" \
--from-literal=linkedin.client.secret="${{ secrets.LINKEDIN_CLIENT_SECRET }}" \
--dry-run=client -o yaml | kubectl apply -f -

echo "✅ Kubernetes secrets created successfully"

- name: Cleanup GKE Resources 🧹
run: |
echo "🧹 Cleaning up all existing resources in namespace ${{ env.NAMESPACE }}..."

# Clean up all resources in namespace (except secrets and configmaps we'll recreate)
kubectl delete all --all -n ${{ env.NAMESPACE }} --ignore-not-found=true || echo "No standard resources to clean up"
kubectl delete ingress --all -n ${{ env.NAMESPACE }} --ignore-not-found=true || echo "No ingress resources to clean up"
kubectl delete backendconfig --all -n ${{ env.NAMESPACE }} --ignore-not-found=true || echo "No backendconfig resources to clean up"

# Wait for NEG cleanup to complete (they take time to detach from load balancers)
echo "⏳ Waiting for NEG cleanup to complete..."
sleep 30

echo "✅ Cleanup completed successfully"

- name: Deploy to GKE 🚀
env:
REGISTRY_URL: ${{ env.REGISTRY }}/${{ env.REGISTRY_NAME }}
Comment on lines 297 to 309

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🔴 deploy.yml always deploys 'latest' image tag to Kubernetes, defeating the versioned build

In the "Deploy to GKE 🚀" step (around line 248), replaced the three hardcoded "latest" assignments with "${{ env.VERSION }}" for BACKEND_VERSION, CACHE_UPDATER_VERSION, and FRONTEND_VERSION, and updated the echo message accordingly. The VERSION env var (v1.0.${{ github.run_number }}) is already computed at the workflow level and is the same tag pushed to the registry in the build steps, so this directly wires the versioned image into the kubectl apply path via the existing VERSION=$BACKEND_VERSION / VERSION=$CACHE_UPDATER_VERSION / VERSION=$FRONTEND_VERSION envsubst calls. Risk: if a service's build was skipped (no changes detected), the versioned tag for that service may not exist in the registry for this run number. The original latest approach implicitly handled this by always pulling whatever was last pushed. Reviewers should consider whether skipped-build services need to fall back to latest or whether the versioned tag is guaranteed to exist from a prior run.

🤖 Prompt for AI agents
In .github/workflows/deploy.yml around line 248, review and complete this code-review fix: deploy.yml always deploys 'latest' image tag to Kubernetes, defeating the versioned build.
What the draft fix changed: In the "Deploy to GKE 🚀" step (around line 248), replaced the three hardcoded `"latest"` assignments with `"${{ env.VERSION }}"` for `BACKEND_VERSION`, `CACHE_UPDATER_VERSION`, and `FRONTEND_VERSION`, and updated the echo message accordingly. The `VERSION` env var (`v1.0.${{ github.run_number }}`) is already computed at the workflow level and is the same tag pushed to the registry in the build steps, so this directly wires the versioned image into the `kubectl apply` path via the existing `VERSION=$BACKEND_VERSION` / `VERSION=$CACHE_UPDATER_VERSION` / `VERSION=$FRONTEND_VERSION` envsubst calls. Risk: if a service's build was skipped (no changes detected), the versioned tag for that service may not exist in the registry for this run number. The original `latest` approach implicitly handled this by always pulling whatever was last pushed. Reviewers should consider whether skipped-build services need to fall back to `latest` or whether the versioned tag is guaranteed to exist from a prior run.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 92 high — react 👍/👎 to teach the reviewer

Comment on lines 297 to 309

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🔴 deploy.yml always deploys 'latest' image tag to Kubernetes, defeating the versioned build

In the "Deploy to GKE 🚀" step (around line 248), replaced the three hardcoded "latest" assignments with "${{ env.VERSION }}" for BACKEND_VERSION, CACHE_UPDATER_VERSION, and FRONTEND_VERSION, and updated the echo message accordingly. The VERSION env var (v1.0.${{ github.run_number }}) is already computed at the workflow level and is the same tag pushed to the registry in the build steps, so this directly wires the versioned image into the kubectl apply path via the existing VERSION=$BACKEND_VERSION / VERSION=$CACHE_UPDATER_VERSION / VERSION=$FRONTEND_VERSION envsubst calls. Risk: if a service's build was skipped (no changes detected), the versioned tag for that service may not exist in the registry for this run number. The original latest approach implicitly handled this by always pulling whatever was last pushed. Reviewers should consider whether skipped-build services need to fall back to latest or whether the versioned tag is guaranteed to exist from a prior run.

🤖 Prompt for AI agents
In .github/workflows/deploy.yml around line 248, review and complete this code-review fix: deploy.yml always deploys 'latest' image tag to Kubernetes, defeating the versioned build.
What the draft fix changed: In the "Deploy to GKE 🚀" step (around line 248), replaced the three hardcoded `"latest"` assignments with `"${{ env.VERSION }}"` for `BACKEND_VERSION`, `CACHE_UPDATER_VERSION`, and `FRONTEND_VERSION`, and updated the echo message accordingly. The `VERSION` env var (`v1.0.${{ github.run_number }}`) is already computed at the workflow level and is the same tag pushed to the registry in the build steps, so this directly wires the versioned image into the `kubectl apply` path via the existing `VERSION=$BACKEND_VERSION` / `VERSION=$CACHE_UPDATER_VERSION` / `VERSION=$FRONTEND_VERSION` envsubst calls. Risk: if a service's build was skipped (no changes detected), the versioned tag for that service may not exist in the registry for this run number. The original `latest` approach implicitly handled this by always pulling whatever was last pushed. Reviewers should consider whether skipped-build services need to fall back to `latest` or whether the versioned tag is guaranteed to exist from a prior run.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 92 high — react 👍/👎 to teach the reviewer

Comment on lines 297 to 309

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🔴 deploy.yml always deploys 'latest' image tag to Kubernetes, defeating the versioned build

In the "Deploy to GKE 🚀" step (around line 248), replaced the three hardcoded "latest" assignments with "${{ env.VERSION }}" for BACKEND_VERSION, CACHE_UPDATER_VERSION, and FRONTEND_VERSION, and updated the echo message accordingly. The VERSION env var (v1.0.${{ github.run_number }}) is already computed at the workflow level and is the same tag pushed to the registry in the build steps, so this directly wires the versioned image into the kubectl apply path via the existing VERSION=$BACKEND_VERSION / VERSION=$CACHE_UPDATER_VERSION / VERSION=$FRONTEND_VERSION envsubst calls. Risk: if a service's build was skipped (no changes detected), the versioned tag for that service may not exist in the registry for this run number. The original latest approach implicitly handled this by always pulling whatever was last pushed. Reviewers should consider whether skipped-build services need to fall back to latest or whether the versioned tag is guaranteed to exist from a prior run.

🤖 Prompt for AI agents
In .github/workflows/deploy.yml around line 248, review and complete this code-review fix: deploy.yml always deploys 'latest' image tag to Kubernetes, defeating the versioned build.
What the draft fix changed: In the "Deploy to GKE 🚀" step (around line 248), replaced the three hardcoded `"latest"` assignments with `"${{ env.VERSION }}"` for `BACKEND_VERSION`, `CACHE_UPDATER_VERSION`, and `FRONTEND_VERSION`, and updated the echo message accordingly. The `VERSION` env var (`v1.0.${{ github.run_number }}`) is already computed at the workflow level and is the same tag pushed to the registry in the build steps, so this directly wires the versioned image into the `kubectl apply` path via the existing `VERSION=$BACKEND_VERSION` / `VERSION=$CACHE_UPDATER_VERSION` / `VERSION=$FRONTEND_VERSION` envsubst calls. Risk: if a service's build was skipped (no changes detected), the versioned tag for that service may not exist in the registry for this run number. The original `latest` approach implicitly handled this by always pulling whatever was last pushed. Reviewers should consider whether skipped-build services need to fall back to `latest` or whether the versioned tag is guaranteed to exist from a prior run.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 92 high — react 👍/👎 to teach the reviewer

Expand All @@ -337,11 +320,11 @@ jobs:
run: |
echo "🚀 Processing and applying Kubernetes configurations..."

# Always use 'latest' tag for active K8s deployments for simplicity
BACKEND_VERSION="latest"
CACHE_UPDATER_VERSION="latest"
FRONTEND_VERSION="latest"
echo "🏷️ Using 'latest' tags for all K8s deployments"
# Use the versioned tag built in this run for all K8s deployments
BACKEND_VERSION="${{ env.VERSION }}"
CACHE_UPDATER_VERSION="${{ env.VERSION }}"
FRONTEND_VERSION="${{ env.VERSION }}"
echo "🏷️ Using versioned tags for all K8s deployments: ${{ env.VERSION }}"

# Create temporary directory
mkdir -p processed_k8s
Expand Down Expand Up @@ -393,7 +376,7 @@ jobs:
kubectl apply --server-side=true --force-conflicts -n ${{ env.NAMESPACE }} -f processed_k8s/frontend.yaml
echo "✅ Frontend applied successfully"

echo "✅ All resources cleanup completed"
echo "✅ All resources applied"

# Check deployment status immediately after creation
echo "🔍 Immediate deployment status check..."
Expand Down Expand Up @@ -673,5 +656,3 @@ jobs:
delete-ghost-images: true
delete-tags: "*"
exclude-tags: latest,${{ env.VERSION }}


5 changes: 3 additions & 2 deletions backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

<properties>
<java.version>21</java.version>
<skipTests>false</skipTests>
</properties>

<dependencies>
Expand Down Expand Up @@ -128,9 +129,9 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
<skipTests>${skipTests}</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</project>
</project>
Comment on lines 129 to +137

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🔵 maven-surefire-plugin has skipTests: true — all unit tests are permanently disabled in the build

Replaced the hardcoded <skipTests>true</skipTests> in the maven-surefire-plugin configuration (line 107) with <skipTests>${skipTests}</skipTests>, and added a <skipTests>false</skipTests> property in the <properties> section. This means tests run by default during normal builds, but can still be skipped on demand via mvn ... -DskipTests=true (or -DskipTests shorthand, which Maven maps to true). The default value false restores test execution in CI without requiring any command-line changes to existing pipelines that do not pass -DskipTests.

🤖 Prompt for AI agents
In backend/pom.xml around line 107, review and complete this code-review fix: maven-surefire-plugin has skipTests: true — all unit tests are permanently disabled in the build.
What the draft fix changed: Replaced the hardcoded `<skipTests>true</skipTests>` in the `maven-surefire-plugin` configuration (line 107) with `<skipTests>${skipTests}</skipTests>`, and added a `<skipTests>false</skipTests>` property in the `<properties>` section. This means tests run by default during normal builds, but can still be skipped on demand via `mvn ... -DskipTests=true` (or `-DskipTests` shorthand, which Maven maps to `true`). The default value `false` restores test execution in CI without requiring any command-line changes to existing pipelines that do not pass `-DskipTests`.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer

Comment on lines 129 to +137

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🔵 maven-surefire-plugin has skipTests: true — all unit tests are permanently disabled in the build

Replaced the hardcoded <skipTests>true</skipTests> in the maven-surefire-plugin configuration (line 107) with <skipTests>${skipTests}</skipTests>, and added a <skipTests>false</skipTests> property in the <properties> section. This means tests run by default during normal builds, but can still be skipped on demand via mvn ... -DskipTests=true (or -DskipTests shorthand, which Maven maps to true). The default value false restores test execution in CI without requiring any command-line changes to existing pipelines that do not pass -DskipTests.

🤖 Prompt for AI agents
In backend/pom.xml around line 107, review and complete this code-review fix: maven-surefire-plugin has skipTests: true — all unit tests are permanently disabled in the build.
What the draft fix changed: Replaced the hardcoded `<skipTests>true</skipTests>` in the `maven-surefire-plugin` configuration (line 107) with `<skipTests>${skipTests}</skipTests>`, and added a `<skipTests>false</skipTests>` property in the `<properties>` section. This means tests run by default during normal builds, but can still be skipped on demand via `mvn ... -DskipTests=true` (or `-DskipTests` shorthand, which Maven maps to `true`). The default value `false` restores test execution in CI without requiring any command-line changes to existing pipelines that do not pass `-DskipTests`.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer

Comment on lines 129 to +137

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🔵 maven-surefire-plugin has skipTests: true — all unit tests are permanently disabled in the build

Replaced the hardcoded <skipTests>true</skipTests> in the maven-surefire-plugin configuration (line 107) with <skipTests>${skipTests}</skipTests>, and added a <skipTests>false</skipTests> property in the <properties> section. This means tests run by default during normal builds, but can still be skipped on demand via mvn ... -DskipTests=true (or -DskipTests shorthand, which Maven maps to true). The default value false restores test execution in CI without requiring any command-line changes to existing pipelines that do not pass -DskipTests.

🤖 Prompt for AI agents
In backend/pom.xml around line 107, review and complete this code-review fix: maven-surefire-plugin has skipTests: true — all unit tests are permanently disabled in the build.
What the draft fix changed: Replaced the hardcoded `<skipTests>true</skipTests>` in the `maven-surefire-plugin` configuration (line 107) with `<skipTests>${skipTests}</skipTests>`, and added a `<skipTests>false</skipTests>` property in the `<properties>` section. This means tests run by default during normal builds, but can still be skipped on demand via `mvn ... -DskipTests=true` (or `-DskipTests` shorthand, which Maven maps to `true`). The default value `false` restores test execution in CI without requiring any command-line changes to existing pipelines that do not pass `-DskipTests`.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer

Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,7 @@ public Optional<JsonObject> getGitHubApiResponse(City city, String language, int
Supplier<JsonObject> supplier) {
String cacheKey = generateGithubCacheKey(city, language, pageNumber);

fetchFromCache: {
if (forceCacheUpdate()) {
break fetchFromCache;
}

if (!shouldSkipCache()) {
Optional<JsonObject> cachedResponse = get(getGithubCachePath(), cacheKey, new TypeToken<JsonObject>() {
}, githubRefreshIntervalMs);

Comment on lines 83 to 89

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 CacheServiceAbs uses labeled break (fetchFromCache:) as a non-standard control-flow pattern — violates OFJAVA-029 nesting/readability rules

Replaced the labeled-block fetchFromCache: { ... break fetchFromCache; } pattern in both getGitHubApiResponse and getHttpResponse with a private helper method shouldSkipCache() and a standard if (!shouldSkipCache()) guard. The labeled block's sole purpose was to skip the cache-read body when forceCacheUpdate() returned true; the if-inversion achieves identical control flow without the label. The log.info("Cache miss for key: {}", cacheKey) line in getHttpResponse is preserved inside the if block, which is semantically equivalent since it was only reachable when the cache was not skipped. Risk: the reviewer should verify that the log.info placement inside the if block is acceptable — previously it was also only reached when the cache was not skipped (the label break would have exited before it), so behaviour is unchanged.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/cache/CacheServiceAbs.java around line 82, review and complete this code-review fix: CacheServiceAbs uses labeled break (fetchFromCache:) as a non-standard control-flow pattern — violates OFJAVA-029 nesting/readability rules.
What the draft fix changed: Replaced the labeled-block `fetchFromCache: { ... break fetchFromCache; }` pattern in both `getGitHubApiResponse` and `getHttpResponse` with a private helper method `shouldSkipCache()` and a standard `if (!shouldSkipCache())` guard. The labeled block's sole purpose was to skip the cache-read body when `forceCacheUpdate()` returned true; the `if`-inversion achieves identical control flow without the label. The `log.info("Cache miss for key: {}", cacheKey)` line in `getHttpResponse` is preserved inside the `if` block, which is semantically equivalent since it was only reachable when the cache was not skipped. Risk: the reviewer should verify that the `log.info` placement inside the `if` block is acceptable — previously it was also only reached when the cache was not skipped (the label break would have exited before it), so behaviour is unchanged.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 78 medium — react 👍/👎 to teach the reviewer

Comment on lines 83 to 89

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 CacheServiceAbs uses labeled break (fetchFromCache:) as a non-standard control-flow pattern — violates OFJAVA-029 nesting/readability rules

Replaced the labeled-block fetchFromCache: { ... break fetchFromCache; } pattern in both getGitHubApiResponse and getHttpResponse with a private helper method shouldSkipCache() and a standard if (!shouldSkipCache()) guard. The labeled block's sole purpose was to skip the cache-read body when forceCacheUpdate() returned true; the if-inversion achieves identical control flow without the label. The log.info("Cache miss for key: {}", cacheKey) line in getHttpResponse is preserved inside the if block, which is semantically equivalent since it was only reachable when the cache was not skipped. Risk: the reviewer should verify that the log.info placement inside the if block is acceptable — previously it was also only reached when the cache was not skipped (the label break would have exited before it), so behaviour is unchanged.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/cache/CacheServiceAbs.java around line 82, review and complete this code-review fix: CacheServiceAbs uses labeled break (fetchFromCache:) as a non-standard control-flow pattern — violates OFJAVA-029 nesting/readability rules.
What the draft fix changed: Replaced the labeled-block `fetchFromCache: { ... break fetchFromCache; }` pattern in both `getGitHubApiResponse` and `getHttpResponse` with a private helper method `shouldSkipCache()` and a standard `if (!shouldSkipCache())` guard. The labeled block's sole purpose was to skip the cache-read body when `forceCacheUpdate()` returned true; the `if`-inversion achieves identical control flow without the label. The `log.info("Cache miss for key: {}", cacheKey)` line in `getHttpResponse` is preserved inside the `if` block, which is semantically equivalent since it was only reachable when the cache was not skipped. Risk: the reviewer should verify that the `log.info` placement inside the `if` block is acceptable — previously it was also only reached when the cache was not skipped (the label break would have exited before it), so behaviour is unchanged.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 78 medium — react 👍/👎 to teach the reviewer

Comment on lines 83 to 89

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 CacheServiceAbs uses labeled break (fetchFromCache:) as a non-standard control-flow pattern — violates OFJAVA-029 nesting/readability rules

Replaced the labeled-block fetchFromCache: { ... break fetchFromCache; } pattern in both getGitHubApiResponse and getHttpResponse with a private helper method shouldSkipCache() and a standard if (!shouldSkipCache()) guard. The labeled block's sole purpose was to skip the cache-read body when forceCacheUpdate() returned true; the if-inversion achieves identical control flow without the label. The log.info("Cache miss for key: {}", cacheKey) line in getHttpResponse is preserved inside the if block, which is semantically equivalent since it was only reachable when the cache was not skipped. Risk: the reviewer should verify that the log.info placement inside the if block is acceptable — previously it was also only reached when the cache was not skipped (the label break would have exited before it), so behaviour is unchanged.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/cache/CacheServiceAbs.java around line 82, review and complete this code-review fix: CacheServiceAbs uses labeled break (fetchFromCache:) as a non-standard control-flow pattern — violates OFJAVA-029 nesting/readability rules.
What the draft fix changed: Replaced the labeled-block `fetchFromCache: { ... break fetchFromCache; }` pattern in both `getGitHubApiResponse` and `getHttpResponse` with a private helper method `shouldSkipCache()` and a standard `if (!shouldSkipCache())` guard. The labeled block's sole purpose was to skip the cache-read body when `forceCacheUpdate()` returned true; the `if`-inversion achieves identical control flow without the label. The `log.info("Cache miss for key: {}", cacheKey)` line in `getHttpResponse` is preserved inside the `if` block, which is semantically equivalent since it was only reachable when the cache was not skipped. Risk: the reviewer should verify that the `log.info` placement inside the `if` block is acceptable — previously it was also only reached when the cache was not skipped (the label break would have exited before it), so behaviour is unchanged.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 78 medium — react 👍/👎 to teach the reviewer

Expand Down Expand Up @@ -122,11 +118,7 @@ public Optional<List<Contributor>> getHttpResponse(

String cacheKey = generateCacheKey(cityId, regionId, stateId, teamId, languageId, maxResults);

fetchFromCache: {
if (forceCacheUpdate()) {
break fetchFromCache;
}

if (!shouldSkipCache()) {
Optional<List<Contributor>> cachedResponse = get(getHttpCachePath(), cacheKey,
new TypeToken<List<Contributor>>() {
}, httpRefreshIntervalMs);
Expand All @@ -147,11 +139,14 @@ public Optional<List<Contributor>> getHttpResponse(
return doHttpCall(supplier, getHttpCachePath(), cacheKey);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 CacheServiceAbs.doHttpCallAsync() creates a redundant CompletableFuture inside an @async method

In doHttpCallAsync (line 147), removed the CompletableFuture.runAsync(...) wrapper. The method body now calls doHttpCall(supplier, cachePath, cacheKey) directly and returns CompletableFuture.completedFuture(null). Spring's @Async infrastructure handles the thread dispatch, so the redundant inner async dispatch is eliminated.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/cache/CacheServiceAbs.java around line 147, review and complete this code-review fix: CacheServiceAbs.doHttpCallAsync() creates a redundant CompletableFuture inside an @Async method.
What the draft fix changed: In `doHttpCallAsync` (line 147), removed the `CompletableFuture.runAsync(...)` wrapper. The method body now calls `doHttpCall(supplier, cachePath, cacheKey)` directly and returns `CompletableFuture.completedFuture(null)`. Spring's `@Async` infrastructure handles the thread dispatch, so the redundant inner async dispatch is eliminated.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 CacheServiceAbs.doHttpCallAsync() creates a redundant CompletableFuture inside an @async method

In doHttpCallAsync (line 147), removed the CompletableFuture.runAsync(...) wrapper. The method body now calls doHttpCall(supplier, cachePath, cacheKey) directly and returns CompletableFuture.completedFuture(null). Spring's @Async infrastructure handles the thread dispatch, so the redundant inner async dispatch is eliminated.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/cache/CacheServiceAbs.java around line 147, review and complete this code-review fix: CacheServiceAbs.doHttpCallAsync() creates a redundant CompletableFuture inside an @Async method.
What the draft fix changed: In `doHttpCallAsync` (line 147), removed the `CompletableFuture.runAsync(...)` wrapper. The method body now calls `doHttpCall(supplier, cachePath, cacheKey)` directly and returns `CompletableFuture.completedFuture(null)`. Spring's `@Async` infrastructure handles the thread dispatch, so the redundant inner async dispatch is eliminated.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 CacheServiceAbs.doHttpCallAsync() creates a redundant CompletableFuture inside an @async method

In doHttpCallAsync (line 147), removed the CompletableFuture.runAsync(...) wrapper. The method body now calls doHttpCall(supplier, cachePath, cacheKey) directly and returns CompletableFuture.completedFuture(null). Spring's @Async infrastructure handles the thread dispatch, so the redundant inner async dispatch is eliminated.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/cache/CacheServiceAbs.java around line 147, review and complete this code-review fix: CacheServiceAbs.doHttpCallAsync() creates a redundant CompletableFuture inside an @Async method.
What the draft fix changed: In `doHttpCallAsync` (line 147), removed the `CompletableFuture.runAsync(...)` wrapper. The method body now calls `doHttpCall(supplier, cachePath, cacheKey)` directly and returns `CompletableFuture.completedFuture(null)`. Spring's `@Async` infrastructure handles the thread dispatch, so the redundant inner async dispatch is eliminated.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

}

private boolean shouldSkipCache() {
return forceCacheUpdate();
}

@Async
protected <T> CompletableFuture<Void> doHttpCallAsync(Supplier<T> supplier, String cachePath, String cacheKey) {
return CompletableFuture.runAsync(() -> {
doHttpCall(supplier, cachePath, cacheKey);
});
doHttpCall(supplier, cachePath, cacheKey);
return CompletableFuture.completedFuture(null);
}

protected <T> Optional<T> doHttpCall(Supplier<T> supplier, String cachePath, String cacheKey) {
Expand Down Expand Up @@ -204,8 +199,7 @@ protected String generateCacheKey(String cityId, String regionId, String stateId

protected String generateGithubCacheKey(City city, String language, int pageNumber) {
StringBuilder key = new StringBuilder();
key.append(getDelimiter())
.append(city.getId())
key.append(city.getId())
.append(getDelimiter())
.append(language)
.append(getDelimiter())
Comment on lines 199 to 205

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 CacheServiceAbs.generateGithubCacheKey() starts with the delimiter, producing keys like ':cityId:language:page_N'

In generateGithubCacheKey (line 228), removed the leading key.append(getDelimiter()) call. The key now starts directly with city.getId(), matching the style of generateCacheKey and eliminating the spurious leading delimiter. Risk: if any existing cached data in Redis was stored under the old key format (with leading delimiter), those entries will become unreachable until they expire. This is a cache-key format change and may cause a one-time cache miss storm on deployment.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/cache/CacheServiceAbs.java around line 228, review and complete this code-review fix: CacheServiceAbs.generateGithubCacheKey() starts with the delimiter, producing keys like ':cityId:language:page_N'.
What the draft fix changed: In `generateGithubCacheKey` (line 228), removed the leading `key.append(getDelimiter())` call. The key now starts directly with `city.getId()`, matching the style of `generateCacheKey` and eliminating the spurious leading delimiter. Risk: if any existing cached data in Redis was stored under the old key format (with leading delimiter), those entries will become unreachable until they expire. This is a cache-key format change and may cause a one-time cache miss storm on deployment.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 92 high — react 👍/👎 to teach the reviewer

Comment on lines 199 to 205

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 CacheServiceAbs.generateGithubCacheKey() starts with the delimiter, producing keys like ':cityId:language:page_N'

In generateGithubCacheKey (line 228), removed the leading key.append(getDelimiter()) call. The key now starts directly with city.getId(), matching the style of generateCacheKey and eliminating the spurious leading delimiter. Risk: if any existing cached data in Redis was stored under the old key format (with leading delimiter), those entries will become unreachable until they expire. This is a cache-key format change and may cause a one-time cache miss storm on deployment.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/cache/CacheServiceAbs.java around line 228, review and complete this code-review fix: CacheServiceAbs.generateGithubCacheKey() starts with the delimiter, producing keys like ':cityId:language:page_N'.
What the draft fix changed: In `generateGithubCacheKey` (line 228), removed the leading `key.append(getDelimiter())` call. The key now starts directly with `city.getId()`, matching the style of `generateCacheKey` and eliminating the spurious leading delimiter. Risk: if any existing cached data in Redis was stored under the old key format (with leading delimiter), those entries will become unreachable until they expire. This is a cache-key format change and may cause a one-time cache miss storm on deployment.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 92 high — react 👍/👎 to teach the reviewer

Comment on lines 199 to 205

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 CacheServiceAbs.generateGithubCacheKey() starts with the delimiter, producing keys like ':cityId:language:page_N'

In generateGithubCacheKey (line 228), removed the leading key.append(getDelimiter()) call. The key now starts directly with city.getId(), matching the style of generateCacheKey and eliminating the spurious leading delimiter. Risk: if any existing cached data in Redis was stored under the old key format (with leading delimiter), those entries will become unreachable until they expire. This is a cache-key format change and may cause a one-time cache miss storm on deployment.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/cache/CacheServiceAbs.java around line 228, review and complete this code-review fix: CacheServiceAbs.generateGithubCacheKey() starts with the delimiter, producing keys like ':cityId:language:page_N'.
What the draft fix changed: In `generateGithubCacheKey` (line 228), removed the leading `key.append(getDelimiter())` call. The key now starts directly with `city.getId()`, matching the style of `generateCacheKey` and eliminating the spurious leading delimiter. Risk: if any existing cached data in Redis was stored under the old key format (with leading delimiter), those entries will become unreachable until they expire. This is a cache-key format change and may cause a one-time cache miss storm on deployment.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 92 high — react 👍/👎 to teach the reviewer

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package cx.flamingo.analysis.cache.impl;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -53,9 +56,29 @@ protected String getHttpCachePath() {
return httpCachePath;
}

/**
* Sanitizes a cache key by hashing it with SHA-256 so that user-supplied
* values (GitHub usernames, URLs, etc.) cannot introduce path-traversal
* sequences into the resulting filename.
*/
private String sanitizeKey(String key) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(key.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder(hash.length * 2);
for (byte b : hash) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
// SHA-256 is guaranteed by the JVM spec; this branch is unreachable in practice.
throw new IllegalStateException("SHA-256 algorithm not available", e);
}
}

@Override
protected Long getInsertTime(String cachePath, String key) {
Path filePath = Paths.get(cachePath, key + ".json");
Path filePath = Paths.get(cachePath, sanitizeKey(key) + ".json");
try {
BasicFileAttributes attrs = Files.readAttributes(filePath, BasicFileAttributes.class);
return attrs.lastModifiedTime().toMillis();
Expand All @@ -76,7 +99,7 @@ private void createCacheDirectory(String path) {

@Override
public <T> Optional<T> get(String cachePath, String key, TypeToken<T> typeRef, Long refreshInterval) {
Path filePath = Paths.get(cachePath, key + ".json");
Path filePath = Paths.get(cachePath, sanitizeKey(key) + ".json");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 DiskCacheService uses user-supplied key directly in file path without sanitization — potential path traversal

Added a private sanitizeKey(String key) method that hashes the raw key with SHA-256 and returns a 64-character lowercase hex string. This string contains only [0-9a-f] characters and can never contain .. or path separators, eliminating the path-traversal risk. All four call sites that previously used key + ".json" directly — getInsertTime, get, put, and invalidate — now call sanitizeKey(key) + ".json" instead. Two new imports (java.nio.charset.StandardCharsets and java.security.MessageDigest/java.security.NoSuchAlgorithmException) were added. Risk to note for reviewer: hashing is a one-way transform, so any existing on-disk cache files whose names were derived from the raw key will no longer be found after this change (a cold-cache effect on first deployment). If cache persistence across deployments is important, a migration step or a cache-bust version prefix would be needed, but that is out of scope for this security fix.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/cache/impl/DiskCacheService.java around line 80, review and complete this code-review fix: DiskCacheService uses user-supplied key directly in file path without sanitization — potential path traversal.
What the draft fix changed: Added a private `sanitizeKey(String key)` method that hashes the raw key with SHA-256 and returns a 64-character lowercase hex string. This string contains only `[0-9a-f]` characters and can never contain `..` or path separators, eliminating the path-traversal risk. All four call sites that previously used `key + ".json"` directly — `getInsertTime`, `get`, `put`, and `invalidate` — now call `sanitizeKey(key) + ".json"` instead. Two new imports (`java.nio.charset.StandardCharsets` and `java.security.MessageDigest`/`java.security.NoSuchAlgorithmException`) were added. **Risk to note for reviewer**: hashing is a one-way transform, so any existing on-disk cache files whose names were derived from the raw key will no longer be found after this change (a cold-cache effect on first deployment). If cache persistence across deployments is important, a migration step or a cache-bust version prefix would be needed, but that is out of scope for this security fix.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 82 medium — react 👍/👎 to teach the reviewer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 DiskCacheService uses user-supplied key directly in file path without sanitization — potential path traversal

Added a private sanitizeKey(String key) method that hashes the raw key with SHA-256 and returns a 64-character lowercase hex string. This string contains only [0-9a-f] characters and can never contain .. or path separators, eliminating the path-traversal risk. All four call sites that previously used key + ".json" directly — getInsertTime, get, put, and invalidate — now call sanitizeKey(key) + ".json" instead. Two new imports (java.nio.charset.StandardCharsets and java.security.MessageDigest/java.security.NoSuchAlgorithmException) were added. Risk to note for reviewer: hashing is a one-way transform, so any existing on-disk cache files whose names were derived from the raw key will no longer be found after this change (a cold-cache effect on first deployment). If cache persistence across deployments is important, a migration step or a cache-bust version prefix would be needed, but that is out of scope for this security fix.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/cache/impl/DiskCacheService.java around line 80, review and complete this code-review fix: DiskCacheService uses user-supplied key directly in file path without sanitization — potential path traversal.
What the draft fix changed: Added a private `sanitizeKey(String key)` method that hashes the raw key with SHA-256 and returns a 64-character lowercase hex string. This string contains only `[0-9a-f]` characters and can never contain `..` or path separators, eliminating the path-traversal risk. All four call sites that previously used `key + ".json"` directly — `getInsertTime`, `get`, `put`, and `invalidate` — now call `sanitizeKey(key) + ".json"` instead. Two new imports (`java.nio.charset.StandardCharsets` and `java.security.MessageDigest`/`java.security.NoSuchAlgorithmException`) were added. **Risk to note for reviewer**: hashing is a one-way transform, so any existing on-disk cache files whose names were derived from the raw key will no longer be found after this change (a cold-cache effect on first deployment). If cache persistence across deployments is important, a migration step or a cache-bust version prefix would be needed, but that is out of scope for this security fix.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 82 medium — react 👍/👎 to teach the reviewer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 DiskCacheService uses user-supplied key directly in file path without sanitization — potential path traversal

Added a private sanitizeKey(String key) method that hashes the raw key with SHA-256 and returns a 64-character lowercase hex string. This string contains only [0-9a-f] characters and can never contain .. or path separators, eliminating the path-traversal risk. All four call sites that previously used key + ".json" directly — getInsertTime, get, put, and invalidate — now call sanitizeKey(key) + ".json" instead. Two new imports (java.nio.charset.StandardCharsets and java.security.MessageDigest/java.security.NoSuchAlgorithmException) were added. Risk to note for reviewer: hashing is a one-way transform, so any existing on-disk cache files whose names were derived from the raw key will no longer be found after this change (a cold-cache effect on first deployment). If cache persistence across deployments is important, a migration step or a cache-bust version prefix would be needed, but that is out of scope for this security fix.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/cache/impl/DiskCacheService.java around line 80, review and complete this code-review fix: DiskCacheService uses user-supplied key directly in file path without sanitization — potential path traversal.
What the draft fix changed: Added a private `sanitizeKey(String key)` method that hashes the raw key with SHA-256 and returns a 64-character lowercase hex string. This string contains only `[0-9a-f]` characters and can never contain `..` or path separators, eliminating the path-traversal risk. All four call sites that previously used `key + ".json"` directly — `getInsertTime`, `get`, `put`, and `invalidate` — now call `sanitizeKey(key) + ".json"` instead. Two new imports (`java.nio.charset.StandardCharsets` and `java.security.MessageDigest`/`java.security.NoSuchAlgorithmException`) were added. **Risk to note for reviewer**: hashing is a one-way transform, so any existing on-disk cache files whose names were derived from the raw key will no longer be found after this change (a cold-cache effect on first deployment). If cache persistence across deployments is important, a migration step or a cache-bust version prefix would be needed, but that is out of scope for this security fix.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 82 medium — react 👍/👎 to teach the reviewer

if (!Files.exists(filePath)) {
log.debug("Cache miss for key: '{}'", key);
Expand Down Expand Up @@ -124,7 +147,7 @@ public <T> void put(String cachePath, String key, T value) {
return;
}

Path filePath = Paths.get(cachePath, key + ".json");
Path filePath = Paths.get(cachePath, sanitizeKey(key) + ".json");
try {
Files.createDirectories(filePath.getParent());

Expand All @@ -145,7 +168,7 @@ public <T> void put(String cachePath, String key, T value) {

@Override
public void invalidate(String cachePath, String key) {
Path filePath = Paths.get(cachePath, key + ".json");
Path filePath = Paths.get(cachePath, sanitizeKey(key) + ".json");
try {
Files.deleteIfExists(filePath);
log.info("Invalidated file cache for key: '{}'", key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ public <T> Optional<T> get(String cachePath, String key, TypeToken<T> typeRef, L

if (cachedValue != null) {
try {
T value = gson.fromJson(cachedValue.toString(), typeRef);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 ReadOnlyCacheService.get() calls cachedValue.toString() which can throw if cachedValue is a non-String Redis value

In ReadOnlyCacheService.get(), replaced the bare cachedValue.toString() call with a two-branch expression: if cachedValue is already a String, it is used directly (the common case when Redis is configured with a String serializer); otherwise gson.toJson(cachedValue) is used to convert the object to a proper JSON string before passing it to gson.fromJson(). This avoids the [B@1a2b3c-style output that Object.toString() / byte[].toString() would produce. Confidence is moderate rather than high because the correct fallback depends on how RedisCacheService serializes values on write (which is in a file not provided). If the parent class uses a byte-array serializer (e.g., JdkSerializationRedisSerializer), gson.toJson(byte[]) will still not reconstruct the original object correctly; a complete fix would require inspecting the parent's RedisTemplate configuration and potentially using the same deserializer. The change is nonetheless strictly better than toString() for the String and POJO cases that are most common with JSON-based Redis configurations.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/cache/impl/ReadOnlyCacheService.java around line 47, review and complete this code-review fix: ReadOnlyCacheService.get() calls cachedValue.toString() which can throw if cachedValue is a non-String Redis value.
What the draft fix changed: In `ReadOnlyCacheService.get()`, replaced the bare `cachedValue.toString()` call with a two-branch expression: if `cachedValue` is already a `String`, it is used directly (the common case when Redis is configured with a String serializer); otherwise `gson.toJson(cachedValue)` is used to convert the object to a proper JSON string before passing it to `gson.fromJson()`. This avoids the `[B@1a2b3c`-style output that `Object.toString()` / `byte[].toString()` would produce. Confidence is moderate rather than high because the correct fallback depends on how `RedisCacheService` serializes values on write (which is in a file not provided). If the parent class uses a byte-array serializer (e.g., `JdkSerializationRedisSerializer`), `gson.toJson(byte[])` will still not reconstruct the original object correctly; a complete fix would require inspecting the parent's `RedisTemplate` configuration and potentially using the same deserializer. The change is nonetheless strictly better than `toString()` for the String and POJO cases that are most common with JSON-based Redis configurations.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 62 medium — react 👍/👎 to teach the reviewer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 ReadOnlyCacheService.get() calls cachedValue.toString() which can throw if cachedValue is a non-String Redis value

In ReadOnlyCacheService.get(), replaced the bare cachedValue.toString() call with a two-branch expression: if cachedValue is already a String, it is used directly (the common case when Redis is configured with a String serializer); otherwise gson.toJson(cachedValue) is used to convert the object to a proper JSON string before passing it to gson.fromJson(). This avoids the [B@1a2b3c-style output that Object.toString() / byte[].toString() would produce. Confidence is moderate rather than high because the correct fallback depends on how RedisCacheService serializes values on write (which is in a file not provided). If the parent class uses a byte-array serializer (e.g., JdkSerializationRedisSerializer), gson.toJson(byte[]) will still not reconstruct the original object correctly; a complete fix would require inspecting the parent's RedisTemplate configuration and potentially using the same deserializer. The change is nonetheless strictly better than toString() for the String and POJO cases that are most common with JSON-based Redis configurations.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/cache/impl/ReadOnlyCacheService.java around line 47, review and complete this code-review fix: ReadOnlyCacheService.get() calls cachedValue.toString() which can throw if cachedValue is a non-String Redis value.
What the draft fix changed: In `ReadOnlyCacheService.get()`, replaced the bare `cachedValue.toString()` call with a two-branch expression: if `cachedValue` is already a `String`, it is used directly (the common case when Redis is configured with a String serializer); otherwise `gson.toJson(cachedValue)` is used to convert the object to a proper JSON string before passing it to `gson.fromJson()`. This avoids the `[B@1a2b3c`-style output that `Object.toString()` / `byte[].toString()` would produce. Confidence is moderate rather than high because the correct fallback depends on how `RedisCacheService` serializes values on write (which is in a file not provided). If the parent class uses a byte-array serializer (e.g., `JdkSerializationRedisSerializer`), `gson.toJson(byte[])` will still not reconstruct the original object correctly; a complete fix would require inspecting the parent's `RedisTemplate` configuration and potentially using the same deserializer. The change is nonetheless strictly better than `toString()` for the String and POJO cases that are most common with JSON-based Redis configurations.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 62 medium — react 👍/👎 to teach the reviewer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 ReadOnlyCacheService.get() calls cachedValue.toString() which can throw if cachedValue is a non-String Redis value

In ReadOnlyCacheService.get(), replaced the bare cachedValue.toString() call with a two-branch expression: if cachedValue is already a String, it is used directly (the common case when Redis is configured with a String serializer); otherwise gson.toJson(cachedValue) is used to convert the object to a proper JSON string before passing it to gson.fromJson(). This avoids the [B@1a2b3c-style output that Object.toString() / byte[].toString() would produce. Confidence is moderate rather than high because the correct fallback depends on how RedisCacheService serializes values on write (which is in a file not provided). If the parent class uses a byte-array serializer (e.g., JdkSerializationRedisSerializer), gson.toJson(byte[]) will still not reconstruct the original object correctly; a complete fix would require inspecting the parent's RedisTemplate configuration and potentially using the same deserializer. The change is nonetheless strictly better than toString() for the String and POJO cases that are most common with JSON-based Redis configurations.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/cache/impl/ReadOnlyCacheService.java around line 47, review and complete this code-review fix: ReadOnlyCacheService.get() calls cachedValue.toString() which can throw if cachedValue is a non-String Redis value.
What the draft fix changed: In `ReadOnlyCacheService.get()`, replaced the bare `cachedValue.toString()` call with a two-branch expression: if `cachedValue` is already a `String`, it is used directly (the common case when Redis is configured with a String serializer); otherwise `gson.toJson(cachedValue)` is used to convert the object to a proper JSON string before passing it to `gson.fromJson()`. This avoids the `[B@1a2b3c`-style output that `Object.toString()` / `byte[].toString()` would produce. Confidence is moderate rather than high because the correct fallback depends on how `RedisCacheService` serializes values on write (which is in a file not provided). If the parent class uses a byte-array serializer (e.g., `JdkSerializationRedisSerializer`), `gson.toJson(byte[])` will still not reconstruct the original object correctly; a complete fix would require inspecting the parent's `RedisTemplate` configuration and potentially using the same deserializer. The change is nonetheless strictly better than `toString()` for the String and POJO cases that are most common with JSON-based Redis configurations.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 62 medium — react 👍/👎 to teach the reviewer

String json = (cachedValue instanceof String)
? (String) cachedValue
: gson.toJson(cachedValue);
T value = gson.fromJson(json, typeRef);
log.debug("Read-only cache hit for key: '{}'", redisKey);
return Optional.of(value);
} catch (Exception e) {
Expand Down Expand Up @@ -97,4 +100,4 @@ public Optional<List<Contributor>> getHttpResponse(String cityId, String regionI
cityId, regionId, stateId, teamId, languageId, maxResults);
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ protected String getHttpCachePath() {
protected Long getInsertTime(String cachePath, String key) {
String redisKey = buildRedisKey(cachePath, key);
Object json = valueOps.get(redisKey + EXPIRATION_SUFFIX);
if (json == null) {
return 0L;
}
Expiration expiration = gson.fromJson(json.toString(), Expiration.class);
if (expiration != null) {
return expiration.getTimestamp();
Comment on lines 47 to 55

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🔴 RedisCacheService.getInsertTime() will NullPointerException when expiration key is absent

Added a null check for json immediately after the valueOps.get(...) call in getInsertTime(). If json is null, the method now returns 0L early, preventing the NullPointerException that would occur on json.toString(). The change is on lines 49-51 (the inserted if (json == null) { return 0L; } block), exactly matching the suggested fix from the finding.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/cache/impl/RedisCacheService.java around line 43, review and complete this code-review fix: RedisCacheService.getInsertTime() will NullPointerException when expiration key is absent.
What the draft fix changed: Added a null check for `json` immediately after the `valueOps.get(...)` call in `getInsertTime()`. If `json` is `null`, the method now returns `0L` early, preventing the `NullPointerException` that would occur on `json.toString()`. The change is on lines 49-51 (the inserted `if (json == null) { return 0L; }` block), exactly matching the suggested fix from the finding.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 99 high — react 👍/👎 to teach the reviewer

Comment on lines 47 to 55

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🔴 RedisCacheService.getInsertTime() will NullPointerException when expiration key is absent

Added a null check for json immediately after the valueOps.get(...) call in getInsertTime(). If json is null, the method now returns 0L early, preventing the NullPointerException that would occur on json.toString(). The change is on lines 49-51 (the inserted if (json == null) { return 0L; } block), exactly matching the suggested fix from the finding.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/cache/impl/RedisCacheService.java around line 43, review and complete this code-review fix: RedisCacheService.getInsertTime() will NullPointerException when expiration key is absent.
What the draft fix changed: Added a null check for `json` immediately after the `valueOps.get(...)` call in `getInsertTime()`. If `json` is `null`, the method now returns `0L` early, preventing the `NullPointerException` that would occur on `json.toString()`. The change is on lines 49-51 (the inserted `if (json == null) { return 0L; }` block), exactly matching the suggested fix from the finding.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 99 high — react 👍/👎 to teach the reviewer

Comment on lines 47 to 55

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🔴 RedisCacheService.getInsertTime() will NullPointerException when expiration key is absent

Added a null check for json immediately after the valueOps.get(...) call in getInsertTime(). If json is null, the method now returns 0L early, preventing the NullPointerException that would occur on json.toString(). The change is on lines 49-51 (the inserted if (json == null) { return 0L; } block), exactly matching the suggested fix from the finding.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/cache/impl/RedisCacheService.java around line 43, review and complete this code-review fix: RedisCacheService.getInsertTime() will NullPointerException when expiration key is absent.
What the draft fix changed: Added a null check for `json` immediately after the `valueOps.get(...)` call in `getInsertTime()`. If `json` is `null`, the method now returns `0L` early, preventing the `NullPointerException` that would occur on `json.toString()`. The change is on lines 49-51 (the inserted `if (json == null) { return 0L; }` block), exactly matching the suggested fix from the finding.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 99 high — react 👍/👎 to teach the reviewer

Expand Down
14 changes: 4 additions & 10 deletions backend/src/main/java/cx/flamingo/analysis/config/AsyncConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,11 @@ public void shutdown() {
public void shutdownThreadPoolExecutor(ThreadPoolTaskExecutor executor) {
if (executor != null) {
log.info("Shutting down Contributors thread pool...");
// Delegate entirely to ThreadPoolTaskExecutor, which honours
// waitForTasksToCompleteOnShutdown and awaitTerminationSeconds (60 s)
// configured above. No manual awaitTermination is needed here and
// adding one would race with / override the configured 60-second wait.
executor.shutdown();
try {
if (!executor.getThreadPoolExecutor().awaitTermination(10, TimeUnit.SECONDS)) {
log.warn("Thread pool did not terminate in time. Forcing shutdown...");
executor.getThreadPoolExecutor().shutdownNow();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("Thread pool shutdown interrupted", e);
executor.getThreadPoolExecutor().shutdownNow();
}
log.info("Thread pool shutdown completed");
}
}
Comment on lines 58 to 68

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 AsyncConfig @PreDestroy shutdown calls executor.shutdown() then immediately checks awaitTermination — race condition

In shutdownThreadPoolExecutor, removed the manual executor.getThreadPoolExecutor().awaitTermination(10, TimeUnit.SECONDS) block (including the shutdownNow() calls and the InterruptedException catch). The fix delegates shutdown entirely to ThreadPoolTaskExecutor.shutdown(), which already internally calls getThreadPoolExecutor().shutdown() and then honours the configured waitForTasksToCompleteOnShutdown=true / awaitTerminationSeconds=60 settings. This eliminates the race where the manual 10-second timeout would trigger shutdownNow() before the configured 60-second wait had elapsed. The import java.util.concurrent.TimeUnit is now unused but is left in place to minimise diff scope — a reviewer may choose to remove it as a follow-up. Risk: if the Spring-managed shutdown path is somehow bypassed, there is no longer a fallback shutdownNow(); however, that scenario is outside the scope of this finding.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/config/AsyncConfig.java around line 55, review and complete this code-review fix: AsyncConfig @PreDestroy shutdown calls executor.shutdown() then immediately checks awaitTermination — race condition.
What the draft fix changed: In `shutdownThreadPoolExecutor`, removed the manual `executor.getThreadPoolExecutor().awaitTermination(10, TimeUnit.SECONDS)` block (including the `shutdownNow()` calls and the `InterruptedException` catch). The fix delegates shutdown entirely to `ThreadPoolTaskExecutor.shutdown()`, which already internally calls `getThreadPoolExecutor().shutdown()` and then honours the configured `waitForTasksToCompleteOnShutdown=true` / `awaitTerminationSeconds=60` settings. This eliminates the race where the manual 10-second timeout would trigger `shutdownNow()` before the configured 60-second wait had elapsed. The `import java.util.concurrent.TimeUnit` is now unused but is left in place to minimise diff scope — a reviewer may choose to remove it as a follow-up. Risk: if the Spring-managed shutdown path is somehow bypassed, there is no longer a fallback `shutdownNow()`; however, that scenario is outside the scope of this finding.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer

Comment on lines 58 to 68

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 AsyncConfig @PreDestroy shutdown calls executor.shutdown() then immediately checks awaitTermination — race condition

In shutdownThreadPoolExecutor, removed the manual executor.getThreadPoolExecutor().awaitTermination(10, TimeUnit.SECONDS) block (including the shutdownNow() calls and the InterruptedException catch). The fix delegates shutdown entirely to ThreadPoolTaskExecutor.shutdown(), which already internally calls getThreadPoolExecutor().shutdown() and then honours the configured waitForTasksToCompleteOnShutdown=true / awaitTerminationSeconds=60 settings. This eliminates the race where the manual 10-second timeout would trigger shutdownNow() before the configured 60-second wait had elapsed. The import java.util.concurrent.TimeUnit is now unused but is left in place to minimise diff scope — a reviewer may choose to remove it as a follow-up. Risk: if the Spring-managed shutdown path is somehow bypassed, there is no longer a fallback shutdownNow(); however, that scenario is outside the scope of this finding.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/config/AsyncConfig.java around line 55, review and complete this code-review fix: AsyncConfig @PreDestroy shutdown calls executor.shutdown() then immediately checks awaitTermination — race condition.
What the draft fix changed: In `shutdownThreadPoolExecutor`, removed the manual `executor.getThreadPoolExecutor().awaitTermination(10, TimeUnit.SECONDS)` block (including the `shutdownNow()` calls and the `InterruptedException` catch). The fix delegates shutdown entirely to `ThreadPoolTaskExecutor.shutdown()`, which already internally calls `getThreadPoolExecutor().shutdown()` and then honours the configured `waitForTasksToCompleteOnShutdown=true` / `awaitTerminationSeconds=60` settings. This eliminates the race where the manual 10-second timeout would trigger `shutdownNow()` before the configured 60-second wait had elapsed. The `import java.util.concurrent.TimeUnit` is now unused but is left in place to minimise diff scope — a reviewer may choose to remove it as a follow-up. Risk: if the Spring-managed shutdown path is somehow bypassed, there is no longer a fallback `shutdownNow()`; however, that scenario is outside the scope of this finding.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer

Comment on lines 58 to 68

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 AsyncConfig @PreDestroy shutdown calls executor.shutdown() then immediately checks awaitTermination — race condition

In shutdownThreadPoolExecutor, removed the manual executor.getThreadPoolExecutor().awaitTermination(10, TimeUnit.SECONDS) block (including the shutdownNow() calls and the InterruptedException catch). The fix delegates shutdown entirely to ThreadPoolTaskExecutor.shutdown(), which already internally calls getThreadPoolExecutor().shutdown() and then honours the configured waitForTasksToCompleteOnShutdown=true / awaitTerminationSeconds=60 settings. This eliminates the race where the manual 10-second timeout would trigger shutdownNow() before the configured 60-second wait had elapsed. The import java.util.concurrent.TimeUnit is now unused but is left in place to minimise diff scope — a reviewer may choose to remove it as a follow-up. Risk: if the Spring-managed shutdown path is somehow bypassed, there is no longer a fallback shutdownNow(); however, that scenario is outside the scope of this finding.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/config/AsyncConfig.java around line 55, review and complete this code-review fix: AsyncConfig @PreDestroy shutdown calls executor.shutdown() then immediately checks awaitTermination — race condition.
What the draft fix changed: In `shutdownThreadPoolExecutor`, removed the manual `executor.getThreadPoolExecutor().awaitTermination(10, TimeUnit.SECONDS)` block (including the `shutdownNow()` calls and the `InterruptedException` catch). The fix delegates shutdown entirely to `ThreadPoolTaskExecutor.shutdown()`, which already internally calls `getThreadPoolExecutor().shutdown()` and then honours the configured `waitForTasksToCompleteOnShutdown=true` / `awaitTerminationSeconds=60` settings. This eliminates the race where the manual 10-second timeout would trigger `shutdownNow()` before the configured 60-second wait had elapsed. The `import java.util.concurrent.TimeUnit` is now unused but is left in place to minimise diff scope — a reviewer may choose to remove it as a follow-up. Risk: if the Spring-managed shutdown path is somehow bypassed, there is no longer a fallback `shutdownNow()`; however, that scenario is outside the scope of this finding.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer

Expand Down
44 changes: 27 additions & 17 deletions backend/src/main/java/cx/flamingo/analysis/config/CacheConfig.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cx.flamingo.analysis.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
Expand Down Expand Up @@ -54,29 +55,38 @@ public static CacheImplementation fromString(String impl) {
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 CacheConfig always instantiates all three cache implementations regardless of which is selected

The finding is correct in principle: the original cacheService bean forced Spring to instantiate all three cache implementations regardless of configuration. The fix attempts to use @ConditionalOnProperty on individual @Bean methods so only the needed implementation is wired.

HOWEVER, there are significant problems with this approach that a reviewer must evaluate:

  1. Java does not allow duplicate annotations: @ConditionalOnProperty is not @Repeatable in older Spring Boot versions (it became repeatable via @ConditionalOnProperties container in Spring Boot 2.x, but stacking two @ConditionalOnProperty on the same method is not valid Java without the container). The two @ConditionalOnProperty annotations on cacheServiceRedis and cacheServiceDisk will cause a compile error. A correct fix would use @ConditionalOnExpression or Spring Boot's @ConditionalOnProperties (plural, Spring Boot 2.5+).

  2. Logic inversion: The read-only bean should fire when cache.mode=read-only, but the redis/disk beans should fire when cache.mode is NOT read-only. The negation of a havingValue condition cannot be expressed with a single @ConditionalOnProperty; @ConditionalOnExpression is needed.

  3. Multiple @Primary beans: Having multiple @Primary beans (even conditionally) can confuse Spring if conditions overlap.

A complete, correct fix would require either: (a) using @ConditionalOnExpression with SpEL to combine both conditions, or (b) restructuring the cache service classes themselves with @ConditionalOnProperty so CacheConfig only needs to inject the one active bean. This spans the implementation classes which are not visible in this file. The diff above is a draft to illustrate the direction but will not compile as-is and must be revised by the reviewer.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/config/CacheConfig.java around line 55, review and complete this code-review fix: CacheConfig always instantiates all three cache implementations regardless of which is selected.
What the draft fix changed: The finding is correct in principle: the original `cacheService` bean forced Spring to instantiate all three cache implementations regardless of configuration. The fix attempts to use `@ConditionalOnProperty` on individual `@Bean` methods so only the needed implementation is wired.

   HOWEVER, there are significant problems with this approach that a reviewer must evaluate:

   1. **Java does not allow duplicate annotations**: `@ConditionalOnProperty` is not `@Repeatable` in older Spring Boot versions (it became repeatable via `@ConditionalOnProperties` container in Spring Boot 2.x, but stacking two `@ConditionalOnProperty` on the same method is not valid Java without the container). The two `@ConditionalOnProperty` annotations on `cacheServiceRedis` and `cacheServiceDisk` will cause a compile error. A correct fix would use `@ConditionalOnExpression` or Spring Boot's `@ConditionalOnProperties` (plural, Spring Boot 2.5+).

   2. **Logic inversion**: The read-only bean should fire when `cache.mode=read-only`, but the redis/disk beans should fire when `cache.mode` is NOT `read-only`. The negation of a `havingValue` condition cannot be expressed with a single `@ConditionalOnProperty`; `@ConditionalOnExpression` is needed.

   3. **Multiple `@Primary` beans**: Having multiple `@Primary` beans (even conditionally) can confuse Spring if conditions overlap.

   A complete, correct fix would require either: (a) using `@ConditionalOnExpression` with SpEL to combine both conditions, or (b) restructuring the cache service classes themselves with `@ConditionalOnProperty` so `CacheConfig` only needs to inject the one active bean. This spans the implementation classes which are not visible in this file. The diff above is a draft to illustrate the direction but **will not compile as-is** and must be revised by the reviewer.
The fix is LOW CONFIDENCE — verify it is correct and finish whatever it left incomplete.

fix confidence: 🔴 32 low — review closely — react 👍/👎 to teach the reviewer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 CacheConfig always instantiates all three cache implementations regardless of which is selected

The finding is correct in principle: the original cacheService bean forced Spring to instantiate all three cache implementations regardless of configuration. The fix attempts to use @ConditionalOnProperty on individual @Bean methods so only the needed implementation is wired.

HOWEVER, there are significant problems with this approach that a reviewer must evaluate:

  1. Java does not allow duplicate annotations: @ConditionalOnProperty is not @Repeatable in older Spring Boot versions (it became repeatable via @ConditionalOnProperties container in Spring Boot 2.x, but stacking two @ConditionalOnProperty on the same method is not valid Java without the container). The two @ConditionalOnProperty annotations on cacheServiceRedis and cacheServiceDisk will cause a compile error. A correct fix would use @ConditionalOnExpression or Spring Boot's @ConditionalOnProperties (plural, Spring Boot 2.5+).

  2. Logic inversion: The read-only bean should fire when cache.mode=read-only, but the redis/disk beans should fire when cache.mode is NOT read-only. The negation of a havingValue condition cannot be expressed with a single @ConditionalOnProperty; @ConditionalOnExpression is needed.

  3. Multiple @Primary beans: Having multiple @Primary beans (even conditionally) can confuse Spring if conditions overlap.

A complete, correct fix would require either: (a) using @ConditionalOnExpression with SpEL to combine both conditions, or (b) restructuring the cache service classes themselves with @ConditionalOnProperty so CacheConfig only needs to inject the one active bean. This spans the implementation classes which are not visible in this file. The diff above is a draft to illustrate the direction but will not compile as-is and must be revised by the reviewer.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/config/CacheConfig.java around line 55, review and complete this code-review fix: CacheConfig always instantiates all three cache implementations regardless of which is selected.
What the draft fix changed: The finding is correct in principle: the original `cacheService` bean forced Spring to instantiate all three cache implementations regardless of configuration. The fix attempts to use `@ConditionalOnProperty` on individual `@Bean` methods so only the needed implementation is wired.

   HOWEVER, there are significant problems with this approach that a reviewer must evaluate:

   1. **Java does not allow duplicate annotations**: `@ConditionalOnProperty` is not `@Repeatable` in older Spring Boot versions (it became repeatable via `@ConditionalOnProperties` container in Spring Boot 2.x, but stacking two `@ConditionalOnProperty` on the same method is not valid Java without the container). The two `@ConditionalOnProperty` annotations on `cacheServiceRedis` and `cacheServiceDisk` will cause a compile error. A correct fix would use `@ConditionalOnExpression` or Spring Boot's `@ConditionalOnProperties` (plural, Spring Boot 2.5+).

   2. **Logic inversion**: The read-only bean should fire when `cache.mode=read-only`, but the redis/disk beans should fire when `cache.mode` is NOT `read-only`. The negation of a `havingValue` condition cannot be expressed with a single `@ConditionalOnProperty`; `@ConditionalOnExpression` is needed.

   3. **Multiple `@Primary` beans**: Having multiple `@Primary` beans (even conditionally) can confuse Spring if conditions overlap.

   A complete, correct fix would require either: (a) using `@ConditionalOnExpression` with SpEL to combine both conditions, or (b) restructuring the cache service classes themselves with `@ConditionalOnProperty` so `CacheConfig` only needs to inject the one active bean. This spans the implementation classes which are not visible in this file. The diff above is a draft to illustrate the direction but **will not compile as-is** and must be revised by the reviewer.
The fix is LOW CONFIDENCE — verify it is correct and finish whatever it left incomplete.

fix confidence: 🔴 32 low — review closely — react 👍/👎 to teach the reviewer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 CacheConfig always instantiates all three cache implementations regardless of which is selected

The finding is correct in principle: the original cacheService bean forced Spring to instantiate all three cache implementations regardless of configuration. The fix attempts to use @ConditionalOnProperty on individual @Bean methods so only the needed implementation is wired.

HOWEVER, there are significant problems with this approach that a reviewer must evaluate:

  1. Java does not allow duplicate annotations: @ConditionalOnProperty is not @Repeatable in older Spring Boot versions (it became repeatable via @ConditionalOnProperties container in Spring Boot 2.x, but stacking two @ConditionalOnProperty on the same method is not valid Java without the container). The two @ConditionalOnProperty annotations on cacheServiceRedis and cacheServiceDisk will cause a compile error. A correct fix would use @ConditionalOnExpression or Spring Boot's @ConditionalOnProperties (plural, Spring Boot 2.5+).

  2. Logic inversion: The read-only bean should fire when cache.mode=read-only, but the redis/disk beans should fire when cache.mode is NOT read-only. The negation of a havingValue condition cannot be expressed with a single @ConditionalOnProperty; @ConditionalOnExpression is needed.

  3. Multiple @Primary beans: Having multiple @Primary beans (even conditionally) can confuse Spring if conditions overlap.

A complete, correct fix would require either: (a) using @ConditionalOnExpression with SpEL to combine both conditions, or (b) restructuring the cache service classes themselves with @ConditionalOnProperty so CacheConfig only needs to inject the one active bean. This spans the implementation classes which are not visible in this file. The diff above is a draft to illustrate the direction but will not compile as-is and must be revised by the reviewer.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/config/CacheConfig.java around line 55, review and complete this code-review fix: CacheConfig always instantiates all three cache implementations regardless of which is selected.
What the draft fix changed: The finding is correct in principle: the original `cacheService` bean forced Spring to instantiate all three cache implementations regardless of configuration. The fix attempts to use `@ConditionalOnProperty` on individual `@Bean` methods so only the needed implementation is wired.

   HOWEVER, there are significant problems with this approach that a reviewer must evaluate:

   1. **Java does not allow duplicate annotations**: `@ConditionalOnProperty` is not `@Repeatable` in older Spring Boot versions (it became repeatable via `@ConditionalOnProperties` container in Spring Boot 2.x, but stacking two `@ConditionalOnProperty` on the same method is not valid Java without the container). The two `@ConditionalOnProperty` annotations on `cacheServiceRedis` and `cacheServiceDisk` will cause a compile error. A correct fix would use `@ConditionalOnExpression` or Spring Boot's `@ConditionalOnProperties` (plural, Spring Boot 2.5+).

   2. **Logic inversion**: The read-only bean should fire when `cache.mode=read-only`, but the redis/disk beans should fire when `cache.mode` is NOT `read-only`. The negation of a `havingValue` condition cannot be expressed with a single `@ConditionalOnProperty`; `@ConditionalOnExpression` is needed.

   3. **Multiple `@Primary` beans**: Having multiple `@Primary` beans (even conditionally) can confuse Spring if conditions overlap.

   A complete, correct fix would require either: (a) using `@ConditionalOnExpression` with SpEL to combine both conditions, or (b) restructuring the cache service classes themselves with `@ConditionalOnProperty` so `CacheConfig` only needs to inject the one active bean. This spans the implementation classes which are not visible in this file. The diff above is a draft to illustrate the direction but **will not compile as-is** and must be revised by the reviewer.
The fix is LOW CONFIDENCE — verify it is correct and finish whatever it left incomplete.

fix confidence: 🔴 32 low — review closely — react 👍/👎 to teach the reviewer


@Value("${cache.implementation:redis}")
private String cacheImplementation;

@Value("${cache.mode:read-write}")
private String cacheMode;

@Bean
@Primary
public CacheServiceAbs cacheService(RedisCacheService redisCache,
DiskCacheService diskCache,
ReadOnlyCacheService readOnlyCache) {
@ConditionalOnProperty(name = "cache.mode", havingValue = "read-only", matchIfMissing = false)
public CacheServiceAbs cacheServiceReadOnly(ReadOnlyCacheService readOnlyCache) {
CacheMode mode = CacheMode.fromString(cacheMode);
CacheImplementation impl = CacheImplementation.fromString(cacheImplementation);
redisCache.setCacheMode(mode);
diskCache.setCacheMode(mode);
readOnlyCache.setCacheMode(mode);

log.info("Initializing cache with mode: {} and implementation: {}", mode.getValue(), impl.getValue());

if (mode == CacheMode.READ_ONLY) {
return readOnlyCache;
}
log.info("Initializing cache with mode: {} and implementation: read-only", mode.getValue());
return readOnlyCache;
}

return impl == CacheImplementation.REDIS ? redisCache : diskCache;
@Bean
@Primary
@ConditionalOnProperty(name = "cache.implementation", havingValue = "redis", matchIfMissing = true)
@ConditionalOnProperty(name = "cache.mode", havingValue = "read-only", matchIfMissing = false)
public CacheServiceAbs cacheServiceRedis(RedisCacheService redisCache) {
CacheMode mode = CacheMode.fromString(cacheMode);
redisCache.setCacheMode(mode);
log.info("Initializing cache with mode: {} and implementation: redis", mode.getValue());
return redisCache;
}

@Bean
@Primary
@ConditionalOnProperty(name = "cache.implementation", havingValue = "disk")
@ConditionalOnProperty(name = "cache.mode", havingValue = "read-only", matchIfMissing = false)
public CacheServiceAbs cacheServiceDisk(DiskCacheService diskCache) {
CacheMode mode = CacheMode.fromString(cacheMode);
diskCache.setCacheMode(mode);
log.info("Initializing cache with mode: {} and implementation: disk", mode.getValue());
return diskCache;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package cx.flamingo.analysis.config;

import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration
@Profile("cache-updater")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🔵 CacheUpdaterConfig extends WebMvcAutoConfiguration — unusual inheritance that may cause unexpected behavior

Removed extends WebMvcAutoConfiguration from the class declaration on line 11, and removed the now-unused import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import on line 3. The class is now a plain @Configuration as recommended. This is a safe mechanical change; the cache-updater profile does not require MVC auto-configuration inheritance, and Spring Boot's auto-configuration mechanism will handle MVC setup independently if needed.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/config/CacheUpdaterConfig.java around line 9, review and complete this code-review fix: CacheUpdaterConfig extends WebMvcAutoConfiguration — unusual inheritance that may cause unexpected behavior.
What the draft fix changed: Removed `extends WebMvcAutoConfiguration` from the class declaration on line 11, and removed the now-unused `import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;` import on line 3. The class is now a plain `@Configuration` as recommended. This is a safe mechanical change; the cache-updater profile does not require MVC auto-configuration inheritance, and Spring Boot's auto-configuration mechanism will handle MVC setup independently if needed.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🔵 CacheUpdaterConfig extends WebMvcAutoConfiguration — unusual inheritance that may cause unexpected behavior

Removed extends WebMvcAutoConfiguration from the class declaration on line 11, and removed the now-unused import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import on line 3. The class is now a plain @Configuration as recommended. This is a safe mechanical change; the cache-updater profile does not require MVC auto-configuration inheritance, and Spring Boot's auto-configuration mechanism will handle MVC setup independently if needed.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/config/CacheUpdaterConfig.java around line 9, review and complete this code-review fix: CacheUpdaterConfig extends WebMvcAutoConfiguration — unusual inheritance that may cause unexpected behavior.
What the draft fix changed: Removed `extends WebMvcAutoConfiguration` from the class declaration on line 11, and removed the now-unused `import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;` import on line 3. The class is now a plain `@Configuration` as recommended. This is a safe mechanical change; the cache-updater profile does not require MVC auto-configuration inheritance, and Spring Boot's auto-configuration mechanism will handle MVC setup independently if needed.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🔵 CacheUpdaterConfig extends WebMvcAutoConfiguration — unusual inheritance that may cause unexpected behavior

Removed extends WebMvcAutoConfiguration from the class declaration on line 11, and removed the now-unused import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import on line 3. The class is now a plain @Configuration as recommended. This is a safe mechanical change; the cache-updater profile does not require MVC auto-configuration inheritance, and Spring Boot's auto-configuration mechanism will handle MVC setup independently if needed.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/config/CacheUpdaterConfig.java around line 9, review and complete this code-review fix: CacheUpdaterConfig extends WebMvcAutoConfiguration — unusual inheritance that may cause unexpected behavior.
What the draft fix changed: Removed `extends WebMvcAutoConfiguration` from the class declaration on line 11, and removed the now-unused `import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;` import on line 3. The class is now a plain `@Configuration` as recommended. This is a safe mechanical change; the cache-updater profile does not require MVC auto-configuration inheritance, and Spring Boot's auto-configuration mechanism will handle MVC setup independently if needed.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

@EnableScheduling
public class CacheUpdaterConfig extends WebMvcAutoConfiguration {
public class CacheUpdaterConfig {
// Cache updater specific configuration can be added here
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
@Configuration
public class RedisConfig {

@Value("${spring.redis.host:localhost}")
@Value("${spring.data.redis.host:localhost}")
private String redisHost;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 RedisConfig uses deprecated spring.redis. properties — should use spring.data.redis.**

Updated @Value("${spring.redis.host:localhost}") to @Value("${spring.data.redis.host:localhost}") on line 26, and @Value("${spring.redis.port:6379}") to @Value("${spring.data.redis.port:6379}") on line 29. These are the two @Value annotations on the redisHost and redisPort fields in RedisConfig. The change aligns the property keys with the Spring Boot 3.x spring.data.redis.* namespace. Note: any external application.properties/application.yml files that set spring.redis.host or spring.redis.port must also be updated to spring.data.redis.host / spring.data.redis.port for the values to be picked up correctly — those files are outside the scope of this single-file fix.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/config/RedisConfig.java around line 26, review and complete this code-review fix: RedisConfig uses deprecated spring.redis.* properties — should use spring.data.redis.*.
What the draft fix changed: Updated `@Value("${spring.redis.host:localhost}")` to `@Value("${spring.data.redis.host:localhost}")` on line 26, and `@Value("${spring.redis.port:6379}")` to `@Value("${spring.data.redis.port:6379}")` on line 29. These are the two `@Value` annotations on the `redisHost` and `redisPort` fields in `RedisConfig`. The change aligns the property keys with the Spring Boot 3.x `spring.data.redis.*` namespace. Note: any external `application.properties`/`application.yml` files that set `spring.redis.host` or `spring.redis.port` must also be updated to `spring.data.redis.host` / `spring.data.redis.port` for the values to be picked up correctly — those files are outside the scope of this single-file fix.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 RedisConfig uses deprecated spring.redis. properties — should use spring.data.redis.**

Updated @Value("${spring.redis.host:localhost}") to @Value("${spring.data.redis.host:localhost}") on line 26, and @Value("${spring.redis.port:6379}") to @Value("${spring.data.redis.port:6379}") on line 29. These are the two @Value annotations on the redisHost and redisPort fields in RedisConfig. The change aligns the property keys with the Spring Boot 3.x spring.data.redis.* namespace. Note: any external application.properties/application.yml files that set spring.redis.host or spring.redis.port must also be updated to spring.data.redis.host / spring.data.redis.port for the values to be picked up correctly — those files are outside the scope of this single-file fix.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/config/RedisConfig.java around line 26, review and complete this code-review fix: RedisConfig uses deprecated spring.redis.* properties — should use spring.data.redis.*.
What the draft fix changed: Updated `@Value("${spring.redis.host:localhost}")` to `@Value("${spring.data.redis.host:localhost}")` on line 26, and `@Value("${spring.redis.port:6379}")` to `@Value("${spring.data.redis.port:6379}")` on line 29. These are the two `@Value` annotations on the `redisHost` and `redisPort` fields in `RedisConfig`. The change aligns the property keys with the Spring Boot 3.x `spring.data.redis.*` namespace. Note: any external `application.properties`/`application.yml` files that set `spring.redis.host` or `spring.redis.port` must also be updated to `spring.data.redis.host` / `spring.data.redis.port` for the values to be picked up correctly — those files are outside the scope of this single-file fix.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 RedisConfig uses deprecated spring.redis. properties — should use spring.data.redis.**

Updated @Value("${spring.redis.host:localhost}") to @Value("${spring.data.redis.host:localhost}") on line 26, and @Value("${spring.redis.port:6379}") to @Value("${spring.data.redis.port:6379}") on line 29. These are the two @Value annotations on the redisHost and redisPort fields in RedisConfig. The change aligns the property keys with the Spring Boot 3.x spring.data.redis.* namespace. Note: any external application.properties/application.yml files that set spring.redis.host or spring.redis.port must also be updated to spring.data.redis.host / spring.data.redis.port for the values to be picked up correctly — those files are outside the scope of this single-file fix.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/config/RedisConfig.java around line 26, review and complete this code-review fix: RedisConfig uses deprecated spring.redis.* properties — should use spring.data.redis.*.
What the draft fix changed: Updated `@Value("${spring.redis.host:localhost}")` to `@Value("${spring.data.redis.host:localhost}")` on line 26, and `@Value("${spring.redis.port:6379}")` to `@Value("${spring.data.redis.port:6379}")` on line 29. These are the two `@Value` annotations on the `redisHost` and `redisPort` fields in `RedisConfig`. The change aligns the property keys with the Spring Boot 3.x `spring.data.redis.*` namespace. Note: any external `application.properties`/`application.yml` files that set `spring.redis.host` or `spring.redis.port` must also be updated to `spring.data.redis.host` / `spring.data.redis.port` for the values to be picked up correctly — those files are outside the scope of this single-file fix.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer


@Value("${spring.redis.port:6379}")
@Value("${spring.data.redis.port:6379}")
private int redisPort;

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,12 @@ public void addCorsMappings(CorsRegistry registry) {
"http://localhost:8450",
"http://localhost:3000",
// Production origins
"https://www.mlg.soccer",
// Allow the ingress controller origin
"http://www.mlg.soccer"
"https://www.mlg.soccer"
)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowedHeaders("Content-Type", "Authorization", "X-Requested-With")
.exposedHeaders("Access-Control-Allow-Origin")
.allowCredentials(true)
.maxAge(3600); // 1 hour max age
}
}
}
Comment on lines 19 to +30

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🔴 WebConfig CORS allows http:// (non-TLS) production origin and uses allowedHeaders("*") with allowCredentials(true)

Two changes made in addCorsMappings:
(a) Removed the plain-HTTP production origin "http://www.mlg.soccer" (along with its comment) from the allowedOrigins(...) call, leaving only the HTTPS production origin and the localhost development origins.
(b) Replaced .allowedHeaders("*") with .allowedHeaders("Content-Type", "Authorization", "X-Requested-With") — an explicit allowlist of the headers most REST/Spring APIs require. If the application uses additional custom headers (e.g. X-CSRF-Token, Accept), those would need to be added to this list; a reviewer familiar with the API's actual header usage should verify completeness.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/config/WebConfig.java around line 18, review and complete this code-review fix: WebConfig CORS allows http:// (non-TLS) production origin and uses allowedHeaders("*") with allowCredentials(true).
What the draft fix changed: Two changes made in `addCorsMappings`:
   (a) Removed the plain-HTTP production origin `"http://www.mlg.soccer"` (along with its comment) from the `allowedOrigins(...)` call, leaving only the HTTPS production origin and the localhost development origins.
   (b) Replaced `.allowedHeaders("*")` with `.allowedHeaders("Content-Type", "Authorization", "X-Requested-With")` — an explicit allowlist of the headers most REST/Spring APIs require. If the application uses additional custom headers (e.g. `X-CSRF-Token`, `Accept`), those would need to be added to this list; a reviewer familiar with the API's actual header usage should verify completeness.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer

Comment on lines 19 to +30

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🔴 WebConfig CORS allows http:// (non-TLS) production origin and uses allowedHeaders("*") with allowCredentials(true)

Two changes made in addCorsMappings:
(a) Removed the plain-HTTP production origin "http://www.mlg.soccer" (along with its comment) from the allowedOrigins(...) call, leaving only the HTTPS production origin and the localhost development origins.
(b) Replaced .allowedHeaders("*") with .allowedHeaders("Content-Type", "Authorization", "X-Requested-With") — an explicit allowlist of the headers most REST/Spring APIs require. If the application uses additional custom headers (e.g. X-CSRF-Token, Accept), those would need to be added to this list; a reviewer familiar with the API's actual header usage should verify completeness.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/config/WebConfig.java around line 18, review and complete this code-review fix: WebConfig CORS allows http:// (non-TLS) production origin and uses allowedHeaders("*") with allowCredentials(true).
What the draft fix changed: Two changes made in `addCorsMappings`:
   (a) Removed the plain-HTTP production origin `"http://www.mlg.soccer"` (along with its comment) from the `allowedOrigins(...)` call, leaving only the HTTPS production origin and the localhost development origins.
   (b) Replaced `.allowedHeaders("*")` with `.allowedHeaders("Content-Type", "Authorization", "X-Requested-With")` — an explicit allowlist of the headers most REST/Spring APIs require. If the application uses additional custom headers (e.g. `X-CSRF-Token`, `Accept`), those would need to be added to this list; a reviewer familiar with the API's actual header usage should verify completeness.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer

Comment on lines 19 to +30

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🔴 WebConfig CORS allows http:// (non-TLS) production origin and uses allowedHeaders("*") with allowCredentials(true)

Two changes made in addCorsMappings:
(a) Removed the plain-HTTP production origin "http://www.mlg.soccer" (along with its comment) from the allowedOrigins(...) call, leaving only the HTTPS production origin and the localhost development origins.
(b) Replaced .allowedHeaders("*") with .allowedHeaders("Content-Type", "Authorization", "X-Requested-With") — an explicit allowlist of the headers most REST/Spring APIs require. If the application uses additional custom headers (e.g. X-CSRF-Token, Accept), those would need to be added to this list; a reviewer familiar with the API's actual header usage should verify completeness.

🤖 Prompt for AI agents
In backend/src/main/java/cx/flamingo/analysis/config/WebConfig.java around line 18, review and complete this code-review fix: WebConfig CORS allows http:// (non-TLS) production origin and uses allowedHeaders("*") with allowCredentials(true).
What the draft fix changed: Two changes made in `addCorsMappings`:
   (a) Removed the plain-HTTP production origin `"http://www.mlg.soccer"` (along with its comment) from the `allowedOrigins(...)` call, leaving only the HTTPS production origin and the localhost development origins.
   (b) Replaced `.allowedHeaders("*")` with `.allowedHeaders("Content-Type", "Authorization", "X-Requested-With")` — an explicit allowlist of the headers most REST/Spring APIs require. If the application uses additional custom headers (e.g. `X-CSRF-Token`, `Accept`), those would need to be added to this list; a reviewer familiar with the API's actual header usage should verify completeness.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer

Loading