Skip to content

Commit db814f0

Browse files
authored
Merge branch 'main' into fix/dashboard-getData-query-engine
2 parents 3465d8d + 53c9e9a commit db814f0

7 files changed

Lines changed: 195 additions & 269 deletions

File tree

apps/dashboard-api/src/controllers/project.controller.js

Lines changed: 14 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,6 @@ const getDefaultRlsForCollection = (collectionName, schema = []) => {
144144

145145
const SOCIAL_PROVIDER_KEYS = ["github", "google"];
146146

147-
/**
148-
* Sanitizes authProviders from a project document for safe API responses.
149-
* Strips clientSecret fields and replaces them with a boolean hasClientSecret flag.
150-
* @param {Object} authProviders - Raw authProviders from the project document
151-
* @returns {Object} Sanitized providers keyed by provider name
152-
*/
153-
154147
const sanitizeAuthProviders = (authProviders = {}) => {
155148
return SOCIAL_PROVIDER_KEYS.reduce((acc, provider) => {
156149
const config = authProviders?.[provider] || {};
@@ -339,19 +332,14 @@ module.exports.getAllProject = async (req, res) => {
339332

340333
const projectIds = projects.map(p => p._id);
341334
const recentLogs = await Log.aggregate([
342-
{ $match: { projectId: { $in: projectIds } } },
343-
{ $sort: { timestamp: -1 } },
344-
{
345-
$group: {
346-
_id: "$projectId",
347-
logs: { $topN: { n: 100, sortBy: { timestamp: -1 }, output: { status: "$status" } } }
348-
}
349-
},
350-
{
351-
$project: {
352-
errorCount: {
353-
$size: {
354-
$filter: { input: "$logs", as: "l", cond: { $gte: ["$$l.status", 400] } } // cap to last 100 logs per project
335+
{ $match: { projectId: { $in: projectIds } } },
336+
{ $sort: { timestamp: -1 } },
337+
{ $limit: 100 },
338+
{ $group: {
339+
_id: "$projectId",
340+
errorCount: { $sum: { $cond: [{ $gte: ["$status", 400] }, 1, 0] } },
341+
successCount: { $sum: { $cond: [{ $lt: ["$status", 400] }, 1, 0] } }
342+
}
355343
}
356344
},
357345
successCount: {
@@ -432,7 +420,7 @@ module.exports.getSingleProject = async (req, res) => {
432420

433421
module.exports.regenerateApiKey = async (req, res) => {
434422
try {
435-
const { keyType } = req.body; // 'publishable' or 'secret'
423+
const { keyType } = req.body;
436424

437425
if (keyType !== "publishable" && keyType !== "secret") {
438426
return res
@@ -817,20 +805,11 @@ return res.status(404).json({ success: false, data: {}, message: `Collection ${c
817805
});
818806
}
819807

820-
// Strip password from fields query param for users collection
821-
const safeQuery = { ...req.query };
822-
if (collectionName === 'users' && safeQuery.fields) {
823-
safeQuery.fields = safeQuery.fields
824-
.split(',')
825-
.filter(f => f.trim().toLowerCase() !== 'password')
826-
.join(',');
827-
}
828-
829-
const features = new QueryEngine(baseQuery, safeQuery)
830-
.filter()
831-
.sort()
832-
.populate()
833-
.limitFields(); // fixes: ?populate= and ?expand= now work
808+
const features = new QueryEngine(baseQuery, req.query)
809+
.filter()
810+
.sort()
811+
.limitFields() // fixes: ?fields= and ?meta=false now work
812+
.populate(); // fixes: ?populate= and ?expand= now work
834813

835814
// Get total before paginating
836815
const total = await features.count();
@@ -880,8 +859,6 @@ const features = new QueryEngine(baseQuery, safeQuery)
880859
if (err?.statusCode === 400 || err?.name === 'QueryFilterError') {
881860
return res.status(400).json({ success: false, data: {}, message: err.message || "Invalid query filter." });
882861
}
883-
return res.status(500).json({ success: false, data: {}, message: "Failed to fetch data." });
884-
}
885862
};
886863

887864
module.exports.deleteCollection = async (req, res) => {

apps/public-api/src/controllers/data.controller.js

Lines changed: 29 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ module.exports.getAllData = async (req, res) => {
264264
features.query = features.query.and([baseFilter]);
265265
}
266266

267-
features.sort().populate().limitFields();
267+
features.sort().limitFields().populate();
268268

269269
const total = await features.count();
270270
const parsedLimit = parseInt(req.query.limit, 10);
@@ -537,54 +537,37 @@ module.exports.updateSingleData = async (req, res, next) => {
537537

538538
// Only enforce quota for internal databases
539539
if (!project.resources.db.isExternal) {
540-
const session = await mongoose.startSession();
541-
session.startTransaction();
542-
543-
try {
544-
// 1. Fetch existing doc securely within transaction
545-
const existingDoc = await Model.findOne(queryFilter).session(session).lean();
546-
if (!existingDoc) {
547-
await session.abortTransaction();
548-
session.endSession();
549-
return next(new AppError(404, "Document not found."));
550-
}
540+
// 1. Fetch existing doc securely
541+
const existingDoc = await Model.findOne(queryFilter).lean();
542+
if (!existingDoc) {
543+
return next(new AppError(404, "Document not found."));
544+
}
551545

552-
// 2. Calculate sizes
553-
const oldSize = mongoose.mongo.BSON.calculateObjectSize(existingDoc);
554-
const simulatedNewDoc = { ...existingDoc, ...sanitizedData };
555-
const newSize = mongoose.mongo.BSON.calculateObjectSize(simulatedNewDoc);
556-
const sizeDelta = newSize - oldSize;
557-
558-
// 3. Enforce quota if size is increasing
559-
if (sizeDelta > 0) {
560-
if ((project.databaseUsed || 0) + sizeDelta > project.databaseLimit) {
561-
await session.abortTransaction();
562-
session.endSession();
563-
return next(new AppError(403, "Storage quota exceeded. Please upgrade your plan."));
564-
}
565-
}
546+
// 2. Calculate sizes
547+
const oldSize = mongoose.mongo.BSON.calculateObjectSize(existingDoc);
548+
const simulatedNewDoc = { ...existingDoc, ...sanitizedData };
549+
const newSize = mongoose.mongo.BSON.calculateObjectSize(simulatedNewDoc);
550+
const sizeDelta = newSize - oldSize;
566551

567-
// 4. Update the document
568-
result = await Model.findOneAndUpdate(
569-
queryFilter,
570-
{ $set: sanitizedData },
571-
{ new: true, runValidators: true, session },
572-
).lean();
573-
574-
// 5. Apply the delta (positive or negative) atomically
575-
await Project.findByIdAndUpdate(
576-
project._id,
577-
{ $inc: { databaseUsed: sizeDelta } },
578-
{ session }
579-
);
580-
581-
await session.commitTransaction();
582-
session.endSession();
583-
} catch (error) {
584-
await session.abortTransaction();
585-
session.endSession();
586-
throw error;
552+
// 3. Enforce quota if size is increasing
553+
if (sizeDelta > 0) {
554+
if ((project.databaseUsed || 0) + sizeDelta > project.databaseLimit) {
555+
return next(new AppError(403, "Storage quota exceeded. Please upgrade your plan."));
556+
}
587557
}
558+
559+
// 4. Update the document
560+
result = await Model.findOneAndUpdate(
561+
queryFilter,
562+
{ $set: sanitizedData },
563+
{ new: true, runValidators: true },
564+
).lean();
565+
566+
// 5. Apply the delta (positive or negative) atomically
567+
await Project.findByIdAndUpdate(
568+
project._id,
569+
{ $inc: { databaseUsed: sizeDelta } }
570+
);
588571
} else {
589572
// External DB Flow (No quota checks)
590573
result = await Model.findOneAndUpdate(

apps/web-dashboard/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"react-hot-toast": "^2.6.0",
2626
"react-markdown": "^10.1.0",
2727
"react-router-dom": "^7.9.6",
28-
"recharts": "^3.5.1",
28+
"recharts": "^2.13.0",
2929
"remark-gfm": "^4.0.1",
3030
"tailwindcss": "^4.1.18"
3131
},

apps/web-dashboard/src/pages/Database.jsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,18 @@ export default function Database() {
109109
if (f.field && f.value !== '') queryStr += `&${f.field}${f.operator === '=' ? '' : f.operator}=${encodeURIComponent(f.value)}`;
110110
});
111111
const res = await api.get(`/api/projects/${projectId}/collections/${activeCollection.name}/data${queryStr}`);
112-
// Handle wrapped metadata response
113-
if (res.data && res.data.items) {
112+
// Handle standard API response format { success, data: { items, total } }
113+
if (res.data?.success && res.data?.data?.items) {
114+
setData(res.data.data.items);
115+
setTotalRecords(res.data.data.total || 0);
116+
}
117+
// Handle legacy metadata response { items, total }
118+
else if (res.data && res.data.items) {
114119
setData(res.data.items);
115120
setTotalRecords(res.data.total || 0);
116-
} else {
121+
}
122+
// Fallback
123+
else {
117124
setData(res.data || []);
118125
setTotalRecords(Array.isArray(res.data) ? res.data.length : 0);
119126
}

apps/web-dashboard/vite.config.js

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,7 @@
11
import { defineConfig } from 'vite';
22
import tailwindcss from '@tailwindcss/vite';
3+
import react from '@vitejs/plugin-react';
34

45
export default defineConfig({
5-
plugins: [tailwindcss()],
6-
esbuild: {
7-
jsx: 'automatic',
8-
},
9-
optimizeDeps: {
10-
esbuildOptions: {
11-
jsx: 'automatic',
12-
},
13-
},
14-
build: {
15-
rollupOptions: {
16-
output: {
17-
manualChunks(id) {
18-
if (id.includes('node_modules')) {
19-
if (id.includes('react') || id.includes('react-dom') || id.includes('react-router-dom')) {
20-
return 'vendor';
21-
}
22-
if (id.includes('recharts')) {
23-
return 'charts';
24-
}
25-
if (id.includes('lucide-react')) {
26-
return 'icons';
27-
}
28-
if (id.includes('axios')) {
29-
return 'axios';
30-
}
31-
}
32-
},
33-
},
34-
},
35-
},
6+
plugins: [react(), tailwindcss()],
367
});

0 commit comments

Comments
 (0)