const { body, param, query, validationResult } = require("express-validator"); const logger = require("../config/logger"); // Validation error handler middleware const handleValidationErrors = (req, res, next) => { const errors = validationResult(req); if (!errors.isEmpty()) { logger.warn("Validation error", { path: req.path, errors: errors.array(), body: req.body, }); return res.status(400).json({ success: false, message: "Validation failed", errors: errors.array().map((err) => ({ field: err.param, message: err.msg, })), }); } next(); }; // Common validation rules const validators = { // Auth validators login: [ body("email") .isEmail() .withMessage("Valid email is required") .normalizeEmail() .trim(), body("password") .isLength({ min: 8 }) .withMessage("Password must be at least 8 characters"), ], // User validators createUser: [ body("email") .isEmail() .withMessage("Valid email is required") .normalizeEmail() .trim(), body("username") .isLength({ min: 3, max: 50 }) .matches(/^[a-zA-Z0-9_-]+$/) .withMessage( "Username must be 3-50 characters and contain only letters, numbers, hyphens, and underscores" ) .trim(), body("password") .isLength({ min: 8 }) .matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/) .withMessage( "Password must be at least 8 characters with uppercase, lowercase, and number" ), body("role_id").notEmpty().withMessage("Role is required").trim(), ], updateUser: [ param("id") .matches(/^user-[a-f0-9-]+$/) .withMessage("Invalid user ID format"), body("email") .optional() .isEmail() .withMessage("Valid email is required") .normalizeEmail() .trim(), body("username") .optional() .isLength({ min: 3, max: 50 }) .withMessage("Username must be 3-50 characters") .matches(/^[a-zA-Z0-9_-]+$/) .trim(), ], // Product validators createProduct: [ body("name") .isLength({ min: 1, max: 255 }) .withMessage("Product name is required (max 255 characters)") .trim() .escape(), body("description") .optional() .isString() .withMessage("Description must be text") .trim(), body("price") .isFloat({ min: 0 }) .withMessage("Price must be a positive number"), body("stockquantity") .optional() .isInt({ min: 0 }) .withMessage("Stock quantity must be a non-negative integer"), body("category") .optional() .isString() .withMessage("Category must be text") .trim() .escape(), ], updateProduct: [ param("id").isUUID().withMessage("Invalid product ID"), body("name") .optional() .isLength({ min: 1, max: 255 }) .withMessage("Product name must be 1-255 characters") .trim() .escape(), body("price") .optional() .isFloat({ min: 0 }) .withMessage("Price must be a positive number"), body("stockquantity") .optional() .isInt({ min: 0 }) .withMessage("Stock quantity must be a non-negative integer"), ], // Blog validators createBlogPost: [ body("title") .isLength({ min: 1, max: 255 }) .withMessage("Title is required (max 255 characters)") .trim() .escape(), body("slug") .isLength({ min: 1, max: 255 }) .matches(/^[a-z0-9-]+$/) .withMessage( "Slug must contain only lowercase letters, numbers, and hyphens" ) .trim(), body("content").notEmpty().withMessage("Content is required").trim(), ], // Generic ID validator idParam: [param("id").notEmpty().withMessage("ID is required").trim()], // Pagination validators pagination: [ query("page") .optional() .isInt({ min: 1 }) .withMessage("Page must be a positive integer"), query("limit") .optional() .isInt({ min: 1, max: 100 }) .withMessage("Limit must be between 1 and 100"), ], }; module.exports = { validators, handleValidationErrors, };