feat(infra): BATCH 4 - Permiso/RolPermiso repos Dapper + tests integracion [UDT-005]

This commit is contained in:
2026-04-15 15:39:25 -03:00
parent 704794a2e2
commit be2257a9bf
8 changed files with 794 additions and 2 deletions

View File

@@ -29,6 +29,8 @@ public static class DependencyInjection
services.AddScoped<IUsuarioRepository, UsuarioRepository>();
services.AddScoped<IRefreshTokenRepository, RefreshTokenRepository>();
services.AddScoped<IRolRepository, RolRepository>();
services.AddScoped<IPermisoRepository, PermisoRepository>();
services.AddScoped<IRolPermisoRepository, RolPermisoRepository>();
// JWT Options — bound lazily via IOptions so tests can override via ConfigureWebHost
services.Configure<JwtOptions>(configuration.GetSection("Jwt"));

View File

@@ -0,0 +1,85 @@
using Dapper;
using SIGCM2.Application.Abstractions.Persistence;
using SIGCM2.Domain.Entities;
namespace SIGCM2.Infrastructure.Persistence;
public sealed class PermisoRepository : IPermisoRepository
{
private readonly SqlConnectionFactory _connectionFactory;
public PermisoRepository(SqlConnectionFactory connectionFactory)
{
_connectionFactory = connectionFactory;
}
public async Task<IReadOnlyList<Permiso>> ListAsync(CancellationToken ct = default)
{
const string sql = """
SELECT Id, Codigo, Nombre, Descripcion, Modulo, Activo, FechaCreacion
FROM dbo.Permiso
ORDER BY Id
""";
await using var connection = _connectionFactory.CreateConnection();
await connection.OpenAsync(ct);
var rows = await connection.QueryAsync<PermisoRow>(sql);
return rows.Select(MapRow).ToList();
}
public async Task<Permiso?> GetByCodigoAsync(string codigo, CancellationToken ct = default)
{
const string sql = """
SELECT Id, Codigo, Nombre, Descripcion, Modulo, Activo, FechaCreacion
FROM dbo.Permiso
WHERE Codigo = @Codigo
""";
await using var connection = _connectionFactory.CreateConnection();
await connection.OpenAsync(ct);
var row = await connection.QuerySingleOrDefaultAsync<PermisoRow>(sql, new { Codigo = codigo });
return row is null ? null : MapRow(row);
}
public async Task<IReadOnlyList<Permiso>> GetByCodigosAsync(
IEnumerable<string> codigos,
CancellationToken ct = default)
{
var codigosList = codigos.ToList();
if (codigosList.Count == 0)
return Array.Empty<Permiso>();
const string sql = """
SELECT Id, Codigo, Nombre, Descripcion, Modulo, Activo, FechaCreacion
FROM dbo.Permiso
WHERE Codigo IN @Codigos
""";
await using var connection = _connectionFactory.CreateConnection();
await connection.OpenAsync(ct);
var rows = await connection.QueryAsync<PermisoRow>(sql, new { Codigos = codigosList });
return rows.Select(MapRow).ToList();
}
private static Permiso MapRow(PermisoRow row)
=> Permiso.ForRead(
id: row.Id,
codigo: row.Codigo,
nombre: row.Nombre,
descripcion: row.Descripcion,
modulo: row.Modulo,
activo: row.Activo,
fechaCreacion: row.FechaCreacion);
private sealed record PermisoRow(
int Id,
string Codigo,
string Nombre,
string? Descripcion,
string Modulo,
bool Activo,
DateTime FechaCreacion);
}

View File

@@ -0,0 +1,97 @@
using Dapper;
using SIGCM2.Application.Abstractions.Persistence;
using SIGCM2.Domain.Entities;
namespace SIGCM2.Infrastructure.Persistence;
public sealed class RolPermisoRepository : IRolPermisoRepository
{
private readonly SqlConnectionFactory _connectionFactory;
public RolPermisoRepository(SqlConnectionFactory connectionFactory)
{
_connectionFactory = connectionFactory;
}
public async Task<IReadOnlyList<Permiso>> GetByRolCodigoAsync(
string rolCodigo,
CancellationToken ct = default)
{
const string sql = """
SELECT p.Id, p.Codigo, p.Nombre, p.Descripcion, p.Modulo, p.Activo, p.FechaCreacion
FROM dbo.RolPermiso rp
JOIN dbo.Rol r ON r.Id = rp.RolId
JOIN dbo.Permiso p ON p.Id = rp.PermisoId
WHERE r.Codigo = @RolCodigo
ORDER BY p.Id
""";
await using var connection = _connectionFactory.CreateConnection();
await connection.OpenAsync(ct);
var rows = await connection.QueryAsync<PermisoRow>(sql, new { RolCodigo = rolCodigo });
return rows.Select(MapRow).ToList();
}
public async Task ReplaceForRolAsync(
int rolId,
IEnumerable<int> permisoIds,
CancellationToken ct = default)
{
var ids = permisoIds.ToList();
await using var connection = _connectionFactory.CreateConnection();
await connection.OpenAsync(ct);
await using var transaction = await connection.BeginTransactionAsync(ct);
try
{
// Step 1: Delete all existing permisos for this rol
await connection.ExecuteAsync(
"DELETE FROM dbo.RolPermiso WHERE RolId = @RolId",
new { RolId = rolId },
transaction);
// Step 2: Insert the new set (bulk via multi-row VALUES)
if (ids.Count > 0)
{
foreach (var permisoId in ids)
{
await connection.ExecuteAsync(
"""
INSERT INTO dbo.RolPermiso (RolId, PermisoId)
VALUES (@RolId, @PermisoId)
""",
new { RolId = rolId, PermisoId = permisoId },
transaction);
}
}
await transaction.CommitAsync(ct);
}
catch
{
await transaction.RollbackAsync(ct);
throw;
}
}
private static Permiso MapRow(PermisoRow row)
=> Permiso.ForRead(
id: row.Id,
codigo: row.Codigo,
nombre: row.Nombre,
descripcion: row.Descripcion,
modulo: row.Modulo,
activo: row.Activo,
fechaCreacion: row.FechaCreacion);
private sealed record PermisoRow(
int Id,
string Codigo,
string Nombre,
string? Descripcion,
string Modulo,
bool Activo,
DateTime FechaCreacion);
}