using Microsoft.Extensions.Configuration; using System.Security.Cryptography; using System.Text; using Dapper; using Npgsql; namespace Registration.Infra.Repositories; public interface IMemberRepository { Task EnsureCreated(); Task GetIsRegistered(string ssn); Task AddRegistration(string ssn); Task ClearRegistrations(); } public class MemberRepository(IConfiguration configuration) : IMemberRepository { private readonly string _connectionString = configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); private readonly string _pepper = configuration["Security:SsnPepper"] ?? throw new InvalidOperationException("Security pepper 'SsnPepper' not found."); private NpgsqlConnection CreateConnection() => new(_connectionString); private string ComputeBlindIndex(string ssn) { using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_pepper)); var hashedBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(ssn)); return Convert.ToBase64String(hashedBytes); } public async Task EnsureCreated() { using var connection = CreateConnection(); var sql = @" CREATE TABLE IF NOT EXISTS members ( id SERIAL PRIMARY KEY, ssn_index VARCHAR(255) NOT NULL UNIQUE ); CREATE INDEX IF NOT EXISTS idx_members_ssn_index ON members(ssn_index); "; await connection.ExecuteAsync(sql); } public async Task AddRegistration(string ssn) { using var connection = CreateConnection(); var sql = "INSERT INTO members (ssn_index) VALUES (@SsnIndex) ON CONFLICT DO NOTHING"; var ssnIndex = ComputeBlindIndex(ssn); var rows = await connection.ExecuteAsync(sql, new { SsnIndex = ssnIndex }); return rows > 0; } public async Task GetIsRegistered(string ssn) { using var connection = CreateConnection(); var ssnIndex = ComputeBlindIndex(ssn); var sql = "SELECT EXISTS(SELECT 1 FROM members WHERE ssn_index = @SsnIndex)"; return await connection.ExecuteScalarAsync(sql, new { SsnIndex = ssnIndex }); } public async Task ClearRegistrations() { using var connection = CreateConnection(); var sql = "DELETE FROM members"; await connection.ExecuteAsync(sql); } }