using System; using System.Collections.Generic; using System.Net.Http; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using FinlyticCore.Dtos.News; using FinlyticCore.Services; using FinlyticCore.Util; using FinlyticNews.Util; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace FinlyticNews.Services; /// /// Defines integration operations with the external n8n AI workflow webhook. /// public interface IN8nService { /// /// Submits raw article content and pre-filtered assets to n8n, returning the parsed response metadata. /// Task AnalyzeArticleAsync(string content, List filteredAssets, CancellationToken ct = default); } /// public class N8nService : IN8nService { private readonly HttpClient _httpClient; private readonly IServiceScopeFactory _scopeFactory; private readonly IConfiguration _configuration; private readonly IFinlyticLogger _finlyticLogger; public N8nService( HttpClient httpClient, IServiceScopeFactory scopeFactory, IConfiguration configuration, IFinlyticLogger finlyticLogger) { _httpClient = httpClient; _scopeFactory = scopeFactory; _configuration = configuration; _finlyticLogger = finlyticLogger; } /// public async Task AnalyzeArticleAsync(string content, List filteredAssets, CancellationToken ct = default) { string? targetUrl = null; using (var scope = _scopeFactory.CreateScope()) { var settingsDb = scope.ServiceProvider.GetService(); if (settingsDb != null) { var settings = await settingsDb.GetSettingsAsync(); targetUrl = settings?.N8nWebhookUrl; } } if (string.IsNullOrWhiteSpace(targetUrl)) { targetUrl = _configuration["N8N:ArticleExtractionUrl"] ?? _configuration["N8N__ArticleExtractionUrl"]; } if (string.IsNullOrWhiteSpace(targetUrl)) { await _finlyticLogger.LogErrorAsync(SettingKeys.NewsChannel, "[N8nService] N8nWebhookUrl is not configured in DB or application settings."); return null; } await _finlyticLogger.LogInfoAsync(SettingKeys.NewsChannel, "[N8nService] Posting article to n8n webhook pipeline at: {Url}", targetUrl); var payload = new N8nRequestPayload(content, filteredAssets ?? []); try { var jsonContent = JsonSerializer.Serialize(payload, FinlyticJsonSerializerContext.Default.N8nRequestPayload); using var requestContent = new StringContent(jsonContent, System.Text.Encoding.UTF8, "application/json"); using var response = await _httpClient.PostAsync(targetUrl, requestContent, ct); if (!response.IsSuccessStatusCode) { var errorMsg = await response.Content.ReadAsStringAsync(ct); await _finlyticLogger.LogErrorAsync(SettingKeys.NewsChannel, "[N8nService] n8n webhook returned status code {StatusCode}. Error payload: {Error}", response.StatusCode, errorMsg); return null; } using var responseStream = await response.Content.ReadAsStreamAsync(ct); using var doc = await JsonDocument.ParseAsync(responseStream, cancellationToken: ct); var root = doc.RootElement; if (root.ValueKind == JsonValueKind.Array) { if (root.GetArrayLength() == 0) { await _finlyticLogger.LogWarningAsync(SettingKeys.NewsChannel, "[N8nService] n8n webhook returned an empty array."); return null; } root = root[0]; } if (root.ValueKind == JsonValueKind.Object) { if (root.TryGetProperty("json", out var jsonChild) && jsonChild.ValueKind == JsonValueKind.Object) root = jsonChild; else if (root.TryGetProperty("output", out var outChild) && outChild.ValueKind == JsonValueKind.Object) root = outChild; else if (root.TryGetProperty("data", out var dataChild) && dataChild.ValueKind == JsonValueKind.Object) root = dataChild; else if (root.TryGetProperty("body", out var bodyChild) && bodyChild.ValueKind == JsonValueKind.Object) root = bodyChild; } var result = root.Deserialize(FinlyticJsonSerializerContext.Default.N8nResponsePayload); return result; } catch (Exception ex) { await _finlyticLogger.LogErrorAsync(SettingKeys.NewsChannel, ex, "[N8nService] Failed to communicate with or parse response from n8n webhook workflow."); return null; } } }