mirror of
https://github.com/fosrl/pangolin.git
synced 2026-04-29 09:12:56 +00:00
251 lines
8.6 KiB
TypeScript
251 lines
8.6 KiB
TypeScript
/*
|
|
* 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 { Request, Response, NextFunction } from "express";
|
|
import { z } from "zod";
|
|
import { db, targetHealthCheck, newts, sites } from "@server/db";
|
|
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 { and, eq, isNull } from "drizzle-orm";
|
|
import { addStandaloneHealthCheck } from "@server/routers/newt/targets";
|
|
|
|
const paramsSchema = z
|
|
.object({
|
|
orgId: z.string().nonempty(),
|
|
healthCheckId: z
|
|
.string()
|
|
.transform(Number)
|
|
.pipe(z.number().int().positive())
|
|
})
|
|
.strict();
|
|
|
|
const bodySchema = z.strictObject({
|
|
name: z.string().nonempty().optional(),
|
|
siteId: z.number().int().positive().optional(),
|
|
hcEnabled: z.boolean().optional(),
|
|
hcMode: z.string().optional(),
|
|
hcHostname: z.string().optional(),
|
|
hcPort: z.number().int().min(1).max(65535).optional(),
|
|
hcPath: z.string().optional(),
|
|
hcScheme: z.string().optional(),
|
|
hcMethod: z.string().optional(),
|
|
hcInterval: z.number().int().positive().optional(),
|
|
hcUnhealthyInterval: z.number().int().positive().optional(),
|
|
hcTimeout: z.number().int().positive().optional(),
|
|
hcHeaders: z.string().optional().nullable(),
|
|
hcFollowRedirects: z.boolean().optional(),
|
|
hcStatus: z.number().int().optional().nullable(),
|
|
hcTlsServerName: z.string().optional(),
|
|
hcHealthyThreshold: z.number().int().positive().optional(),
|
|
hcUnhealthyThreshold: z.number().int().positive().optional()
|
|
});
|
|
|
|
export type UpdateHealthCheckResponse = {
|
|
targetHealthCheckId: number;
|
|
name: string | null;
|
|
siteId: number | null;
|
|
hcEnabled: boolean;
|
|
hcHealth: string | null;
|
|
hcMode: string | null;
|
|
hcHostname: string | null;
|
|
hcPort: number | null;
|
|
hcPath: string | null;
|
|
hcScheme: string | null;
|
|
hcMethod: string | null;
|
|
hcInterval: number | null;
|
|
hcUnhealthyInterval: number | null;
|
|
hcTimeout: number | null;
|
|
hcHeaders: string | null;
|
|
hcFollowRedirects: boolean | null;
|
|
hcStatus: number | null;
|
|
hcTlsServerName: string | null;
|
|
hcHealthyThreshold: number | null;
|
|
hcUnhealthyThreshold: number | null;
|
|
};
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/org/{orgId}/health-check/{healthCheckId}",
|
|
description: "Update a health check for a specific organization.",
|
|
tags: [OpenAPITags.Org],
|
|
request: {
|
|
params: paramsSchema,
|
|
body: {
|
|
content: {
|
|
"application/json": {
|
|
schema: bodySchema
|
|
}
|
|
}
|
|
}
|
|
},
|
|
responses: {}
|
|
});
|
|
|
|
export async function updateHealthCheck(
|
|
req: Request,
|
|
res: Response,
|
|
next: NextFunction
|
|
): Promise<any> {
|
|
try {
|
|
const parsedParams = paramsSchema.safeParse(req.params);
|
|
if (!parsedParams.success) {
|
|
return next(
|
|
createHttpError(
|
|
HttpCode.BAD_REQUEST,
|
|
fromError(parsedParams.error).toString()
|
|
)
|
|
);
|
|
}
|
|
|
|
const { orgId, healthCheckId } = parsedParams.data;
|
|
|
|
const parsedBody = bodySchema.safeParse(req.body);
|
|
if (!parsedBody.success) {
|
|
return next(
|
|
createHttpError(
|
|
HttpCode.BAD_REQUEST,
|
|
fromError(parsedBody.error).toString()
|
|
)
|
|
);
|
|
}
|
|
|
|
const [existing] = await db
|
|
.select()
|
|
.from(targetHealthCheck)
|
|
.where(
|
|
and(
|
|
eq(targetHealthCheck.targetHealthCheckId, healthCheckId),
|
|
eq(targetHealthCheck.orgId, orgId),
|
|
isNull(targetHealthCheck.targetId)
|
|
)
|
|
);
|
|
|
|
if (!existing) {
|
|
return next(
|
|
createHttpError(
|
|
HttpCode.NOT_FOUND,
|
|
"Standalone health check not found"
|
|
)
|
|
);
|
|
}
|
|
|
|
const {
|
|
name,
|
|
siteId,
|
|
hcEnabled,
|
|
hcMode,
|
|
hcHostname,
|
|
hcPort,
|
|
hcPath,
|
|
hcScheme,
|
|
hcMethod,
|
|
hcInterval,
|
|
hcUnhealthyInterval,
|
|
hcTimeout,
|
|
hcHeaders,
|
|
hcFollowRedirects,
|
|
hcStatus,
|
|
hcTlsServerName,
|
|
hcHealthyThreshold,
|
|
hcUnhealthyThreshold
|
|
} = parsedBody.data;
|
|
|
|
const updateData: Record<string, unknown> = {};
|
|
|
|
if (name !== undefined) updateData.name = name;
|
|
if (siteId !== undefined) updateData.siteId = siteId;
|
|
if (hcEnabled !== undefined) updateData.hcEnabled = hcEnabled;
|
|
if (hcMode !== undefined) updateData.hcMode = hcMode;
|
|
if (hcHostname !== undefined) updateData.hcHostname = hcHostname;
|
|
if (hcPort !== undefined) updateData.hcPort = hcPort;
|
|
if (hcPath !== undefined) updateData.hcPath = hcPath;
|
|
if (hcScheme !== undefined) updateData.hcScheme = hcScheme;
|
|
if (hcMethod !== undefined) updateData.hcMethod = hcMethod;
|
|
if (hcInterval !== undefined) updateData.hcInterval = hcInterval;
|
|
if (hcUnhealthyInterval !== undefined)
|
|
updateData.hcUnhealthyInterval = hcUnhealthyInterval;
|
|
if (hcTimeout !== undefined) updateData.hcTimeout = hcTimeout;
|
|
if (hcHeaders !== undefined) updateData.hcHeaders = hcHeaders;
|
|
if (hcFollowRedirects !== undefined)
|
|
updateData.hcFollowRedirects = hcFollowRedirects;
|
|
if (hcStatus !== undefined) updateData.hcStatus = hcStatus;
|
|
if (hcTlsServerName !== undefined)
|
|
updateData.hcTlsServerName = hcTlsServerName;
|
|
if (hcHealthyThreshold !== undefined)
|
|
updateData.hcHealthyThreshold = hcHealthyThreshold;
|
|
if (hcUnhealthyThreshold !== undefined)
|
|
updateData.hcUnhealthyThreshold = hcUnhealthyThreshold;
|
|
|
|
const [updated] = await db
|
|
.update(targetHealthCheck)
|
|
.set(updateData)
|
|
.where(
|
|
and(
|
|
eq(targetHealthCheck.targetHealthCheckId, healthCheckId),
|
|
eq(targetHealthCheck.orgId, orgId),
|
|
isNull(targetHealthCheck.targetId)
|
|
)
|
|
)
|
|
.returning();
|
|
|
|
// Push updated health check to newt if the site is a newt site
|
|
const [newt] = await db
|
|
.select()
|
|
.from(newts)
|
|
.where(eq(newts.siteId, updated.siteId))
|
|
.limit(1);
|
|
|
|
if (newt) {
|
|
await addStandaloneHealthCheck(newt.newtId, updated, newt.version);
|
|
}
|
|
|
|
return response<UpdateHealthCheckResponse>(res, {
|
|
data: {
|
|
targetHealthCheckId: updated.targetHealthCheckId,
|
|
siteId: updated.siteId ?? null,
|
|
name: updated.name ?? null,
|
|
hcEnabled: updated.hcEnabled,
|
|
hcHealth: updated.hcHealth ?? null,
|
|
hcMode: updated.hcMode ?? null,
|
|
hcHostname: updated.hcHostname ?? null,
|
|
hcPort: updated.hcPort ?? null,
|
|
hcPath: updated.hcPath ?? null,
|
|
hcScheme: updated.hcScheme ?? null,
|
|
hcMethod: updated.hcMethod ?? null,
|
|
hcInterval: updated.hcInterval ?? null,
|
|
hcUnhealthyInterval: updated.hcUnhealthyInterval ?? null,
|
|
hcTimeout: updated.hcTimeout ?? null,
|
|
hcHeaders: updated.hcHeaders ?? null,
|
|
hcFollowRedirects: updated.hcFollowRedirects ?? null,
|
|
hcStatus: updated.hcStatus ?? null,
|
|
hcTlsServerName: updated.hcTlsServerName ?? null,
|
|
hcHealthyThreshold: updated.hcHealthyThreshold ?? null,
|
|
hcUnhealthyThreshold: updated.hcUnhealthyThreshold ?? null
|
|
},
|
|
success: true,
|
|
error: false,
|
|
message: "Standalone health check updated successfully",
|
|
status: HttpCode.OK
|
|
});
|
|
} catch (error) {
|
|
logger.error(error);
|
|
return next(
|
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
|
);
|
|
}
|
|
}
|