55 * List all permission groups with optional filtering.
66 *
77 * Query Parameters:
8- * - organizationId?: string - Filter by organization ID
8+ * - workspaceId?: string - Filter by workspace ID
9+ * - organizationId?: string - Filter by organization ID (joins via workspace)
910 *
1011 * Response: { data: AdminPermissionGroup[], pagination: PaginationMeta }
1112 *
1213 * DELETE /api/v1/admin/access-control
13- * Delete permission groups for an organization.
14+ * Delete permission groups scoped to a workspace or organization (via workspace join) .
1415 * Used when an enterprise plan churns to clean up access control data.
1516 *
1617 * Query Parameters:
17- * - organizationId: string - Delete all permission groups for this organization
18+ * - workspaceId?: string - Delete all permission groups for this workspace
19+ * - organizationId?: string - Delete all permission groups for every workspace in this org
20+ * - reason?: string - Reason recorded in audit log (default: "Enterprise plan churn cleanup")
1821 *
1922 * Response: { success: true, deletedCount: number, membersRemoved: number }
2023 */
2124
2225import { db } from '@sim/db'
23- import { organization , permissionGroup , permissionGroupMember , user } from '@sim/db/schema'
26+ import { permissionGroup , permissionGroupMember , user , workspace } from '@sim/db/schema'
2427import { createLogger } from '@sim/logger'
2528import { count , eq , inArray , sql } from 'drizzle-orm'
29+ import { AuditAction , AuditResourceType , recordAudit } from '@/lib/audit/log'
2630import { withAdminAuth } from '@/app/api/v1/admin/middleware'
2731import {
2832 badRequestResponse ,
@@ -34,8 +38,9 @@ const logger = createLogger('AdminAccessControlAPI')
3438
3539export interface AdminPermissionGroup {
3640 id : string
37- organizationId : string
38- organizationName : string | null
41+ workspaceId : string
42+ workspaceName : string | null
43+ organizationId : string | null
3944 name : string
4045 description : string | null
4146 memberCount : number
@@ -46,27 +51,31 @@ export interface AdminPermissionGroup {
4651
4752export const GET = withAdminAuth ( async ( request ) => {
4853 const url = new URL ( request . url )
54+ const workspaceId = url . searchParams . get ( 'workspaceId' )
4955 const organizationId = url . searchParams . get ( 'organizationId' )
5056
5157 try {
5258 const baseQuery = db
5359 . select ( {
5460 id : permissionGroup . id ,
55- organizationId : permissionGroup . organizationId ,
56- organizationName : organization . name ,
61+ workspaceId : permissionGroup . workspaceId ,
62+ workspaceName : workspace . name ,
63+ workspaceOrganizationId : workspace . organizationId ,
5764 name : permissionGroup . name ,
5865 description : permissionGroup . description ,
5966 createdAt : permissionGroup . createdAt ,
6067 createdByUserId : permissionGroup . createdBy ,
6168 createdByEmail : user . email ,
6269 } )
6370 . from ( permissionGroup )
64- . leftJoin ( organization , eq ( permissionGroup . organizationId , organization . id ) )
71+ . leftJoin ( workspace , eq ( permissionGroup . workspaceId , workspace . id ) )
6572 . leftJoin ( user , eq ( permissionGroup . createdBy , user . id ) )
6673
6774 let groups
68- if ( organizationId ) {
69- groups = await baseQuery . where ( eq ( permissionGroup . organizationId , organizationId ) )
75+ if ( workspaceId ) {
76+ groups = await baseQuery . where ( eq ( permissionGroup . workspaceId , workspaceId ) )
77+ } else if ( organizationId ) {
78+ groups = await baseQuery . where ( eq ( workspace . organizationId , organizationId ) )
7079 } else {
7180 groups = await baseQuery
7281 }
@@ -80,8 +89,9 @@ export const GET = withAdminAuth(async (request) => {
8089
8190 return {
8291 id : group . id ,
83- organizationId : group . organizationId ,
84- organizationName : group . organizationName ,
92+ workspaceId : group . workspaceId ,
93+ workspaceName : group . workspaceName ,
94+ organizationId : group . workspaceOrganizationId ,
8595 name : group . name ,
8696 description : group . description ,
8797 memberCount : memberCount ?. count ?? 0 ,
@@ -93,6 +103,7 @@ export const GET = withAdminAuth(async (request) => {
93103 )
94104
95105 logger . info ( 'Admin API: Listed permission groups' , {
106+ workspaceId,
96107 organizationId,
97108 count : groupsWithCounts . length ,
98109 } )
@@ -107,33 +118,47 @@ export const GET = withAdminAuth(async (request) => {
107118 } ,
108119 } )
109120 } catch ( error ) {
110- logger . error ( 'Admin API: Failed to list permission groups' , { error, organizationId } )
121+ logger . error ( 'Admin API: Failed to list permission groups' , {
122+ error,
123+ workspaceId,
124+ organizationId,
125+ } )
111126 return internalErrorResponse ( 'Failed to list permission groups' )
112127 }
113128} )
114129
115130export const DELETE = withAdminAuth ( async ( request ) => {
116131 const url = new URL ( request . url )
132+ const workspaceId = url . searchParams . get ( 'workspaceId' )
117133 const organizationId = url . searchParams . get ( 'organizationId' )
118134 const reason = url . searchParams . get ( 'reason' ) || 'Enterprise plan churn cleanup'
119135
120- if ( ! organizationId ) {
121- return badRequestResponse ( 'organizationId is required' )
136+ if ( ! workspaceId && ! organizationId ) {
137+ return badRequestResponse ( 'workspaceId or organizationId is required' )
122138 }
123139
124140 try {
125- const existingGroups = await db
126- . select ( { id : permissionGroup . id } )
141+ const selectBase = db
142+ . select ( {
143+ id : permissionGroup . id ,
144+ workspaceId : permissionGroup . workspaceId ,
145+ name : permissionGroup . name ,
146+ } )
127147 . from ( permissionGroup )
128- . where ( eq ( permissionGroup . organizationId , organizationId ) )
148+
149+ const existingGroups = workspaceId
150+ ? await selectBase . where ( eq ( permissionGroup . workspaceId , workspaceId ) )
151+ : await selectBase
152+ . innerJoin ( workspace , eq ( workspace . id , permissionGroup . workspaceId ) )
153+ . where ( eq ( workspace . organizationId , organizationId ! ) )
129154
130155 if ( existingGroups . length === 0 ) {
131- logger . info ( 'Admin API: No permission groups to delete' , { organizationId } )
156+ logger . info ( 'Admin API: No permission groups to delete' , { workspaceId , organizationId } )
132157 return singleResponse ( {
133158 success : true ,
134159 deletedCount : 0 ,
135160 membersRemoved : 0 ,
136- message : 'No permission groups found for the given organization ' ,
161+ message : 'No permission groups found for the given scope ' ,
137162 } )
138163 }
139164
@@ -146,10 +171,24 @@ export const DELETE = withAdminAuth(async (request) => {
146171
147172 const membersToRemove = Number ( memberCountResult ?. count ?? 0 )
148173
149- // Members are deleted via cascade when permission groups are deleted
150- await db . delete ( permissionGroup ) . where ( eq ( permissionGroup . organizationId , organizationId ) )
174+ await db . delete ( permissionGroup ) . where ( inArray ( permissionGroup . id , groupIds ) )
175+
176+ for ( const group of existingGroups ) {
177+ recordAudit ( {
178+ workspaceId : group . workspaceId ,
179+ actorId : 'admin-api' ,
180+ action : AuditAction . PERMISSION_GROUP_DELETED ,
181+ resourceType : AuditResourceType . PERMISSION_GROUP ,
182+ resourceId : group . id ,
183+ resourceName : group . name ,
184+ description : `Admin API deleted permission group "${ group . name } "` ,
185+ metadata : { reason, workspaceId : group . workspaceId , organizationId } ,
186+ request,
187+ } )
188+ }
151189
152190 logger . info ( 'Admin API: Deleted permission groups' , {
191+ workspaceId,
153192 organizationId,
154193 deletedCount : existingGroups . length ,
155194 membersRemoved : membersToRemove ,
@@ -163,7 +202,11 @@ export const DELETE = withAdminAuth(async (request) => {
163202 reason,
164203 } )
165204 } catch ( error ) {
166- logger . error ( 'Admin API: Failed to delete permission groups' , { error, organizationId } )
205+ logger . error ( 'Admin API: Failed to delete permission groups' , {
206+ error,
207+ workspaceId,
208+ organizationId,
209+ } )
167210 return internalErrorResponse ( 'Failed to delete permission groups' )
168211 }
169212} )
0 commit comments