using System;
using MySqlConnector;
using Serilog;
using SyncEngine.Configuration;
namespace SyncEngine.MySql
{
public sealed class ShopSyncLogClient
{
private readonly ILogger _logger;
private readonly SyncOptions _options;
private readonly IShopSyncLogApiClient _apiClient;
public ShopSyncLogClient(ILogger logger, SyncOptions options)
: this(logger, options, options != null && options.HasShopSyncApiFallback
? new ShopSyncLogApiClient(options, logger)
: null)
{
}
internal ShopSyncLogClient(ILogger logger, SyncOptions options, IShopSyncLogApiClient apiClient)
{
_logger = logger;
_options = options;
_apiClient = apiClient;
}
/// MySQL INSERT only; throws on failure.
public void WriteLog(ShopSyncLogEntry entry, MySqlConnectionFactory mysqlFactory)
{
if (MySqlWriteForTests != null)
{
MySqlWriteForTests(entry, mysqlFactory);
_logger.Information("Inserted shop_sync_log row: {Status}", entry.Status);
return;
}
using (var conn = mysqlFactory.Create())
{
conn.Open();
var sql = @"
INSERT INTO shop_sync_log
(run_started_at, run_finished_at, status, source_reachable, products_upserted, products_withdrawn, message)
VALUES (@start, @finish, @status, @reachable, @upserted, @withdrawn, @message)";
using (var cmd = new MySqlCommand(sql, conn))
{
BindInsertParameters(cmd, entry);
cmd.ExecuteNonQuery();
}
}
_logger.Information("Inserted shop_sync_log row: {Status}", entry.Status);
}
/// MySQL first; on any write failure, POST JSON to CI4 API when configured.
public void WriteLogWithFallback(ShopSyncLogEntry entry, MySqlConnectionFactory mysqlFactory, SyncOptions options)
{
try
{
WriteLog(entry, mysqlFactory);
}
catch (Exception mysqlEx)
{
if (options == null || !options.HasShopSyncApiFallback)
{
throw;
}
var apiClient = _apiClient ?? new ShopSyncLogApiClient(options, _logger);
try
{
apiClient.PostLog(entry);
_logger.Information("shop_sync_log written via API fallback: {Status}", entry.Status);
}
catch (Exception apiEx)
{
var localPath = ShopSyncLogLocalWriter.WriteFailedApiPost(entry, mysqlEx, apiEx);
_logger.Warning(apiEx, "shop-sync/log API fallback failed; run data written to {LocalPath}", localPath);
throw new AggregateException(
"Failed to write shop_sync_log via MySQL and API fallback.",
mysqlEx,
apiEx);
}
}
}
/// Test seam to simulate MySQL write success or failure without a live database.
internal Action MySqlWriteForTests { get; set; }
/// Maps entry fields to INSERT parameters (testable without a live connection).
public static void BindInsertParameters(MySqlCommand cmd, ShopSyncLogEntry entry)
{
cmd.Parameters.AddWithValue("@start", entry.RunStartedAt);
cmd.Parameters.AddWithValue("@finish", entry.RunFinishedAt);
cmd.Parameters.AddWithValue("@status", entry.Status);
cmd.Parameters.AddWithValue("@reachable", entry.SourceReachable ? 1 : 0);
cmd.Parameters.AddWithValue("@upserted", entry.ProductsUpserted);
cmd.Parameters.AddWithValue("@withdrawn", entry.ProductsWithdrawn);
cmd.Parameters.AddWithValue("@message", (object)entry.Message ?? DBNull.Value);
}
}
public sealed class ShopSyncLogEntry
{
public DateTime RunStartedAt { get; set; }
public DateTime RunFinishedAt { get; set; }
public string Status { get; set; }
public bool SourceReachable { get; set; }
public int ProductsUpserted { get; set; }
public int ProductsWithdrawn { get; set; }
public string Message { get; set; }
}
}