mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-14 21:48:39 -05:00
validation refactoring
This commit is contained in:
Generated
+25
-1
@@ -43,7 +43,8 @@
|
||||
"ssl-checker": "2.0.10",
|
||||
"super-simple-scheduler": "1.4.5",
|
||||
"swagger-ui-express": "5.0.1",
|
||||
"winston": "^3.13.0"
|
||||
"winston": "^3.13.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
@@ -876,6 +877,7 @@
|
||||
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.28.6",
|
||||
"@babel/generator": "^7.28.6",
|
||||
@@ -1505,6 +1507,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -1549,6 +1552,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -4376,6 +4380,7 @@
|
||||
"integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/body-parser": "*",
|
||||
"@types/express-serve-static-core": "^5.0.0",
|
||||
@@ -4536,6 +4541,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz",
|
||||
"integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
@@ -5022,6 +5028,7 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -5600,6 +5607,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.19",
|
||||
"caniuse-lite": "^1.0.30001751",
|
||||
@@ -6542,6 +6550,7 @@
|
||||
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.1.tgz",
|
||||
"integrity": "sha512-fm4D8ti0dQmFPeF8DXSAA//btEmqCOgAc/9Oa3C1LW94h5usNrJEfrON7b4FkPZgnDEn6OUs5NdxiJZmAtGOpQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cssnano-preset-default": "^7.0.9",
|
||||
"lilconfig": "^3.1.3"
|
||||
@@ -7314,6 +7323,7 @@
|
||||
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -7675,6 +7685,7 @@
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
|
||||
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
@@ -9323,6 +9334,7 @@
|
||||
"integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/core": "30.2.0",
|
||||
"@jest/types": "30.2.0",
|
||||
@@ -12212,6 +12224,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -14436,6 +14449,7 @@
|
||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-support": "^0.8.0",
|
||||
"@tsconfig/node10": "^1.0.7",
|
||||
@@ -14598,6 +14612,7 @@
|
||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -15402,6 +15417,15 @@
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -58,7 +58,8 @@
|
||||
"ssl-checker": "2.0.10",
|
||||
"super-simple-scheduler": "1.4.5",
|
||||
"swagger-ui-express": "5.0.1",
|
||||
"winston": "^3.13.0"
|
||||
"winston": "^3.13.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
|
||||
@@ -6,17 +6,20 @@ import type { UserRole } from "@/types/user.js";
|
||||
import {
|
||||
registrationBodyValidation,
|
||||
loginValidation,
|
||||
editUserBodyValidation,
|
||||
createUserBodyValidation,
|
||||
recoveryValidation,
|
||||
recoveryTokenBodyValidation,
|
||||
newPasswordValidation,
|
||||
} from "@/validation/authValidation.js";
|
||||
|
||||
import {
|
||||
editUserBodyValidation,
|
||||
createUserBodyValidation,
|
||||
getUserByIdParamValidation,
|
||||
editUserByIdParamValidation,
|
||||
editUserByIdBodyValidation,
|
||||
editSuperadminUserByIdBodyValidation,
|
||||
editUserPasswordByIdBodyValidation,
|
||||
} from "@/validation/joi.js";
|
||||
} from "@/validation/userValidation.js";
|
||||
|
||||
const SERVICE_NAME = "authController";
|
||||
|
||||
@@ -40,7 +43,7 @@ class AuthController {
|
||||
if (newUser?.email) {
|
||||
newUser.email = newUser.email.toLowerCase();
|
||||
}
|
||||
await registrationBodyValidation.validateAsync(newUser);
|
||||
registrationBodyValidation.parse(newUser);
|
||||
|
||||
const { user, token } = await this.userService.registerUser(newUser, newUserToken, req.file);
|
||||
res.status(200).json({
|
||||
@@ -59,7 +62,7 @@ class AuthController {
|
||||
if (userData?.email) {
|
||||
userData.email = userData.email.toLowerCase();
|
||||
}
|
||||
await createUserBodyValidation.validateAsync(userData);
|
||||
createUserBodyValidation.parse(userData);
|
||||
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const actorRoles = requireUserRoles(req.user?.role) as UserRole[];
|
||||
@@ -79,7 +82,7 @@ class AuthController {
|
||||
if (req.body?.email) {
|
||||
req.body.email = req.body.email?.toLowerCase();
|
||||
}
|
||||
await loginValidation.validateAsync(req.body);
|
||||
loginValidation.parse(req.body);
|
||||
const { user, token } = await this.userService.loginUser(req.body.email, req.body.password);
|
||||
|
||||
return res.status(200).json({
|
||||
@@ -97,7 +100,7 @@ class AuthController {
|
||||
|
||||
editUser = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await editUserBodyValidation.validateAsync(req.body);
|
||||
editUserBodyValidation.parse(req.body);
|
||||
const updatedUser = await this.userService.editUser(req.body, req.file, req.user);
|
||||
|
||||
res.status(200).json({
|
||||
@@ -125,7 +128,7 @@ class AuthController {
|
||||
|
||||
requestRecovery = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await recoveryValidation.validateAsync(req.body);
|
||||
recoveryValidation.parse(req.body);
|
||||
const email = req?.body?.email;
|
||||
const msgId = await this.userService.requestRecovery(email);
|
||||
return res.status(200).json({
|
||||
@@ -140,7 +143,7 @@ class AuthController {
|
||||
|
||||
validateRecovery = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await recoveryTokenBodyValidation.validateAsync(req.body);
|
||||
recoveryTokenBodyValidation.parse(req.body);
|
||||
await this.userService.validateRecovery(req.body.recoveryToken);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
@@ -153,7 +156,7 @@ class AuthController {
|
||||
|
||||
resetPassword = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await newPasswordValidation.validateAsync(req.body);
|
||||
newPasswordValidation.parse(req.body);
|
||||
const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
@@ -192,7 +195,7 @@ class AuthController {
|
||||
|
||||
getUserById = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await getUserByIdParamValidation.validateAsync(req.params);
|
||||
getUserByIdParamValidation.parse(req.params);
|
||||
const userId = req?.params?.userId;
|
||||
const roles = req?.user?.role;
|
||||
|
||||
@@ -223,12 +226,12 @@ class AuthController {
|
||||
const userId = req.params.userId as string;
|
||||
const user = { ...req.body };
|
||||
|
||||
await editUserByIdParamValidation.validateAsync(req.params);
|
||||
editUserByIdParamValidation.parse(req.params);
|
||||
// If this is superadmin self edit, allow "superadmin" role
|
||||
if (userId === req.user?.id) {
|
||||
await editSuperadminUserByIdBodyValidation.validateAsync(req.body);
|
||||
editSuperadminUserByIdBodyValidation.parse(req.body);
|
||||
} else {
|
||||
await editUserByIdBodyValidation.validateAsync(req.body);
|
||||
editUserByIdBodyValidation.parse(req.body);
|
||||
}
|
||||
|
||||
await this.userService.editUserById(userId, user);
|
||||
@@ -246,8 +249,8 @@ class AuthController {
|
||||
}
|
||||
|
||||
const userId = req.params.userId as string;
|
||||
await editUserByIdParamValidation.validateAsync(req.params);
|
||||
await editUserPasswordByIdBodyValidation.validateAsync(req.body);
|
||||
editUserByIdParamValidation.parse(req.params);
|
||||
editUserPasswordByIdBodyValidation.parse(req.body);
|
||||
const updatedPassword = req.body.password;
|
||||
await this.userService.setPasswordByUserId(userId, updatedPassword);
|
||||
return res.status(200).json({ success: true, msg: "Password reset successfully" });
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
ackCheckBodyValidation,
|
||||
ackAllChecksParamValidation,
|
||||
ackAllChecksBodyValidation,
|
||||
} from "@/validation/joi.js";
|
||||
} from "@/validation/checkValidation.js";
|
||||
|
||||
const SERVICE_NAME = "checkController";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { getChecksParamValidation, getChecksQueryValidation } from "@/validation/joi.js";
|
||||
import { getChecksParamValidation, getChecksQueryValidation } from "@/validation/checkValidation.js";
|
||||
import type { IGeoChecksService } from "@/service/business/geoChecksService.js";
|
||||
|
||||
const SERVICE_NAME = "geoCheckController";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { inviteBodyValidation, inviteVerificationBodyValidation } from "@/validation/joi.js";
|
||||
import { inviteBodyValidation, inviteVerificationBodyValidation } from "@/validation/authValidation.js";
|
||||
import { requireTeamId, requireUserRoles } from "@/controllers/controllerUtils.js";
|
||||
const SERVICE_NAME = "inviteController";
|
||||
|
||||
@@ -20,7 +20,7 @@ class InviteController {
|
||||
const userRoles = requireUserRoles(req.user?.role);
|
||||
const invite = req.body;
|
||||
invite.teamId = teamId;
|
||||
await inviteBodyValidation.validateAsync(invite);
|
||||
inviteBodyValidation.parse(invite);
|
||||
const inviteToken = await this.inviteService.getInviteToken({ invite, teamId, userRoles });
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
@@ -39,7 +39,7 @@ class InviteController {
|
||||
|
||||
const inviteRequest = req.body;
|
||||
inviteRequest.teamId = teamId;
|
||||
await inviteBodyValidation.validateAsync(inviteRequest);
|
||||
inviteBodyValidation.parse(inviteRequest);
|
||||
|
||||
const inviteToken = await this.inviteService.sendInviteEmail({
|
||||
invite: inviteRequest,
|
||||
@@ -58,7 +58,7 @@ class InviteController {
|
||||
|
||||
verifyInviteToken = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await inviteVerificationBodyValidation.validateAsync(req.body);
|
||||
inviteVerificationBodyValidation.parse(req.body);
|
||||
const invite = await this.inviteService.verifyInviteToken({ inviteToken: req?.body?.token });
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
getMaintenanceWindowsByMonitorIdParamValidation,
|
||||
getMaintenanceWindowsByTeamIdQueryValidation,
|
||||
deleteMaintenanceWindowByIdParamValidation,
|
||||
} from "@/validation/joi.js";
|
||||
} from "@/validation/maintenanceWindowValidation.js";
|
||||
import { requireTeamId } from "@/controllers/controllerUtils.js";
|
||||
|
||||
const SERVICE_NAME = "maintenanceWindowController";
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
getCertificateParamValidation,
|
||||
getHardwareDetailsByIdParamValidation,
|
||||
getHardwareDetailsByIdQueryValidation,
|
||||
} from "@/validation/joi.js";
|
||||
} from "@/validation/monitorValidation.js";
|
||||
import sslChecker from "ssl-checker";
|
||||
import {
|
||||
fetchMonitorCertificate,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
|
||||
import { Notification } from "@/types/index.js";
|
||||
import { createNotificationBodyValidation } from "@/validation/joi.js";
|
||||
import { createNotificationBodyValidation } from "@/validation/notificationValidation.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { IMonitorsRepository } from "@/repositories/index.js";
|
||||
import { INotificationsService } from "@/service/index.js";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { sendTestEmailBodyValidation, updateAppSettingsBodyValidation } from "@/validation/joi.js";
|
||||
import { updateAppSettingsBodyValidation } from "@/validation/settingsValidation.js";
|
||||
import { sendTestEmailBodyValidation } from "@/validation/announcementValidation.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
|
||||
const SERVICE_NAME = "SettingsController";
|
||||
@@ -68,7 +69,7 @@ class SettingsController {
|
||||
|
||||
sendTestEmail = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await sendTestEmailBodyValidation.validateAsync(req.body);
|
||||
sendTestEmailBodyValidation.parse(req.body);
|
||||
|
||||
const {
|
||||
to,
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
|
||||
import { createStatusPageBodyValidation, getStatusPageParamValidation, getStatusPageQueryValidation, imageValidation } from "@/validation/joi.js";
|
||||
import {
|
||||
createStatusPageBodyValidation,
|
||||
getStatusPageParamValidation,
|
||||
getStatusPageQueryValidation,
|
||||
imageValidation,
|
||||
} from "@/validation/statusPageValidation.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { requireTeamId, requireUserId } from "@/controllers/controllerUtils.js";
|
||||
import { IStatusPageService } from "@/service/business/statusPageService.js";
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { z } from "zod";
|
||||
|
||||
//****************************************
|
||||
// Announcement Validations
|
||||
//****************************************
|
||||
|
||||
export const sendTestEmailBodyValidation = z.object({
|
||||
to: z.string().min(1, "To field is required"),
|
||||
systemEmailHost: z.string().optional(),
|
||||
systemEmailPort: z.number().optional(),
|
||||
systemEmailSecure: z.boolean().optional(),
|
||||
systemEmailPool: z.boolean().optional(),
|
||||
systemEmailAddress: z.string().optional(),
|
||||
systemEmailPassword: z.string().optional(),
|
||||
systemEmailUser: z.string().optional(),
|
||||
systemEmailConnectionHost: z.union([z.string(), z.literal("")]).optional(),
|
||||
systemEmailIgnoreTLS: z.boolean().optional(),
|
||||
systemEmailRequireTLS: z.boolean().optional(),
|
||||
systemEmailRejectUnauthorized: z.boolean().optional(),
|
||||
systemEmailTLSServername: z.union([z.string(), z.literal("")]).optional(),
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import { z } from "zod";
|
||||
import { passwordPattern, nameValidation, lowercaseEmailValidation } from "./shared.js";
|
||||
|
||||
//****************************************
|
||||
// Auth Validations
|
||||
//****************************************
|
||||
|
||||
export const loginValidation = z.object({
|
||||
email: z.email("Must be a valid email address").transform((val) => val.toLowerCase()),
|
||||
password: z.string().min(1, "Password is required"),
|
||||
});
|
||||
|
||||
export const registrationBodyValidation = z.object({
|
||||
firstName: nameValidation,
|
||||
lastName: nameValidation,
|
||||
email: lowercaseEmailValidation,
|
||||
password: z
|
||||
.string()
|
||||
.min(8, "Password must be at least 8 characters")
|
||||
.regex(passwordPattern, "Password must contain at least one lowercase letter, one uppercase letter, one number, and one special character"),
|
||||
profileImage: z.any().optional(),
|
||||
inviteToken: z.string().optional().default(""),
|
||||
});
|
||||
|
||||
export const recoveryValidation = z.object({
|
||||
email: z.email("Must be a valid email address"),
|
||||
});
|
||||
|
||||
export const recoveryTokenBodyValidation = z.object({
|
||||
recoveryToken: z.string().min(1, "Recovery token is required"),
|
||||
});
|
||||
|
||||
export const newPasswordValidation = z.object({
|
||||
recoveryToken: z.string().min(1, "Recovery token is required"),
|
||||
password: z
|
||||
.string()
|
||||
.min(8, "Password must be at least 8 characters")
|
||||
.regex(passwordPattern, "Password must contain at least one lowercase letter, one uppercase letter, one number, and one special character"),
|
||||
confirm: z.string().optional(),
|
||||
});
|
||||
|
||||
export const inviteBodyValidation = z.object({
|
||||
email: z.email("Must be a valid email address"),
|
||||
role: z.array(z.string()).min(1, "At least one role is required"),
|
||||
teamId: z.string().min(1, "Team ID is required"),
|
||||
});
|
||||
|
||||
export const inviteVerificationBodyValidation = z.object({
|
||||
token: z.string().min(1, "Token is required"),
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import joi from "joi";
|
||||
import { GeoContinents } from "@/types/geoCheck.js";
|
||||
|
||||
//****************************************
|
||||
// Check Validations
|
||||
//****************************************
|
||||
|
||||
export const ackCheckBodyValidation = joi.object({
|
||||
ack: joi.boolean(),
|
||||
});
|
||||
|
||||
export const ackAllChecksParamValidation = joi.object({
|
||||
monitorId: joi.string().optional(),
|
||||
path: joi.string().valid("monitor", "team").required(),
|
||||
});
|
||||
|
||||
export const ackAllChecksBodyValidation = joi.object({
|
||||
ack: joi.boolean(),
|
||||
});
|
||||
|
||||
export const getChecksParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
export const getChecksQueryValidation = joi.object({
|
||||
type: joi.string().valid("http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc"),
|
||||
sortOrder: joi.string().valid("asc", "desc"),
|
||||
limit: joi.number(),
|
||||
dateRange: joi.string().valid("recent", "hour", "day", "week", "month", "all"),
|
||||
filter: joi.string().valid("all", "up", "down", "resolve"),
|
||||
ack: joi.boolean(),
|
||||
page: joi.number(),
|
||||
rowsPerPage: joi.number(),
|
||||
status: joi.boolean(),
|
||||
continent: joi.alternatives().try(joi.string().valid(...GeoContinents), joi.array().items(joi.string().valid(...GeoContinents))),
|
||||
});
|
||||
|
||||
export const getTeamChecksQueryValidation = joi.object({
|
||||
sortOrder: joi.string().valid("asc", "desc"),
|
||||
limit: joi.number(),
|
||||
dateRange: joi.string().valid("recent", "hour", "day", "week", "month", "all"),
|
||||
filter: joi.string().valid("all", "up", "down", "resolve"),
|
||||
ack: joi.boolean(),
|
||||
page: joi.number(),
|
||||
rowsPerPage: joi.number(),
|
||||
});
|
||||
|
||||
export const deleteChecksParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
export const deleteChecksByTeamIdParamValidation = joi.object({});
|
||||
|
||||
export const updateChecksTTLBodyValidation = joi.object({
|
||||
ttl: joi.number().required(),
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Central validation exports
|
||||
*
|
||||
* This file re-exports all validation schemas from their respective modules.
|
||||
* Import from here for convenience: import { loginValidation } from "@/validation";
|
||||
*/
|
||||
|
||||
// Shared utilities
|
||||
export * from "./shared.js";
|
||||
|
||||
// Domain-specific validations
|
||||
export * from "./authValidation.js";
|
||||
export * from "./monitorValidation.js";
|
||||
export * from "./checkValidation.js";
|
||||
export * from "./maintenanceWindowValidation.js";
|
||||
export * from "./settingsValidation.js";
|
||||
export * from "./statusPageValidation.js";
|
||||
export * from "./notificationValidation.js";
|
||||
export * from "./announcementValidation.js";
|
||||
export * from "./userValidation.js";
|
||||
@@ -1,822 +0,0 @@
|
||||
import joi, { type CustomHelpers } from "joi";
|
||||
import { type UserRole, UserRoles } from "@/types/user.js";
|
||||
import { GeoContinents } from "@/types/geoCheck.js";
|
||||
|
||||
//****************************************
|
||||
// Custom Validators
|
||||
//****************************************
|
||||
|
||||
const roleValidatior = (role: UserRole[]) => (value: UserRole[], helpers: CustomHelpers) => {
|
||||
const hasRole = role.some((role: UserRole) => value.includes(role));
|
||||
if (!hasRole) {
|
||||
return helpers.error("any.invalid", { message: `You do not have the required authorization. Required roles: ${role.join(", ")}` });
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
//****************************************
|
||||
// Auth
|
||||
//****************************************
|
||||
|
||||
const passwordPattern = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\-_=+[\]{};:'",.~`|\\/])[A-Za-z0-9!?@#$%^&*()\-_=+[\]{};:'",.~`|\\/]+$/;
|
||||
|
||||
const loginValidation = joi.object({
|
||||
email: joi.string().email().required().lowercase(),
|
||||
password: joi.string().required(),
|
||||
});
|
||||
const nameValidation = joi
|
||||
.string()
|
||||
.trim()
|
||||
.max(50)
|
||||
.pattern(/^(?=.*[\p{L}\p{Sc}])[\p{L}\p{Sc}\s'\-().]+$/u)
|
||||
.messages({
|
||||
"string.empty": "Name is required",
|
||||
"string.max": "Name must be less than 50 characters",
|
||||
"string.pattern.base":
|
||||
"Names must contain at least 1 letter and may only include letters, currency symbols, spaces, apostrophes, hyphens (-), periods (.), and parentheses ().",
|
||||
});
|
||||
|
||||
const registrationBodyValidation = joi.object({
|
||||
firstName: nameValidation.required(),
|
||||
lastName: nameValidation.required(),
|
||||
email: joi
|
||||
.string()
|
||||
.email()
|
||||
.required()
|
||||
.custom((value, helpers) => {
|
||||
const lowercasedValue = value.toLowerCase();
|
||||
if (value !== lowercasedValue) {
|
||||
return helpers.message({ custom: "Email must be in lowercase" });
|
||||
}
|
||||
return lowercasedValue;
|
||||
}),
|
||||
password: joi.string().min(8).required().pattern(passwordPattern),
|
||||
profileImage: joi.any(),
|
||||
inviteToken: joi.string().allow("").optional(),
|
||||
});
|
||||
|
||||
const editUserBodyValidation = joi.object({
|
||||
firstName: nameValidation.optional(),
|
||||
lastName: nameValidation.optional(),
|
||||
profileImage: joi.any(),
|
||||
newPassword: joi.string().min(8).pattern(passwordPattern),
|
||||
password: joi.string().min(8).pattern(passwordPattern),
|
||||
deleteProfileImage: joi.alternatives().try(joi.boolean(), joi.string().valid("true", "false")),
|
||||
});
|
||||
|
||||
const recoveryValidation = joi.object({
|
||||
email: joi
|
||||
.string()
|
||||
.email({ tlds: { allow: false } })
|
||||
.required(),
|
||||
});
|
||||
|
||||
const recoveryTokenBodyValidation = joi.object({
|
||||
recoveryToken: joi.string().required(),
|
||||
});
|
||||
|
||||
const newPasswordValidation = joi.object({
|
||||
recoveryToken: joi.string().required(),
|
||||
password: joi.string().min(8).required().pattern(passwordPattern),
|
||||
confirm: joi.string(),
|
||||
});
|
||||
|
||||
const deleteUserParamValidation = joi.object({
|
||||
email: joi.string().email().required(),
|
||||
});
|
||||
|
||||
const inviteBodyValidation = joi.object({
|
||||
email: joi.string().trim().email().required().messages({
|
||||
"string.empty": "Email is required",
|
||||
"string.email": "Must be a valid email address",
|
||||
}),
|
||||
role: joi.array().required(),
|
||||
teamId: joi.string().required(),
|
||||
});
|
||||
|
||||
const inviteVerificationBodyValidation = joi.object({
|
||||
token: joi.string().required(),
|
||||
});
|
||||
|
||||
//****************************************
|
||||
// Monitors
|
||||
//****************************************
|
||||
|
||||
const getMonitorByIdParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
const getMonitorByIdQueryValidation = joi.object({
|
||||
status: joi.boolean(),
|
||||
sortOrder: joi.string().valid("asc", "desc"),
|
||||
limit: joi.number(),
|
||||
dateRange: joi.string().valid("recent", "hour", "day", "week", "month", "all"),
|
||||
numToDisplay: joi.number(),
|
||||
normalize: joi.boolean(),
|
||||
continent: joi.string().valid(...GeoContinents),
|
||||
});
|
||||
|
||||
const getMonitorsByTeamIdParamValidation = joi.object({});
|
||||
|
||||
const getMonitorsByTeamIdQueryValidation = joi.object({
|
||||
type: joi
|
||||
.alternatives()
|
||||
.try(
|
||||
joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc"),
|
||||
joi.array().items(joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc"))
|
||||
),
|
||||
filter: joi.string().allow("", null),
|
||||
});
|
||||
|
||||
const getMonitorsWithChecksQueryValidation = joi.object({
|
||||
limit: joi.number().integer().min(1).max(100).optional(),
|
||||
page: joi.number().integer().min(0).optional(),
|
||||
rowsPerPage: joi.number().integer().min(1).max(100).optional(),
|
||||
filter: joi.string().allow("", null).optional(),
|
||||
field: joi.string().optional(),
|
||||
order: joi.string().valid("asc", "desc").optional(),
|
||||
type: joi
|
||||
.alternatives()
|
||||
.try(
|
||||
joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc"),
|
||||
joi.array().items(joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc"))
|
||||
)
|
||||
.optional(),
|
||||
explain: joi.boolean().optional(),
|
||||
});
|
||||
|
||||
const getCertificateParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
const createMonitorBodyValidation = joi.object({
|
||||
_id: joi.string(),
|
||||
name: joi.string().required(),
|
||||
description: joi.string().allow(null, ""),
|
||||
type: joi.string().required(),
|
||||
statusWindowSize: joi.number().min(1).max(20).default(5),
|
||||
statusWindowThreshold: joi.number().min(1).max(100).default(60),
|
||||
url: joi.string().required(),
|
||||
ignoreTlsErrors: joi.boolean().default(false),
|
||||
useAdvancedMatching: joi.boolean().default(false),
|
||||
port: joi.number(),
|
||||
isActive: joi.boolean(),
|
||||
interval: joi.number(),
|
||||
cpuAlertThreshold: joi.number(),
|
||||
memoryAlertThreshold: joi.number(),
|
||||
diskAlertThreshold: joi.number(),
|
||||
tempAlertThreshold: joi.number(),
|
||||
notifications: joi.array().items(joi.string()),
|
||||
secret: joi.string(),
|
||||
jsonPath: joi.string().allow(""),
|
||||
expectedValue: joi.string().allow(""),
|
||||
matchMethod: joi.string().allow(null, ""),
|
||||
gameId: joi.string().allow(""),
|
||||
grpcServiceName: joi.string().allow("").default(""),
|
||||
selectedDisks: joi.array().items(joi.string()).optional(),
|
||||
group: joi.string().max(50).trim().allow(null, "").optional(),
|
||||
geoCheckEnabled: joi.boolean().optional(),
|
||||
geoCheckLocations: joi
|
||||
.array()
|
||||
.items(joi.string().valid(...GeoContinents))
|
||||
.optional(),
|
||||
geoCheckInterval: joi.number().min(300000).optional(),
|
||||
});
|
||||
|
||||
const createMonitorsBodyValidation = joi.array().items(
|
||||
createMonitorBodyValidation.keys({
|
||||
userId: joi.string().required(),
|
||||
teamId: joi.string().required(),
|
||||
})
|
||||
);
|
||||
|
||||
const editMonitorBodyValidation = joi
|
||||
.object({
|
||||
name: joi.string(),
|
||||
statusWindowSize: joi.number().min(1).max(20).default(5),
|
||||
statusWindowThreshold: joi.number().min(1).max(100).default(60),
|
||||
description: joi.string().allow(null, ""),
|
||||
interval: joi.number(),
|
||||
notifications: joi.array().items(joi.string()),
|
||||
secret: joi.string(),
|
||||
ignoreTlsErrors: joi.boolean(),
|
||||
useAdvancedMatching: joi.boolean(),
|
||||
jsonPath: joi.string().allow(""),
|
||||
expectedValue: joi.string().allow(""),
|
||||
matchMethod: joi.string().allow(null, ""),
|
||||
port: joi.number().min(1).max(65535),
|
||||
cpuAlertThreshold: joi.number(),
|
||||
memoryAlertThreshold: joi.number(),
|
||||
diskAlertThreshold: joi.number(),
|
||||
tempAlertThreshold: joi.number(),
|
||||
gameId: joi.string().allow(""),
|
||||
grpcServiceName: joi.string().allow(""),
|
||||
selectedDisks: joi.array().items(joi.string()).optional(),
|
||||
group: joi.string().max(50).trim().allow(null, "").optional(),
|
||||
geoCheckEnabled: joi.boolean().optional(),
|
||||
geoCheckLocations: joi
|
||||
.array()
|
||||
.items(joi.string().valid(...GeoContinents))
|
||||
.optional(),
|
||||
geoCheckInterval: joi.number().min(300000).optional(),
|
||||
})
|
||||
.options({ stripUnknown: true });
|
||||
|
||||
const pauseMonitorParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
const getMonitorURLByQueryValidation = joi.object({
|
||||
monitorURL: joi.string().uri().required(),
|
||||
});
|
||||
|
||||
const getHardwareDetailsByIdParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
const getHardwareDetailsByIdQueryValidation = joi.object({
|
||||
dateRange: joi.string().valid("recent", "hour", "day", "week", "month", "all"),
|
||||
});
|
||||
|
||||
//****************************************
|
||||
// Alerts
|
||||
//****************************************
|
||||
|
||||
const createAlertParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
const createAlertBodyValidation = joi.object({
|
||||
checkId: joi.string().required(),
|
||||
monitorId: joi.string().required(),
|
||||
userId: joi.string().required(),
|
||||
status: joi.boolean(),
|
||||
message: joi.string(),
|
||||
notifiedStatus: joi.boolean(),
|
||||
acknowledgeStatus: joi.boolean(),
|
||||
});
|
||||
|
||||
const getAlertsByUserIdParamValidation = joi.object({
|
||||
userId: joi.string().required(),
|
||||
});
|
||||
|
||||
const getAlertsByMonitorIdParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
const getAlertByIdParamValidation = joi.object({
|
||||
alertId: joi.string().required(),
|
||||
});
|
||||
|
||||
const editAlertParamValidation = joi.object({
|
||||
alertId: joi.string().required(),
|
||||
});
|
||||
|
||||
const editAlertBodyValidation = joi.object({
|
||||
status: joi.boolean(),
|
||||
message: joi.string(),
|
||||
notifiedStatus: joi.boolean(),
|
||||
acknowledgeStatus: joi.boolean(),
|
||||
});
|
||||
|
||||
const deleteAlertParamValidation = joi.object({
|
||||
alertId: joi.string().required(),
|
||||
});
|
||||
|
||||
//****************************************
|
||||
// Checks
|
||||
//****************************************
|
||||
|
||||
const createCheckParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
const createCheckBodyValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
status: joi.boolean().required(),
|
||||
responseTime: joi.number().required(),
|
||||
statusCode: joi.number().required(),
|
||||
message: joi.string().required(),
|
||||
});
|
||||
|
||||
const ackCheckBodyValidation = joi.object({
|
||||
ack: joi.boolean(),
|
||||
});
|
||||
|
||||
const ackAllChecksParamValidation = joi.object({
|
||||
monitorId: joi.string().optional(),
|
||||
path: joi.string().valid("monitor", "team").required(),
|
||||
});
|
||||
|
||||
const ackAllChecksBodyValidation = joi.object({
|
||||
ack: joi.boolean(),
|
||||
});
|
||||
|
||||
const getChecksParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
const getChecksQueryValidation = joi.object({
|
||||
type: joi.string().valid("http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc"),
|
||||
sortOrder: joi.string().valid("asc", "desc"),
|
||||
limit: joi.number(),
|
||||
dateRange: joi.string().valid("recent", "hour", "day", "week", "month", "all"),
|
||||
filter: joi.string().valid("all", "up", "down", "resolve"),
|
||||
ack: joi.boolean(),
|
||||
page: joi.number(),
|
||||
rowsPerPage: joi.number(),
|
||||
status: joi.boolean(),
|
||||
continent: joi.alternatives().try(joi.string().valid(...GeoContinents), joi.array().items(joi.string().valid(...GeoContinents))),
|
||||
});
|
||||
|
||||
const getTeamChecksQueryValidation = joi.object({
|
||||
sortOrder: joi.string().valid("asc", "desc"),
|
||||
limit: joi.number(),
|
||||
dateRange: joi.string().valid("recent", "hour", "day", "week", "month", "all"),
|
||||
filter: joi.string().valid("all", "up", "down", "resolve"),
|
||||
ack: joi.boolean(),
|
||||
page: joi.number(),
|
||||
rowsPerPage: joi.number(),
|
||||
});
|
||||
|
||||
const deleteChecksParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
const deleteChecksByTeamIdParamValidation = joi.object({});
|
||||
|
||||
const updateChecksTTLBodyValidation = joi.object({
|
||||
ttl: joi.number().required(),
|
||||
});
|
||||
|
||||
//****************************************
|
||||
// PageSpeedCheckValidation
|
||||
//****************************************
|
||||
|
||||
const getPageSpeedCheckParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
//Validation schema for the monitorId parameter
|
||||
const createPageSpeedCheckParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
//Validation schema for the monitorId body
|
||||
const createPageSpeedCheckBodyValidation = joi.object({
|
||||
url: joi.string().required(),
|
||||
});
|
||||
|
||||
const deletePageSpeedCheckParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
//****************************************
|
||||
// MaintenanceWindowValidation
|
||||
//****************************************
|
||||
|
||||
const createMaintenanceWindowBodyValidation = joi.object({
|
||||
monitors: joi.array().items(joi.string()).required(),
|
||||
name: joi.string().required(),
|
||||
active: joi.boolean(),
|
||||
duration: joi.number().required(),
|
||||
durationUnit: joi.string().valid("seconds", "minutes", "hours", "days").required(),
|
||||
start: joi.date().required(),
|
||||
end: joi.date().required(),
|
||||
repeat: joi.number().required(),
|
||||
expiry: joi.date(),
|
||||
});
|
||||
|
||||
const getMaintenanceWindowByIdParamValidation = joi.object({
|
||||
id: joi.string().required(),
|
||||
});
|
||||
|
||||
const getMaintenanceWindowsByTeamIdQueryValidation = joi.object({
|
||||
active: joi.boolean(),
|
||||
page: joi.number(),
|
||||
rowsPerPage: joi.number(),
|
||||
field: joi.string(),
|
||||
order: joi.string().valid("asc", "desc"),
|
||||
});
|
||||
|
||||
const getMaintenanceWindowsByMonitorIdParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
const deleteMaintenanceWindowByIdParamValidation = joi.object({
|
||||
id: joi.string().required(),
|
||||
});
|
||||
|
||||
const editMaintenanceWindowByIdParamValidation = joi.object({
|
||||
id: joi.string().required(),
|
||||
});
|
||||
|
||||
const editMaintenanceByIdWindowBodyValidation = joi.object({
|
||||
active: joi.boolean(),
|
||||
name: joi.string(),
|
||||
repeat: joi.number(),
|
||||
start: joi.date(),
|
||||
end: joi.date(),
|
||||
expiry: joi.date(),
|
||||
monitors: joi.array(),
|
||||
duration: joi.number(),
|
||||
durationUnit: joi.string().valid("seconds", "minutes", "hours", "days"),
|
||||
});
|
||||
|
||||
//****************************************
|
||||
// SettingsValidation
|
||||
//****************************************
|
||||
const updateAppSettingsBodyValidation = joi.object({
|
||||
checkTTL: joi.number().allow(""),
|
||||
pagespeedApiKey: joi.string().allow(""),
|
||||
language: joi.string().allow(""),
|
||||
timezone: joi.string().allow(""),
|
||||
showURL: joi.bool().optional(),
|
||||
systemEmailHost: joi.string().allow(""),
|
||||
systemEmailPort: joi.number().allow(""),
|
||||
systemEmailAddress: joi.string().allow(""),
|
||||
systemEmailPassword: joi.string().allow(""),
|
||||
systemEmailUser: joi.string().allow(""),
|
||||
systemEmailConnectionHost: joi.string().allow(""),
|
||||
systemEmailTLSServername: joi.string().allow(""),
|
||||
systemEmailSecure: joi.boolean(),
|
||||
systemEmailPool: joi.boolean(),
|
||||
systemEmailIgnoreTLS: joi.boolean(),
|
||||
systemEmailRequireTLS: joi.boolean(),
|
||||
systemEmailRejectUnauthorized: joi.boolean(),
|
||||
|
||||
globalThresholds: joi
|
||||
.object({
|
||||
cpu: joi.number().min(1).max(100).allow(""),
|
||||
memory: joi.number().min(1).max(100).allow(""),
|
||||
disk: joi.number().min(1).max(100).allow(""),
|
||||
temperature: joi.number().min(1).max(150).allow(""),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
//****************************************
|
||||
// Status Page Validation
|
||||
//****************************************
|
||||
|
||||
const getStatusPageParamValidation = joi.object({
|
||||
url: joi.string().required(),
|
||||
});
|
||||
|
||||
const getStatusPageQueryValidation = joi.object({
|
||||
type: joi.string().valid("uptime").required(),
|
||||
timeFrame: joi.number().optional(),
|
||||
});
|
||||
|
||||
const createStatusPageBodyValidation = joi.object({
|
||||
type: joi.string().valid("uptime").required(),
|
||||
companyName: joi.string().required(),
|
||||
url: joi
|
||||
.string()
|
||||
.pattern(/^[a-zA-Z0-9_-]+$/) // Only allow alphanumeric, underscore, and hyphen
|
||||
.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(),
|
||||
});
|
||||
|
||||
const imageValidation = joi
|
||||
.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)",
|
||||
}),
|
||||
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(),
|
||||
})
|
||||
.messages({
|
||||
"any.required": "Image file is required",
|
||||
});
|
||||
|
||||
const webhookConfigValidation = joi
|
||||
.object({
|
||||
webhookUrl: joi
|
||||
.string()
|
||||
.uri()
|
||||
.when("$platform", {
|
||||
switch: [
|
||||
{
|
||||
is: "telegram",
|
||||
then: joi.optional(),
|
||||
},
|
||||
{
|
||||
is: "discord",
|
||||
then: joi.required().messages({
|
||||
"string.empty": "Discord webhook URL is required",
|
||||
"string.uri": "Discord webhook URL must be a valid URL",
|
||||
"any.required": "Discord webhook URL is required",
|
||||
}),
|
||||
},
|
||||
{
|
||||
is: "slack",
|
||||
then: joi.required().messages({
|
||||
"string.empty": "Slack webhook URL is required",
|
||||
"string.uri": "Slack webhook URL must be a valid URL",
|
||||
"any.required": "Slack webhook URL is required",
|
||||
}),
|
||||
},
|
||||
],
|
||||
}),
|
||||
botToken: joi.string().when("$platform", {
|
||||
is: "telegram",
|
||||
then: joi.required().messages({
|
||||
"string.empty": "Telegram bot token is required",
|
||||
"any.required": "Telegram bot token is required",
|
||||
}),
|
||||
otherwise: joi.optional(),
|
||||
}),
|
||||
chatId: joi.string().when("$platform", {
|
||||
is: "telegram",
|
||||
then: joi.required().messages({
|
||||
"string.empty": "Telegram chat ID is required",
|
||||
"any.required": "Telegram chat ID is required",
|
||||
}),
|
||||
otherwise: joi.optional(),
|
||||
}),
|
||||
})
|
||||
.required();
|
||||
|
||||
const triggerNotificationBodyValidation = joi.object({
|
||||
monitorId: joi.string().required().messages({
|
||||
"string.empty": "Monitor ID is required",
|
||||
"any.required": "Monitor ID is required",
|
||||
}),
|
||||
type: joi.string().valid("webhook").required().messages({
|
||||
"string.empty": "Notification type is required",
|
||||
"any.required": "Notification type is required",
|
||||
"any.only": "Notification type must be webhook",
|
||||
}),
|
||||
platform: joi.string().valid("telegram", "discord", "slack").required().messages({
|
||||
"string.empty": "Platform type is required",
|
||||
"any.required": "Platform type is required",
|
||||
"any.only": "Platform must be telegram, discord, or slack",
|
||||
}),
|
||||
config: webhookConfigValidation.required().messages({
|
||||
"any.required": "Webhook configuration is required",
|
||||
}),
|
||||
});
|
||||
|
||||
const createNotificationBodyValidation = joi.object({
|
||||
notificationName: joi.string().required().messages({
|
||||
"string.empty": "Notification name is required",
|
||||
"any.required": "Notification name is required",
|
||||
}),
|
||||
|
||||
type: joi.string().valid("email", "webhook", "slack", "discord", "pager_duty", "matrix").required().messages({
|
||||
"string.empty": "Notification type is required",
|
||||
"any.required": "Notification type is required",
|
||||
"any.only": "Notification type must be email, webhook, slack, discord, pager_duty, or matrix",
|
||||
}),
|
||||
|
||||
address: joi.when("type", {
|
||||
switch: [
|
||||
{
|
||||
is: "email",
|
||||
then: joi.string().email().required().messages({
|
||||
"string.empty": "E-mail address cannot be empty",
|
||||
"any.required": "E-mail address is required",
|
||||
"string.email": "Please enter a valid e-mail address",
|
||||
}),
|
||||
},
|
||||
{
|
||||
is: "pager_duty",
|
||||
then: joi.string().required().messages({
|
||||
"string.empty": "PagerDuty integration key cannot be empty",
|
||||
"any.required": "PagerDuty integration key is required",
|
||||
}),
|
||||
},
|
||||
{
|
||||
is: joi.string().valid("webhook", "slack", "discord"),
|
||||
then: joi.string().uri().required().messages({
|
||||
"string.empty": "Webhook URL cannot be empty",
|
||||
"any.required": "Webhook URL is required",
|
||||
"string.uri": "Please enter a valid Webhook URL",
|
||||
}),
|
||||
},
|
||||
{
|
||||
is: "matrix",
|
||||
then: joi.string().allow("").optional(),
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
homeserverUrl: joi.when("type", {
|
||||
is: "matrix",
|
||||
then: joi.string().uri().required().messages({
|
||||
"string.empty": "Homeserver URL cannot be empty",
|
||||
"any.required": "Homeserver URL is required",
|
||||
"string.uri": "Please enter a valid Homeserver URL",
|
||||
}),
|
||||
otherwise: joi.string().allow("").optional(),
|
||||
}),
|
||||
|
||||
roomId: joi.when("type", {
|
||||
is: "matrix",
|
||||
then: joi.string().required().messages({
|
||||
"string.empty": "Room ID cannot be empty",
|
||||
"any.required": "Room ID is required",
|
||||
}),
|
||||
otherwise: joi.string().allow("").optional(),
|
||||
}),
|
||||
|
||||
accessToken: joi.when("type", {
|
||||
is: "matrix",
|
||||
then: joi.string().required().messages({
|
||||
"string.empty": "Access Token cannot be empty",
|
||||
"any.required": "Access Token is required",
|
||||
}),
|
||||
otherwise: joi.string().allow("").optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
//****************************************
|
||||
// Announcetment Page Validation
|
||||
//****************************************
|
||||
|
||||
const createAnnouncementValidation = joi.object({
|
||||
title: joi.string().required().messages({
|
||||
"string.empty": "Title cannot be empty",
|
||||
"any.required": "Title is required",
|
||||
}),
|
||||
message: joi.string().required().messages({
|
||||
"string.empty": "Message cannot be empty",
|
||||
"any.required": "Message is required",
|
||||
}),
|
||||
userId: joi.string().required(),
|
||||
});
|
||||
|
||||
const sendTestEmailBodyValidation = joi.object({
|
||||
to: joi.string().required(),
|
||||
systemEmailHost: joi.string(),
|
||||
systemEmailPort: joi.number(),
|
||||
systemEmailSecure: joi.boolean(),
|
||||
systemEmailPool: joi.boolean(),
|
||||
systemEmailAddress: joi.string(),
|
||||
systemEmailPassword: joi.string(),
|
||||
systemEmailUser: joi.string(),
|
||||
systemEmailConnectionHost: joi.string().allow("").optional(),
|
||||
systemEmailIgnoreTLS: joi.boolean(),
|
||||
systemEmailRequireTLS: joi.boolean(),
|
||||
systemEmailRejectUnauthorized: joi.boolean(),
|
||||
systemEmailTLSServername: joi.string().allow("").optional(),
|
||||
});
|
||||
|
||||
const getUserByIdParamValidation = joi.object({
|
||||
userId: joi.string().required(),
|
||||
});
|
||||
|
||||
const editUserByIdParamValidation = joi.object({
|
||||
userId: joi.string().required(),
|
||||
});
|
||||
|
||||
const editUserByIdBodyValidation = joi.object({
|
||||
firstName: nameValidation.required(),
|
||||
lastName: nameValidation.required(),
|
||||
role: joi
|
||||
.array()
|
||||
.items(joi.string().valid(...UserRoles))
|
||||
.min(1)
|
||||
.required(),
|
||||
});
|
||||
|
||||
const editSuperadminUserByIdBodyValidation = joi.object({
|
||||
firstName: nameValidation.required(),
|
||||
lastName: nameValidation.required(),
|
||||
role: joi
|
||||
.array()
|
||||
.items(joi.string().valid(...UserRoles))
|
||||
.min(1)
|
||||
.required(),
|
||||
});
|
||||
|
||||
const editUserPasswordByIdBodyValidation = joi.object({
|
||||
password: joi.string().min(8).required().pattern(passwordPattern),
|
||||
});
|
||||
|
||||
const createUserBodyValidation = joi.object({
|
||||
firstName: nameValidation.required(),
|
||||
lastName: nameValidation.required(),
|
||||
email: joi
|
||||
.string()
|
||||
.email()
|
||||
.required()
|
||||
.custom((value, helpers) => {
|
||||
const lowercasedValue = value.toLowerCase();
|
||||
if (value !== lowercasedValue) {
|
||||
return helpers.message({ custom: "Email must be in lowercase" });
|
||||
}
|
||||
return lowercasedValue;
|
||||
}),
|
||||
password: joi.string().min(8).required().pattern(passwordPattern),
|
||||
role: joi
|
||||
.array()
|
||||
.items(joi.string().valid(...UserRoles))
|
||||
.min(1)
|
||||
.required(),
|
||||
});
|
||||
|
||||
export {
|
||||
roleValidatior,
|
||||
loginValidation,
|
||||
registrationBodyValidation,
|
||||
recoveryValidation,
|
||||
recoveryTokenBodyValidation,
|
||||
newPasswordValidation,
|
||||
inviteBodyValidation,
|
||||
inviteVerificationBodyValidation,
|
||||
createMonitorBodyValidation,
|
||||
createMonitorsBodyValidation,
|
||||
getMonitorByIdParamValidation,
|
||||
getMonitorByIdQueryValidation,
|
||||
getMonitorsByTeamIdParamValidation,
|
||||
getMonitorsByTeamIdQueryValidation,
|
||||
getMonitorsWithChecksQueryValidation,
|
||||
getHardwareDetailsByIdParamValidation,
|
||||
getHardwareDetailsByIdQueryValidation,
|
||||
getCertificateParamValidation,
|
||||
editMonitorBodyValidation,
|
||||
pauseMonitorParamValidation,
|
||||
getMonitorURLByQueryValidation,
|
||||
editUserBodyValidation,
|
||||
createAlertParamValidation,
|
||||
createAlertBodyValidation,
|
||||
getAlertsByUserIdParamValidation,
|
||||
getAlertsByMonitorIdParamValidation,
|
||||
getAlertByIdParamValidation,
|
||||
editAlertParamValidation,
|
||||
editAlertBodyValidation,
|
||||
deleteAlertParamValidation,
|
||||
createCheckParamValidation,
|
||||
createCheckBodyValidation,
|
||||
getChecksParamValidation,
|
||||
getChecksQueryValidation,
|
||||
getTeamChecksQueryValidation,
|
||||
ackCheckBodyValidation,
|
||||
ackAllChecksParamValidation,
|
||||
ackAllChecksBodyValidation,
|
||||
deleteChecksParamValidation,
|
||||
deleteChecksByTeamIdParamValidation,
|
||||
updateChecksTTLBodyValidation,
|
||||
deleteUserParamValidation,
|
||||
getPageSpeedCheckParamValidation,
|
||||
createPageSpeedCheckParamValidation,
|
||||
deletePageSpeedCheckParamValidation,
|
||||
createPageSpeedCheckBodyValidation,
|
||||
createMaintenanceWindowBodyValidation,
|
||||
getMaintenanceWindowByIdParamValidation,
|
||||
getMaintenanceWindowsByTeamIdQueryValidation,
|
||||
getMaintenanceWindowsByMonitorIdParamValidation,
|
||||
deleteMaintenanceWindowByIdParamValidation,
|
||||
editMaintenanceWindowByIdParamValidation,
|
||||
editMaintenanceByIdWindowBodyValidation,
|
||||
updateAppSettingsBodyValidation,
|
||||
createStatusPageBodyValidation,
|
||||
getStatusPageParamValidation,
|
||||
getStatusPageQueryValidation,
|
||||
imageValidation,
|
||||
triggerNotificationBodyValidation,
|
||||
createNotificationBodyValidation,
|
||||
webhookConfigValidation,
|
||||
createAnnouncementValidation,
|
||||
sendTestEmailBodyValidation,
|
||||
getUserByIdParamValidation,
|
||||
editUserByIdParamValidation,
|
||||
editUserByIdBodyValidation,
|
||||
editSuperadminUserByIdBodyValidation,
|
||||
editUserPasswordByIdBodyValidation,
|
||||
createUserBodyValidation,
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
import joi from "joi";
|
||||
|
||||
//****************************************
|
||||
// Maintenance Window Validations
|
||||
//****************************************
|
||||
|
||||
export const createMaintenanceWindowBodyValidation = joi.object({
|
||||
monitors: joi.array().items(joi.string()).required(),
|
||||
name: joi.string().required(),
|
||||
active: joi.boolean(),
|
||||
duration: joi.number().required(),
|
||||
durationUnit: joi.string().valid("seconds", "minutes", "hours", "days").required(),
|
||||
start: joi.date().required(),
|
||||
end: joi.date().required(),
|
||||
repeat: joi.number().required(),
|
||||
expiry: joi.date(),
|
||||
});
|
||||
|
||||
export const getMaintenanceWindowByIdParamValidation = joi.object({
|
||||
id: joi.string().required(),
|
||||
});
|
||||
|
||||
export const getMaintenanceWindowsByTeamIdQueryValidation = joi.object({
|
||||
active: joi.boolean(),
|
||||
page: joi.number(),
|
||||
rowsPerPage: joi.number(),
|
||||
field: joi.string(),
|
||||
order: joi.string().valid("asc", "desc"),
|
||||
});
|
||||
|
||||
export const getMaintenanceWindowsByMonitorIdParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
export const deleteMaintenanceWindowByIdParamValidation = joi.object({
|
||||
id: joi.string().required(),
|
||||
});
|
||||
|
||||
export const editMaintenanceWindowByIdParamValidation = joi.object({
|
||||
id: joi.string().required(),
|
||||
});
|
||||
|
||||
export const editMaintenanceByIdWindowBodyValidation = joi.object({
|
||||
active: joi.boolean(),
|
||||
name: joi.string(),
|
||||
repeat: joi.number(),
|
||||
start: joi.date(),
|
||||
end: joi.date(),
|
||||
expiry: joi.date(),
|
||||
monitors: joi.array(),
|
||||
duration: joi.number(),
|
||||
durationUnit: joi.string().valid("seconds", "minutes", "hours", "days"),
|
||||
});
|
||||
@@ -0,0 +1,127 @@
|
||||
import joi from "joi";
|
||||
import { GeoContinents } from "@/types/geoCheck.js";
|
||||
|
||||
export const getMonitorByIdParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
export const getMonitorByIdQueryValidation = joi.object({
|
||||
status: joi.boolean(),
|
||||
sortOrder: joi.string().valid("asc", "desc"),
|
||||
limit: joi.number(),
|
||||
dateRange: joi.string().valid("recent", "hour", "day", "week", "month", "all"),
|
||||
numToDisplay: joi.number(),
|
||||
normalize: joi.boolean(),
|
||||
continent: joi.string().valid(...GeoContinents),
|
||||
});
|
||||
|
||||
export const getMonitorsByTeamIdParamValidation = joi.object({});
|
||||
|
||||
export const getMonitorsByTeamIdQueryValidation = joi.object({
|
||||
type: joi
|
||||
.alternatives()
|
||||
.try(
|
||||
joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc"),
|
||||
joi.array().items(joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc"))
|
||||
),
|
||||
filter: joi.string().allow("", null),
|
||||
});
|
||||
|
||||
export const getMonitorsWithChecksQueryValidation = joi.object({
|
||||
limit: joi.number().integer().min(1).max(100).optional(),
|
||||
page: joi.number().integer().min(0).optional(),
|
||||
rowsPerPage: joi.number().integer().min(1).max(100).optional(),
|
||||
filter: joi.string().allow("", null).optional(),
|
||||
field: joi.string().optional(),
|
||||
order: joi.string().valid("asc", "desc").optional(),
|
||||
type: joi
|
||||
.alternatives()
|
||||
.try(
|
||||
joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc"),
|
||||
joi.array().items(joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc"))
|
||||
)
|
||||
.optional(),
|
||||
explain: joi.boolean().optional(),
|
||||
});
|
||||
|
||||
export const getCertificateParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
export const createMonitorBodyValidation = joi.object({
|
||||
_id: joi.string(),
|
||||
name: joi.string().required(),
|
||||
description: joi.string().allow(null, ""),
|
||||
type: joi.string().required(),
|
||||
statusWindowSize: joi.number().min(1).max(20).default(5),
|
||||
statusWindowThreshold: joi.number().min(1).max(100).default(60),
|
||||
url: joi.string().required(),
|
||||
ignoreTlsErrors: joi.boolean().default(false),
|
||||
useAdvancedMatching: joi.boolean().default(false),
|
||||
port: joi.number(),
|
||||
isActive: joi.boolean(),
|
||||
interval: joi.number(),
|
||||
cpuAlertThreshold: joi.number(),
|
||||
memoryAlertThreshold: joi.number(),
|
||||
diskAlertThreshold: joi.number(),
|
||||
tempAlertThreshold: joi.number(),
|
||||
notifications: joi.array().items(joi.string()),
|
||||
secret: joi.string(),
|
||||
jsonPath: joi.string().allow(""),
|
||||
expectedValue: joi.string().allow(""),
|
||||
matchMethod: joi.string().allow(null, ""),
|
||||
gameId: joi.string().allow(""),
|
||||
grpcServiceName: joi.string().allow("").default(""),
|
||||
selectedDisks: joi.array().items(joi.string()).optional(),
|
||||
group: joi.string().max(50).trim().allow(null, "").optional(),
|
||||
geoCheckEnabled: joi.boolean().optional(),
|
||||
geoCheckLocations: joi
|
||||
.array()
|
||||
.items(joi.string().valid(...GeoContinents))
|
||||
.optional(),
|
||||
geoCheckInterval: joi.number().min(300000).optional(),
|
||||
});
|
||||
|
||||
export const editMonitorBodyValidation = joi
|
||||
.object({
|
||||
name: joi.string(),
|
||||
statusWindowSize: joi.number().min(1).max(20).default(5),
|
||||
statusWindowThreshold: joi.number().min(1).max(100).default(60),
|
||||
description: joi.string().allow(null, ""),
|
||||
interval: joi.number(),
|
||||
notifications: joi.array().items(joi.string()),
|
||||
secret: joi.string(),
|
||||
ignoreTlsErrors: joi.boolean(),
|
||||
useAdvancedMatching: joi.boolean(),
|
||||
jsonPath: joi.string().allow(""),
|
||||
expectedValue: joi.string().allow(""),
|
||||
matchMethod: joi.string().allow(null, ""),
|
||||
port: joi.number().min(1).max(65535),
|
||||
cpuAlertThreshold: joi.number(),
|
||||
memoryAlertThreshold: joi.number(),
|
||||
diskAlertThreshold: joi.number(),
|
||||
tempAlertThreshold: joi.number(),
|
||||
gameId: joi.string().allow(""),
|
||||
grpcServiceName: joi.string().allow(""),
|
||||
selectedDisks: joi.array().items(joi.string()).optional(),
|
||||
group: joi.string().max(50).trim().allow(null, "").optional(),
|
||||
geoCheckEnabled: joi.boolean().optional(),
|
||||
geoCheckLocations: joi
|
||||
.array()
|
||||
.items(joi.string().valid(...GeoContinents))
|
||||
.optional(),
|
||||
geoCheckInterval: joi.number().min(300000).optional(),
|
||||
})
|
||||
.options({ stripUnknown: true });
|
||||
|
||||
export const pauseMonitorParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
export const getHardwareDetailsByIdParamValidation = joi.object({
|
||||
monitorId: joi.string().required(),
|
||||
});
|
||||
|
||||
export const getHardwareDetailsByIdQueryValidation = joi.object({
|
||||
dateRange: joi.string().valid("recent", "hour", "day", "week", "month", "all"),
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import joi from "joi";
|
||||
|
||||
//****************************************
|
||||
// Notification Validations
|
||||
//****************************************
|
||||
|
||||
export const createNotificationBodyValidation = joi.object({
|
||||
notificationName: joi.string().required().messages({
|
||||
"string.empty": "Notification name is required",
|
||||
"any.required": "Notification name is required",
|
||||
}),
|
||||
|
||||
type: joi.string().valid("email", "webhook", "slack", "discord", "pager_duty", "matrix").required().messages({
|
||||
"string.empty": "Notification type is required",
|
||||
"any.required": "Notification type is required",
|
||||
"any.only": "Notification type must be email, webhook, slack, discord, pager_duty, or matrix",
|
||||
}),
|
||||
|
||||
address: joi.when("type", {
|
||||
switch: [
|
||||
{
|
||||
is: "email",
|
||||
then: joi.string().email().required().messages({
|
||||
"string.empty": "E-mail address cannot be empty",
|
||||
"any.required": "E-mail address is required",
|
||||
"string.email": "Please enter a valid e-mail address",
|
||||
}),
|
||||
},
|
||||
{
|
||||
is: "pager_duty",
|
||||
then: joi.string().required().messages({
|
||||
"string.empty": "PagerDuty integration key cannot be empty",
|
||||
"any.required": "PagerDuty integration key is required",
|
||||
}),
|
||||
},
|
||||
{
|
||||
is: joi.string().valid("webhook", "slack", "discord"),
|
||||
then: joi.string().uri().required().messages({
|
||||
"string.empty": "Webhook URL cannot be empty",
|
||||
"any.required": "Webhook URL is required",
|
||||
"string.uri": "Please enter a valid Webhook URL",
|
||||
}),
|
||||
},
|
||||
{
|
||||
is: "matrix",
|
||||
then: joi.string().allow("").optional(),
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
homeserverUrl: joi.when("type", {
|
||||
is: "matrix",
|
||||
then: joi.string().uri().required().messages({
|
||||
"string.empty": "Homeserver URL cannot be empty",
|
||||
"any.required": "Homeserver URL is required",
|
||||
"string.uri": "Please enter a valid Homeserver URL",
|
||||
}),
|
||||
otherwise: joi.string().allow("").optional(),
|
||||
}),
|
||||
|
||||
roomId: joi.when("type", {
|
||||
is: "matrix",
|
||||
then: joi.string().required().messages({
|
||||
"string.empty": "Room ID cannot be empty",
|
||||
"any.required": "Room ID is required",
|
||||
}),
|
||||
otherwise: joi.string().allow("").optional(),
|
||||
}),
|
||||
|
||||
accessToken: joi.when("type", {
|
||||
is: "matrix",
|
||||
then: joi.string().required().messages({
|
||||
"string.empty": "Access Token cannot be empty",
|
||||
"any.required": "Access Token is required",
|
||||
}),
|
||||
otherwise: joi.string().allow("").optional(),
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import joi from "joi";
|
||||
|
||||
//****************************************
|
||||
// Settings Validations
|
||||
//****************************************
|
||||
|
||||
export const updateAppSettingsBodyValidation = joi.object({
|
||||
checkTTL: joi.number().allow(""),
|
||||
pagespeedApiKey: joi.string().allow(""),
|
||||
language: joi.string().allow(""),
|
||||
timezone: joi.string().allow(""),
|
||||
showURL: joi.bool().optional(),
|
||||
systemEmailHost: joi.string().allow(""),
|
||||
systemEmailPort: joi.number().allow(""),
|
||||
systemEmailAddress: joi.string().allow(""),
|
||||
systemEmailPassword: joi.string().allow(""),
|
||||
systemEmailUser: joi.string().allow(""),
|
||||
systemEmailConnectionHost: joi.string().allow(""),
|
||||
systemEmailTLSServername: joi.string().allow(""),
|
||||
systemEmailSecure: joi.boolean(),
|
||||
systemEmailPool: joi.boolean(),
|
||||
systemEmailIgnoreTLS: joi.boolean(),
|
||||
systemEmailRequireTLS: joi.boolean(),
|
||||
systemEmailRejectUnauthorized: joi.boolean(),
|
||||
|
||||
globalThresholds: joi
|
||||
.object({
|
||||
cpu: joi.number().min(1).max(100).allow(""),
|
||||
memory: joi.number().min(1).max(100).allow(""),
|
||||
disk: joi.number().min(1).max(100).allow(""),
|
||||
temperature: joi.number().min(1).max(150).allow(""),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { z } from "zod";
|
||||
import { type UserRole } from "@/types/user.js";
|
||||
|
||||
/**
|
||||
* Password pattern: requires at least one lowercase, uppercase, number, and special character
|
||||
*/
|
||||
export const passwordPattern =
|
||||
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\-_=+[\]{};:'",.~`|\\/])[A-Za-z0-9!?@#$%^&*()\-_=+[\]{};:'",.~`|\\/]+$/;
|
||||
|
||||
/**
|
||||
* Reusable name validation schema
|
||||
*/
|
||||
export const nameValidation = z
|
||||
.string()
|
||||
.trim()
|
||||
.max(50, "Name must be less than 50 characters")
|
||||
.regex(
|
||||
/^(?=.*[\p{L}\p{Sc}])[\p{L}\p{Sc}\s'\-().]+$/u,
|
||||
"Names must contain at least 1 letter and may only include letters, currency symbols, spaces, apostrophes, hyphens (-), periods (.), and parentheses ()."
|
||||
);
|
||||
|
||||
/**
|
||||
* Reusable email validation with lowercase enforcement
|
||||
*/
|
||||
export const lowercaseEmailValidation = z
|
||||
.email()
|
||||
.transform((val) => val.toLowerCase())
|
||||
.refine((val) => val === val.toLowerCase(), {
|
||||
message: "Email must be in lowercase",
|
||||
});
|
||||
|
||||
/**
|
||||
* Custom validator for role-based authorization
|
||||
*/
|
||||
export const roleValidator = (allowedRoles: UserRole[]) => {
|
||||
return z.array(z.custom<UserRole>()).refine((userRoles) => allowedRoles.some((role) => userRoles.includes(role)), {
|
||||
message: `You do not have the required authorization. Required roles: ${allowedRoles.join(", ")}`,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
import joi from "joi";
|
||||
|
||||
//****************************************
|
||||
// Status Page Validations
|
||||
//****************************************
|
||||
|
||||
export const getStatusPageParamValidation = joi.object({
|
||||
url: joi.string().required(),
|
||||
});
|
||||
|
||||
export const getStatusPageQueryValidation = joi.object({
|
||||
type: joi.string().valid("uptime").required(),
|
||||
timeFrame: joi.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
|
||||
.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)",
|
||||
}),
|
||||
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(),
|
||||
})
|
||||
.messages({
|
||||
"any.required": "Image file is required",
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
import { z } from "zod";
|
||||
import { UserRoles } from "@/types/user.js";
|
||||
import { nameValidation, lowercaseEmailValidation, passwordPattern } from "./shared.js";
|
||||
|
||||
//****************************************
|
||||
// User Validations
|
||||
//****************************************
|
||||
|
||||
export const getUserByIdParamValidation = z.object({
|
||||
userId: z.string().min(1, "User ID is required"),
|
||||
});
|
||||
|
||||
export const editUserByIdParamValidation = z.object({
|
||||
userId: z.string().min(1, "User ID is required"),
|
||||
});
|
||||
|
||||
export const editUserByIdBodyValidation = z.object({
|
||||
firstName: nameValidation,
|
||||
lastName: nameValidation,
|
||||
role: z.array(z.enum(UserRoles)).min(1, "At least one role is required"),
|
||||
});
|
||||
|
||||
export const editSuperadminUserByIdBodyValidation = z.object({
|
||||
firstName: nameValidation,
|
||||
lastName: nameValidation,
|
||||
role: z.array(z.enum(UserRoles)).min(1, "At least one role is required"),
|
||||
});
|
||||
|
||||
export const editUserPasswordByIdBodyValidation = z.object({
|
||||
password: z
|
||||
.string()
|
||||
.min(8, "Password must be at least 8 characters")
|
||||
.regex(passwordPattern, "Password must contain at least one lowercase letter, one uppercase letter, one number, and one special character"),
|
||||
});
|
||||
|
||||
export const createUserBodyValidation = z.object({
|
||||
firstName: nameValidation,
|
||||
lastName: nameValidation,
|
||||
email: lowercaseEmailValidation,
|
||||
password: z
|
||||
.string()
|
||||
.min(8, "Password must be at least 8 characters")
|
||||
.regex(passwordPattern, "Password must contain at least one lowercase letter, one uppercase letter, one number, and one special character"),
|
||||
role: z.array(z.enum(UserRoles)).min(1, "At least one role is required"),
|
||||
});
|
||||
|
||||
export const editUserBodyValidation = z.object({
|
||||
firstName: nameValidation.optional(),
|
||||
lastName: nameValidation.optional(),
|
||||
email: lowercaseEmailValidation.optional(),
|
||||
profilePicture: z.string().optional(),
|
||||
});
|
||||
Reference in New Issue
Block a user