Fix custom parser OpenAPI types and add structured default response schema

Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/73990123-9c27-444b-bc6e-77e890a0d57c

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-05-17 06:38:44 +00:00
committed by GitHub
parent 82745c701a
commit 9cec711427
66 changed files with 98 additions and 76 deletions

View File

@@ -152,11 +152,17 @@ function getOpenApiDocumentation() {
if (!hasExistingResponses) {
def.route.responses = {
"*": {
description: "",
"200": {
description: "Successful response",
content: {
"application/json": {
schema: z.object({})
schema: z.object({
data: z.unknown().nullable(),
success: z.boolean(),
error: z.boolean(),
message: z.string(),
status: z.number()
})
}
}
}

View File

@@ -873,7 +873,13 @@ export const portRangeStringSchema = z
message:
'Port range must be "*" for all ports, or a comma-separated list of ports and ranges (e.g., "80,443,8000-9000"). Ports must be between 1 and 65535, and ranges must have start <= end.'
}
);
)
.openapi({
type: "string",
description:
'Port range string. Use "*" for all ports, a comma-separated list of ports, or ranges (e.g., "80,443,8000-9000"). Ports must be between 1 and 65535.',
example: "80,443,8000-9000"
});
/**
* Parses a port range string into an array of port range objects

View File

@@ -24,7 +24,7 @@ import type { NextFunction, Request, Response } from "express";
const paramsSchema = z.strictObject({
orgId: z.string(),
approvalId: z.string().transform(Number).pipe(z.int().positive())
approvalId: z.coerce.number().int().positive()
});
const bodySchema = z.strictObject({

View File

@@ -25,7 +25,7 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const restartCertificateParamsSchema = z.strictObject({
certId: z.string().transform(stoi).pipe(z.int().positive()),
certId: z.coerce.number().int().positive(),
orgId: z.string()
});
@@ -36,7 +36,7 @@ registry.registerPath({
tags: ["Certificate"],
request: {
params: z.object({
certId: z.string().transform(stoi).pipe(z.int().positive()),
certId: z.coerce.number().int().positive(),
orgId: z.string()
})
},

View File

@@ -28,7 +28,7 @@ import { OlmErrorCodes, sendOlmError } from "@server/routers/olm/error";
import { sendTerminateClient } from "@server/routers/client/terminate";
const reGenerateSecretParamsSchema = z.strictObject({
clientId: z.string().transform(Number).pipe(z.int().positive())
clientId: z.coerce.number().int().positive()
});
const reGenerateSecretBodySchema = z.strictObject({

View File

@@ -27,7 +27,7 @@ import { getAllowedIps } from "@server/routers/target/helpers";
import { disconnectClient, sendToClient } from "#private/routers/ws";
const updateSiteParamsSchema = z.strictObject({
siteId: z.string().transform(Number).pipe(z.int().positive())
siteId: z.coerce.number().int().positive()
});
const updateSiteBodySchema = z.strictObject({

View File

@@ -27,7 +27,7 @@ import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAs
const addUserRoleParamsSchema = z.strictObject({
userId: z.string(),
roleId: z.string().transform(stoi).pipe(z.number())
roleId: z.coerce.number()
});
registry.registerPath({

View File

@@ -27,7 +27,7 @@ import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAs
const removeUserRoleParamsSchema = z.strictObject({
userId: z.string(),
roleId: z.string().transform(stoi).pipe(z.number())
roleId: z.coerce.number()
});
registry.registerPath({

View File

@@ -31,7 +31,7 @@ export const generateAccessTokenBodySchema = z.strictObject({
});
export const generateAccssTokenParamsSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
export type GenerateAccessTokenResponse = Omit<

View File

@@ -9,7 +9,7 @@ import logger from "@server/logger";
export const params = z.strictObject({
token: z.string(),
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
export type CheckResourceSessionParams = z.infer<typeof params>;

View File

@@ -13,7 +13,7 @@ import { OpenAPITags, registry } from "@server/openApi";
import { BlueprintData } from "./types";
const getBlueprintSchema = z.strictObject({
blueprintId: z.string().transform(stoi).pipe(z.int().positive()),
blueprintId: z.coerce.number().int().positive(),
orgId: z.string()
});

View File

@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const archiveClientSchema = z.strictObject({
clientId: z.string().transform(Number).pipe(z.int().positive())
clientId: z.coerce.number().int().positive()
});
registry.registerPath({

View File

@@ -13,7 +13,7 @@ import { sendTerminateClient } from "./terminate";
import { OlmErrorCodes } from "../olm/error";
const blockClientSchema = z.strictObject({
clientId: z.string().transform(Number).pipe(z.int().positive())
clientId: z.coerce.number().int().positive()
});
registry.registerPath({

View File

@@ -14,7 +14,7 @@ import { sendTerminateClient } from "./terminate";
import { OlmErrorCodes } from "../olm/error";
const deleteClientSchema = z.strictObject({
clientId: z.string().transform(Number).pipe(z.int().positive())
clientId: z.coerce.number().int().positive()
});
registry.registerPath({

View File

@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const unarchiveClientSchema = z.strictObject({
clientId: z.string().transform(Number).pipe(z.int().positive())
clientId: z.coerce.number().int().positive()
});
registry.registerPath({

View File

@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const unblockClientSchema = z.strictObject({
clientId: z.string().transform(Number).pipe(z.int().positive())
clientId: z.coerce.number().int().positive()
});
registry.registerPath({

View File

@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const updateClientParamsSchema = z.strictObject({
clientId: z.string().transform(Number).pipe(z.int().positive())
clientId: z.coerce.number().int().positive()
});
const updateClientSchema = z.strictObject({

View File

@@ -22,7 +22,7 @@ const addEmailToResourceWhitelistBodySchema = z.strictObject({
});
const addEmailToResourceWhitelistParamsSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
registry.registerPath({

View File

@@ -20,7 +20,7 @@ export const authWithPasswordBodySchema = z.strictObject({
});
export const authWithPasswordParamsSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
export type AuthWithPasswordResponse = {

View File

@@ -19,7 +19,7 @@ export const authWithPincodeBodySchema = z.strictObject({
});
export const authWithPincodeParamsSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
export type AuthWithPincodeResponse = {

View File

@@ -20,7 +20,7 @@ const authWithWhitelistBodySchema = z.strictObject({
});
const authWithWhitelistParamsSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
export type AuthWithWhitelistResponse = {

View File

@@ -25,7 +25,7 @@ const createResourceRuleSchema = z.strictObject({
});
const createResourceRuleParamsSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
registry.registerPath({

View File

@@ -15,7 +15,7 @@ import { OpenAPITags, registry } from "@server/openApi";
// Define Zod schema for request parameters validation
const deleteResourceSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
registry.registerPath({

View File

@@ -11,8 +11,8 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const deleteResourceRuleSchema = z.strictObject({
ruleId: z.string().transform(Number).pipe(z.int().positive()),
resourceId: z.string().transform(Number).pipe(z.int().positive())
ruleId: z.coerce.number().int().positive(),
resourceId: z.coerce.number().int().positive()
});
registry.registerPath({

View File

@@ -17,7 +17,7 @@ import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
import { logAccessAudit } from "#dynamic/lib/logAccessAudit";
const getExchangeTokenParams = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
export type GetExchangeTokenResponse = {

View File

@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const getResourceWhitelistSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
async function queryWhitelist(resourceId: number) {

View File

@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const listResourceRolesSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
async function query(resourceId: number) {

View File

@@ -11,7 +11,7 @@ import logger from "@server/logger";
import { OpenAPITags, registry } from "@server/openApi";
const listResourceRulesParamsSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
const listResourceRulesSchema = z.object({

View File

@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const listResourceUsersSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
async function queryUsers(resourceId: number) {

View File

@@ -22,7 +22,7 @@ const removeEmailFromResourceWhitelistBodySchema = z.strictObject({
});
const removeEmailFromResourceWhitelistParamsSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
registry.registerPath({

View File

@@ -15,7 +15,7 @@ import { hashPassword } from "@server/auth/password";
import { OpenAPITags, registry } from "@server/openApi";
const setResourceAuthMethodsParamsSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
const setResourceAuthMethodsBodySchema = z.strictObject({

View File

@@ -13,7 +13,7 @@ import { hashPassword } from "@server/auth/password";
import { OpenAPITags, registry } from "@server/openApi";
const setResourceAuthMethodsParamsSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
const setResourceAuthMethodsBodySchema = z.strictObject({

View File

@@ -14,7 +14,7 @@ import { hashPassword } from "@server/auth/password";
import { OpenAPITags, registry } from "@server/openApi";
const setResourceAuthMethodsParamsSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
const setResourceAuthMethodsBodySchema = z.strictObject({

View File

@@ -15,7 +15,7 @@ const setResourceRolesBodySchema = z.strictObject({
});
const setResourceRolesParamsSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
registry.registerPath({

View File

@@ -15,7 +15,7 @@ const setUserResourcesBodySchema = z.strictObject({
});
const setUserResourcesParamsSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
registry.registerPath({

View File

@@ -24,7 +24,7 @@ const setResourceWhitelistBodySchema = z.strictObject({
});
const setResourceWhitelistParamsSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
registry.registerPath({

View File

@@ -31,7 +31,7 @@ import { tierMatrix } from "@server/lib/billing/tierMatrix";
import { isSubscribed } from "#dynamic/lib/isSubscribed";
const updateResourceParamsSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
const updateHttpResourceBodySchema = z

View File

@@ -18,8 +18,8 @@ import { isValidRegionId } from "@server/db/regions";
// Define Zod schema for request parameters validation
const updateResourceRuleParamsSchema = z.strictObject({
ruleId: z.string().transform(Number).pipe(z.int().positive()),
resourceId: z.string().transform(Number).pipe(z.int().positive())
ruleId: z.coerce.number().int().positive(),
resourceId: z.coerce.number().int().positive()
});
// Define Zod schema for request body validation

View File

@@ -10,7 +10,7 @@ import { eq } from "drizzle-orm";
import { fromError } from "zod-validation-error";
const addRoleActionParamSchema = z.strictObject({
roleId: z.string().transform(Number).pipe(z.int().positive())
roleId: z.coerce.number().int().positive()
});
const addRoleActionSchema = z.strictObject({

View File

@@ -10,11 +10,11 @@ import { eq } from "drizzle-orm";
import { fromError } from "zod-validation-error";
const addRoleSiteParamsSchema = z.strictObject({
roleId: z.string().transform(Number).pipe(z.int().positive())
roleId: z.coerce.number().int().positive()
});
const addRoleSiteSchema = z.strictObject({
siteId: z.string().transform(Number).pipe(z.int().positive())
siteId: z.coerce.number().int().positive()
});
export async function addRoleSite(

View File

@@ -11,11 +11,11 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const deleteRoleSchema = z.strictObject({
roleId: z.string().transform(Number).pipe(z.int().positive())
roleId: z.coerce.number().int().positive()
});
const deelteRoleBodySchema = z.strictObject({
roleId: z.string().transform(Number).pipe(z.int().positive())
roleId: z.coerce.number().int().positive()
});
registry.registerPath({

View File

@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const getRoleSchema = z.strictObject({
roleId: z.string().transform(Number).pipe(z.int().positive())
roleId: z.coerce.number().int().positive()
});
registry.registerPath({

View File

@@ -10,7 +10,7 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const listRoleActionsSchema = z.strictObject({
roleId: z.string().transform(Number).pipe(z.int().positive())
roleId: z.coerce.number().int().positive()
});
export async function listRoleActions(

View File

@@ -10,7 +10,7 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const listRoleResourcesSchema = z.strictObject({
roleId: z.string().transform(Number).pipe(z.int().positive())
roleId: z.coerce.number().int().positive()
});
export async function listRoleResources(

View File

@@ -10,7 +10,7 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const listRoleSitesSchema = z.strictObject({
roleId: z.string().transform(Number).pipe(z.int().positive())
roleId: z.coerce.number().int().positive()
});
export async function listRoleSites(

View File

@@ -10,7 +10,7 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const removeRoleActionParamsSchema = z.strictObject({
roleId: z.string().transform(Number).pipe(z.int().positive())
roleId: z.coerce.number().int().positive()
});
const removeRoleActionSchema = z.strictObject({

View File

@@ -10,11 +10,11 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const removeRoleResourceParamsSchema = z.strictObject({
roleId: z.string().transform(Number).pipe(z.int().positive())
roleId: z.coerce.number().int().positive()
});
const removeRoleResourceSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
export async function removeRoleResource(

View File

@@ -10,11 +10,11 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const removeRoleSiteParamsSchema = z.strictObject({
roleId: z.string().transform(Number).pipe(z.int().positive())
roleId: z.coerce.number().int().positive()
});
const removeRoleSiteSchema = z.strictObject({
siteId: z.string().transform(Number).pipe(z.int().positive())
siteId: z.coerce.number().int().positive()
});
export async function removeRoleSite(

View File

@@ -14,7 +14,7 @@ import { OpenAPITags, registry } from "@server/openApi";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
const updateRoleParamsSchema = z.strictObject({
roleId: z.string().transform(Number).pipe(z.int().positive())
roleId: z.coerce.number().int().positive()
});
const sshSudoModeSchema = z.enum(["none", "full", "commands"]);

View File

@@ -16,7 +16,7 @@ import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/billing";
const deleteSiteSchema = z.strictObject({
siteId: z.string().transform(Number).pipe(z.int().positive())
siteId: z.coerce.number().int().positive()
});
registry.registerPath({

View File

@@ -10,7 +10,7 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const listSiteRolesSchema = z.strictObject({
siteId: z.string().transform(Number).pipe(z.int().positive())
siteId: z.coerce.number().int().positive()
});
export async function listSiteRoles(

View File

@@ -44,7 +44,7 @@ export interface Container {
}
const siteIdParamsSchema = z.strictObject({
siteId: z.string().transform(stoi).pipe(z.int().positive())
siteId: z.coerce.number().int().positive()
});
const DockerStatusSchema = z.strictObject({

View File

@@ -12,7 +12,7 @@ import { OpenAPITags, registry } from "@server/openApi";
import { isValidCIDR } from "@server/lib/validators";
const updateSiteParamsSchema = z.strictObject({
siteId: z.string().transform(Number).pipe(z.int().positive())
siteId: z.coerce.number().int().positive()
});
const updateSiteBodySchema = z

View File

@@ -22,7 +22,7 @@ import {
const batchAddClientToSiteResourcesParamsSchema = z
.object({
clientId: z.string().transform(Number).pipe(z.number().int().positive())
clientId: z.coerce.number().int().positive()
})
.strict();

View File

@@ -42,7 +42,12 @@ const createSiteResourceParamsSchema = z.strictObject({
const createSiteResourceSchema = z
.strictObject({
name: z.string().min(1).max(255),
niceId: z.string().optional(),
niceId: z.string().optional()
.openapi({
description:
"Fully qualified domain name with optional wildcards, e.g., example.internal, *.example.internal, or host-0?.example.internal",
example: "service.example.internal"
}),
// protocol: z.enum(["tcp", "udp"]).optional(),
mode: z.enum(["host", "cidr", "http"]),
ssl: z.boolean().optional(), // only used for http mode

View File

@@ -12,7 +12,7 @@ import { OpenAPITags, registry } from "@server/openApi";
import { rebuildClientAssociationsFromSiteResource } from "@server/lib/rebuildClientAssociations";
const deleteSiteResourceParamsSchema = z.strictObject({
siteResourceId: z.string().transform(Number).pipe(z.int().positive())
siteResourceId: z.coerce.number().int().positive()
});
export type DeleteSiteResourceResponse = {

View File

@@ -11,7 +11,7 @@ import logger from "@server/logger";
import { OpenAPITags, registry } from "@server/openApi";
const listSiteResourcesParamsSchema = z.strictObject({
siteId: z.string().transform(Number).pipe(z.int().positive()),
siteId: z.coerce.number().int().positive(),
orgId: z.string()
});

View File

@@ -37,7 +37,7 @@ import { z } from "zod";
import { fromError } from "zod-validation-error";
const updateSiteResourceParamsSchema = z.strictObject({
siteResourceId: z.string().transform(Number).pipe(z.int().positive())
siteResourceId: z.coerce.number().int().positive()
});
const updateSiteResourceSchema = z
@@ -58,7 +58,12 @@ const updateSiteResourceSchema = z
// mode: z.enum(["host", "cidr", "port"]).optional(),
mode: z.enum(["host", "cidr", "http"]).optional(),
ssl: z.boolean().optional(),
scheme: z.enum(["http", "https"]).nullish(),
scheme: z.enum(["http", "https"]).nullish()
.openapi({
description:
"Fully qualified domain name with optional wildcards, e.g., example.internal, *.example.internal, or host-0?.example.internal",
example: "service.example.internal"
}),
// proxyPort: z.int().positive().nullish(),
destinationPort: z.int().positive().nullish(),
destination: z.string().min(1).optional(),

View File

@@ -26,7 +26,7 @@ import {
} from "@server/lib/alerts";
const createTargetParamsSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
const createTargetSchema = z.strictObject({

View File

@@ -13,7 +13,7 @@ import { OpenAPITags, registry } from "@server/openApi";
import { targetHealthCheck } from "@server/db";
const deleteTargetSchema = z.strictObject({
targetId: z.string().transform(Number).pipe(z.int().positive())
targetId: z.coerce.number().int().positive()
});
registry.registerPath({

View File

@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const getTargetSchema = z.strictObject({
targetId: z.string().transform(Number).pipe(z.int().positive())
targetId: z.coerce.number().int().positive()
});
type GetTargetResponse = Target &

View File

@@ -11,7 +11,7 @@ import logger from "@server/logger";
import { OpenAPITags, registry } from "@server/openApi";
const listTargetsParamsSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
const listTargetsSchema = z.object({

View File

@@ -20,7 +20,7 @@ import { isTargetValid } from "@server/lib/validators";
import { OpenAPITags, registry } from "@server/openApi";
const updateTargetParamsSchema = z.strictObject({
targetId: z.string().transform(Number).pipe(z.int().positive())
targetId: z.coerce.number().int().positive()
});
const updateTargetBodySchema = z

View File

@@ -14,7 +14,7 @@ import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAs
/** Legacy path param order: /role/:roleId/add/:userId */
const addUserRoleLegacyParamsSchema = z.strictObject({
roleId: z.string().transform(stoi).pipe(z.number()),
roleId: z.coerce.number(),
userId: z.string()
});

View File

@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
const addUserSiteSchema = z.strictObject({
userId: z.string(),
siteId: z.string().transform(Number).pipe(z.int().positive())
siteId: z.coerce.number().int().positive()
});
export async function addUserSite(

View File

@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
const removeUserResourceSchema = z.strictObject({
userId: z.string(),
resourceId: z.string().transform(Number).pipe(z.int().positive())
resourceId: z.coerce.number().int().positive()
});
export async function removeUserResource(