diff --git a/backend/controllers/data.controller.js b/backend/controllers/data.controller.js
index 4a5c5df73..50ee2ec47 100644
--- a/backend/controllers/data.controller.js
+++ b/backend/controllers/data.controller.js
@@ -3,6 +3,7 @@ const mongoose = require('mongoose');
const Project = require("../models/Project");
const { getConnection } = require("../utils/connection.manager");
const { getCompiledModel } = require("../utils/injectModel");
+const QueryEngine = require("../utils/queryEngine");
// Validate MongoDB ObjectId
const isValidId = (id) => mongoose.Types.ObjectId.isValid(id);
@@ -82,7 +83,12 @@ module.exports.getAllData = async (req, res) => {
const connection = await getConnection(project._id);
const Model = getCompiledModel(connection, collectionConfig, project._id, project.resources.db.isExternal);
- const data = await Model.find({}).limit(100).lean();
+ const features = new QueryEngine(Model.find(), req.query)
+ .filter()
+ .sort()
+ .paginate();
+
+ const data = await features.query.lean();
console.timeEnd("getall")
res.json(data);
} catch (err) {
diff --git a/backend/controllers/project.controller.js b/backend/controllers/project.controller.js
index 759b9077c..39a825c6e 100644
--- a/backend/controllers/project.controller.js
+++ b/backend/controllers/project.controller.js
@@ -10,6 +10,7 @@ const { encrypt } = require('../utils/encryption');
const { URL } = require('url');
const { getConnection } = require("../utils/connection.manager");
const { getCompiledModel } = require("../utils/injectModel")
+const QueryEngine = require("../utils/queryEngine");
const { storageRegistry } = require("../utils/registry");
const { deleteProjectByApiKeyCache, setProjectById, getProjectById, deleteProjectById } = require("../services/redisCaching");
const { getPublicIp } = require("../utils/network");
@@ -332,7 +333,12 @@ module.exports.getData = async (req, res) => {
// const collectionsList = await mongoose.connection.db.listCollections({ name: finalCollectionName }).toArray();
- const data = await model.find({}).limit(50);
+ const features = new QueryEngine(model.find(), req.query)
+ .filter()
+ .sort()
+ .paginate();
+
+ const data = await features.query.lean();
// let data = [];
// if (collectionsList.length > 0) {
diff --git a/backend/utils/queryEngine.js b/backend/utils/queryEngine.js
new file mode 100644
index 000000000..6a65d94bd
--- /dev/null
+++ b/backend/utils/queryEngine.js
@@ -0,0 +1,65 @@
+class QueryEngine {
+ constructor(query, queryString) {
+ this.query = query;
+ this.queryString = queryString;
+ }
+
+ 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;
+ }
+
+ sort() {
+ if (this.queryString.sort) {
+ const sortBy = this.queryString.sort.split(',').map(item => {
+ const [field, order] = item.split(':');
+ if (order && (order === '-1' || order.toLowerCase() === 'desc')) {
+ return `-${field}`;
+ }
+ return field;
+ }).join(' ');
+
+ this.query = this.query.sort(sortBy);
+ } else {
+ this.query = this.query.sort('-createdAt');
+ }
+
+ return this;
+ }
+
+ 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;
+ }
+}
+
+module.exports = QueryEngine;
diff --git a/diff_output.txt b/diff_output.txt
deleted file mode 100644
index 7f832bb06..000000000
--- a/diff_output.txt
+++ /dev/null
@@ -1,261 +0,0 @@
-diff --git a/backend/app.js b/backend/app.js
-index 25410ca6..d0a80b08 100644
---- a/backend/app.js
-+++ b/backend/app.js
-@@ -38,18 +38,24 @@ if (process.env.NODE_ENV === 'development') {
-
- const adminCorsOptions = {
- origin: function (origin, callback) {
-- console.time("cors for admin chk")
-- // Allow requests with no origin (like mobile apps or curl)
-- if (!origin || adminWhitelist.indexOf(origin) !== -1) {
-+ const start = process.hrtime.bigint();
-+
-+ const allowed = !origin || adminWhitelist.includes(origin);
-+
-+ const end = process.hrtime.bigint();
-+ console.log("Pure CORS check time:",
-+ Number(end - start) / 1e6, "ms");
-+
-+ if (allowed) {
- callback(null, true);
- } else {
- callback(new Error('Not allowed by CORS for Admin access'));
- }
-- console.timeEnd("cors for admin chk")
- },
- credentials: true
- };
-
-+
- if (process.env.NODE_ENV !== 'test') {
- GC.garbageCollect();
- GC.storageGarbageCollect();
-diff --git a/backend/controllers/data.controller.js b/backend/controllers/data.controller.js
-index f64f52ed..4a5c5df7 100644
---- a/backend/controllers/data.controller.js
-+++ b/backend/controllers/data.controller.js
-@@ -10,6 +10,7 @@ const isValidId = (id) => mongoose.Types.ObjectId.isValid(id);
- // INSERT DATA
- module.exports.insertData = async (req, res) => {
- try {
-+ console.time("insert data")
- const { collectionName } = req.params;
- const project = req.project;
-
-@@ -61,6 +62,7 @@ module.exports.insertData = async (req, res) => {
- );
- }
-
-+ console.timeEnd("insert data")
- res.status(201).json(result);
- } catch (err) {
- res.status(500).json({ error: err.message });
-diff --git a/backend/controllers/project.controller.js b/backend/controllers/project.controller.js
-index d447725c..759b9077 100644
---- a/backend/controllers/project.controller.js
-+++ b/backend/controllers/project.controller.js
-@@ -262,6 +262,8 @@ module.exports.createCollection = async (req, res) => {
-
- await deleteProjectById(projectId);
- await setProjectById(projectId, project);
-+ await deleteProjectByApiKeyCache(project.apiKey);
-+ await deleteProjectByApiKeyCache(project.apiKey);
- // Safe Response
- const projectObj = project.toObject();
- delete projectObj.apiKey;
-@@ -345,6 +347,7 @@ module.exports.getData = async (req, res) => {
-
- module.exports.insertData = async (req, res) => {
- try {
-+ console.time("insert data")
- const { projectId, collectionName } = req.params;
- const project = await Project.findOne({ _id: projectId, owner: req.user._id });
- if (!project) return res.status(404).json({ error: "Project not found." });
-@@ -377,6 +380,7 @@ module.exports.insertData = async (req, res) => {
- project.databaseUsed = (project.databaseUsed || 0) + docSize;
- }
- await project.save();
-+ console.timeEnd("insert data")
-
- res.json(result);
- } catch (err) {
-diff --git a/backend/middleware/verifyApiKey.js b/backend/middleware/verifyApiKey.js
-index 804dd9ff..bdee2ce5 100644
---- a/backend/middleware/verifyApiKey.js
-+++ b/backend/middleware/verifyApiKey.js
-@@ -7,7 +7,6 @@ const {
-
- module.exports = async (req, res, next) => {
- try {
-- console.time("api chk middleware")
- const apiKey = req.header('x-api-key');
- if (!apiKey) {
- return res.status(401).json({ error: 'API key not found' });
-@@ -45,21 +44,26 @@ module.exports = async (req, res, next) => {
- await setProjectByApiKeyCache(hashedApi, project);
- }
-
-+ console.time("checking if owner is verified")
- if (!project.owner.isVerified) {
- return res.status(401).json({
- error: 'Owner not verified',
- fix: 'Verify your account on https://urbackend.bitbros.in/dashboard'
- });
- }
-+ console.timeEnd("checking if owner is verified")
-
- // Ensure defaults are present (crucial for lean objects or cached POJOs)
-+ console.time("setting defaults")
- if (!project.resources) project.resources = {};
- if (!project.resources.db) project.resources.db = { isExternal: false };
- if (!project.resources.storage) project.resources.storage = { isExternal: false };
-+ console.timeEnd("setting defaults")
-
-+ console.time("setting project and hashed api key")
- req.project = project;
- req.hashedApiKey = hashedApi;
-- console.timeEnd("api chk middleware")
-+ console.timeEnd("setting project and hashed api key")
- next();
- } catch (err) {
- res.status(500).json({ error: err.message });
-diff --git a/backend/services/redisCaching.js b/backend/services/redisCaching.js
-index 437f1aa1..7d378cae 100644
---- a/backend/services/redisCaching.js
-+++ b/backend/services/redisCaching.js
-@@ -2,14 +2,21 @@ const redis = require("../config/redis");
-
- async function setProjectByApiKeyCache(api, project) {
- if (redis.status !== "ready") return;
-+
- try {
-+ console.time("cache stringify");
- const data = JSON.stringify(project);
-+ console.timeEnd("cache stringify");
-+
-+ console.time("redis set");
- await redis.set(
- `project:apikey:${api}`,
- data,
- 'EX',
- 60 * 60 * 2
- );
-+ console.timeEnd("redis set");
-+
- } catch (err) {
- console.log(err);
- }
-@@ -17,12 +24,20 @@ async function setProjectByApiKeyCache(api, project) {
-
- async function getProjectByApiKeyCache(api) {
- if (redis.status !== "ready") return null;
-+
- try {
-+ console.time("redis get");
- const data = await redis.get(`project:apikey:${api}`);
-+ console.timeEnd("redis get");
-+
- if (!data) return null;
-
-- const parsedData = JSON.parse(data)
-+ console.time("cache parse");
-+ const parsedData = JSON.parse(data);
-+ console.timeEnd("cache parse");
-+
- return parsedData;
-+
- } catch (err) {
- console.log(err);
- return null;
-@@ -38,37 +53,50 @@ async function deleteProjectByApiKeyCache(api) {
- }
- }
-
--
- async function setProjectById(id, project) {
- if (redis.status !== "ready") return;
-+
- try {
-+ console.time("cache stringify by id");
- const data = JSON.stringify(project);
-+ console.timeEnd("cache stringify by id");
-+
-+ console.time("redis set by id");
- await redis.set(
- `project:id:${id}`,
- data,
- 'EX',
- 60 * 60 * 2
- );
-+ console.timeEnd("redis set by id");
-+
- } catch (err) {
- console.log(err);
- }
- }
-
--
- async function getProjectById(id) {
- if (redis.status !== "ready") return null;
-+
- try {
-+ console.time("redis get by id");
- const data = await redis.get(`project:id:${id}`);
-+ console.timeEnd("redis get by id");
-+
- if (!data) return null;
-- const parsedData = JSON.parse(data)
-+
-+ console.time("cache parse by id");
-+ const parsedData = JSON.parse(data);
-+ console.timeEnd("cache parse by id");
-+
- return parsedData;
-+
- } catch (err) {
- console.log(err);
- return null;
- }
- }
-
--
- async function deleteProjectById(id) {
- if (redis.status !== "ready") return;
- try {
-@@ -78,7 +106,6 @@ async function deleteProjectById(id) {
- }
- }
-
--
- module.exports = {
- setProjectByApiKeyCache,
- getProjectByApiKeyCache,
-@@ -86,4 +113,4 @@ module.exports = {
- setProjectById,
- getProjectById,
- deleteProjectById
--};
-+};
-\ No newline at end of file
-diff --git a/backend/utils/api.js b/backend/utils/api.js
-index 65fdd0ca..3b700c31 100644
---- a/backend/utils/api.js
-+++ b/backend/utils/api.js
-@@ -15,11 +15,10 @@ function generateApiKey() {
-
- //api hashing
- function hashApiKey(apikey) {
-- // scrypt hashing: fast but brute focre resistant
-- const salt = process.env.API_KEY_SALT;
--
-- // derived key of 64 bytes
-- return crypto.scryptSync(apikey, salt, 64).toString('hex');
-+ return crypto
-+ .createHash("sha256")
-+ .update(apikey)
-+ .digest("hex");
- }
-
- module.exports = { generateApiKey, hashApiKey }
-\ No newline at end of file
diff --git a/frontend/src/components/CollectionTable.jsx b/frontend/src/components/CollectionTable.jsx
index 4fe0dc709..7790a2d1f 100644
--- a/frontend/src/components/CollectionTable.jsx
+++ b/frontend/src/components/CollectionTable.jsx
@@ -131,7 +131,7 @@ export default function CollectionTable({ data, activeCollection, onDelete, onVi
{
id: "actions",
header: "Actions",
- size: 80,
+ size: 120,
enableResizing: false,
enableHiding: false,
cell: (info) => (
diff --git a/frontend/src/components/DatabaseSidebar.jsx b/frontend/src/components/DatabaseSidebar.jsx
index 716b5c5c7..24352c45d 100644
--- a/frontend/src/components/DatabaseSidebar.jsx
+++ b/frontend/src/components/DatabaseSidebar.jsx
@@ -96,8 +96,8 @@ export default function DatabaseSidebar({
/* Sidebar Styles - Scoped */
.db-sidebar {
width: 280px;
- background: var(--color-bg-sidebar);
- border-right: 1px solid var(--color-border);
+ background: transparent;
+ border-right: none;
display: flex;
flex-direction: column;
z-index: 100;
diff --git a/frontend/src/components/RowDetailDrawer.jsx b/frontend/src/components/RowDetailDrawer.jsx
index ad79f04be..e14e90f3a 100644
--- a/frontend/src/components/RowDetailDrawer.jsx
+++ b/frontend/src/components/RowDetailDrawer.jsx
@@ -1,164 +1,269 @@
-import React from "react";
-import { X, Calendar, Type, Hash, ToggleLeft, FileText, Link as LinkIcon, AlertCircle } from "lucide-react";
+import React, { useEffect } from "react";
+import { X, FileText, Edit2 } from "lucide-react";
-export default function RowDetailDrawer({ isOpen, onClose, record, fields }) {
- if (!isOpen || !record) return null;
-
- const getIconForType = (type) => {
- switch (type) {
- case "String": return
{JSON.stringify(value, null, 2)};
- }
+ return (
+ <>
+ {/* Backdrop */}
+
- return String(value);
- };
+ {/* Drawer Panel */}
+ + ID: {record._id} +
+ID: {record._id}
-Enterprise-grade tools packaged for individual developers.
@@ -256,8 +258,8 @@ function LandingPage() {@@ -273,8 +275,8 @@ function LandingPage() {
@@ -287,8 +289,8 @@ function LandingPage() {
@@ -298,8 +300,8 @@ function LandingPage() {
@@ -316,7 +318,7 @@ function LandingPage() {
+
Connect your self-hosted MongoDB or Atlas cluster. We provide the instant API layer, auth, and validation schema, while you keep full ownership of the data.
+
Link your supabase buckets or Supabase Storage. We handle the file upload tokens, permissions, and CDN delivery automatically.
Handle complex data relationships, multi-tenant auth, and subscriptions securely.
+Handle complex data relationships, multi-tenant auth, and subscriptions securely.
Serve data to Flutter or React Native apps with lightweight, fast JSON responses.
+Serve data to Flutter or React Native apps with lightweight, fast JSON responses.
Power blogs, portfolios, and e-commerce catalogs without CMS bloat.
+Power blogs, portfolios, and e-commerce catalogs without CMS bloat.