From f54cab55626df5acb754238ae8458db6cf75e11b Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Sat, 27 Jun 2026 15:58:42 +0900 Subject: [PATCH] feat: notify telegram on new inquiries --- .../InquiryServiceTests.cs | 10 ++- TaxBaik.Application/DependencyInjection.cs | 1 + .../Services/IInquiryNotificationService.cs | 6 ++ .../Services/InquiryService.cs | 6 +- .../NoopInquiryNotificationService.cs | 7 +++ TaxBaik.Web/Program.cs | 6 ++ .../TelegramInquiryNotificationService.cs | 63 +++++++++++++++++++ TaxBaik.Web/appsettings.json | 7 +++ 8 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 TaxBaik.Application/Services/IInquiryNotificationService.cs create mode 100644 TaxBaik.Application/Services/NoopInquiryNotificationService.cs create mode 100644 TaxBaik.Web/Services/TelegramInquiryNotificationService.cs diff --git a/TaxBaik.Application.Tests/InquiryServiceTests.cs b/TaxBaik.Application.Tests/InquiryServiceTests.cs index 44d2cda..4648cdb 100644 --- a/TaxBaik.Application.Tests/InquiryServiceTests.cs +++ b/TaxBaik.Application.Tests/InquiryServiceTests.cs @@ -10,7 +10,7 @@ public class InquiryServiceTests [Fact] public async Task UpdateStatusAsync_WhenStatusIsInvalid_ThrowsValidationException() { - var service = new InquiryService(new FakeInquiryRepository()); + var service = new InquiryService(new FakeInquiryRepository(), new FakeInquiryNotificationService()); await Assert.ThrowsAsync(() => service.UpdateStatusAsync(1, "invalid")); } @@ -19,7 +19,7 @@ public class InquiryServiceTests public async Task SubmitAsync_StoresEmailAndNewStatus() { var repository = new FakeInquiryRepository(); - var service = new InquiryService(repository); + var service = new InquiryService(repository, new FakeInquiryNotificationService()); await service.SubmitAsync("홍길동", "010-1234-5678", "기장", "문의합니다.", "user@example.com"); @@ -56,4 +56,10 @@ public class InquiryServiceTests return Task.CompletedTask; } } + + private sealed class FakeInquiryNotificationService : IInquiryNotificationService + { + public Task NotifyCreatedAsync(int inquiryId, string name, string phone, string serviceType, string message, CancellationToken ct = default) + => Task.CompletedTask; + } } diff --git a/TaxBaik.Application/DependencyInjection.cs b/TaxBaik.Application/DependencyInjection.cs index ae0bf2b..841aaab 100644 --- a/TaxBaik.Application/DependencyInjection.cs +++ b/TaxBaik.Application/DependencyInjection.cs @@ -9,6 +9,7 @@ public static class DependencyInjection { services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); return services; } diff --git a/TaxBaik.Application/Services/IInquiryNotificationService.cs b/TaxBaik.Application/Services/IInquiryNotificationService.cs new file mode 100644 index 0000000..8519e1c --- /dev/null +++ b/TaxBaik.Application/Services/IInquiryNotificationService.cs @@ -0,0 +1,6 @@ +namespace TaxBaik.Application.Services; + +public interface IInquiryNotificationService +{ + Task NotifyCreatedAsync(int inquiryId, string name, string phone, string serviceType, string message, CancellationToken ct = default); +} diff --git a/TaxBaik.Application/Services/InquiryService.cs b/TaxBaik.Application/Services/InquiryService.cs index 41df299..8415d7c 100644 --- a/TaxBaik.Application/Services/InquiryService.cs +++ b/TaxBaik.Application/Services/InquiryService.cs @@ -5,7 +5,7 @@ using TaxBaik.Domain.Entities; using TaxBaik.Domain.Enums; using TaxBaik.Domain.Interfaces; -public class InquiryService(IInquiryRepository repository) +public class InquiryService(IInquiryRepository repository, IInquiryNotificationService notificationService) { private static readonly Regex PhoneRegex = new(@"^01[0-9]-\d{3,4}-\d{4}$"); @@ -34,7 +34,9 @@ public class InquiryService(IInquiryRepository repository) CreatedAt = DateTime.UtcNow }; - return await repository.CreateAsync(inquiry, ct); + var inquiryId = await repository.CreateAsync(inquiry, ct); + await notificationService.NotifyCreatedAsync(inquiryId, inquiry.Name, inquiry.Phone, inquiry.ServiceType, inquiry.Message, ct); + return inquiryId; } public async Task GetByIdAsync(int id, CancellationToken ct = default) => diff --git a/TaxBaik.Application/Services/NoopInquiryNotificationService.cs b/TaxBaik.Application/Services/NoopInquiryNotificationService.cs new file mode 100644 index 0000000..2b3e7ee --- /dev/null +++ b/TaxBaik.Application/Services/NoopInquiryNotificationService.cs @@ -0,0 +1,7 @@ +namespace TaxBaik.Application.Services; + +public sealed class NoopInquiryNotificationService : IInquiryNotificationService +{ + public Task NotifyCreatedAsync(int inquiryId, string name, string phone, string serviceType, string message, CancellationToken ct = default) + => Task.CompletedTask; +} diff --git a/TaxBaik.Web/Program.cs b/TaxBaik.Web/Program.cs index 9489282..b0bf4af 100644 --- a/TaxBaik.Web/Program.cs +++ b/TaxBaik.Web/Program.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.ResponseCompression; using Microsoft.IdentityModel.Tokens; using MudBlazor.Services; using TaxBaik.Application; +using TaxBaik.Application.Services; using TaxBaik.Infrastructure; using TaxBaik.Web.Services; @@ -23,6 +24,10 @@ builder.Services.AddHealthChecks(); // Razor Pages + Blazor Server 통합 builder.Services.AddRazorPages(); builder.Services.AddRazorComponents().AddInteractiveServerComponents(); +builder.Services.Configure(options => +{ + options.DetailedErrors = true; +}); // JWT 인증 var connectionString = builder.Configuration.GetConnectionString("Default") @@ -70,6 +75,7 @@ builder.Services.AddMemoryCache(); builder.Services.AddResponseCompression(opts => { opts.Providers.Add(); }); +builder.Services.AddScoped(); // 한글 포함 다국어 문자를 유니코드 엔티티로 변환하지 않도록 설정 builder.Services.AddSingleton(HtmlEncoder.Create(UnicodeRanges.All)); diff --git a/TaxBaik.Web/Services/TelegramInquiryNotificationService.cs b/TaxBaik.Web/Services/TelegramInquiryNotificationService.cs new file mode 100644 index 0000000..cf38821 --- /dev/null +++ b/TaxBaik.Web/Services/TelegramInquiryNotificationService.cs @@ -0,0 +1,63 @@ +using System.Net.Http.Json; +using System.Text; +using TaxBaik.Application.Services; + +namespace TaxBaik.Web.Services; + +public class TelegramInquiryNotificationService : IInquiryNotificationService +{ + private readonly IHttpClientFactory _httpClientFactory; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + private readonly string _baseUrl; + + public TelegramInquiryNotificationService(IHttpClientFactory httpClientFactory, IConfiguration configuration, ILogger logger) + { + _httpClientFactory = httpClientFactory; + _configuration = configuration; + _logger = logger; + _baseUrl = (_configuration["App:PublicBaseUrl"] ?? "http://178.104.200.7/taxbaik").TrimEnd('/'); + } + + public async Task NotifyCreatedAsync(int inquiryId, string name, string phone, string serviceType, string message, CancellationToken ct = default) + { + var botToken = _configuration["Telegram:BotToken"]; + var chatId = _configuration["Telegram:ChatId"]; + if (string.IsNullOrWhiteSpace(botToken) || string.IsNullOrWhiteSpace(chatId)) + return; + + var adminLink = $"{_baseUrl}/admin/inquiries/{inquiryId}"; + var text = new StringBuilder() + .AppendLine("새 문의가 접수되었습니다.") + .AppendLine() + .AppendLine($"제목: {serviceType}") + .AppendLine($"이름: {name}") + .AppendLine($"연락처: {phone}") + .AppendLine() + .AppendLine("내용:") + .AppendLine(message) + .AppendLine() + .AppendLine($"답변 링크: {adminLink}") + .ToString(); + + var client = _httpClientFactory.CreateClient(); + var url = $"https://api.telegram.org/bot{botToken}/sendMessage"; + var payload = new + { + chat_id = chatId, + text, + disable_web_page_preview = false + }; + + try + { + var response = await client.PostAsJsonAsync(url, payload, ct); + if (!response.IsSuccessStatusCode) + _logger.LogWarning("텔레그램 알림 전송 실패: {StatusCode}", response.StatusCode); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "텔레그램 알림 전송 중 오류 발생"); + } + } +} diff --git a/TaxBaik.Web/appsettings.json b/TaxBaik.Web/appsettings.json index 4321036..4da6028 100644 --- a/TaxBaik.Web/appsettings.json +++ b/TaxBaik.Web/appsettings.json @@ -11,6 +11,13 @@ "Jwt": { "SecretKey": "dev-secret-key-change-in-production-min-32-chars!" }, + "App": { + "PublicBaseUrl": "http://178.104.200.7/taxbaik" + }, + "Telegram": { + "BotToken": "", + "ChatId": "" + }, "SiteSettings": { "PhoneNumber": "010-4122-8268", "EmailAddress": "taxbaik5668@gmail.com",