recommended changes

This commit is contained in:
Anurag123Chauhan
2025-05-15 11:16:02 +05:30
parent 18b439bad6
commit 9da9ef6939

View File

@@ -4,119 +4,120 @@ import dayjs from "dayjs";
const THRESHOLD_COMMON_BASE_MSG = "Threshold must be a number.";
const nameSchema = joi
.string()
.max(50)
.trim()
.pattern(/^[\p{L}\p{M}''\- ]+$/u)
.messages({
"string.empty": "Name is required",
"string.max": "Name must be less than 50 characters",
"string.pattern.base":
"Name must contain only letters, spaces, apostrophes, or hyphens",
});
.string()
.max(50)
.trim()
.pattern(/^[\p{L}\p{M}''\- ]+$/u)
.messages({
"string.empty": "Name is required",
"string.max": "Name must be less than 50 characters",
"string.pattern.base":
"Name must contain only letters, spaces, apostrophes, or hyphens",
});
const lastnameSchema = joi
.string()
.max(50)
.trim()
.pattern(/^[\p{L}\p{M}''\- ]+$/u)
.messages({
"string.empty": "Surname is required",
"string.max": "Surname must be less than 50 characters",
"string.pattern.base": "Surname must contain only letters, spaces, apostrophes, or hyphens"
});
.string()
.max(50)
.trim()
.pattern(/^[\p{L}\p{M}''\- ]+$/u)
.messages({
"string.empty": "Surname is required",
"string.max": "Surname must be less than 50 characters",
"string.pattern.base": "Surname must contain only letters, spaces, apostrophes, or hyphens"
});
const passwordSchema = joi
.string()
.trim()
.min(8)
.custom((value, helpers) => {
if (!/[A-Z]/.test(value)) {
return helpers.error("uppercase");
}
return value;
})
.custom((value, helpers) => {
if (!/[a-z]/.test(value)) {
return helpers.error("lowercase");
}
return value;
})
.custom((value, helpers) => {
if (!/\d/.test(value)) {
return helpers.error("number");
}
return value;
})
.custom((value, helpers) => {
if (!/[!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/]/.test(value)) {
return helpers.error("special");
}
return value;
})
.messages({
"string.empty": "Password is required",
"string.min": "Password must be at least 8 characters long",
uppercase: "Password must contain at least one uppercase letter",
lowercase: "Password must contain at least one lowercase letter",
number: "Password must contain at least one number",
special: "Password must contain at least one special character",
});
.string()
.trim()
.min(8)
.custom((value, helpers) => {
if (!/[A-Z]/.test(value)) {
return helpers.error("uppercase");
}
return value;
})
.custom((value, helpers) => {
if (!/[a-z]/.test(value)) {
return helpers.error("lowercase");
}
return value;
})
.custom((value, helpers) => {
if (!/\d/.test(value)) {
return helpers.error("number");
}
return value;
})
.custom((value, helpers) => {
if (!/[!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/]/.test(value)) {
return helpers.error("special");
}
return value;
})
.messages({
"string.empty": "Password is required",
"string.min": "Password must be at least 8 characters long",
uppercase: "Password must contain at least one uppercase letter",
lowercase: "Password must contain at least one lowercase letter",
number: "Password must contain at least one number",
special: "Password must contain at least one special character",
});
const credentials = joi.object({
firstName: nameSchema,
lastName: lastnameSchema,
email: joi
.string()
.trim()
.email({ tlds: { allow: false } })
.custom((value, helpers) => {
const lowercasedValue = value.toLowerCase();
if (value !== lowercasedValue) {
return helpers.message("Email must be in lowercase");
}
return lowercasedValue;
})
.messages({
"string.empty": "authRegisterEmailRequired",
"string.email": "authRegisterEmailInvalid",
}),
password: passwordSchema,
newPassword: passwordSchema,
confirm: joi
.string()
.trim()
.custom((value, helpers) => {
const { password } = helpers.prefs.context;
if (value !== password) {
return helpers.error("different");
}
return value;
})
.messages({
"string.empty": "This field can't be empty",
different: "Passwords do not match",
}),
role: joi.array(),
teamId: joi.string().allow("").optional(),
inviteToken: joi.string().allow(""),
firstName: nameSchema,
lastName: lastnameSchema,
email: joi
.string()
.trim()
.email({ tlds: { allow: false } })
.custom((value, helpers) => {
const lowercasedValue = value.toLowerCase();
if (value !== lowercasedValue) {
return helpers.message("Email must be in lowercase");
}
return lowercasedValue;
})
.messages({
"string.empty": "authRegisterEmailRequired",
"string.email": "authRegisterEmailInvalid",
}),
password: passwordSchema,
newPassword: passwordSchema,
confirm: joi
.string()
.trim()
.custom((value, helpers) => {
const { password } = helpers.prefs.context;
if (value !== password) {
return helpers.error("different");
}
return value;
})
.messages({
"string.empty": "This field can't be empty",
different: "Passwords do not match",
}),
role: joi.array(),
teamId: joi.string().allow("").optional(),
inviteToken: joi.string().allow(""),
});
const monitorValidation = joi.object({
url: joi
.when("type", {
is: "docker",
then: joi
.string()
.trim()
.regex(/^[a-z0-9]{64}$/),
otherwise: joi
url: joi
.when("type", {
is: "docker",
then: joi
.string()
.trim()
.regex(/^[a-z0-9]{64}$/)
.messages({
"string.empty": "This field is required.",
"string.pattern.base": "Please enter a valid 64-character Docker container ID.",
}),
otherwise: joi
.string()
.trim()
.custom((value, helpers) => {
// Get the current validation context
const { type } = helpers.state.ancestors[0];
// Regex from https://gist.github.com/dperini/729294
var urlRegex = new RegExp(
"^" +
@@ -155,253 +156,251 @@ const monitorValidation = joi.object({
"i"
);
if (!urlRegex.test(value)) {
// Use different error key based on monitor type
return helpers.error(type === "docker" ? "string.invalidDockerID" : "string.invalidUrl");
return helpers.error("string.invalidUrl");
}
return value;
})
.messages({
"string.empty": "This field is required.",
"string.uri": "The URL you provided is not valid.",
"string.invalidUrl": "Please enter a valid URL with optional port",
}),
}),
port: joi
.number()
.integer()
.min(1)
.max(65535)
.when("type", {
is: "port",
then: joi.number().messages({
"number.base": "Port must be a number.",
"number.min": "Port must be at least 1.",
"number.max": "Port must be at most 65535.",
"any.required": "Port is required for port monitors.",
}),
otherwise: joi.optional(),
}),
name: joi.string().trim().max(50).allow("").messages({
"string.max": "This field should not exceed the 50 characters limit.",
}),
type: joi.string().trim().messages({ "string.empty": "This field is required." }),
ignoreTlsErrors: joi.boolean(),
interval: joi.number().messages({
"number.base": "Frequency must be a number.",
"any.required": "Frequency is required.",
}),
expectedValue: joi.string().allow(""),
jsonPath: joi.string().allow(""),
matchMethod: joi.string(),
});
const imageValidation = joi.object({
type: joi.string().valid("image/jpeg", "image/png").messages({
"any.only": "Invalid file format.",
"string.empty": "File type required.",
}),
size: joi
.number()
.max(3 * 1024 * 1024)
.messages({
"number.base": "File size must be a number.",
"number.max": "File size must be less than 3 MB.",
"number.empty": "File size required.",
}),
});
const logoImageValidation = joi
.object({
src: joi.string(),
name: joi.string(),
type: joi
.string()
.valid("image/jpeg", "image/png")
.allow(null) // Allow null and empty string
.messages({
"any.only": "Invalid file format.",
"string.empty": "File type required.",
})
.optional(),
size: joi
.number()
.max(3000000)
.allow(null) // Allow null and empty string
.messages({
"number.base": "File size must be a number.",
"number.max": "File size must be less than 3MB.",
"number.empty": "File size required.",
})
.optional(),
})
.allow(null)
.optional(); // Make entire object optional
const statusPageValidation = joi.object({
type: joi.string().valid("uptime", "distributed").required(),
isPublished: joi.bool(),
companyName: joi
.string()
.trim()
.messages({ "string.empty": "Company name is 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().trim().messages({ "string.empty": "Timezone is required." }),
color: joi.string().trim().messages({ "string.empty": "Color is required." }),
theme: joi.string(),
monitors: joi.array().min(1).required().messages({
"string.pattern.base": "Must be a valid monitor ID",
"array.base": "Monitors must be an array",
"array.min": "At least one monitor is required",
"array.empty": "At least one monitor is required",
"any.required": "At least one monitor is required",
}),
subMonitors: joi.array().optional(),
logo: logoImageValidation,
showUptimePercentage: joi.boolean(),
showCharts: joi.boolean(),
});
const settingsValidation = joi.object({
checkTTL: joi.number().required().messages({
"string.empty": "Please enter a value",
"number.base": "Please enter a valid number",
"any.required": "Please enter a value",
}),
pagespeedApiKey: joi.string().allow("").optional(),
language: joi.string().required(),
systemEmailHost: joi.string().allow(""),
systemEmailPort: joi.number().allow(null, ""),
systemEmailAddress: joi.string().allow(""),
systemEmailPassword: joi.string().allow(""),
systemEmailUser: joi.string().allow(""),
});
const dayjsValidator = (value, helpers) => {
if (!dayjs(value).isValid()) {
return helpers.error("any.invalid");
}
return value;
};
const maintenanceWindowValidation = joi.object({
repeat: joi.string(),
startDate: joi.custom(dayjsValidator, "Day.js date validation"),
startTime: joi.custom(dayjsValidator, "Day.js date validation"),
duration: joi.number().integer().min(0),
durationUnit: joi.string(),
name: joi.string(),
monitors: joi.array().min(1),
});
const advancedSettingsValidation = joi.object({
apiBaseUrl: joi.string().uri({ allowRelative: true }).trim().messages({
"string.empty": "API base url is required.",
"string.uri": "The URL you provided is not valid.",
}),
logLevel: joi.string().valid("debug", "none", "error", "warn").allow(""),
systemEmailHost: joi.string().allow(""),
systemEmailPort: joi.number().allow(null, ""),
systemEmailAddress: joi.string().allow(""),
systemEmailPassword: joi.string().allow(""),
jwtTTLNum: joi.number().messages({
"number.base": "JWT TTL is required.",
}),
jwtTTLUnits: joi
.string()
.trim()
.custom((value, helpers) => {
if (!["days", "hours"].includes(value)) {
return helpers.message("JWT TTL unit is required.");
}
return value;
}),
dbType: joi.string().trim().messages({
"string.empty": "DB type is required.",
}),
redisHost: joi.string().trim().messages({
"string.empty": "Redis host is required.",
}),
redisPort: joi.number().allow(null, ""),
pagespeedApiKey: joi.string().allow(""),
});
const infrastructureMonitorValidation = joi.object({
url: joi
.string()
.trim()
.custom((value, helpers) => {
const urlRegex =
/^(https?:\/\/)?(([0-9]{1,3}\.){3}[0-9]{1,3}|[\da-z\.-]+)(\.[a-z\.]{2,6})?(:(\d+))?([\/\w \.-]*)*\/?$/i;
if (!urlRegex.test(value)) {
return helpers.error("string.invalidUrl");
}
return value;
})
.messages({
"string.empty": "This field is required.",
"string.uri": "The URL you provided is not valid.",
"string.invalidUrl": "Please enter a valid URL with optional port",
"string.invalidDockerID": "Please enter a valid 64-character Docker container ID.",
"string.pattern.base": "Please enter a valid container ID.",
}),
port: joi
.number()
.integer()
.min(1)
.max(65535)
.when("type", {
is: "port",
then: joi.number().messages({
"number.base": "Port must be a number.",
"number.min": "Port must be at least 1.",
"number.max": "Port must be at most 65535.",
"any.required": "Port is required for port monitors.",
}),
otherwise: joi.optional(),
}),
name: joi.string().trim().max(50).allow("").messages({
"string.max": "This field should not exceed the 50 characters limit.",
}),
type: joi.string().trim().messages({ "string.empty": "This field is required." }),
ignoreTlsErrors: joi.boolean(),
interval: joi.number().messages({
"number.base": "Frequency must be a number.",
"any.required": "Frequency is required.",
}),
expectedValue: joi.string().allow(""),
jsonPath: joi.string().allow(""),
matchMethod: joi.string(),
});
const imageValidation = joi.object({
type: joi.string().valid("image/jpeg", "image/png").messages({
"any.only": "Invalid file format.",
"string.empty": "File type required.",
}),
size: joi
.number()
.max(3 * 1024 * 1024)
.messages({
"number.base": "File size must be a number.",
"number.max": "File size must be less than 3 MB.",
"number.empty": "File size required.",
}),
});
const logoImageValidation = joi
.object({
src: joi.string(),
name: joi.string(),
type: joi
.string()
.valid("image/jpeg", "image/png")
.allow(null) // Allow null and empty string
.messages({
"any.only": "Invalid file format.",
"string.empty": "File type required.",
})
.optional(),
size: joi
.number()
.max(3000000)
.allow(null) // Allow null and empty string
.messages({
"number.base": "File size must be a number.",
"number.max": "File size must be less than 3MB.",
"number.empty": "File size required.",
})
.optional(),
})
.allow(null)
.optional(); // Make entire object optional
const statusPageValidation = joi.object({
type: joi.string().valid("uptime", "distributed").required(),
isPublished: joi.bool(),
companyName: joi
.string()
.trim()
.messages({ "string.empty": "Company name is 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().trim().messages({ "string.empty": "Timezone is required." }),
color: joi.string().trim().messages({ "string.empty": "Color is required." }),
theme: joi.string(),
monitors: joi.array().min(1).required().messages({
"string.pattern.base": "Must be a valid monitor ID",
"array.base": "Monitors must be an array",
"array.min": "At least one monitor is required",
"array.empty": "At least one monitor is required",
"any.required": "At least one monitor is required",
}),
subMonitors: joi.array().optional(),
logo: logoImageValidation,
showUptimePercentage: joi.boolean(),
showCharts: joi.boolean(),
});
const settingsValidation = joi.object({
checkTTL: joi.number().required().messages({
"string.empty": "Please enter a value",
"number.base": "Please enter a valid number",
"any.required": "Please enter a value",
}),
pagespeedApiKey: joi.string().allow("").optional(),
language: joi.string().required(),
systemEmailHost: joi.string().allow(""),
systemEmailPort: joi.number().allow(null, ""),
systemEmailAddress: joi.string().allow(""),
systemEmailPassword: joi.string().allow(""),
systemEmailUser: joi.string().allow(""),
});
const dayjsValidator = (value, helpers) => {
if (!dayjs(value).isValid()) {
return helpers.error("any.invalid");
}
return value;
};
const maintenanceWindowValidation = joi.object({
repeat: joi.string(),
startDate: joi.custom(dayjsValidator, "Day.js date validation"),
startTime: joi.custom(dayjsValidator, "Day.js date validation"),
duration: joi.number().integer().min(0),
durationUnit: joi.string(),
name: joi.string(),
monitors: joi.array().min(1),
});
const advancedSettingsValidation = joi.object({
apiBaseUrl: joi.string().uri({ allowRelative: true }).trim().messages({
"string.empty": "API base url is required.",
"string.uri": "The URL you provided is not valid.",
}),
logLevel: joi.string().valid("debug", "none", "error", "warn").allow(""),
systemEmailHost: joi.string().allow(""),
systemEmailPort: joi.number().allow(null, ""),
systemEmailAddress: joi.string().allow(""),
systemEmailPassword: joi.string().allow(""),
jwtTTLNum: joi.number().messages({
"number.base": "JWT TTL is required.",
}),
jwtTTLUnits: joi
.string()
.trim()
.custom((value, helpers) => {
if (!["days", "hours"].includes(value)) {
return helpers.message("JWT TTL unit is required.");
}
return value;
}),
dbType: joi.string().trim().messages({
"string.empty": "DB type is required.",
}),
redisHost: joi.string().trim().messages({
"string.empty": "Redis host is required.",
}),
redisPort: joi.number().allow(null, ""),
pagespeedApiKey: joi.string().allow(""),
});
const infrastructureMonitorValidation = joi.object({
url: joi
.string()
.trim()
.custom((value, helpers) => {
const urlRegex =
/^(https?:\/\/)?(([0-9]{1,3}\.){3}[0-9]{1,3}|[\da-z\.-]+)(\.[a-z\.]{2,6})?(:(\d+))?([\/\w \.-]*)*\/?$/i;
if (!urlRegex.test(value)) {
return helpers.error("string.invalidUrl");
}
return value;
})
.messages({
"string.empty": "This field is required.",
"string.uri": "The URL you provided is not valid.",
"string.invalidUrl": "Please enter a valid URL with optional port",
}),
name: joi.string().trim().max(50).allow("").messages({
"string.max": "This field should not exceed the 50 characters limit.",
}),
secret: joi.string().trim().messages({ "string.empty": "This field is required." }),
usage_cpu: joi.number().messages({
"number.base": THRESHOLD_COMMON_BASE_MSG,
}),
cpu: joi.boolean(),
memory: joi.boolean(),
disk: joi.boolean(),
temperature: joi.boolean(),
usage_memory: joi.number().messages({
"number.base": THRESHOLD_COMMON_BASE_MSG,
}),
usage_disk: joi.number().messages({
"number.base": THRESHOLD_COMMON_BASE_MSG,
}),
usage_temperature: joi.number().messages({
"number.base": "Temperature must be a number.",
}),
name: joi.string().trim().max(50).allow("").messages({
"string.max": "This field should not exceed the 50 characters limit.",
}),
secret: joi.string().trim().messages({ "string.empty": "This field is required." }),
usage_cpu: joi.number().messages({
"number.base": THRESHOLD_COMMON_BASE_MSG,
}),
cpu: joi.boolean(),
memory: joi.boolean(),
disk: joi.boolean(),
temperature: joi.boolean(),
usage_memory: joi.number().messages({
"number.base": THRESHOLD_COMMON_BASE_MSG,
}),
usage_disk: joi.number().messages({
"number.base": THRESHOLD_COMMON_BASE_MSG,
}),
usage_temperature: joi.number().messages({
"number.base": "Temperature must be a number.",
}),
// usage_system: joi.number().messages({
// "number.base": "System load must be a number.",
// }),
// usage_swap: joi.number().messages({
// "number.base": "Swap used must be a number.",
// }),
interval: joi.number().messages({
"number.base": "Frequency must be a number.",
"any.required": "Frequency is required.",
}),
notifications: joi.array().items(
joi.object({
type: joi.string().valid("email").required(),
address: joi
.string()
.email({ tlds: { allow: false } })
.required(),
})
),
interval: joi.number().messages({
"number.base": "Frequency must be a number.",
"any.required": "Frequency is required.",
}),
notifications: joi.array().items(
joi.object({
type: joi.string().valid("email").required(),
address: joi
.string()
.email({ tlds: { allow: false } })
.required(),
})
),
});
export {
credentials,
imageValidation,
monitorValidation,
settingsValidation,
maintenanceWindowValidation,
advancedSettingsValidation,
infrastructureMonitorValidation,
statusPageValidation,
logoImageValidation,
credentials,
imageValidation,
monitorValidation,
settingsValidation,
maintenanceWindowValidation,
advancedSettingsValidation,
infrastructureMonitorValidation,
statusPageValidation,
logoImageValidation,
};