diff --git a/server/routers/external.ts b/server/routers/external.ts index 51cd51f9..45ab58bb 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -86,16 +86,14 @@ authenticated.post( org.updateOrg ); -if (build !== "saas") { - authenticated.delete( - "/org/:orgId", - verifyOrgAccess, - verifyUserIsOrgOwner, - verifyUserHasAction(ActionsEnum.deleteOrg), - logActionAudit(ActionsEnum.deleteOrg), - org.deleteOrg - ); -} +authenticated.delete( + "/org/:orgId", + verifyOrgAccess, + verifyUserIsOrgOwner, + verifyUserHasAction(ActionsEnum.deleteOrg), + logActionAudit(ActionsEnum.deleteOrg), + org.deleteOrg +); authenticated.put( "/org/:orgId/site", diff --git a/server/routers/org/deleteOrg.ts b/server/routers/org/deleteOrg.ts index 0e5b87a2..7de02162 100644 --- a/server/routers/org/deleteOrg.ts +++ b/server/routers/org/deleteOrg.ts @@ -7,6 +7,8 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; import { deleteOrgById, sendTerminationMessages } from "@server/lib/deleteOrg"; +import { db, userOrgs, orgs } from "@server/db"; +import { eq, and } from "drizzle-orm"; const deleteOrgSchema = z.strictObject({ orgId: z.string() @@ -41,6 +43,48 @@ export async function deleteOrg( ); } const { orgId } = parsedParams.data; + + const [data] = await db + .select() + .from(userOrgs) + .innerJoin(orgs, eq(userOrgs.orgId, orgs.orgId)) + .where( + and( + eq(userOrgs.orgId, orgId), + eq(userOrgs.userId, req.user!.userId) + ) + ); + + const org = data?.orgs; + const userOrg = data?.userOrgs; + + if (!org || !userOrg) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Organization with ID ${orgId} not found` + ) + ); + } + + if (!userOrg.isOwner) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + "Only organization owners can delete the organization" + ) + ); + } + + if (org.isBillingOrg) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Cannot delete a primary organization" + ) + ); + } + const result = await deleteOrgById(orgId); sendTerminationMessages(result); return response(res, { diff --git a/src/app/[orgId]/settings/general/page.tsx b/src/app/[orgId]/settings/general/page.tsx index 0b3ae3d5..0a2ed39b 100644 --- a/src/app/[orgId]/settings/general/page.tsx +++ b/src/app/[orgId]/settings/general/page.tsx @@ -3,11 +3,7 @@ import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; import { Button } from "@app/components/ui/button"; import { useOrgContext } from "@app/hooks/useOrgContext"; import { toast } from "@app/hooks/useToast"; -import { - useState, - useTransition, - useActionState -} from "react"; +import { useState, useTransition, useActionState } from "react"; import { Form, FormControl, @@ -54,7 +50,7 @@ export default function GeneralPage() { return ( - {build !== "saas" && } + {!org.org.isBillingOrg && } ); } diff --git a/src/components/OrgSelector.tsx b/src/components/OrgSelector.tsx index 45fed43c..f5351362 100644 --- a/src/components/OrgSelector.tsx +++ b/src/components/OrgSelector.tsx @@ -25,7 +25,7 @@ import { useEnvContext } from "@app/hooks/useEnvContext"; import { cn } from "@app/lib/cn"; import { ListUserOrgsResponse } from "@server/routers/org"; import { Check, ChevronsUpDown, Plus, Building2, Users } from "lucide-react"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { useMemo, useState } from "react"; import { useUserContext } from "@app/hooks/useUserContext"; import { useTranslations } from "next-intl"; @@ -44,6 +44,7 @@ export function OrgSelector({ const { user } = useUserContext(); const [open, setOpen] = useState(false); const router = useRouter(); + const pathname = usePathname(); const { env } = useEnvContext(); const t = useTranslations(); @@ -141,7 +142,11 @@ export function OrgSelector({ key={org.orgId} onSelect={() => { setOpen(false); - router.push(`/${org.orgId}/settings`); + const newPath = pathname.replace( + /^\/[^/]+/, + `/${org.orgId}` + ); + router.push(newPath); }} className="mx-2 rounded-md" >