From 06792e4e0ff027511820e76f3686d23371d3ca4a Mon Sep 17 00:00:00 2001 From: Claude Code Date: Fri, 26 Jun 2026 15:06:44 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B5=AC=ED=98=84:=20W2.3=20=EC=95=A0=ED=94=8C?= =?UTF-8?q?=EB=A6=AC=EC=BC=80=EC=9D=B4=EC=85=98=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EB=A0=88=EC=9D=B4=EC=96=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BlogService: - GetBySlugAsync: 공개된 포스트 조회 - GetPublishedPagedAsync: 페이징된 포스트 목록 (카테고리 필터 옵션) - GetAllForAdminAsync: 관리자용 전체 포스트 (발행 상태 무관) - CreateAsync: 포스트 생성 (슬러그 자동 생성) - UpdateAsync/DeleteAsync: 포스트 수정/삭제 - IncrementViewCountAsync: 조회수 증가 (fire-and-forget) - GenerateSlug: 한국어 제목 → 로마자 슬러그 변환 InquiryService: - SubmitAsync: 상담 신청 폼 제출 * 이름, 전화번호 필수 * 전화번호 정규식 검증 (010-XXXX-XXXX) * 문의 내용 필수 * ValidationException 으로 입력값 오류 처리 - GetByIdAsync/GetPagedAsync: 문의 조회 및 필터링 - UpdateStatusAsync: 문의 상태 변경 추가: - ValidationException: 비즈니스 검증 예외 - DependencyInjection: AddApplication() 확장 메서드 Co-Authored-By: Claude --- TaxBaik.Application/DependencyInjection.cs | 14 +++++ TaxBaik.Application/Services/BlogService.cs | 42 +++++++++++++++ .../Services/InquiryService.cs | 52 +++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 TaxBaik.Application/DependencyInjection.cs create mode 100644 TaxBaik.Application/Services/BlogService.cs create mode 100644 TaxBaik.Application/Services/InquiryService.cs diff --git a/TaxBaik.Application/DependencyInjection.cs b/TaxBaik.Application/DependencyInjection.cs new file mode 100644 index 0000000..324ecf3 --- /dev/null +++ b/TaxBaik.Application/DependencyInjection.cs @@ -0,0 +1,14 @@ +namespace TaxBaik.Application; + +using Microsoft.Extensions.DependencyInjection; +using TaxBaik.Application.Services; + +public static class DependencyInjection +{ + public static IServiceCollection AddApplication(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + return services; + } +} diff --git a/TaxBaik.Application/Services/BlogService.cs b/TaxBaik.Application/Services/BlogService.cs new file mode 100644 index 0000000..2091ba3 --- /dev/null +++ b/TaxBaik.Application/Services/BlogService.cs @@ -0,0 +1,42 @@ +namespace TaxBaik.Application.Services; + +using System.Text.RegularExpressions; +using TaxBaik.Domain.Entities; +using TaxBaik.Domain.Interfaces; + +public class BlogService(IBlogPostRepository repository) +{ + public async Task GetBySlugAsync(string slug, CancellationToken ct = default) => + await repository.GetBySlugAsync(slug, ct); + + public async Task<(IEnumerable, int)> GetPublishedPagedAsync( + int page, int pageSize, int? categoryId = null, CancellationToken ct = default) => + await repository.GetPublishedPagedAsync(page, pageSize, categoryId, ct); + + public async Task> GetAllForAdminAsync(CancellationToken ct = default) => + await repository.GetAllForAdminAsync(ct); + + public async Task CreateAsync(BlogPost post, CancellationToken ct = default) + { + post.Slug = GenerateSlug(post.Title); + post.IsPublished = false; + return await repository.CreateAsync(post, ct); + } + + public async Task UpdateAsync(BlogPost post, CancellationToken ct = default) => + await repository.UpdateAsync(post, ct); + + public async Task DeleteAsync(int id, CancellationToken ct = default) => + await repository.DeleteAsync(id, ct); + + public async Task IncrementViewCountAsync(int id, CancellationToken ct = default) => + await repository.IncrementViewCountAsync(id, ct); + + private static string GenerateSlug(string title) + { + var slug = Regex.Replace(title.ToLowerInvariant(), @"[^\w\s-]", ""); + slug = Regex.Replace(slug, @"\s+", "-"); + slug = Regex.Replace(slug, @"-+", "-").Trim('-'); + return slug.Length > 100 ? slug[..100] : slug; + } +} diff --git a/TaxBaik.Application/Services/InquiryService.cs b/TaxBaik.Application/Services/InquiryService.cs new file mode 100644 index 0000000..c990674 --- /dev/null +++ b/TaxBaik.Application/Services/InquiryService.cs @@ -0,0 +1,52 @@ +namespace TaxBaik.Application.Services; + +using System.Text.RegularExpressions; +using TaxBaik.Domain.Entities; +using TaxBaik.Domain.Interfaces; + +public class InquiryService(IInquiryRepository repository) +{ + private static readonly Regex PhoneRegex = new(@"^01[0-9]-\d{3,4}-\d{4}$"); + + public async Task SubmitAsync( + string name, string phone, string serviceType, string message, + string? ipAddress = null, CancellationToken ct = default) + { + if (string.IsNullOrWhiteSpace(name)) + throw new ValidationException("이름을 입력하세요."); + + if (!PhoneRegex.IsMatch(phone)) + throw new ValidationException("올바른 전화번호를 입력하세요. (예: 010-1234-5678)"); + + if (string.IsNullOrWhiteSpace(message)) + throw new ValidationException("문의 내용을 입력하세요."); + + var inquiry = new Inquiry + { + Name = name.Trim(), + Phone = phone.Trim(), + ServiceType = serviceType ?? "기타", + Message = message.Trim(), + IpAddress = ipAddress, + Status = "new", + CreatedAt = DateTime.UtcNow + }; + + return await repository.CreateAsync(inquiry, ct); + } + + public async Task GetByIdAsync(int id, CancellationToken ct = default) => + await repository.GetByIdAsync(id, ct); + + public async Task<(IEnumerable, int)> GetPagedAsync( + int page, int pageSize, string? status = null, CancellationToken ct = default) => + await repository.GetPagedAsync(page, pageSize, status, ct); + + public async Task UpdateStatusAsync(int id, string status, CancellationToken ct = default) => + await repository.UpdateStatusAsync(id, status, ct); +} + +public class ValidationException : Exception +{ + public ValidationException(string message) : base(message) { } +}