@ideal-photography/shared
Version:
Shared MongoDB and utility logic for Ideal Photography PWAs: users, products, services, bookings, orders/cart, galleries, reviews, notifications, campaigns, settings, audit logs, minimart items/orders, and push notification subscriptions.
886 lines (749 loc) • 31.5 kB
JavaScript
/**
* Product validation utilities for backend GraphQL operations
* Provides comprehensive validation for equipment rental and product data
*/
import mongoose from 'mongoose';
/**
* Validate complete product input data
* @param {Object} input - Product input data
* @param {boolean} isUpdate - Whether this is an update operation
* @returns {Object} Validation result
*/
export const validateProductInput = (input, isUpdate = false) => {
const errors = [];
const warnings = [];
try {
// Basic field validation
if (!isUpdate) {
validateRequiredFields(input, errors);
}
// Type-specific validation
if (input.type) {
validateProductType(input, errors, warnings);
}
// Pricing validation
if (input.pricing) {
validatePricing(input.pricing, errors, warnings);
}
// Inventory validation
if (input.inventory) {
validateInventory(input.inventory, input.type, errors, warnings);
}
// Specifications validation
if (input.specifications) {
validateSpecifications(input.specifications, input.category, errors, warnings);
}
// Availability rules validation
if (input.availabilityRules) {
validateAvailabilityRules(input.availabilityRules, errors, warnings);
}
// Business logic validation
validateBusinessRules(input, errors, warnings);
return {
isValid: errors.length === 0,
errors,
warnings
};
} catch (error) {
return {
isValid: false,
errors: [`Validation error: ${error.message}`],
warnings: []
};
}
};
/**
* Validate required fields based on product type
*/
function validateRequiredFields(input, errors) {
const requiredFields = ['name', 'description', 'type', 'category'];
requiredFields.forEach(field => {
if (!input[field] || (typeof input[field] === 'string' && input[field].trim() === '')) {
errors.push(`${field} is required`);
}
});
// Name length validation
if (input.name && input.name.length < 3) {
errors.push('Product name must be at least 3 characters long');
}
if (input.name && input.name.length > 100) {
errors.push('Product name must be less than 100 characters');
}
// Description length validation
if (input.description && input.description.length < 10) {
errors.push('Product description must be at least 10 characters long');
}
if (input.description && input.description.length > 2000) {
errors.push('Product description must be less than 2000 characters');
}
}
/**
* Validate product type and related requirements
*/
function validateProductType(input, errors, warnings) {
const validTypes = ['equipment_rental', 'mini_mart_sale', 'service_booking'];
if (!validTypes.includes(input.type)) {
errors.push(`Invalid product type. Must be one of: ${validTypes.join(', ')}`);
return;
}
// Type-specific requirements
switch (input.type) {
case 'equipment_rental':
validateEquipmentRentalRequirements(input, errors, warnings);
break;
case 'mini_mart_sale':
validateMinimartSaleRequirements(input, errors, warnings);
break;
case 'service_booking':
validateServiceBookingRequirements(input, errors, warnings);
break;
}
}
/**
* Validate equipment rental specific requirements - MVP minimal approach
*/
function validateEquipmentRentalRequirements(input, errors, warnings) {
// Only require baseRate for pricing - everything else can be defaulted
if (input.pricing && input.pricing.baseRate !== undefined) {
if (input.pricing.baseRate < 0) {
errors.push('Base rate cannot be negative');
}
}
// Inventory validation is now optional - system will provide defaults
if (input.inventory) {
// Only validate if inventory data is provided
if (input.inventory.totalUnits !== undefined && input.inventory.totalUnits < 1) {
errors.push('Total units must be at least 1');
}
if (input.inventory.availableUnits !== undefined && input.inventory.totalUnits !== undefined) {
if (input.inventory.availableUnits > input.inventory.totalUnits) {
errors.push('Available units cannot exceed total units');
}
}
}
// Validate equipment categories - expanded list for broader equipment support
const validEquipmentCategories = [
'cameras', 'lenses', 'tripods', 'lighting', 'audio', 'accessories',
'stabilizers', 'filters', 'memory_cards', 'batteries', 'bags',
'other', 'monitors', 'computers', 'projectors', 'microphones',
'stands', 'cables', 'adapters', 'tools', 'cases'
];
if (input.category && !validEquipmentCategories.includes(input.category)) {
errors.push(`Invalid equipment category. Must be one of: ${validEquipmentCategories.join(', ')}`);
}
// Specifications are completely optional for MVP
// No warnings for missing specifications
}
/**
* Validate minimart sale requirements
*/
function validateMinimartSaleRequirements(input, errors, warnings) {
if (!input.inventory) {
errors.push('Inventory information is required for minimart sales');
} else {
if (input.inventory.stockQuantity === undefined) {
errors.push('Stock quantity is required for minimart sales');
}
if (input.inventory.stockQuantity < 0) {
errors.push('Stock quantity cannot be negative');
}
if (input.inventory.lowStockThreshold === undefined) {
warnings.push('Low stock threshold is recommended for inventory management');
}
}
// Pricing should be simple for minimart items
if (input.pricing && input.pricing.rateType !== 'fixed') {
warnings.push('Minimart items typically use fixed pricing');
}
}
/**
* Validate service booking requirements
*/
function validateServiceBookingRequirements(input, errors, warnings) {
if (!input.pricing) {
errors.push('Pricing information is required for service bookings');
}
// Services don't need physical inventory
if (input.inventory && (input.inventory.totalUnits || input.inventory.stockQuantity)) {
warnings.push('Services typically do not require physical inventory tracking');
}
}
/**
* Validate pricing information - MVP minimal approach
*/
function validatePricing(pricing, errors, warnings) {
// Only validate baseRate if provided - it's required for equipment rentals
if (pricing.baseRate !== undefined && pricing.baseRate !== null) {
if (pricing.baseRate < 0) {
errors.push('Base rate cannot be negative');
}
if (pricing.baseRate === 0) {
warnings.push('Zero base rate - confirm this is intentional');
}
if (pricing.baseRate > 1000000) {
warnings.push('Very high base rate - please verify');
}
}
// Rate type validation - only if provided
const validRateTypes = ['hourly', 'daily', 'weekly', 'monthly', 'fixed'];
if (pricing.rateType && !validRateTypes.includes(pricing.rateType)) {
errors.push(`Invalid rate type. Must be one of: ${validRateTypes.join(', ')}`);
}
// Currency validation - only if provided
const validCurrencies = ['NGN', 'USD', 'EUR', 'GBP'];
if (pricing.currency && !validCurrencies.includes(pricing.currency)) {
warnings.push(`Unusual currency: ${pricing.currency}. Supported currencies: ${validCurrencies.join(', ')}`);
}
// Optional advanced pricing features - only validate if provided
if (pricing.securityDeposit !== undefined) {
if (pricing.securityDeposit < 0) {
errors.push('Security deposit cannot be negative');
}
}
// Discount validation - only if provided
if (pricing.discounts && Array.isArray(pricing.discounts)) {
pricing.discounts.forEach((discount, index) => {
validateDiscount(discount, index, errors, warnings);
});
}
// Seasonal pricing validation - only if provided
if (pricing.seasonalPricing && Array.isArray(pricing.seasonalPricing)) {
pricing.seasonalPricing.forEach((season, index) => {
validateSeasonalPricing(season, index, errors, warnings);
});
}
}
/**
* Validate discount information
*/
function validateDiscount(discount, index, errors, warnings) {
if (!discount.type) {
errors.push(`Discount ${index + 1}: Type is required`);
}
if (discount.value === undefined || discount.value === null) {
errors.push(`Discount ${index + 1}: Value is required`);
} else {
if (discount.value < 0) {
errors.push(`Discount ${index + 1}: Value cannot be negative`);
}
if (discount.type === 'percentage' && discount.value > 100) {
errors.push(`Discount ${index + 1}: Percentage discount cannot exceed 100%`);
}
if (discount.type === 'percentage' && discount.value > 50) {
warnings.push(`Discount ${index + 1}: High percentage discount (${discount.value}%) - please verify`);
}
}
// Date validation
if (discount.validFrom && discount.validUntil) {
const startDate = new Date(discount.validFrom);
const endDate = new Date(discount.validUntil);
if (startDate >= endDate) {
errors.push(`Discount ${index + 1}: End date must be after start date`);
}
if (endDate < new Date()) {
warnings.push(`Discount ${index + 1}: Discount has already expired`);
}
}
}
/**
* Validate seasonal pricing
*/
function validateSeasonalPricing(season, index, errors, warnings) {
if (!season.name) {
errors.push(`Seasonal pricing ${index + 1}: Name is required`);
}
if (season.multiplier === undefined || season.multiplier === null) {
errors.push(`Seasonal pricing ${index + 1}: Multiplier is required`);
} else {
if (season.multiplier <= 0) {
errors.push(`Seasonal pricing ${index + 1}: Multiplier must be positive`);
}
if (season.multiplier > 5) {
warnings.push(`Seasonal pricing ${index + 1}: Very high multiplier (${season.multiplier}x) - please verify`);
}
}
// Date format validation (MM-DD)
const dateRegex = /^(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
if (season.startDate && !dateRegex.test(season.startDate)) {
errors.push(`Seasonal pricing ${index + 1}: Start date must be in MM-DD format`);
}
if (season.endDate && !dateRegex.test(season.endDate)) {
errors.push(`Seasonal pricing ${index + 1}: End date must be in MM-DD format`);
}
}
/**
* Validate inventory information - MVP minimal approach
*/
function validateInventory(inventory, productType, errors, warnings) {
// Only validate if inventory data is provided - system will provide defaults
if (!inventory) return;
// Equipment rental inventory validation - only if provided
if (productType === 'equipment_rental') {
if (inventory.totalUnits !== undefined) {
if (inventory.totalUnits < 1) {
errors.push('Total units must be at least 1 for equipment');
}
if (inventory.totalUnits > 100) {
warnings.push('Large equipment inventory - ensure this is correct');
}
}
if (inventory.availableUnits !== undefined && inventory.totalUnits !== undefined) {
if (inventory.availableUnits > inventory.totalUnits) {
errors.push('Available units cannot exceed total units');
}
if (inventory.availableUnits < 0) {
errors.push('Available units cannot be negative');
}
}
// Serial numbers validation - only if provided
if (inventory.serialNumbers && Array.isArray(inventory.serialNumbers)) {
const uniqueSerials = new Set(inventory.serialNumbers);
if (uniqueSerials.size !== inventory.serialNumbers.length) {
errors.push('Serial numbers must be unique');
}
inventory.serialNumbers.forEach((serial, index) => {
if (!serial || typeof serial !== 'string' || serial.trim() === '') {
errors.push(`Serial number ${index + 1} cannot be empty`);
}
});
}
}
// Minimart inventory validation - only if provided
if (productType === 'mini_mart_sale') {
if (inventory.stockQuantity !== undefined) {
if (inventory.stockQuantity < 0) {
errors.push('Stock quantity cannot be negative');
}
}
if (inventory.lowStockThreshold !== undefined) {
if (inventory.lowStockThreshold < 0) {
errors.push('Low stock threshold cannot be negative');
}
if (inventory.stockQuantity !== undefined && inventory.lowStockThreshold > inventory.stockQuantity) {
warnings.push('Low stock threshold is higher than current stock quantity');
}
}
}
// Condition validation - only if provided
const validConditions = ['excellent', 'good', 'fair', 'needs_repair'];
if (inventory.condition && !validConditions.includes(inventory.condition)) {
errors.push(`Invalid condition. Must be one of: ${validConditions.join(', ')}`);
}
if (inventory.condition === 'needs_repair' && productType === 'equipment_rental') {
warnings.push('Equipment marked as needing repair - ensure it is not available for rental');
}
}
/**
* Validate product specifications
*/
function validateSpecifications(specifications, category, errors, warnings) {
// Brand validation
if (specifications.brand) {
if (specifications.brand.length < 2) {
errors.push('Brand name must be at least 2 characters long');
}
if (specifications.brand.length > 50) {
errors.push('Brand name must be less than 50 characters');
}
}
// Model validation
if (specifications.model) {
if (specifications.model.length > 100) {
errors.push('Model name must be less than 100 characters');
}
}
// Year validation
if (specifications.year !== undefined) {
const currentYear = new Date().getFullYear();
if (specifications.year < 1900 || specifications.year > currentYear + 1) {
errors.push(`Year must be between 1900 and ${currentYear + 1}`);
}
if (specifications.year < currentYear - 20) {
warnings.push('Very old equipment - consider noting age in description');
}
}
// Category-specific validations
if (category === 'cameras') {
validateCameraSpecs(specifications, errors, warnings);
} else if (category === 'lenses') {
validateLensSpecs(specifications, errors, warnings);
}
// Dimensions validation
if (specifications.dimensions) {
validateDimensions(specifications.dimensions, errors, warnings);
}
// Custom specs validation
if (specifications.customSpecs && Array.isArray(specifications.customSpecs)) {
specifications.customSpecs.forEach((spec, index) => {
if (!spec.name || !spec.value) {
errors.push(`Custom specification ${index + 1}: Name and value are required`);
}
});
}
}
/**
* Validate camera-specific specifications
*/
function validateCameraSpecs(specs, errors, warnings) {
if (specs.megapixels !== undefined) {
if (specs.megapixels < 1 || specs.megapixels > 200) {
errors.push('Megapixels must be between 1 and 200');
}
if (specs.megapixels < 10) {
warnings.push('Low megapixel count - ensure this is accurate for modern equipment');
}
}
if (specs.sensor) {
const validSensorTypes = ['full-frame', 'aps-c', 'micro-four-thirds', 'medium-format', '35mm'];
const sensorLower = specs.sensor.toLowerCase();
if (!validSensorTypes.some(type => sensorLower.includes(type))) {
warnings.push('Unusual sensor type - please verify accuracy');
}
}
}
/**
* Validate lens-specific specifications
*/
function validateLensSpecs(specs, errors, warnings) {
if (specs.focalLength) {
// Basic focal length format validation
const focalLengthPattern = /^\d+(-\d+)?(mm)?$/i;
if (!focalLengthPattern.test(specs.focalLength.replace(/\s/g, ''))) {
warnings.push('Focal length format may be incorrect (expected format: "50mm" or "24-70mm")');
}
}
if (specs.aperture) {
// Basic aperture format validation
const aperturePattern = /^f\/?\d+(\.\d+)?(-\d+(\.\d+)?)?$/i;
if (!aperturePattern.test(specs.aperture.replace(/\s/g, ''))) {
warnings.push('Aperture format may be incorrect (expected format: "f/1.8" or "f/2.8-5.6")');
}
}
}
/**
* Validate dimensions
*/
function validateDimensions(dimensions, errors, warnings) {
['length', 'width', 'height'].forEach(dimension => {
if (dimensions[dimension] !== undefined) {
if (dimensions[dimension] < 0) {
errors.push(`${dimension} cannot be negative`);
}
if (dimensions[dimension] > 1000) {
warnings.push(`Very large ${dimension} (${dimensions[dimension]}cm) - please verify`);
}
}
});
if (!dimensions.unit) {
warnings.push('Dimension unit not specified - defaulting to cm');
} else {
const validUnits = ['cm', 'mm', 'in', 'm'];
if (!validUnits.includes(dimensions.unit)) {
errors.push(`Invalid dimension unit. Must be one of: ${validUnits.join(', ')}`);
}
}
}
/**
* Validate availability rules
*/
function validateAvailabilityRules(rules, errors, warnings) {
// Rental period validation
if (rules.minRentalPeriod !== undefined) {
if (rules.minRentalPeriod < 1) {
errors.push('Minimum rental period must be at least 1 hour');
}
}
if (rules.maxRentalPeriod !== undefined) {
if (rules.maxRentalPeriod < 1) {
errors.push('Maximum rental period must be at least 1 hour');
}
if (rules.minRentalPeriod && rules.maxRentalPeriod < rules.minRentalPeriod) {
errors.push('Maximum rental period cannot be less than minimum rental period');
}
if (rules.maxRentalPeriod > 8760) { // 365 days
warnings.push('Very long maximum rental period (over 1 year) - please verify');
}
}
if (rules.advanceBookingRequired !== undefined) {
if (rules.advanceBookingRequired < 0) {
errors.push('Advance booking requirement cannot be negative');
}
if (rules.advanceBookingRequired > 8760) {
warnings.push('Very long advance booking requirement - please verify');
}
}
// Blackout dates validation
if (rules.blackoutDates && Array.isArray(rules.blackoutDates)) {
rules.blackoutDates.forEach((date, index) => {
try {
const blackoutDate = new Date(date);
if (isNaN(blackoutDate.getTime())) {
errors.push(`Blackout date ${index + 1}: Invalid date format`);
} else if (blackoutDate < new Date()) {
warnings.push(`Blackout date ${index + 1}: Date is in the past`);
}
} catch (error) {
errors.push(`Blackout date ${index + 1}: Invalid date`);
}
});
}
// Maintenance schedule validation
if (rules.maintenanceSchedule && Array.isArray(rules.maintenanceSchedule)) {
rules.maintenanceSchedule.forEach((schedule, index) => {
validateMaintenanceSchedule(schedule, index, errors, warnings);
});
}
}
/**
* Validate maintenance schedule
*/
function validateMaintenanceSchedule(schedule, index, errors, warnings) {
if (!schedule.startDate) {
errors.push(`Maintenance schedule ${index + 1}: Start date is required`);
}
if (!schedule.endDate) {
errors.push(`Maintenance schedule ${index + 1}: End date is required`);
}
if (schedule.startDate && schedule.endDate) {
const startDate = new Date(schedule.startDate);
const endDate = new Date(schedule.endDate);
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
errors.push(`Maintenance schedule ${index + 1}: Invalid date format`);
} else {
if (startDate >= endDate) {
errors.push(`Maintenance schedule ${index + 1}: End date must be after start date`);
}
if (endDate < new Date()) {
warnings.push(`Maintenance schedule ${index + 1}: Maintenance period is in the past`);
}
const duration = (endDate - startDate) / (1000 * 60 * 60 * 24); // days
if (duration > 365) {
warnings.push(`Maintenance schedule ${index + 1}: Very long maintenance period (${Math.round(duration)} days)`);
}
}
}
if (!schedule.reason) {
warnings.push(`Maintenance schedule ${index + 1}: Reason not provided`);
}
if (schedule.recurring && !schedule.interval) {
errors.push(`Maintenance schedule ${index + 1}: Interval required for recurring maintenance`);
}
if (schedule.interval) {
const validIntervals = ['daily', 'weekly', 'monthly', 'yearly'];
if (!validIntervals.includes(schedule.interval)) {
errors.push(`Maintenance schedule ${index + 1}: Invalid interval. Must be one of: ${validIntervals.join(', ')}`);
}
}
}
/**
* Validate business rules and logic
*/
function validateBusinessRules(input, errors, warnings) {
// SKU validation
if (input.sku) {
const skuPattern = /^[A-Z]{2,3}-[A-Z]{2,3}-\d{6}-[A-Z0-9]{3}$/;
if (!skuPattern.test(input.sku)) {
warnings.push('SKU format does not match standard pattern (XX-XXX-XXXXXX-XXX)');
}
}
// Tags validation
if (input.tags && Array.isArray(input.tags)) {
if (input.tags.length > 20) {
warnings.push('Many tags specified - consider reducing for better organization');
}
input.tags.forEach((tag, index) => {
if (!tag || typeof tag !== 'string' || tag.trim() === '') {
errors.push(`Tag ${index + 1} is invalid`);
} else if (tag.length > 50) {
errors.push(`Tag ${index + 1} is too long (max 50 characters)`);
}
});
// Check for duplicate tags
const uniqueTags = new Set(input.tags.map(tag => tag.toLowerCase()));
if (uniqueTags.size !== input.tags.length) {
warnings.push('Duplicate tags detected');
}
}
// Features validation
if (input.features && Array.isArray(input.features)) {
if (input.features.length > 50) {
warnings.push('Many features listed - consider grouping similar features');
}
input.features.forEach((feature, index) => {
if (!feature || typeof feature !== 'string' || feature.trim() === '') {
errors.push(`Feature ${index + 1} is invalid`);
} else if (feature.length > 200) {
errors.push(`Feature ${index + 1} is too long (max 200 characters)`);
}
});
}
// What's included validation
if (input.whatsIncluded && Array.isArray(input.whatsIncluded)) {
input.whatsIncluded.forEach((item, index) => {
if (!item || typeof item !== 'string' || item.trim() === '') {
errors.push(`What's included item ${index + 1} is invalid`);
}
});
}
// Requirements validation
if (input.requirements && Array.isArray(input.requirements)) {
input.requirements.forEach((requirement, index) => {
if (!requirement || typeof requirement !== 'string' || requirement.trim() === '') {
errors.push(`Requirement ${index + 1} is invalid`);
}
});
}
// Usage instructions validation
if (input.usageInstructions) {
if (input.usageInstructions.length > 5000) {
errors.push('Usage instructions are too long (max 5000 characters)');
}
}
}
/**
* Validate MongoDB ObjectId
* @param {string} id - ID to validate
* @returns {boolean} Whether the ID is valid
*/
export const validateObjectId = (id) => {
return mongoose.Types.ObjectId.isValid(id);
};
/**
* Validate product availability for booking
* @param {Object} product - Product object
* @param {Date} startDate - Booking start date
* @param {Date} endDate - Booking end date
* @param {number} quantity - Requested quantity
* @returns {Object} Availability validation result
*/
export const validateProductAvailability = (product, startDate, endDate, quantity = 1) => {
const errors = [];
const warnings = [];
try {
// Basic product checks
if (!product.isActive) {
errors.push('Product is not active');
}
if (product.type !== 'equipment_rental') {
errors.push('Product is not available for rental');
}
// Inventory checks
if (product.inventory.availableUnits < quantity) {
errors.push(`Not enough units available (requested: ${quantity}, available: ${product.inventory.availableUnits})`);
}
// Date validation
const now = new Date();
if (startDate <= now) {
errors.push('Start date must be in the future');
}
if (endDate <= startDate) {
errors.push('End date must be after start date');
}
// Availability rules checks
if (product.availabilityRules) {
const rules = product.availabilityRules;
// Check minimum rental period
if (rules.minRentalPeriod) {
const rentalHours = (endDate - startDate) / (1000 * 60 * 60);
if (rentalHours < rules.minRentalPeriod) {
errors.push(`Rental period too short (minimum: ${rules.minRentalPeriod} hours)`);
}
}
// Check maximum rental period
if (rules.maxRentalPeriod) {
const rentalHours = (endDate - startDate) / (1000 * 60 * 60);
if (rentalHours > rules.maxRentalPeriod) {
errors.push(`Rental period too long (maximum: ${rules.maxRentalPeriod} hours)`);
}
}
// Check advance booking requirement
if (rules.advanceBookingRequired) {
const hoursUntilStart = (startDate - now) / (1000 * 60 * 60);
if (hoursUntilStart < rules.advanceBookingRequired) {
errors.push(`Booking must be made at least ${rules.advanceBookingRequired} hours in advance`);
}
}
// Check blackout dates
if (rules.blackoutDates && Array.isArray(rules.blackoutDates)) {
const hasBlackoutConflict = rules.blackoutDates.some(blackoutDate => {
const blackout = new Date(blackoutDate);
return blackout >= startDate && blackout <= endDate;
});
if (hasBlackoutConflict) {
errors.push('Requested dates conflict with blackout periods');
}
}
// Check maintenance schedule
if (rules.maintenanceSchedule && Array.isArray(rules.maintenanceSchedule)) {
const hasMaintenanceConflict = rules.maintenanceSchedule.some(schedule => {
const maintenanceStart = new Date(schedule.startDate);
const maintenanceEnd = new Date(schedule.endDate);
return (maintenanceStart <= endDate && maintenanceEnd >= startDate);
});
if (hasMaintenanceConflict) {
errors.push('Requested dates conflict with scheduled maintenance');
}
}
}
// Condition check
if (product.inventory.condition === 'needs_repair') {
errors.push('Equipment needs repair and is not available for rental');
}
if (product.inventory.condition === 'fair') {
warnings.push('Equipment condition is fair - customer should be informed');
}
return {
isAvailable: errors.length === 0,
errors,
warnings
};
} catch (error) {
return {
isAvailable: false,
errors: [`Availability validation error: ${error.message}`],
warnings: []
};
}
};
/**
* Validate image upload data
* @param {Array} images - Array of image upload objects
* @returns {Object} Validation result
*/
export const validateImageUploads = (images) => {
const errors = [];
const warnings = [];
if (!Array.isArray(images)) {
errors.push('Images must be provided as an array');
return { isValid: false, errors, warnings };
}
if (images.length === 0) {
warnings.push('No images provided');
return { isValid: true, errors, warnings };
}
if (images.length > 3) {
errors.push('Maximum 3 images allowed per product');
}
images.forEach((image, index) => {
if (!image.file) {
errors.push(`Image ${index + 1}: File is required`);
}
if (image.alt && image.alt.length > 200) {
warnings.push(`Image ${index + 1}: Alt text is very long`);
}
if (image.order !== undefined && (image.order < 0 || image.order > 10)) {
errors.push(`Image ${index + 1}: Order must be between 0 and 10`);
}
});
return {
isValid: errors.length === 0,
errors,
warnings
};
};
export default {
validateProductInput,
validateObjectId,
validateProductAvailability,
validateImageUploads
};