Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
2.3 kB ยท 68 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869import { and, eq } from "@openstatus/db";import { usersToWorkspaces } from "@openstatus/db/src/schema";import { z } from "zod";
import { emitAudit } from "../audit";import { type DB, type ServiceContext } from "../context";import { clearIncidentCommander } from "../incident/members";import { revokeGrantsForUser } from "../oauth/revoke";import { deleteSlackUserMappings } from "../slack-user/internal";
// Composite-PK rows: drizzle's createSelectSchema would flatten the join,// but the membership row has no auto-generated columns we'd want to drop// from a snapshot. Inline parser keeps the audit `before` shape stable// even if a column is added to `users_to_workspaces` later.export const memberRowSnapshot = z.object({ userId: z.number(), workspaceId: z.number(), role: z.string(), createdAt: z.coerce.date().nullable(),});
/** * Shared delete+audit body for a single membership row โ the sole place * the `member.delete` write lives. `deleteMember` wraps this with its * owner-actor / self-removal guards; the Stripe downgrade cascade calls it * directly as a `system` actor. This is an extraction, not a duplicate: * the guard-bearing entry point (`deleteMember`) delegates here so the two * callers can't drift on the workspace-scoping or the audit snapshot. * * No authorization guard of its own โ callers own that. Idempotent on the * target row: a missing membership deletes nothing and emits no audit row. */export async function removeMemberInWorkspace(args: { tx: DB; ctx: ServiceContext; userId: number;}): Promise<void> { const { tx, ctx, userId } = args;
const [removed] = await tx .delete(usersToWorkspaces) .where( and( eq(usersToWorkspaces.workspaceId, ctx.workspace.id), eq(usersToWorkspaces.userId, userId), ), ) .returning();
if (!removed) return;
await emitAudit(tx, ctx, { action: "member.delete", entityType: "member", entityId: userId, before: memberRowSnapshot.parse(removed), });
await revokeGrantsForUser({ tx, ctx, userId, workspaceId: ctx.workspace.id, reason: "member_removed", }); await clearIncidentCommander({ tx, ctx, userId }); await deleteSlackUserMappings({ tx, ctx, where: { userId } });}