@@ -3,41 +3,82 @@ const { getPublicIp } = require("./network");
33const Project = require ( "../models/Project" ) ;
44const { decrypt } = require ( "./encryption" ) ;
55const mongoose = require ( "mongoose" ) ;
6-
6+ const redis = require ( "../config/redis" ) ;
77
88async function getConnection ( projectId ) {
99 const key = projectId . toString ( ) ;
1010
11+ // 1. Instant Cache Hit (Fastest Path for UX)
1112 if ( registry . has ( key ) ) {
1213 const cachedConn = registry . get ( key ) ;
1314 if ( cachedConn . readyState === 1 ) {
14- cachedConn . lastAccessed = new Date ( ) ;
15+ cachedConn . lastAccessed = new Date ( ) ; // Update access timestamp instantly
1516 return cachedConn ;
1617 }
1718 }
1819
19- const projectDoc = await Project . findById ( projectId )
20- . select ( "+resources.db.config.encrypted +resources.db.config.iv +resources.db.config.tag resources.db.isExternal" ) ;
20+ let dbUri ;
21+ let plan = 'free' ; // default
2122
22- if ( ! projectDoc ) throw new Error ( "Project not found" ) ;
23+ // 2. Redis Cache Check (Saves Main DB load and Decryption CPU cycles)
24+ const redisKey = `project:uri:${ key } ` ;
25+ const cachedDataStr = await redis . get ( redisKey ) ;
2326
24- if ( ! projectDoc . resources . db . isExternal ) {
25- return mongoose . connection ;
27+ if ( cachedDataStr ) {
28+ try {
29+ const cachedData = JSON . parse ( cachedDataStr ) ;
30+ dbUri = cachedData . dbUri ;
31+ plan = cachedData . plan || 'free' ;
32+ } catch ( e ) {
33+ console . error ( "Failed to parse cached project data from Redis" , e ) ;
34+ }
2635 }
2736
28- let config ;
29- try {
30- const decryptedConfig = decrypt ( projectDoc . resources . db . config ) ;
31- config = JSON . parse ( decryptedConfig ) ;
32- } catch ( err ) {
33- console . error ( "Decryption Error:" , err ) ;
34- throw new Error ( "Invalid or corrupted external config" ) ;
37+ // 3. Database Lookup & Decryption (Only runs if Redis is empty)
38+ if ( ! dbUri ) {
39+ const projectDoc = await Project . findById ( projectId )
40+ . select ( "+resources.db.config.encrypted +resources.db.config.iv +resources.db.config.tag resources.db.isExternal plan" ) ;
41+
42+ if ( ! projectDoc ) throw new Error ( "Project not found" ) ;
43+
44+ if ( ! projectDoc . resources . db . isExternal ) {
45+ return mongoose . connection ;
46+ }
47+
48+ try {
49+ const decryptedConfig = decrypt ( projectDoc . resources . db . config ) ;
50+ dbUri = JSON . parse ( decryptedConfig ) . dbUri ;
51+ plan = projectDoc . plan || 'free' ;
52+ } catch ( err ) {
53+ console . error ( "Decryption Error:" , err ) ;
54+ throw new Error ( "Invalid or corrupted external config" ) ;
55+ }
56+
57+ // Save to Redis for 1 Hour so subsequent server processes can grab it instantly
58+ await redis . set ( redisKey , JSON . stringify ( { dbUri, plan } ) , 'EX' , 3600 ) ;
3559 }
3660
37- const connection = mongoose . createConnection ( config . dbUri ) ;
61+ // 4. ENTERPRISE CONFIGURATION FIX
62+ // We dynamically allocate pooling size depending on usage tiers to balance RAM & Throughput
63+ const isPremiumUser = plan === 'premium' ;
64+
65+ const connectionOptions = {
66+ maxPoolSize : isPremiumUser ? 50 : 15 , // Cap free/hobby projects at 15 to prevent crashing your server
67+ minPoolSize : 2 , // Keeps 2 warm sockets alive for instant response time
68+ maxIdleTimeMS : 15000 , // Native driver automatically kills idle sockets after 15 seconds
69+ connectTimeoutMS : 5000 , // Fail fast (5s) if user provides a bad/dead connection string
70+ socketTimeoutMS : 45000 ,
71+ waitQueueTimeoutMS : 5000 // Prevents requests from stalling forever if pool is exhausted
72+ } ;
73+
74+ // Initialize the pool with custom options
75+ const connection = mongoose . createConnection ( dbUri , connectionOptions ) ;
76+
77+ // Track active query load on this connection to prevent overflow crashes
78+ connection . lastAccessed = new Date ( ) ;
3879
3980 connection . on ( "connected" , ( ) => {
40- console . log ( `✅ External DB connected : ${ projectId } ` ) ;
81+ console . log ( `✅ External DB pool opened for : ${ projectId } (maxPoolSize: ${ connectionOptions . maxPoolSize } ) ` ) ;
4182 } ) ;
4283
4384 try {
@@ -52,7 +93,12 @@ async function getConnection(projectId) {
5293 }
5394
5495 connection . on ( "error" , ( err ) => {
55- console . error ( "❌ Connection error [%s]:" , projectId , err ) ;
96+ console . error ( `❌ DB Connection Error for project ${ projectId } :` , err ) ;
97+ registry . delete ( key ) ; // Clear bad connections immediately
98+ } ) ;
99+
100+ connection . on ( "disconnected" , ( ) => {
101+ console . log ( `🔌 External DB disconnected: ${ projectId } ` ) ;
56102 registry . delete ( key ) ;
57103 } ) ;
58104
@@ -61,7 +107,7 @@ async function getConnection(projectId) {
61107 console . log ( `🔌 Connection closed: ${ key } ` ) ;
62108 } ) ;
63109
64- connection . lastAccessed = new Date ( ) ;
110+ // Save back to your central in-memory store
65111 registry . set ( key , connection ) ;
66112
67113 return connection ;
0 commit comments