using System.Data.Common; using System.Globalization; using System.Text.Json.Nodes; using Eis.Infrastructure.Data; namespace Eis.Infrastructure.Candidate; internal sealed record CandidateAdmissionRecord( string Id, string Kind, string ExamId, string? UserId, string? SchoolId, string Status, JsonObject Payload, string CreatedAt, string UpdatedAt); internal sealed record CandidateAdmissionSchool(string Id, string Code, string Name); internal sealed record CandidateAdmissionSnapshot( IReadOnlyList Records, IReadOnlyDictionary Schools); internal sealed class CandidateAdmissionRepository(IRelationalConnectionFactory connectionFactory) { public async Task LoadAsync(CancellationToken cancellationToken) { await using var connection = await connectionFactory.OpenAsync(cancellationToken); var records = new List(); await using (var command = connection.CreateCommand()) { command.CommandText = "SELECT id, kind, exam_id, user_id, school_id, status, payload_json, created_at, updated_at FROM admission_records ORDER BY created_at, id"; await using var reader = await command.ExecuteReaderAsync(cancellationToken); while (await reader.ReadAsync(cancellationToken)) { records.Add(new CandidateAdmissionRecord( ReadString(reader, "id"), ReadString(reader, "kind"), ReadString(reader, "exam_id"), ReadOptionalString(reader, "user_id"), ReadOptionalString(reader, "school_id"), ReadString(reader, "status"), JsonNode.Parse(ReadString(reader, "payload_json"))?.AsObject() ?? new JsonObject(), ReadString(reader, "created_at"), ReadString(reader, "updated_at"))); } } var schools = new List(); await using (var command = connection.CreateCommand()) { command.CommandText = "SELECT id, code, name FROM schools ORDER BY id"; await using var reader = await command.ExecuteReaderAsync(cancellationToken); while (await reader.ReadAsync(cancellationToken)) { schools.Add(new CandidateAdmissionSchool( ReadString(reader, "id"), ReadString(reader, "code"), ReadString(reader, "name"))); } } return new CandidateAdmissionSnapshot( records, schools.ToDictionary(item => item.Id, StringComparer.Ordinal)); } public async Task SavePreferenceAsync( CandidateAdmissionRecord record, CancellationToken cancellationToken) { await using var connection = await connectionFactory.OpenAsync(cancellationToken); await using var transaction = await connection.BeginTransactionAsync(cancellationToken); try { await using (var delete = connection.CreateCommand()) { delete.Transaction = transaction; delete.CommandText = "DELETE FROM admission_records WHERE id = @id"; AddParameter(delete, "@id", record.Id); await delete.ExecuteNonQueryAsync(cancellationToken); } await using (var insert = connection.CreateCommand()) { insert.Transaction = transaction; insert.CommandText = """ INSERT INTO admission_records ( id, kind, exam_id, user_id, school_id, status, payload_json, created_at, updated_at ) VALUES ( @id, @kind, @examId, @userId, @schoolId, @status, @payload, @createdAt, @updatedAt ) """; AddParameter(insert, "@id", record.Id); AddParameter(insert, "@kind", record.Kind); AddParameter(insert, "@examId", record.ExamId); AddParameter(insert, "@userId", record.UserId); AddParameter(insert, "@schoolId", record.SchoolId); AddParameter(insert, "@status", record.Status); AddParameter(insert, "@payload", record.Payload.ToJsonString()); AddParameter(insert, "@createdAt", record.CreatedAt); AddParameter(insert, "@updatedAt", record.UpdatedAt); await insert.ExecuteNonQueryAsync(cancellationToken); } await transaction.CommitAsync(cancellationToken); } catch { await transaction.RollbackAsync(cancellationToken); throw; } } private static void AddParameter(DbCommand command, string name, object? value) { var parameter = command.CreateParameter(); parameter.ParameterName = name; parameter.Value = value ?? DBNull.Value; command.Parameters.Add(parameter); } private static string ReadString(DbDataReader reader, string name) => Convert.ToString(reader.GetValue(reader.GetOrdinal(name)), CultureInfo.InvariantCulture) ?? string.Empty; private static string? ReadOptionalString(DbDataReader reader, string name) { var ordinal = reader.GetOrdinal(name); return reader.IsDBNull(ordinal) ? null : Convert.ToString(reader.GetValue(ordinal), CultureInfo.InvariantCulture); } }