@@ -186,28 +186,28 @@ const sanitizeProjectResponse = (projectObj) => {
186186} ;
187187
188188module . exports . createProject = async ( req , res ) => {
189+ const session = await mongoose . startSession ( ) ;
190+ session . startTransaction ( ) ;
191+
189192 try {
190193 // POST FOR - PROJECT CREATION
191194 const { name, description, siteUrl } = createProjectSchema . parse ( req . body ) ;
192195
193- // --- PROJECT LIMIT CHECK ---
194- const ADMIN_EMAIL = process . env . ADMIN_EMAIL ;
195-
196- // GET MAX PROJECTS
197- const dev = await Developer . findById ( req . user . _id ) ;
198- const MAX_PROJECTS = dev ?. maxProjects || 1 ;
199-
200- const isUserAdmin = dev . email === ADMIN_EMAIL ;
201- const projectCount = await Project . countDocuments ( { owner : req . user . _id } ) ;
196+ // Atomic limit enforcement: count and create within transaction
197+ if ( req . projectLimit !== undefined ) {
198+ const currentCount = await Project . countDocuments (
199+ { owner : req . user . _id } ,
200+ { session }
201+ ) ;
202202
203- if ( ! isUserAdmin && projectCount >= MAX_PROJECTS ) {
204- return res . status ( 403 ) . json ( {
205- error : `Project limit reached. Your current plan allows up to ${ MAX_PROJECTS } projects.` ,
206- limit : MAX_PROJECTS ,
207- current : projectCount ,
208- } ) ;
203+ if ( currentCount >= req . projectLimit ) {
204+ await session . abortTransaction ( ) ;
205+ session . endSession ( ) ;
206+ return res . status ( 403 ) . json ( {
207+ error : `Project limit reached (${ req . projectLimit } ). Please upgrade your plan to create more projects.`
208+ } ) ;
209+ }
209210 }
210- // ---------------------------
211211
212212 const rawPublishableKey = generateApiKey ( "pk_live_" ) ;
213213 const hashedPublishableKey = hashApiKey ( rawPublishableKey ) ;
@@ -226,7 +226,10 @@ module.exports.createProject = async (req, res) => {
226226 jwtSecret : rawJwtSecret ,
227227 siteUrl : siteUrl || "" ,
228228 } ) ;
229- await newProject . save ( ) ;
229+ await newProject . save ( { session } ) ;
230+
231+ await session . commitTransaction ( ) ;
232+ session . endSession ( ) ;
230233
231234 const projectObj = newProject . toObject ( ) ;
232235 projectObj . publishableKey = rawPublishableKey ;
@@ -236,6 +239,9 @@ module.exports.createProject = async (req, res) => {
236239
237240 res . status ( 201 ) . json ( projectObj ) ;
238241 } catch ( err ) {
242+ await session . abortTransaction ( ) ;
243+ session . endSession ( ) ;
244+
239245 if ( err instanceof z . ZodError ) {
240246 return res . status ( 400 ) . json ( { error : err . issues } ) ;
241247 }
@@ -562,6 +568,8 @@ module.exports.createCollection = async (req, res) => {
562568 let collectionWasPersisted = false ;
563569 let collectionNameForRollback ;
564570 let collectionExistedBefore = false ;
571+ const session = await mongoose . startSession ( ) ;
572+ session . startTransaction ( ) ;
565573
566574 try {
567575 const { projectId, collectionName, schema } = createCollectionSchema . parse (
@@ -573,19 +581,39 @@ module.exports.createCollection = async (req, res) => {
573581 project = await Project . findOne ( {
574582 _id : projectId ,
575583 owner : req . user . _id ,
576- } ) ;
577- if ( ! project ) return res . status ( 404 ) . json ( { error : "Project not found" } ) ;
584+ } ) . session ( session ) ;
585+ if ( ! project ) {
586+ await session . abortTransaction ( ) ;
587+ session . endSession ( ) ;
588+ return res . status ( 404 ) . json ( { error : "Project not found" } ) ;
589+ }
578590
579591 const exists = project . collections . find ( ( c ) => c . name === collectionName ) ;
580- if ( exists )
592+ if ( exists ) {
593+ await session . abortTransaction ( ) ;
594+ session . endSession ( ) ;
581595 return res . status ( 400 ) . json ( { error : "Collection already exists" } ) ;
596+ }
597+
598+ // Atomic limit enforcement within transaction
599+ if ( req . collectionLimit !== undefined ) {
600+ if ( project . collections . length >= req . collectionLimit ) {
601+ await session . abortTransaction ( ) ;
602+ session . endSession ( ) ;
603+ return res . status ( 403 ) . json ( {
604+ error : `Collection limit reached (${ req . collectionLimit } ). Please upgrade your plan to create more collections.`
605+ } ) ;
606+ }
607+ }
582608
583609 if ( ! project . jwtSecret ) {
584610 project . jwtSecret = generateApiKey ( "jwt_" ) ;
585611 }
586612
587613 if ( collectionName === "users" ) {
588614 if ( ! validateUsersSchema ( schema ) ) {
615+ await session . abortTransaction ( ) ;
616+ session . endSession ( ) ;
589617 return res . status ( 422 ) . json ( {
590618 error :
591619 "The 'users' collection must have required 'email' and 'password' string fields." ,
@@ -604,7 +632,7 @@ module.exports.createCollection = async (req, res) => {
604632 } ;
605633
606634 project . collections . push ( newCollectionConfig ) ;
607- await project . save ( ) ;
635+ await project . save ( { session } ) ;
608636 collectionWasPersisted = true ;
609637
610638 connection = await getConnection ( projectId ) ;
@@ -622,6 +650,9 @@ module.exports.createCollection = async (req, res) => {
622650
623651 await createUniqueIndexes ( Model , newCollectionConfig . model ) ;
624652
653+ await session . commitTransaction ( ) ;
654+ session . endSession ( ) ;
655+
625656 await deleteProjectById ( projectId ) ;
626657 await setProjectById ( projectId , project . toObject ( ) ) ;
627658 await deleteProjectByApiKeyCache ( project . publishableKey ) ;
@@ -634,14 +665,10 @@ module.exports.createCollection = async (req, res) => {
634665
635666 return res . status ( 201 ) . json ( projectObj ) ;
636667 } catch ( err ) {
637- try {
638- if ( project && collectionWasPersisted ) {
639- project . collections = project . collections . filter (
640- ( c ) => c . name !== collectionNameForRollback ,
641- ) ;
642- await project . save ( ) ;
643- }
668+ await session . abortTransaction ( ) ;
669+ session . endSession ( ) ;
644670
671+ try {
645672 if ( connection && compiledCollectionName ) {
646673 clearCompiledModel ( connection , compiledCollectionName ) ;
647674
@@ -2006,4 +2033,4 @@ module.exports.updateCollectionRls = async (req, res) => {
20062033 } catch ( err ) {
20072034 res . status ( 500 ) . json ( { error : err . message } ) ;
20082035 }
2009- }
2036+ }
0 commit comments