Files
Checkmate/server/src/api/validation/statusPageValidation.ts
T

82 lines
3.3 KiB
TypeScript
Raw Normal View History

2026-03-02 19:25:34 +00:00
import { z } from "zod";
2026-08-17 11:48:56 -07:00
import { booleanCoercion, dnsHostnameRegex, timezoneValidation } from "./shared.js";
2026-08-17 11:48:44 -07:00
import { StatusPageTypes, StatusPageThemes, StatusPageThemeModes, StatusPageRanges } from "@/domain/status-pages/status-page.type.js";
import { normalizeStatusPageDomain } from "@/utils/statusPageDomain.js";
import { cssReferencesExternalResource } from "@/utils/customCss.js";
2026-07-21 10:48:18 -07:00
import { ImageMimeTypes } from "@/types/upload.js";
2026-03-01 17:35:00 +00:00
//****************************************
// Status Page Validations
//****************************************
2026-03-02 19:25:34 +00:00
export const getStatusPageParamValidation = z.object({
url: z.string().min(1, "URL is required"),
2026-03-01 17:35:00 +00:00
});
2026-03-02 19:25:34 +00:00
export const getStatusPageQueryValidation = z.object({
2026-03-18 16:25:07 -04:00
type: z.union([z.enum(StatusPageTypes), z.array(z.enum(StatusPageTypes))]).transform((val) => (Array.isArray(val) ? val : [val])),
2026-08-17 11:48:44 -07:00
range: z.enum(StatusPageRanges).optional().default("latest"),
2026-03-01 17:35:00 +00:00
});
export const resolveStatusPageQueryValidation = getStatusPageQueryValidation.extend({
domain: z.string().optional(),
});
const customDomainValidation = z.preprocess(
(val) => {
if (val === undefined || val === null) {
return undefined;
}
return normalizeStatusPageDomain(String(val));
},
z.union([z.string().regex(dnsHostnameRegex, "Enter a valid domain name (e.g. status.example.com)"), z.null()]).optional()
);
2026-03-02 19:25:34 +00:00
export const createStatusPageBodyValidation = z
.object({
2026-03-18 16:25:07 -04:00
type: z.union([z.enum(StatusPageTypes), z.array(z.enum(StatusPageTypes))]).transform((val) => (Array.isArray(val) ? val : [val])),
2026-03-02 19:25:34 +00:00
companyName: z.string().min(1, "Company name is required"),
url: z.string().regex(/^[a-zA-Z0-9_-]+$/, {
message: "URL can only contain letters, numbers, underscores, and hyphens",
2026-03-01 17:35:00 +00:00
}),
customDomain: customDomainValidation,
2026-08-17 11:48:56 -07:00
timezone: timezoneValidation.optional(),
2026-03-02 19:25:34 +00:00
color: z.string().optional(),
monitors: z.array(z.string().regex(/^[0-9a-fA-F]{24}$/, "Must be a valid monitor ID")).min(1, "At least one monitor is required"),
subMonitors: z.array(z.string().regex(/^[0-9a-fA-F]{24}$/)).optional(),
deleteSubmonitors: z.boolean().optional(),
isPublished: booleanCoercion,
showCharts: booleanCoercion.optional(),
showUptimePercentage: booleanCoercion,
showAdminLoginLink: booleanCoercion.optional(),
showInfrastructure: booleanCoercion.optional(),
customCSS: z
.string()
.max(100000, "Custom CSS must be at most 100000 characters")
.refine((css) => !cssReferencesExternalResource(css), {
message: "Custom CSS cannot reference external URLs or use @import",
})
.optional(),
2026-03-02 19:25:34 +00:00
removeLogo: z.union([z.literal("true"), z.literal("false")]).optional(),
theme: z.enum(StatusPageThemes).optional(),
themeMode: z.enum(StatusPageThemeModes).optional(),
2026-03-02 19:25:34 +00:00
})
.strip();
2026-03-01 17:35:00 +00:00
2026-03-02 19:25:34 +00:00
export const imageValidation = z
2026-03-01 17:35:00 +00:00
.object({
2026-03-02 19:25:34 +00:00
fieldname: z.string().min(1, "Field name is required"),
originalname: z.string().min(1, "Original name is required"),
encoding: z.string().min(1, "Encoding is required"),
2026-07-21 10:48:18 -07:00
mimetype: z.enum(ImageMimeTypes, {
2026-03-02 19:25:34 +00:00
message: "File must be a valid image (jpeg, jpg, or png)",
2026-03-01 17:35:00 +00:00
}),
2026-03-02 19:25:34 +00:00
size: z.number().max(3145728, "File size must be less than 3MB"),
buffer: z.instanceof(Buffer, { message: "Buffer is required" }),
destination: z.string().optional(),
filename: z.string().optional(),
path: z.string().optional(),
2026-03-01 17:35:00 +00:00
})
2026-03-02 19:25:34 +00:00
.refine((data) => data.buffer, {
message: "Image file is required",
2026-03-01 17:35:00 +00:00
});