Skip to content

feat: implement redis caching layer for AI summary endpoint - #69

Merged
AnkanMisra merged 14 commits into
AnkanMisra:mainfrom
roshankumar0036singh:feat/redis-caching
Jan 12, 2026
Merged

AnkanMisra merged 14 commits into
AnkanMisra:mainfrom
roshankumar0036singh:feat/redis-caching

Conversation

@roshankumar0036singh

@roshankumar0036singh roshankumar0036singh commented Jan 10, 2026 •

Copy link
Copy Markdown
Contributor

Feature: Intelligent Redis Caching Layer for AI Summarization

Description

closes #31
This PR implements a robust, secure, and efficient caching layer for the /api/ai/summarize endpoint using Redis. This feature is designed to significantly reduce API costs for the OpenRouter service and improve response latency for repeat requests.

Key Features

  • Cache-Aside Pattern: Middleware checks Redis before processing requests.
  • Security-First Architecture:
    • Cache hits are strictly verified against the X-402-Signature to ensure payment rules are enforced even for cached content.
    • Invalid or missing signatures result in 403 or 402 responses, preventing cache leakage to unpaid users.
  • Content-Addressable Keys: Uses SHA256 hashes of the request text as cache keys to ensure collision resistance.
  • Async Storage: Cache writes are performed asynchronously to minimalize latency impact on cache misses.
  • DoS Protection: Enforces a strict 10MB limit on request body size within the middleware.

Configuration

New environment variables added to .env.example:

  • REDIS_URL: Connection string (default variable).
  • REDIS_PASSWORD: Optional password.
  • REDIS_DB: Database index (default 0).
  • CACHE_ENABLED: Set to true to enable caching.
  • CACHE_TTL_SECONDS: Time-to-live for cached items (default 3600s).

Verification

  • Unit Tests: gateway/cache_test.go verifies key generation, determinism, and format.
  • Integration Tests: gateway/cache_integration_test.go verifies the full flow including cache hits, misses, and security enforcement (signature validation).
  • Manual Testing: Verified locally using Docker-hosted Redis.

Checklist

  • Dependency go-redis/v9 added.
  • Middleware implemented and wired.
  • Security verification integrated.
  • Tests passed.

Summary by CodeRabbit

  • New Features

    • Redis-backed caching for AI summarize requests with enable/disable, configurable TTL, and content-addressable keys; cache responses require payment verification.
  • Documentation

    • Added "Caching Configuration" section to README describing behavior and setup (duplicate block present).
  • Tests

    • Added integration and unit tests covering full cache flow and key generation.
  • Chores

    • Added Redis service/volume to docker-compose and new caching env vars to the example env file.

✏️ Tip: You can customize this high-level summary in your review settings.

Greptile Overview

Greptile Summary

This PR implements a Redis-backed caching layer for the /api/ai/summarize endpoint to reduce OpenRouter API costs and improve response latency. The implementation follows a cache-aside pattern with security-first design - payment verification is enforced even for cached responses.

Key Implementation Details

Cache Flow:

  • Cache keys are content-addressable using SHA256(version + text + model)
  • Cache middleware checks Redis before forwarding to the handler
  • On cache hit: verify payment → serve cached response → generate receipt
  • On cache miss: verify payment → call AI → serve response → async cache storage
  • Cache writes are asynchronous with 5-second timeout to minimize latency impact

Security Architecture:
The implementation correctly enforces payment verification on both cache hits and misses. The X-402-Signature is validated before serving any cached content, preventing unauthorized access to cached responses.

Issues Found

Critical Issues

  1. Goroutine leak on server shutdown (cache.go:176): Async cache storage uses context.Background() instead of a shutdown-aware context. When the server shuts down, pending goroutines may try to write to an already-closed Redis client, causing errors or panics.

  2. Resource leak in integration test (cache_integration_test.go:18): Redis test client is never closed, causing connection leaks during test runs.

Style Issues

  1. Inefficient nil body handling (cache.go:59): The code allows nil request bodies to reach JSON unmarshaling instead of returning early with a clear error message.

Positive Aspects

  • Clean separation of concerns with dedicated redis.go for connection management
  • Comprehensive integration tests covering security scenarios (invalid signature, missing signature)
  • Graceful degradation when Redis is unavailable (caching disabled, requests still work)
  • Docker compose properly configured with Redis service and environment variables
  • Cache key versioning (v1) allows future cache invalidation if parameters change

Confidence Score: 3/5

  • This PR has critical goroutine management issues that could cause instability during server shutdown
  • The implementation is mostly solid with good security practices, but contains a critical goroutine leak issue that affects production stability. The async cache storage goroutines can outlive the server shutdown sequence and attempt to write to a closed Redis client. Additionally, there's a minor resource leak in tests. These issues must be fixed before merging.
  • Pay close attention to gateway/cache.go (lines 176-181) - the async cache storage needs proper shutdown coordination

Important Files Changed

File Analysis

Filename Score Overview
gateway/cache.go 3/5 Implements Redis caching middleware with payment verification. Contains goroutine leak issue on shutdown.
gateway/cache_integration_test.go 4/5 Integration tests for cache flow. Contains resource leak (Redis client not closed).
gateway/redis.go 5/5 Redis connection initialization with graceful fallback. Implementation is clean and handles errors well.
gateway/main.go 4/5 Modified to integrate cache middleware. Changes are minimal and mostly correct.
docker-compose.yml 5/5 Adds Redis service and configuration. Properly configured with dependencies.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Gateway
    participant CacheMiddleware
    participant Redis
    participant Verifier
    participant OpenRouter
    
    Note over Client,OpenRouter: Cache MISS Flow (First Request)
    Client->>Gateway: POST /api/ai/summarize<br/>(signature, nonce, text)
    Gateway->>CacheMiddleware: Process request
    CacheMiddleware->>CacheMiddleware: Read body & generate cache key<br/>SHA256(v1:text:model)
    CacheMiddleware->>Redis: GET cache key
    Redis-->>CacheMiddleware: Cache MISS (not found)
    CacheMiddleware->>Gateway: Continue to handler
    Gateway->>Verifier: POST /verify (signature, context)
    Verifier-->>Gateway: {is_valid: true, recovered_address}
    Gateway->>OpenRouter: POST /chat/completions (text)
    OpenRouter-->>Gateway: AI summary result
    Gateway->>Gateway: Generate receipt<br/>(hash request & response)
    Gateway->>Client: 200 + receipt + result
    Note over CacheMiddleware,Redis: Async (background goroutine)
    CacheMiddleware->>Redis: SET cache key = result (TTL: 1h)
    
    Note over Client,OpenRouter: Cache HIT Flow (Subsequent Request)
    Client->>Gateway: POST /api/ai/summarize<br/>(signature, nonce, same text)
    Gateway->>CacheMiddleware: Process request
    CacheMiddleware->>CacheMiddleware: Read body & generate cache key
    CacheMiddleware->>Redis: GET cache key
    Redis-->>CacheMiddleware: Cache HIT (cached result)
    CacheMiddleware->>Verifier: POST /verify (STILL VERIFY!)
    Verifier-->>CacheMiddleware: {is_valid: true, recovered_address}
    CacheMiddleware->>CacheMiddleware: Generate receipt with cached result
    CacheMiddleware->>Client: 200 + receipt + cached result
    Note over CacheMiddleware: Handler never runs - request aborted
    
    Note over Client,OpenRouter: Cache HIT with Invalid Signature
    Client->>Gateway: POST /api/ai/summarize<br/>(INVALID signature, same text)
    Gateway->>CacheMiddleware: Process request
    CacheMiddleware->>Redis: GET cache key
    Redis-->>CacheMiddleware: Cache HIT
    CacheMiddleware->>Verifier: POST /verify
    Verifier-->>CacheMiddleware: {is_valid: FALSE}
    CacheMiddleware->>Client: 403 Forbidden
    Note over CacheMiddleware: Cached response NOT served!
Loading

@coderabbitai

coderabbitai Bot commented Jan 10, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds a Redis-backed cache-aside layer for /api/ai/summarize: env and docker-compose Redis service, Redis client init with health check and graceful fallback, Gin cache middleware + helpers, handler adjustments to enforce payment verification and receipts, unit and integration tests, and README updates.

Changes

Cohort / File(s) Summary
Infrastructure & Config
\.env.example, docker-compose.yml
Add Redis env vars (REDIS_URL, REDIS_PASSWORD, REDIS_DB, CACHE_ENABLED, CACHE_TTL_SECONDS); add redis:7-alpine service with redis-data volume; set gateway env REDIS_URL/CACHE_ENABLED and depends_on: redis.
Go Module
**gateway/go.mod**
Add github.com/redis/go-redis/v9 dependency (plus a few indirect deps).
Cache Layer & Helpers
**gateway/cache.go**, **gateway/redis.go**
New CacheMiddleware Gin middleware and CachedResponse type; SHA-256 content-addressable cache keys; async get/set helpers; 10MB request body guard; initRedis respects CACHE_ENABLED, parses REDIS_URL, pings Redis, logs non-fatal warnings and disables caching on failure.
Gateway Handler Changes
**gateway/main.go**
Initialize Redis on startup; conditionally wrap /api/ai/summarize with cache middleware when enabled; add internal verifyPayment and generateAndSendReceipt helpers; adjust body/read/timeout/error handling and close Redis on shutdown.
Tests
**gateway/cache_test.go**, **gateway/cache_integration_test.go**
Unit tests for cache key determinism/format; integration test covering Redis miss→store→hit flow with verifier and AI mocks and signature/nonce/error cases (skips if Redis unavailable).
Docs
**README.md**
Add Redis-backed caching documentation describing cache-aside behavior, key design, TTL, env config (note: duplicated block present in diff).
Formatting Only
**gateway/receipt.go**, **gateway/receipt_test.go**
Whitespace/formatting adjustments only; no behavioral changes.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Gateway
    participant Redis
    participant Verifier
    participant AI

    Client->>Gateway: POST /api/ai/summarize (body, X-402-Signature, X-402-Nonce)
    Gateway->>Gateway: Read body (<=10MB), compute SHA256 cache key
    Gateway->>Redis: GET ai:summary:<hash>

    alt Cache Hit
        Redis-->>Gateway: CachedResponse
        Gateway->>Verifier: Verify signature/nonce
        Verifier-->>Gateway: Valid
        Gateway-->>Client: 200 cached summary + receipt
    else Cache Miss
        Redis-->>Gateway: nil
        Gateway->>Verifier: Verify signature/nonce
        Verifier-->>Gateway: Valid
        Gateway->>AI: Call summarize API
        AI-->>Gateway: AI result
        Gateway->>Redis: SET ai:summary:<hash> (TTL) [async]
        Gateway-->>Client: 200 fresh summary + receipt
    else Verification Failure
        Verifier-->>Gateway: Invalid/Timeout
        Gateway-->>Client: 403/504/402
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nibble keys beneath moon's glow,
SHA-256 hops where summaries grow,
Redis burrow keeps results warm,
I check your coin before the storm,
Fetch, store, repeat — a cached hop of charm.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: implementing a Redis caching layer for the AI summary endpoint, which is the primary focus of this comprehensive PR.
Linked Issues check ✅ Passed The PR implementation fulfills all core coding requirements from issue #31: Redis initialization with health checks, cache-aside pattern, SHA256-based deterministic keys, TTL configuration, payment verification enforcement, graceful degradation, comprehensive tests, and proper env var support.
Out of Scope Changes check ✅ Passed All code changes are directly related to the caching feature scope. Formatting-only changes to receipt.go and receipt_test.go are incidental cleanup. No unrelated features or requirements are introduced.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment thread gateway/main.go Outdated
Comment thread gateway/cache.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docker-compose.yml (1)

4-15: docker-compose won’t actually use Redis cache unless gateway gets CACHE_ENABLED/REDIS_URL (and Redis port exposure is broader than needed).

Right now the gateway container won’t connect to the redis service unless you set CACHE_ENABLED=true and REDIS_URL=redis:6379 (its current default localhost:6379 resolves to the container itself). Also consider binding Redis to localhost (or not publishing it) to reduce accidental exposure.

Proposed diff
 services:
   gateway:
     build: ./gateway
     ports:
       - "3000:3000"
     environment:
       - PORT=3000
       - VERIFIER_URL=http://verifier:3002
+      - CACHE_ENABLED=true
+      - REDIS_URL=redis:6379
+      - CACHE_TTL_SECONDS=3600
     depends_on:
       - verifier
+      - redis
     networks:
       - microai-net

   redis:
     image: redis:7-alpine
     ports:
-      - "6379:6379"
+      - "127.0.0.1:6379:6379"
     networks:
       - microai-net

Also applies to: 23-29

gateway/go.mod (1)

13-46: Run go mod tidy to move github.com/redis/go-redis/v9 to the direct require block.

github.com/redis/go-redis/v9 is directly imported in gateway/redis.go and gateway/cache_integration_test.go, so it should not be marked // indirect. Running go mod tidy will correct the module graph. The version v9.17.2 is current and has no known security advisories.

🤖 Fix all issues with AI agents
In @.env.example:
- Around line 61-69: The .env.example has an inline comment inside the value for
CACHE_TTL_SECONDS and an incorrect Redis host for containerized runs; remove the
inline comment from the value and place any explanatory text on its own comment
line above (adjust the CACHE_TTL_SECONDS entry), and change
REDIS_URL=localhost:6379 to REDIS_URL=redis:6379 so the gateway container
resolves Redis via docker-compose; update the two variables CACHE_TTL_SECONDS
and REDIS_URL accordingly.

In @gateway/main.go:
- Around line 187-189: The code launches startReceiptCleanup twice, causing
duplicate cleanup goroutines and contention on receiptStoreMu; remove the
duplicate goroutine start so only a single call to
startReceiptCleanup(cleanupCtx) remains and keep the single "Receipt cleanup
goroutine started" log; search for the startReceiptCleanup invocation near where
receiptStoreMu is used to ensure no other duplicate launches exist.

In @gateway/redis.go:
- Around line 12-34: The initRedis function currently overwrites or abandons
redis.Client instances without closing them; before assigning a new client
(redisClient = redis.NewClient(...)) and when a Ping fails, call
redisClient.Close() on the old or failing client to avoid leaking connections;
specifically, check if redisClient != nil and close it before creating a new
client, and if Ping returns an error call redisClient.Close() before setting
redisClient = nil so the client is properly disposed.

In @README.md:
- Around line 523-545: Update the README caching docs to note that in
docker-compose the REDIS_URL host should be "redis:6379" (not "localhost:6379")
and adjust the prose to clarify that cache hits still run payment signature
verification (so the main latency win is avoiding the OpenRouter provider call
rather than skipping verification); update the example .env entry and the
descriptive lines under "Cache-Aside Pattern" and "Secure by Design"
accordingly.
🧹 Nitpick comments (7)
gateway/cache_integration_test.go (1)

29-45: Check decode/request errors in test helpers to avoid silent false positives.

Right now the verifier mock ignores JSON decode errors (Line 32) and makeRequest drops marshal/request errors (Lines 90-91). Even small if err != nil { t.Fatal(...) } checks will make failures clearer.

Also applies to: 87-99

gateway/redis.go (1)

14-23: Support redis:// URLs via redis.ParseURL(), or rename REDIS_URL to clarify host:port format.

The variable name REDIS_URL suggests users may supply redis://localhost:6379, which will fail because Options.Addr expects only host:port. Either:

  • Use redis.ParseURL() to accept full redis URLs with optional authentication and database number
  • Rename the environment variable to REDIS_ADDR to clarify the expected format
  • Document that only host:port is supported
gateway/cache.go (3)

102-113: Consider removing unused context assignments before abort.

Lines 103-104 set payment_verification and payment_context in the Gin context, but since c.Abort() is called immediately after, these values are never consumed by downstream handlers. This is dead code that could be removed for clarity.

♻️ Suggested cleanup
-			// Payment Verified. Store verification for downstream if needed (though we abort)
-			c.Set("payment_verification", verifyResp)
-			c.Set("payment_context", paymentCtx)
-
 			// Generate Receipt and Respond

197-202: The ctx field in cachedWriter is unused.

The ctx field is assigned at line 124 but never referenced within the struct's methods or elsewhere. Consider removing it to reduce confusion.

♻️ Suggested cleanup
 type cachedWriter struct {
 	gin.ResponseWriter
 	body     *bytes.Buffer
 	cacheKey string
-	ctx      context.Context
 }

And remove assignment at line 124:

 		writer := &cachedWriter{
 			ResponseWriter: c.Writer,
 			body:           &bytes.Buffer{},
 			cacheKey:       cacheKey,
-			ctx:            c.Request.Context(), // Use request context (has timeouts)
 		}

186-186: Handle the JSON marshal error instead of ignoring it.

While CachedResponse is simple and unlikely to cause marshal errors, silently ignoring the error is not a best practice. A failed marshal would result in writing invalid/empty data to Redis.

♻️ Suggested fix
-	jsonData, _ := json.Marshal(cached)
+	jsonData, err := json.Marshal(cached)
+	if err != nil {
+		log.Printf("Failed to marshal cache data: %v", err)
+		return
+	}
gateway/main.go (2)

250-253: Remove development notes from production code.

These inline comments appear to be thinking notes from development rather than documentation. They reduce code clarity.

♻️ Suggested cleanup
 		// Verify
 		verifyResp, paymentCtx, err = verifyPayment(c.Request.Context(), signature, nonce)
 		if err != nil {
-			// verifyPayment returns appropriate error/codes? No, let's make it return error
-			// and handle it. But wait, verifyPayment needs to make HTTP call.
-			// Let's look at the extracted function below.
 			log.Printf("Verification error: %v", err)

297-303: Remove development notes from production code.

Similar to the earlier comment, these notes are leftover from development.

♻️ Suggested cleanup
 	// 4. Generate & Send Receipt
 	if err := generateAndSendReceipt(c, *paymentCtx, verifyResp.RecoveredAddress, requestBody, summary); err != nil {
 		log.Printf("Failed to generate receipt: %v", err)
-		// generateAndSendReceipt sends error response if it fails?
-		// No, it returns error, we might have already written status if we aren't careful.
-		// Let's implement generateAndSendReceipt to handle sending response.
 		return
 	}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7afc3e and aa8ef58.

⛔ Files ignored due to path filters (1)
  • gateway/go.sum is excluded by !**/*.sum
📒 Files selected for processing (9)
  • .env.example
  • README.md
  • docker-compose.yml
  • gateway/cache.go
  • gateway/cache_integration_test.go
  • gateway/cache_test.go
  • gateway/go.mod
  • gateway/main.go
  • gateway/redis.go
🧰 Additional context used
🧬 Code graph analysis (1)
gateway/cache_integration_test.go (3)
gateway/main.go (2)
  • VerifyRequest (40-43)
  • VerifyResponse (45-49)
gateway/middleware.go (1)
  • RequestTimeoutMiddleware (114-186)
gateway/cache.go (1)
  • CacheMiddleware (25-149)
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 61-61: [ExtraBlankLine] Extra blank line detected

(ExtraBlankLine)


[warning] 64-64: [UnorderedKey] The REDIS_PASSWORD key should go before the REDIS_URL key

(UnorderedKey)


[warning] 65-65: [UnorderedKey] The REDIS_DB key should go before the REDIS_PASSWORD key

(UnorderedKey)


[warning] 69-69: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)

🔇 Additional comments (9)
gateway/cache_test.go (1)

10-62: Cache key tests look solid and match the stated SHA-256 keying scheme.

Optional nit: TestCacheKeyCollisionWait doesn’t “wait”; consider renaming for clarity.

gateway/redis.go (1)

14-23: No action needed—getEnvAsInt exists and is accessible.

The function is defined in gateway/main.go (line 632) within the same gateway package, making it properly accessible to gateway/redis.go. No changes required.

Likely an incorrect or invalid review comment.

gateway/cache.go (4)

19-23: LGTM!

The CachedResponse struct is well-defined with appropriate JSON tags for Redis serialization.


151-154: LGTM!

Cache key generation uses SHA256 for content-addressable hashing with a clear namespace prefix. This is deterministic and provides good collision resistance.


156-172: LGTM!

The function properly handles nil Redis client and follows standard Redis retrieval patterns with appropriate error propagation.


204-212: LGTM!

The cachedWriter correctly implements a tee-writer pattern, capturing response data while passing through to the underlying writer. Both Write and WriteString methods are properly implemented.

gateway/main.go (3)

168-172: LGTM!

Clean conditional registration of the cache middleware based on configuration. The pattern correctly applies middleware only when caching is enabled.


306-357: LGTM!

The verifyPayment function is well-structured with proper timeout handling, error wrapping, and separation of concerns. Using a derived context with dedicated timeout is a good practice.


359-386: LGTM!

The generateAndSendReceipt function provides clean separation of concerns for receipt generation, storage, encoding, and response handling. Error cases are properly managed.

Comment thread .env.example Outdated
Comment thread gateway/cache_integration_test.go
Comment thread gateway/main.go
Comment thread gateway/redis.go
Comment thread README.md
@roshankumar0036singh
roshankumar0036singh force-pushed the feat/redis-caching branch 2 times, most recently from a730af3 to 77956ba Compare January 10, 2026 17:41
@roshankumar0036singh

Copy link
Copy Markdown
Contributor Author

@AnkanMisra review it

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In @gateway/cache_integration_test.go:
- Around line 164-174: The test currently ignores json.Unmarshal errors and may
compare nil values; update the unmarshal calls for resp1 and resp2 to capture
and assert their errors (e.g., err := json.Unmarshal(w1.Body.Bytes(), &resp1);
if err != nil { t.Fatalf("failed to unmarshal response1: %v", err) }) and
similarly for w2, then only read resp1["result"] and resp2["result"] after
successful unmarshalling (optionally assert the keys exist/are strings before
comparing) so invalid JSON fails the test immediately.
- Around line 84-95: The helper makeRequest is ignoring errors from json.Marshal
and http.NewRequest which can mask real failures; update makeRequest (and the
similar helper around lines 153-159) to handle and return errors instead of
discarding them: check the error returned by json.Marshal and http.NewRequest,
propagate or fail the test with t.Fatalf/t.Helper (or return (
*httptest.ResponseRecorder, error ) from the helper), and ensure callers handle
the error so nil-pointer/empty-body behavior is avoided; reference the
makeRequest function and the other helper to locate where to add error checks
and proper error propagation.
- Around line 28-44: The mock verifier handler created by httptest.NewServer
ignores json.Decode/json.Encode errors, which can hide test failures; update the
handler inside the verifier (the http.HandlerFunc used with httptest.NewServer)
to check the error returned by json.NewDecoder(r.Body).Decode(&req) and return
an HTTP 400/500 with a JSON error body or call t.Fatalf/t.Error when decode
fails, and likewise check json.NewEncoder(w).Encode(resp) and handle/return the
encoding error (write HTTP 500 and a clear error message). Ensure you reference
VerifyRequest and VerifyResponse in the handler error paths so failures are
surfaced instead of silently ignored.

In @gateway/main.go:
- Around line 224-245: In handleSummarize: don’t call io.ReadAll(c.Request.Body)
before validating the X-402 headers; first read the X-402-Signature and
X-402-Nonce via c.GetHeader and return the 402 response (with
createPaymentContext) if missing, then when you must read the body enforce a
10MB limit (e.g. replace direct io.ReadAll with a reader limited to 10*1024*1024
bytes or use http.MaxBytesReader) and restore c.Request.Body only after a
successful limited read so you preserve DoS protection and avoid wasteful reads
on 402 paths.
🧹 Nitpick comments (9)
gateway/receipt.go (1)

77-86: Consider increasing receipt ID entropy.

The current implementation uses 6 random bytes (48 bits), which provides ~16.7 million IDs before 50% collision probability (birthday paradox). For a high-throughput payment gateway, consider using 16 bytes (128 bits) to match UUID standards and eliminate collision concerns.

♻️ Increase entropy to 128 bits
 func generateReceiptID() (string, error) {
-	// Generate 6 random bytes (12 hex characters)
-	bytes := make([]byte, 6)
+	// Generate 16 random bytes (32 hex characters) for UUID-level entropy
+	bytes := make([]byte, 16)
 	if _, err := rand.Read(bytes); err != nil {
 		return "", fmt.Errorf("failed to generate random receipt ID: %w", err)
 	}
 	return "rcpt_" + hex.EncodeToString(bytes), nil
 }
docker-compose.yml (1)

23-28: Consider production hardening for the Redis service.

The basic Redis configuration works for development. For production deployments, consider adding:

  1. Data persistence: Mount a volume to prevent cache loss on restarts
  2. Authentication: Set REDIS_PASSWORD and pass it via command: redis-server --requirepass ${REDIS_PASSWORD}
  3. Health check: Add a healthcheck directive for better orchestration
♻️ Production-ready Redis configuration
   redis:
     image: redis:7-alpine
+    command: redis-server --requirepass ${REDIS_PASSWORD:-}
     ports:
       - "6379:6379"
+    volumes:
+      - redis-data:/data
+    healthcheck:
+      test: ["CMD", "redis-cli", "ping"]
+      interval: 10s
+      timeout: 3s
+      retries: 3
     networks:
       - microai-net

Add volume declaration:

 networks:
   microai-net:
     driver: bridge
+
+volumes:
+  redis-data:
gateway/cache_integration_test.go (1)

112-114: Time-based assertions may flake under load/CI.

duration1 < 100ms can be brittle (scheduler jitter). Consider relaxing the threshold (or asserting aiCalls only, since that’s the real signal).

Also applies to: 141-144

gateway/cache_test.go (1)

44-51: Rename the “collision” test to avoid implying real collision resistance validation.

E.g. TestCacheKey_DifferentInputsDifferentKeys (since this isn’t a collision test in the cryptographic sense).

gateway/main.go (2)

168-172: Avoid connecting to Redis when cache is disabled.

initRedis() runs unconditionally (Line 190-192) even when getCacheEnabled() is false (Line 168-172). Consider guarding initRedis() behind getCacheEnabled() to prevent unnecessary startup dependency/cost.

Also applies to: 190-192


213-218: Avoid panics on c.Get(...) type assertions.

v.(*VerifyResponse) / p.(*PaymentContext) will panic if the key is set unexpectedly (or refactored later). Safer to use an ok assertion and fall back to normal verification.

gateway/cache.go (3)

151-154: Include model/prompt versioning in the cache key to prevent “wrong result” after config changes.

If OPENROUTER_MODEL (or prompt behavior) changes, you can serve stale summaries computed under the old configuration.


130-147: Parsing captured JSON via map[string]interface{} is brittle.

If the response shape changes (or Gin writes additional fields), caching silently stops working. Prefer decoding into a small struct { Result string \json:"result"` }` and explicitly handling errors.


174-199: Clamp/validate TTL to avoid accidental “disable cache” via misconfig.

CACHE_TTL_SECONDS set to 0/negative will effectively disable caching (or behave unexpectedly). Consider enforcing a minimum (or treating <=0 as “don’t cache”).

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa8ef58 and a730af3.

⛔ Files ignored due to path filters (1)
  • gateway/go.sum is excluded by !**/*.sum
📒 Files selected for processing (12)
  • .env.example
  • PR_DESCRIPTION.md
  • README.md
  • docker-compose.yml
  • gateway/cache.go
  • gateway/cache_integration_test.go
  • gateway/cache_test.go
  • gateway/go.mod
  • gateway/main.go
  • gateway/receipt.go
  • gateway/receipt_test.go
  • gateway/redis.go
✅ Files skipped from review due to trivial changes (1)
  • gateway/receipt_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • gateway/redis.go
  • gateway/go.mod
🧰 Additional context used
🧬 Code graph analysis (3)
gateway/cache_integration_test.go (3)
gateway/main.go (2)
  • VerifyRequest (40-43)
  • VerifyResponse (45-49)
gateway/middleware.go (1)
  • RequestTimeoutMiddleware (114-186)
gateway/cache.go (1)
  • CacheMiddleware (25-149)
gateway/cache.go (1)
gateway/main.go (1)
  • SummarizeRequest (51-53)
gateway/receipt.go (2)
web/src/lib/verify-receipt.ts (4)
  • PaymentDetails (14-21)
  • ServiceDetails (23-27)
  • Receipt (29-35)
  • SignedReceipt (37-41)
gateway/main.go (1)
  • PaymentContext (32-38)
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 61-61: [ExtraBlankLine] Extra blank line detected

(ExtraBlankLine)


[warning] 65-65: [UnorderedKey] The REDIS_PASSWORD key should go before the REDIS_URL key

(UnorderedKey)


[warning] 66-66: [UnorderedKey] The REDIS_DB key should go before the REDIS_PASSWORD key

(UnorderedKey)

🪛 LanguageTool
PR_DESCRIPTION.md

[style] ~4-~4: Try moving the adverb to make the sentence clearer.
Context: ...` endpoint using Redis. This feature is designed to significantly reduce API costs for the OpenRouter service and improve ...

(SPLIT_INFINITIVE)

🪛 markdownlint-cli2 (0.18.1)
PR_DESCRIPTION.md

9-9: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


10-10: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

🔇 Additional comments (13)
gateway/receipt.go (4)

15-46: Type definitions are well-structured.

The receipt types are properly defined with appropriate JSON tags and align with the frontend TypeScript interfaces shown in the relevant code snippets.


48-75: Receipt generation logic is correct.

The function properly constructs the receipt with all required payment and service details, delegates cryptographic operations to specialized helpers, and maintains clear error propagation.


88-95: Hash function is correctly implemented.

Properly handles the empty data edge case and uses standard SHA256 with a clear prefix for hash type identification.


97-135: Cryptographic signing implementation is sound.

The function correctly:

  • Leverages Go's deterministic JSON marshaling for consistent signatures
  • Uses Keccak256 for Ethereum-compatible hashing
  • Applies ECDSA signing with constant-time operations to prevent timing attacks
  • Includes the server's public key for client-side verification
.env.example (1)

61-71: Redis caching configuration is well-structured.

The environment variables cover all necessary Redis connection parameters and cache behavior settings. The inline comment distinguishing Docker Compose vs local usage is helpful for developers.

README.md (1)

523-546: Caching documentation is comprehensive and security-conscious.

The documentation clearly explains the cache-aside pattern and, importantly, emphasizes that payment verification remains enforced even for cached responses. This clarification prevents potential security misunderstandings.

PR_DESCRIPTION.md (1)

1-32: PR description is clear and comprehensive.

The description effectively communicates the feature's purpose, security considerations, and verification approach.

gateway/cache_test.go (2)

10-42: Good coverage for determinism/prefix/length.

These tests lock down the important key properties without overfitting.


53-62: Spec test is solid.

This is the most valuable test here (exact "ai:summary:" + sha256(text) contract).

gateway/main.go (3)

305-356: Verification flow looks clean (timeout + marshal error handling).

Nice to see the explicit json.Marshal error handling and the verifier-specific timeout.


358-385: Receipt generation/response path is well-factored.

Having a single function responsible for header + JSON response reduces the chance of partial writes.


179-189: No duplicate receipt cleanup goroutine found. Verification confirms only one go startReceiptCleanup(cleanupCtx) call at line 187. The second match (line 660) is the function definition, not a goroutine start. Greptile's concern was a false positive.

gateway/cache.go (1)

80-114: Good: verify payment before serving cached content.

The cache-hit path correctly verifies X-402-* before returning a cached result.

Comment thread gateway/cache_integration_test.go
Comment thread gateway/cache_integration_test.go
Comment thread gateway/cache.go
Comment thread gateway/main.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In @.env.example:
- Around line 61-71: Remove the extra blank line and fix key ordering in
.env.example by grouping related entries together: keep the "Redis
Configuration" block as consecutive lines REDIS_URL, REDIS_PASSWORD, REDIS_DB
(no extra blank line after REDIS_DB) and then the "Cache Settings" block
immediately after with CACHE_ENABLED and CACHE_TTL_SECONDS in that order; ensure
there are no stray blank lines between keys or comments to satisfy
dotenv-linter.

In @gateway/cache_integration_test.go:
- Around line 84-95: The test's timing assertion is too strict and flakes on CI;
relax it by increasing the sleep/threshold used around the makeRequest timing
checks and adjust the assertion to allow a larger margin (e.g., change 100ms to
a larger safe value like 200ms or use >=/greater-or-equal logic) for both
occurrences referenced around the makeRequest helper and the second check (lines
~84-95 and ~112-114), ensuring the test still validates caching behavior but
tolerates scheduler/clock granularity.

In @gateway/main.go:
- Around line 213-218: The code reads context values with c.Get and blindly
dereferences paymentCtx causing a panic when "payment_context" is missing or
wrong type; update both places (where VerifyResponse and PaymentContext are read
and where generateAndSendReceipt is called) to use safe type assertions (v, ok
:= c.Get("payment_verification"); verifyResp, ok = v.(*VerifyResponse) style)
and check ok/non-nil before using or dereferencing paymentCtx, returning an
error or skipping/handling the receipt generation when paymentCtx is absent; do
the same safe checks for "payment_context" (p, ok := c.Get("payment_context");
paymentCtx, ok = p.(*PaymentContext)) and ensure generateAndSendReceipt is only
called with a valid paymentCtx (or accept a pointer and handle nil inside the
function).
- Around line 207-275: handleSummarize reads the entire request body with
io.ReadAll without a size cap, allowing large payload DoS when cache/middleware
is bypassed; wrap c.Request.Body with http.MaxBytesReader (e.g., maxBody :=
int64(10<<20); c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body,
maxBody)) before each io.ReadAll call (both in the verifyResp==nil branch and
the else branch), then read into requestBody and restore c.Request.Body with
io.NopCloser(bytes.NewBuffer(requestBody)); also handle the MaxBytesReader error
(return 413 Payload Too Large) when ReadAll returns an http.MaxBytesError.
🧹 Nitpick comments (2)
gateway/cache_integration_test.go (1)

28-45: Don’t ignore verifier mock decode/encode errors (avoids false positives).

Proposed diff
 	verifier := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		// Mock validation based on signature
 		var req VerifyRequest
-		json.NewDecoder(r.Body).Decode(&req)
+		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+			http.Error(w, "bad request", http.StatusBadRequest)
+			return
+		}
 
 		isValid := req.Signature == "0xValidSig"
 		resp := VerifyResponse{
 			IsValid:          isValid,
 			RecoveredAddress: "0xTestUser",
 			Error:            "",
 		}
@@
-		json.NewEncoder(w).Encode(resp)
+		if err := json.NewEncoder(w).Encode(resp); err != nil {
+			http.Error(w, "encode error", http.StatusInternalServerError)
+			return
+		}
 	}))
gateway/cache.go (1)

179-199: Clamp invalid TTL values (0/negative) to a safe default.

Proposed diff
 	ttl := time.Duration(getEnvAsInt("CACHE_TTL_SECONDS", 3600)) * time.Second
+	if ttl <= 0 {
+		ttl = 3600 * time.Second
+	}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a730af3 and 77956ba.

⛔ Files ignored due to path filters (1)
  • gateway/go.sum is excluded by !**/*.sum
📒 Files selected for processing (11)
  • .env.example
  • README.md
  • docker-compose.yml
  • gateway/cache.go
  • gateway/cache_integration_test.go
  • gateway/cache_test.go
  • gateway/go.mod
  • gateway/main.go
  • gateway/receipt.go
  • gateway/receipt_test.go
  • gateway/redis.go
✅ Files skipped from review due to trivial changes (2)
  • gateway/receipt_test.go
  • gateway/receipt.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • gateway/redis.go
  • docker-compose.yml
  • gateway/cache_test.go
  • gateway/go.mod
  • README.md
🧰 Additional context used
🧬 Code graph analysis (3)
gateway/cache_integration_test.go (3)
gateway/main.go (2)
  • VerifyRequest (40-43)
  • VerifyResponse (45-49)
gateway/middleware.go (1)
  • RequestTimeoutMiddleware (114-186)
gateway/cache.go (1)
  • CacheMiddleware (25-149)
gateway/main.go (3)
gateway/cache.go (1)
  • CacheMiddleware (25-149)
src/services/payment.ts (1)
  • PaymentContext (8-14)
gateway/receipt.go (1)
  • GenerateReceipt (49-75)
gateway/cache.go (1)
gateway/main.go (1)
  • SummarizeRequest (51-53)
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 61-61: [ExtraBlankLine] Extra blank line detected

(ExtraBlankLine)


[warning] 65-65: [UnorderedKey] The REDIS_PASSWORD key should go before the REDIS_URL key

(UnorderedKey)


[warning] 66-66: [UnorderedKey] The REDIS_DB key should go before the REDIS_PASSWORD key

(UnorderedKey)

🔇 Additional comments (3)
gateway/cache_integration_test.go (1)

58-72: Integration test mutates process env + global redisClient; keep it non-parallel and isolated.

Please confirm no other tests in this package call t.Parallel() or rely on redisClient/VERIFIER_URL/OPENROUTER_URL concurrently; otherwise this test can become order-dependent.

gateway/main.go (1)

168-172: Confirm initRedis() is gated by CACHE_ENABLED (avoid startup redis dials when caching off).

If initRedis() always attempts a Redis connection, it adds startup latency/noise even when caching is disabled. Please confirm gateway/redis.go:initRedis() no-ops unless getCacheEnabled() is true (or equivalent).

Also applies to: 190-192

gateway/cache.go (1)

130-134: Verify Gin status-code behavior for caching condition (writer.Status() == 200).

This relies on gin.ResponseWriter.Status() semantics (often defaults to 200 before headers are written). Please confirm this won’t cache responses where the handler never wrote a body/JSON (or adjust to also require writer.body.Len() > 0).

Comment thread .env.example
Comment thread gateway/cache_integration_test.go
Comment thread gateway/main.go
Comment thread gateway/main.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
.env.example (1)

61-71: Optional: Address linter formatting suggestions.

The dotenv-linter flags a few minor style issues: an extra blank line at line 61 and key ordering for REDIS_PASSWORD and REDIS_DB. While these don't affect functionality, addressing them would align with dotenv best practices.

♻️ Proposed formatting fixes
-

 # Redis Configuration (for Caching)
 # Use 'redis:6379' for docker-compose, 'localhost:6379' for local run
+REDIS_DB=0
+REDIS_PASSWORD=
 REDIS_URL=redis:6379
-REDIS_PASSWORD=
-REDIS_DB=0
 
 # Cache Settings
 CACHE_ENABLED=true
 # Time-to-live for cached items in seconds (default: 3600 = 1 hour)
 CACHE_TTL_SECONDS=3600
gateway/cache.go (2)

174-199: Cache storage implementation is robust.

Error handling is complete: JSON marshal errors are logged and function returns early (lines 187-189), and Redis write failures are logged (lines 196-198). The 2-second timeout for storage operations prevents blocking on slow Redis.

Note: getEnvAsInt is called on each cache write. For high-throughput scenarios, consider reading the TTL once at initialization. This is a minor optimization opportunity, not a blocking concern.


201-206: Remove unused struct fields.

The cacheKey and ctx fields are assigned in CacheMiddleware (lines 123-124) but never accessed from the cachedWriter struct. The cache storage logic uses variables from the closure scope instead. These fields can be removed to avoid confusion.

♻️ Suggested cleanup
 type cachedWriter struct {
 	gin.ResponseWriter
 	body     *bytes.Buffer
-	cacheKey string
-	ctx      context.Context
 }

And update the initialization in CacheMiddleware:

 	writer := &cachedWriter{
 		ResponseWriter: c.Writer,
 		body:           &bytes.Buffer{},
-		cacheKey:       cacheKey,
-		ctx:            c.Request.Context(),
 	}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77956ba and 1281373.

⛔ Files ignored due to path filters (1)
  • gateway/go.sum is excluded by !**/*.sum
📒 Files selected for processing (11)
  • .env.example
  • README.md
  • docker-compose.yml
  • gateway/cache.go
  • gateway/cache_integration_test.go
  • gateway/cache_test.go
  • gateway/go.mod
  • gateway/main.go
  • gateway/receipt.go
  • gateway/receipt_test.go
  • gateway/redis.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • gateway/receipt_test.go
  • gateway/cache_test.go
  • gateway/receipt.go
  • README.md
  • docker-compose.yml
🧰 Additional context used
🧬 Code graph analysis (3)
gateway/cache.go (1)
gateway/main.go (1)
  • SummarizeRequest (51-53)
gateway/main.go (3)
gateway/cache.go (1)
  • CacheMiddleware (25-149)
src/services/payment.ts (1)
  • PaymentContext (8-14)
gateway/receipt.go (1)
  • GenerateReceipt (49-75)
gateway/cache_integration_test.go (3)
gateway/main.go (2)
  • VerifyRequest (40-43)
  • VerifyResponse (45-49)
gateway/middleware.go (1)
  • RequestTimeoutMiddleware (114-186)
gateway/cache.go (1)
  • CacheMiddleware (25-149)
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 61-61: [ExtraBlankLine] Extra blank line detected

(ExtraBlankLine)


[warning] 65-65: [UnorderedKey] The REDIS_PASSWORD key should go before the REDIS_URL key

(UnorderedKey)


[warning] 66-66: [UnorderedKey] The REDIS_DB key should go before the REDIS_PASSWORD key

(UnorderedKey)

🪛 Gitleaks (8.30.0)
gateway/cache_integration_test.go

[high] 69-69: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🔇 Additional comments (16)
gateway/redis.go (2)

14-40: LGTM! Solid graceful degradation pattern.

The Redis initialization correctly handles connection failures by setting the client to nil and logging warnings, allowing the application to continue without caching. The pattern of closing any existing client before reinitializing prevents resource leaks.


27-27: No action needed. The function getEnvAsInt is defined in gateway/main.go at line 633 and is accessible within the gateway package. Since both gateway/redis.go and gateway/main.go belong to the same package, the function call is valid.

gateway/cache_integration_test.go (2)

16-199: LGTM! Comprehensive integration test coverage.

This integration test thoroughly validates the caching flow including:

  • Cache miss and hit scenarios with timing validation
  • Security enforcement (invalid/missing signatures properly rejected)
  • Async cache population with polling
  • Proper cleanup with t.Setenv and deferred closes

The test design is solid and covers all critical paths.


69-69: Static analysis false positive - test fixture API key.

The Gitleaks warning about the API key on line 69 is a false positive. This is a test fixture string ("test-key") used in the integration test environment, not a real credential. The hardcoded private key on the same line is also a test fixture (64 hex zeros) for the mock environment.

gateway/main.go (5)

168-172: LGTM! Clean conditional caching integration.

The conditional application of CacheMiddleware based on the CACHE_ENABLED flag is well-structured and allows for easy toggling of the caching feature.


207-305: LGTM! Well-structured refactoring with clear separation of concerns.

The refactored handleSummarize function now cleanly separates verification, AI processing, and receipt generation. The dual-path logic (pre-verified from cache vs. fresh verification) is handled correctly, and error handling covers all edge cases including timeouts and invalid signatures.


307-358: LGTM! Clean extraction of verification logic.

The verifyPayment function is well-structured with appropriate timeout handling, error propagation, and returns both the verification response and payment context for downstream use.


360-387: LGTM! Proper error handling in receipt generation.

The generateAndSendReceipt function correctly handles all error cases by sending appropriate JSON error responses before returning the error. This ensures clients always receive a response even when receipt generation fails. The comment on lines 300-302 questioning error handling appears to be resolved - the function does handle sending error responses.


187-191: No duplicate startReceiptCleanup calls found. The codebase contains only a single call to startReceiptCleanup on line 187; the PR description's mention of a "duplicate startReceiptCleanup goroutine" does not reflect the current state of the code.

gateway/go.mod (1)

11-11: v9.17.2 is the latest stable release of go-redis/v9, so no action is required.

gateway/cache.go (6)

139-144: Async cache storage with detached context is correct.

Using context.Background() for the goroutine prevents the cache write from being canceled when the HTTP request completes. The storeInCache function internally applies a 2-second timeout. This is a sound pattern for fire-and-forget cache population.


83-114: Security: Payment verification before serving cached content is well-implemented.

The cache-hit path correctly verifies the payment signature before returning cached data. This prevents unauthorized users from receiving cached responses without valid payment credentials.


151-154: LGTM!

Content-addressable key using SHA256 is deterministic and privacy-preserving. The prefix provides clear namespace separation in Redis.


156-172: LGTM!

Proper nil check for Redis client, and JSON unmarshal errors are correctly propagated, allowing graceful handling of corrupted cache entries.


208-216: Response capture methods are correctly implemented.

The tee pattern properly writes to both the internal buffer and the underlying ResponseWriter. While bytes.Buffer.Write technically returns an error, it never fails in practice, so ignoring it here is acceptable.


130-147: No issues found. The code correctly prevents caching of error responses because all handler paths explicitly set status codes via c.JSON(), so the check at line 133 safely filters out non-200 responses.

@roshankumar0036singh

Copy link
Copy Markdown
Contributor Author

@AnkanMisra i have addressed all issues plz review it now

@AnkanMisra

Copy link
Copy Markdown
Owner

@greptile review the pull request with respect to #31

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment thread gateway/cache.go
@AnkanMisra

Copy link
Copy Markdown
Owner

@AnkanMisra i have addressed all issues plz review it now

You asking me or ordering me with the "now"?

@roshankumar0036singh

Copy link
Copy Markdown
Contributor Author

@AnkanMisra just asking you brother , i jsut said that "i have addressed all the issues by ai, you can review it now"

Store parsed request body in Gin context to avoid double-reading
and redundant MaxBytesReader validation between middleware and handler.

Resolves code review comment on cache.go:44-66
@roshankumar0036singh

Copy link
Copy Markdown
Contributor Author

@greptile review the pull request with respect to #31

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @gateway/cache_integration_test.go:
- Around line 54-61: The aiCalls counter is mutated inside the
httptest.NewServer handler goroutine and read from the test goroutine causing a
data race; change aiCalls to an atomic counter (e.g., declare an atomic.Int32 or
an int32 and use sync/atomic) and replace the increment in the handler with an
atomic add, then replace any reads of aiCalls in assertions with atomic loads to
ensure synchronization when checking the value after the server interactions.

In @gateway/cache.go:
- Around line 86-96: The handler is returning internal error text to clients by
using err.Error() in the JSON response; instead, stop leaking internal details
by returning a generic error message and log the detailed error already (logged
via log.Printf). Update the error branch handling the result from verifyPayment
(variables verifyResp, paymentCtx, err) to: for context.DeadlineExceeded keep
the 504 response as-is; for other errors send a 500 with a safe, generic message
such as {"error":"Verification Service Failed","message":"An internal error
occurred"} (or include a safe error code) and do not include err.Error() in the
JSON; continue to call c.Abort() and return as before.

In @gateway/main.go:
- Around line 273-280: The else branch that reads c.Request.Body when verifyResp
is set is unreachable because CacheMiddleware sends the cached response and
calls c.Abort() on a cache HIT, so the handler never runs; remove the dead code
(the entire else block that reads requestBody via io.ReadAll) or, if you intend
to support a future non-abort flow, replace it with a short explanatory comment
referencing verifyResp and CacheMiddleware and keep only a defensive nil-check
for c.Request.Body (no processing) to avoid confusion.
🧹 Nitpick comments (10)
.env.example (1)

61-71: Minor formatting: extra blank line detected.

Static analysis flagged an extra blank line at line 61. This is a minor issue but worth cleaning up for consistency.

🔧 Suggested fix
 HEALTH_CHECK_TIMEOUT_SECONDS=2

-
 # Redis Configuration (for Caching)
docker-compose.yml (1)

23-28: Consider adding depends_on for Redis in the gateway service.

The gateway service should depend on Redis to ensure proper startup order. While the code handles Redis unavailability gracefully, adding the dependency improves reliability during initial startup.

🔧 Suggested fix
   gateway:
     build: ./gateway
     ports:
       - "3000:3000"
     environment:
       - PORT=3000
       - VERIFIER_URL=http://verifier:3002
     depends_on:
       - verifier
+      - redis
     networks:
       - microai-net

Additionally, consider adding a health check for production readiness:

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3
    networks:
      - microai-net
gateway/redis.go (2)

12-40: Global redisClient lacks synchronization for concurrent access.

While initRedis() is called once at startup, the global redisClient variable could be read concurrently by request handlers while being written during initialization or if initRedis is ever called again (e.g., for reconnection). Consider using sync.Once for initialization or an atomic.Pointer for safer access.

🔧 Suggested approach using sync.Once
+var redisOnce sync.Once
 var redisClient *redis.Client

 func initRedis() {
+	redisOnce.Do(func() {
 	if !getCacheEnabled() {
 		return
 	}
-
-	// Close existing client if any
-	if redisClient != nil {
-		redisClient.Close()
-	}

 	redisClient = redis.NewClient(&redis.Options{
 		Addr:     getEnv("REDIS_URL", "localhost:6379"),
 		Password: os.Getenv("REDIS_PASSWORD"),
 		DB:       getEnvAsInt("REDIS_DB", 0),
 	})

 	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 	defer cancel()

 	if err := redisClient.Ping(ctx).Err(); err != nil {
 		log.Printf("Redis unavailable: %v (caching disabled)", err)
 		redisClient.Close()
 		redisClient = nil
 	} else {
 		log.Println("Redis connected successfully")
 	}
+	})
 }

42-44: Case-sensitive check for CACHE_ENABLED.

The comparison == "true" is strict. Consider matching the pattern used in getRateLimitEnabled() (main.go) which accepts both "true" and "1":

func getCacheEnabled() bool {
    enabled := strings.ToLower(os.Getenv("CACHE_ENABLED"))
    return enabled == "true" || enabled == "1"
}
gateway/cache_test.go (2)

44-51: Misleading test name: TestCacheKeyCollisionWait.

The test name suggests it's testing collision waiting behavior, but it actually verifies that different inputs produce different keys. Consider renaming to TestCacheKeyDifferentInputs or TestCacheKeyUniqueness.

🔧 Suggested fix
-func TestCacheKeyCollisionWait(t *testing.T) {
-	// Not a real collision test, but ensuring different content = different key
+func TestCacheKeyUniqueness(t *testing.T) {
+	// Ensure different content produces different keys
 	k1 := getCacheKey("abc")
 	k2 := getCacheKey("abd")
 	if k1 == k2 {
 		t.Error("Collision detected for different content")
 	}
 }

10-42: Consider adding edge case for empty string input.

The test cases cover simple text, long text, and special characters, but don't test empty string input. Since getCacheKey uses SHA256, an empty input is valid but worth testing explicitly.

🔧 Suggested addition
 	tests := []struct {
 		name string
 		text string
 	}{
+		{"Empty String", ""},
 		{"Simple Text", "Hello World"},
 		{"Long Text", strings.Repeat("a", 1000)},
 		{"Special Chars", "!@#$%^&*()"},
 	}
gateway/cache_integration_test.go (1)

16-25: Comment numbering is inconsistent.

Line 17 has // 1. and line 26 has // 3. but there's no // 2. section. Consider either adding the missing section or renumbering for clarity.

gateway/main.go (2)

190-191: Consider initializing Redis earlier in startup sequence.

initRedis() is called after the router is fully configured. While this works, initializing Redis earlier (before route setup) would ensure caching is ready before any potential early requests. Consider moving it before the router configuration or at least before the route group setup.


300-307: Clarify error handling flow in comments.

The inline comments express uncertainty about error handling:

// generateAndSendReceipt sends error response if it fails?
// No, it returns error, we might have already written status if we aren't careful.
// Let's implement generateAndSendReceipt to handle sending response.

These read like development notes rather than documentation. Since generateAndSendReceipt does handle error responses internally (sends JSON error and returns error), consider replacing these with a clear comment:

// generateAndSendReceipt handles both success and error responses internally
gateway/cache.go (1)

203-217: Remove unused fields from cachedWriter.

The cacheKey and ctx fields are assigned at lines 125-126 but never accessed within the struct's methods. The actual caching logic resides in the middleware after c.Next(), making these fields dead code.

Proposed fix
 type cachedWriter struct {
 	gin.ResponseWriter
 	body     *bytes.Buffer
-	cacheKey string
-	ctx      context.Context
 }

And update the initialization at lines 122-127:

 	writer := &cachedWriter{
 		ResponseWriter: c.Writer,
 		body:           &bytes.Buffer{},
-		cacheKey:       cacheKey,
-		ctx:            c.Request.Context(),
 	}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77956ba and 1c90c78.

⛔ Files ignored due to path filters (1)
  • gateway/go.sum is excluded by !**/*.sum
📒 Files selected for processing (11)
  • .env.example
  • README.md
  • docker-compose.yml
  • gateway/cache.go
  • gateway/cache_integration_test.go
  • gateway/cache_test.go
  • gateway/go.mod
  • gateway/main.go
  • gateway/receipt.go
  • gateway/receipt_test.go
  • gateway/redis.go
✅ Files skipped from review due to trivial changes (1)
  • gateway/receipt_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • README.md
  • gateway/go.mod
🧰 Additional context used
🧬 Code graph analysis (2)
gateway/main.go (3)
gateway/cache.go (1)
  • CacheMiddleware (25-151)
src/services/payment.ts (1)
  • PaymentContext (8-14)
gateway/receipt.go (1)
  • GenerateReceipt (49-75)
gateway/cache.go (1)
gateway/main.go (1)
  • SummarizeRequest (51-53)
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 61-61: [ExtraBlankLine] Extra blank line detected

(ExtraBlankLine)


[warning] 65-65: [UnorderedKey] The REDIS_PASSWORD key should go before the REDIS_URL key

(UnorderedKey)


[warning] 66-66: [UnorderedKey] The REDIS_DB key should go before the REDIS_PASSWORD key

(UnorderedKey)

🪛 Gitleaks (8.30.0)
gateway/cache_integration_test.go

[high] 69-69: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🔇 Additional comments (12)
gateway/receipt.go (1)

1-135: LGTM!

The receipt generation logic is well-implemented with proper security considerations:

  • Cryptographically secure receipt ID generation using crypto/rand
  • Correct handling of empty data in hashData
  • Appropriate use of constant-time ECDSA signing from go-ethereum
gateway/cache_integration_test.go (2)

69-69: Gitleaks false positive: test private key.

The static analysis tool flagged this as a potential API key leak. This is a false positive since this is a deterministic test key in a test file, not a real secret. No action needed, but you may want to add a comment to clarify this is intentional.


130-142: Polling-based cache population check is appropriate.

The assertCachePopulated helper correctly uses polling with a timeout to wait for the async cache write. This is a good pattern for testing async operations.

gateway/main.go (3)

168-172: Cache middleware integration looks correct.

The conditional application of CacheMiddleware() based on getCacheEnabled() is appropriate. When caching is disabled, requests go directly to handleSummarize.


310-361: verifyPayment extraction is clean and well-structured.

The function properly handles context timeout, marshaling, HTTP request creation, and response parsing. Error wrapping provides good context for debugging.


363-390: generateAndSendReceipt handles response lifecycle correctly.

The function properly generates the receipt, stores it, encodes it, and sends the JSON response. Error handling sends appropriate 500 responses for each failure mode.

gateway/cache.go (6)

1-23: LGTM!

Package, imports, and CachedResponse struct are well-defined. The struct fields appropriately capture the cached result and timestamp for potential cache age tracking.


44-68: LGTM!

Good implementation of request body handling:

  • DoS protection via MaxBytesReader with 10MB limit
  • Proper body preservation in context and restoration for downstream handlers
  • Appropriate error handling for payload-too-large vs other read errors

118-150: LGTM!

Cache miss handling is well-implemented:

  • Response capture via wrapped writer
  • Asynchronous cache storage to avoid blocking the response
  • Correct use of context.Background() in the goroutine since the request context may be cancelled
  • Only caches successful (200) responses

153-174: LGTM!

  • Content-addressable cache keys using SHA256 provide determinism and collision resistance
  • Proper nil-safety check for Redis client
  • Clean JSON unmarshaling with appropriate error propagation

176-201: LGTM!

Robust cache storage implementation:

  • Configurable TTL via environment variable with sensible default
  • 2-second timeout prevents blocking on Redis issues
  • Errors are logged but don't crash the service (graceful degradation)

25-151: All external dependencies are properly defined. redisClient is declared in gateway/redis.go, and the functions verifyPayment, generateAndSendReceipt, getFromCache, getCacheKey, and storeInCache are all properly defined with correct signatures that match their usage in the middleware.

Comment thread gateway/cache_integration_test.go Outdated
Comment thread gateway/cache.go Outdated
Comment thread gateway/main.go Outdated

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

9 files reviewed, 9 comments

Edit Code Review Agent Settings | Greptile

Comment thread gateway/cache.go
Comment thread gateway/cache.go Outdated
Comment thread gateway/cache.go
Comment thread gateway/main.go Outdated
Comment thread gateway/main.go Outdated
Comment thread gateway/redis.go
Comment thread docker-compose.yml
Comment thread gateway/cache.go
@greptile-apps

greptile-apps Bot commented Jan 11, 2026

Copy link
Copy Markdown
Contributor
Additional Comments (1)

docker-compose.yml
Critical: Gateway cannot connect to Redis in Docker

The gateway service doesn't have Redis configuration passed via environment variables. Looking at gateway/redis.go line 25, the default REDIS_URL is "localhost:6379", but in the Docker network, Redis is accessible at "redis:6379" (the service name).

This means when running via docker-compose up, the gateway will try to connect to localhost:6379 (which doesn't exist inside the container) and fail. The .env.example correctly specifies REDIS_URL=redis:6379 for docker-compose, but this environment variable needs to be passed to the container.

The gateway service needs to:

  1. Include Redis environment variables (REDIS_URL, CACHE_ENABLED, etc.)
  2. Add redis to the depends_on list so the gateway starts after Redis is ready

Without this, caching will be silently disabled in Docker deployments since Redis connection will fail during initialization.

Prompt To Fix With AI
This is a comment left during a code review.
Path: docker-compose.yml
Line: 4:14

Comment:
**Critical: Gateway cannot connect to Redis in Docker**

The gateway service doesn't have Redis configuration passed via environment variables. Looking at `gateway/redis.go` line 25, the default `REDIS_URL` is `"localhost:6379"`, but in the Docker network, Redis is accessible at `"redis:6379"` (the service name).

This means when running via `docker-compose up`, the gateway will try to connect to `localhost:6379` (which doesn't exist inside the container) and fail. The .env.example correctly specifies `REDIS_URL=redis:6379` for docker-compose, but this environment variable needs to be passed to the container.

The gateway service needs to:
1. Include Redis environment variables (REDIS_URL, CACHE_ENABLED, etc.)
2. Add `redis` to the `depends_on` list so the gateway starts after Redis is ready

Without this, caching will be silently disabled in Docker deployments since Redis connection will fail during initialization.

How can I resolve this? If you propose a fix, please make it concise.

- Add 5s timeout guard to async cache storage goroutine
- Fix inefficient string->[]byte->string conversion in storeInCache
- Make getCacheEnabled() consistent with getRateLimitEnabled() (accept 'true'/'1', case-insensitive)
- Remove unreachable dead code in handleSummarize checking for pre-verified payment
- Configure Docker Compose: add Redis env vars to gateway, enable persistence, set memory limits
- Fix critical issue: gateway can now connect to Redis in Docker (redis:6379 vs localhost:6379)
@roshankumar0036singh

Copy link
Copy Markdown
Contributor Author

@greptile review the pr with respect to #31

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment thread gateway/cache.go
Comment thread gateway/cache.go Outdated
Comment on lines +194 to +195
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P3] The function creates a 2s timeout context when it already receives a context with a 5s timeout from the caller (line 142). This creates a nested timeout that will always hit the 2s limit. Consider removing this nested timeout since the caller already provides timeout protection.

Prompt To Fix With AI
This is a comment left during a code review.
Path: gateway/cache.go
Line: 194:195

Comment:
[P3] The function creates a 2s timeout context when it already receives a context with a 5s timeout from the caller (line 142). This creates a nested timeout that will always hit the 2s limit. Consider removing this nested timeout since the caller already provides timeout protection.

How can I resolve this? If you propose a fix, please make it concise.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @gateway/main.go:
- Around line 227-229: The code uses an unsafe type assertion on
c.Get("request_body") which can panic; change the block around c.Get,
requestBody and the "request_body" key to use the comma-ok form: retrieve body
via c.Get("request_body"), check the first ok, then assert body.([]byte) using
the second ok and only assign to requestBody when that assertion succeeds;
otherwise handle the mismatch (e.g., leave requestBody nil/empty and log or
return an error) to avoid a panic.
🧹 Nitpick comments (5)
docker-compose.yml (1)

11-15: Gateway Redis configuration is functional but consider adding optional env vars.

The gateway connects to Redis correctly. Consider explicitly setting CACHE_TTL_SECONDS for visibility, even if the default (3600s) is acceptable.

gateway/main.go (1)

283-286: Remove stale/confusing comments.

These comments question the implementation ("generateAndSendReceipt sends error response if it fails? No, it returns error...") but generateAndSendReceipt does send error responses before returning. The comments are misleading.

Proposed fix
 	// 4. Generate & Send Receipt
 	if err := generateAndSendReceipt(c, *paymentCtx, verifyResp.RecoveredAddress, requestBody, summary); err != nil {
 		log.Printf("Failed to generate receipt: %v", err)
-		// generateAndSendReceipt sends error response if it fails?
-		// No, it returns error, we might have already written status if we aren't careful.
-		// Let's implement generateAndSendReceipt to handle sending response.
 		return
 	}
gateway/cache.go (3)

104-106: Dead code: context values stored but never used.

Since c.Abort() is called at line 114, these stored values are never consumed by downstream handlers.

Proposed fix
-			// Payment Verified. Store verification for downstream if needed (though we abort)
-			c.Set("payment_verification", verifyResp)
-			c.Set("payment_context", paymentCtx)
-
 			// Generate Receipt and Respond

140-145: Nested timeout contexts - the 2s timeout in storeInCache will take precedence.

The goroutine creates a 5s timeout context, but storeInCache (line 194) creates another 2s timeout. The shorter deadline wins, so the 5s timeout here is effectively unused. Consider removing one of them for clarity.

Option: Remove inner timeout and rely on caller's context

In storeInCache, change lines 193-195 to use the passed context directly:

-	// Create context with timeout for storage
-	ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
-	defer cancel()
-
 	if err := redisClient.Set(ctx, key, jsonData, ttl).Err(); err != nil {

This way the 5s timeout in the caller governs the operation.


180-180: Environment variable read on every cache store.

getEnvAsInt("CACHE_TTL_SECONDS", 3600) is called on each store operation. Consider caching this value at init time if high throughput is expected, though current impact is negligible.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1c90c78 and 502d465.

📒 Files selected for processing (4)
  • docker-compose.yml
  • gateway/cache.go
  • gateway/main.go
  • gateway/redis.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • gateway/redis.go
🧰 Additional context used
🧬 Code graph analysis (1)
gateway/cache.go (1)
gateway/main.go (1)
  • SummarizeRequest (51-53)
🔇 Additional comments (11)
docker-compose.yml (2)

35-38: deploy.resources.limits only applies in Docker Swarm mode.

The deploy section is ignored when using docker-compose up. For standalone Compose, use the legacy mem_limit directive or run with docker stack deploy in Swarm mode.

Proposed fix for non-Swarm environments
     command: redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru
-    deploy:
-      resources:
-        limits:
-          memory: 512M
+    mem_limit: 512m

Note: Redis's --maxmemory 512mb already caps Redis data, but mem_limit enforces the container-level limit if you want defense-in-depth.


26-34: Redis service configuration looks good.

AOF persistence with --appendonly yes, memory limit with LRU eviction, and volume mount for data durability are well-chosen for a caching layer.

gateway/main.go (4)

168-172: LGTM!

Conditional middleware wrapping based on getCacheEnabled() is clean and avoids unnecessary overhead when caching is disabled.


190-192: LGTM!

Redis initialization placed appropriately before server starts. The middleware gracefully degrades if Redis is unavailable.


290-341: LGTM!

The verifyPayment function properly creates a timeout-bounded sub-context for the verifier call, handles errors comprehensively with wrapping, and returns structured results. Clean separation of concerns.


343-370: LGTM!

The function cleanly handles receipt generation, storage, encoding, and response. Error responses are sent before returning errors, preventing double-writes.

gateway/cache.go (5)

19-23: LGTM!

Simple, well-defined struct for cached data with timestamp for debugging/observability.


44-68: LGTM!

Body reading with size limit, context storage, and restoration for downstream handlers is correctly implemented.


152-155: LGTM!

Content-addressable cache key using SHA256 ensures determinism and avoids embedding raw text in keys, as required by the objectives.


157-173: LGTM!

Nil-check for graceful degradation, straightforward get-and-unmarshal pattern.


202-215: LGTM!

The cachedWriter correctly captures response body while passing through to the original writer. For the summarize endpoint's typical response sizes, the unbounded buffer is acceptable.

Comment thread gateway/main.go Outdated
- Remove redundant 2s timeout in storeInCache (caller already provides 5s timeout)
- Use comma-ok idiom for request_body type assertion to prevent panic
@roshankumar0036singh

Copy link
Copy Markdown
Contributor Author

@greptile review the pr with repsect to #31

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @gateway/redis.go:
- Around line 15-58: The initRedis function currently calls log.Fatalf on
redis.ParseURL errors but handles redisClient.Ping failures by logging a
warning, closing the client, setting redisClient=nil, and returning; make the
error handling consistent by replacing the fatal exit on parse errors with the
same graceful-degradation flow: catch the error from redis.ParseURL, log a
warning including the parse error, ensure redisClient remains nil, and return so
caching is disabled instead of calling log.Fatalf; update references in
initRedis around redis.ParseURL, log.Fatalf, and the existing redisClient.Ping
error branch to use the same warning/disable/return pattern and include the
parse error details in the log.
🧹 Nitpick comments (5)
gateway/cache_test.go (2)

10-43: Avoid hardcoding key constants in multiple places (prefix/version).

TestCacheKey bakes in "ai:summary:" and assumes prefix + 64 length; if gateway/cache.go already has constants (e.g., cacheVersion, cachePrefix), reference them to reduce drift. If the intent is to lock the wire format, consider a single “spec” test and keep the others focused on behavior.


45-53: Consider also asserting model affects the key (collision prevention).

Right now uniqueness is only demonstrated for two texts with the same model; adding a quick same text + different model => different key check would better match the “text+model” requirement.

gateway/redis.go (1)

25-58: Add explicit Redis client timeouts to avoid long stalls in edge failure modes.

Ping(ctx) is bounded, but other Redis ops will use go-redis defaults; consider setting DialTimeout, ReadTimeout, WriteTimeout in redis.Options (especially important for degraded network conditions).

gateway/main.go (2)

116-118: Route wiring is OK, but “CACHE_ENABLED=true + Redis down” becomes “middleware installed but inert” — confirm that’s intentional.

Because initRedis() can nil out redisClient on ping failure while getCacheEnabled() still returns true, the route will still attach CacheMiddleware() (which immediately falls through when redisClient == nil). If you want clearer behavior/metrics, consider a derived flag like cacheReady := getCacheEnabled() && redisClient != nil.

Also applies to: 171-175


305-356: Verifier call should ideally avoid http.DefaultClient (transport timeouts), even with context.

You’re correctly using NewRequestWithContext + a verifier-specific deadline, but consider a dedicated http.Client with conservative transport timeouts (dial/TLS/response header) to reduce tail latency risk in pathological network scenarios.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff6e7c8 and 9e17e09.

📒 Files selected for processing (4)
  • gateway/cache.go
  • gateway/cache_test.go
  • gateway/main.go
  • gateway/redis.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • gateway/cache.go
🧰 Additional context used
🧬 Code graph analysis (1)
gateway/main.go (3)
gateway/cache.go (1)
  • CacheMiddleware (27-188)
src/services/payment.ts (1)
  • PaymentContext (8-14)
gateway/receipt.go (1)
  • GenerateReceipt (49-75)
🔇 Additional comments (3)
gateway/cache_test.go (1)

55-67: Good: spec-style test validates the exact SHA256 input composition.

This is a nice “contract” test for key construction (version/text/model) and should catch accidental changes.

gateway/redis.go (1)

60-70: getCacheEnabled() / getEnv() look fine.

Small and predictable parsing (true/1).

gateway/main.go (1)

213-303: Nice: avoids double-reading body by reusing request_body from middleware.

The handler’s “middleware already read body” flow (Lines 231-252) is a solid integration point with CacheMiddleware().

Comment thread gateway/main.go
Comment thread gateway/redis.go

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

3 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment thread gateway/cache.go Outdated
Comment thread gateway/cache.go Outdated
Comment thread README.md
- Make Redis parse errors consistent with ping failures (graceful degradation)
- Remove redundant timeout wrapping (verifyPayment handles its own timeout)
- Remove unused getBodyBytes() method (dead code)

All tests passing.
@AnkanMisra

AnkanMisra commented Jan 11, 2026 •

Copy link
Copy Markdown
Owner

@roshankumar0036singh
Never in my life i saw 135 convo for 1k LOC changes, you really misused greptile
greptile isnt for sale do not use it as many times as you want

the pr is now that big it is now making more heavy load on my github server that runs the ci pipelines

you clearly need to learn about ethics, ask someone before you use their tech or service

@AnkanMisra
AnkanMisra self-requested a review January 12, 2026 09:50
AnkanMisra added a commit that referenced this pull request Jan 12, 2026
Add Redis-backed response caching to reduce OpenRouter API costs and improve latency.

Key features:
- Cache-aside pattern with SHA256 content-addressable keys
- Payment verification on every cache hit (security)
- Configurable TTL via CACHE_TTL_SECONDS (default: 1 hour)
- Graceful degradation when Redis unavailable
- Race-safe implementation with sync.RWMutex

Closes #31
@AnkanMisra

Copy link
Copy Markdown
Owner

Merged manually via local squash merge. Commit: 84ad6da

@AnkanMisra AnkanMisra closed this Jan 12, 2026
@AnkanMisra AnkanMisra reopened this Jan 12, 2026

@AnkanMisra AnkanMisra left a comment •

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@roshankumar0036singh
⚠️ Merge Conflicts Detected

Please resolve conflicts in:

  • gateway/cache.go
  • gateway/redis.go

Run:

git fetch origin main
git merge origin/main
# Resolve conflicts
git push

Once conflicts are resolved, I'll re-approve.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
gateway/redis.go (1)

13-13: Race condition on global redisClient pointer.

The redisClient global is accessed from multiple goroutines—written in initRedis() and read in CacheMiddleware() and cache helper functions—without synchronization. This can cause data races if initRedis() is ever called while requests are in flight (e.g., during hot-reload or reinitialization scenarios).

Consider protecting access with a sync.RWMutex or using atomic.Pointer[redis.Client] (Go 1.19+).

♻️ Suggested fix using sync.RWMutex
+var (
+	redisClient   *redis.Client
+	redisClientMu sync.RWMutex
+)
-var redisClient *redis.Client

Then wrap reads with redisClientMu.RLock()/RUnlock() and writes with redisClientMu.Lock()/Unlock().

gateway/cache.go (3)

114-147: Inconsistent indentation in cache hit block.

Lines 115-147 have inconsistent indentation (some lines appear to have an extra tab/spaces). This doesn't affect functionality but hurts readability.

♻️ Fix indentation
 		// Cache HIT! -> Verify Payment *BEFORE* serving
-		// verifyPayment creates its own timeout context, so pass request context directly
-		verifyResp, paymentCtx, err := verifyPayment(c.Request.Context(), signature, nonce)
-			if err != nil {
+		// verifyPayment creates its own timeout context, so pass request context directly
+		verifyResp, paymentCtx, err := verifyPayment(c.Request.Context(), signature, nonce)
+		if err != nil {

(Apply similar fixes to the rest of the block)


257-269: Buffer write errors are discarded.

w.body.Write(data) and w.body.WriteString(s) errors are ignored. While bytes.Buffer never returns errors in practice (it panics on OOM), this pattern could mask issues if the implementation changes.

For consistency, you could log if these unexpectedly fail:

if _, bufErr := w.body.Write(data); bufErr != nil {
    log.Printf("[WARNING] Unexpected buffer write error: %v", bufErr)
}

250-255: Unused cacheKey field in cachedWriter.

The cacheKey field is set during construction (line 158) but never read. Consider removing it or using it for diagnostic logging.

♻️ Remove unused field
 type cachedWriter struct {
 	gin.ResponseWriter
 	body     *bytes.Buffer
-	cacheKey string
 	mu       sync.RWMutex
 }

And update the initialization on line 155-159 accordingly.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9e17e09 and 07b4854.

📒 Files selected for processing (2)
  • gateway/cache.go
  • gateway/redis.go
🧰 Additional context used
🧬 Code graph analysis (1)
gateway/cache.go (1)
gateway/main.go (1)
  • SummarizeRequest (51-53)
🔇 Additional comments (8)
gateway/redis.go (2)

15-61: LGTM: Graceful degradation pattern.

The initialization handles failures gracefully—logging warnings and disabling caching rather than crashing. Properly closes the client on ping failure before clearing the reference.


63-73: LGTM!

Helper functions are straightforward and correct. Using os.LookupEnv properly distinguishes between unset and empty values.

gateway/cache.go (6)

21-25: LGTM!

Clean struct definition with appropriate fields for caching. The CachedAt timestamp enables cache age visibility for debugging and monitoring.


46-81: LGTM: Robust body size enforcement.

Good defense-in-depth: checking Content-Length header first for early rejection, then using MaxBytesReader for chunked/unknown-length bodies. Proper error differentiation and body restoration for downstream handlers.


83-108: Consider supporting model field in request.

The cache key includes the model from OPENROUTER_MODEL env var, but if SummarizeRequest is extended to accept a model override in the request body, the cache key derivation here would need updating. The inline comment (lines 84-86) acknowledges this—just flagging for awareness.


164-183: LGTM: Async cache storage.

Good approach: captures response, only caches on success (200), and stores asynchronously with a bounded timeout using context.Background() (correctly avoiding the cancelled request context).


187-198: LGTM: Content-addressable cache key design.

Good practices: versioned prefix for invalidation, deterministic SHA256 hashing, and clear documentation about adding parameters in future.


200-240: LGTM!

Cache operations handle nil client gracefully and use caller-provided context for proper timeout propagation. Safe key prefix logging is a nice touch.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

3 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment thread gateway/cache.go
Comment thread gateway/cache_integration_test.go
Comment thread gateway/cache.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

level:advanced Large, risky, or cross-service work requiring strong project context. SWoC26

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement response caching layer with Redis backend

2 participants