vbytes-lan-registration/src/Registration/Registration.Infra/Repositories/MemberRepository.cs
2026-01-26 21:38:33 +01:00

79 lines
2.4 KiB
C#

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<bool> GetIsRegistered(string ssn);
Task<bool> 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<bool> 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<bool> 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<bool>(sql, new { SsnIndex = ssnIndex });
}
public async Task ClearRegistrations()
{
using var connection = CreateConnection();
var sql = "DELETE FROM members";
await connection.ExecuteAsync(sql);
}
}