/** * Org management procedures - update, delete, leave */ import { ORPCError } from "@orpc/server"; import { authMiddleware, os } from "../base.js"; import { countOwners, getMembership, lookupOrgBySlug, requireRole, } from "./helpers.js"; /** * Update org details * Requires admin or owner role */ export const orgsUpdate = os.orgs.update .use(authMiddleware) .handler(async ({ input, context }) => { const { slug, displayName, logoUrl } = input; // Lookup org and verify membership with admin+ role const org = await lookupOrgBySlug(context.db, slug); const membership = await getMembership(context.db, org.id, context.user.id); requireRole(membership, "admin"); // Build update object with only provided fields const updates: Record = { updated_at: new Date() }; if (displayName !== undefined) { updates.display_name = displayName; } if (logoUrl !== undefined) { updates.logo_url = logoUrl; } await context.db .updateTable("orgs") .set(updates) .where("id", "=", org.id) .execute(); return { success: true }; }); /** * Delete an org * Requires owner role * FK CASCADE handles deleting members, invites, and sites */ export const orgsDelete = os.orgs.delete .use(authMiddleware) .handler(async ({ input, context }) => { const { slug } = input; // Lookup org and verify ownership const org = await lookupOrgBySlug(context.db, slug); const membership = await getMembership(context.db, org.id, context.user.id); requireRole(membership, "owner"); await context.db.deleteFrom("orgs").where("id", "=", org.id).execute(); return { success: true }; }); /** * Leave an org * Cannot leave if you're the only owner * Uses transaction to prevent race condition where multiple owners leave simultaneously */ export const orgsLeave = os.orgs.leave .use(authMiddleware) .handler(async ({ input, context }) => { const { slug } = input; // Lookup org and get membership const org = await lookupOrgBySlug(context.db, slug); const membership = await getMembership(context.db, org.id, context.user.id); await context.db.transaction().execute(async (trx) => { // If user is an owner, check if they're the last one (with lock) if (membership.role === "owner") { const ownerCount = await countOwners(trx, org.id); if (ownerCount === 1) { throw new ORPCError("BAD_REQUEST", { message: "Cannot leave as the only owner. Transfer ownership or delete the organization.", }); } } // Remove membership await trx .deleteFrom("org_members") .where("org_id", "=", org.id) .where("user_id", "=", context.user.id) .execute(); }); return { success: true }; });