(db: DrizzleDb, organizationId: string)
| 38 | * cascades to local `memberships`). Idempotent — a second run deletes nothing. |
| 39 | */ |
| 40 | export const purgeOrganizationData = (db: DrizzleDb, organizationId: string): Promise<void> => |
| 41 | db.transaction(async (tx) => { |
| 42 | // Executor tenant tables — every row is scoped by `tenant = organizationId`. |
| 43 | await tx.delete(tool).where(eq(tool.tenant, organizationId)); |
| 44 | await tx.delete(definition).where(eq(definition.tenant, organizationId)); |
| 45 | await tx.delete(connection).where(eq(connection.tenant, organizationId)); |
| 46 | await tx.delete(integration).where(eq(integration.tenant, organizationId)); |
| 47 | await tx.delete(oauth_client).where(eq(oauth_client.tenant, organizationId)); |
| 48 | await tx.delete(oauth_session).where(eq(oauth_session.tenant, organizationId)); |
| 49 | await tx.delete(tool_policy).where(eq(tool_policy.tenant, organizationId)); |
| 50 | await tx.delete(plugin_storage).where(eq(plugin_storage.tenant, organizationId)); |
| 51 | |
| 52 | // Secrets, OAuth tokens, and cached specs live in `blob`, namespaced by |
| 53 | // owner: `o:<org>/<plugin>` (org scope) and `u:<org>:<subject>/<plugin>` |
| 54 | // (per-user scope). Match both prefixes for this org. |
| 55 | const esc = escapeLike(organizationId); |
| 56 | await tx |
| 57 | .delete(blob) |
| 58 | .where( |
| 59 | or( |
| 60 | sql`${blob.namespace} LIKE ${`o:${esc}/%`} ESCAPE '\\'`, |
| 61 | sql`${blob.namespace} LIKE ${`u:${esc}:%`} ESCAPE '\\'`, |
| 62 | ), |
| 63 | ); |
| 64 | |
| 65 | // Identity mirror — FK `ON DELETE CASCADE` removes local memberships too. |
| 66 | // `accounts` are intentionally left: a user may belong to other orgs. |
| 67 | await tx.delete(organizations).where(eq(organizations.id, organizationId)); |
| 68 | }); |
no test coverage detected