51 lines
1.9 KiB
C#
51 lines
1.9 KiB
C#
using System.Globalization;
|
|
using StackExchange.Redis;
|
|
|
|
namespace w4c_workflows.Services.Triggers;
|
|
|
|
/// <summary>
|
|
/// Stores the last time a scheduled trigger (cron/interval) fired, per
|
|
/// workflow. Redis-backed in v1 (key <c>wf:{tenant}:triggers:{workflowId}:lastFire</c>);
|
|
/// ephemeral by design — losing it only re-fires a workflow once, which is safe
|
|
/// because delivery is at-least-once and the run lifecycle dedups via
|
|
/// correlation id.
|
|
/// </summary>
|
|
public interface ITriggerState
|
|
{
|
|
Task<DateTimeOffset?> GetLastFireAsync(string tenantId, Guid workflowId, CancellationToken ct);
|
|
Task SetLastFireAsync(string tenantId, Guid workflowId, DateTimeOffset at, CancellationToken ct);
|
|
}
|
|
|
|
public class RedisTriggerState : ITriggerState
|
|
{
|
|
private readonly IDatabase _db;
|
|
private readonly TimeSpan _ttl;
|
|
|
|
public RedisTriggerState(IConnectionMultiplexer redis, IConfiguration config)
|
|
{
|
|
_db = redis.GetDatabase();
|
|
_ttl = TimeSpan.FromDays(ParseInt(config["Workflows:TriggerStateTtlDays"], 90));
|
|
}
|
|
|
|
public async Task<DateTimeOffset?> GetLastFireAsync(string tenantId, Guid workflowId, CancellationToken ct)
|
|
{
|
|
var value = await _db.StringGetAsync(KeyFor(tenantId, workflowId));
|
|
if (value.IsNullOrEmpty)
|
|
return null;
|
|
|
|
return DateTimeOffset.TryParse(value.ToString(), CultureInfo.InvariantCulture,
|
|
DateTimeStyles.RoundtripKind, out var parsed)
|
|
? parsed
|
|
: null;
|
|
}
|
|
|
|
public Task SetLastFireAsync(string tenantId, Guid workflowId, DateTimeOffset at, CancellationToken ct)
|
|
=> _db.StringSetAsync(KeyFor(tenantId, workflowId), at.ToString("O"), _ttl);
|
|
|
|
private static RedisKey KeyFor(string tenantId, Guid workflowId)
|
|
=> $"wf:{tenantId}:triggers:{workflowId}:lastFire";
|
|
|
|
private static int ParseInt(string? text, int fallback)
|
|
=> int.TryParse(text, out var value) && value > 0 ? value : fallback;
|
|
}
|