using System; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; namespace StellaOps.Feedser.Storage.Mongo.Documents; [BsonIgnoreExtraElements] public sealed class DocumentDocument { [BsonId] public string Id { get; set; } = string.Empty; [BsonElement("sourceName")] public string SourceName { get; set; } = string.Empty; [BsonElement("uri")] public string Uri { get; set; } = string.Empty; [BsonElement("fetchedAt")] public DateTime FetchedAt { get; set; } [BsonElement("sha256")] public string Sha256 { get; set; } = string.Empty; [BsonElement("status")] public string Status { get; set; } = string.Empty; [BsonElement("contentType")] [BsonIgnoreIfNull] public string? ContentType { get; set; } [BsonElement("headers")] [BsonIgnoreIfNull] public BsonDocument? Headers { get; set; } [BsonElement("metadata")] [BsonIgnoreIfNull] public BsonDocument? Metadata { get; set; } [BsonElement("etag")] [BsonIgnoreIfNull] public string? Etag { get; set; } [BsonElement("lastModified")] [BsonIgnoreIfNull] public DateTime? LastModified { get; set; } [BsonElement("expiresAt")] [BsonIgnoreIfNull] public DateTime? ExpiresAt { get; set; } [BsonElement("gridFsId")] [BsonIgnoreIfNull] public ObjectId? GridFsId { get; set; } } internal static class DocumentDocumentExtensions { public static DocumentDocument FromRecord(DocumentRecord record) { return new DocumentDocument { Id = record.Id.ToString(), SourceName = record.SourceName, Uri = record.Uri, FetchedAt = record.FetchedAt.UtcDateTime, Sha256 = record.Sha256, Status = record.Status, ContentType = record.ContentType, Headers = ToBson(record.Headers), Metadata = ToBson(record.Metadata), Etag = record.Etag, LastModified = record.LastModified?.UtcDateTime, GridFsId = record.GridFsId, ExpiresAt = record.ExpiresAt?.UtcDateTime, }; } public static DocumentRecord ToRecord(this DocumentDocument document) { IReadOnlyDictionary? headers = null; if (document.Headers is not null) { headers = document.Headers.Elements.ToDictionary( static e => e.Name, static e => e.Value?.ToString() ?? string.Empty, StringComparer.Ordinal); } IReadOnlyDictionary? metadata = null; if (document.Metadata is not null) { metadata = document.Metadata.Elements.ToDictionary( static e => e.Name, static e => e.Value?.ToString() ?? string.Empty, StringComparer.Ordinal); } return new DocumentRecord( Guid.Parse(document.Id), document.SourceName, document.Uri, DateTime.SpecifyKind(document.FetchedAt, DateTimeKind.Utc), document.Sha256, document.Status, document.ContentType, headers, metadata, document.Etag, document.LastModified.HasValue ? DateTime.SpecifyKind(document.LastModified.Value, DateTimeKind.Utc) : null, document.GridFsId, document.ExpiresAt.HasValue ? DateTime.SpecifyKind(document.ExpiresAt.Value, DateTimeKind.Utc) : null); } private static BsonDocument? ToBson(IReadOnlyDictionary? values) { if (values is null) { return null; } var document = new BsonDocument(); foreach (var kvp in values) { document[kvp.Key] = kvp.Value; } return document; } }