From 1831ca4e751e813f445933f8750a8bb6fdbff4b6 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Fri, 8 May 2026 00:33:47 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20detach=20label=20from=20si?= =?UTF-8?q?te/resoirce?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/auth/actions.ts | 1 + server/private/routers/external.ts | 8 + .../routers/labels/attachLabelToItem.ts | 2 +- .../routers/labels/detachLabelFromItem.ts | 160 ++++++++++++++++++ server/private/routers/labels/index.ts | 1 + 5 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 server/private/routers/labels/detachLabelFromItem.ts diff --git a/server/auth/actions.ts b/server/auth/actions.ts index 5e2f58287..969f9e4ae 100644 --- a/server/auth/actions.ts +++ b/server/auth/actions.ts @@ -152,6 +152,7 @@ export enum ActionsEnum { createOrgLabel = "createOrgLabel", updateOrgLabel = "updateOrgLabel", attachLabelToItem = "attachLabelToItem", + detachLabelFromItem = "detachLabelFromItem", getAlertRule = "getAlertRule", createHealthCheck = "createHealthCheck", updateHealthCheck = "updateHealthCheck", diff --git a/server/private/routers/external.ts b/server/private/routers/external.ts index 7941cd1fe..5b146da18 100644 --- a/server/private/routers/external.ts +++ b/server/private/routers/external.ts @@ -765,6 +765,14 @@ authenticated.put( labels.attachLabelToItem ); +authenticated.delete( + "/org/:orgId/label/:labelId/detach", + verifyValidLicense, + verifyOrgAccess, + verifyUserHasAction(ActionsEnum.detachLabelFromItem), + labels.detachLabelFromItem +); + authenticated.get( "/org/:orgId/health-checks", verifyValidLicense, diff --git a/server/private/routers/labels/attachLabelToItem.ts b/server/private/routers/labels/attachLabelToItem.ts index 4655c3f3b..392332776 100644 --- a/server/private/routers/labels/attachLabelToItem.ts +++ b/server/private/routers/labels/attachLabelToItem.ts @@ -127,7 +127,7 @@ export async function attachLabelToItem( if (resourceCount === 0) { return next( createHttpError( - HttpCode.BAD_REQUEST, + HttpCode.NOT_FOUND, `Resource with Id ${resourceId} doesn't exist.` ) ); diff --git a/server/private/routers/labels/detachLabelFromItem.ts b/server/private/routers/labels/detachLabelFromItem.ts new file mode 100644 index 000000000..1e09234a0 --- /dev/null +++ b/server/private/routers/labels/detachLabelFromItem.ts @@ -0,0 +1,160 @@ +/* + * This file is part of a proprietary work. + * + * Copyright (c) 2025-2026 Fossorial, Inc. + * All rights reserved. + * + * This file is licensed under the Fossorial Commercial License. + * You may not use this file except in compliance with the License. + * Unauthorized use, copying, modification, or distribution is strictly prohibited. + * + * This file is not licensed under the AGPLv3. + */ + +import { + db, + labels, + resourceLabels, + resources, + siteLabels, + sites +} from "@server/db"; +import response from "@server/lib/response"; +import logger from "@server/logger"; +import HttpCode from "@server/types/HttpCode"; +import { and, eq } from "drizzle-orm"; +import { NextFunction, Request, Response } from "express"; +import createHttpError from "http-errors"; +import { z } from "zod"; +import { fromError } from "zod-validation-error"; + +const paramsSchema = z.strictObject({ + orgId: z.string().nonempty(), + labelId: z.string().transform(Number).pipe(z.int().positive()) +}); + +const detachLabelBodySchema = z.strictObject({ + siteId: z.number().int().optional(), + resourceId: z.number().int().optional() +}); + +export async function detachLabelFromItem( + req: Request, + res: Response, + next: NextFunction +) { + try { + const parsedParams = paramsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error).toString() + ) + ); + } + + const { orgId, labelId } = parsedParams.data; + + const parsedBody = detachLabelBodySchema.safeParse(req.body); + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedBody.error).toString() + ) + ); + } + + const { siteId, resourceId } = parsedBody.data; + + if (!siteId && !resourceId) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "At least one of `siteId` or `resourceId` should be provided." + ) + ); + } + + const [existing] = await db + .select() + .from(labels) + .where(and(eq(labels.labelId, labelId), eq(labels.orgId, orgId))); + + if (!existing) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Label with Id ${labelId} not found` + ) + ); + } + + if (siteId) { + const siteCount = await db.$count( + sites, + and(eq(sites.siteId, siteId), eq(sites.orgId, orgId)) + ); + + if (siteCount === 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Site with Id ${siteId} doesn't exist.` + ) + ); + } + + await db + .delete(siteLabels) + .where( + and( + eq(siteLabels.labelId, labelId), + eq(siteLabels.siteId, siteId) + ) + ); + } + + if (resourceId) { + const resourceCount = await db.$count( + resources, + and( + eq(resources.resourceId, resourceId), + eq(resources.orgId, orgId) + ) + ); + + if (resourceCount === 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Resource with Id ${resourceId} doesn't exist.` + ) + ); + } + + await db + .delete(resourceLabels) + .where( + and( + eq(resourceLabels.labelId, labelId), + eq(resourceLabels.resourceId, resourceId) + ) + ); + } + + return response(res, { + data: {}, + success: true, + error: false, + message: "Label detached successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/server/private/routers/labels/index.ts b/server/private/routers/labels/index.ts index cc15ebffe..ffec1229b 100644 --- a/server/private/routers/labels/index.ts +++ b/server/private/routers/labels/index.ts @@ -15,3 +15,4 @@ export * from "./listOrgLabels"; export * from "./createOrgLabel"; export * from "./updateOrgLabel"; export * from "./attachLabelToItem"; +export * from "./detachLabelFromItem";