using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; using StellaOps.Concelier.Core.Jobs; namespace StellaOps.Concelier.Storage.Mongo; [BsonIgnoreExtraElements] public sealed class JobRunDocument { [BsonId] public string Id { get; set; } = string.Empty; [BsonElement("kind")] public string Kind { get; set; } = string.Empty; [BsonElement("status")] public string Status { get; set; } = JobRunStatus.Pending.ToString(); [BsonElement("trigger")] public string Trigger { get; set; } = string.Empty; [BsonElement("parameters")] public BsonDocument Parameters { get; set; } = new(); [BsonElement("parametersHash")] [BsonIgnoreIfNull] public string? ParametersHash { get; set; } [BsonElement("createdAt")] public DateTime CreatedAt { get; set; } [BsonElement("startedAt")] [BsonIgnoreIfNull] public DateTime? StartedAt { get; set; } [BsonElement("completedAt")] [BsonIgnoreIfNull] public DateTime? CompletedAt { get; set; } [BsonElement("error")] [BsonIgnoreIfNull] public string? Error { get; set; } [BsonElement("timeoutMs")] [BsonIgnoreIfNull] public long? TimeoutMs { get; set; } [BsonElement("leaseMs")] [BsonIgnoreIfNull] public long? LeaseMs { get; set; } } internal static class JobRunDocumentExtensions { public static JobRunDocument FromRequest(JobRunCreateRequest request, Guid id) { return new JobRunDocument { Id = id.ToString(), Kind = request.Kind, Status = JobRunStatus.Pending.ToString(), Trigger = request.Trigger, Parameters = request.Parameters is { Count: > 0 } ? BsonDocument.Parse(JsonSerializer.Serialize(request.Parameters)) : new BsonDocument(), ParametersHash = request.ParametersHash, CreatedAt = request.CreatedAt.UtcDateTime, TimeoutMs = request.Timeout?.MillisecondsFromTimespan(), LeaseMs = request.LeaseDuration?.MillisecondsFromTimespan(), }; } public static JobRunSnapshot ToSnapshot(this JobRunDocument document) { var parameters = document.Parameters?.ToDictionary() ?? new Dictionary(); return new JobRunSnapshot( Guid.Parse(document.Id), document.Kind, Enum.Parse(document.Status, ignoreCase: true), DateTime.SpecifyKind(document.CreatedAt, DateTimeKind.Utc), document.StartedAt.HasValue ? DateTime.SpecifyKind(document.StartedAt.Value, DateTimeKind.Utc) : null, document.CompletedAt.HasValue ? DateTime.SpecifyKind(document.CompletedAt.Value, DateTimeKind.Utc) : null, document.Trigger, document.ParametersHash, document.Error, document.TimeoutMs?.MillisecondsToTimespan(), document.LeaseMs?.MillisecondsToTimespan(), parameters); } public static Dictionary ToDictionary(this BsonDocument document) { return document.Elements.ToDictionary( static element => element.Name, static element => element.Value switch { BsonString s => (object?)s.AsString, BsonBoolean b => b.AsBoolean, BsonInt32 i => i.AsInt32, BsonInt64 l => l.AsInt64, BsonDouble d => d.AsDouble, BsonNull => null, BsonArray array => array.Select(v => v.IsBsonDocument ? ToDictionary(v.AsBsonDocument) : (object?)v.ToString()).ToArray(), BsonDocument doc => ToDictionary(doc), _ => element.Value.ToString(), }); } private static long MillisecondsFromTimespan(this TimeSpan timeSpan) => (long)timeSpan.TotalMilliseconds; private static TimeSpan MillisecondsToTimespan(this long milliseconds) => TimeSpan.FromMilliseconds(milliseconds); }