Skip to content

Cache POST-based recommendation endpoints and document cache layer - #71

Merged
zeemscript merged 10 commits into
Deen-Bridge:devfrom
Tijesunimi004:feat/19-wire-up-redis-cache-layer
Jul 24, 2026
Merged

Cache POST-based recommendation endpoints and document cache layer#71
zeemscript merged 10 commits into
Deen-Bridge:devfrom
Tijesunimi004:feat/19-wire-up-redis-cache-layer

Conversation

@Tijesunimi004

@Tijesunimi004 Tijesunimi004 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Issue #19 asked for the Redis cache layer (cacheMiddleware, smartCache, invalidateCacheMiddleware) to be wired onto the read endpoints for books, courses, spaces, and search, with invalidation on writes. That work is already merged into main (the change that closed #2 covers books, courses, spaces, search, and users list/detail endpoints).

Two gaps remained:

  • POST /api/books/recommended and POST /api/courses/recommended take interests via the request body, so the existing GET-only cacheMiddleware can't cache them via route wiring. They were left uncached (courses even has a comment noting "POST routes not cached").
  • No documentation of which endpoints are cached and at what TTL, which the issue calls out as an acceptance criterion.

Changes

  • getRecommendedBooks (src/controllers/books/recommendedBooksController.js) and fetchRecommendedCourses (src/controllers/courses/courseController.js) now cache their results via getCacheOrSet, keyed by the sorted interests list, with the existing CACHE_TTL.BOOKS / CACHE_TTL.COURSES presets.
  • These new keys (books:recommended:*, courses:recommended:*) are already covered by the existing books:* / courses:* invalidation patterns on book/course writes, so no route changes were needed.
  • Added docs/caching.md documenting every cached endpoint, its TTL, the invalidation rules, and what's deliberately left uncached (per-user bookmarks, and reels - whose response embeds per-viewer like/love state and would leak across users under a shared cache key).

Test plan

  • node --check on both modified controllers
  • Full test suite run before/after to confirm no new failures (pre-existing failures in this sandbox are native bcrypt binding + crypto.getRandomValues polyfill issues unrelated to this change)
  • Manual smoke test against a live Redis instance to confirm cache hit/miss behavior on POST /api/books/recommended and POST /api/courses/recommended

Summary by CodeRabbit

  • Performance

    • Improved performance for recommended books and courses by caching results for the recommended POST endpoints.
    • Cache keys are deterministic for interest-based requests (with a consistent “popular/none” fallback).
    • Added graceful degradation so recommendation caching won’t impact service when the cache layer is unavailable.
  • Reliability

    • Improved reliability of Stellar Horizon interactions for account lookups, payment submission, and transaction verification.
  • Documentation

    • Updated caching documentation with endpoint coverage, TTL/keying rules, invalidation behavior, and routes intentionally left uncached.
    • Expanded environment configuration guidance for Redis and Horizon tuning options.

Closes #19

Banx17 and others added 8 commits July 21, 2026 17:07
- Apply cacheMiddleware to GET endpoints for books, courses, users,
  spaces, and search routes with appropriate TTLs
- Add cache invalidation middleware to POST, PUT, DELETE operations
  to ensure cache consistency when data changes
- Add Redis environment variables to validateEnv.js optional vars
- Create .env.example documenting all environment variables including
  Redis configuration options

Closes Deen-Bridge#2
feat: configure Redis cache layer for production use
…ests

test: add Stellar payment flow coverage
…nt-horizon-client

feat: resilient horizon client layer
The books/courses/spaces/search/users list and detail endpoints were
already wired to the Redis cache layer in a prior change. The two
POST /recommended endpoints (books and courses) were left uncached
since they take interests via the request body, which the shared
GET-only cacheMiddleware can't key off. Cache them explicitly in
their controllers, keyed by the sorted interests list, and document
every cached endpoint and TTL in docs/caching.md.
Copilot AI review requested due to automatic review settings July 24, 2026 08:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 192d5ade-54df-4596-b696-49dee411e257

📥 Commits

Reviewing files that changed from the base of the PR and between 8a841fe and 769e0c9.

📒 Files selected for processing (1)
  • test/stellarPaymentController.test.js

Walkthrough

Recommendation book and course endpoints now cache responses using normalized keys and configured TTLs. Redis and Horizon settings are documented and validated, Stellar operations use a shared client wrapper, and payment-controller transaction outcomes are covered by Jest tests.

Changes

Recommendation caching

Layer / File(s) Summary
Cache recommendation responses
src/controllers/books/recommendedBooksController.js, src/controllers/courses/courseController.js, docs/caching.md
Recommendation handlers use deterministic keys, domain-specific TTLs, and getCacheOrSet; documentation records coverage, invalidation, fallback behavior, and user-specific key requirements.
Configure Redis environment
.env.example, src/config/validateEnv.js
Environment examples and validation add optional Redis connection settings.

Stellar payment resilience

Layer / File(s) Summary
Configure Horizon resilience
src/config/validateEnv.js
Validation adds default Horizon URLs, timeouts, retry counts, and circuit-breaker settings.
Route Stellar operations through Horizon client
src/services/stellar/stellarService.js
Account loading, transaction submission, and verification use the shared Horizon client while preserving an exported server fallback.
Validate payment transaction outcomes
test/stellarPaymentController.test.js
Tests cover payment initialization, invalid XDR, failed verification, successful course enrollment, and transaction-session abort behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant RecommendationController
  participant getCacheOrSet
  participant Redis
  participant MongoDB
  Client->>RecommendationController: POST recommendation request
  RecommendationController->>getCacheOrSet: normalized cache key and TTL
  getCacheOrSet->>Redis: lookup cached payload
  alt cache hit
    Redis-->>getCacheOrSet: cached payload
  else cache miss
    getCacheOrSet->>MongoDB: query recommendations
    MongoDB-->>getCacheOrSet: recommendation results
    getCacheOrSet->>Redis: store payload with TTL
  end
  getCacheOrSet-->>RecommendationController: recommendation payload
  RecommendationController-->>Client: success response
Loading

Possibly related issues

  • Deen-Bridge/dnb-backend issue 19 — Covers Redis caching configuration, endpoint caching, invalidation, TTLs, and environment settings addressed by this change.

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR only adds caching for two recommendation endpoints and docs/env wiring, but it does not address stable Redis connectivity, broader endpoint coverage, or full invalidation. Implement the Redis connection/reconnect path, add caching for the remaining targeted endpoints, and wire invalidation into writes for cached resources.
Out of Scope Changes check ⚠️ Warning The Stellar/Horizon service refactor and new payment-controller test suite are unrelated to the Redis caching issue and appear out of scope. Move the Stellar/Horizon and payment-controller changes to a separate PR unless they are required for the caching work.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: caching POST recommendation endpoints and documenting the cache layer.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@zeemscript
zeemscript changed the base branch from main to dev July 24, 2026 08:12
@zeemscript

Copy link
Copy Markdown
Collaborator

@Tijesunimi004 please fix conflicts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
docs/caching.md (1)

41-43: 🚀 Performance & Scalability | 🔵 Trivial

Avoid Redis KEYS scans at production scale.

A wildcard KEYS scan can block Redis while traversing a large keyspace, turning writes into latency or availability incidents. Prefer SCAN with bounded deletion or explicit key tracking before production traffic grows.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/caching.md` around lines 41 - 43, Update invalidateCacheMiddleware and
its deleteCachePattern path to avoid Redis KEYS scans for wildcard cache
invalidation. Replace the unbounded lookup with bounded SCAN-based iteration and
deletion, or reuse explicit key tracking, while preserving invalidation of all
relevant create, update, delete, and review cache entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/controllers/books/recommendedBooksController.js`:
- Line 49: Standardize both recommendation response envelopes to `{ success,
message, data }`: in src/controllers/books/recommendedBooksController.js lines
49-49, map the cached result to data and provide a stable message; in
src/controllers/courses/courseController.js lines 258-258, place the recommended
courses under data and include message while preserving the success field.
- Around line 12-17: Use one shared canonicalization contract for interests:
validate and bound the input, normalize it once, encode the resulting list
without delimiter collisions, and reuse that normalized list in database filters
and cache keys. Apply this in
src/controllers/books/recommendedBooksController.js lines 12-17 and
src/controllers/courses/courseController.js lines 244-250, then document the
actual encoded or hashed representation in docs/caching.md lines 20-24.
- Around line 20-47: Add Jest coverage for the recommendation cache flows in
src/controllers/books/recommendedBooksController.js (lines 20-47) and
src/controllers/courses/courseController.js (lines 252-256), covering cache hits
and misses, Redis fallback, sorted-interest cache keys, popular-book fallback,
and empty-interest handling for courses. Use the controller methods and existing
cache helpers to verify each path without changing the recommendation behavior.

In `@src/controllers/courses/courseController.js`:
- Around line 252-255: Update the recommendation flow around getCacheOrSet and
Course.find to handle missing or empty interests before constructing the
category query. Return the defined no-interest response or reject the request in
that branch, and ensure the cache key reflects the same path instead of querying
with $in: undefined.

---

Nitpick comments:
In `@docs/caching.md`:
- Around line 41-43: Update invalidateCacheMiddleware and its deleteCachePattern
path to avoid Redis KEYS scans for wildcard cache invalidation. Replace the
unbounded lookup with bounded SCAN-based iteration and deletion, or reuse
explicit key tracking, while preserving invalidation of all relevant create,
update, delete, and review cache entries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3e33cd24-1d9c-46f7-8488-abdeba093933

📥 Commits

Reviewing files that changed from the base of the PR and between 9dabb73 and 2799ef6.

📒 Files selected for processing (3)
  • docs/caching.md
  • src/controllers/books/recommendedBooksController.js
  • src/controllers/courses/courseController.js

Comment thread src/controllers/books/recommendedBooksController.js
Comment thread src/controllers/books/recommendedBooksController.js
Comment thread src/controllers/books/recommendedBooksController.js
Comment thread src/controllers/courses/courseController.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
src/services/stellar/stellarService.js (2)

89-93: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Route every Horizon request through the resilient client.

These direct server calls always target client.endpoints[0] and bypass configured timeout, retry, failover, and circuit-breaker behavior.

  • src/services/stellar/stellarService.js#L89-L93: wrap strictReceivePaths(...).call() in client.execute(...).
  • src/services/stellar/stellarService.js#L122-L124: load the path-payment source account through client.execute(...).
  • src/services/stellar/stellarService.js#L317-L321: load preflight accounts through client.execute(...).
  • src/services/stellar/stellarService.js#L483-L485: load the reverse-payment source account through client.execute(...).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/stellar/stellarService.js` around lines 89 - 93, Route all four
Horizon requests through the resilient client: in
src/services/stellar/stellarService.js lines 89-93, wrap
strictReceivePaths(...).call() in client.execute(...); at lines 122-124,
317-321, and 483-485, wrap the respective source-account and preflight-account
loads in client.execute(...). Preserve each request’s existing operation and
result handling while removing direct server calls.

626-635: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Consume each matched operation during verification.

find() can return the same operation for multiple identical { destination, amount } expectations. A transaction containing one payment can therefore pass verification for two required payments and grant access despite underpayment.

Proposed fix
+    const remainingPaymentOps = [...paymentOps];
     for (const expected of expectedPayments) {
-      const match = paymentOps.find((op) => {
+      const matchIndex = remainingPaymentOps.findIndex((op) => {
         if (op.to !== expected.destination) return false;
 
         const opAmount =
           op.type === "path_payment_strict_receive"
             ? op.destination_amount
             : op.amount;
         return toStroops(opAmount) === toStroops(expected.amount);
       });
-      if (!match) {
+      if (matchIndex === -1) {
         return {
           verified: false,
           reason: `Missing expected USDC payment of ${expected.amount} to ${expected.destination}`,
         };
       }
+      remainingPaymentOps.splice(matchIndex, 1);
     }

As per path instructions, “payments are verified against Horizon before granting access.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/stellar/stellarService.js` around lines 626 - 635, Update the
payment verification loop over expectedPayments so each matched operation is
consumed and cannot satisfy another expectation. Replace the reusable lookup
behavior in the paymentOps matching logic with tracking or removal of the
matched operation, while preserving destination and stroop-amount comparisons
before granting access.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.env.example:
- Around line 29-32: Remove the duplicate EMAILJS_PRIVATE_KEY,
EMAILJS_PUBLIC_KEY, EMAILJS_SERVICE_ID, and EMAILJS_TEMPLATE_ID declarations
from this block in .env.example, preserving the single existing declaration for
each key elsewhere in the file.

---

Outside diff comments:
In `@src/services/stellar/stellarService.js`:
- Around line 89-93: Route all four Horizon requests through the resilient
client: in src/services/stellar/stellarService.js lines 89-93, wrap
strictReceivePaths(...).call() in client.execute(...); at lines 122-124,
317-321, and 483-485, wrap the respective source-account and preflight-account
loads in client.execute(...). Preserve each request’s existing operation and
result handling while removing direct server calls.
- Around line 626-635: Update the payment verification loop over
expectedPayments so each matched operation is consumed and cannot satisfy
another expectation. Replace the reusable lookup behavior in the paymentOps
matching logic with tracking or removal of the matched operation, while
preserving destination and stroop-amount comparisons before granting access.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fb68dbff-3b74-4577-8d73-759e092c4e9a

📥 Commits

Reviewing files that changed from the base of the PR and between 2799ef6 and 8a841fe.

📒 Files selected for processing (3)
  • .env.example
  • src/config/validateEnv.js
  • src/services/stellar/stellarService.js

Comment thread .env.example
Comment on lines 29 to 32
EMAILJS_PRIVATE_KEY=your_emailjs_private_key
EMAILJS_PUBLIC_KEY=your_emailjs_public_key
EMAILJS_SERVICE_ID=your_emailjs_service_id
EMAILJS_TEMPLATE_ID=your_emailjs_template_id

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the duplicate EmailJS declarations.

These keys appear elsewhere in the file, so dotenv will use the last occurrence and make edits to the earlier block ineffective. Keep one declaration for each key.

🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 29-29: [DuplicatedKey] The EMAILJS_PRIVATE_KEY key is duplicated

(DuplicatedKey)


[warning] 30-30: [DuplicatedKey] The EMAILJS_PUBLIC_KEY key is duplicated

(DuplicatedKey)


[warning] 31-31: [DuplicatedKey] The EMAILJS_SERVICE_ID key is duplicated

(DuplicatedKey)


[warning] 32-32: [DuplicatedKey] The EMAILJS_TEMPLATE_ID key is duplicated

(DuplicatedKey)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.env.example around lines 29 - 32, Remove the duplicate EMAILJS_PRIVATE_KEY,
EMAILJS_PUBLIC_KEY, EMAILJS_SERVICE_ID, and EMAILJS_TEMPLATE_ID declarations
from this block in .env.example, preserving the single existing declaration for
each key elsewhere in the file.

Source: Linters/SAST tools

@Tijesunimi004
Tijesunimi004 changed the base branch from dev to main July 24, 2026 08:32
@Tijesunimi004
Tijesunimi004 changed the base branch from main to dev July 24, 2026 08:36
The stellarService.js mock in this suite only listed the handful of
exports the original paymentController.js used. Since then
paymentController.js grew (path payments, pre-flight checks,
platform-collect settlement) and other test files
(preflightPayment.test.js, pathPayments.test.js) started importing
the real, unmocked stellarService.js. When both run in the same Jest
worker, the incomplete mock trips a SyntaxError from Jest's ESM
module linker ("does not provide an export named 'USDC'") for
exports this file never even references.

Mirror every named export of stellarService.js in the mock, and add
mocks for preflightPayment/calculateFeeSplit (now called by
initializePayment) and the job queue's enqueue (now called by
submitPayment for receipt generation), which paymentController.js
picked up in the meantime.
@zeemscript
zeemscript merged commit 65229da into Deen-Bridge:dev Jul 24, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Enhancement] Redis cache layer is not properly configured for production use

6 participants