Fase 1: Inicialización del Backend .NET 10, Configuración de Dapper, Autenticación JWT y Entidades Base
This commit is contained in:
6
src/SIGCM.Infrastructure/Class1.cs
Normal file
6
src/SIGCM.Infrastructure/Class1.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace SIGCM.Infrastructure;
|
||||
|
||||
public class Class1
|
||||
{
|
||||
|
||||
}
|
||||
26
src/SIGCM.Infrastructure/Data/DbConnectionFactory.cs
Normal file
26
src/SIGCM.Infrastructure/Data/DbConnectionFactory.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Data;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace SIGCM.Infrastructure.Data;
|
||||
|
||||
public interface IDbConnectionFactory
|
||||
{
|
||||
IDbConnection CreateConnection();
|
||||
}
|
||||
|
||||
public class DbConnectionFactory : IDbConnectionFactory
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
|
||||
public DbConnectionFactory(IConfiguration configuration)
|
||||
{
|
||||
_connectionString = configuration.GetConnectionString("DefaultConnection")
|
||||
?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
|
||||
}
|
||||
|
||||
public IDbConnection CreateConnection()
|
||||
{
|
||||
return new SqlConnection(_connectionString);
|
||||
}
|
||||
}
|
||||
117
src/SIGCM.Infrastructure/Data/DbInitializer.cs
Normal file
117
src/SIGCM.Infrastructure/Data/DbInitializer.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using Dapper;
|
||||
|
||||
namespace SIGCM.Infrastructure.Data;
|
||||
|
||||
public class DbInitializer
|
||||
{
|
||||
private readonly IDbConnectionFactory _connectionFactory;
|
||||
|
||||
public DbInitializer(IDbConnectionFactory connectionFactory)
|
||||
{
|
||||
_connectionFactory = connectionFactory;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
|
||||
var sql = @"
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Users')
|
||||
BEGIN
|
||||
CREATE TABLE Users (
|
||||
Id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
Username NVARCHAR(50) NOT NULL UNIQUE,
|
||||
PasswordHash NVARCHAR(255) NOT NULL,
|
||||
Role NVARCHAR(20) NOT NULL,
|
||||
Email NVARCHAR(100) NULL,
|
||||
CreatedAt DATETIME DEFAULT GETUTCDATE()
|
||||
);
|
||||
|
||||
-- Seed generic admin (password: admin123)
|
||||
-- Hash created with BCrypt
|
||||
INSERT INTO Users (Username, PasswordHash, Role)
|
||||
VALUES ('admin', '$2a$11$u.w..ExampleHashPlaceholder...', 'Admin');
|
||||
END
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Categories')
|
||||
BEGIN
|
||||
CREATE TABLE Categories (
|
||||
Id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
ParentId INT NULL,
|
||||
Name NVARCHAR(100) NOT NULL,
|
||||
Slug NVARCHAR(100) NOT NULL,
|
||||
Active BIT DEFAULT 1,
|
||||
FOREIGN KEY (ParentId) REFERENCES Categories(Id)
|
||||
);
|
||||
END
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Operations')
|
||||
BEGIN
|
||||
CREATE TABLE Operations (
|
||||
Id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
Name NVARCHAR(50) NOT NULL UNIQUE
|
||||
);
|
||||
END
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'CategoryOperations')
|
||||
BEGIN
|
||||
CREATE TABLE CategoryOperations (
|
||||
CategoryId INT NOT NULL,
|
||||
OperationId INT NOT NULL,
|
||||
PRIMARY KEY (CategoryId, OperationId),
|
||||
FOREIGN KEY (CategoryId) REFERENCES Categories(Id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (OperationId) REFERENCES Operations(Id) ON DELETE CASCADE
|
||||
);
|
||||
END
|
||||
";
|
||||
// Fixing the placeholder hash to a valid one might be necessary if I want to login immediately.
|
||||
// I will update the hash command later or create a small utility to generate one.
|
||||
// For now, I'll remove the INSERT or comment it out until I can generate a real hash in C#.
|
||||
|
||||
var schemaSql = @"
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Users')
|
||||
BEGIN
|
||||
CREATE TABLE Users (
|
||||
Id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
Username NVARCHAR(50) NOT NULL UNIQUE,
|
||||
PasswordHash NVARCHAR(255) NOT NULL,
|
||||
Role NVARCHAR(20) NOT NULL,
|
||||
Email NVARCHAR(100) NULL,
|
||||
CreatedAt DATETIME DEFAULT GETUTCDATE()
|
||||
);
|
||||
END
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Categories')
|
||||
BEGIN
|
||||
CREATE TABLE Categories (
|
||||
Id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
ParentId INT NULL,
|
||||
Name NVARCHAR(100) NOT NULL,
|
||||
Slug NVARCHAR(100) NOT NULL,
|
||||
Active BIT DEFAULT 1,
|
||||
FOREIGN KEY (ParentId) REFERENCES Categories(Id)
|
||||
);
|
||||
END
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Operations')
|
||||
BEGIN
|
||||
CREATE TABLE Operations (
|
||||
Id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
Name NVARCHAR(50) NOT NULL UNIQUE
|
||||
);
|
||||
END
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'CategoryOperations')
|
||||
BEGIN
|
||||
CREATE TABLE CategoryOperations (
|
||||
CategoryId INT NOT NULL,
|
||||
OperationId INT NOT NULL,
|
||||
PRIMARY KEY (CategoryId, OperationId),
|
||||
FOREIGN KEY (CategoryId) REFERENCES Categories(Id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (OperationId) REFERENCES Operations(Id) ON DELETE CASCADE
|
||||
);
|
||||
END
|
||||
";
|
||||
await connection.ExecuteAsync(schemaSql);
|
||||
}
|
||||
}
|
||||
25
src/SIGCM.Infrastructure/DependencyInjection.cs
Normal file
25
src/SIGCM.Infrastructure/DependencyInjection.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SIGCM.Domain.Interfaces;
|
||||
using SIGCM.Application.Interfaces;
|
||||
using SIGCM.Infrastructure.Data;
|
||||
using SIGCM.Infrastructure.Repositories;
|
||||
|
||||
namespace SIGCM.Infrastructure;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
public static IServiceCollection AddInfrastructure(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<IDbConnectionFactory, DbConnectionFactory>();
|
||||
services.AddSingleton<DbInitializer>();
|
||||
services.AddScoped<ICategoryRepository, CategoryRepository>();
|
||||
services.AddScoped<IOperationRepository, OperationRepository>();
|
||||
services.AddScoped<IUserRepository, UserRepository>();
|
||||
services.AddScoped<ITokenService, Services.TokenService>();
|
||||
services.AddScoped<IAuthService, Services.AuthService>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
60
src/SIGCM.Infrastructure/Repositories/CategoryRepository.cs
Normal file
60
src/SIGCM.Infrastructure/Repositories/CategoryRepository.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Dapper;
|
||||
using SIGCM.Domain.Entities;
|
||||
using SIGCM.Domain.Interfaces;
|
||||
using SIGCM.Infrastructure.Data;
|
||||
|
||||
namespace SIGCM.Infrastructure.Repositories;
|
||||
|
||||
public class CategoryRepository : ICategoryRepository
|
||||
{
|
||||
private readonly IDbConnectionFactory _connectionFactory;
|
||||
|
||||
public CategoryRepository(IDbConnectionFactory connectionFactory)
|
||||
{
|
||||
_connectionFactory = connectionFactory;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Category>> GetAllAsync()
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
return await conn.QueryAsync<Category>("SELECT * FROM Categories");
|
||||
}
|
||||
|
||||
public async Task<Category?> GetByIdAsync(int id)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
return await conn.QueryFirstOrDefaultAsync<Category>("SELECT * FROM Categories WHERE Id = @Id", new { Id = id });
|
||||
}
|
||||
|
||||
public async Task<int> AddAsync(Category category)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
var sql = @"
|
||||
INSERT INTO Categories (ParentId, Name, Slug, Active)
|
||||
VALUES (@ParentId, @Name, @Slug, @Active);
|
||||
SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||
return await conn.QuerySingleAsync<int>(sql, category);
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Category category)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
var sql = @"
|
||||
UPDATE Categories
|
||||
SET ParentId = @ParentId, Name = @Name, Slug = @Slug, Active = @Active
|
||||
WHERE Id = @Id";
|
||||
await conn.ExecuteAsync(sql, category);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
await conn.ExecuteAsync("DELETE FROM Categories WHERE Id = @Id", new { Id = id });
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Category>> GetSubCategoriesAsync(int parentId)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
return await conn.QueryAsync<Category>("SELECT * FROM Categories WHERE ParentId = @ParentId", new { ParentId = parentId });
|
||||
}
|
||||
}
|
||||
44
src/SIGCM.Infrastructure/Repositories/OperationRepository.cs
Normal file
44
src/SIGCM.Infrastructure/Repositories/OperationRepository.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Dapper;
|
||||
using SIGCM.Domain.Entities;
|
||||
using SIGCM.Domain.Interfaces;
|
||||
using SIGCM.Infrastructure.Data;
|
||||
|
||||
namespace SIGCM.Infrastructure.Repositories;
|
||||
|
||||
public class OperationRepository : IOperationRepository
|
||||
{
|
||||
private readonly IDbConnectionFactory _connectionFactory;
|
||||
|
||||
public OperationRepository(IDbConnectionFactory connectionFactory)
|
||||
{
|
||||
_connectionFactory = connectionFactory;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Operation>> GetAllAsync()
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
return await conn.QueryAsync<Operation>("SELECT * FROM Operations");
|
||||
}
|
||||
|
||||
public async Task<Operation?> GetByIdAsync(int id)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
return await conn.QueryFirstOrDefaultAsync<Operation>("SELECT * FROM Operations WHERE Id = @Id", new { Id = id });
|
||||
}
|
||||
|
||||
public async Task<int> AddAsync(Operation operation)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
var sql = @"
|
||||
INSERT INTO Operations (Name)
|
||||
VALUES (@Name);
|
||||
SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||
return await conn.QuerySingleAsync<int>(sql, operation);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
await conn.ExecuteAsync("DELETE FROM Operations WHERE Id = @Id", new { Id = id });
|
||||
}
|
||||
}
|
||||
34
src/SIGCM.Infrastructure/Repositories/UserRepository.cs
Normal file
34
src/SIGCM.Infrastructure/Repositories/UserRepository.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Dapper;
|
||||
using SIGCM.Domain.Entities;
|
||||
using SIGCM.Domain.Interfaces;
|
||||
using SIGCM.Infrastructure.Data;
|
||||
|
||||
namespace SIGCM.Infrastructure.Repositories;
|
||||
|
||||
public class UserRepository : IUserRepository
|
||||
{
|
||||
private readonly IDbConnectionFactory _connectionFactory;
|
||||
|
||||
public UserRepository(IDbConnectionFactory connectionFactory)
|
||||
{
|
||||
_connectionFactory = connectionFactory;
|
||||
}
|
||||
|
||||
public async Task<User?> GetByUsernameAsync(string username)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
return await conn.QuerySingleOrDefaultAsync<User>(
|
||||
"SELECT * FROM Users WHERE Username = @Username",
|
||||
new { Username = username });
|
||||
}
|
||||
|
||||
public async Task<int> CreateAsync(User user)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
var sql = @"
|
||||
INSERT INTO Users (Username, PasswordHash, Role, Email)
|
||||
VALUES (@Username, @PasswordHash, @Role, @Email);
|
||||
SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||
return await conn.QuerySingleAsync<int>(sql, user);
|
||||
}
|
||||
}
|
||||
21
src/SIGCM.Infrastructure/SIGCM.Infrastructure.csproj
Normal file
21
src/SIGCM.Infrastructure/SIGCM.Infrastructure.csproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SIGCM.Application\SIGCM.Application.csproj" />
|
||||
<ProjectReference Include="..\SIGCM.Domain\SIGCM.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
27
src/SIGCM.Infrastructure/Services/AuthService.cs
Normal file
27
src/SIGCM.Infrastructure/Services/AuthService.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using SIGCM.Application.Interfaces;
|
||||
using SIGCM.Domain.Interfaces;
|
||||
|
||||
namespace SIGCM.Infrastructure.Services;
|
||||
|
||||
public class AuthService : IAuthService
|
||||
{
|
||||
private readonly IUserRepository _userRepo;
|
||||
private readonly ITokenService _tokenService;
|
||||
|
||||
public AuthService(IUserRepository userRepo, ITokenService tokenService)
|
||||
{
|
||||
_userRepo = userRepo;
|
||||
_tokenService = tokenService;
|
||||
}
|
||||
|
||||
public async Task<string?> LoginAsync(string username, string password)
|
||||
{
|
||||
var user = await _userRepo.GetByUsernameAsync(username);
|
||||
if (user == null) return null;
|
||||
|
||||
bool valid = BCrypt.Net.BCrypt.Verify(password, user.PasswordHash);
|
||||
if (!valid) return null;
|
||||
|
||||
return _tokenService.GenerateToken(user);
|
||||
}
|
||||
}
|
||||
42
src/SIGCM.Infrastructure/Services/TokenService.cs
Normal file
42
src/SIGCM.Infrastructure/Services/TokenService.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using SIGCM.Application.Interfaces;
|
||||
using SIGCM.Domain.Entities;
|
||||
|
||||
namespace SIGCM.Infrastructure.Services;
|
||||
|
||||
public class TokenService : ITokenService
|
||||
{
|
||||
private readonly IConfiguration _config;
|
||||
|
||||
public TokenService(IConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public string GenerateToken(User user)
|
||||
{
|
||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]!));
|
||||
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Sub, user.Username),
|
||||
new Claim(ClaimTypes.Role, user.Role),
|
||||
new Claim("Id", user.Id.ToString())
|
||||
};
|
||||
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: _config["Jwt:Issuer"],
|
||||
audience: _config["Jwt:Audience"],
|
||||
claims: claims,
|
||||
expires: DateTime.UtcNow.AddHours(4),
|
||||
signingCredentials: creds
|
||||
);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user