Skip to content

fix: harden populate robustness, clamp paginate limit, fix docs and test coverage - #88

Merged
Shivkumardhakad merged 2 commits into
mainfrom
feat/populate-option
Apr 9, 2026
Merged

Shivkumardhakad merged 2 commits into
mainfrom
feat/populate-option

Conversation

@yash-pouranik

@yash-pouranik yash-pouranik commented Apr 9, 2026 •

Copy link
Copy Markdown
Member

Review feedback on the relational population feature exposed four categories of bugs: populate/expand leaking into MongoDB filters, split(',') crashing on Express array params, docs mismatching actual paginate() behavior, and a test that only checked construction rather than chain invocation.

queryEngine.js

  • Added populate and expand to excludedFields in filter() — without this, ?populate=author would emit Model.find({ populate: 'author' }) and return nothing
  • Normalized populateParam to coerce Express array params (?populate=a&populate=b → ['a','b']) before .split(',')
  • Clamped limit to Math.min(..., 100) in paginate() to match documented max

data.controller.js

  • Same array-param normalization applied to getSingleDoc() for consistency

docs/database.md

  • Fixed missing opening ```bash fence on "Fetch Single Document" example
  • Restored ## 3. Update a Document heading (PUT docs were orphaned under Query Parameters)
  • Updated page description from "Pagination offset" → "1-indexed page number"; limit now shows "default: 100, max: 100"

data.controller.read.test.js

  • Wired mockEnginePopulate spy into mockQueryEngine factory; tests now assert .populate() is called on the engine chain
  • Added test: populate/expand are not forwarded to MongoDB filter
  • Added test: array-format populate param (['author', 'category']) is normalized without crashing
  • Fixed findOne mock to return a chainable object supporting multiple sequential .populate() calls

@coderabbitai

coderabbitai Bot commented Apr 9, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This pull request introduces support for populating referenced fields in data queries. The feature adds populate() and expand query parameters that allow users to expand reference fields (replacing raw IDs with full documents). Changes include controller logic updates, QueryEngine enhancements, dynamic schema ref handling for multi-project environments, test expansions, and documentation additions.

Changes

Cohort / File(s) Summary
Query Engine & Schema Building
packages/common/src/utils/queryEngine.js, packages/common/src/utils/injectModel.js
Added populate() method to QueryEngine for chainable query population. Updated schema building to support dynamic ref targets based on isExternal flag, enabling project-scoped model prefixing.
Controller Logic
apps/public-api/src/controllers/data.controller.js
Added unconditional .populate() step in getAllData query pipeline and conditional population in getSingleDoc based on req.query.populate or req.query.expand parameters.
Test Coverage
apps/public-api/src/__tests__/data.controller.read.test.js, apps/public-api/src/__tests__/mail.controller.test.js
Extended mocked query chains to support .populate() with call tracking. Updated Redis mock configuration and test assertions for mail controller. Added tests for populate parameter handling in data retrieval.
API Documentation
docs/api-reference.md, docs/database.md
Documented new populate and expand query parameters alongside existing sort, limit, and page parameters. Added explanation of relational data expansion and N+1 query problem resolution with usage examples.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Controller as Data Controller
    participant Engine as QueryEngine
    participant Mongoose as Mongoose Query
    participant DB as Database

    Client->>Controller: GET /api/data?populate=author,category
    Controller->>Engine: new QueryEngine(queryString)
    Controller->>Engine: find() → populate()
    Engine->>Engine: Parse populate param (comma-separated)
    Engine->>Mongoose: query.populate('author')
    Engine->>Mongoose: query.populate('category')
    Mongoose->>DB: Execute aggregated query
    DB-->>Mongoose: Return docs with expanded refs
    Mongoose-->>Engine: Hydrated documents
    Engine->>Engine: lean() → chainable result
    Engine-->>Controller: Result object
    Controller-->>Client: JSON response with populated fields
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 Whiskers twitching with delight,
References now fully bright!
No more IDs, just numbers bare—
Populate expands with utmost care,
One query now solves N+1's snare! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main changes: implementing relational population (for referenced documents) and hardening mail service quota (atomic Redis operations). It is specific, concise, and directly reflects the primary objectives of the changeset.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/populate-option

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.

Copilot AI 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.

Pull request overview

This PR adds first-class relational expansion support to the Public API via a ?populate=/?expand= query param, and documents the new query capabilities while updating tests around data reads and mail quota handling.

Changes:

  • Extended QueryEngine with a .populate() step and wired it into getAllData.
  • Updated dynamic schema injection to set Mongoose ref correctly for Ref and Array of Ref, including tenant-prefixed model names.
  • Updated docs and Jest tests to reflect the new populate behavior and Redis eval-based mail quota logic.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/common/src/utils/queryEngine.js Adds .populate() support for query-driven population.
packages/common/src/utils/injectModel.js Injects tenant-aware ref metadata into generated Mongoose schemas for population.
apps/public-api/src/controllers/data.controller.js Chains population for list reads and adds population handling for single-doc reads.
apps/public-api/src/tests/mail.controller.test.js Updates mocks/assertions for Redis eval quota logic and standardized responses.
apps/public-api/src/tests/data.controller.read.test.js Adjusts controller read tests to accommodate populate chaining and adds new populate-related assertions.
docs/database.md Documents populate/expand query params and adds a population section + examples.
docs/api-reference.md Adds a query-parameter reference table including populate/expand.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +64 to +70
populate() {
const populateParam = this.queryString.populate || this.queryString.expand;
if (populateParam) {
const fields = populateParam.split(',').map(f => f.trim()).filter(Boolean);
fields.forEach(f => {
this.query = this.query.populate(f);
});

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

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

populate/expand is currently not excluded from filter(). With the common call order new QueryEngine(...).filter().sort().populate(), filter() will treat populate as a Mongo filter field (e.g. { populate: 'author' }) and likely return no results. Add populate and expand to the excludedFields list in filter() (or strip them before building mongoQuery) so population doesn't affect filtering.

Copilot uses AI. Check for mistakes.
Comment on lines +65 to +68
const populateParam = this.queryString.populate || this.queryString.expand;
if (populateParam) {
const fields = populateParam.split(',').map(f => f.trim()).filter(Boolean);
fields.forEach(f => {

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

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

populateParam.split(',') assumes req.query.populate is always a string. Express can parse repeated query params into an array (e.g. ?populate=a&populate=b), which would throw here. Consider normalizing populateParam to a string (join arrays) and ignoring non-string values before calling .split().

Copilot uses AI. Check for mistakes.
Comment on lines +165 to +169
const populateParam = req.query.populate || req.query.expand;
if (populateParam) {
const fields = populateParam.split(',').map(f => f.trim()).filter(Boolean);
fields.forEach(f => {
query = query.populate(f);

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

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

Same as QueryEngine: populateParam.split(',') assumes a string. If clients send repeated params (or non-string values), this can throw and become a 500. Normalize populateParam (array -> join, non-string -> ignore) before splitting/populating.

Copilot uses AI. Check for mistakes.
Comment thread docs/database.md
Comment on lines 57 to 61
**Endpoint**: `GET /api/data/:collectionName/:id`

```bash
curl "https://api.ub.bitbros.in/api/data/posts/64fd1234abcd5678ef901234" ^
-H "x-api-key: pk_live_xxx"
```

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

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

The "Fetch Single Document" example is missing the opening code fence (bash). Right now the snippet starts with `curl ...` but only has a closing which breaks Markdown rendering.

Copilot uses AI. Check for mistakes.
Comment thread docs/database.md
Comment on lines +63 to +66
## 2.1 Query Parameters

You can refine your `GET` requests using the following query parameters:

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

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

The heading ## 3. Update a Document appears to have been replaced by ## 2.1 Query Parameters, but the subsequent content still transitions directly into the PUT endpoint. Reintroduce an explicit Update section heading (or adjust numbering) so the PUT docs aren't orphaned under the Read section.

Copilot uses AI. Check for mistakes.
Comment thread docs/database.md Outdated
Comment on lines +69 to +73
| `populate` | `String` | `?populate=author,category` | Expand `Ref` fields into full objects. |
| `expand` | `String` | `?expand=author` | Alias for `populate`. |
| `sort` | `String` | `?sort=createdAt:desc` | Sort results by field and order. |
| `limit` | `Number` | `?limit=25` | Number of documents to return (max 100). |
| `page` | `Number` | `?page=2` | Pagination offset. |

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

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

Docs say limit is "max 100" and page is a "Pagination offset", but QueryEngine.paginate() currently defaults to 100 without clamping and treats page as a 1-indexed page number. Either clamp limit in code or update these descriptions to match actual behavior.

Copilot uses AI. Check for mistakes.
Comment thread apps/public-api/src/__tests__/data.controller.read.test.js
@yash-pouranik

Copy link
Copy Markdown
Member Author

@copilot apply changes based on the comments in this thread

@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

🧹 Nitpick comments (5)
apps/public-api/src/__tests__/mail.controller.test.js (2)

112-115: Assert full 429 response contract.

This check should also verify data: {} (and ideally limit) to lock in the standardized { success, data, message } error shape.

Suggested assertion update
         expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
             success: false,
+            data: {},
             message: 'Monthly mail limit exceeded.',
+            limit: expect.any(Number),
         }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/public-api/src/__tests__/mail.controller.test.js` around lines 112 -
115, Update the test assertion in
apps/public-api/src/__tests__/mail.controller.test.js that checks the 429
response to assert the full error contract from res.json: in addition to
success: false and message: 'Monthly mail limit exceeded.', assert data: {} and
include the limit field (e.g., expect.objectContaining({ limit:
expect.any(Number) }) or the exact value) so the test validates the standardized
{ success, data, message } shape returned by the controller; modify the
expect(res.json).toHaveBeenCalledWith(...) assertion accordingly.

76-76: Strengthen quota assertions beyond call count.

Line 76 only checks redis.eval call count. Please also assert key format, TTL argument, and that redis.incr/redis.expire are not used, so atomic quota behavior can’t silently regress.

Suggested test hardening
-        expect(redis.eval).toHaveBeenCalledTimes(1);
+        expect(redis.eval).toHaveBeenCalledTimes(1);
+        expect(redis.eval).toHaveBeenCalledWith(
+            expect.stringContaining('INCR'),
+            1,
+            expect.stringMatching(/^project:mail:count:proj_1:\d{4}-\d{2}$/),
+            expect.any(Number),
+        );
+        const [, , , ttlSeconds] = redis.eval.mock.calls[0];
+        expect(ttlSeconds).toBeGreaterThan(0);
+        expect(redis.incr).not.toHaveBeenCalled();
+        expect(redis.expire).not.toHaveBeenCalled();

Based on learnings: Mail count Redis keys must follow pattern project:mail:count:{projectId}:{YYYY-MM} with TTL set to end of month. Do not change this pattern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/public-api/src/__tests__/mail.controller.test.js` at line 76, The test
currently only asserts redis.eval call count; update the test in
mail.controller.test.js to strengthen assertions by verifying the redis.eval was
called with the correct key pattern (project:mail:count:{projectId}:{YYYY-MM})
and that the TTL argument corresponds to the end-of-month expiration, and also
assert that redis.incr and redis.expire were not called to ensure atomic eval
usage; locate references to redis.eval, redis.incr, and redis.expire in the test
and add expectations that inspect the eval call arguments (keys and TTL arg) and
negative assertions for incr/expire.
apps/public-api/src/controllers/data.controller.js (1)

164-171: Consider extracting duplicate parsing logic.

The field parsing logic (split by comma, trim, filter) duplicates QueryEngine.populate(). This is minor since both locations are stable, but a shared helper could reduce future drift.

// Potential shared helper in `@urbackend/common`
function parsePopulateFields(populateParam) {
  if (!populateParam) return [];
  return populateParam.split(',').map(f => f.trim()).filter(Boolean);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/public-api/src/controllers/data.controller.js` around lines 164 - 171,
Extract the duplicate parsing logic into a shared helper (e.g.,
parsePopulateFields) and replace the inline parsing in data.controller.js with a
call to that helper; specifically, move the split/trim/filter behavior currently
applied to populateParam into the new helper (to be consumed by controllers) and
update the code that calls QueryEngine.populate() to iterate over the returned
array, ensuring both data.controller.js (populateParam) and
QueryEngine.populate() use the same helper to avoid drift.
packages/common/src/utils/injectModel.js (1)

50-56: Minor: Redundant null check on field.items.

At line 50, field.items is already guaranteed to be truthy by the guard at line 28. The extra check is harmless but unnecessary.

     // Array of Ref
-    if (field.items && field.items.type === "Ref") {
+    if (field.items.type === "Ref") {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/common/src/utils/injectModel.js` around lines 50 - 56, Remove the
redundant null check on field.items inside the injectModel logic: change the if
condition that currently reads "if (field.items && field.items.type === 'Ref')"
to just check "field.items.type === 'Ref'" (or equivalent) because field.items
is already guaranteed truthy by the earlier guard; keep the existing behavior
that computes targetRef (using field.items.ref) and returns the Schema
definition and required flag unchanged.
apps/public-api/src/__tests__/data.controller.read.test.js (1)

120-130: Test doesn't verify actual population behavior.

This test only asserts mockQueryEngine was called, but doesn't verify that populate() was invoked with the expected fields. Since mockQueryEngine is fully mocked and doesn't call mockPopulate, this test doesn't validate the QueryEngine.populate() method behavior.

Consider either:

  1. Testing the real QueryEngine with a mocked Mongoose query, or
  2. Asserting that the mock engine's populate() method was called
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/public-api/src/__tests__/data.controller.read.test.js` around lines 120
- 130, The test for getAllData only checks mockQueryEngine was called but not
that populate() was invoked; update the test in the "getAllData passes populate
param to QueryEngine" case to assert the mock engine's populate method was
called with the expected fields (e.g., that mockPopulate or
mockQueryEngine().populate was called with 'author,comments' or
['author','comments'] depending on how your mock is implemented), or
alternatively replace the mock and exercise the real QueryEngine with a mocked
Mongoose query to verify QueryEngine.populate() is invoked; specifically locate
the getAllData test and add an assertion referencing mockPopulate or
mockQueryEngine().populate to validate the populate argument.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/public-api/src/__tests__/data.controller.read.test.js`:
- Around line 41-47: The mock for the Mongoose query does not support chained
populate calls; update the returned object in the populate mock so that
populate(...) returns the same chainable query object (i.e., it still exposes
populate and lean) instead of an object with only lean. Specifically, modify the
populate implementation that calls mockPopulate(...) so it returns the parent
query object (or an object containing both populate and lean) so multiple calls
to populate(...) are valid; adjust the mock referenced as
mockPopulate/populate/lean in the test to be chainable to mirror Mongoose's API.

In `@docs/database.md`:
- Around line 56-61: The Markdown snippet under the "Fetch Single Document"
section is missing the opening code fence for the curl example; add an opening
fenced code block (e.g., ```bash) immediately before the curl line in that
section so the example is properly fenced and renders correctly, keeping the
existing closing ``` after the curl command.

---

Nitpick comments:
In `@apps/public-api/src/__tests__/data.controller.read.test.js`:
- Around line 120-130: The test for getAllData only checks mockQueryEngine was
called but not that populate() was invoked; update the test in the "getAllData
passes populate param to QueryEngine" case to assert the mock engine's populate
method was called with the expected fields (e.g., that mockPopulate or
mockQueryEngine().populate was called with 'author,comments' or
['author','comments'] depending on how your mock is implemented), or
alternatively replace the mock and exercise the real QueryEngine with a mocked
Mongoose query to verify QueryEngine.populate() is invoked; specifically locate
the getAllData test and add an assertion referencing mockPopulate or
mockQueryEngine().populate to validate the populate argument.

In `@apps/public-api/src/__tests__/mail.controller.test.js`:
- Around line 112-115: Update the test assertion in
apps/public-api/src/__tests__/mail.controller.test.js that checks the 429
response to assert the full error contract from res.json: in addition to
success: false and message: 'Monthly mail limit exceeded.', assert data: {} and
include the limit field (e.g., expect.objectContaining({ limit:
expect.any(Number) }) or the exact value) so the test validates the standardized
{ success, data, message } shape returned by the controller; modify the
expect(res.json).toHaveBeenCalledWith(...) assertion accordingly.
- Line 76: The test currently only asserts redis.eval call count; update the
test in mail.controller.test.js to strengthen assertions by verifying the
redis.eval was called with the correct key pattern
(project:mail:count:{projectId}:{YYYY-MM}) and that the TTL argument corresponds
to the end-of-month expiration, and also assert that redis.incr and redis.expire
were not called to ensure atomic eval usage; locate references to redis.eval,
redis.incr, and redis.expire in the test and add expectations that inspect the
eval call arguments (keys and TTL arg) and negative assertions for incr/expire.

In `@apps/public-api/src/controllers/data.controller.js`:
- Around line 164-171: Extract the duplicate parsing logic into a shared helper
(e.g., parsePopulateFields) and replace the inline parsing in data.controller.js
with a call to that helper; specifically, move the split/trim/filter behavior
currently applied to populateParam into the new helper (to be consumed by
controllers) and update the code that calls QueryEngine.populate() to iterate
over the returned array, ensuring both data.controller.js (populateParam) and
QueryEngine.populate() use the same helper to avoid drift.

In `@packages/common/src/utils/injectModel.js`:
- Around line 50-56: Remove the redundant null check on field.items inside the
injectModel logic: change the if condition that currently reads "if (field.items
&& field.items.type === 'Ref')" to just check "field.items.type === 'Ref'" (or
equivalent) because field.items is already guaranteed truthy by the earlier
guard; keep the existing behavior that computes targetRef (using
field.items.ref) and returns the Schema definition and required flag unchanged.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9a3f24c1-ff63-4452-a7b0-f981d4a10d92

📥 Commits

Reviewing files that changed from the base of the PR and between c9a6b9b and 051fb7c.

📒 Files selected for processing (7)
  • apps/public-api/src/__tests__/data.controller.read.test.js
  • apps/public-api/src/__tests__/mail.controller.test.js
  • apps/public-api/src/controllers/data.controller.js
  • docs/api-reference.md
  • docs/database.md
  • packages/common/src/utils/injectModel.js
  • packages/common/src/utils/queryEngine.js

Comment on lines +41 to +47
return {
populate: (...pArgs) => {
mockPopulate(...pArgs);
return { lean: jest.fn().mockResolvedValue({ _id: 'doc_1' }) };
},
lean: jest.fn().mockResolvedValue({ _id: 'doc_1' })
};

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.

⚠️ Potential issue | 🟡 Minor

Mock doesn't support chaining multiple populate() calls.

The current mock returns a new object from populate() that only has lean(). If getSingleDoc is called with multiple fields (e.g., populate=author,category), only the first field's populate would be captured—subsequent calls would fail because the returned object lacks a populate method.

🔧 Proposed fix for chainable mocks
         findOne: (...args) => {
             mockFindOne(...args);
-            return { 
-                populate: (...pArgs) => {
-                    mockPopulate(...pArgs);
-                    return { lean: jest.fn().mockResolvedValue({ _id: 'doc_1' }) };
-                },
-                lean: jest.fn().mockResolvedValue({ _id: 'doc_1' }) 
-            };
+            const queryObj = {
+                populate: (...pArgs) => {
+                    mockPopulate(...pArgs);
+                    return queryObj; // Return self for chaining
+                },
+                lean: jest.fn().mockResolvedValue({ _id: 'doc_1' })
+            };
+            return queryObj;
         },
📝 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
return {
populate: (...pArgs) => {
mockPopulate(...pArgs);
return { lean: jest.fn().mockResolvedValue({ _id: 'doc_1' }) };
},
lean: jest.fn().mockResolvedValue({ _id: 'doc_1' })
};
const queryObj = {
populate: (...pArgs) => {
mockPopulate(...pArgs);
return queryObj; // Return self for chaining
},
lean: jest.fn().mockResolvedValue({ _id: 'doc_1' })
};
return queryObj;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/public-api/src/__tests__/data.controller.read.test.js` around lines 41 -
47, The mock for the Mongoose query does not support chained populate calls;
update the returned object in the populate mock so that populate(...) returns
the same chainable query object (i.e., it still exposes populate and lean)
instead of an object with only lean. Specifically, modify the populate
implementation that calls mockPopulate(...) so it returns the parent query
object (or an object containing both populate and lean) so multiple calls to
populate(...) are valid; adjust the mock referenced as
mockPopulate/populate/lean in the test to be chainable to mirror Mongoose's API.

Comment thread docs/database.md
Comment on lines 56 to 61
### Fetch Single Document
**Endpoint**: `GET /api/data/:collectionName/:id`

```bash
curl "https://api.ub.bitbros.in/api/data/posts/64fd1234abcd5678ef901234" ^
-H "x-api-key: pk_live_xxx"
```

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.

⚠️ Potential issue | 🟡 Minor

Markdown syntax error: missing opening code fence.

The "Fetch Single Document" curl example is missing its opening code fence (```bash). This will cause rendering issues.

📝 Proposed fix
 ### Fetch Single Document
 **Endpoint**: `GET /api/data/:collectionName/:id`
 
+```bash
 curl "https://api.ub.bitbros.in/api/data/posts/64fd1234abcd5678ef901234" ^
   -H "x-api-key: pk_live_xxx"
</details>

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Betterleaks (1.1.1)</summary>

[high] 59-60: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.

(curl-auth-header)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @docs/database.md around lines 56 - 61, The Markdown snippet under the "Fetch
Single Document" section is missing the opening code fence for the curl example;
add an opening fenced code block (e.g., bash) immediately before the curl line in that section so the example is properly fenced and renders correctly, keeping the existing closing after the curl command.


</details>

<!-- fingerprinting:phantom:poseidon:ocelot:b57d0361-900b-4d7b-bfaa-011f154f9a75 -->

<!-- This is an auto-generated comment by CodeRabbit -->

@yash-pouranik
yash-pouranik temporarily deployed to feat/populate-option - urBackend-frankfrut PR #88 April 9, 2026 10:29 — with Render Destroyed

Copilot AI commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

@copilot apply changes based on the comments in this thread

Applied all changes from the review thread in commit c2eb981:

  • queryEngine.js: Added populate and expand to excludedFields so they're never passed to MongoDB as filter conditions. Normalized populateParam to handle Express array params (e.g. ?populate=a&populate=b). Clamped limit to max 100 in paginate().
  • data.controller.js: Same array-param normalization applied to getSingleDoc().
  • docs/database.md: Fixed the missing opening ```bash fence for "Fetch Single Document". Restored ## 3. Update a Document heading. Updated page description to "1-indexed page number" and limit to show "default: 100, max: 100".
  • data.controller.read.test.js: Added mockEnginePopulate spy to assert .populate() is called on the QueryEngine chain. Added tests for populate/expand not leaking into filtering and for array-format param normalization. Fixed findOne mock to support chaining multiple .populate() calls.

All 83 public-api tests pass.

Copilot AI changed the title feat: implement relational population and harden mail service quota fix: harden populate robustness, clamp paginate limit, fix docs and test coverage Apr 9, 2026
@Shivkumardhakad
Shivkumardhakad merged commit 08f90b6 into main Apr 9, 2026
4 checks passed

This branch was successfully deployed

No deployments
feat/populate-option - urBackend-frankfrut PR #88 — c2eb9817 Deployed Apr 9, 2026 by yash-pouranik
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.

4 participants