Skip to content

Feat/64 full text search - #74

Merged
zeemscript merged 9 commits into
Deen-Bridge:devfrom
Kaycee276:feat/64-full-text-search
Jul 25, 2026
Merged

Feat/64 full text search#74
zeemscript merged 9 commits into
Deen-Bridge:devfrom
Kaycee276:feat/64-full-text-search

Conversation

@Kaycee276

@Kaycee276 Kaycee276 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Title

feat(search): implement full-text search API with text indexes, ranking, and pagination

Context

This addresses the unoptimized and unpaginated search implementation in searchAll which relied on leading wildcard regexes, causing full-collection scans without index utilization. The new implementation establishes appropriate MongoDB text indexes across our core models, solving performance bottlenecks while also introducing missing feature sets including filtering, relevance ranking, and paginated blocks.

Implementation Details

  • Created proper MongoDB text indexes on Course (title^5, description, category), Book (title^5, description, category), Space (title, description, category), User (name, bio, interests), and Reel (description).
  • Added searchService.js to manage multi-collection querying using $text and projecting { score: { $meta: "textScore" } } for accurate relevance sort ordering.
  • Implemented robust multi-type pagination alongside deep filter parameters: minPrice, maxPrice, free, category, and minRating.
  • Refactored searchController.js to strip away bare array returning patterns, standardizing responses with { success, results, pagination }.
  • Configured a dedicated type=educators (and /api/search/educators) search tailored for discovering tutors via names, bios, or interests, while strictly stripping private user fields from payloads.
  • Appended filtering query variables onto searchRoutes.js cache generation mechanisms to securely leverage route caching.

Verification

  • Seeded diverse, dummy collections through an internal test suite mimicking multiple document permutations.
  • Validated text relevance sorts correctly align highest weighted textual collisions at the top of the dataset.
  • Verified filtering accurately whittles out-of-band documents, including specific assertions on free parameters.
  • Validated that password, email, and other sensitive parameters are forcibly stripped when matching educators.
  • Executed npm test -- test/search.test.js completely passing without CI integration problems.

closes #64

Summary by CodeRabbit

  • New Features
    • Added full-text search across courses, books, spaces, reels, and educators, with pagination, relevance-based sorting, and advanced filters.
    • Added a dedicated educator search endpoint with cached results.
  • Bug Fixes
    • Standardized API JSON response shapes for upload signature, avatar update validation, and book creation errors/success payloads.
    • Hardened upload validation to reject invalid files with consistent 400 errors.
  • Tests
    • Expanded integration coverage for search (including educator visibility), upload signatures, and updated book upload/response assertions.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Kaycee276, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 23 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 089ae3be-3c58-4847-afe7-96f8c44e99b2

📥 Commits

Reviewing files that changed from the base of the PR and between df10742 and f6db82f.

📒 Files selected for processing (5)
  • .github/workflows/ci.yml
  • .gitignore
  • src/controllers/uploadController.js
  • test/jest.setup.js
  • test/upload.test.js

Walkthrough

The PR standardizes upload validation and response payloads, adds avatar-update authorization, and introduces indexed, paginated search for collections and educators with filtering, relevance sorting, caching, and integration tests.

Changes

Upload hardening

Layer / File(s) Summary
Upload intake and validation
src/middlewares/upload.js, src/utils/fileValidation.js, src/routes/userRoutes.js, src/controllers/userController.js, src/middlewares/errorHandler.js
Dedicated Multer configurations, magic-byte validation, normalized errors, and avatar ownership checks are applied to upload flows.
Book upload persistence and response contract
src/models/Book.js, src/controllers/books/bookController.js, test/bookUpload.test.js
Book records store filePublicId; creation and validation responses use nested data fields, with upload assertions updated accordingly.
Authenticated upload signature
src/controllers/uploadController.js, test/upload.test.js, jest.config.js, test/jest.setup.js
Signature responses include a success message and nested Cloudinary data, with authenticated, unauthenticated, and secret-redaction coverage.

Full-text search

Layer / File(s) Summary
Indexed search service
src/models/{Book,Course,Reel,Space,User}.js, src/services/search/searchService.js
MongoDB text indexes and shared service logic provide short-query matching, text relevance, filters, sorting, pagination, and tutor-only projections.
Search API and cache integration
src/controllers/searchController.js, src/routes/searchRoutes.js
Search requests delegate to the service layer, enforce query-length limits, expose educator search, and vary cache keys by pagination and filters.
Search behavior validation
test/search.test.js
Integration tests seed indexed collections and verify relevance ordering, pagination, free and category filters, and public educator fields.

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

Possibly related issues

  • Deen-Bridge/dnb-backend#67 — Covers the media-upload hardening, magic-byte validation, Multer constraints, and related upload tests included here.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes substantial upload, user-auth, error-handling, and file-validation changes unrelated to the search issue. Move the upload, auth, and validation work into a separate PR so this one stays focused on search and educator endpoints.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: a full-text search feature.
Linked Issues check ✅ Passed The PR adds weighted text indexes, relevance-ranked paginated search, filters, educator search, and public-field projection as requested.
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.

@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: 13

🧹 Nitpick comments (2)
test/search.test.js (1)

59-100: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the short-query (<3 char) path and cache isolation between endpoints.

Every q used here is ≥3 characters, so the short-query regex fallback (currently broken, see searchService.js) and the /search vs /educators cache-key collision (see searchRoutes.js) both ship untested. Once those are fixed, adding a q=Re (2-char) test and a same-params request against both /api/search and /api/search/educators would lock in the fixes.

Happy to draft these test cases if useful.

🤖 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 `@test/search.test.js` around lines 59 - 100, Extend the “Full-text search API”
tests to cover a two-character query such as q=Re, asserting the short-query
search returns a successful, relevant response. Add a cache-isolation test that
sends identical query parameters to /api/search and /api/search/educators, then
verifies each response retains its endpoint-specific result shape and data.
src/models/Course.js (1)

51-52: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Filter fields used by the new search service lack supporting indexes.

The search service filters courses/books by category and price, spaces by category/price, and educators by role/interests — none of these are backed by a regular (non-text) index, only by the new text indexes on unrelated tokenized fields. As data grows, filter-only queries (e.g. ?category=Cooking with no q, as exercised in test/search.test.js) will fall back to full collection scans.

  • src/models/Course.js#L51-L52: add courseSchema.index({ category: 1, price: 1 }) (or separate indexes) to support the category/price filters.
  • src/models/Space.js#L86-L87: add spaceSchema.index({ category: 1, price: 1 }) for the same reason.
  • src/models/User.js#L138-L139: add userSchema.index({ role: 1, interests: 1 }) to support searchEducators's role/interests filters.

As per path instructions for src/models/**: "Flag schema changes that break existing documents, missing indexes for new query patterns, and TTL indexes that could delete durable records."

🤖 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/models/Course.js` around lines 51 - 52, Add regular filter-supporting
indexes alongside the existing text indexes: in src/models/Course.js lines
51-52, add a compound index on category and price; in src/models/Space.js lines
86-87, add the same category/price index; and in src/models/User.js lines
138-139, add a compound index on role and interests. These changes should
support filter-only search queries without altering existing text-search
indexes.

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 `@src/controllers/books/bookController.js`:
- Around line 30-32: Update the invalid upload responses in the book validation
path at src/controllers/books/bookController.js#L30-L32 and the avatar
validation path at src/controllers/userController.js#L17-L19 to use the
documented { success, message, data } envelope by adding data: null (or the
documented structured error payload) to both responses.

In `@src/controllers/searchController.js`:
- Around line 17-20: Update both error handlers in the search controller,
including the handler corresponding to the second reported location, to stop
returning err.message in the JSON response. Keep logger.error logging the caught
error server-side and preserve the existing 500 response with only the safe
public error fields.

In `@src/controllers/uploadController.js`:
- Around line 15-24: The upload response contract must use the shared { success,
message, data } envelope. In src/controllers/uploadController.js lines 15-24,
place signature metadata inside data, add an appropriate success message, and
return data: null with the failure message in the catch response. In
test/bookUpload.test.js lines 96-97, update the expected contract and assert the
file public ID through res.body.data.filePublicId.
- Around line 7-13: Restrict the signature generated by the upload controller’s
cloudinary.utils.api_sign_request call to the validated upload flow: either
route uploads through the existing Multer size and magic-byte checks, or sign
only explicitly allowed parameters backed by a restrictive Cloudinary preset and
size policy. Do not issue a reusable folder-only signature for direct-uploads
without enforcing those constraints.

In `@src/middlewares/errorHandler.js`:
- Around line 81-87: Update the MulterError normalization branch in the error
handler to set the response status to the failure value alongside statusCode
400, ensuring normalized upload errors are serialized as failures while
preserving the existing file-size message handling.

In `@src/middlewares/upload.js`:
- Around line 6-29: The invalid-upload paths in imageFilter and bookFilter
currently pass the intended message as MulterError’s field argument, so
errorHandler.js returns “Unexpected field.” Update errorHandler.js to map
LIMIT_UNEXPECTED_FILE to the intended custom message, or replace these errors
with APIError instances carrying those messages; preserve the existing
validation conditions and distinct image/book text.

In `@src/routes/searchRoutes.js`:
- Around line 9-23: Update searchCacheKey and its route usage so cache keys
distinguish the main search endpoint from the educators endpoint, using an
explicit route-specific namespace or key component. Add limit to the keyed query
parameters so requests with different page sizes do not share entries. Also
verify cacheMiddleware stores responses only when the request succeeds,
preventing transient error responses from being cached.

In `@src/routes/uploadRoutes.js`:
- Line 7: Add Jest coverage for the POST /signature route registered in
uploadRoutes, including rejection of unauthenticated requests and successful
signature generation for an authenticated request. Mock or isolate the signing
dependency so assertions verify the response and behavior without exposing or
comparing CLOUDINARY_API_SECRET contents.

In `@src/routes/userRoutes.js`:
- Around line 46-51: Add an ownership-or-admin authorization middleware between
protect and uploadImage.single in the "/update/:id" route, requiring
req.user._id to match req.params.id unless the user has admin privileges. Ensure
unauthorized requests are rejected before uploadImage runs, while authorized
requests continue to updateUser.

In `@src/services/search/searchService.js`:
- Around line 55-153: Validate and clamp the page and limit inputs in both
searchCollections and searchEducators before calculating skip or issuing
queries: ensure page is at least 1 and limit is within the enforced positive
maximum, then derive skip from the normalized values. Reuse the same
normalization behavior in both functions so zero, negative, non-numeric, or
oversized request values cannot produce negative skips or unlimited MongoDB
queries.
- Around line 8-14: Update getSearchQuery to accept the target field, escape
regex metacharacters before constructing the case-insensitive prefix RegExp, and
return the regex under that field instead of as a top-level MongoDB operator. In
searchModel, pass the appropriate field for each model, and apply the same
escaped, field-scoped handling to the educator short-query path.
- Around line 16-33: Update the course-specific search configuration and
handling around applyFilters so course rating filtering and sorting no longer
target the nonexistent top-level rating field. Either add and populate an
aggregated top-level Course rating consistently, or remove rating from course
filter/sort options; preserve rating search behavior for books.

In `@src/utils/fileValidation.js`:
- Around line 16-19: Remove the NODE_ENV === "test" bypass in the file
validation flow so fileValidation uses the same magic-byte detection and MIME
allowlist for tests and production. Update the test fixtures to provide minimal
valid binary signatures rather than arbitrary buffers, preserving rejection of
unsupported file types.

---

Nitpick comments:
In `@src/models/Course.js`:
- Around line 51-52: Add regular filter-supporting indexes alongside the
existing text indexes: in src/models/Course.js lines 51-52, add a compound index
on category and price; in src/models/Space.js lines 86-87, add the same
category/price index; and in src/models/User.js lines 138-139, add a compound
index on role and interests. These changes should support filter-only search
queries without altering existing text-search indexes.

In `@test/search.test.js`:
- Around line 59-100: Extend the “Full-text search API” tests to cover a
two-character query such as q=Re, asserting the short-query search returns a
successful, relevant response. Add a cache-isolation test that sends identical
query parameters to /api/search and /api/search/educators, then verifies each
response retains its endpoint-specific result shape and data.
🪄 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: 2def0743-5693-4051-a2ce-48fb24d1fd80

📥 Commits

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

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (21)
  • app.js
  • package.json
  • src/controllers/books/bookController.js
  • src/controllers/searchController.js
  • src/controllers/uploadController.js
  • src/controllers/userController.js
  • src/middlewares/errorHandler.js
  • src/middlewares/upload.js
  • src/models/Book.js
  • src/models/Course.js
  • src/models/Reel.js
  • src/models/Space.js
  • src/models/User.js
  • src/routes/books/bookRoutes.js
  • src/routes/searchRoutes.js
  • src/routes/uploadRoutes.js
  • src/routes/userRoutes.js
  • src/services/search/searchService.js
  • src/utils/fileValidation.js
  • test/bookUpload.test.js
  • test/search.test.js

Comment thread src/controllers/books/bookController.js
Comment thread src/controllers/searchController.js
Comment on lines +7 to +13
const signature = cloudinary.utils.api_sign_request(
{
timestamp,
folder: "direct-uploads",
},
process.env.CLOUDINARY_API_SECRET
);

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 | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files 'src/controllers/uploadController.js' 'src/routes/*' 'src/**/*.js' '.env.example' | sed 's#^`#-` #'

echo
echo "== uploadController outline =="
ast-grep outline src/controllers/uploadController.js --view expanded || true

echo
echo "== route/validation references =="
rg -n "api_sign_request|CLOUDINARY_API_SECRET|protect|multer|magic|upload" src/controllers src/routes src/middleware src -g '*.js' -g '*.mjs' || true

echo
echo "== uploadController lines =="
nl -ba src/controllers/uploadController.js | sed -n '1,220p'

Repository: Deen-Bridge/dnb-backend

Length of output: 22097


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== uploadController.js =="
sed -n '1,220p' src/controllers/uploadController.js | cat -n

echo
echo "== uploadRoutes.js =="
sed -n '1,120p' src/routes/uploadRoutes.js | cat -n

echo
echo "== upload middleware =="
sed -n '1,220p' src/middlewares/upload.js | cat -n

echo
echo "== env validation =="
sed -n '1,120p' src/config/validateEnv.js | cat -n

echo
echo "== cloudinary util =="
sed -n '1,120p' src/utils/cloudinary.js | cat -n

echo
echo "== direct upload references =="
rg -n "direct-uploads|generateSignature|signature" src -g '*.js'

Repository: Deen-Bridge/dnb-backend

Length of output: 8083


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== fileValidation.js =="
sed -n '1,220p' src/utils/fileValidation.js | cat -n

echo
echo "== where magic-byte validation is used =="
rg -n "validateMagicBytes|magic bytes|fileValidation" src -g '*.js'

Repository: Deen-Bridge/dnb-backend

Length of output: 2737


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== cloudinary usage =="
rg -n "upload_preset|unsigned|signed upload|direct upload|resource_type|allowed_formats|max_file_size|eager" src -g '*.js'

Repository: Deen-Bridge/dnb-backend

Length of output: 496


Scope Cloudinary signatures to the validated upload flow.
protect only blocks anonymous callers; this endpoint still hands out a reusable signature for folder=direct-uploads without the Multer size checks or magic-byte validation used by the server-side upload paths. Sign only the exact upload params you intend to allow, or move these uploads through the hardened server flow and enforce a restrictive Cloudinary preset/size policy.

🤖 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/controllers/uploadController.js` around lines 7 - 13, Restrict the
signature generated by the upload controller’s cloudinary.utils.api_sign_request
call to the validated upload flow: either route uploads through the existing
Multer size and magic-byte checks, or sign only explicitly allowed parameters
backed by a restrictive Cloudinary preset and size policy. Do not issue a
reusable folder-only signature for direct-uploads without enforcing those
constraints.

Source: Path instructions

Comment thread src/controllers/uploadController.js Outdated
Comment thread src/middlewares/errorHandler.js
Comment thread src/routes/userRoutes.js
Comment on lines 46 to 51
router.put(
"/update/:id",
protect,
upload.single("avatar"),
uploadImage.single("avatar"),
invalidateCacheMiddleware([`${CACHE_KEYS.USER}*`]),
updateUser

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

Enforce ownership before accepting the upload.

protect authenticates but does not require req.user._id to match :id; the controller updates by req.params.id. Any signed-in user can therefore overwrite another profile. Add self-or-admin authorization before uploadImage so unauthorized requests cannot consume Cloudinary resources either.

As per path instructions, src/**/*.js requires missing auth/ownership checks to be flagged.

🤖 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/userRoutes.js` around lines 46 - 51, Add an ownership-or-admin
authorization middleware between protect and uploadImage.single in the
"/update/:id" route, requiring req.user._id to match req.params.id unless the
user has admin privileges. Ensure unauthorized requests are rejected before
uploadImage runs, while authorized requests continue to updateUser.

Source: Path instructions

Comment thread src/services/search/searchService.js
Comment thread src/services/search/searchService.js
Comment thread src/services/search/searchService.js
Comment thread src/utils/fileValidation.js Outdated

@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: 5

Caution

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

⚠️ Outside diff range comments (2)
src/controllers/searchController.js (2)

14-19: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Align both handlers with the required response contract.

These endpoints currently return { success, results, pagination } on success and { success, error } on failure, rather than the required { success, message, data } shape. This breaks clients that consume the standardized envelope; reconcile this with the PR’s stated results/pagination contract before merging.

Also applies to: 31-36

🤖 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/controllers/searchController.js` around lines 14 - 19, Update both
handlers in the search controller to use the required response envelope: return
`{ success, message, data }` on success, placing the existing results and
pagination payload under `data`, and return the corresponding message-based `{
success, message, data }` shape on failure. Preserve the existing search
behavior and error status while aligning both success and catch paths.

Source: Path instructions


8-14: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard q before trimming in search and cache-key generation src/controllers/searchController.js:8-14 src/routes/searchRoutes.js:10-16
Repeated query params can arrive as an array, so q.trim() can throw and turn a bad request into a 500. Reject non-string values with 400, and normalize q once before reusing it in the handler and searchCacheKey.

🤖 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/controllers/searchController.js` around lines 8 - 14, Update the search
handler and searchCacheKey flow to validate q is a string before trimming,
returning a 400 response for array or other non-string query values. Normalize q
once after validation, then reuse that normalized value for length checks,
searchCollections, and cache-key generation instead of trimming independently.

Source: Path instructions

♻️ Duplicate comments (2)
src/services/search/searchService.js (2)

65-67: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Normalize pagination as positive safe integers in both search paths.

The current clamping permits fractional and non-finite values, which can make MongoDB reject skip() or limit() arguments.

  • src/services/search/searchService.js#L65-L67: validate page and limit before calculating skip.
  • src/services/search/searchService.js#L126-L129: reuse the same normalizer in searchEducators.

As per path instructions, request input must be validated before database use.

🤖 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/search/searchService.js` around lines 65 - 67, Normalize page
and limit as positive finite integers before database use, preserving the
existing page and limit bounds. Add or reuse a shared normalizer near the first
pagination flow, update src/services/search/searchService.js lines 65-67 to
calculate skip from normalized values, and reuse it in searchEducators at lines
126-129; both sites require the same validation behavior.

Source: Path instructions


101-104: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep sort capabilities model-specific.

The course configuration excludes rating, but the shared sortOption is applied to every searchModel. Therefore, type=courses&sort=rating still sends a rating sort to courses while Books retain rating support. Derive or validate sorting per model so unsupported course sorts are rejected or ignored.

🤖 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/search/searchService.js` around lines 101 - 104, Update the
course search configuration in the search service so its model-specific sort
options cannot accept or apply the shared rating sort; preserve rating sorting
for the Book searchModel call. Derive or validate sortOption separately for each
model, rejecting or ignoring rating for courses while retaining supported course
sorts such as category and price.
🤖 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/routes/userRoutes.js`:
- Around line 49-54: Update the authorization failure response in the user
profile middleware to include data: null alongside success and message,
preserving the required { success, message, data } shape.
- Around line 49-55: Extend test/upload.test.js with coverage for the PUT
/update/:id authorization middleware: add an authenticated owner case that
succeeds and an authenticated different-user case that returns 403, ensuring
both exercise the ownership check before uploadImage.single("avatar") runs.

In `@test/upload.test.js`:
- Around line 56-58: Update the secret assertion in the upload test to first
require that process.env.CLOUDINARY_API_SECRET is configured, then assert resStr
does not contain that actual secret value. Remove the "undefined" fallback so
the test fails when the environment variable is missing.
- Around line 6-12: Remove the hardcoded JWT, Cloudinary, and MongoDB values
from the test setup in upload.test.js, and read them from Jest/CI-provided
environment variables or a non-committed test environment file instead. Add the
required variable names and safe placeholder values to .env.example, while
preserving the existing test configuration behavior.
- Around line 6-14: Move the environment-variable initialization before
application loading by configuring Jest setupFiles/CI environment, or replace
the static app import with a dynamic await import after the assignments. Update
the test’s app-loading flow so app.js and transitive modules such as cloudinary
configuration observe the configured values.

---

Outside diff comments:
In `@src/controllers/searchController.js`:
- Around line 14-19: Update both handlers in the search controller to use the
required response envelope: return `{ success, message, data }` on success,
placing the existing results and pagination payload under `data`, and return the
corresponding message-based `{ success, message, data }` shape on failure.
Preserve the existing search behavior and error status while aligning both
success and catch paths.
- Around line 8-14: Update the search handler and searchCacheKey flow to
validate q is a string before trimming, returning a 400 response for array or
other non-string query values. Normalize q once after validation, then reuse
that normalized value for length checks, searchCollections, and cache-key
generation instead of trimming independently.

---

Duplicate comments:
In `@src/services/search/searchService.js`:
- Around line 65-67: Normalize page and limit as positive finite integers before
database use, preserving the existing page and limit bounds. Add or reuse a
shared normalizer near the first pagination flow, update
src/services/search/searchService.js lines 65-67 to calculate skip from
normalized values, and reuse it in searchEducators at lines 126-129; both sites
require the same validation behavior.
- Around line 101-104: Update the course search configuration in the search
service so its model-specific sort options cannot accept or apply the shared
rating sort; preserve rating sorting for the Book searchModel call. Derive or
validate sortOption separately for each model, rejecting or ignoring rating for
courses while retaining supported course sorts such as category and price.
🪄 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: ad458873-1cc3-477f-9fda-a15cf9a22964

📥 Commits

Reviewing files that changed from the base of the PR and between ac2b1bb and 0b34c14.

📒 Files selected for processing (12)
  • src/controllers/books/bookController.js
  • src/controllers/searchController.js
  • src/controllers/uploadController.js
  • src/controllers/userController.js
  • src/middlewares/errorHandler.js
  • src/middlewares/upload.js
  • src/routes/searchRoutes.js
  • src/routes/userRoutes.js
  • src/services/search/searchService.js
  • src/utils/fileValidation.js
  • test/bookUpload.test.js
  • test/upload.test.js
💤 Files with no reviewable changes (1)
  • src/utils/fileValidation.js
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/routes/searchRoutes.js
  • src/controllers/userController.js
  • src/middlewares/errorHandler.js
  • src/controllers/uploadController.js
  • src/middlewares/upload.js
  • test/bookUpload.test.js
  • src/controllers/books/bookController.js

Comment thread src/routes/userRoutes.js
Comment thread src/routes/userRoutes.js
Comment thread test/upload.test.js Outdated
Comment thread test/upload.test.js Outdated
Comment thread test/upload.test.js Outdated

@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.

Caution

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

⚠️ Outside diff range comments (2)
src/controllers/books/bookController.js (2)

144-145: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Do not expose raw internal error messages.

error.message may reveal database or implementation details. Log the original error server-side, but return a generic client message with the standard response envelope.

🤖 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/controllers/books/bookController.js` around lines 144 - 145, Update the
catch block in the book controller to log the original error server-side, then
replace error.message in the res.status(500).json response with a generic
client-safe message while preserving the existing success:false response
envelope.

132-143: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Standardize all changed responses on the documented envelope.

These newly changed responses should include data: null (or the documented payload) alongside success and message.

  • src/controllers/books/bookController.js#L132-L143: add data to delete-book 404, 403, and success responses.
  • src/controllers/userController.js#L13-L16: add data to the update authorization response.
  • src/controllers/userController.js#L133-L136: add data to the delete authorization response.

As per path instructions, changed endpoints must use consistent response shapes: { success, message, data }.

🤖 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/controllers/books/bookController.js` around lines 132 - 143, Standardize
the response envelopes in the delete-book flow of bookController.js and the
update/delete authorization responses in userController.js to include data
alongside success and message. Add data: null to error and empty success
responses, or the documented payload where applicable, while preserving the
existing status codes and messages across all three affected sites.

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.

Outside diff comments:
In `@src/controllers/books/bookController.js`:
- Around line 144-145: Update the catch block in the book controller to log the
original error server-side, then replace error.message in the
res.status(500).json response with a generic client-safe message while
preserving the existing success:false response envelope.
- Around line 132-143: Standardize the response envelopes in the delete-book
flow of bookController.js and the update/delete authorization responses in
userController.js to include data alongside success and message. Add data: null
to error and empty success responses, or the documented payload where
applicable, while preserving the existing status codes and messages across all
three affected sites.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 69356c6f-9209-4713-8b97-59452123b86f

📥 Commits

Reviewing files that changed from the base of the PR and between 0b34c14 and bb1f3be.

📒 Files selected for processing (3)
  • src/controllers/books/bookController.js
  • src/controllers/userController.js
  • src/models/User.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/models/User.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

♻️ Duplicate comments (1)
src/routes/userRoutes.js (1)

49-54: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Preserve the administrator authorization path.

This middleware allows only the exact profile owner; administrators cannot update another profile. Add the documented admin exception or reuse shared ownership/admin authorization middleware before uploadImage.single("avatar"). This is the same unresolved admin-override finding from the earlier review.

As per path instructions, src/**/*.js endpoints must enforce appropriate authorization checks.

🤖 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/userRoutes.js` around lines 49 - 54, Update the authorization
middleware before uploadImage.single("avatar") to allow administrators to update
other profiles while retaining the existing owner-only check for regular users.
Reuse the shared ownership/admin authorization middleware if available;
otherwise add the documented administrator exception in the middleware
containing the req.user._id and req.params.id comparison.

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 `@test/jest.setup.js`:
- Around line 1-7: Remove the hardcoded JWT, MongoDB, and Cloudinary values from
the Jest setup configuration and read them from injected CI/Jest environment
variables or an ignored .env.test file. Validate that the required variables are
present rather than assigning fallback secrets, while preserving the existing
NODE_ENV and PORT setup. Document the required configuration keys in
.env.example.

---

Duplicate comments:
In `@src/routes/userRoutes.js`:
- Around line 49-54: Update the authorization middleware before
uploadImage.single("avatar") to allow administrators to update other profiles
while retaining the existing owner-only check for regular users. Reuse the
shared ownership/admin authorization middleware if available; otherwise add the
documented administrator exception in the middleware containing the req.user._id
and req.params.id comparison.
🪄 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: 02fbc4f4-be16-475c-8bd3-497703737570

📥 Commits

Reviewing files that changed from the base of the PR and between bb1f3be and df10742.

📒 Files selected for processing (5)
  • jest.config.js
  • src/routes/userRoutes.js
  • test/bookUpload.test.js
  • test/jest.setup.js
  • test/upload.test.js
💤 Files with no reviewable changes (1)
  • test/bookUpload.test.js

Comment thread test/jest.setup.js Outdated
@zeemscript
zeemscript merged commit f22bdb8 into Deen-Bridge:dev Jul 25, 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] Full-text search API: text indexes, relevance ranking, filters, pagination, and educator search

2 participants