status page validation
This commit is contained in:
1 parent
8236b02034
commit
16cf23baf1
6 files changed
+70
-81
No files matched your search
@@ -33,8 +33,10 @@ class StatusPageController {
|
||||
|
||||
createStatusPage = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await createStatusPageBodyValidation.validateAsync(req.body);
|
||||
await imageValidation.validateAsync(req.file);
|
||||
createStatusPageBodyValidation.parse(req.body);
|
||||
if (req.file) {
|
||||
imageValidation.parse(req.file);
|
||||
}
|
||||
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
const userId = requireUserId(req?.user?.id);
|
||||
@@ -52,8 +54,10 @@ class StatusPageController {
|
||||
|
||||
updateStatusPage = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await createStatusPageBodyValidation.validateAsync(req.body);
|
||||
await imageValidation.validateAsync(req.file);
|
||||
createStatusPageBodyValidation.parse(req.body);
|
||||
if (req.file) {
|
||||
imageValidation.parse(req.file);
|
||||
}
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
const statusPageId = req.params.id as string;
|
||||
if (!statusPageId) {
|
||||
@@ -75,8 +79,8 @@ class StatusPageController {
|
||||
|
||||
getStatusPageByUrl = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await getStatusPageParamValidation.validateAsync(req.params);
|
||||
await getStatusPageQueryValidation.validateAsync(req.query);
|
||||
getStatusPageParamValidation.parse(req.params);
|
||||
getStatusPageQueryValidation.parse(req.query);
|
||||
|
||||
if (!req.params.url) {
|
||||
throw new AppError({ message: "Status page URL is required", status: 400 });
|
||||
|
||||
@@ -1,23 +1,11 @@
|
||||
import { z } from "zod";
|
||||
import { booleanCoercion } from "./shared.js";
|
||||
import { GeoContinents } from "@/types/geoCheck.js";
|
||||
|
||||
//****************************************
|
||||
// Check Validations
|
||||
//****************************************
|
||||
|
||||
export const ackCheckBodyValidation = z.object({
|
||||
ack: z.coerce.boolean().optional(),
|
||||
});
|
||||
|
||||
export const ackAllChecksParamValidation = z.object({
|
||||
monitorId: z.string().optional(),
|
||||
path: z.enum(["monitor", "team"]),
|
||||
});
|
||||
|
||||
export const ackAllChecksBodyValidation = z.object({
|
||||
ack: z.coerce.boolean().optional(),
|
||||
});
|
||||
|
||||
export const getChecksParamValidation = z.object({
|
||||
monitorId: z.string().min(1, "Monitor ID is required"),
|
||||
});
|
||||
@@ -28,10 +16,10 @@ export const getChecksQueryValidation = z.object({
|
||||
limit: z.coerce.number().optional(),
|
||||
dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]).optional(),
|
||||
filter: z.enum(["all", "up", "down", "resolve"]).optional(),
|
||||
ack: z.coerce.boolean().optional(),
|
||||
ack: booleanCoercion.optional(),
|
||||
page: z.coerce.number().optional(),
|
||||
rowsPerPage: z.coerce.number().optional(),
|
||||
status: z.coerce.boolean().optional(),
|
||||
status: booleanCoercion.optional(),
|
||||
continent: z.union([z.enum(GeoContinents), z.array(z.enum(GeoContinents))]).optional(),
|
||||
});
|
||||
|
||||
@@ -40,7 +28,7 @@ export const getTeamChecksQueryValidation = z.object({
|
||||
limit: z.coerce.number().optional(),
|
||||
dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]).optional(),
|
||||
filter: z.enum(["all", "up", "down", "resolve"]).optional(),
|
||||
ack: z.coerce.boolean().optional(),
|
||||
ack: booleanCoercion.optional(),
|
||||
page: z.coerce.number().optional(),
|
||||
rowsPerPage: z.coerce.number().optional(),
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { booleanCoercion } from "./shared.js";
|
||||
|
||||
//****************************************
|
||||
// Maintenance Window Validations
|
||||
@@ -21,7 +22,7 @@ export const getMaintenanceWindowByIdParamValidation = z.object({
|
||||
});
|
||||
|
||||
export const getMaintenanceWindowsByTeamIdQueryValidation = z.object({
|
||||
active: z.coerce.boolean().optional(),
|
||||
active: booleanCoercion.optional(),
|
||||
page: z.coerce.number().optional(),
|
||||
rowsPerPage: z.coerce.number().optional(),
|
||||
field: z.string().optional(),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { booleanCoercion } from "./shared.js";
|
||||
import { GeoContinents } from "@/types/geoCheck.js";
|
||||
|
||||
export const getMonitorByIdParamValidation = z.object({
|
||||
@@ -6,12 +7,12 @@ export const getMonitorByIdParamValidation = z.object({
|
||||
});
|
||||
|
||||
export const getMonitorByIdQueryValidation = z.object({
|
||||
status: z.coerce.boolean().optional(),
|
||||
status: booleanCoercion.optional(),
|
||||
sortOrder: z.enum(["asc", "desc"]).optional(),
|
||||
limit: z.coerce.number().optional(),
|
||||
dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]).optional(),
|
||||
numToDisplay: z.coerce.number().optional(),
|
||||
normalize: z.coerce.boolean().optional(),
|
||||
normalize: booleanCoercion.optional(),
|
||||
continent: z.enum(GeoContinents).optional(),
|
||||
});
|
||||
|
||||
@@ -40,7 +41,7 @@ export const getMonitorsWithChecksQueryValidation = z.object({
|
||||
z.array(z.enum(["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc"])),
|
||||
])
|
||||
.optional(),
|
||||
explain: z.coerce.boolean().optional(),
|
||||
explain: booleanCoercion.optional(),
|
||||
});
|
||||
|
||||
export const getCertificateParamValidation = z.object({
|
||||
|
||||
@@ -29,6 +29,16 @@ export const lowercaseEmailValidation = z
|
||||
message: "Email must be in lowercase",
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper to properly coerce boolean from strings (handles "true"/"false" strings)
|
||||
* Needed for query parameters and form data where Express sends strings
|
||||
*/
|
||||
export const booleanCoercion = z.preprocess((val) => {
|
||||
if (val === "true" || val === true) return true;
|
||||
if (val === "false" || val === false) return false;
|
||||
return val; // Let Zod validation handle invalid values
|
||||
}, z.boolean());
|
||||
|
||||
/**
|
||||
* Custom validator for role-based authorization
|
||||
*/
|
||||
|
||||
@@ -1,68 +1,53 @@
|
||||
import joi from "joi";
|
||||
import { z } from "zod";
|
||||
import { booleanCoercion } from "./shared.js";
|
||||
|
||||
//****************************************
|
||||
// Status Page Validations
|
||||
//****************************************
|
||||
|
||||
export const getStatusPageParamValidation = joi.object({
|
||||
url: joi.string().required(),
|
||||
export const getStatusPageParamValidation = z.object({
|
||||
url: z.string().min(1, "URL is required"),
|
||||
});
|
||||
|
||||
export const getStatusPageQueryValidation = joi.object({
|
||||
type: joi.string().valid("uptime").required(),
|
||||
timeFrame: joi.number().optional(),
|
||||
export const getStatusPageQueryValidation = z.object({
|
||||
type: z.literal("uptime"),
|
||||
timeFrame: z.coerce.number().optional(),
|
||||
});
|
||||
|
||||
export const createStatusPageBodyValidation = joi.object({
|
||||
type: joi.string().valid("uptime").required(),
|
||||
companyName: joi.string().required(),
|
||||
url: joi
|
||||
.string()
|
||||
.pattern(/^[a-zA-Z0-9_-]+$/)
|
||||
.required()
|
||||
.messages({
|
||||
"string.pattern.base": "URL can only contain letters, numbers, underscores, and hyphens",
|
||||
}),
|
||||
timezone: joi.string().optional(),
|
||||
color: joi.string().optional(),
|
||||
monitors: joi
|
||||
.array()
|
||||
.items(joi.string().pattern(/^[0-9a-fA-F]{24}$/))
|
||||
.required()
|
||||
.messages({
|
||||
"string.pattern.base": "Must be a valid monitor ID",
|
||||
"array.base": "Monitors must be an array",
|
||||
"array.empty": "At least one monitor is required",
|
||||
"any.required": "Monitors are required",
|
||||
}),
|
||||
subMonitors: joi
|
||||
.array()
|
||||
.items(joi.string().pattern(/^[0-9a-fA-F]{24}$/))
|
||||
.optional(),
|
||||
deleteSubmonitors: joi.boolean().optional(),
|
||||
isPublished: joi.boolean(),
|
||||
showCharts: joi.boolean().optional(),
|
||||
showUptimePercentage: joi.boolean(),
|
||||
showAdminLoginLink: joi.boolean().optional(),
|
||||
removeLogo: joi.string().valid("true", "false").optional(),
|
||||
});
|
||||
|
||||
export const imageValidation = joi
|
||||
export const createStatusPageBodyValidation = z
|
||||
.object({
|
||||
fieldname: joi.string().required(),
|
||||
originalname: joi.string().required(),
|
||||
encoding: joi.string().required(),
|
||||
mimetype: joi.string().valid("image/jpeg", "image/png", "image/jpg").required().messages({
|
||||
"string.valid": "File must be a valid image (jpeg, jpg, or png)",
|
||||
type: z.literal("uptime"),
|
||||
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",
|
||||
}),
|
||||
size: joi.number().max(3145728).required().messages({
|
||||
"number.max": "File size must be less than 3MB",
|
||||
}),
|
||||
buffer: joi.binary().required(),
|
||||
destination: joi.string(),
|
||||
filename: joi.string(),
|
||||
path: joi.string(),
|
||||
timezone: z.string().optional(),
|
||||
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(),
|
||||
removeLogo: z.union([z.literal("true"), z.literal("false")]).optional(),
|
||||
})
|
||||
.messages({
|
||||
"any.required": "Image file is required",
|
||||
.strip();
|
||||
|
||||
export const imageValidation = z
|
||||
.object({
|
||||
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"),
|
||||
mimetype: z.enum(["image/jpeg", "image/png", "image/jpg"], {
|
||||
message: "File must be a valid image (jpeg, jpg, or png)",
|
||||
}),
|
||||
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(),
|
||||
})
|
||||
.refine((data) => data.buffer, {
|
||||
message: "Image file is required",
|
||||
});
|
||||
Reference in new issue
Block a user