Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 64 additions & 11 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,20 +1,56 @@
CLOUDINARY_API_KEY=your_cloudinary_api_key
CLOUDINARY_API_SECRET=your_cloudinary_api_secret
CLOUDINARY_CLOUD_NAME=your_cloudinary_cloud_name
CLOUDINARY_URL=your_cloudinary_url
JWT_SECRET=your_jwt_secret
MONGO_URI="your_mongodb_uri"
PORT=5000
# DeenBridge Backend Environment Variables

# MongoDB connection string
MONGO_URI=mongodb+srv://user:password@cluster.mongodb.net/dnb-backend?retryWrites=true&w=majority

# JWT secret for authentication (use a strong random string, min 32 characters)
JWT_SECRET=your_jwt_secret_here

# Node environment (development, production, test)
NODE_ENV=development

# Server port
PORT=5000

# Cloudinary configuration for file uploads
CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret
CLOUDINARY_URL=cloudinary://api_key:api_secret@cloud_name

# EmailJS configuration for sending emails
EMAILJS_API_URL=https://api.emailjs.com/api/v1.0/email/send
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
EMAILJS_PRIVATE_KEY=your_private_key
EMAILJS_PUBLIC_KEY=your_public_key
EMAILJS_SERVICE_ID=your_service_id
EMAILJS_TEMPLATE_ID=your_template_id
EMAILJS_RECEIPT_TEMPLATE_ID=your_emailjs_receipt_template_id

# Stellar blockchain network (testnet or mainnet)
STELLAR_NETWORK=testnet

# Resilient Horizon Client Configuration (Optional)
# HORIZON_URLS=https://horizon-testnet.stellar.org,https://horizon-testnet.stellar.org (Comma-separated list of Horizon endpoints)
# HORIZON_TIMEOUT_MS=10000 (Request timeout in milliseconds)
# HORIZON_MAX_RETRIES=3 (Maximum number of retries for transient errors)
# HORIZON_CB_THRESHOLD=5 (Number of consecutive failures before opening the circuit breaker)
# HORIZON_CB_COOLDOWN_MS=30000 (Time to wait in ms before attempting a half-open probe)

# SEP-1 discovery metadata (optional)
STELLAR_PLATFORM_PUBLIC_KEY=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
ORG_NAME=DeenBridge
ORG_URL=https://deenbridge.com
ORG_DESCRIPTION="A platform for Islamic education and Stellar-based creator payments."
ORG_LOGO=https://deenbridge.com/logo.png
ORG_GITHUB=Deen-Bridge
ORG_TELEGRAM_URL=https://t.me/+nst9lXNj1wc4ZDE0
# Set these when SEP-10 and SEP-24 are enabled
SIGNING_KEY=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
WEB_AUTH_ENDPOINT=https://api.deenbridge.com/auth
TRANSFER_SERVER_SEP0024=https://api.deenbridge.com/sep24
# Stellar donation fund (public key only - the secret key must NEVER be stored here)
DONATION_WALLET_PUBLIC_KEY=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

# Platform fee split on purchases (0-20, 0 disables the split)
PLATFORM_FEE_PERCENT=0
PLATFORM_WALLET_PUBLIC_KEY=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Expand All @@ -28,3 +64,20 @@ JOBS_ENABLED=true
QUEUE_DRIVER=mongo
JOBS_DASHBOARD_TOKEN=replace_with_a_long_random_token

# Redis Configuration (optional - app works without Redis but with reduced performance)
# Option 1: Use REDIS_URL for full connection string (recommended for cloud services)
# REDIS_URL=redis://username:password@host:port

# Option 2: Use separate credentials
REDIS_HOST=localhost
REDIS_PORT=6379
# REDIS_USERNAME=default
# REDIS_PASSWORD=your_password

# Jitsi configuration for video calls (optional)
# JITSI_MEET_DOMAIN=your_jitsi_domain
# JITSI_APP_ID=your_app_id
# JITSI_PRIVATE_KEY=your_private_key
# JITSI_PUBLIC_KEY_ID=your_public_key_id
# JITSI_KID=your_kid
# JITSI_TENANT=your_tenant
15 changes: 15 additions & 0 deletions QUICK_START.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@ NODE_ENV=development
PORT=5000
```

Optional Stellar discovery settings:

```env
STELLAR_NETWORK=testnet
STELLAR_PLATFORM_PUBLIC_KEY=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
ORG_URL=https://deenbridge.com
ORG_LOGO=https://deenbridge.com/logo.png
SIGNING_KEY=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
WEB_AUTH_ENDPOINT=https://api.deenbridge.com/auth
TRANSFER_SERVER_SEP0024=https://api.deenbridge.com/sep24
```

Unset optional values are left out of `GET /.well-known/stellar.toml`. The
endpoint remains available and returns the configured network and USDC metadata.

### **3. Start Server**

```bash
Expand Down
4 changes: 4 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import stellarPaymentRoutes from "./src/routes/stellar/paymentRoutes.js";
import stellarDonationRoutes from "./src/routes/stellar/donationRoutes.js";
import payoutRoutes from "./src/routes/payoutRoutes.js";
import jobsRoutes from "./src/routes/jobsRoutes.js";
import { getStellarToml } from "./src/routes/wellKnownRoutes.js";

handleUncaughtException();
validateEnv();
Expand Down Expand Up @@ -105,6 +106,9 @@ app.get("/metrics", metricsMiddleware);
app.use(helmetMiddleware);
app.use(customSecurityHeaders);

// SEP-1 discovery must allow requests from wallets on any origin.
app.get("/.well-known/stellar.toml", getStellarToml);

const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = [
Expand Down
8 changes: 8 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"xss-clean": "^0.1.4"
},
"devDependencies": {
"@iarna/toml": "^2.2.5",
"concurrently": "^9.1.2",
"jest": "^29.7.0",
"mongodb-memory-server": "^11.2.0",
Expand Down
34 changes: 34 additions & 0 deletions src/config/validateEnv.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ const optionalEnvVars = [
"JITSI_KID",
"JITSI_TENANT",
"STELLAR_NETWORK",
"STELLAR_PLATFORM_PUBLIC_KEY",
"ORG_NAME",
"ORG_URL",
"ORG_DESCRIPTION",
"ORG_LOGO",
"ORG_GITHUB",
"ORG_TELEGRAM_URL",
"SIGNING_KEY",
"WEB_AUTH_ENDPOINT",
"TRANSFER_SERVER_SEP0024",
"DONATION_WALLET_PUBLIC_KEY",
"PLATFORM_FEE_PERCENT",
"PLATFORM_WALLET_PUBLIC_KEY",
Expand All @@ -28,13 +38,37 @@ const optionalEnvVars = [
"JOBS_ENABLED",
"JOBS_DASHBOARD_TOKEN",
"EMAILJS_RECEIPT_TEMPLATE_ID",
// Redis configuration (optional - app works without Redis)
"REDIS_URL",
"REDIS_HOST",
"REDIS_PORT",
"REDIS_USERNAME",
"REDIS_PASSWORD",
"HORIZON_URLS",
"HORIZON_TIMEOUT_MS",
"HORIZON_MAX_RETRIES",
"HORIZON_CB_THRESHOLD",
"HORIZON_CB_COOLDOWN_MS",
];

export const validateEnv = () => {
// Default values for TTLs if not provided
process.env.ACCESS_TOKEN_TTL = process.env.ACCESS_TOKEN_TTL || "15m";
process.env.REFRESH_TOKEN_TTL = process.env.REFRESH_TOKEN_TTL || "30d";

// Default values for Horizon resilient client if not provided
const network = process.env.STELLAR_NETWORK || "testnet";
if (!process.env.HORIZON_URLS) {
process.env.HORIZON_URLS =
network === "mainnet"
? "https://horizon.stellar.org"
: "https://horizon-testnet.stellar.org";
Comment on lines +60 to +65

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject unsupported STELLAR_NETWORK values.

The current ternary treats every value other than exactly mainnet—including typos—as testnet. Since this setting controls the Horizon endpoint and Stellar discovery network, fail validation for values other than testnet or mainnet instead of silently selecting the wrong network.

As per path instructions, STELLAR_NETWORK must be configured as testnet or mainnet.

🤖 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/config/validateEnv.js` around lines 60 - 65, Update the STELLAR_NETWORK
validation in validateEnv so only “testnet” and “mainnet” are accepted; reject
any other configured value before deriving HORIZON_URLS. Preserve the existing
endpoint selection for valid values and ensure the required configuration error
identifies the invalid network.

Source: Path instructions

Comment on lines +64 to +65

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 | 🟠 Major | ⚡ Quick win

Keep Horizon connection strings in environment-backed configuration.

These URLs are hardcoded in JavaScript even though HORIZON_URLS is already documented in .env.example. Move endpoint defaults into the environment/configuration layer or require HORIZON_URLS during startup.

As per path instructions: “Flag hardcoded secrets, connection strings, JWT secrets, or wallet keys; all configuration belongs in environment variables documented in .env.example.”

🤖 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/config/validateEnv.js` around lines 64 - 65, Update the Horizon endpoint
selection in validateEnv so it no longer hardcodes Stellar URLs in JavaScript.
Read the endpoint values from the documented HORIZON_URLS
environment/configuration setting, or require that setting during startup, while
preserving the existing network-specific selection behavior.

Source: Path instructions

}
process.env.HORIZON_TIMEOUT_MS = process.env.HORIZON_TIMEOUT_MS || "10000";
process.env.HORIZON_MAX_RETRIES = process.env.HORIZON_MAX_RETRIES || "3";
process.env.HORIZON_CB_THRESHOLD = process.env.HORIZON_CB_THRESHOLD || "5";
process.env.HORIZON_CB_COOLDOWN_MS = process.env.HORIZON_CB_COOLDOWN_MS || "30000";
Comment on lines +59 to +70

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Files ==\n'
git ls-files 'src/config/validateEnv.js' 'src/**/horizonClient.js' 'src/**/stellar*.js' 'src/**/stellar*.mjs' 'src/**/stellar*.ts' 'src/**/stellar*.cjs' | sed 's#^`#-` #'

printf '\n== validateEnv.js outline ==\n'
ast-grep outline src/config/validateEnv.js --view expanded || true

printf '\n== horizonClient candidates ==\n'
fd -a 'horizonClient\.(js|mjs|cjs|ts)$' src || true

printf '\n== search for imports/usages ==\n'
rg -n --hidden --glob 'src/**/*.js' 'horizonClient|validateEnv\(' src || true

printf '\n== src/config/validateEnv.js (selected lines) ==\n'
nl -ba src/config/validateEnv.js | sed -n '1,180p'

Repository: Deen-Bridge/dnb-backend

Length of output: 1509


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== src/config/validateEnv.js ==\n'
sed -n '1,220p' src/config/validateEnv.js

printf '\n== src/services/stellar/horizonClient.js ==\n'
sed -n '1,240p' src/services/stellar/horizonClient.js

printf '\n== app/bootstrap imports ==\n'
rg -n --hidden --glob 'src/**/*.js' 'validateEnv\(\)|from "./config/validateEnv\.js"|from ".*/horizonClient\.js"|from "./services/stellar/stellarService\.js"|from "./services/stellar/horizonClient\.js"' src

Repository: Deen-Bridge/dnb-backend

Length of output: 10279


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== root js files ==\n'
git ls-files '*.js' | sed -n '1,200p'

printf '\n== startup-related imports ==\n'
rg -n --hidden --glob 'src/**/*.js' --glob '*.js' \
  'validateEnv\(\)|from "\./config/validateEnv\.js"|from "\./src/config/validateEnv\.js"|from ".*/stellarService\.js"|from ".*/horizonClient\.js"|from "\./services/stellar/stellarService\.js"|from "\./services/stellar/horizonClient\.js"' \
  . || true

printf '\n== likely entrypoints ==\n'
fd -a '^(app|server|index|main)\.js$' . || true

printf '\n== package scripts ==\n'
sed -n '1,220p' package.json 2>/dev/null || true

Repository: Deen-Bridge/dnb-backend

Length of output: 6450


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== app.js ==\n'
sed -n '1,220p' app.js

printf '\n== server.js ==\n'
sed -n '1,220p' server.js

printf '\n== direct stellar imports from app boot path ==\n'
rg -n --hidden --glob 'app.js' --glob 'server.js' --glob 'src/**/*.js' \
  'stellarService\.js|horizonClient\.js|validateEnv\.js|routes/stellar|controllers/stellar' \
  app.js server.js src || true

Repository: Deen-Bridge/dnb-backend

Length of output: 9633


Move env defaults before the Stellar client is instantiated

app.js pulls in src/routes/stellar/* before validateEnv() runs, and that import chain loads src/services/stellar/horizonClient.js, which creates the exported singleton from process.env during module evaluation. The Horizon client will keep the pre-default URL/timeout values, so mainnet routing and retry settings can be ignored. Call validateEnv() before any Stellar imports, or make the client lazy/injected.

🤖 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/config/validateEnv.js` around lines 59 - 70, Ensure validateEnv() runs
before app.js loads any src/routes/stellar modules, preventing horizonClient’s
exported singleton from reading unset environment variables during module
evaluation. Reorder initialization so the defaults for HORIZON_URLS and Horizon
settings are applied before Stellar imports, or convert the client
initialization to lazy/injected creation while preserving existing configuration
behavior.

Comment on lines +67 to +70

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate Horizon tuning values before passing them through.

Malformed values survive these defaults and are later parsed with parseInt(). NaN can cause the retry loop to skip entirely, disable circuit-breaker thresholds, or make timeouts fire immediately. Validate finite integer ranges and fail startup with a clear configuration error.

🤖 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/config/validateEnv.js` around lines 67 - 70, Validate the Horizon tuning
environment variables in the configuration validation flow before assigning or
consuming them: HORIZON_TIMEOUT_MS, HORIZON_MAX_RETRIES, HORIZON_CB_THRESHOLD,
and HORIZON_CB_COOLDOWN_MS must be finite integers within appropriate
non-negative ranges. Reject malformed or out-of-range values with a clear
startup configuration error instead of allowing parseInt() to produce NaN or
unsafe settings.


const missing = [];

requiredEnvVars.forEach((envVar) => {
Expand Down
57 changes: 57 additions & 0 deletions src/config/validateEnv.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { jest } from "@jest/globals";
import { validateEnv } from "./validateEnv.js";

describe("validateEnv", () => {
const originalEnv = process.env;

beforeEach(() => {
jest.resetModules();
process.env = {
...originalEnv,
MONGO_URI: "mongodb://localhost:27017/test",
JWT_SECRET: "test-secret-key-for-ci-minimum-32-chars",
Comment on lines +11 to +12

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Move test credentials and connection strings out of JavaScript.

These lines embed MONGO_URI and JWT_SECRET literals. Even though they are test values, this path requires connection strings and JWT secrets to come from documented environment configuration; load them through Jest setup or the test environment instead.

🤖 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/config/validateEnv.test.js` around lines 11 - 12, Update the test setup
around the MONGO_URI and JWT_SECRET fixtures in validateEnv tests to read these
values from documented environment configuration or Jest setup instead of
embedding literals in the JavaScript. Ensure the validation tests continue using
the configured credentials and connection string, with suitable test-environment
defaults handled outside the test source.

Source: Path instructions

NODE_ENV: "test",
PORT: "5000",
};
// Ensure new vars are unset
delete process.env.HORIZON_URLS;
delete process.env.HORIZON_TIMEOUT_MS;
delete process.env.HORIZON_MAX_RETRIES;
delete process.env.HORIZON_CB_THRESHOLD;
delete process.env.HORIZON_CB_COOLDOWN_MS;
});

afterAll(() => {
process.env = originalEnv;
});

it("should derive testnet default endpoint when STELLAR_NETWORK is unset or testnet", () => {
delete process.env.STELLAR_NETWORK;
validateEnv();
expect(process.env.HORIZON_URLS).toBe("https://horizon-testnet.stellar.org");
expect(process.env.HORIZON_TIMEOUT_MS).toBe("10000");
expect(process.env.HORIZON_MAX_RETRIES).toBe("3");
expect(process.env.HORIZON_CB_THRESHOLD).toBe("5");
expect(process.env.HORIZON_CB_COOLDOWN_MS).toBe("30000");
});

it("should derive mainnet default endpoint when STELLAR_NETWORK is mainnet", () => {
process.env.STELLAR_NETWORK = "mainnet";
validateEnv();
expect(process.env.HORIZON_URLS).toBe("https://horizon.stellar.org");
});

it("should preserve explicitly set Horizon values", () => {
process.env.HORIZON_URLS = "https://custom.stellar.org";
process.env.HORIZON_TIMEOUT_MS = "5000";
process.env.HORIZON_MAX_RETRIES = "1";
process.env.HORIZON_CB_THRESHOLD = "10";
process.env.HORIZON_CB_COOLDOWN_MS = "10000";
validateEnv();
expect(process.env.HORIZON_URLS).toBe("https://custom.stellar.org");
expect(process.env.HORIZON_TIMEOUT_MS).toBe("5000");
expect(process.env.HORIZON_MAX_RETRIES).toBe("1");
expect(process.env.HORIZON_CB_THRESHOLD).toBe("10");
expect(process.env.HORIZON_CB_COOLDOWN_MS).toBe("10000");
});
});
13 changes: 13 additions & 0 deletions src/middlewares/errorHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ export const errorHandler = (err, req, res, next) => {
err.status = err.status || "error";

if (process.env.NODE_ENV === "development") {
if (err.name === "AllEndpointsOpenError") {
return res.status(503).json({
error: "Stellar network currently unreachable. Please try again later.",
code: "NETWORK_UNAVAILABLE"
});
}
sendErrorDev(err, req, res);
} else {
let error = { ...err };
Expand All @@ -89,6 +95,13 @@ export const errorHandler = (err, req, res, next) => {
if (err.name === "ValidationError") error = handleValidationErrorDB(err);
if (err.name === "JsonWebTokenError") error = handleJWTError();
if (err.name === "TokenExpiredError") error = handleJWTExpiredError();

if (err.name === "AllEndpointsOpenError") {
return res.status(503).json({
error: "Stellar network currently unreachable. Please try again later.",
code: "NETWORK_UNAVAILABLE"
});
}

sendErrorProd(error, req, res);
}
Expand Down
59 changes: 46 additions & 13 deletions src/routes/books/bookRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,76 @@ import {
removeBookBookmark,
} from "../../controllers/books/bookmarkBookController.js";
import { protect } from "../../middlewares/authMiddleware.js";
import {
cacheMiddleware,
invalidateCacheMiddleware,
} from "../../middlewares/cache.js";
import { CACHE_TTL, CACHE_KEYS } from "../../utils/cache.js";

const router = express.Router();

// creating book
// Cache key generators
const booksListCacheKey = () => `${CACHE_KEYS.BOOKS}list`;
const bookDetailCacheKey = (req) => `${CACHE_KEYS.BOOK}${req.params.id}`;
const booksByAuthorCacheKey = (req) =>
`${CACHE_KEYS.BOOKS}author:${req.params.authorId}`;

// creating book - invalidates books list cache
router.post(
"/",
protect,
upload.fields([
{ name: "thumbnail", maxCount: 1 },
{ name: "file", maxCount: 1 },
]),
invalidateCacheMiddleware([`${CACHE_KEYS.BOOKS}*`]),
createBook
);
Comment on lines 35 to 44

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Invalidate personalized recommendations when their source content changes. GET /users/recommendations caches books and courses per user, but these mutations only clear content namespaces. Users can receive removed or outdated recommendations for ten minutes.

  • src/routes/books/bookRoutes.js#L35-L44: clear ${CACHE_KEYS.USER}*:recommendations after book creation.
  • src/routes/books/bookRoutes.js#L77-L82: clear ${CACHE_KEYS.USER}*:recommendations after book deletion.
  • src/routes/courses/courseRoutes.js#L60-L65: clear ${CACHE_KEYS.USER}*:recommendations after course creation.
  • src/routes/courses/courseRoutes.js#L78-L83: clear ${CACHE_KEYS.USER}*:recommendations after course updates.
📍 Affects 2 files
  • src/routes/books/bookRoutes.js#L35-L44 (this comment)
  • src/routes/books/bookRoutes.js#L77-L82
  • src/routes/courses/courseRoutes.js#L60-L65
  • src/routes/courses/courseRoutes.js#L78-L83
🤖 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/routes/books/bookRoutes.js` around lines 35 - 44, Update the
invalidateCacheMiddleware calls for book creation at
src/routes/books/bookRoutes.js:35-44, book deletion at
src/routes/books/bookRoutes.js:77-82, course creation at
src/routes/courses/courseRoutes.js:60-65, and course updates at
src/routes/courses/courseRoutes.js:78-83 to also clear the
`${CACHE_KEYS.USER}*:recommendations` namespace, while preserving each route’s
existing content-cache invalidation.

// getting all books
router.get("/", getBooks);

// get recommended books for user
router.get("/recom", fetchRecommendedBooks);
// getting all books - cached for 15 minutes
router.get("/", cacheMiddleware(CACHE_TTL.BOOKS, booksListCacheKey), getBooks);

// get recommended books for user - cached for 5 minutes
router.get(
"/recom",
cacheMiddleware(CACHE_TTL.SHORT, () => `${CACHE_KEYS.BOOKS}recommended`),
fetchRecommendedBooks
);

// Bookmarks (must come before dynamic :id routes)
router.get("/bookmarks", protect, getBookmarkedBooks);
router.post("/:bookId/bookmark", protect, toggleBookBookmark);
router.get("/:bookId/bookmark/check", protect, checkIfBookBookmarked);
router.delete("/:bookId/bookmark", protect, removeBookBookmark);

//get books created by the author
router.get("/by-author/:authorId", getBooksByAuthor);
// get books created by the author - cached for 15 minutes
router.get(
"/by-author/:authorId",
cacheMiddleware(CACHE_TTL.BOOKS, booksByAuthorCacheKey),
getBooksByAuthor
);

//get a spefic book
// get a specific book - cached for 15 minutes
router.get("/:id/preview", protect, streamBookPreview);
router.get("/:id", getBook);
router.get(
"/:id",
cacheMiddleware(CACHE_TTL.BOOKS, bookDetailCacheKey),
getBook
);

// delete a book
router.delete("/:id", deleteBook);
// delete a book - invalidates book caches
router.delete(
"/:id",
invalidateCacheMiddleware([`${CACHE_KEYS.BOOKS}*`, `${CACHE_KEYS.BOOK}*`]),
deleteBook
);
Comment on lines +78 to +82

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Require authentication before deleting a book.

DELETE /:id currently reaches deleteBook without protect; any unauthenticated caller can delete an arbitrary book by ID. Add protect before cache invalidation.

Proposed fix
 router.delete(
   "/:id",
+  protect,
   invalidateCacheMiddleware([`${CACHE_KEYS.BOOKS}*`, `${CACHE_KEYS.BOOK}*`]),
   deleteBook
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
router.delete(
"/:id",
invalidateCacheMiddleware([`${CACHE_KEYS.BOOKS}*`, `${CACHE_KEYS.BOOK}*`]),
deleteBook
);
router.delete(
"/:id",
protect,
invalidateCacheMiddleware([`${CACHE_KEYS.BOOKS}*`, `${CACHE_KEYS.BOOK}*`]),
deleteBook
);
🤖 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/routes/books/bookRoutes.js` around lines 78 - 82, Update the DELETE
"/:id" route registration to include the existing protect middleware before
invalidateCacheMiddleware and deleteBook, ensuring unauthenticated requests are
rejected before cache invalidation or deletion.

Source: Path instructions


//review a book
router.post("/:id/reviews", protect, addBookReview);
// review a book - invalidates specific book cache
router.post(
"/:id/reviews",
protect,
invalidateCacheMiddleware([`${CACHE_KEYS.BOOK}*`]),
addBookReview
);
Comment on lines +84 to +90

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Invalidate every cached view affected by the mutation. These handlers clear only a subset of cached representations, leaving list, profile, or count responses stale until their TTL expires.

  • src/routes/books/bookRoutes.js#L84-L90: also invalidate ${CACHE_KEYS.BOOKS}* after a review changes a book’s rating/reviews.
  • src/routes/courses/courseRoutes.js#L66-L77: also invalidate ${CACHE_KEYS.COURSES}* after enrollment or review changes.
  • src/routes/spaceRoutes.js#L58-L64: also invalidate ${CACHE_KEYS.SPACES}* after waitlist changes.
  • src/routes/userRoutes.js#L70-L82: invalidate base user and :followers:count/:following:count keys too; clearing ${CACHE_KEYS.USER}* is the simplest correct option for these relationship mutations.
📍 Affects 4 files
  • src/routes/books/bookRoutes.js#L84-L90 (this comment)
  • src/routes/courses/courseRoutes.js#L66-L77
  • src/routes/spaceRoutes.js#L58-L64
  • src/routes/userRoutes.js#L70-L82


export default router;
Loading