310 lines
8.6 KiB
JavaScript
310 lines
8.6 KiB
JavaScript
/**
|
|
* Validation middleware for various data types
|
|
*/
|
|
|
|
const { body, validationResult } = require('express-validator');
|
|
|
|
/**
|
|
* Validation error handler
|
|
*/
|
|
const handleValidationErrors = (req, res, next) => {
|
|
const errors = validationResult(req);
|
|
|
|
if (!errors.isEmpty()) {
|
|
// For API requests
|
|
if (req.xhr || req.headers.accept?.includes('application/json')) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Validation failed',
|
|
errors: errors.array()
|
|
});
|
|
}
|
|
|
|
// For web requests
|
|
const errorMessages = errors.array().map(error => error.msg);
|
|
req.flash('error', errorMessages);
|
|
return res.redirect('back');
|
|
}
|
|
|
|
next();
|
|
};
|
|
|
|
/**
|
|
* Contact form validation
|
|
*/
|
|
const validateContactForm = [
|
|
body('name')
|
|
.trim()
|
|
.isLength({ min: 2, max: 50 })
|
|
.withMessage('이름은 2-50자 사이여야 합니다.')
|
|
.matches(/^[a-zA-Z가-힣\s]+$/)
|
|
.withMessage('이름에는 한글, 영문, 공백만 사용할 수 있습니다.'),
|
|
|
|
body('email')
|
|
.isEmail()
|
|
.withMessage('유효한 이메일 주소를 입력해주세요.')
|
|
.normalizeEmail(),
|
|
|
|
body('phone')
|
|
.optional()
|
|
.matches(/^[0-9\-\+\(\)\s]+$/)
|
|
.withMessage('유효한 전화번호를 입력해주세요.'),
|
|
|
|
body('company')
|
|
.optional()
|
|
.trim()
|
|
.isLength({ max: 100 })
|
|
.withMessage('회사명은 100자 이하여야 합니다.'),
|
|
|
|
body('service')
|
|
.isIn(['web-development', 'mobile-app', 'ui-ux-design', 'branding', 'digital-marketing', 'consulting', 'other'])
|
|
.withMessage('유효한 서비스를 선택해주세요.'),
|
|
|
|
body('budget')
|
|
.optional()
|
|
.isIn(['under-500', '500-1000', '1000-3000', '3000-5000', '5000-10000', 'over-10000', 'discuss'])
|
|
.withMessage('유효한 예산 범위를 선택해주세요.'),
|
|
|
|
body('message')
|
|
.trim()
|
|
.isLength({ min: 10, max: 2000 })
|
|
.withMessage('메시지는 10-2000자 사이여야 합니다.'),
|
|
|
|
handleValidationErrors
|
|
];
|
|
|
|
/**
|
|
* User registration validation
|
|
*/
|
|
const validateRegistration = [
|
|
body('name')
|
|
.trim()
|
|
.isLength({ min: 2, max: 50 })
|
|
.withMessage('이름은 2-50자 사이여야 합니다.')
|
|
.matches(/^[a-zA-Z가-힣\s]+$/)
|
|
.withMessage('이름에는 한글, 영문, 공백만 사용할 수 있습니다.'),
|
|
|
|
body('email')
|
|
.isEmail()
|
|
.withMessage('유효한 이메일 주소를 입력해주세요.')
|
|
.normalizeEmail(),
|
|
|
|
body('password')
|
|
.isLength({ min: 8 })
|
|
.withMessage('비밀번호는 최소 8자 이상이어야 합니다.')
|
|
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/)
|
|
.withMessage('비밀번호는 대소문자, 숫자, 특수문자를 포함해야 합니다.'),
|
|
|
|
body('confirmPassword')
|
|
.custom((value, { req }) => {
|
|
if (value !== req.body.password) {
|
|
throw new Error('비밀번호 확인이 일치하지 않습니다.');
|
|
}
|
|
return true;
|
|
}),
|
|
|
|
handleValidationErrors
|
|
];
|
|
|
|
/**
|
|
* User login validation
|
|
*/
|
|
const validateLogin = [
|
|
body('email')
|
|
.isEmail()
|
|
.withMessage('유효한 이메일 주소를 입력해주세요.')
|
|
.normalizeEmail(),
|
|
|
|
body('password')
|
|
.isLength({ min: 1 })
|
|
.withMessage('비밀번호를 입력해주세요.'),
|
|
|
|
handleValidationErrors
|
|
];
|
|
|
|
/**
|
|
* Portfolio validation
|
|
*/
|
|
const validatePortfolio = [
|
|
body('title')
|
|
.trim()
|
|
.isLength({ min: 2, max: 100 })
|
|
.withMessage('제목은 2-100자 사이여야 합니다.'),
|
|
|
|
body('description')
|
|
.trim()
|
|
.isLength({ min: 10, max: 5000 })
|
|
.withMessage('설명은 10-5000자 사이여야 합니다.'),
|
|
|
|
body('shortDescription')
|
|
.optional()
|
|
.trim()
|
|
.isLength({ max: 200 })
|
|
.withMessage('간단한 설명은 200자 이하여야 합니다.'),
|
|
|
|
body('category')
|
|
.isIn(['web-development', 'mobile-app', 'ui-ux-design', 'branding', 'marketing'])
|
|
.withMessage('유효한 카테고리를 선택해주세요.'),
|
|
|
|
body('technologies')
|
|
.optional()
|
|
.isArray()
|
|
.withMessage('기술 스택은 배열이어야 합니다.'),
|
|
|
|
body('clientName')
|
|
.optional()
|
|
.trim()
|
|
.isLength({ max: 100 })
|
|
.withMessage('클라이언트 이름은 100자 이하여야 합니다.'),
|
|
|
|
body('projectUrl')
|
|
.optional()
|
|
.isURL()
|
|
.withMessage('유효한 URL을 입력해주세요.'),
|
|
|
|
body('status')
|
|
.optional()
|
|
.isIn(['planning', 'in-progress', 'completed', 'on-hold'])
|
|
.withMessage('유효한 상태를 선택해주세요.'),
|
|
|
|
handleValidationErrors
|
|
];
|
|
|
|
/**
|
|
* Service validation
|
|
*/
|
|
const validateService = [
|
|
body('name')
|
|
.trim()
|
|
.isLength({ min: 2, max: 100 })
|
|
.withMessage('서비스명은 2-100자 사이여야 합니다.'),
|
|
|
|
body('description')
|
|
.trim()
|
|
.isLength({ min: 10, max: 5000 })
|
|
.withMessage('설명은 10-5000자 사이여야 합니다.'),
|
|
|
|
body('shortDescription')
|
|
.optional()
|
|
.trim()
|
|
.isLength({ max: 200 })
|
|
.withMessage('간단한 설명은 200자 이하여야 합니다.'),
|
|
|
|
body('category')
|
|
.isIn(['development', 'design', 'marketing', 'consulting'])
|
|
.withMessage('유효한 카테고리를 선택해주세요.'),
|
|
|
|
body('pricing.basePrice')
|
|
.optional()
|
|
.isNumeric()
|
|
.withMessage('기본 가격은 숫자여야 합니다.'),
|
|
|
|
body('pricing.priceType')
|
|
.optional()
|
|
.isIn(['project', 'hourly', 'monthly'])
|
|
.withMessage('유효한 가격 유형을 선택해주세요.'),
|
|
|
|
handleValidationErrors
|
|
];
|
|
|
|
/**
|
|
* Calculator validation
|
|
*/
|
|
const validateCalculator = [
|
|
body('service')
|
|
.isMongoId()
|
|
.withMessage('유효한 서비스를 선택해주세요.'),
|
|
|
|
body('projectType')
|
|
.optional()
|
|
.isIn(['simple', 'medium', 'complex', 'enterprise'])
|
|
.withMessage('유효한 프로젝트 유형을 선택해주세요.'),
|
|
|
|
body('timeline')
|
|
.optional()
|
|
.isIn(['urgent', 'normal', 'flexible'])
|
|
.withMessage('유효한 타임라인을 선택해주세요.'),
|
|
|
|
body('features')
|
|
.optional()
|
|
.isArray()
|
|
.withMessage('기능은 배열이어야 합니다.'),
|
|
|
|
body('contactInfo.name')
|
|
.optional()
|
|
.trim()
|
|
.isLength({ min: 2, max: 50 })
|
|
.withMessage('이름은 2-50자 사이여야 합니다.'),
|
|
|
|
body('contactInfo.email')
|
|
.optional()
|
|
.isEmail()
|
|
.withMessage('유효한 이메일 주소를 입력해주세요.'),
|
|
|
|
body('contactInfo.phone')
|
|
.optional()
|
|
.matches(/^[0-9\-\+\(\)\s]+$/)
|
|
.withMessage('유효한 전화번호를 입력해주세요.'),
|
|
|
|
handleValidationErrors
|
|
];
|
|
|
|
/**
|
|
* Settings validation
|
|
*/
|
|
const validateSettings = [
|
|
body('siteName')
|
|
.optional()
|
|
.trim()
|
|
.isLength({ min: 1, max: 100 })
|
|
.withMessage('사이트명은 1-100자 사이여야 합니다.'),
|
|
|
|
body('siteDescription')
|
|
.optional()
|
|
.trim()
|
|
.isLength({ max: 500 })
|
|
.withMessage('사이트 설명은 500자 이하여야 합니다.'),
|
|
|
|
body('contact.email')
|
|
.optional()
|
|
.isEmail()
|
|
.withMessage('유효한 이메일 주소를 입력해주세요.'),
|
|
|
|
body('contact.phone')
|
|
.optional()
|
|
.matches(/^[0-9\-\+\(\)\s]+$/)
|
|
.withMessage('유효한 전화번호를 입력해주세요.'),
|
|
|
|
body('social.facebook')
|
|
.optional()
|
|
.isURL()
|
|
.withMessage('유효한 Facebook URL을 입력해주세요.'),
|
|
|
|
body('social.twitter')
|
|
.optional()
|
|
.isURL()
|
|
.withMessage('유효한 Twitter URL을 입력해주세요.'),
|
|
|
|
body('social.linkedin')
|
|
.optional()
|
|
.isURL()
|
|
.withMessage('유효한 LinkedIn URL을 입력해주세요.'),
|
|
|
|
body('social.instagram')
|
|
.optional()
|
|
.isURL()
|
|
.withMessage('유효한 Instagram URL을 입력해주세요.'),
|
|
|
|
handleValidationErrors
|
|
];
|
|
|
|
module.exports = {
|
|
handleValidationErrors,
|
|
validateContactForm,
|
|
validateRegistration,
|
|
validateLogin,
|
|
validatePortfolio,
|
|
validateService,
|
|
validateCalculator,
|
|
validateSettings
|
|
}; |