@@ -86,25 +86,23 @@ export async function listSharedResourceProjectIdsForUser(executor: DbOrTx, user
8686}
8787
8888/** Validates the canonical Project owner rather than borrowing an environment's payer. */
89- export async function handoffProjectCreatorReferencesTx (
90- tx : DbTransaction ,
89+ export async function assertProjectCreatorSuccessor (
90+ executor : DbOrTx ,
9191 record : typeof project . $inferSelect ,
9292 departingUserId : string ,
93- environmentIds : string [ ]
93+ environmentIds : string [ ] ,
94+ hold : boolean
9495) {
9596 const successorId = record . ownerId
9697 if ( successorId === departingUserId )
9798 throw new ProjectConflictError (
9899 'Project resources need a successor before this account can leave'
99100 )
100- const [ successor ] = await tx
101- . select ( { id : user . id } )
102- . from ( user )
103- . where ( eq ( user . id , successorId ) )
104- . for ( 'key share' )
101+ const successorQuery = executor . select ( { id : user . id } ) . from ( user ) . where ( eq ( user . id , successorId ) )
102+ const [ successor ] = await ( hold ? successorQuery . for ( 'key share' ) : successorQuery )
105103 if ( ! successor ) throw new ProjectConflictError ( 'Project successor changed; retry the operation' )
106- const [ admin ] = record . organizationId
107- ? await tx
104+ const adminQuery = record . organizationId
105+ ? executor
108106 . select ( { id : member . id } )
109107 . from ( member )
110108 . where (
@@ -114,11 +112,11 @@ export async function handoffProjectCreatorReferencesTx(
114112 inArray ( member . role , ORG_ADMIN_ROLES )
115113 )
116114 )
117- . for ( 'share' )
118- : [ ]
115+ : null
116+ const [ admin ] = adminQuery ? await ( hold ? adminQuery . for ( 'share' ) : adminQuery ) : [ ]
119117 if ( ! admin ) {
120- const grants = environmentIds . length
121- ? await tx
118+ const grantQuery = environmentIds . length
119+ ? executor
122120 . select ( { id : permissions . entityId } )
123121 . from ( permissions )
124122 . where (
@@ -129,13 +127,24 @@ export async function handoffProjectCreatorReferencesTx(
129127 inArray ( permissions . entityId , environmentIds )
130128 )
131129 )
132- . for ( 'share' )
133- : [ ]
130+ : null
131+ const grants = grantQuery ? await ( hold ? grantQuery . for ( 'share' ) : grantQuery ) : [ ]
134132 if ( ! environmentIds . length || grants . length !== environmentIds . length )
135133 throw new ProjectConflictError (
136134 'Project successor must administer every surviving environment'
137135 )
138136 }
137+ }
138+
139+ /** Transfers creator references only after the canonical successor is held and validated. */
140+ export async function handoffProjectCreatorReferencesTx (
141+ tx : DbTransaction ,
142+ record : typeof project . $inferSelect ,
143+ departingUserId : string ,
144+ environmentIds : string [ ]
145+ ) {
146+ await assertProjectCreatorSuccessor ( tx , record , departingUserId , environmentIds , true )
147+ const successorId = record . ownerId
139148 await handoffFileCreatorsInTx (
140149 tx ,
141150 and ( eq ( workspaceFiles . projectId , record . id ) , eq ( workspaceFiles . userId , departingUserId ) ) ,
0 commit comments