feat: add message content length validation
TaxBaik CI/CD / build-and-deploy (push) Failing after 2m21s
TaxBaik CI/CD / build-and-deploy (push) Failing after 2m21s
- Backend: MinMessageLength=10, MaxMessageLength=5000 - Frontend: Real-time character counter - Frontend: Client-side validation before submission - Frontend: Error messages for length violations - Applied to both Submit and Update operations Prevents empty or excessively long messages while maintaining user-friendly feedback on character count. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,9 @@ public class InquiryService(
|
|||||||
private static readonly Regex PhoneRegex = new(
|
private static readonly Regex PhoneRegex = new(
|
||||||
@"^(0(?:2|3[1-3]|4[1-4]|5[1-5]|6[1-4]|70|50[5-9]|[7-9](?:\d{1,2})?)\d{7,8}|0\d{9,10})$");
|
@"^(0(?:2|3[1-3]|4[1-4]|5[1-5]|6[1-4]|70|50[5-9]|[7-9](?:\d{1,2})?)\d{7,8}|0\d{9,10})$");
|
||||||
|
|
||||||
|
private const int MinMessageLength = 10;
|
||||||
|
private const int MaxMessageLength = 5000;
|
||||||
|
|
||||||
public async Task<int> SubmitAsync(
|
public async Task<int> SubmitAsync(
|
||||||
string name, string phone, string serviceType, string message,
|
string name, string phone, string serviceType, string message,
|
||||||
string? email = null, string? ipAddress = null, bool suppressNotification = false, CancellationToken ct = default)
|
string? email = null, string? ipAddress = null, bool suppressNotification = false, CancellationToken ct = default)
|
||||||
@@ -34,13 +37,20 @@ public class InquiryService(
|
|||||||
if (string.IsNullOrWhiteSpace(message))
|
if (string.IsNullOrWhiteSpace(message))
|
||||||
throw new ValidationException("문의 내용을 입력하세요.");
|
throw new ValidationException("문의 내용을 입력하세요.");
|
||||||
|
|
||||||
|
var trimmedMessage = message.Trim();
|
||||||
|
if (trimmedMessage.Length < MinMessageLength)
|
||||||
|
throw new ValidationException($"문의 내용은 최소 {MinMessageLength}자 이상이어야 합니다.");
|
||||||
|
|
||||||
|
if (trimmedMessage.Length > MaxMessageLength)
|
||||||
|
throw new ValidationException($"문의 내용은 최대 {MaxMessageLength}자 이하여야 합니다.");
|
||||||
|
|
||||||
var inquiry = new Inquiry
|
var inquiry = new Inquiry
|
||||||
{
|
{
|
||||||
Name = name.Trim(),
|
Name = name.Trim(),
|
||||||
Phone = formattedPhone,
|
Phone = formattedPhone,
|
||||||
Email = string.IsNullOrWhiteSpace(email) ? null : email.Trim(),
|
Email = string.IsNullOrWhiteSpace(email) ? null : email.Trim(),
|
||||||
ServiceType = serviceType ?? "기타",
|
ServiceType = serviceType ?? "기타",
|
||||||
Message = message.Trim(),
|
Message = trimmedMessage,
|
||||||
IpAddress = ipAddress,
|
IpAddress = ipAddress,
|
||||||
Status = InquiryStatusMapper.ToStorageValue(InquiryStatus.New),
|
Status = InquiryStatusMapper.ToStorageValue(InquiryStatus.New),
|
||||||
CreatedAt = DateTime.UtcNow
|
CreatedAt = DateTime.UtcNow
|
||||||
@@ -96,6 +106,13 @@ public class InquiryService(
|
|||||||
if (string.IsNullOrWhiteSpace(dto.Message))
|
if (string.IsNullOrWhiteSpace(dto.Message))
|
||||||
throw new ValidationException("문의 내용을 입력하세요.");
|
throw new ValidationException("문의 내용을 입력하세요.");
|
||||||
|
|
||||||
|
var trimmedUpdateMessage = dto.Message.Trim();
|
||||||
|
if (trimmedUpdateMessage.Length < MinMessageLength)
|
||||||
|
throw new ValidationException($"문의 내용은 최소 {MinMessageLength}자 이상이어야 합니다.");
|
||||||
|
|
||||||
|
if (trimmedUpdateMessage.Length > MaxMessageLength)
|
||||||
|
throw new ValidationException($"문의 내용은 최대 {MaxMessageLength}자 이하여야 합니다.");
|
||||||
|
|
||||||
if (!InquiryStatusMapper.TryParse(dto.Status, out var parsedStatus))
|
if (!InquiryStatusMapper.TryParse(dto.Status, out var parsedStatus))
|
||||||
throw new ValidationException("지원하지 않는 문의 상태입니다.");
|
throw new ValidationException("지원하지 않는 문의 상태입니다.");
|
||||||
|
|
||||||
@@ -103,7 +120,7 @@ public class InquiryService(
|
|||||||
inquiry.Phone = FormatPhoneNumber(cleanPhone);
|
inquiry.Phone = FormatPhoneNumber(cleanPhone);
|
||||||
inquiry.Email = string.IsNullOrWhiteSpace(dto.Email) ? null : dto.Email.Trim();
|
inquiry.Email = string.IsNullOrWhiteSpace(dto.Email) ? null : dto.Email.Trim();
|
||||||
inquiry.ServiceType = string.IsNullOrWhiteSpace(dto.ServiceType) ? "기타" : dto.ServiceType.Trim();
|
inquiry.ServiceType = string.IsNullOrWhiteSpace(dto.ServiceType) ? "기타" : dto.ServiceType.Trim();
|
||||||
inquiry.Message = dto.Message.Trim();
|
inquiry.Message = trimmedUpdateMessage;
|
||||||
inquiry.Status = InquiryStatusMapper.ToStorageValue(parsedStatus);
|
inquiry.Status = InquiryStatusMapper.ToStorageValue(parsedStatus);
|
||||||
inquiry.AdminMemo = dto.AdminMemo;
|
inquiry.AdminMemo = dto.AdminMemo;
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,11 @@
|
|||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="message" class="form-label">문의내용 <span class="text-danger">*</span></label>
|
<label for="message" class="form-label">문의내용 <span class="text-danger">*</span></label>
|
||||||
<textarea class="form-control" id="message" name="Message" rows="5" required></textarea>
|
<textarea class="form-control" id="message" name="Message" rows="5" required minlength="10" maxlength="5000" placeholder="최소 10자, 최대 5000자까지 입력 가능합니다"></textarea>
|
||||||
|
<small class="form-text text-muted">
|
||||||
|
<span id="messageCount">0</span>/5000
|
||||||
|
</small>
|
||||||
|
<div id="messageError" class="text-danger mt-2" style="display: none;"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3 form-check">
|
<div class="mb-3 form-check">
|
||||||
@@ -88,8 +92,14 @@
|
|||||||
<script>
|
<script>
|
||||||
const phoneInput = document.getElementById('phone');
|
const phoneInput = document.getElementById('phone');
|
||||||
const phoneError = document.getElementById('phoneError');
|
const phoneError = document.getElementById('phoneError');
|
||||||
|
const messageInput = document.getElementById('message');
|
||||||
|
const messageError = document.getElementById('messageError');
|
||||||
|
const messageCount = document.getElementById('messageCount');
|
||||||
const contactForm = document.getElementById('contactForm');
|
const contactForm = document.getElementById('contactForm');
|
||||||
|
|
||||||
|
const MIN_MESSAGE_LENGTH = 10;
|
||||||
|
const MAX_MESSAGE_LENGTH = 5000;
|
||||||
|
|
||||||
// 한국 전화번호 정규식
|
// 한국 전화번호 정규식
|
||||||
const koreanPhoneRegex = /^(0(2|3[1-3]|4[1-4]|5[1-5]|6[1-4]|70|50[5-9]|[7-9](?:\d{1,2})?)\d{7,8}|0\d{9,10})$/;
|
const koreanPhoneRegex = /^(0(2|3[1-3]|4[1-4]|5[1-5]|6[1-4]|70|50[5-9]|[7-9](?:\d{1,2})?)\d{7,8}|0\d{9,10})$/;
|
||||||
|
|
||||||
@@ -161,6 +171,47 @@
|
|||||||
|
|
||||||
// 포커스 아웃 시 최종 검증
|
// 포커스 아웃 시 최종 검증
|
||||||
phoneInput.addEventListener('blur', validatePhone);
|
phoneInput.addEventListener('blur', validatePhone);
|
||||||
|
|
||||||
|
// 메시지 길이 실시간 표시
|
||||||
|
messageInput.addEventListener('input', (e) => {
|
||||||
|
const length = e.target.value.length;
|
||||||
|
messageCount.textContent = length;
|
||||||
|
validateMessage();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 메시지 검증
|
||||||
|
function validateMessage() {
|
||||||
|
const value = messageInput.value.trim();
|
||||||
|
const isValid = value.length >= MIN_MESSAGE_LENGTH && value.length <= MAX_MESSAGE_LENGTH;
|
||||||
|
|
||||||
|
if (!isValid && messageInput.value.length > 0) {
|
||||||
|
messageError.style.display = 'block';
|
||||||
|
if (value.length < MIN_MESSAGE_LENGTH) {
|
||||||
|
messageError.textContent = `최소 ${MIN_MESSAGE_LENGTH}자 이상 입력해주세요. (현재: ${value.length}자)`;
|
||||||
|
} else if (value.length > MAX_MESSAGE_LENGTH) {
|
||||||
|
messageError.textContent = `최대 ${MAX_MESSAGE_LENGTH}자까지만 입력 가능합니다. (현재: ${value.length}자)`;
|
||||||
|
}
|
||||||
|
messageInput.classList.add('is-invalid');
|
||||||
|
} else {
|
||||||
|
messageError.style.display = 'none';
|
||||||
|
messageInput.classList.remove('is-invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 폼 제출 시 메시지 검증 포함
|
||||||
|
const originalSubmitHandler = contactForm.onsubmit;
|
||||||
|
contactForm.addEventListener('submit', (e) => {
|
||||||
|
if (!validatePhone() || !validateMessage()) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!validatePhone()) {
|
||||||
|
phoneInput.focus();
|
||||||
|
} else if (!validateMessage()) {
|
||||||
|
messageInput.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<hr class="my-5" />
|
<hr class="my-5" />
|
||||||
|
|||||||
Reference in New Issue
Block a user