From e461b231d5f98d4069e03a60e96ed2ba986bfd14 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 12 Oct 2026 16:21:12 -0400 Subject: [PATCH] Add labels to the integration api --- server/openApi.ts | 3 +- server/routers/integration.ts | 51 ++++++++++++++++++++ server/routers/labels/attachLabelToItem.ts | 35 ++++++++++++++ server/routers/labels/createOrgLabel.ts | 34 +++++++++++++ server/routers/labels/deleteOrgLabel.ts | 27 +++++++++++ server/routers/labels/detachLabelFromItem.ts | 35 ++++++++++++++ server/routers/labels/listOrgLabels.ts | 28 +++++++++++ server/routers/labels/updateOrgLabel.ts | 34 +++++++++++++ 8 files changed, 246 insertions(+), 1 deletion(-) diff --git a/server/openApi.ts b/server/openApi.ts index b4b7c1a7a..a9e36948c 100644 --- a/server/openApi.ts +++ b/server/openApi.ts @@ -32,7 +32,8 @@ export enum OpenAPITags { AiProvider = "AI Provider", AiModel = "AI Model", AiBudget = "AI Budget", - VirtualApiKey = "Virtual API Key" + VirtualApiKey = "Virtual API Key", + Label = "Label" } // Order here controls the order tags are displayed in Swagger UI diff --git a/server/routers/integration.ts b/server/routers/integration.ts index bcf5a6a32..87a2db086 100644 --- a/server/routers/integration.ts +++ b/server/routers/integration.ts @@ -16,6 +16,7 @@ import * as siteResource from "./siteResource"; import * as aiProvider from "./aiProvider"; import * as aiBudget from "./aiBudget"; import * as virtualApiKey from "./virtualApiKey"; +import * as labels from "./labels"; import { verifyApiKey, verifyApiKeyOrgAccess, @@ -1833,3 +1834,53 @@ authenticated.get( verifyApiKeyHasAction(ActionsEnum.listAiBudgets), aiBudget.listAiBudgetsForVirtualApiKey ); + +authenticated.get( + "/org/:orgId/labels", + verifyApiKeyOrgAccess, + verifyApiKeyHasAction(ActionsEnum.listOrgLabels), + labels.listOrgLabels +); + +authenticated.post( + "/org/:orgId/labels", + verifyApiKeyOrgAccess, + verifyLimits, + verifyApiKeyHasAction(ActionsEnum.createOrgLabel), + logActionAudit(ActionsEnum.createOrgLabel), + labels.createOrgLabel +); + +authenticated.patch( + "/org/:orgId/label/:labelId", + verifyApiKeyOrgAccess, + verifyLimits, + verifyApiKeyHasAction(ActionsEnum.updateOrgLabel), + logActionAudit(ActionsEnum.updateOrgLabel), + labels.updateOrgLabel +); + +authenticated.delete( + "/org/:orgId/label/:labelId", + verifyApiKeyOrgAccess, + verifyApiKeyHasAction(ActionsEnum.deleteOrgLabel), + logActionAudit(ActionsEnum.deleteOrgLabel), + labels.deleteOrgLabel +); + +authenticated.put( + "/org/:orgId/label/:labelId/attach", + verifyApiKeyOrgAccess, + verifyLimits, + verifyApiKeyHasAction(ActionsEnum.attachLabelToItem), + logActionAudit(ActionsEnum.attachLabelToItem), + labels.attachLabelToItem +); + +authenticated.put( + "/org/:orgId/label/:labelId/detach", + verifyApiKeyOrgAccess, + verifyApiKeyHasAction(ActionsEnum.detachLabelFromItem), + logActionAudit(ActionsEnum.detachLabelFromItem), + labels.detachLabelFromItem +); diff --git a/server/routers/labels/attachLabelToItem.ts b/server/routers/labels/attachLabelToItem.ts index 0d9e49b35..6f1234dc6 100644 --- a/server/routers/labels/attachLabelToItem.ts +++ b/server/routers/labels/attachLabelToItem.ts @@ -12,6 +12,7 @@ import { } from "@server/db"; import response from "@server/lib/response"; import logger from "@server/logger"; +import { OpenAPITags, registry } from "@server/openApi"; import HttpCode from "@server/types/HttpCode"; import { and, eq, isNull } from "drizzle-orm"; import { NextFunction, Request, Response } from "express"; @@ -31,6 +32,40 @@ const attachLabelBodySchema = z.strictObject({ clientId: z.number().int().optional() }); +registry.registerPath({ + method: "put", + path: "/org/{orgId}/label/{labelId}/attach", + description: + "Attach a label to a site, public resource, private resource or client.", + tags: [OpenAPITags.Label], + request: { + params: paramsSchema, + body: { + content: { + "application/json": { + schema: attachLabelBodySchema + } + } + } + }, + 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 attachLabelToItem( req: Request, res: Response, diff --git a/server/routers/labels/createOrgLabel.ts b/server/routers/labels/createOrgLabel.ts index 6fa20816e..07e505c43 100644 --- a/server/routers/labels/createOrgLabel.ts +++ b/server/routers/labels/createOrgLabel.ts @@ -9,6 +9,7 @@ import { import response from "@server/lib/response"; import logger from "@server/logger"; import type { CreateOrEditLabelResponse } from "@server/routers/labels/types"; +import { OpenAPITags, registry } from "@server/openApi"; import HttpCode from "@server/types/HttpCode"; import { and, eq, sql } from "drizzle-orm"; import { NextFunction, Request, Response } from "express"; @@ -30,6 +31,39 @@ const bodySchema = z.strictObject({ resourceId: z.number().int().optional() }); +registry.registerPath({ + method: "post", + path: "/org/{orgId}/labels", + description: "Create a label in an organization.", + tags: [OpenAPITags.Label], + request: { + params: paramsSchema, + body: { + content: { + "application/json": { + schema: bodySchema + } + } + } + }, + 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 createOrgLabel( req: Request, res: Response, diff --git a/server/routers/labels/deleteOrgLabel.ts b/server/routers/labels/deleteOrgLabel.ts index c46e67437..50908bf17 100644 --- a/server/routers/labels/deleteOrgLabel.ts +++ b/server/routers/labels/deleteOrgLabel.ts @@ -1,6 +1,7 @@ import { db, labels } from "@server/db"; import response from "@server/lib/response"; import logger from "@server/logger"; +import { OpenAPITags, registry } from "@server/openApi"; import HttpCode from "@server/types/HttpCode"; import { and, eq } from "drizzle-orm"; import { NextFunction, Request, Response } from "express"; @@ -13,6 +14,32 @@ const paramsSchema = z.strictObject({ labelId: z.string().transform(Number).pipe(z.int().positive()) }); +registry.registerPath({ + method: "delete", + path: "/org/{orgId}/label/{labelId}", + description: "Delete a label.", + tags: [OpenAPITags.Label], + request: { + params: paramsSchema + }, + 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 deleteOrgLabel( req: Request, res: Response, diff --git a/server/routers/labels/detachLabelFromItem.ts b/server/routers/labels/detachLabelFromItem.ts index 52c752ca8..147fc47e5 100644 --- a/server/routers/labels/detachLabelFromItem.ts +++ b/server/routers/labels/detachLabelFromItem.ts @@ -12,6 +12,7 @@ import { } from "@server/db"; import response from "@server/lib/response"; import logger from "@server/logger"; +import { OpenAPITags, registry } from "@server/openApi"; import HttpCode from "@server/types/HttpCode"; import { and, eq, isNull } from "drizzle-orm"; import { NextFunction, Request, Response } from "express"; @@ -31,6 +32,40 @@ const detachLabelBodySchema = z.strictObject({ clientId: z.number().int().optional() }); +registry.registerPath({ + method: "put", + path: "/org/{orgId}/label/{labelId}/detach", + description: + "Detach a label from a site, public resource, private resource or client.", + tags: [OpenAPITags.Label], + request: { + params: paramsSchema, + body: { + content: { + "application/json": { + schema: detachLabelBodySchema + } + } + } + }, + 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 detachLabelFromItem( req: Request, res: Response, diff --git a/server/routers/labels/listOrgLabels.ts b/server/routers/labels/listOrgLabels.ts index 6d6b853cf..7e77316b6 100644 --- a/server/routers/labels/listOrgLabels.ts +++ b/server/routers/labels/listOrgLabels.ts @@ -2,6 +2,7 @@ import { db, labels } from "@server/db"; import response from "@server/lib/response"; import logger from "@server/logger"; import type { ListOrgLabelsResponse } from "@server/routers/labels/types"; +import { OpenAPITags, registry } from "@server/openApi"; import HttpCode from "@server/types/HttpCode"; import { and, asc, eq, like, sql } from "drizzle-orm"; import { NextFunction, Request, Response } from "express"; @@ -51,6 +52,33 @@ function queryLabelsBase() { .from(labels); } +registry.registerPath({ + method: "get", + path: "/org/{orgId}/labels", + description: "List labels in an organization.", + tags: [OpenAPITags.Label], + request: { + params: paramsSchema, + query: listLabelsSchema + }, + 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 listOrgLabels( req: Request, res: Response, diff --git a/server/routers/labels/updateOrgLabel.ts b/server/routers/labels/updateOrgLabel.ts index fe8a6f573..424908241 100644 --- a/server/routers/labels/updateOrgLabel.ts +++ b/server/routers/labels/updateOrgLabel.ts @@ -2,6 +2,7 @@ import { db, labels } from "@server/db"; import response from "@server/lib/response"; import logger from "@server/logger"; import type { CreateOrEditLabelResponse } from "@server/routers/labels/types"; +import { OpenAPITags, registry } from "@server/openApi"; import HttpCode from "@server/types/HttpCode"; import { and, eq, ne, sql } from "drizzle-orm"; import { NextFunction, Request, Response } from "express"; @@ -22,6 +23,39 @@ const updateLabelBodySchema = z.strictObject({ .nonempty() }); +registry.registerPath({ + method: "patch", + path: "/org/{orgId}/label/{labelId}", + description: "Update a label.", + tags: [OpenAPITags.Label], + request: { + params: paramsSchema, + body: { + content: { + "application/json": { + schema: updateLabelBodySchema + } + } + } + }, + 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 updateOrgLabel( req: Request, res: Response,