Files
itworx-knowledge-hub-Public/src/backend/ItWorx.KnowledgeHub.Domain/Sources/DataSource.cs
T

88 lines
3.8 KiB
C#

using System.Text.RegularExpressions;
namespace ItWorx.KnowledgeHub.Domain.Sources;
public enum DataSourceKind { Inventory, Gitea, KnowledgeClient, ManualIntake, LeafWiki, CuratedPackage }
/// <summary>Operator registration and observed evidence; credentials never enter this aggregate.</summary>
public sealed class DataSource
{
private DataSource() { }
public Guid Id { get; private set; }
public Guid WorkspaceId { get; private set; }
public string SourceKey { get; private set; } = "";
public string DisplayName { get; private set; } = "";
public DataSourceKind Kind { get; private set; }
public bool Enabled { get; private set; } = true;
public int ExpectedIntervalHours { get; private set; }
public int Version { get; private set; } = 1;
public DateTimeOffset CreatedAt { get; private set; }
public DateTimeOffset? LastAttemptAt { get; private set; }
public DateTimeOffset? LastSuccessAt { get; private set; }
public DateTimeOffset? LastFailureAt { get; private set; }
public DateTimeOffset? DataObservedAt { get; private set; }
public long? RecordCount { get; private set; }
public string? LastErrorCode { get; private set; }
public string? LastCorrelationId { get; private set; }
public static DataSource Create(Guid workspaceId, string sourceKey, string displayName, DataSourceKind kind, int expectedIntervalHours, DateTimeOffset now)
{
if (workspaceId == Guid.Empty || !Enum.IsDefined(kind) || !Regex.IsMatch(sourceKey, "^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,199}$", RegexOptions.CultureInvariant))
{
throw new ArgumentException("Invalid source identity.");
}
var source = new DataSource { Id = Guid.NewGuid(), WorkspaceId = workspaceId, SourceKey = sourceKey, Kind = kind, CreatedAt = now, Version = 0 };
source.Configure(displayName, true, expectedIntervalHours);
return source;
}
public void Configure(string displayName, bool enabled, int expectedIntervalHours)
{
if (string.IsNullOrWhiteSpace(displayName) || displayName.Length > 120 || displayName.Any(char.IsControl) || expectedIntervalHours is < 1 or > 8760)
{
throw new ArgumentException("Invalid source policy.");
}
DisplayName = displayName.Trim();
Enabled = enabled;
ExpectedIntervalHours = expectedIntervalHours;
Version++;
}
public void Observe(bool success, long? recordCount, string? errorCode, string? correlationId, DateTimeOffset observedAt, DateTimeOffset? dataObservedAt = null)
{
if (recordCount < 0 || (!success && errorCode is not ("authentication" or "connectivity" or "validation" or "unexpected"))
|| (LastAttemptAt is { } previous && observedAt < previous)
|| dataObservedAt > observedAt || correlationId?.Length > 100 || correlationId?.Any(char.IsControl) == true)
{
throw new ArgumentException("Invalid source observation.");
}
LastAttemptAt = observedAt;
LastCorrelationId = correlationId;
if (success)
{
LastSuccessAt = observedAt;
var captured = dataObservedAt ?? observedAt;
if (DataObservedAt is null || captured >= DataObservedAt)
{
DataObservedAt = captured;
RecordCount = recordCount;
}
LastErrorCode = null;
}
else
{
LastFailureAt = observedAt;
LastErrorCode = errorCode;
}
Version++;
}
public string GetState(DateTimeOffset now)
{
if (!Enabled) return "Disabled";
if (LastErrorCode is not null) return "Failed";
if (DataObservedAt is null || DataObservedAt > now) return "Unknown";
return now - DataObservedAt.Value > TimeSpan.FromHours(ExpectedIntervalHours) ? "Stale" : "Current";
}
}