@@ -3,19 +3,28 @@ import { member, permissions, project, workspace } from '@sim/db/schema'
33import { ORG_ADMIN_ROLES } from '@sim/platform-authz/workspace'
44import { and , asc , eq , inArray , ne , or , sql } from 'drizzle-orm'
55import type { DbOrTx , DbTransaction } from '@/lib/db/types'
6+ import {
7+ deleteObsoleteProjectMemberships ,
8+ getProjectEnvironmentSource ,
9+ } from '@/lib/projects/environment-source'
610import {
711 lockProjectBackfillWrites ,
812 lockProjects ,
913 ProjectConflictError ,
1014} from '@/lib/projects/membership'
1115
1216/** Two indexed lookups; an `OR` around a membership subquery would scan every Project. */
13- async function loadRelatedProjects ( executor : DbOrTx , userId : string , doomedWorkspaceIds : string [ ] ) {
17+ async function loadRelatedProjects (
18+ executor : DbTransaction ,
19+ userId : string ,
20+ doomedWorkspaceIds : string [ ]
21+ ) {
22+ const environments = await getProjectEnvironmentSource ( executor )
1423 const doomedMemberships = doomedWorkspaceIds . length
1524 ? await executor
16- . select ( { projectId : workspace . projectId } )
17- . from ( workspace )
18- . where ( inArray ( workspace . id , doomedWorkspaceIds ) )
25+ . select ( { projectId : environments . projectId } )
26+ . from ( environments )
27+ . where ( inArray ( environments . id , doomedWorkspaceIds ) )
1928 : [ ]
2029 const projectIds = doomedMemberships . flatMap ( ( row ) => ( row . projectId ? [ row . projectId ] : [ ] ) )
2130 return executor
@@ -100,16 +109,17 @@ interface ProjectEnvironment {
100109}
101110
102111/** Every environment of `projectIds`, in one query, keyed by Project. */
103- async function loadProjectEnvironments ( executor : DbOrTx , projectIds : string [ ] ) {
112+ async function loadProjectEnvironments ( executor : DbTransaction , projectIds : string [ ] ) {
113+ const environments = await getProjectEnvironmentSource ( executor )
104114 const rows = projectIds . length
105115 ? await executor
106116 . select ( {
107- projectId : workspace . projectId ,
108- id : workspace . id ,
109- archivedAt : workspace . archivedAt ,
117+ projectId : environments . projectId ,
118+ id : environments . id ,
119+ archivedAt : environments . archivedAt ,
110120 } )
111- . from ( workspace )
112- . where ( inArray ( workspace . projectId , projectIds ) )
121+ . from ( environments )
122+ . where ( inArray ( environments . projectId , projectIds ) )
113123 : [ ]
114124 const byProject = new Map < string , ProjectEnvironment [ ] > ( )
115125 for ( const { projectId, ...environment } of rows ) {
@@ -157,25 +167,27 @@ export async function getProjectAccountDeletionBlockers(
157167 userId : string ,
158168 doomedWorkspaceIds : string [ ]
159169) : Promise < string [ ] > {
160- const records = await loadRelatedProjects ( db , userId , doomedWorkspaceIds )
161- const environments = await loadProjectEnvironments (
162- db ,
163- records . map ( ( record ) => record . id )
164- )
165- const doomed = new Set ( doomedWorkspaceIds )
166- const blockers : string [ ] = [ ]
167- for ( const record of records ) {
168- const decision = await planProjectDeletion (
169- db ,
170- record ,
171- environments . get ( record . id ) ?? [ ] ,
172- userId ,
173- doomed ,
174- false
170+ return db . transaction ( async ( tx ) => {
171+ const records = await loadRelatedProjects ( tx , userId , doomedWorkspaceIds )
172+ const environments = await loadProjectEnvironments (
173+ tx ,
174+ records . map ( ( record ) => record . id )
175175 )
176- if ( 'blocker' in decision ) blockers . push ( decision . blocker )
177- }
178- return blockers
176+ const doomed = new Set ( doomedWorkspaceIds )
177+ const blockers : string [ ] = [ ]
178+ for ( const record of records ) {
179+ const decision = await planProjectDeletion (
180+ tx ,
181+ record ,
182+ environments . get ( record . id ) ?? [ ] ,
183+ userId ,
184+ doomed ,
185+ false
186+ )
187+ if ( 'blocker' in decision ) blockers . push ( decision . blocker )
188+ }
189+ return blockers
190+ } )
179191}
180192
181193/** Transfers surviving Projects and returns private Projects to delete after their workspaces. */
@@ -235,5 +247,6 @@ export async function prepareProjectsForAccountDeletion(
235247 } )
236248 . where ( eq ( project . id , record . id ) )
237249 }
250+ await deleteObsoleteProjectMemberships ( tx , projectsToDelete )
238251 return projectsToDelete
239252}
0 commit comments