( db: DrizzleDb, organizationId: string, deletedAt: Date, )
| 52 | * nothing and keeps the first mark. |
| 53 | */ |
| 54 | export const purgeOrganizationData = ( |
| 55 | db: DrizzleDb, |
| 56 | organizationId: string, |
| 57 | deletedAt: Date, |
| 58 | ): Promise<void> => |
| 59 | db.transaction(async (tx) => { |
| 60 | // Executor tenant tables — every row is scoped by `tenant = organizationId`. |
| 61 | await tx.delete(tool).where(eq(tool.tenant, organizationId)); |
| 62 | await tx.delete(definition).where(eq(definition.tenant, organizationId)); |
| 63 | await tx.delete(connection).where(eq(connection.tenant, organizationId)); |
| 64 | await tx.delete(integration).where(eq(integration.tenant, organizationId)); |
| 65 | await tx.delete(oauth_client).where(eq(oauth_client.tenant, organizationId)); |
| 66 | await tx.delete(oauth_session).where(eq(oauth_session.tenant, organizationId)); |
| 67 | await tx.delete(tool_policy).where(eq(tool_policy.tenant, organizationId)); |
| 68 | await tx.delete(plugin_storage).where(eq(plugin_storage.tenant, organizationId)); |
| 69 | await tx.delete(subject).where(eq(subject.tenant, organizationId)); |
| 70 | await tx.delete(artifact).where(eq(artifact.tenant, organizationId)); |
| 71 | |
| 72 | // Secrets, OAuth tokens, and cached specs live in `blob`, namespaced by |
| 73 | // owner: `o:<org>/<plugin>` (org scope) and `u:<org>:<subject>/<plugin>` |
| 74 | // (per-user scope). Match both prefixes for this org. |
| 75 | const esc = escapeLike(organizationId); |
| 76 | await tx |
| 77 | .delete(blob) |
| 78 | .where( |
| 79 | or( |
| 80 | sql`${blob.namespace} LIKE ${`o:${esc}/%`} ESCAPE '\\'`, |
| 81 | sql`${blob.namespace} LIKE ${`u:${esc}:%`} ESCAPE '\\'`, |
| 82 | ), |
| 83 | ); |
| 84 | |
| 85 | // Identity mirror: the memberships go, the organization row stays as a |
| 86 | // tombstone (see the header). `accounts` are intentionally left: a user |
| 87 | // may belong to other orgs. |
| 88 | await tx.delete(memberships).where(eq(memberships.organizationId, organizationId)); |
| 89 | await tx |
| 90 | .update(organizations) |
| 91 | .set({ |
| 92 | deletedAt: sql`coalesce(${organizations.deletedAt}, ${deletedAt.toISOString()}::timestamptz)`, |
| 93 | }) |
| 94 | .where(eq(organizations.id, organizationId)); |
| 95 | }); |
no test coverage detected