Add FluentValidation to application services
TaxBaik CI/CD / build-and-deploy (push) Successful in 2m45s
TaxBaik CI/CD / build-and-deploy (push) Successful in 2m45s
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
namespace TaxBaik.Application.Tests;
|
namespace TaxBaik.Application.Tests;
|
||||||
|
|
||||||
|
using FluentValidation;
|
||||||
using TaxBaik.Application.DTOs;
|
using TaxBaik.Application.DTOs;
|
||||||
using TaxBaik.Application.Services;
|
using TaxBaik.Application.Services;
|
||||||
using TaxBaik.Domain.Entities;
|
using TaxBaik.Domain.Entities;
|
||||||
@@ -12,9 +13,9 @@ public class BlogServiceTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public async Task CreateAsync_WhenPublishedWithoutSeoTitle_ThrowsValidationException()
|
public async Task CreateAsync_WhenPublishedWithoutSeoTitle_ThrowsValidationException()
|
||||||
{
|
{
|
||||||
var service = new BlogService(new FakeBlogPostRepository(), new MemoryCache(new MemoryCacheOptions()));
|
var service = new BlogService(new FakeBlogPostRepository(), new MemoryCache(new MemoryCacheOptions()), new PassThroughValidator<CreateBlogPostDto>());
|
||||||
|
|
||||||
await Assert.ThrowsAsync<ValidationException>(() => service.CreateAsync(new CreateBlogPostDto
|
await Assert.ThrowsAsync<TaxBaik.Application.Services.ValidationException>(() => service.CreateAsync(new CreateBlogPostDto
|
||||||
{
|
{
|
||||||
Title = "테스트 포스트",
|
Title = "테스트 포스트",
|
||||||
Content = "본문",
|
Content = "본문",
|
||||||
@@ -33,7 +34,7 @@ public class BlogServiceTests
|
|||||||
new BlogPost { Id = 1, Title = "같은 제목", Content = "본문", Slug = "같은-제목" }
|
new BlogPost { Id = 1, Title = "같은 제목", Content = "본문", Slug = "같은-제목" }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
var service = new BlogService(repository, new MemoryCache(new MemoryCacheOptions()));
|
var service = new BlogService(repository, new MemoryCache(new MemoryCacheOptions()), new PassThroughValidator<CreateBlogPostDto>());
|
||||||
|
|
||||||
var post = await service.CreateAsync(new CreateBlogPostDto
|
var post = await service.CreateAsync(new CreateBlogPostDto
|
||||||
{
|
{
|
||||||
@@ -54,7 +55,7 @@ public class BlogServiceTests
|
|||||||
new BlogPost { Id = 1, Title = "삭제 대상", Content = "본문", Slug = "delete-me", IsPublished = true }
|
new BlogPost { Id = 1, Title = "삭제 대상", Content = "본문", Slug = "delete-me", IsPublished = true }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
var service = new BlogService(repository, new MemoryCache(new MemoryCacheOptions()));
|
var service = new BlogService(repository, new MemoryCache(new MemoryCacheOptions()), new PassThroughValidator<CreateBlogPostDto>());
|
||||||
|
|
||||||
await service.DeleteAsync(1);
|
await service.DeleteAsync(1);
|
||||||
|
|
||||||
@@ -129,4 +130,14 @@ public class BlogServiceTests
|
|||||||
|
|
||||||
public Task IncrementViewCountAsync(int id, CancellationToken cancellationToken = default) => Task.CompletedTask;
|
public Task IncrementViewCountAsync(int id, CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class PassThroughValidator<T> : IValidator<T>
|
||||||
|
{
|
||||||
|
public FluentValidation.Results.ValidationResult Validate(T instance) => new();
|
||||||
|
public Task<FluentValidation.Results.ValidationResult> ValidateAsync(T instance, CancellationToken cancellation = default) => Task.FromResult(new FluentValidation.Results.ValidationResult());
|
||||||
|
public FluentValidation.Results.ValidationResult Validate(IValidationContext context) => new();
|
||||||
|
public Task<FluentValidation.Results.ValidationResult> ValidateAsync(IValidationContext context, CancellationToken cancellation = default) => Task.FromResult(new FluentValidation.Results.ValidationResult());
|
||||||
|
public IValidatorDescriptor CreateDescriptor() => throw new NotImplementedException();
|
||||||
|
public bool CanValidateInstancesOfType(Type type) => true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
namespace TaxBaik.Application.Tests;
|
namespace TaxBaik.Application.Tests;
|
||||||
|
|
||||||
|
using FluentValidation;
|
||||||
|
using TaxBaik.Application.DTOs;
|
||||||
using TaxBaik.Application.Services;
|
using TaxBaik.Application.Services;
|
||||||
using TaxBaik.Domain.Entities;
|
using TaxBaik.Domain.Entities;
|
||||||
using TaxBaik.Domain.Interfaces;
|
using TaxBaik.Domain.Interfaces;
|
||||||
@@ -11,16 +13,16 @@ public class InquiryServiceTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public async Task UpdateStatusAsync_WhenStatusIsInvalid_ThrowsValidationException()
|
public async Task UpdateStatusAsync_WhenStatusIsInvalid_ThrowsValidationException()
|
||||||
{
|
{
|
||||||
var service = new InquiryService(new FakeInquiryRepository(), new FakeInquiryNotificationService(), new MemoryCache(new MemoryCacheOptions()));
|
var service = new InquiryService(new FakeInquiryRepository(), new FakeInquiryNotificationService(), new MemoryCache(new MemoryCacheOptions()), new PassThroughValidator<SubmitInquiryDto>(), new PassThroughValidator<UpdateInquiryDto>());
|
||||||
|
|
||||||
await Assert.ThrowsAsync<ValidationException>(() => service.UpdateStatusAsync(1, "invalid"));
|
await Assert.ThrowsAsync<TaxBaik.Application.Services.ValidationException>(() => service.UpdateStatusAsync(1, "invalid"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SubmitAsync_StoresEmailAndNewStatus()
|
public async Task SubmitAsync_StoresEmailAndNewStatus()
|
||||||
{
|
{
|
||||||
var repository = new FakeInquiryRepository();
|
var repository = new FakeInquiryRepository();
|
||||||
var service = new InquiryService(repository, new FakeInquiryNotificationService(), new MemoryCache(new MemoryCacheOptions()));
|
var service = new InquiryService(repository, new FakeInquiryNotificationService(), new MemoryCache(new MemoryCacheOptions()), new PassThroughValidator<SubmitInquiryDto>(), new PassThroughValidator<UpdateInquiryDto>());
|
||||||
|
|
||||||
await service.SubmitAsync("홍길동", "010-1234-5678", "기장", "사업자 세무 관련해서 문의드립니다.", "user@example.com");
|
await service.SubmitAsync("홍길동", "010-1234-5678", "기장", "사업자 세무 관련해서 문의드립니다.", "user@example.com");
|
||||||
|
|
||||||
@@ -121,4 +123,14 @@ public class InquiryServiceTests
|
|||||||
public Task NotifyStatusChangedAsync(int inquiryId, string name, string phone, string serviceType, string previousStatus, string newStatus, string? changedBy = null, CancellationToken ct = default)
|
public Task NotifyStatusChangedAsync(int inquiryId, string name, string phone, string serviceType, string previousStatus, string newStatus, string? changedBy = null, CancellationToken ct = default)
|
||||||
=> Task.CompletedTask;
|
=> Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class PassThroughValidator<T> : IValidator<T>
|
||||||
|
{
|
||||||
|
public FluentValidation.Results.ValidationResult Validate(T instance) => new();
|
||||||
|
public Task<FluentValidation.Results.ValidationResult> ValidateAsync(T instance, CancellationToken cancellation = default) => Task.FromResult(new FluentValidation.Results.ValidationResult());
|
||||||
|
public FluentValidation.Results.ValidationResult Validate(IValidationContext context) => new();
|
||||||
|
public Task<FluentValidation.Results.ValidationResult> ValidateAsync(IValidationContext context, CancellationToken cancellation = default) => Task.FromResult(new FluentValidation.Results.ValidationResult());
|
||||||
|
public IValidatorDescriptor CreateDescriptor() => throw new NotImplementedException();
|
||||||
|
public bool CanValidateInstancesOfType(Type type) => true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
namespace TaxBaik.Application.DTOs;
|
||||||
|
|
||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
public sealed class CreateBlogPostDtoValidator : AbstractValidator<CreateBlogPostDto>
|
||||||
|
{
|
||||||
|
public CreateBlogPostDtoValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.Title).NotEmpty().MaximumLength(ValidationRules.TitleMaxLength);
|
||||||
|
RuleFor(x => x.Content).NotEmpty().MinimumLength(ValidationRules.MessageMinLength).MaximumLength(ValidationRules.ContentMaxLength);
|
||||||
|
RuleFor(x => x.Tags).MaximumLength(ValidationRules.TagsMaxLength);
|
||||||
|
RuleFor(x => x.SeoTitle).MaximumLength(ValidationRules.SeoTitleMaxLength);
|
||||||
|
RuleFor(x => x.SeoDescription).MaximumLength(ValidationRules.SeoDescriptionMaxLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class SubmitInquiryDtoValidator : AbstractValidator<SubmitInquiryDto>
|
||||||
|
{
|
||||||
|
public SubmitInquiryDtoValidator()
|
||||||
|
{
|
||||||
|
ApplyInquiryRules(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void ApplyInquiryRules(AbstractValidator<SubmitInquiryDto> validator) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class UpdateInquiryDtoValidator : AbstractValidator<UpdateInquiryDto>
|
||||||
|
{
|
||||||
|
public UpdateInquiryDtoValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.Name).NotEmpty().MaximumLength(ValidationRules.NameMaxLength);
|
||||||
|
RuleFor(x => x.Phone).NotEmpty().Matches(ValidationRules.KoreanPhonePattern);
|
||||||
|
RuleFor(x => x.Email).EmailAddress().When(x => !string.IsNullOrWhiteSpace(x.Email));
|
||||||
|
RuleFor(x => x.ServiceType).MaximumLength(ValidationRules.ServiceTypeMaxLength);
|
||||||
|
RuleFor(x => x.Message).NotEmpty().MinimumLength(ValidationRules.MessageMinLength).MaximumLength(ValidationRules.MessageMaxLength);
|
||||||
|
RuleFor(x => x.Status).NotEmpty();
|
||||||
|
RuleFor(x => x.AdminMemo).MaximumLength(ValidationRules.MemoMaxLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class CreateClientDtoValidator : AbstractValidator<CreateClientDto>
|
||||||
|
{
|
||||||
|
public CreateClientDtoValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.Name).NotEmpty().MaximumLength(ValidationRules.NameMaxLength);
|
||||||
|
RuleFor(x => x.CompanyName).MaximumLength(ValidationRules.NameMaxLength);
|
||||||
|
RuleFor(x => x.Phone).MaximumLength(ValidationRules.PhoneMaxLength);
|
||||||
|
RuleFor(x => x.Email).EmailAddress().When(x => !string.IsNullOrWhiteSpace(x.Email));
|
||||||
|
RuleFor(x => x.ServiceType).MaximumLength(ValidationRules.ServiceTypeMaxLength);
|
||||||
|
RuleFor(x => x.TaxType).MaximumLength(ValidationRules.ServiceTypeMaxLength);
|
||||||
|
RuleFor(x => x.Status).NotEmpty();
|
||||||
|
RuleFor(x => x.Source).MaximumLength(ValidationRules.ServiceTypeMaxLength);
|
||||||
|
RuleFor(x => x.Memo).MaximumLength(ValidationRules.MemoMaxLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
namespace TaxBaik.Application.Services;
|
namespace TaxBaik.Application.Services;
|
||||||
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using FluentValidation;
|
||||||
using TaxBaik.Application.DTOs;
|
using TaxBaik.Application.DTOs;
|
||||||
using TaxBaik.Domain.Entities;
|
using TaxBaik.Domain.Entities;
|
||||||
using TaxBaik.Domain.Interfaces;
|
using TaxBaik.Domain.Interfaces;
|
||||||
|
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
|
||||||
public class BlogService(IBlogPostRepository repository, IMemoryCache memoryCache)
|
public class BlogService(IBlogPostRepository repository, IMemoryCache memoryCache, IValidator<CreateBlogPostDto> validator)
|
||||||
{
|
{
|
||||||
public async Task<BlogPost?> GetByIdAsync(int id, CancellationToken ct = default) =>
|
public async Task<BlogPost?> GetByIdAsync(int id, CancellationToken ct = default) =>
|
||||||
await repository.GetByIdAsync(id, ct);
|
await repository.GetByIdAsync(id, ct);
|
||||||
@@ -60,6 +61,7 @@ public class BlogService(IBlogPostRepository repository, IMemoryCache memoryCach
|
|||||||
|
|
||||||
public async Task<BlogPost> CreateAsync(CreateBlogPostDto dto, CancellationToken ct = default)
|
public async Task<BlogPost> CreateAsync(CreateBlogPostDto dto, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
validator.ValidateAndThrow(dto);
|
||||||
var post = new BlogPost
|
var post = new BlogPost
|
||||||
{
|
{
|
||||||
Title = dto.Title,
|
Title = dto.Title,
|
||||||
@@ -87,6 +89,7 @@ public class BlogService(IBlogPostRepository repository, IMemoryCache memoryCach
|
|||||||
|
|
||||||
public async Task<BlogPost?> UpdateAsync(int id, CreateBlogPostDto dto, CancellationToken ct = default)
|
public async Task<BlogPost?> UpdateAsync(int id, CreateBlogPostDto dto, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
validator.ValidateAndThrow(dto);
|
||||||
var post = await repository.GetByIdAsync(id, ct);
|
var post = await repository.GetByIdAsync(id, ct);
|
||||||
if (post == null)
|
if (post == null)
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
namespace TaxBaik.Application.Services;
|
namespace TaxBaik.Application.Services;
|
||||||
|
|
||||||
|
using FluentValidation;
|
||||||
using TaxBaik.Application.DTOs;
|
using TaxBaik.Application.DTOs;
|
||||||
using TaxBaik.Domain.Entities;
|
using TaxBaik.Domain.Entities;
|
||||||
using TaxBaik.Domain.Interfaces;
|
using TaxBaik.Domain.Interfaces;
|
||||||
|
|
||||||
public class ClientService(IClientRepository repository)
|
public class ClientService(IClientRepository repository, IValidator<CreateClientDto> validator)
|
||||||
{
|
{
|
||||||
public async Task<(IEnumerable<Client> Items, int Total)> GetPagedAsync(
|
public async Task<(IEnumerable<Client> Items, int Total)> GetPagedAsync(
|
||||||
int page, int pageSize, string? status = null, string? search = null, CancellationToken ct = default) =>
|
int page, int pageSize, string? status = null, string? search = null, CancellationToken ct = default) =>
|
||||||
@@ -24,8 +25,7 @@ public class ClientService(IClientRepository repository)
|
|||||||
|
|
||||||
public async Task<int> CreateAsync(CreateClientDto dto, CancellationToken ct = default)
|
public async Task<int> CreateAsync(CreateClientDto dto, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(dto.Name))
|
validator.ValidateAndThrow(dto);
|
||||||
throw new ValidationException("고객명을 입력하세요.");
|
|
||||||
|
|
||||||
var client = new Client
|
var client = new Client
|
||||||
{
|
{
|
||||||
@@ -45,8 +45,7 @@ public class ClientService(IClientRepository repository)
|
|||||||
|
|
||||||
public async Task UpdateAsync(int id, CreateClientDto dto, CancellationToken ct = default)
|
public async Task UpdateAsync(int id, CreateClientDto dto, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(dto.Name))
|
validator.ValidateAndThrow(dto);
|
||||||
throw new ValidationException("고객명을 입력하세요.");
|
|
||||||
|
|
||||||
var client = await repository.GetByIdAsync(id, ct)
|
var client = await repository.GetByIdAsync(id, ct)
|
||||||
?? throw new KeyNotFoundException($"고객 ID {id}를 찾을 수 없습니다.");
|
?? throw new KeyNotFoundException($"고객 ID {id}를 찾을 수 없습니다.");
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
namespace TaxBaik.Application.Services;
|
namespace TaxBaik.Application.Services;
|
||||||
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using FluentValidation;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using TaxBaik.Application.DTOs;
|
using TaxBaik.Application.DTOs;
|
||||||
using TaxBaik.Domain.Entities;
|
using TaxBaik.Domain.Entities;
|
||||||
@@ -10,7 +11,9 @@ using TaxBaik.Domain.Interfaces;
|
|||||||
public class InquiryService(
|
public class InquiryService(
|
||||||
IInquiryRepository repository,
|
IInquiryRepository repository,
|
||||||
IInquiryNotificationService notificationService,
|
IInquiryNotificationService notificationService,
|
||||||
IMemoryCache memoryCache)
|
IMemoryCache memoryCache,
|
||||||
|
IValidator<SubmitInquiryDto> submitValidator,
|
||||||
|
IValidator<UpdateInquiryDto> updateValidator)
|
||||||
{
|
{
|
||||||
// 한국 전화번호 정규식
|
// 한국 전화번호 정규식
|
||||||
// 휴대폰: 010~019, 070, 0505~0509
|
// 휴대폰: 010~019, 070, 0505~0509
|
||||||
@@ -18,15 +21,18 @@ 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)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
submitValidator.ValidateAndThrow(new SubmitInquiryDto
|
||||||
throw new ValidationException("이름을 입력하세요.");
|
{
|
||||||
|
Name = name,
|
||||||
|
Phone = phone,
|
||||||
|
Email = email,
|
||||||
|
ServiceType = serviceType,
|
||||||
|
Message = message
|
||||||
|
});
|
||||||
|
|
||||||
var cleanPhone = phone?.Replace("-", "").Replace(" ", "").Trim() ?? "";
|
var cleanPhone = phone?.Replace("-", "").Replace(" ", "").Trim() ?? "";
|
||||||
if (!PhoneRegex.IsMatch(cleanPhone) || cleanPhone.Length < 10)
|
if (!PhoneRegex.IsMatch(cleanPhone) || cleanPhone.Length < 10)
|
||||||
@@ -34,15 +40,7 @@ public class InquiryService(
|
|||||||
|
|
||||||
var formattedPhone = FormatPhoneNumber(cleanPhone);
|
var formattedPhone = FormatPhoneNumber(cleanPhone);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(message))
|
|
||||||
throw new ValidationException("문의 내용을 입력하세요.");
|
|
||||||
|
|
||||||
var trimmedMessage = message.Trim();
|
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
|
||||||
{
|
{
|
||||||
@@ -95,23 +93,13 @@ public class InquiryService(
|
|||||||
var inquiry = await repository.GetByIdAsync(id, ct);
|
var inquiry = await repository.GetByIdAsync(id, ct);
|
||||||
if (inquiry == null)
|
if (inquiry == null)
|
||||||
return null;
|
return null;
|
||||||
|
updateValidator.ValidateAndThrow(dto);
|
||||||
if (string.IsNullOrWhiteSpace(dto.Name))
|
|
||||||
throw new ValidationException("이름을 입력하세요.");
|
|
||||||
|
|
||||||
var cleanPhone = dto.Phone?.Replace("-", "").Replace(" ", "").Trim() ?? "";
|
var cleanPhone = dto.Phone?.Replace("-", "").Replace(" ", "").Trim() ?? "";
|
||||||
if (!PhoneRegex.IsMatch(cleanPhone) || cleanPhone.Length < 10)
|
if (!PhoneRegex.IsMatch(cleanPhone) || cleanPhone.Length < 10)
|
||||||
throw new ValidationException("올바른 전화번호를 입력하세요. (예: 01012345678 또는 010-1234-5678)");
|
throw new ValidationException("올바른 전화번호를 입력하세요. (예: 01012345678 또는 010-1234-5678)");
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(dto.Message))
|
|
||||||
throw new ValidationException("문의 내용을 입력하세요.");
|
|
||||||
|
|
||||||
var trimmedUpdateMessage = dto.Message.Trim();
|
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("지원하지 않는 문의 상태입니다.");
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.0" />
|
||||||
|
<PackageReference Include="FluentValidation" Version="11.11.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using Microsoft.AspNetCore.ResponseCompression;
|
|||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using MudBlazor.Services;
|
using MudBlazor.Services;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using FluentValidation;
|
||||||
using FastEndpoints;
|
using FastEndpoints;
|
||||||
using System.Threading.RateLimiting;
|
using System.Threading.RateLimiting;
|
||||||
using TaxBaik.Application;
|
using TaxBaik.Application;
|
||||||
@@ -357,6 +358,7 @@ builder.Services.AddSingleton(HtmlEncoder.Create(UnicodeRanges.All));
|
|||||||
|
|
||||||
builder.Services.AddInfrastructure();
|
builder.Services.AddInfrastructure();
|
||||||
builder.Services.AddApplication();
|
builder.Services.AddApplication();
|
||||||
|
builder.Services.AddValidatorsFromAssemblyContaining<TaxBaik.Application.DTOs.CreateBlogPostDtoValidator>();
|
||||||
builder.Services.AddScoped<IInquiryNotificationService, TelegramInquiryNotificationService>();
|
builder.Services.AddScoped<IInquiryNotificationService, TelegramInquiryNotificationService>();
|
||||||
builder.Services.AddScoped<TaxBaik.Web.Services.SitemapValidationService>();
|
builder.Services.AddScoped<TaxBaik.Web.Services.SitemapValidationService>();
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.19.1" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.19.1" />
|
||||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.19.1" />
|
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.19.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.9" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.9" />
|
||||||
|
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.11.0" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
|
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||||
|
|||||||
Reference in New Issue
Block a user