using ScadaLink.Commons.Entities.Audit; using ScadaLink.Commons.Types; namespace ScadaLink.Communication.Grpc; /// /// Canonical bridge for Site Call Audit (#22) operational rows between the /// wire-format exchanged on the /// CachedCallTelemetry packet and the in-process /// persistence entity central writes into the SiteCalls table. /// /// /// /// This mapper lives in ScadaLink.Communication (which owns the generated /// and references Commons for /// ) so both SiteStreamGrpcServer and /// ScadaLink.AuditLog can share one implementation without the /// project-reference cycle that would result from hosting it in /// ScadaLink.AuditLog (AuditLog → Communication, never the reverse). /// Mirrors the sibling . /// /// /// Only the DTO→entity direction is provided: nothing in the system maps a /// back onto the wire (sites emit the operational state /// from SiteCallOperational, never from the central /// entity), so an entity→DTO method would be dead code. /// /// /// String nullability convention: proto3 scalar strings cannot be absent, so the /// optional rehydrates from an empty string back /// to null. The optional HttpStatus and TerminalAtUtc use proto /// wrappers so they preserve true null semantics. /// /// public static class SiteCallDtoMapper { /// /// Reconstructs a persistence entity from its /// wire-format DTO. An empty LastError rehydrates as null; absent /// HttpStatus/TerminalAtUtc wrappers stay null. /// /// /// is stamped here as a placeholder /// (); the central ingest actor overwrites it /// inside the dual-write transaction so the AuditLog and SiteCalls rows /// share one instant. The value sent on the wire is informational only. /// public static SiteCall FromDto(SiteCallOperationalDto dto) { ArgumentNullException.ThrowIfNull(dto); return new SiteCall { TrackedOperationId = TrackedOperationId.Parse(dto.TrackedOperationId), Channel = dto.Channel, Target = dto.Target, SourceSite = dto.SourceSite, SourceNode = string.IsNullOrEmpty(dto.SourceNode) ? null : dto.SourceNode, Status = dto.Status, RetryCount = dto.RetryCount, LastError = string.IsNullOrEmpty(dto.LastError) ? null : dto.LastError, HttpStatus = dto.HttpStatus, CreatedAtUtc = DateTime.SpecifyKind(dto.CreatedAtUtc.ToDateTime(), DateTimeKind.Utc), UpdatedAtUtc = DateTime.SpecifyKind(dto.UpdatedAtUtc.ToDateTime(), DateTimeKind.Utc), TerminalAtUtc = dto.TerminalAtUtc is null ? null : DateTime.SpecifyKind(dto.TerminalAtUtc.ToDateTime(), DateTimeKind.Utc), IngestedAtUtc = DateTime.UtcNow, // overwritten by AuditLogIngestActor }; } }