@@ -144,12 +144,6 @@ const getDefaultRlsForCollection = (collectionName, schema = []) => {
144144
145145const 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- */
153147const sanitizeAuthProviders = ( authProviders = { } ) => {
154148 return SOCIAL_PROVIDER_KEYS . reduce ( ( acc , provider ) => {
155149 const config = authProviders ?. [ provider ] || { } ;
@@ -336,13 +330,11 @@ module.exports.getAllProject = async (req, res) => {
336330 . select ( "name description databaseUsed databaseLimit storageUsed storageLimit updatedAt isAuthEnabled collections" )
337331 . lean ( ) ;
338332
339- // --- HEALTH CALCULATION (SIMULATED / CALCULATED) ---
340- // Fetch recent log status for all projects to determine health
341333 const projectIds = projects . map ( p => p . _id ) ;
342334 const recentLogs = await Log . aggregate ( [
343335 { $match : { projectId : { $in : projectIds } } } ,
344336 { $sort : { timestamp : - 1 } } ,
345- { $limit : 100 } , // Get the last 100 logs globally for user to keep it fast
337+ { $limit : 100 } ,
346338 { $group : {
347339 _id : "$projectId" ,
348340 errorCount : { $sum : { $cond : [ { $gte : [ "$status" , 400 ] } , 1 , 0 ] } } ,
@@ -360,7 +352,6 @@ module.exports.getAllProject = async (req, res) => {
360352 const stats = logsMap [ project . _id . toString ( ) ] ;
361353 let health = 'healthy' ;
362354
363- // Determine health: If > 20% recent errors, mark as warning
364355 if ( stats ) {
365356 const total = stats . errorCount + stats . successCount ;
366357 const errorRate = stats . errorCount / total ;
@@ -410,7 +401,6 @@ module.exports.getSingleProject = async (req, res) => {
410401 await setProjectById ( req . params . projectId , projectObj ) ;
411402 }
412403
413- // Ownership Check (Even for Cache)
414404 if ( projectObj . owner . toString ( ) !== req . user . _id . toString ( ) ) {
415405 return res . status ( 403 ) . json ( { error : "Access denied." } ) ;
416406 }
@@ -423,7 +413,7 @@ module.exports.getSingleProject = async (req, res) => {
423413
424414module . exports . regenerateApiKey = async ( req , res ) => {
425415 try {
426- const { keyType } = req . body ; // 'publishable' or 'secret'
416+ const { keyType } = req . body ;
427417
428418 if ( keyType !== "publishable" && keyType !== "secret" ) {
429419 return res
@@ -442,7 +432,6 @@ module.exports.regenerateApiKey = async (req, res) => {
442432 if ( ! oldApiProj )
443433 return res . status ( 404 ) . json ( { error : "Project not found." } ) ;
444434
445- // CLEAR CACHE
446435 await deleteProjectByApiKeyCache ( oldApiProj . publishableKey ) ;
447436 await deleteProjectByApiKeyCache ( oldApiProj . secretKey ) ;
448437
@@ -481,7 +470,7 @@ const dropCollectionIfExists = async (connection, collectionName) => {
481470 }
482471 }
483472} ;
484- // VALIDATE URI
473+
485474const isSafeUri = ( uri ) => {
486475 try {
487476 const parsed = new URL ( uri ) ;
@@ -497,13 +486,11 @@ module.exports.updateExternalConfig = async (req, res) => {
497486 try {
498487 const { projectId } = req . params ;
499488
500- // POST FOR - EXTERNAL CONFIG
501489 const validatedData = updateExternalConfigSchema . parse ( req . body ) ;
502490 const { dbUri, storageUrl, storageKey, storageProvider } = validatedData ;
503491
504492 const updateData = { } ;
505493
506- // DB CONFIG
507494 if ( dbUri ) {
508495 if ( ! isSafeUri ( dbUri ) )
509496 return res . status ( 400 ) . json ( {
@@ -514,7 +501,6 @@ module.exports.updateExternalConfig = async (req, res) => {
514501 updateData [ "resources.db.config" ] = encrypt ( JSON . stringify ( { dbUri } ) ) ;
515502 updateData [ "resources.db.isExternal" ] = true ;
516503
517- // --- VERIFY CONNECTION ---
518504 console . log ( "Verifying connection to:" , projectId ) ;
519505 try {
520506 const tempConn = mongoose . createConnection ( dbUri , {
@@ -538,10 +524,8 @@ module.exports.updateExternalConfig = async (req, res) => {
538524
539525 return res . status ( 400 ) . json ( { error : errorMsg } ) ;
540526 }
541- // -------------------------
542527 }
543528
544- // STORAGE CONFIG
545529 if ( storageUrl && storageKey ) {
546530 const storageConfig = {
547531 storageUrl,
@@ -641,7 +625,6 @@ module.exports.deleteExternalStorageConfig = async (req, res) => {
641625 }
642626} ;
643627
644- // POST REQ FOR CREATE COLLECTION
645628module . exports . createCollection = async ( req , res ) => {
646629 const executeOperation = async ( session ) => {
647630 const { projectId, collectionName, schema } = createCollectionSchema . parse ( req . body ) ;
@@ -775,7 +758,7 @@ module.exports.createCollection = async (req, res) => {
775758 }
776759} ;
777760
778- // GET DOC BY ID
761+ // GET DOC BY ID — FIXED: added limitFields(), populate(), cursor pagination, count support, structured response
779762module . exports . getData = async ( req , res ) => {
780763 try {
781764 const { projectId, collectionName } = req . params ;
@@ -1061,7 +1044,6 @@ module.exports.editRow = async (req, res) => {
10611044
10621045 if ( collectionName === "users" ) {
10631046 delete req . body . password ;
1064- // Also ensure it's not and nested or sneaky
10651047 Object . keys ( req . body ) . forEach ( ( key ) => {
10661048 if ( key . toLowerCase ( ) . includes ( "password" ) ) delete req . body [ key ] ;
10671049 } ) ;
@@ -1293,7 +1275,6 @@ module.exports.requestUpload = async (req, res, next) => {
12931275
12941276 const external = isProjectStorageExternal ( project ) ;
12951277
1296- // Pre-check quota only; actual storage usage is charged after confirmUpload verifies object existence and size.
12971278 if ( ! external ) {
12981279 const storageLimit =
12991280 typeof project . storageLimit === "number"
@@ -1353,12 +1334,10 @@ module.exports.confirmUpload = async (req, res, next) => {
13531334 const external = isProjectStorageExternal ( project ) ;
13541335 const normalizedPath = normalizeProjectPath ( projectId , sanitizedFilePath ) ;
13551336
1356- // make sure client isn't confirming someone else's file
13571337 if ( ! normalizedPath ) {
13581338 return next ( new AppError ( 403 , "Access denied." ) ) ;
13591339 }
13601340
1361- // verify file actually exists on cloud before touching quota
13621341 let actualSize ;
13631342 try {
13641343 actualSize = await verifyUploadedFile ( project , normalizedPath ) ;
@@ -1387,7 +1366,6 @@ module.exports.confirmUpload = async (req, res, next) => {
13871366 ) ;
13881367 }
13891368
1390- // now it's safe to charge quota
13911369 if ( ! external ) {
13921370 const result = await Project . updateOne (
13931371 {
@@ -1502,7 +1480,6 @@ module.exports.updateProject = async (req, res) => {
15021480 . json ( { error : "resendApiKey must be a non-empty string." } ) ;
15031481 }
15041482
1505- // Sanitize the key: Prevent CRLF (HTTP Header Injection) and invalid characters
15061483 if ( ! / ^ r e _ [ A - Z a - z 0 - 9 _ ] + $ / . test ( trimmedKey ) ) {
15071484 return res . status ( 400 ) . json ( { error : "Invalid Resend API Key format." } ) ;
15081485 }
@@ -1551,7 +1528,6 @@ module.exports.listMailTemplates = async (req, res, next) => {
15511528 try {
15521529 const { projectId } = req . params ;
15531530
1554- // Load as document so we can migrate legacy embedded templates if present
15551531 const project = await Project . findOne ( { _id : projectId , owner : req . user . _id } ) . select ( "+mailTemplates" ) ;
15561532
15571533 if ( ! project ) return res . status ( 404 ) . json ( { success : false , data : { } , message : "Project not found." } ) ;
@@ -1635,7 +1611,6 @@ module.exports.listMailTemplates = async (req, res, next) => {
16351611 }
16361612
16371613 if ( migrationSafeToFinalize ) {
1638- // Clear legacy embedded templates only after migration writes are complete.
16391614 project . mailTemplates = [ ] ;
16401615 await project . save ( ) ;
16411616 await deleteProjectById ( project . _id . toString ( ) ) . catch ( ( ) => { } ) ;
@@ -1671,7 +1646,6 @@ module.exports.listGlobalMailTemplates = async (req, res, next) => {
16711646 try {
16721647 const { projectId } = req . params ;
16731648
1674- // Keep auth consistent: only show to project owners
16751649 const project = await Project . findOne ( { _id : projectId , owner : req . user . _id } )
16761650 . select ( "_id" )
16771651 . lean ( ) ;
@@ -1723,7 +1697,6 @@ module.exports.getMailTemplate = async (req, res, next) => {
17231697 . select ( "_id key name subject html text updatedAt projectId isSystem" )
17241698 . lean ( ) ;
17251699
1726- // Legacy fallback (should be rare after listMailTemplates migration)
17271700 if ( ! template ) {
17281701 const legacy = Array . isArray ( project . mailTemplates ) ? project . mailTemplates : [ ] ;
17291702 const lt = legacy . find ( ( x ) => String ( x . _id ) === String ( templateId ) ) ;
@@ -2007,7 +1980,6 @@ module.exports.deleteProject = async (req, res) => {
20071980 . json ( { error : "Project not found or access denied." } ) ;
20081981 }
20091982
2010- // DROP COLLECTIONS: Only for internal databases
20111983 if ( ! project . resources . db . isExternal ) {
20121984 for ( const col of project . collections ) {
20131985 const collectionName = `${ project . _id } _${ col . name } ` ;
@@ -2021,7 +1993,6 @@ module.exports.deleteProject = async (req, res) => {
20211993 } catch ( e ) { }
20221994 }
20231995
2024- // DELETE: Only for internal Infraa
20251996 if ( ! isProjectStorageExternal ( project ) ) {
20261997 const supabase = await getStorage ( project ) ;
20271998 const bucket = getBucket ( project ) ;
@@ -2057,7 +2028,6 @@ module.exports.deleteProject = async (req, res) => {
20572028 }
20582029} ;
20592030
2060- // ENRICHED analytics function for the premium dashboard
20612031module . exports . analytics = async ( req , res , next ) => {
20622032 try {
20632033 const { projectId } = req . params ;
@@ -2089,7 +2059,7 @@ module.exports.analytics = async (req, res, next) => {
20892059 case 'last1h' :
20902060 startDate . setHours ( startDate . getHours ( ) - 1 ) ;
20912061 format = "%H:%M" ;
2092- groupStep = "minute" ; // We'll group by minute for 1h
2062+ groupStep = "minute" ;
20932063 break ;
20942064 case 'last24h' :
20952065 startDate . setDate ( startDate . getDate ( ) - 1 ) ;
@@ -2112,7 +2082,6 @@ module.exports.analytics = async (req, res, next) => {
21122082 timestamp : { $gte : startDate } ,
21132083 } ;
21142084
2115- // 1. Aggregation for Time Series (Requests & Latency)
21162085 const timeSeriesData = await ApiAnalytics . aggregate ( [
21172086 { $match : match } ,
21182087 {
@@ -2126,7 +2095,6 @@ module.exports.analytics = async (req, res, next) => {
21262095 { $sort : { _id : 1 } }
21272096 ] ) ;
21282097
2129- // 2. Aggregation for Breakdowns (Status, Method, Top Endpoints)
21302098 const [ breakdownStats , topEndpoints ] = await Promise . all ( [
21312099 ApiAnalytics . aggregate ( [
21322100 { $match : match } ,
@@ -2169,7 +2137,6 @@ module.exports.analytics = async (req, res, next) => {
21692137 const stats = breakdownStats [ 0 ] . global [ 0 ] || { avgResponseTimeMs : 0 , totalRequests : 0 , errors : 0 } ;
21702138 const errorRate = stats . totalRequests > 0 ? ( stats . errors / stats . totalRequests ) * 100 : 0 ;
21712139
2172- // 3. Approximate p95
21732140 let p95 = 0 ;
21742141 if ( stats . totalRequests > 0 ) {
21752142 const p95Results = await ApiAnalytics . find ( match )
@@ -2181,11 +2148,9 @@ module.exports.analytics = async (req, res, next) => {
21812148 p95 = p95Results [ 0 ] ?. responseTimeMs || 0 ;
21822149 }
21832150
2184- // 4. Logs (last 50)
21852151 const rawLogs = await ApiAnalytics . find ( match ) . sort ( { timestamp : - 1 } ) . limit ( 50 ) . lean ( ) ;
21862152 const logs = rawLogs . map ( l => ( { ...l , path : l . endpoint , status : l . statusCode } ) ) ;
21872153
2188- // Cumulative stats for the project
21892154 const allTimeRequests = await Log . countDocuments ( { projectId } ) ;
21902155
21912156 return res . json ( {
@@ -2223,16 +2188,11 @@ module.exports.analytics = async (req, res, next) => {
22232188 }
22242189} ;
22252190
2226- // FUNCTION - TOGGLE AUTH
22272191module . exports . toggleAuth = async ( req , res ) => {
22282192 try {
22292193 const { projectId } = req . params ;
2230- const { enable } = req . body ; // true or false
2194+ const { enable } = req . body ;
22312195
2232- // Ensure user owns project, and load authProviders secrets so sanitizeAuthProviders
2233- // can correctly compute hasClientSecret in the response.
2234- // NOTE: If new OAuth providers are added to SOCIAL_PROVIDER_KEYS, extend this select list
2235- // to include their clientSecret fields as well.
22362196 const project = await Project . findOne ( {
22372197 _id : projectId ,
22382198 owner : req . user . _id ,
@@ -2284,11 +2244,6 @@ module.exports.toggleAuth = async (req, res) => {
22842244 }
22852245} ;
22862246
2287- /**
2288- * Updates GitHub/Google OAuth provider settings for a project.
2289- * Preserves existing encrypted client secrets when not provided in the update.
2290- * @route PUT /api/projects/:projectId/auth-providers
2291- */
22922247module . exports . updateAuthProviders = async ( req , res ) => {
22932248 try {
22942249 const { projectId } = req . params ;
@@ -2331,7 +2286,6 @@ module.exports.updateAuthProviders = async (req, res) => {
23312286 } ) ;
23322287 }
23332288
2334- // P1: Require siteUrl before enabling any OAuth provider
23352289 if ( nextEnabled && ! project . siteUrl ?. trim ( ) ) {
23362290 return res . status ( 422 ) . json ( {
23372291 error : "siteUrl required" ,
@@ -2365,8 +2319,6 @@ module.exports.updateAuthProviders = async (req, res) => {
23652319 }
23662320} ;
23672321
2368-
2369- // PATCH FOR UPDATING COLLECTION RLS
23702322module . exports . updateCollectionRls = async ( req , res ) => {
23712323 try {
23722324 const { projectId, collectionName } = req . params ;
@@ -2404,7 +2356,6 @@ module.exports.updateCollectionRls = async (req, res) => {
24042356 } ) ;
24052357 }
24062358
2407- // Restrict use of '_id' as ownerField to the 'users' collection only.
24082359 if ( nextOwnerField === '_id' && collection . name !== 'users' ) {
24092360 return res . status ( 400 ) . json ( {
24102361 error : "Invalid owner field" ,
@@ -2632,7 +2583,6 @@ module.exports.deleteContact = async (req, res) => {
26322583 const safeAudienceId = encodeURIComponent ( audienceId ) ;
26332584 const safeContactId = encodeURIComponent ( contactId ) ;
26342585
2635- // Resend uses DELETE /audiences/{audience_id}/contacts/{id} or by email
26362586 await axios . delete ( `https://api.resend.com/audiences/${ safeAudienceId } /contacts/${ safeContactId } ` , {
26372587 headers : { Authorization : `Bearer ${ key } ` }
26382588 } ) ;
@@ -2667,7 +2617,6 @@ module.exports.sendMarketingBroadcast = async (req, res) => {
26672617 return res . status ( 400 ) . json ( { success : false , message : "Audience ID, subject, and html content are required." } ) ;
26682618 }
26692619
2670- // Mass marketing broadcasts logic using Resend Broadcasts API
26712620 const payload = {
26722621 audience_id : audienceId ,
26732622 subject,
0 commit comments