using System.Data.Common; using System.Data.OleDb; using System.Runtime.Versioning; using Campaign_Tracker.Server.LegacyData.Models; namespace Campaign_Tracker.Server.LegacyData; /// /// Read-only OleDb implementation for legacy Access-derived data. /// All SQL is parameterized and validated as SELECT-only before execution. /// [SupportedOSPlatform("windows")] public sealed class OleDbLegacyDataAccess : ILegacyDataAccess { private readonly string _connectionString; public OleDbLegacyDataAccess(string connectionString) { if (string.IsNullOrWhiteSpace(connectionString)) { throw new ArgumentException("Legacy database connection string is required.", nameof(connectionString)); } _connectionString = connectionString; } public async Task GetJurisdictionAsync( string jCode, CancellationToken cancellationToken = default) { const string sql = """ SELECT JCode, Name, MailingAddress, CityStateZip, Phone, Email FROM Jurisdiction WHERE JCode = ? """; var results = await QueryAsync(sql, [jCode], MapJurisdiction, cancellationToken); return results.FirstOrDefault(); } public Task> GetAllJurisdictionsAsync( CancellationToken cancellationToken = default) { const string sql = """ SELECT JCode, Name, MailingAddress, CityStateZip, Phone, Email FROM Jurisdiction ORDER BY JCode """; return QueryAsync(sql, [], MapJurisdiction, cancellationToken); } public async Task GetContactByIdAsync( int id, CancellationToken cancellationToken = default) { const string sql = """ SELECT ID, JURISCODE, ContactName, Title, Email, Phone1, Phone2, MailingAddress, MailingAddress2, MailingAddress3, BusinessAddress, BusinessAddress2, BusinessAddress3, TownshipName, TownshipNum FROM Contacts WHERE ID = ? """; var results = await QueryAsync(sql, [id], MapContact, cancellationToken); return results.FirstOrDefault(); } public Task> GetContactsByJurisdictionAsync( string jCode, CancellationToken cancellationToken = default) { const string sql = """ SELECT ID, JURISCODE, ContactName, Title, Email, Phone1, Phone2, MailingAddress, MailingAddress2, MailingAddress3, BusinessAddress, BusinessAddress2, BusinessAddress3, TownshipName, TownshipNum FROM Contacts WHERE JURISCODE = ? ORDER BY ID """; return QueryAsync(sql, [jCode], MapContact, cancellationToken); } public async Task GetKitByIdAsync( int id, CancellationToken cancellationToken = default) { const string sql = """ SELECT ID, Jcode, JobNumber, JobType, Status, Filename, Cass, InkJetJob, CreatedOn, ExportedToSnailWorks, LabelsPrinted, OfficeCopiesAmount, InboundStid, OutboundStid FROM Kit WHERE ID = ? """; var results = await QueryAsync(sql, [id], MapKit, cancellationToken); return results.FirstOrDefault(); } public Task> GetKitsByJurisdictionAsync( string jCode, CancellationToken cancellationToken = default) { const string sql = """ SELECT ID, Jcode, JobNumber, JobType, Status, Filename, Cass, InkJetJob, CreatedOn, ExportedToSnailWorks, LabelsPrinted, OfficeCopiesAmount, InboundStid, OutboundStid FROM Kit WHERE Jcode = ? ORDER BY ID """; return QueryAsync(sql, [jCode], MapKit, cancellationToken); } public Task> GetKitLabelsByKitAsync( int kitId, CancellationToken cancellationToken = default) { const string sql = """ SELECT ID, KitID, InBoundImb, InBoundImbDigits, InBoundSerial, OutboundImb, OutboundImbDigits, OutboundSerial, SetNumber FROM KitLabels WHERE KitID = ? ORDER BY ID """; return QueryAsync(sql, [kitId], MapKitLabel, cancellationToken); } private async Task> QueryAsync( string sql, IReadOnlyList parameters, Func map, CancellationToken cancellationToken) { ReadOnlyCommandGuard.Validate(sql); try { await using var connection = new OleDbConnection(_connectionString); await connection.OpenAsync(cancellationToken); await using var command = connection.CreateCommand(); command.CommandText = sql; foreach (var parameter in parameters) { command.Parameters.AddWithValue(string.Empty, parameter); } var results = new List(); await using var reader = await command.ExecuteReaderAsync(cancellationToken); while (await reader.ReadAsync(cancellationToken)) { results.Add(map(reader)); } return results; } catch (LegacyDataAccessException) { throw; } catch (Exception ex) { throw new LegacyDataAccessException("Legacy read operation failed.", ex); } } private static LegacyJurisdiction MapJurisdiction(DbDataReader reader) => new( GetRequiredString(reader, "JCode"), GetString(reader, "Name"), GetString(reader, "MailingAddress"), GetString(reader, "CityStateZip"), GetString(reader, "Phone"), GetString(reader, "Email")); private static LegacyContact MapContact(DbDataReader reader) => new( GetRequiredInt(reader, "ID"), GetRequiredString(reader, "JURISCODE"), GetString(reader, "ContactName"), GetString(reader, "Title"), GetString(reader, "Email"), GetString(reader, "Phone1"), GetString(reader, "Phone2"), GetString(reader, "MailingAddress"), GetString(reader, "MailingAddress2"), GetString(reader, "MailingAddress3"), GetString(reader, "BusinessAddress"), GetString(reader, "BusinessAddress2"), GetString(reader, "BusinessAddress3"), GetString(reader, "TownshipName"), GetString(reader, "TownshipNum")); private static LegacyKit MapKit(DbDataReader reader) => new( GetRequiredInt(reader, "ID"), GetRequiredString(reader, "Jcode"), GetString(reader, "JobNumber"), GetString(reader, "JobType"), GetString(reader, "Status"), GetString(reader, "Filename"), GetBool(reader, "Cass"), GetBool(reader, "InkJetJob"), GetDateTime(reader, "CreatedOn"), GetDateTime(reader, "ExportedToSnailWorks"), GetDateTime(reader, "LabelsPrinted"), GetInt(reader, "OfficeCopiesAmount"), GetString(reader, "InboundStid"), GetString(reader, "OutboundStid")); private static LegacyKitLabel MapKitLabel(DbDataReader reader) => new( GetRequiredInt(reader, "ID"), GetRequiredInt(reader, "KitID"), GetString(reader, "InBoundImb"), GetString(reader, "InBoundImbDigits"), GetString(reader, "InBoundSerial"), GetString(reader, "OutboundImb"), GetString(reader, "OutboundImbDigits"), GetString(reader, "OutboundSerial"), GetDouble(reader, "SetNumber")); private static string GetRequiredString(DbDataReader reader, string name) { var value = GetString(reader, name); return string.IsNullOrWhiteSpace(value) ? throw new LegacyDataAccessException($"Required legacy join key {name} was null or empty.") : value; } private static int GetRequiredInt(DbDataReader reader, string name) => GetInt(reader, name) ?? throw new LegacyDataAccessException($"Required legacy join key {name} was null."); private static string? GetString(DbDataReader reader, string name) { var value = GetValue(reader, name); return value is null ? null : Convert.ToString(value); } private static int? GetInt(DbDataReader reader, string name) { var value = GetValue(reader, name); return value is null ? null : Convert.ToInt32(value); } private static double? GetDouble(DbDataReader reader, string name) { var value = GetValue(reader, name); return value is null ? null : Convert.ToDouble(value); } private static bool GetBool(DbDataReader reader, string name) { var value = GetValue(reader, name); return value is not null && Convert.ToBoolean(value); } private static DateTime? GetDateTime(DbDataReader reader, string name) { var value = GetValue(reader, name); return value is null ? null : Convert.ToDateTime(value); } private static object? GetValue(DbDataReader reader, string name) { var ordinal = reader.GetOrdinal(name); return reader.IsDBNull(ordinal) ? null : reader.GetValue(ordinal); } }