29ac56006d
- Add pipeline registry with JSON-based configuration and hot-reload support - Implement manual sync request feature with API, client UI, and database - Improve ConfigManager: connection string dropdown in pipeline editor, step delete/reorder functionality, and fix JSON parsing for ConnectionStrings
203 lines
7.1 KiB
C#
203 lines
7.1 KiB
C#
using Dapper;
|
|
using JdeScoping.DataAccess.Interfaces;
|
|
using JdeScoping.Domain.Models;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace JdeScoping.DataAccess.Services;
|
|
|
|
/// <summary>
|
|
/// Service implementation for managing manual data sync requests.
|
|
/// </summary>
|
|
public sealed class ManualSyncRequestService : IManualSyncRequestService
|
|
{
|
|
private readonly IDbConnectionFactory _connectionFactory;
|
|
private readonly ILogger<ManualSyncRequestService> _logger;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ManualSyncRequestService"/> class.
|
|
/// </summary>
|
|
/// <param name="connectionFactory">The database connection factory.</param>
|
|
/// <param name="logger">The logger instance.</param>
|
|
public ManualSyncRequestService(
|
|
IDbConnectionFactory connectionFactory,
|
|
ILogger<ManualSyncRequestService> logger)
|
|
{
|
|
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
|
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<IReadOnlyList<ManualSyncRequest>> GetRequestsAsync(
|
|
bool pendingOnly = false,
|
|
CancellationToken ct = default)
|
|
{
|
|
_logger.LogDebug("Getting manual sync requests (pendingOnly: {PendingOnly})", pendingOnly);
|
|
|
|
const string sqlAll = """
|
|
SELECT ID, PipelineName, SyncType, RequestDT, RequestedBy,
|
|
CompletedDT, CancelDT, CancelledBy, RowVersion
|
|
FROM dbo.ManualSyncRequest
|
|
ORDER BY RequestDT DESC
|
|
""";
|
|
|
|
const string sqlPending = """
|
|
SELECT ID, PipelineName, SyncType, RequestDT, RequestedBy,
|
|
CompletedDT, CancelDT, CancelledBy, RowVersion
|
|
FROM dbo.ManualSyncRequest
|
|
WHERE CompletedDT IS NULL AND CancelDT IS NULL
|
|
ORDER BY RequestDT ASC
|
|
""";
|
|
|
|
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
|
|
var results = await connection.QueryAsync<ManualSyncRequest>(
|
|
pendingOnly ? sqlPending : sqlAll);
|
|
|
|
var list = results.ToList();
|
|
_logger.LogDebug("Retrieved {Count} manual sync requests", list.Count);
|
|
return list;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<ManualSyncRequest?> GetNextPendingRequestAsync(CancellationToken ct = default)
|
|
{
|
|
_logger.LogDebug("Getting next pending manual sync request");
|
|
|
|
const string sql = """
|
|
SELECT TOP 1 ID, PipelineName, SyncType, RequestDT, RequestedBy,
|
|
CompletedDT, CancelDT, CancelledBy, RowVersion
|
|
FROM dbo.ManualSyncRequest
|
|
WHERE CompletedDT IS NULL AND CancelDT IS NULL
|
|
ORDER BY RequestDT ASC
|
|
""";
|
|
|
|
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
|
|
var result = await connection.QueryFirstOrDefaultAsync<ManualSyncRequest>(sql);
|
|
|
|
if (result != null)
|
|
{
|
|
_logger.LogDebug("Found pending request ID {Id} for pipeline {Pipeline}",
|
|
result.Id, result.PipelineName);
|
|
}
|
|
else
|
|
{
|
|
_logger.LogDebug("No pending manual sync requests found");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<ManualSyncRequest> CreateRequestAsync(
|
|
string pipelineName,
|
|
string syncType,
|
|
string requestedBy,
|
|
CancellationToken ct = default)
|
|
{
|
|
_logger.LogInformation(
|
|
"Creating manual sync request for pipeline {Pipeline}, type {SyncType}, by {User}",
|
|
pipelineName, syncType, requestedBy);
|
|
|
|
const string sql = """
|
|
INSERT INTO dbo.ManualSyncRequest (PipelineName, SyncType, RequestedBy)
|
|
OUTPUT INSERTED.ID, INSERTED.PipelineName, INSERTED.SyncType,
|
|
INSERTED.RequestDT, INSERTED.RequestedBy,
|
|
INSERTED.CompletedDT, INSERTED.CancelDT, INSERTED.CancelledBy,
|
|
INSERTED.RowVersion
|
|
VALUES (@PipelineName, @SyncType, @RequestedBy)
|
|
""";
|
|
|
|
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
|
|
var result = await connection.QuerySingleAsync<ManualSyncRequest>(sql, new
|
|
{
|
|
PipelineName = pipelineName,
|
|
SyncType = syncType,
|
|
RequestedBy = requestedBy
|
|
});
|
|
|
|
_logger.LogInformation("Created manual sync request with ID {Id}", result.Id);
|
|
return result;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<bool> CancelRequestAsync(
|
|
int id,
|
|
string cancelledBy,
|
|
byte[] rowVersion,
|
|
CancellationToken ct = default)
|
|
{
|
|
_logger.LogInformation(
|
|
"Cancelling manual sync request ID {Id} by {User}",
|
|
id, cancelledBy);
|
|
|
|
const string sql = """
|
|
UPDATE dbo.ManualSyncRequest
|
|
SET CancelDT = @CancelDT, CancelledBy = @CancelledBy
|
|
WHERE ID = @Id AND RowVersion = @RowVersion
|
|
AND CompletedDT IS NULL AND CancelDT IS NULL
|
|
""";
|
|
|
|
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
|
|
var affectedRows = await connection.ExecuteAsync(sql, new
|
|
{
|
|
Id = id,
|
|
CancelDT = DateTime.UtcNow,
|
|
CancelledBy = cancelledBy,
|
|
RowVersion = rowVersion
|
|
});
|
|
|
|
var success = affectedRows > 0;
|
|
|
|
if (success)
|
|
{
|
|
_logger.LogInformation("Successfully cancelled manual sync request ID {Id}", id);
|
|
}
|
|
else
|
|
{
|
|
_logger.LogWarning(
|
|
"Failed to cancel manual sync request ID {Id} - already completed/cancelled or version mismatch",
|
|
id);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<bool> CompleteRequestAsync(
|
|
int id,
|
|
byte[] rowVersion,
|
|
CancellationToken ct = default)
|
|
{
|
|
_logger.LogInformation("Completing manual sync request ID {Id}", id);
|
|
|
|
const string sql = """
|
|
UPDATE dbo.ManualSyncRequest
|
|
SET CompletedDT = @CompletedDT
|
|
WHERE ID = @Id AND RowVersion = @RowVersion
|
|
AND CompletedDT IS NULL AND CancelDT IS NULL
|
|
""";
|
|
|
|
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
|
|
var affectedRows = await connection.ExecuteAsync(sql, new
|
|
{
|
|
Id = id,
|
|
CompletedDT = DateTime.UtcNow,
|
|
RowVersion = rowVersion
|
|
});
|
|
|
|
var success = affectedRows > 0;
|
|
|
|
if (success)
|
|
{
|
|
_logger.LogInformation("Successfully completed manual sync request ID {Id}", id);
|
|
}
|
|
else
|
|
{
|
|
_logger.LogWarning(
|
|
"Failed to complete manual sync request ID {Id} - already completed/cancelled or version mismatch",
|
|
id);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
}
|