From 9853122a51c8c03b547add70534be57443a61f5d Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Wed, 2 Sep 2026 00:25:49 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20delete=20org?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/external.ts | 5 ++ server/routers/org/adminDeleteOrg.ts | 98 ++++++++++++++++++++++++++++ server/routers/org/index.ts | 1 + src/components/OrgsTable.tsx | 13 ++-- 4 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 server/routers/org/adminDeleteOrg.ts diff --git a/server/routers/external.ts b/server/routers/external.ts index 9bd3a3f4f..67197d1ca 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -88,6 +88,11 @@ authenticated.put("/org", getUserOrgs, org.createOrg); authenticated.get("/orgs", verifyUserIsServerAdmin, org.listOrgs); authenticated.get("/admin/orgs", verifyUserIsServerAdmin, org.adminListOrgs); +authenticated.delete( + "/admin/org/:orgId", + verifyUserIsServerAdmin, + org.adminDeleteOrg +); authenticated.get("/user/:userId/orgs", verifyIsLoggedInUser, org.listUserOrgs); authenticated.get( diff --git a/server/routers/org/adminDeleteOrg.ts b/server/routers/org/adminDeleteOrg.ts new file mode 100644 index 000000000..3f7fd0607 --- /dev/null +++ b/server/routers/org/adminDeleteOrg.ts @@ -0,0 +1,98 @@ +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import response from "@server/lib/response"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +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, orgs } from "@server/db"; +import { eq } from "drizzle-orm"; + +const adminDeleteOrgSchema = z.strictObject({ + orgId: z.string() +}); + +export type AdminDeleteOrgResponse = {}; + +registry.registerPath({ + method: "delete", + path: "/admin/org/{orgId}", + description: "Delete any organization in the system (server admin).", + tags: [OpenAPITags.Org], + request: { + params: adminDeleteOrgSchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + +export async function adminDeleteOrg( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedParams = adminDeleteOrgSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error).toString() + ) + ); + } + const { orgId } = parsedParams.data; + + const [org] = await db + .select() + .from(orgs) + .where(eq(orgs.orgId, orgId)) + .limit(1); + + if (!org) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Organization with ID ${orgId} not found` + ) + ); + } + + const result = await deleteOrgById(orgId); + sendTerminationMessages(result); + return response(res, { + data: null, + success: true, + error: false, + message: "Organization deleted successfully", + status: HttpCode.OK + }); + } catch (error) { + if (createHttpError.isHttpError(error)) { + return next(error); + } + logger.error(error); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred..." + ) + ); + } +} diff --git a/server/routers/org/index.ts b/server/routers/org/index.ts index afda206a1..461ef003f 100644 --- a/server/routers/org/index.ts +++ b/server/routers/org/index.ts @@ -10,3 +10,4 @@ export * from "./pickOrgDefaults"; export * from "./checkOrgUserAccess"; export * from "./resetOrgBandwidth"; export * from "./adminListOrgs"; +export * from "./adminDeleteOrg"; diff --git a/src/components/OrgsTable.tsx b/src/components/OrgsTable.tsx index 112c19987..c29e45540 100644 --- a/src/components/OrgsTable.tsx +++ b/src/components/OrgsTable.tsx @@ -8,8 +8,10 @@ import { import { useNavigationContext } from "@app/hooks/useNavigationContext"; import { toast } from "@app/hooks/useToast"; import { getNextSortOrder, getSortDirection } from "@app/lib/sortColumn"; -import type { AdminOrgRow, DeleteOrgResponse } from "@server/routers/org"; +import type { AdminOrgRow } from "@server/routers/org"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { createApiClient, formatAxiosError } from "@app/lib/api"; import { type PaginationState } from "@tanstack/react-table"; import { ArrowDown01Icon, @@ -24,10 +26,6 @@ import { useRouter } from "next/navigation"; import { useMemo, useState, useTransition } from "react"; import { useDebouncedCallback } from "use-debounce"; import ConfirmDeleteDialog from "./ConfirmDeleteDialog"; -import { createApiClient, formatAxiosError } from "@app/lib/api"; -import type { AxiosResponse } from "axios"; -import api from "gpt-tokenizer"; -import { useEnvContext } from "@app/hooks/useEnvContext"; type OrgTableProps = { orgs: AdminOrgRow[]; @@ -236,10 +234,7 @@ export default function OrgsTable({ async function deleteOrg(orgId: string) { try { - // TODO - // const res = await api.delete>( - // `/org/${orgId}` - // ); + const res = await api.delete(`/admin/org/${orgId}`); toast({ title: t("orgDeleted"), description: t("orgDeletedMessage")