using System; using System.Net.Http; using System.Text; using System.Threading; using Newtonsoft.Json; using Serilog; using SyncEngine.Configuration; namespace SyncEngine.MySql { public interface IShopSyncLogApiClient { void PostLog(ShopSyncLogEntry entry); } public sealed class ShopSyncLogApiClient : IShopSyncLogApiClient, IDisposable { private static readonly TimeSpan RequestTimeout = TimeSpan.FromSeconds(30); private readonly SyncOptions _options; private readonly ILogger _logger; private readonly HttpClient _httpClient; private readonly bool _ownsHttpClient; public ShopSyncLogApiClient(SyncOptions options, ILogger logger) : this(options, logger, new HttpClient { Timeout = RequestTimeout }, ownsHttpClient: true) { } internal ShopSyncLogApiClient(SyncOptions options, ILogger logger, HttpClient httpClient, bool ownsHttpClient) { _options = options; _logger = logger; _httpClient = httpClient; _ownsHttpClient = ownsHttpClient; } public void PostLog(ShopSyncLogEntry entry) { var json = BuildPayloadJson(entry); using (var content = new StringContent(json, Encoding.UTF8, "application/json")) using (var request = new HttpRequestMessage(HttpMethod.Post, _options.ShopSyncApiUrl.Trim())) { request.Content = content; request.Headers.TryAddWithoutValidation("X-Shop-Sync-Key", _options.ShopSyncApiKey); var response = _httpClient.SendAsync(request, CancellationToken.None).GetAwaiter().GetResult(); var body = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); if (!response.IsSuccessStatusCode) { _logger.Warning("shop-sync/log API returned {StatusCode}: {Body}", (int)response.StatusCode, body); throw new InvalidOperationException( "shop-sync/log API returned " + (int)response.StatusCode + ": " + body); } } } /// Serializes entry to CI4 API JSON (testable without HTTP). public static string BuildPayloadJson(ShopSyncLogEntry entry) { var payload = BuildPayload(entry); return JsonConvert.SerializeObject(payload); } public static object BuildPayload(ShopSyncLogEntry entry) { return new { status = entry.Status, source_reachable = entry.SourceReachable ? 1 : 0, products_upserted = entry.ProductsUpserted, products_withdrawn = entry.ProductsWithdrawn, run_started_at = FormatUtcDateTime(entry.RunStartedAt), run_finished_at = FormatUtcDateTime(entry.RunFinishedAt), message = string.IsNullOrEmpty(entry.Message) ? null : entry.Message }; } private static string FormatUtcDateTime(DateTime value) { var utc = value.Kind == DateTimeKind.Utc ? value : value.ToUniversalTime(); return utc.ToString("yyyy-MM-dd HH:mm:ss"); } public void Dispose() { if (_ownsHttpClient) { _httpClient.Dispose(); } } } }