Skip to content

feat: Created Filter feature and redesign Database UI - #29

Merged
yash-pouranik merged 1 commit into
mainfrom
feat/ui-updates
Feb 25, 2026
Merged

feat: Created Filter feature and redesign Database UI#29
yash-pouranik merged 1 commit into
mainfrom
feat/ui-updates

Conversation

@yash-pouranik

@yash-pouranik yash-pouranik commented Feb 25, 2026

Copy link
Copy Markdown
Member

Pull Request: feat: Created Filter feature and redesign Database UI

Overview

Is PR mein urBackend ke data retrieval system ko upgrade kiya gaya hai aur database management interface ko ek naya, clean look diya gaya hai.

Key Changes

1. Backend: Query Engine v1 Implementation

  • Advanced Filtering: Request query parser implement kiya gaya hai jo operators (_gt, _lt, _gte, _lte) ko support karta hai.
  • Pagination Support: Data fetching mein page, limit, aur skip logic add kiya gaya hai.
  • Dynamic Sorting: URL parameters ke through fields ko sort=field:order format mein sort karne ki capability add ki gayi hai.
  • Logic Refactoring: Query handling ko controller se utility file (queryEngine.js) mein move kiya gaya hai.

2. Frontend: Database UI Redesign

  • Layout Optimization: Database.jsx aur DatabaseSidebar ko redesign kiya gaya hai naye navigation flow ke liye.
  • Record Management: CollectionTable.jsx ko update kiya gaya hai naye filters aur action buttons ke saath.
  • UX Improvements: Mobile aur desktop responsiveness ko improve kiya gaya hai.

Testing Steps

  1. Backend Testing: Postman se GET /api/data/:collectionName?field_gt=value test karein.
  2. Pagination: page=1&limit=10 pass karke response size verify karein.
  3. UI Check: Dashboard par naya sidebar aur table layout verify karein.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added dynamic filtering, sorting, and pagination capabilities for data queries.
    • Introduced filter/sort UI panel on Database page for intuitive query building.
    • Added pagination controls with configurable rows per page.
    • Added Edit action to row detail drawer.
    • Drawer now closes on Escape key or backdrop click.
  • Style

    • Updated landing page with glow effects and refined dark theme.
    • Enhanced button and component styling across the interface.
  • Chores

    • Removed internal documentation files.

@vercel

vercel Bot commented Feb 25, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ur-backend Ready Ready Preview, Comment Feb 25, 2026 4:40pm

@yash-pouranik
yash-pouranik temporarily deployed to feat/ui-updates - urBackend-frankfrut PR #29 February 25, 2026 16:40 — with Render Destroyed
@coderabbitai

coderabbitai Bot commented Feb 25, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR introduces a QueryEngine utility for dynamic backend query building (filtering, sorting, pagination) and integrates it into data retrieval endpoints. The frontend is updated with an enhanced RowDetailDrawer component featuring structured read-only field display and a revamped Database page with client-side filtering/sorting UI. Landing page styling is modernized with a darker theme and glow effects.

Changes

Cohort / File(s) Summary
Backend Query Engine
backend/utils/queryEngine.js
New QueryEngine class that encapsulates filter, sort, and paginate methods for chained MongoDB query building with support for comparison operators (_gt, _gte, _lt, _lte) and configurable pagination.
Backend Controller Integration
backend/controllers/data.controller.js, backend/controllers/project.controller.js
Integrated QueryEngine into getAllData and getData endpoints; replaced static .find({}).limit() calls with dynamic query pipelines that apply filters, sorting, and pagination based on request parameters.
Frontend Row Detail View
frontend/src/components/RowDetailDrawer.jsx
Complete rewrite to provide structured, read-only field display with form-like grid layout, System Metadata section, Escape-key and backdrop closing, and new onEdit prop for edit workflow integration. Added field-type-specific rendering (booleans, dates, objects).
Frontend Database Query UI
frontend/src/pages/Database.jsx
Added client-side query state management (queryParams, showFilterMenu) and UI controls for dynamic filtering, sorting, and pagination; fetchData now builds query strings from state and passes to API; wired onEdit handler for row editing from detail drawer.
Frontend Layout & Styling
frontend/src/components/CollectionTable.jsx, frontend/src/components/DatabaseSidebar.jsx
Minor adjustments: Actions column width increased to 120; sidebar background changed from colored to transparent with no right border.
Landing Page Styling
frontend/src/pages/LandingPage/index.jsx, frontend/src/pages/LandingPage/style.css
Comprehensive visual refresh with darker theme (near-black backgrounds), enhanced glow effects on icons, updated typography weights/colors, refined button/card styling, and responsive grid background adjustments (50px grid, 150vh height).
Documentation & Instrumentation Removal
next.txt, diff_output.txt
Removed planning document (next.txt) containing product positioning guidance; removed timing instrumentation references from diff output.

Sequence Diagram

sequenceDiagram
    participant Client as Client (Browser)
    participant DB as Database.jsx
    participant API as Backend API
    participant QE as QueryEngine
    participant Mongo as MongoDB

    Client->>DB: User applies filters/sort/pagination
    activate DB
    DB->>DB: Update queryParams state
    DB->>API: fetchData with query string<br/>(filters, sort, page, limit)
    deactivate DB
    
    activate API
    API->>QE: new QueryEngine(Model.find(), queryParams)
    activate QE
    QE->>QE: filter() - parse operators (_gt, _gte, etc)
    QE->>QE: sort() - build sort expressions
    QE->>QE: paginate() - calculate skip/limit
    QE->>Mongo: Execute chained query with lean()
    deactivate QE
    
    Mongo-->>API: Return filtered/sorted/paginated results
    deactivate API
    
    API-->>DB: JSON response
    activate DB
    DB->>DB: Update data state
    DB->>Client: Render table with fetched records
    deactivate DB
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 A query engine hops into the scene,
Filtering, sorting with grace pristine.
Detail drawers open with gleaming form,
Database queries dance to transform.
Landing pages glow in shades of night—
Your backend now queries just right! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 'feat: Created Filter feature and redesign Database UI' accurately reflects the main changes: introduction of a filtering feature and a redesign of the Database UI across both frontend and backend.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/ui-updates

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @yash-pouranik, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly upgrades the data retrieval capabilities of the backend by implementing a flexible query engine for filtering, sorting, and pagination. Concurrently, it revamps the frontend database user interface to provide a more intuitive and responsive experience for managing collections and records, alongside a visual refresh of the landing page.

Highlights

  • Backend Query Engine Implementation: A new QueryEngine utility has been introduced to centralize and manage advanced data retrieval operations, including filtering, dynamic sorting, and pagination, moving this logic out of controllers.
  • Advanced Data Filtering: The backend now supports advanced filtering capabilities using URL query parameters with operators like _gt, _lt, _gte, and _lte for numerical and date comparisons.
  • Dynamic Sorting and Pagination: Data can now be dynamically sorted based on specified fields and order (asc/desc) via URL parameters, and pagination (page, limit, skip) has been added to control data fetching size.
  • Database UI Redesign: The frontend database management interface has undergone a significant redesign, including layout optimizations for Database.jsx and DatabaseSidebar, and updates to CollectionTable.jsx to accommodate new filtering and action buttons.
  • Enhanced Record Detail Drawer: The RowDetailDrawer component has been refactored with improved styling, better display of various field types, and the addition of an 'Edit Document' action.
  • Landing Page Visual Refresh: The landing page (LandingPage/index.jsx and LandingPage/style.css) received visual enhancements, including new glow effects, updated button styles, and refined aesthetics for various sections.
Changelog
  • backend/controllers/data.controller.js
    • Updated data retrieval to use the new QueryEngine for advanced filtering, sorting, and pagination.
  • backend/controllers/project.controller.js
    • Modified project data retrieval to leverage the new QueryEngine for query processing.
  • backend/utils/queryEngine.js
    • Added a new utility class QueryEngine to encapsulate data filtering, sorting, and pagination logic.
  • diff_output.txt
    • Removed a file containing previous diff outputs.
  • frontend/src/components/CollectionTable.jsx
    • Adjusted the width of the 'Actions' column in the table.
  • frontend/src/components/DatabaseSidebar.jsx
    • Redesigned the database sidebar with a transparent background and removed its border.
  • frontend/src/components/RowDetailDrawer.jsx
    • Refactored the record detail drawer to improve styling, add an edit option, and enhance field value rendering.
  • frontend/src/pages/Database.jsx
    • Implemented comprehensive filtering, sorting, and pagination controls.
    • Integrated the QueryEngine for data fetching.
    • Updated the UI layout and added new Lucide icons.
  • frontend/src/pages/LandingPage/index.jsx
    • Enhanced the landing page with visual improvements, including a hero glow and updated button styles.
    • Refined styling for various sections and icons.
  • frontend/src/pages/LandingPage/style.css
    • Applied extensive styling updates to the landing page for a more modern and dynamic appearance.
    • Introduced new grid background and hero glow effects.
    • Updated styles for navigation, section titles, descriptions, and interactive elements like bento grid items and use-case cards.
Activity
  • The author has provided detailed testing steps for both backend and frontend changes, indicating self-testing has been performed.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

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

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/pages/Database.jsx (1)

118-161: ⚠️ Potential issue | 🟠 Major

Every filter keystroke triggers an API call — "Apply Queries" button is ineffective.

fetchData is a useCallback that depends on queryParams (line 152). The useEffect on line 155 depends on fetchData. So every change to queryParams — including every character typed in a filter value input (line 473) or every operator/field selection — recreates fetchData and triggers the effect, firing an API request immediately.

The "Apply Queries" button (line 506) only resets page to 1, but the fetch already happened on each keystroke.

To fix this, separate the "draft" filter state from the "committed" query state. Only update queryParams when "Apply Queries" is clicked:

Suggested approach
+ // Draft state for the filter menu (not triggering fetches)
+ const [draftFilters, setDraftFilters] = useState([]);
+ const [draftSort, setDraftSort] = useState('-createdAt');

  // In the Apply button handler:
  onClick={() => {
    setShowFilterMenu(false);
-   setQueryParams(p => ({ ...p, page: 1 }));
+   setQueryParams(p => ({ ...p, page: 1, sort: draftSort, filters: draftFilters }));
  }}

Use draftFilters / draftSort in the filter menu inputs, and sync them from queryParams when the menu opens.

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

In `@frontend/src/pages/Database.jsx` around lines 118 - 161, fetchData is being
re-created on every keystroke because it depends on queryParams, which causes
the useEffect watching fetchData to fire immediately; fix by introducing
separate "draft" filter/sort state for the filter UI (e.g., draftFilters,
draftSort) and a distinct committed/applied query state used for fetching (e.g.,
appliedQueryParams or committedQueryParams). Bind filter inputs to
draftFilters/draftSort and initialize/sync them from queryParams when the filter
menu opens, then update queryParams (setQueryParams or setAppliedQueryParams)
only when the "Apply Queries" button is clicked; change fetchData to depend on
appliedQueryParams (not the draft) so typing no longer triggers API calls, and
ensure the useEffect that calls fetchData depends on
fetchData/appliedQueryParams accordingly.
🧹 Nitpick comments (3)
backend/utils/queryEngine.js (1)

9-10: Minor: suppress linter warning on forEach callback return value.

The delete expression returns a boolean, which triggers the Biome useIterableCallbackReturn warning. Use a block body to discard the return.

Proposed fix
-        excludedFields.forEach(el => delete queryObj[el]);
+        excludedFields.forEach(el => { delete queryObj[el]; });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/utils/queryEngine.js` around lines 9 - 10, The forEach callback
currently uses a concise arrow that returns the boolean from the delete
expression (excludedFields.forEach(el => delete queryObj[el])), triggering the
linter; change the callback to use a block body that discards the return value —
e.g., replace the arrow with a function body that calls delete queryObj[el]; (or
use a for...of loop) so the callback does not return the delete result; update
the code around excludedFields and queryObj accordingly.
frontend/src/pages/LandingPage/style.css (1)

44-47: Rename keyframe to kebab-case: pulse-glow.

Stylelint flags pulseGlow as violating the keyframes-name-pattern rule (kebab-case expected).

Proposed fix
-@keyframes pulseGlow {
+@keyframes pulse-glow {
     0% { transform: translateX(-50%) scale(1); opacity: 0.8; }
     100% { transform: translateX(-50%) scale(1.1); opacity: 1; }
 }

Also update the reference on line 41:

-    animation: pulseGlow 8s ease-in-out infinite alternate;
+    animation: pulse-glow 8s ease-in-out infinite alternate;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/LandingPage/style.css` around lines 44 - 47, Rename the
`@keyframes` identifier from "pulseGlow" to kebab-case "pulse-glow" and update all
usages that reference it (e.g., animation, animation-name, or shorthand
animation properties that currently use pulseGlow) to "pulse-glow" so the
keyframes-name-pattern rule is satisfied; ensure the `@keyframes` declaration and
every CSS rule that applies the animation are updated consistently.
frontend/src/components/RowDetailDrawer.jsx (1)

6-14: Only bind the Escape listener while the drawer is open.

This currently registers a global keydown handler even when closed. Not incorrect, but unnecessary global work.

Suggested patch
   useEffect(() => {
+    if (!isOpen) return;
+
     const handleKeyDown = (e) => {
-      if (e.key === "Escape" && isOpen) {
+      if (e.key === "Escape") {
         onClose();
       }
     };
     window.addEventListener("keydown", handleKeyDown);
     return () => window.removeEventListener("keydown", handleKeyDown);
   }, [isOpen, onClose]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/RowDetailDrawer.jsx` around lines 6 - 14, The Escape
key handler is being attached globally even when the drawer is closed; update
the useEffect that declares handleKeyDown so it only adds the window "keydown"
listener when isOpen is true and removes it when isOpen becomes false or on
unmount: inside the useEffect for isOpen/onClose, guard with if (!isOpen) return
(or only call addEventListener when isOpen), and ensure the cleanup always
removes the same handleKeyDown reference so onClose and handleKeyDown are
referenced correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/utils/queryEngine.js`:
- Around line 54-62: In paginate(), prevent unbounded requests by clamping the
parsed limit from this.queryString.limit to a safe maximum (e.g., MAX_LIMIT)
before computing skip; parse the incoming limit as an integer, ensure it's at
least 1, then set limit = Math.min(parsedLimit, MAX_LIMIT) (or a default if
missing/invalid), compute skip and apply this.query =
this.query.skip(skip).limit(limit); add a clear constant like MAX_LIMIT near
paginate() for easy adjustment and reference.
- Around line 7-31: The filter() method builds mongoQuery from this.queryString
and currently copies keys directly into mongoQuery before calling
this.query.find(mongoQuery), which permits NoSQL injection; fix by
sanitizing/whitelisting keys: either derive an allowedFields list from the
model/schema used by this class (e.g., check against this.model.schema.paths or
an explicit allowedFields array) and only copy keys that exist there, or at
minimum drop any key that begins with '$' or contains prototype pollution names
like '__proto__' or 'constructor' before adding to mongoQuery; ensure
special-suffix handlers (keys ending with _gt/_gte/_lt/_lte) also respect the
same whitelist/sanitization before setting field operators.
- Around line 12-29: The loop that builds mongoQuery in
backend/utils/queryEngine.js uses req.query string values directly (e.g., in the
_gt/_gte/_lt/_lte branches), causing lexicographic comparisons; update the logic
in that loop (the for...in over queryObj that constructs mongoQuery) so that
when handling comparison suffixes (_gt, _gte, _lt, _lte) you attempt to coerce
the string value to a Number (e.g., parseFloat and check !isNaN) and use the
numeric value in the $gt/$gte/$lt/$lte operators when valid; otherwise fall back
to the original string. Keep the coercion local to those branches (do not change
non-comparison keys) and preserve existing merging behavior (mongoQuery[field] =
{ ...mongoQuery[field], $gt: ... } etc.).

In `@frontend/src/components/RowDetailDrawer.jsx`:
- Around line 133-139: Guard the date rendering in the RowDetailDrawer JSX: when
field.type === "Date" compute const d = new Date(record[field.key]) and check d
is valid (e.g. !isNaN(d.getTime())) before calling d.toLocaleString(); if
invalid or missing, return the safe fallback string ("Empty") instead of letting
"Invalid Date" render. Update the value prop used in the input (and the other
similar occurrences in this file) to use this validity check rather than only
testing record[field.key].
- Around line 141-147: The render branch in RowDetailDrawer.jsx treats
null/objects incorrectly because the condition `typeof record[field.key] ===
"object"` and the value `JSON.stringify(record[field.key] ?? {}, null, 2)`
coerce null/undefined to `{}`; change the condition to explicitly handle
objects/arrays (e.g., use `field.type === "Object" || field.type === "Array" ||
Array.isArray(record[field.key]) || (record[field.key] !== null && typeof
record[field.key] === "object")`) and update the textarea value to only
JSON.stringify when the actual value is an object/array (e.g.,
`value={isObjectOrArray ? JSON.stringify(record[field.key], null, 2) :
(record[field.key] ?? "")}`) so null/undefined/empty values render as empty
string instead of `{}`.
- Around line 38-81: The drawer currently renders as a plain div (the element
with className "drawer-panel glass-panel") and the close button
(onClick={onClose} className="btn-icon") is icon-only without an accessible
name; add proper dialog semantics by giving the panel role="dialog" and
aria-modal="true", add an aria-labelledby that references the header title (give
the h2 a stable id), ensure the close button gets an accessible name (e.g.,
aria-label="Close" or a visually-hidden label), and implement basic focus
management when isOpen changes (move focus into the dialog — e.g., to the header
or the panel via tabIndex and focus — and restore focus on close) plus handle
Escape key to call onClose to meet expected modal behavior.

In `@frontend/src/pages/Database.jsx`:
- Around line 388-393: The inline style on the filter-menu glass-panel sets an
excessive zIndex of 9000000000; update the style for the <div
className="filter-menu glass-panel"> to use a sensible z-index (e.g., 1000) or
reference your app's existing z-index scale variable instead of the huge literal
so stacking behavior remains correct and cross-browser safe.
- Around line 436-439: The handler mutates objects inside queryParams.filters
via newFilters[idx].field which is a shallow-array copy only; instead create a
new filter object for the modified index and replace it in the copied array
before calling setQueryParams. In the onChange handlers that update
queryParams.filters (the handlers using idx, setQueryParams, and
queryParams.filters), do: clone the filters array, clone the filter object at
idx (e.g., const updated = { ...filters[idx], field: e.target.value }), assign
updated into the cloned array, then call setQueryParams(p => ({ ...p, filters:
clonedArray })) so no existing state objects are mutated.

---

Outside diff comments:
In `@frontend/src/pages/Database.jsx`:
- Around line 118-161: fetchData is being re-created on every keystroke because
it depends on queryParams, which causes the useEffect watching fetchData to fire
immediately; fix by introducing separate "draft" filter/sort state for the
filter UI (e.g., draftFilters, draftSort) and a distinct committed/applied query
state used for fetching (e.g., appliedQueryParams or committedQueryParams). Bind
filter inputs to draftFilters/draftSort and initialize/sync them from
queryParams when the filter menu opens, then update queryParams (setQueryParams
or setAppliedQueryParams) only when the "Apply Queries" button is clicked;
change fetchData to depend on appliedQueryParams (not the draft) so typing no
longer triggers API calls, and ensure the useEffect that calls fetchData depends
on fetchData/appliedQueryParams accordingly.

---

Nitpick comments:
In `@backend/utils/queryEngine.js`:
- Around line 9-10: The forEach callback currently uses a concise arrow that
returns the boolean from the delete expression (excludedFields.forEach(el =>
delete queryObj[el])), triggering the linter; change the callback to use a block
body that discards the return value — e.g., replace the arrow with a function
body that calls delete queryObj[el]; (or use a for...of loop) so the callback
does not return the delete result; update the code around excludedFields and
queryObj accordingly.

In `@frontend/src/components/RowDetailDrawer.jsx`:
- Around line 6-14: The Escape key handler is being attached globally even when
the drawer is closed; update the useEffect that declares handleKeyDown so it
only adds the window "keydown" listener when isOpen is true and removes it when
isOpen becomes false or on unmount: inside the useEffect for isOpen/onClose,
guard with if (!isOpen) return (or only call addEventListener when isOpen), and
ensure the cleanup always removes the same handleKeyDown reference so onClose
and handleKeyDown are referenced correctly.

In `@frontend/src/pages/LandingPage/style.css`:
- Around line 44-47: Rename the `@keyframes` identifier from "pulseGlow" to
kebab-case "pulse-glow" and update all usages that reference it (e.g.,
animation, animation-name, or shorthand animation properties that currently use
pulseGlow) to "pulse-glow" so the keyframes-name-pattern rule is satisfied;
ensure the `@keyframes` declaration and every CSS rule that applies the animation
are updated consistently.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3cf8a3e and 141a523.

📒 Files selected for processing (11)
  • backend/controllers/data.controller.js
  • backend/controllers/project.controller.js
  • backend/utils/queryEngine.js
  • diff_output.txt
  • frontend/src/components/CollectionTable.jsx
  • frontend/src/components/DatabaseSidebar.jsx
  • frontend/src/components/RowDetailDrawer.jsx
  • frontend/src/pages/Database.jsx
  • frontend/src/pages/LandingPage/index.jsx
  • frontend/src/pages/LandingPage/style.css
  • next.txt
💤 Files with no reviewable changes (2)
  • next.txt
  • diff_output.txt

Comment on lines +7 to +31
filter() {
const queryObj = { ...this.queryString };
const excludedFields = ['page', 'sort', 'limit', 'fields'];
excludedFields.forEach(el => delete queryObj[el]);

const mongoQuery = {};
for (const key in queryObj) {
if (key.endsWith('_gt')) {
const field = key.replace(/_gt$/, '');
mongoQuery[field] = { ...mongoQuery[field], $gt: queryObj[key] };
} else if (key.endsWith('_gte')) {
const field = key.replace(/_gte$/, '');
mongoQuery[field] = { ...mongoQuery[field], $gte: queryObj[key] };
} else if (key.endsWith('_lt')) {
const field = key.replace(/_lt$/, '');
mongoQuery[field] = { ...mongoQuery[field], $lt: queryObj[key] };
} else if (key.endsWith('_lte')) {
const field = key.replace(/_lte$/, '');
mongoQuery[field] = { ...mongoQuery[field], $lte: queryObj[key] };
} else {
mongoQuery[key] = queryObj[key];
}
}

this.query = this.query.find(mongoQuery);

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 | 🟠 Major

User-supplied query keys are passed directly into MongoDB filter — potential NoSQL injection vector.

An attacker can craft query parameters like $where=... or __proto__ that flow straight into mongoQuery and then into Model.find(). While Mongoose provides some protections, the else branch on line 27 passes arbitrary keys unvalidated. You should either whitelist allowed field names (e.g., check against the collection's schema model) or at minimum reject keys starting with $.

Proposed safeguard
         for (const key in queryObj) {
+            // Strip any keys that could be Mongo operators
+            const baseField = key.replace(/_(gt|gte|lt|lte)$/, '');
+            if (baseField.startsWith('$') || baseField.startsWith('__')) continue;
+
             if (key.endsWith('_gt')) {
🧰 Tools
🪛 Biome (2.4.4)

[error] 10-10: This callback passed to forEach() iterable method should not return a value.

(lint/suspicious/useIterableCallbackReturn)

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

In `@backend/utils/queryEngine.js` around lines 7 - 31, The filter() method builds
mongoQuery from this.queryString and currently copies keys directly into
mongoQuery before calling this.query.find(mongoQuery), which permits NoSQL
injection; fix by sanitizing/whitelisting keys: either derive an allowedFields
list from the model/schema used by this class (e.g., check against
this.model.schema.paths or an explicit allowedFields array) and only copy keys
that exist there, or at minimum drop any key that begins with '$' or contains
prototype pollution names like '__proto__' or 'constructor' before adding to
mongoQuery; ensure special-suffix handlers (keys ending with _gt/_gte/_lt/_lte)
also respect the same whitelist/sanitization before setting field operators.

Comment on lines +12 to +29
const mongoQuery = {};
for (const key in queryObj) {
if (key.endsWith('_gt')) {
const field = key.replace(/_gt$/, '');
mongoQuery[field] = { ...mongoQuery[field], $gt: queryObj[key] };
} else if (key.endsWith('_gte')) {
const field = key.replace(/_gte$/, '');
mongoQuery[field] = { ...mongoQuery[field], $gte: queryObj[key] };
} else if (key.endsWith('_lt')) {
const field = key.replace(/_lt$/, '');
mongoQuery[field] = { ...mongoQuery[field], $lt: queryObj[key] };
} else if (key.endsWith('_lte')) {
const field = key.replace(/_lte$/, '');
mongoQuery[field] = { ...mongoQuery[field], $lte: queryObj[key] };
} else {
mongoQuery[key] = queryObj[key];
}
}

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 | 🔴 Critical

Query values are always strings — numeric comparisons will silently misbehave.

req.query values are always strings. When you build { price: { $gt: "5" } }, MongoDB performs a lexicographic comparison, not a numeric one. This means price_gt=5 won't correctly filter numeric fields (e.g., "50" is less than "5" lexicographically).

You need to coerce values to their appropriate types. At minimum, attempt numeric parsing for comparison operators:

Proposed fix
+        const coerce = (val) => {
+            if (!isNaN(val) && val.trim() !== '') return Number(val);
+            // Could also handle date parsing here
+            return val;
+        };
+
         const mongoQuery = {};
         for (const key in queryObj) {
             if (key.endsWith('_gt')) {
                 const field = key.replace(/_gt$/, '');
-                mongoQuery[field] = { ...mongoQuery[field], $gt: queryObj[key] };
+                mongoQuery[field] = { ...mongoQuery[field], $gt: coerce(queryObj[key]) };
             } else if (key.endsWith('_gte')) {
                 const field = key.replace(/_gte$/, '');
-                mongoQuery[field] = { ...mongoQuery[field], $gte: queryObj[key] };
+                mongoQuery[field] = { ...mongoQuery[field], $gte: coerce(queryObj[key]) };
             } else if (key.endsWith('_lt')) {
                 const field = key.replace(/_lt$/, '');
-                mongoQuery[field] = { ...mongoQuery[field], $lt: queryObj[key] };
+                mongoQuery[field] = { ...mongoQuery[field], $lt: coerce(queryObj[key]) };
             } else if (key.endsWith('_lte')) {
                 const field = key.replace(/_lte$/, '');
-                mongoQuery[field] = { ...mongoQuery[field], $lte: queryObj[key] };
+                mongoQuery[field] = { ...mongoQuery[field], $lte: coerce(queryObj[key]) };
             } else {
                 mongoQuery[key] = queryObj[key];
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/utils/queryEngine.js` around lines 12 - 29, The loop that builds
mongoQuery in backend/utils/queryEngine.js uses req.query string values directly
(e.g., in the _gt/_gte/_lt/_lte branches), causing lexicographic comparisons;
update the logic in that loop (the for...in over queryObj that constructs
mongoQuery) so that when handling comparison suffixes (_gt, _gte, _lt, _lte) you
attempt to coerce the string value to a Number (e.g., parseFloat and check
!isNaN) and use the numeric value in the $gt/$gte/$lt/$lte operators when valid;
otherwise fall back to the original string. Keep the coercion local to those
branches (do not change non-comparison keys) and preserve existing merging
behavior (mongoQuery[field] = { ...mongoQuery[field], $gt: ... } etc.).

Comment on lines +54 to +62
paginate() {
const page = parseInt(this.queryString.page, 10) || 1;
const limit = parseInt(this.queryString.limit, 10) || 100;
const skip = (page - 1) * limit;

this.query = this.query.skip(skip).limit(limit);

return this;
}

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 | 🟠 Major

No upper bound on limit — allows unbounded data retrieval.

A client can pass limit=10000000 and dump the entire collection in one request, causing potential memory exhaustion and slow responses. Clamp to a reasonable maximum.

Proposed fix
     paginate() {
+        const MAX_LIMIT = 500;
         const page = parseInt(this.queryString.page, 10) || 1;
-        const limit = parseInt(this.queryString.limit, 10) || 100;
+        const limit = Math.min(parseInt(this.queryString.limit, 10) || 100, MAX_LIMIT);
         const skip = (page - 1) * limit;
📝 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
paginate() {
const page = parseInt(this.queryString.page, 10) || 1;
const limit = parseInt(this.queryString.limit, 10) || 100;
const skip = (page - 1) * limit;
this.query = this.query.skip(skip).limit(limit);
return this;
}
paginate() {
const MAX_LIMIT = 500;
const page = parseInt(this.queryString.page, 10) || 1;
const limit = Math.min(parseInt(this.queryString.limit, 10) || 100, MAX_LIMIT);
const skip = (page - 1) * limit;
this.query = this.query.skip(skip).limit(limit);
return this;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/utils/queryEngine.js` around lines 54 - 62, In paginate(), prevent
unbounded requests by clamping the parsed limit from this.queryString.limit to a
safe maximum (e.g., MAX_LIMIT) before computing skip; parse the incoming limit
as an integer, ensure it's at least 1, then set limit = Math.min(parsedLimit,
MAX_LIMIT) (or a default if missing/invalid), compute skip and apply this.query
= this.query.skip(skip).limit(limit); add a clear constant like MAX_LIMIT near
paginate() for easy adjustment and reference.

Comment on lines +38 to +81
<div
className="drawer-panel glass-panel"
style={{
position: "fixed",
top: 0,
right: 0,
bottom: 0,
width: isWideForm ? "600px" : "450px",
maxWidth: "100%",
zIndex: 1000,
display: "flex",
flexDirection: "column",
transform: isOpen ? "translateX(0)" : "translateX(100%)",
animation: "slideInRight 0.3s cubic-bezier(0.16, 1, 0.3, 1)",
borderLeft: "1px solid var(--color-border)",
background: "var(--color-bg-card)",
boxShadow: "-10px 0 30px rgba(0,0,0,0.3)"
}}
>
{/* Header */}
<div className="drawer-header" style={{
padding: "1.5rem",
borderBottom: "1px solid var(--color-border)",
display: "flex",
justifyContent: "space-between",
alignItems: "center"
}}>
<div>
<h2 style={{ fontSize: "1.25rem", fontWeight: 600, margin: 0, display: "flex", alignItems: "center", gap: "8px" }}>
<FileText size={20} className="text-primary" />
Record Details
</h2>
<p style={{ fontSize: "0.85rem", color: "var(--color-text-muted)", margin: "4px 0 0 0", fontFamily: "monospace" }}>
ID: {record._id}
</p>
</div>
<button
onClick={onClose}
className="btn-icon"
style={{ color: "var(--color-text-muted)" }}
>
<X size={20} />
</button>
</div>

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 | 🟠 Major

Add proper dialog accessibility semantics and labeling.

Line 38 renders a modal-like panel without dialog semantics/focus context, and Line 74 has an icon-only close button without an accessible name. This makes keyboard/screen-reader usage fragile.

Suggested patch
-      <div
-        className="drawer-panel glass-panel"
+      <div
+        className="drawer-panel glass-panel"
+        role="dialog"
+        aria-modal="true"
+        aria-labelledby="row-detail-drawer-title"
+        tabIndex={-1}
         style={{
@@
-            <h2 style={{ fontSize: "1.25rem", fontWeight: 600, margin: 0, display: "flex", alignItems: "center", gap: "8px" }}>
+            <h2 id="row-detail-drawer-title" style={{ fontSize: "1.25rem", fontWeight: 600, margin: 0, display: "flex", alignItems: "center", gap: "8px" }}>
@@
           <button
             onClick={onClose}
             className="btn-icon"
+            aria-label="Close record details"
             style={{ color: "var(--color-text-muted)" }}
           >
📝 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
<div
className="drawer-panel glass-panel"
style={{
position: "fixed",
top: 0,
right: 0,
bottom: 0,
width: isWideForm ? "600px" : "450px",
maxWidth: "100%",
zIndex: 1000,
display: "flex",
flexDirection: "column",
transform: isOpen ? "translateX(0)" : "translateX(100%)",
animation: "slideInRight 0.3s cubic-bezier(0.16, 1, 0.3, 1)",
borderLeft: "1px solid var(--color-border)",
background: "var(--color-bg-card)",
boxShadow: "-10px 0 30px rgba(0,0,0,0.3)"
}}
>
{/* Header */}
<div className="drawer-header" style={{
padding: "1.5rem",
borderBottom: "1px solid var(--color-border)",
display: "flex",
justifyContent: "space-between",
alignItems: "center"
}}>
<div>
<h2 style={{ fontSize: "1.25rem", fontWeight: 600, margin: 0, display: "flex", alignItems: "center", gap: "8px" }}>
<FileText size={20} className="text-primary" />
Record Details
</h2>
<p style={{ fontSize: "0.85rem", color: "var(--color-text-muted)", margin: "4px 0 0 0", fontFamily: "monospace" }}>
ID: {record._id}
</p>
</div>
<button
onClick={onClose}
className="btn-icon"
style={{ color: "var(--color-text-muted)" }}
>
<X size={20} />
</button>
</div>
<div
className="drawer-panel glass-panel"
role="dialog"
aria-modal="true"
aria-labelledby="row-detail-drawer-title"
tabIndex={-1}
style={{
position: "fixed",
top: 0,
right: 0,
bottom: 0,
width: isWideForm ? "600px" : "450px",
maxWidth: "100%",
zIndex: 1000,
display: "flex",
flexDirection: "column",
transform: isOpen ? "translateX(0)" : "translateX(100%)",
animation: "slideInRight 0.3s cubic-bezier(0.16, 1, 0.3, 1)",
borderLeft: "1px solid var(--color-border)",
background: "var(--color-bg-card)",
boxShadow: "-10px 0 30px rgba(0,0,0,0.3)"
}}
>
{/* Header */}
<div className="drawer-header" style={{
padding: "1.5rem",
borderBottom: "1px solid var(--color-border)",
display: "flex",
justifyContent: "space-between",
alignItems: "center"
}}>
<div>
<h2 id="row-detail-drawer-title" style={{ fontSize: "1.25rem", fontWeight: 600, margin: 0, display: "flex", alignItems: "center", gap: "8px" }}>
<FileText size={20} className="text-primary" />
Record Details
</h2>
<p style={{ fontSize: "0.85rem", color: "var(--color-text-muted)", margin: "4px 0 0 0", fontFamily: "monospace" }}>
ID: {record._id}
</p>
</div>
<button
onClick={onClose}
className="btn-icon"
aria-label="Close record details"
style={{ color: "var(--color-text-muted)" }}
>
<X size={20} />
</button>
</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/RowDetailDrawer.jsx` around lines 38 - 81, The drawer
currently renders as a plain div (the element with className "drawer-panel
glass-panel") and the close button (onClick={onClose} className="btn-icon") is
icon-only without an accessible name; add proper dialog semantics by giving the
panel role="dialog" and aria-modal="true", add an aria-labelledby that
references the header title (give the h2 a stable id), ensure the close button
gets an accessible name (e.g., aria-label="Close" or a visually-hidden label),
and implement basic focus management when isOpen changes (move focus into the
dialog — e.g., to the header or the panel via tabIndex and focus — and restore
focus on close) plus handle Escape key to call onClose to meet expected modal
behavior.

Comment on lines +133 to +139
) : field.type === "Date" ? (
<input
type="text"
className="form-input"
readOnly
value={record[field.key] ? new Date(record[field.key]).toLocaleString() : "Empty"}
style={{ cursor: "default" }}

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

Guard date formatting to avoid exposing "Invalid Date" in the UI.

Lines 138/185/191 directly format new Date(...). Invalid values will render "Invalid Date" to users instead of a safe fallback.

Suggested patch
 export default function RowDetailDrawer({ isOpen, onClose, record, fields = [], onEdit }) {
+  const formatDate = (value) => {
+    const d = new Date(value);
+    return Number.isNaN(d.getTime()) ? "Empty" : d.toLocaleString();
+  };
@@
-                    value={record[field.key] ? new Date(record[field.key]).toLocaleString() : "Empty"}
+                    value={record[field.key] ? formatDate(record[field.key]) : "Empty"}
@@
-                         <span style={{ color: "var(--color-text-main)" }}>{new Date(record.createdAt).toLocaleString()}</span>
+                         <span style={{ color: "var(--color-text-main)" }}>{formatDate(record.createdAt)}</span>
@@
-                         <span style={{ color: "var(--color-text-main)" }}>{new Date(record.updatedAt).toLocaleString()}</span>
+                         <span style={{ color: "var(--color-text-main)" }}>{formatDate(record.updatedAt)}</span>

Also applies to: 182-191

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

In `@frontend/src/components/RowDetailDrawer.jsx` around lines 133 - 139, Guard
the date rendering in the RowDetailDrawer JSX: when field.type === "Date"
compute const d = new Date(record[field.key]) and check d is valid (e.g.
!isNaN(d.getTime())) before calling d.toLocaleString(); if invalid or missing,
return the safe fallback string ("Empty") instead of letting "Invalid Date"
render. Update the value prop used in the input (and the other similar
occurrences in this file) to use this validity check rather than only testing
record[field.key].

Comment on lines +141 to +147
) : field.type === "Object" || field.type === "Array" || typeof record[field.key] === "object" ? (
<textarea
className="form-input custom-scrollbar"
readOnly
rows={4}
value={JSON.stringify(record[field.key] ?? {}, null, 2)}
style={{ cursor: "default", resize: "vertical", fontFamily: "monospace", fontSize: "0.85rem", color: "#3ECF8E" }}

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

Avoid rendering null/empty object fields as {} by default.

Line 141 treats null as an object, and Line 146 coerces empty values to {}. That misrepresents missing data.

Suggested patch
-                ) : field.type === "Object" || field.type === "Array" || typeof record[field.key] === "object" ? (
+                ) : field.type === "Object" || field.type === "Array" || (record[field.key] !== null && typeof record[field.key] === "object") ? (
                    <textarea
@@
-                     value={JSON.stringify(record[field.key] ?? {}, null, 2)}
+                     value={record[field.key] == null ? "Empty" : JSON.stringify(record[field.key], null, 2)}
📝 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
) : field.type === "Object" || field.type === "Array" || typeof record[field.key] === "object" ? (
<textarea
className="form-input custom-scrollbar"
readOnly
rows={4}
value={JSON.stringify(record[field.key] ?? {}, null, 2)}
style={{ cursor: "default", resize: "vertical", fontFamily: "monospace", fontSize: "0.85rem", color: "#3ECF8E" }}
) : field.type === "Object" || field.type === "Array" || (record[field.key] !== null && typeof record[field.key] === "object") ? (
<textarea
className="form-input custom-scrollbar"
readOnly
rows={4}
value={record[field.key] == null ? "Empty" : JSON.stringify(record[field.key], null, 2)}
style={{ cursor: "default", resize: "vertical", fontFamily: "monospace", fontSize: "0.85rem", color: "#3ECF8E" }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/RowDetailDrawer.jsx` around lines 141 - 147, The
render branch in RowDetailDrawer.jsx treats null/objects incorrectly because the
condition `typeof record[field.key] === "object"` and the value
`JSON.stringify(record[field.key] ?? {}, null, 2)` coerce null/undefined to
`{}`; change the condition to explicitly handle objects/arrays (e.g., use
`field.type === "Object" || field.type === "Array" ||
Array.isArray(record[field.key]) || (record[field.key] !== null && typeof
record[field.key] === "object")`) and update the textarea value to only
JSON.stringify when the actual value is an object/array (e.g.,
`value={isObjectOrArray ? JSON.stringify(record[field.key], null, 2) :
(record[field.key] ?? "")}`) so null/undefined/empty values render as empty
string instead of `{}`.

Comment on lines +388 to +393
<div className="filter-menu glass-panel" style={{
position: 'absolute', right: 0, top: 'calc(100% + 10px)', width: '300px',
zIndex: 9000000000, padding: '1rem', display: 'flex', flexDirection: 'column', gap: '1rem',
background: '#151515', border: '1px solid var(--color-border)',
boxShadow: '0 10px 30px rgba(0,0,0,0.7)', borderRadius: '8px'
}}>

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

zIndex: 9000000000 is excessive.

This value is unreasonable and may cause issues with z-index stacking on some browsers (values above 2^31 - 1 can overflow to negative on some engines). Use a sensible value like 1000 or match your existing z-index scale.

Proposed fix
-  zIndex: 9000000000, padding: '1rem',
+  zIndex: 1000, padding: '1rem',
📝 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
<div className="filter-menu glass-panel" style={{
position: 'absolute', right: 0, top: 'calc(100% + 10px)', width: '300px',
zIndex: 9000000000, padding: '1rem', display: 'flex', flexDirection: 'column', gap: '1rem',
background: '#151515', border: '1px solid var(--color-border)',
boxShadow: '0 10px 30px rgba(0,0,0,0.7)', borderRadius: '8px'
}}>
<div className="filter-menu glass-panel" style={{
position: 'absolute', right: 0, top: 'calc(100% + 10px)', width: '300px',
zIndex: 1000, padding: '1rem', display: 'flex', flexDirection: 'column', gap: '1rem',
background: '#151515', border: '1px solid var(--color-border)',
boxShadow: '0 10px 30px rgba(0,0,0,0.7)', borderRadius: '8px'
}}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/Database.jsx` around lines 388 - 393, The inline style on
the filter-menu glass-panel sets an excessive zIndex of 9000000000; update the
style for the <div className="filter-menu glass-panel"> to use a sensible
z-index (e.g., 1000) or reference your app's existing z-index scale variable
instead of the huge literal so stacking behavior remains correct and
cross-browser safe.

Comment on lines +436 to +439
onChange={e => {
const newFilters = [...queryParams.filters];
newFilters[idx].field = e.target.value;
setQueryParams(p => ({ ...p, filters: newFilters }));

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 | 🟠 Major

Direct mutation of state objects — React anti-pattern.

[...queryParams.filters] creates a shallow copy of the array, but the objects inside are still the same references. Mutating newFilters[idx].field on line 438 mutates the object that's already in React state, bypassing React's change detection and potentially causing stale renders.

Create new objects for modified entries:

Proposed fix (apply to all three onChange handlers at lines 436, 452, 472)
  onChange={e => {
-   const newFilters = [...queryParams.filters];
-   newFilters[idx].field = e.target.value;
+   const newFilters = queryParams.filters.map((f, i) =>
+     i === idx ? { ...f, field: e.target.value } : f
+   );
    setQueryParams(p => ({ ...p, filters: newFilters }));
  }}
📝 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
onChange={e => {
const newFilters = [...queryParams.filters];
newFilters[idx].field = e.target.value;
setQueryParams(p => ({ ...p, filters: newFilters }));
onChange={e => {
const newFilters = queryParams.filters.map((f, i) =>
i === idx ? { ...f, field: e.target.value } : f
);
setQueryParams(p => ({ ...p, filters: newFilters }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/Database.jsx` around lines 436 - 439, The handler mutates
objects inside queryParams.filters via newFilters[idx].field which is a
shallow-array copy only; instead create a new filter object for the modified
index and replace it in the copied array before calling setQueryParams. In the
onChange handlers that update queryParams.filters (the handlers using idx,
setQueryParams, and queryParams.filters), do: clone the filters array, clone the
filter object at idx (e.g., const updated = { ...filters[idx], field:
e.target.value }), assign updated into the cloned array, then call
setQueryParams(p => ({ ...p, filters: clonedArray })) so no existing state
objects are mutated.

@gemini-code-assist gemini-code-assist 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.

Code Review

This PR introduces a new QueryEngine for the backend and redesigns the Database UI on the frontend. While this is a significant upgrade, the filter method in backend/utils/queryEngine.js contains a high-severity NoSQL injection vulnerability. The method fails to validate query parameters before passing them to MongoDB's find method, potentially allowing attackers to inject arbitrary MongoDB operators. Besides this vulnerability, consider the following:

  • Refactor the filter logic in queryEngine.js to improve code readability.
  • Refactor the query string creation in Database.jsx to improve code readability.
  • Manage z-index values in Database.jsx using CSS variables.
  • Move inline styles in components to separate CSS files to improve maintainability.

Comment on lines +388 to +393
<div className="filter-menu glass-panel" style={{
position: 'absolute', right: 0, top: 'calc(100% + 10px)', width: '300px',
zIndex: 9000000000, padding: '1rem', display: 'flex', flexDirection: 'column', gap: '1rem',
background: '#151515', border: '1px solid var(--color-border)',
boxShadow: '0 10px 30px rgba(0,0,0,0.7)', borderRadius: '8px'
}}>

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.

critical

Filter menu ke liye zIndex: 9000000000 ka istemal kiya gaya hai. Yeh ek bahut bada "magic number" hai aur z-index conflicts paida kar sakta hai. Isse maintain karna bhi mushkil hai.

General rule ke mutabik, itne bade z-index values se bachna chahiye. Behtar hoga ki aap ek chhota, apekshakrit value (jaise 1100) istemal karein aur sabhi z-index values ko CSS variables mein centralize karein. Isse code scalable aur maintainable rahega.

                      <div className="filter-menu glass-panel" style={{ 
                        position: 'absolute', right: 0, top: 'calc(100% + 10px)', width: '300px', 
                        zIndex: 1100, padding: '1rem', display: 'flex', flexDirection: 'column', gap: '1rem',
                        background: '#151515', border: '1px solid var(--color-border)', 
                        boxShadow: '0 10px 30px rgba(0,0,0,0.7)', borderRadius: '8px'
                      }}>
References
  1. A large magic number (9000000000) is used for z-index, which should be avoided in favor of smaller, managed values, preferably through CSS variables.

Comment on lines +7 to +34
filter() {
const queryObj = { ...this.queryString };
const excludedFields = ['page', 'sort', 'limit', 'fields'];
excludedFields.forEach(el => delete queryObj[el]);

const mongoQuery = {};
for (const key in queryObj) {
if (key.endsWith('_gt')) {
const field = key.replace(/_gt$/, '');
mongoQuery[field] = { ...mongoQuery[field], $gt: queryObj[key] };
} else if (key.endsWith('_gte')) {
const field = key.replace(/_gte$/, '');
mongoQuery[field] = { ...mongoQuery[field], $gte: queryObj[key] };
} else if (key.endsWith('_lt')) {
const field = key.replace(/_lt$/, '');
mongoQuery[field] = { ...mongoQuery[field], $lt: queryObj[key] };
} else if (key.endsWith('_lte')) {
const field = key.replace(/_lte$/, '');
mongoQuery[field] = { ...mongoQuery[field], $lte: queryObj[key] };
} else {
mongoQuery[key] = queryObj[key];
}
}

this.query = this.query.find(mongoQuery);

return this;
}

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.

security-high high

The filter method is vulnerable to NoSQL injection. It iterates over the queryString object (derived from req.query) and directly assigns values to the mongoQuery object without validation. If an attacker provides an object containing MongoDB operators (e.g., ?field[$ne]=value), Express's query parser (typically qs) will parse this into a nested object, which is then passed directly to Mongoose's find() method. This allows attackers to bypass intended filters or perform unauthorized data retrieval. You should ensure that all filter values are primitives (strings, numbers, or booleans) or use a sanitization library like mongo-sanitize.

To prevent NoSQL injection, ensure that the value is a primitive.

    filter() {
        const queryObj = { ...this.queryString };
        const excludedFields = ['page', 'sort', 'limit', 'fields'];
        excludedFields.forEach(el => delete queryObj[el]);

        const mongoQuery = {};
        for (const key in queryObj) {
            // Prevent NoSQL injection by ensuring the value is a primitive
            if (typeof queryObj[key] === 'object') continue;

            if (key.endsWith('_gt')) {
                const field = key.replace(/_gt$/, '');
                mongoQuery[field] = { ...mongoQuery[field], $gt: queryObj[key] };
            } else if (key.endsWith('_gte')) {
                const field = key.replace(/_gte$/, '');
                mongoQuery[field] = { ...mongoQuery[field], $gte: queryObj[key] };
            } else if (key.endsWith('_lt')) {
                const field = key.replace(/_lt$/, '');
                mongoQuery[field] = { ...mongoQuery[field], $lt: queryObj[key] };
            } else if (key.endsWith('_lte')) {
                const field = key.replace(/_lte$/, '');
                mongoQuery[field] = { ...mongoQuery[field], $lte: queryObj[key] };
            } else {
                mongoQuery[key] = queryObj[key];
            }
        }
        
        this.query = this.query.find(mongoQuery);
        
        return this;
    }

Comment on lines +38 to +55
<div
className="drawer-panel glass-panel"
style={{
position: "fixed",
top: 0,
right: 0,
bottom: 0,
width: isWideForm ? "600px" : "450px",
maxWidth: "100%",
zIndex: 1000,
display: "flex",
flexDirection: "column",
transform: isOpen ? "translateX(0)" : "translateX(100%)",
animation: "slideInRight 0.3s cubic-bezier(0.16, 1, 0.3, 1)",
borderLeft: "1px solid var(--color-border)",
background: "var(--color-bg-card)",
boxShadow: "-10px 0 30px rgba(0,0,0,0.3)"
}}

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.

medium

Is component mein bahut saare inline styles ka istemal kiya gaya hai. Isse component ko padhna aur maintain karna mushkil ho jaata hai, aur yeh styling ko component logic ke saath mix kar deta hai.

Behtar hoga ki in styles ko ek alag CSS file mein move kiya jaaye aur CSS classes ka istemal kiya jaaye. Aap CSS Modules ya kisi CSS-in-JS library (jaise styled-components) ka bhi istemal kar sakte hain. Isse code saaf aur organized rahega.

Comment on lines +122 to +137
// Build query string from state
let queryStr = `?page=${queryParams.page}&limit=${queryParams.limit}`;
if (queryParams.sort) {
queryStr += `&sort=${queryParams.sort}`;
}

// Append advanced filters
queryParams.filters.forEach(filter => {
if (filter.field && filter.operator && filter.value !== '') {
if (filter.operator === '=') {
queryStr += `&${filter.field}=${encodeURIComponent(filter.value)}`;
} else {
queryStr += `&${filter.field}${filter.operator}=${encodeURIComponent(filter.value)}`;
}
}
});

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.

medium

Query string ko manually string concatenation se banaya ja raha hai. Isse code padhna thoda mushkil ho jaata hai aur URL encoding mein galtiyan ho sakti hain.

URLSearchParams ka istemal karke isse aur saaf aur surakshit banaya ja sakta hai. Yeh automatically values ko encode kar deta hai aur code ko zyada readable banata hai.

      // Build query string from state
      const params = new URLSearchParams({
        page: queryParams.page,
        limit: queryParams.limit,
      });

      if (queryParams.sort) {
        params.append('sort', queryParams.sort);
      }

      // Append advanced filters
      queryParams.filters.forEach(filter => {
        if (filter.field && filter.operator && filter.value !== '') {
          const key = filter.operator === '=' ? filter.field : `${filter.field}${filter.operator}`;
          params.append(key, filter.value);
        }
      });
      const queryStr = `?${params.toString()}`;

@yash-pouranik
yash-pouranik merged commit 75c419a into main Feb 25, 2026
7 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Mar 6, 2026
3 tasks
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.

1 participant