using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text; using Application.Abstractions.Notification; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using SharedKernel; namespace Infrastructure.Notification; /// /// Nextcloud Talk OCS Chat API 운영 알림 발송기. /// 메시지는 백그라운드로 발송(fire-and-forget) — 외부 메신저 장애가 파트너 API 응답을 지연/실패시키지 않는다. /// 모든 메시지에 [환경명] prefix 를 붙여 dev/prod 발신원을 구분한다. /// internal sealed class NextcloudTalkAlertService( IHttpClientFactory httpClientFactory, IOptions options, IHostEnvironment env, ILogger logger ) : IAdminAlertService { public Task SendActivityAsync(string message, CancellationToken ct = default) { QueueSend(options.Value.NextcloudTalk.ActivityRoomToken, message); return Task.CompletedTask; } public Task SendAlertAsync(string message, CancellationToken ct = default) { QueueSend(options.Value.NextcloudTalk.AlertRoomToken, message); return Task.CompletedTask; } private void QueueSend(string roomToken, string message) { var cfg = options.Value.NextcloudTalk; if (!cfg.Enabled || string.IsNullOrWhiteSpace(cfg.BaseUrl) || string.IsNullOrWhiteSpace(cfg.Username) || string.IsNullOrWhiteSpace(cfg.AppPassword) || string.IsNullOrWhiteSpace(roomToken)) { return; } // fire-and-forget — 호출자 CancellationToken 과 분리해 클라이언트 연결 종료에도 발송을 완료한다 var label = string.IsNullOrWhiteSpace(cfg.Label) ? env.EnvironmentName : cfg.Label; _ = Task.Run(() => SendCoreAsync(cfg, roomToken, $"[{label}] {message}")); } private async Task SendCoreAsync(AppSettings.NextcloudTalkSection cfg, string roomToken, string message) { try { var client = httpClientFactory.CreateClient("NextcloudTalk"); var request = new HttpRequestMessage(HttpMethod.Post, $"{cfg.BaseUrl.TrimEnd('/')}/ocs/v2.php/apps/spreed/api/v1/chat/{roomToken}"); request.Headers.Add("OCS-APIRequest", "true"); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{cfg.Username}:{cfg.AppPassword}"))); request.Content = JsonContent.Create(new { message }); var response = await client.SendAsync(request); if (!response.IsSuccessStatusCode) { logger.LogWarning("[NextcloudTalk] 발송 실패 — HTTP {Status}, room={Room}", (int)response.StatusCode, roomToken); } } catch (Exception ex) { logger.LogWarning(ex, "[NextcloudTalk] 발송 오류 — room={Room}", roomToken); } } }