From adc0a81592b5827cdfee56af695c9cb3b16c0ed4 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Mon, 28 Jul 2025 15:34:56 -0700 Subject: [PATCH] delete org domains and resources on org delete --- server/routers/org/deleteOrg.ts | 58 ++++++++++++++++++++++++++------- src/components/DomainPicker.tsx | 5 ++- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/server/routers/org/deleteOrg.ts b/server/routers/org/deleteOrg.ts index 2f4ddf9e..76e2ad79 100644 --- a/server/routers/org/deleteOrg.ts +++ b/server/routers/org/deleteOrg.ts @@ -1,14 +1,8 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db, resources } from "@server/db"; -import { - newts, - newtSessions, - orgs, - sites, - userActions -} from "@server/db"; -import { eq } from "drizzle-orm"; +import { db, domains, orgDomains, resources } from "@server/db"; +import { newts, newtSessions, orgs, sites, userActions } from "@server/db"; +import { eq, and, inArray, sql } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -126,8 +120,45 @@ export async function deleteOrg( } } - await trx.delete(orgs).where(eq(orgs.orgId, orgId)); + const allOrgDomains = await trx + .select() + .from(orgDomains) + .innerJoin(domains, eq(domains.domainId, orgDomains.domainId)) + .where( + and( + eq(orgDomains.orgId, orgId), + eq(domains.configManaged, false) + ) + ); + + // For each domain, check if it belongs to multiple organizations + const domainIdsToDelete: string[] = []; + for (const orgDomain of allOrgDomains) { + const domainId = orgDomain.domains.domainId; + + // Count how many organizations this domain belongs to + const orgCount = await trx + .select({ count: sql`count(*)` }) + .from(orgDomains) + .where(eq(orgDomains.domainId, domainId)); + + // Only delete the domain if it belongs to exactly 1 organization (the one being deleted) + if (orgCount[0].count === 1) { + domainIdsToDelete.push(domainId); + } + } + + // Delete domains that belong exclusively to this organization + if (domainIdsToDelete.length > 0) { + await trx + .delete(domains) + .where(inArray(domains.domainId, domainIdsToDelete)); + } + + // Delete resources await trx.delete(resources).where(eq(resources.orgId, orgId)); + + await trx.delete(orgs).where(eq(orgs.orgId, orgId)); }); // Send termination messages outside of transaction to prevent blocking @@ -137,8 +168,11 @@ export async function deleteOrg( data: {} }; // Don't await this to prevent blocking the response - sendToClient(newtId, payload).catch(error => { - logger.error("Failed to send termination message to newt:", error); + sendToClient(newtId, payload).catch((error) => { + logger.error( + "Failed to send termination message to newt:", + error + ); }); } diff --git a/src/components/DomainPicker.tsx b/src/components/DomainPicker.tsx index 28dbcdbd..5f4104ea 100644 --- a/src/components/DomainPicker.tsx +++ b/src/components/DomainPicker.tsx @@ -129,9 +129,6 @@ export default function DomainPicker({ if (!userInput.trim()) return options; - // Check if input is more than one level deep (contains multiple dots) - const isMultiLevel = (userInput.match(/\./g) || []).length > 1; - // Add organization domain options organizationDomains.forEach((orgDomain) => { if (orgDomain.type === "cname") { @@ -319,6 +316,8 @@ export default function DomainPicker({ "" ); setUserInput(validInput); + // Clear selection when input changes + setSelectedOption(null); }} />