diff --git a/apps/dashboard-api/src/controllers/project.controller.js b/apps/dashboard-api/src/controllers/project.controller.js index b25582c8e..ae2f21c79 100644 --- a/apps/dashboard-api/src/controllers/project.controller.js +++ b/apps/dashboard-api/src/controllers/project.controller.js @@ -332,16 +332,29 @@ module.exports.getAllProject = async (req, res) => { const projectIds = projects.map(p => p._id); const recentLogs = await Log.aggregate([ - { $match: { projectId: { $in: projectIds } } }, - { $sort: { timestamp: -1 } }, - { $limit: 100 }, - { $group: { - _id: "$projectId", - errorCount: { $sum: { $cond: [{ $gte: ["$status", 400] }, 1, 0] } }, - successCount: { $sum: { $cond: [{ $lt: ["$status", 400] }, 1, 0] } } + { $match: { projectId: { $in: projectIds } } }, + { $sort: { timestamp: -1 } }, + { + $group: { + _id: "$projectId", + logs: { $topN: { n: 100, sortBy: { timestamp: -1 }, output: { status: "$status" } } } + } + }, + { + $project: { + errorCount: { + $size: { + $filter: { input: "$logs", as: "l", cond: { $gte: ["$$l.status", 400] } } + } + }, + successCount: { + $size: { + $filter: { input: "$logs", as: "l", cond: { $lt: ["$$l.status", 400] } } } } - ]); + } + } +]); const logsMap = recentLogs.reduce((acc, log) => { acc[log._id.toString()] = log; @@ -395,8 +408,7 @@ module.exports.getSingleProject = async (req, res) => { "+resendApiKey.iv " + "+resendApiKey.tag", ); - if (!project) - return res.status(404).json({ error: "Project not found." }); + if (!project) return res.status(404).json({ success: false, data: {}, message: "Project not found." }); projectObj = project.toObject(); await setProjectById(req.params.projectId, projectObj); } @@ -763,14 +775,13 @@ module.exports.getData = async (req, res) => { try { 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." }); + if (!project) return res.status(404).json({ success: false, data: {}, message: "Project not found." }); + + const collectionConfig = project.collections.find(c => c.name === collectionName); if (!collectionConfig) { - return res.status(404).json({ - error: "Collection not found", - collection: collectionName - }); + return res.status(404).json({ success: false, data: {}, message: `Collection ${collectionName} not found.` }); } const connection = await getConnection(projectId); @@ -850,10 +861,11 @@ module.exports.getData = async (req, res) => { message: "Data fetched successfully.", }); } catch (err) { - res.status(500).json({ error: err.message }); + if (err?.statusCode === 400 || err?.name === 'QueryFilterError') { + return res.status(400).json({ success: false, data: {}, message: err.message || "Invalid query filter." }); } }; - +}; module.exports.deleteCollection = async (req, res) => { try { const { projectId, collectionName } = req.params; @@ -925,10 +937,8 @@ module.exports.insertData = async (req, res) => { (c) => c.name === collectionName, ); if (!collectionConfig) { - return res - .status(404) - .json({ error: "Collection configuration not found." }); - } + return res.status(404).json({ success: false, data: {}, message: `Collection ${collectionName} not found.` }); +} // Prevent manual injection of soft-delete fields delete incomingData.isDeleted; diff --git a/apps/public-api/src/__tests__/data.controller.read.test.js b/apps/public-api/src/__tests__/data.controller.read.test.js index 26a5f22c2..dada8a21f 100644 --- a/apps/public-api/src/__tests__/data.controller.read.test.js +++ b/apps/public-api/src/__tests__/data.controller.read.test.js @@ -27,7 +27,7 @@ jest.mock('@urbackend/common', () => ({ sanitize: (v) => v, Project: {}, getConnection: jest.fn().mockResolvedValue({}), - getCompiledModel: jest.fn(() => ({ + getCompiledModel: jest.fn((connection, collectionConfig, projectId, isExternal) => ({ find: (...args) => { mockFind(...args); return { diff --git a/apps/public-api/src/controllers/data.controller.js b/apps/public-api/src/controllers/data.controller.js index 063c7408e..2ca42079f 100644 --- a/apps/public-api/src/controllers/data.controller.js +++ b/apps/public-api/src/controllers/data.controller.js @@ -217,8 +217,14 @@ module.exports.getAllData = async (req, res) => { const collectionConfig = project.collections.find( (c) => c.name === collectionName, ); - if (!collectionConfig) - return res.status(404).json({ error: "Collection not found" }); + + if (!collectionConfig) { + return res.status(404).json({ + success: false, + data: {}, + message: "Collection not found", + }); + } const connection = await getConnection(project._id); const Model = getCompiledModel( @@ -264,7 +270,6 @@ module.exports.getAllData = async (req, res) => { const parsedLimit = parseInt(req.query.limit, 10); const limit = Math.max(1, Math.min(Number.isNaN(parsedLimit) ? 100 : parsedLimit, 100)); - // Use cursor-based pagination if cursor parameter is provided, otherwise use offset-based const useCursor = !!req.query.cursor; if (useCursor) { features.cursorPaginate(); @@ -274,7 +279,6 @@ module.exports.getAllData = async (req, res) => { const data = await features.query.lean(); - // Handle cursor pagination: slice to actual limit and generate next cursor let items = data; let nextCursor = null; if (useCursor) { @@ -327,7 +331,6 @@ module.exports.getAllData = async (req, res) => { }); } }; - // GET SINGLE DOC module.exports.getSingleDoc = async (req, res) => { try { @@ -737,4 +740,4 @@ module.exports.recoverSingleDoc = async (req, res, next) => { } return next(new AppError(500, "Failed to recover document.")); } -}; \ No newline at end of file +};