webupdate
This commit is contained in:
245
backend/utils/validation.js
Normal file
245
backend/utils/validation.js
Normal file
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* Input Validation Utilities
|
||||
* Reusable validation functions with consistent error messages
|
||||
*/
|
||||
|
||||
const { AppError } = require("../middleware/errorHandler");
|
||||
const { HTTP_STATUS } = require("../config/constants");
|
||||
|
||||
/**
|
||||
* Validate required fields
|
||||
* @param {Object} data - Data object to validate
|
||||
* @param {string[]} requiredFields - Array of required field names
|
||||
* @throws {AppError} If validation fails
|
||||
*/
|
||||
const validateRequiredFields = (data, requiredFields) => {
|
||||
const missingFields = requiredFields.filter(
|
||||
(field) =>
|
||||
!data[field] ||
|
||||
(typeof data[field] === "string" && data[field].trim() === ""),
|
||||
);
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
throw new AppError(
|
||||
`Missing required fields: ${missingFields.join(", ")}`,
|
||||
HTTP_STATUS.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate email format
|
||||
* @param {string} email - Email to validate
|
||||
* @returns {boolean} True if valid
|
||||
*/
|
||||
const isValidEmail = (email) => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate email field
|
||||
* @param {string} email - Email to validate
|
||||
* @throws {AppError} If validation fails
|
||||
*/
|
||||
const validateEmail = (email) => {
|
||||
if (!email || !isValidEmail(email)) {
|
||||
throw new AppError("Invalid email format", HTTP_STATUS.BAD_REQUEST);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate UUID format
|
||||
* @param {string} id - UUID to validate
|
||||
* @returns {boolean} True if valid UUID
|
||||
*/
|
||||
const isValidUUID = (id) => {
|
||||
const uuidRegex =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
return uuidRegex.test(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate number range
|
||||
* @param {number} value - Value to validate
|
||||
* @param {number} min - Minimum value
|
||||
* @param {number} max - Maximum value
|
||||
* @param {string} fieldName - Field name for error message
|
||||
* @throws {AppError} If validation fails
|
||||
*/
|
||||
const validateNumberRange = (value, min, max, fieldName = "Value") => {
|
||||
const num = parseFloat(value);
|
||||
if (isNaN(num) || num < min || num > max) {
|
||||
throw new AppError(
|
||||
`${fieldName} must be between ${min} and ${max}`,
|
||||
HTTP_STATUS.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
return num;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate string length
|
||||
* @param {string} value - String to validate
|
||||
* @param {number} min - Minimum length
|
||||
* @param {number} max - Maximum length
|
||||
* @param {string} fieldName - Field name for error message
|
||||
* @throws {AppError} If validation fails
|
||||
*/
|
||||
const validateStringLength = (value, min, max, fieldName = "Field") => {
|
||||
if (!value || value.length < min || value.length > max) {
|
||||
throw new AppError(
|
||||
`${fieldName} must be between ${min} and ${max} characters`,
|
||||
HTTP_STATUS.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sanitize string input (remove HTML tags, trim)
|
||||
* @param {string} input - String to sanitize
|
||||
* @returns {string} Sanitized string
|
||||
*/
|
||||
const sanitizeString = (input) => {
|
||||
if (typeof input !== "string") return "";
|
||||
return input
|
||||
.replace(/<[^>]*>/g, "") // Remove HTML tags
|
||||
.trim();
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate and sanitize slug
|
||||
* @param {string} slug - Slug to validate
|
||||
* @returns {string} Valid slug
|
||||
* @throws {AppError} If validation fails
|
||||
*/
|
||||
const validateSlug = (slug) => {
|
||||
const slugRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
||||
const sanitized = slug.toLowerCase().trim();
|
||||
|
||||
if (!slugRegex.test(sanitized)) {
|
||||
throw new AppError(
|
||||
"Slug can only contain lowercase letters, numbers, and hyphens",
|
||||
HTTP_STATUS.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate slug from string
|
||||
* @param {string} text - Text to convert to slug
|
||||
* @returns {string} Generated slug
|
||||
*/
|
||||
const generateSlug = (text) => {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9\s-]/g, "")
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/-+/g, "-")
|
||||
.trim();
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate pagination parameters
|
||||
* @param {Object} query - Query parameters
|
||||
* @returns {Object} Validated pagination params
|
||||
*/
|
||||
const validatePagination = (query) => {
|
||||
const page = Math.max(1, parseInt(query.page) || 1);
|
||||
const limit = Math.min(100, Math.max(1, parseInt(query.limit) || 20));
|
||||
|
||||
return { page, limit };
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate image file
|
||||
* @param {Object} file - Multer file object
|
||||
* @throws {AppError} If validation fails
|
||||
*/
|
||||
const validateImageFile = (file) => {
|
||||
const allowedMimeTypes = [
|
||||
"image/jpeg",
|
||||
"image/jpg",
|
||||
"image/png",
|
||||
"image/gif",
|
||||
"image/webp",
|
||||
"image/bmp",
|
||||
"image/tiff",
|
||||
"image/svg+xml",
|
||||
"image/x-icon",
|
||||
"image/vnd.microsoft.icon",
|
||||
"image/ico",
|
||||
"image/avif",
|
||||
"image/heic",
|
||||
"image/heif",
|
||||
];
|
||||
const maxSize = 10 * 1024 * 1024; // 10MB for larger image formats
|
||||
|
||||
if (!file) {
|
||||
throw new AppError("No file provided", HTTP_STATUS.BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (!allowedMimeTypes.includes(file.mimetype)) {
|
||||
throw new AppError(
|
||||
"Invalid file type. Allowed: JPEG, PNG, GIF, WebP, BMP, TIFF, SVG, ICO, AVIF, HEIC",
|
||||
HTTP_STATUS.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
if (file.size > maxSize) {
|
||||
throw new AppError(
|
||||
"File too large. Maximum size is 5MB",
|
||||
HTTP_STATUS.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate price value
|
||||
* @param {number} price - Price to validate
|
||||
* @param {string} fieldName - Field name for error message
|
||||
* @returns {number} Validated price
|
||||
* @throws {AppError} If validation fails
|
||||
*/
|
||||
const validatePrice = (price, fieldName = "Price") => {
|
||||
return validateNumberRange(price, 0, 999999, fieldName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate stock quantity
|
||||
* @param {number} stock - Stock to validate
|
||||
* @returns {number} Validated stock
|
||||
* @throws {AppError} If validation fails
|
||||
*/
|
||||
const validateStock = (stock) => {
|
||||
return validateNumberRange(stock, 0, 999999, "Stock quantity");
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate color code (hex format)
|
||||
* @param {string} colorCode - Color code to validate
|
||||
* @returns {boolean} True if valid
|
||||
*/
|
||||
const isValidColorCode = (colorCode) => {
|
||||
const hexRegex = /^#[0-9A-F]{6}$/i;
|
||||
return hexRegex.test(colorCode);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
validateRequiredFields,
|
||||
validateEmail,
|
||||
isValidEmail,
|
||||
isValidUUID,
|
||||
validateNumberRange,
|
||||
validateStringLength,
|
||||
sanitizeString,
|
||||
validateSlug,
|
||||
generateSlug,
|
||||
validatePagination,
|
||||
validateImageFile,
|
||||
validatePrice,
|
||||
validateStock,
|
||||
isValidColorCode,
|
||||
};
|
||||
Reference in New Issue
Block a user