From eda016f93f59af25f8620c6c9d56f0eda1017b45 Mon Sep 17 00:00:00 2001 From: dmolinari Date: Wed, 17 Dec 2025 13:08:21 -0300 Subject: [PATCH] =?UTF-8?q?Fase=201:=20Inicializaci=C3=B3n=20del=20Backend?= =?UTF-8?q?=20.NET=2010,=20Configuraci=C3=B3n=20de=20Dapper,=20Autenticaci?= =?UTF-8?q?=C3=B3n=20JWT=20y=20Entidades=20Base?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SIG-CM.pdf | Bin 0 -> 42567 bytes src/SIGCM.API/Controllers/AuthController.cs | 26 ++++ .../Controllers/CategoriesController.cs | 55 ++++++++ .../Controllers/OperationsController.cs | 47 +++++++ src/SIGCM.API/Program.cs | 35 ++++++ src/SIGCM.API/Properties/launchSettings.json | 23 ++++ src/SIGCM.API/SIGCM.API.csproj | 20 +++ src/SIGCM.API/SIGCM.API.http | 6 + src/SIGCM.API/appsettings.json | 17 +++ src/SIGCM.Application/Class1.cs | 6 + src/SIGCM.Application/DTOs/LoginDto.cs | 7 ++ .../Interfaces/IAuthService.cs | 6 + .../Interfaces/ITokenService.cs | 7 ++ .../SIGCM.Application.csproj | 13 ++ src/SIGCM.Domain/Class1.cs | 6 + src/SIGCM.Domain/Entities/Category.cs | 10 ++ src/SIGCM.Domain/Entities/Operation.cs | 7 ++ src/SIGCM.Domain/Entities/User.cs | 11 ++ .../Interfaces/ICategoryRepository.cs | 12 ++ .../Interfaces/IOperationRepository.cs | 10 ++ .../Interfaces/IUserRepository.cs | 8 ++ src/SIGCM.Domain/SIGCM.Domain.csproj | 9 ++ src/SIGCM.Infrastructure/Class1.cs | 6 + .../Data/DbConnectionFactory.cs | 26 ++++ .../Data/DbInitializer.cs | 117 ++++++++++++++++++ .../DependencyInjection.cs | 25 ++++ .../Repositories/CategoryRepository.cs | 60 +++++++++ .../Repositories/OperationRepository.cs | 44 +++++++ .../Repositories/UserRepository.cs | 34 +++++ .../SIGCM.Infrastructure.csproj | 21 ++++ .../Services/AuthService.cs | 27 ++++ .../Services/TokenService.cs | 42 +++++++ src/SIGCM.sln | 76 ++++++++++++ 33 files changed, 819 insertions(+) create mode 100644 SIG-CM.pdf create mode 100644 src/SIGCM.API/Controllers/AuthController.cs create mode 100644 src/SIGCM.API/Controllers/CategoriesController.cs create mode 100644 src/SIGCM.API/Controllers/OperationsController.cs create mode 100644 src/SIGCM.API/Program.cs create mode 100644 src/SIGCM.API/Properties/launchSettings.json create mode 100644 src/SIGCM.API/SIGCM.API.csproj create mode 100644 src/SIGCM.API/SIGCM.API.http create mode 100644 src/SIGCM.API/appsettings.json create mode 100644 src/SIGCM.Application/Class1.cs create mode 100644 src/SIGCM.Application/DTOs/LoginDto.cs create mode 100644 src/SIGCM.Application/Interfaces/IAuthService.cs create mode 100644 src/SIGCM.Application/Interfaces/ITokenService.cs create mode 100644 src/SIGCM.Application/SIGCM.Application.csproj create mode 100644 src/SIGCM.Domain/Class1.cs create mode 100644 src/SIGCM.Domain/Entities/Category.cs create mode 100644 src/SIGCM.Domain/Entities/Operation.cs create mode 100644 src/SIGCM.Domain/Entities/User.cs create mode 100644 src/SIGCM.Domain/Interfaces/ICategoryRepository.cs create mode 100644 src/SIGCM.Domain/Interfaces/IOperationRepository.cs create mode 100644 src/SIGCM.Domain/Interfaces/IUserRepository.cs create mode 100644 src/SIGCM.Domain/SIGCM.Domain.csproj create mode 100644 src/SIGCM.Infrastructure/Class1.cs create mode 100644 src/SIGCM.Infrastructure/Data/DbConnectionFactory.cs create mode 100644 src/SIGCM.Infrastructure/Data/DbInitializer.cs create mode 100644 src/SIGCM.Infrastructure/DependencyInjection.cs create mode 100644 src/SIGCM.Infrastructure/Repositories/CategoryRepository.cs create mode 100644 src/SIGCM.Infrastructure/Repositories/OperationRepository.cs create mode 100644 src/SIGCM.Infrastructure/Repositories/UserRepository.cs create mode 100644 src/SIGCM.Infrastructure/SIGCM.Infrastructure.csproj create mode 100644 src/SIGCM.Infrastructure/Services/AuthService.cs create mode 100644 src/SIGCM.Infrastructure/Services/TokenService.cs create mode 100644 src/SIGCM.sln diff --git a/SIG-CM.pdf b/SIG-CM.pdf new file mode 100644 index 0000000000000000000000000000000000000000..65252845845805805dde5da92f6f229187ef2ee6 GIT binary patch literal 42567 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
  • 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 Gmj44olrOCS literal 0 HcmV?d00001 diff --git a/src/SIGCM.API/Controllers/AuthController.cs b/src/SIGCM.API/Controllers/AuthController.cs new file mode 100644 index 0000000..a9a4bc1 --- /dev/null +++ b/src/SIGCM.API/Controllers/AuthController.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Mvc; +using SIGCM.Application.DTOs; +using SIGCM.Application.Interfaces; + +namespace SIGCM.API.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class AuthController : ControllerBase +{ + private readonly IAuthService _authService; + + public AuthController(IAuthService authService) + { + _authService = authService; + } + + [HttpPost("login")] + public async Task Login(LoginDto dto) + { + var token = await _authService.LoginAsync(dto.Username, dto.Password); + if (token == null) return Unauthorized("Invalid credentials"); + + return Ok(new { token }); + } +} diff --git a/src/SIGCM.API/Controllers/CategoriesController.cs b/src/SIGCM.API/Controllers/CategoriesController.cs new file mode 100644 index 0000000..7517f31 --- /dev/null +++ b/src/SIGCM.API/Controllers/CategoriesController.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Mvc; +using SIGCM.Domain.Entities; +using SIGCM.Domain.Interfaces; + +namespace SIGCM.API.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class CategoriesController : ControllerBase +{ + private readonly ICategoryRepository _repository; + + public CategoriesController(ICategoryRepository repository) + { + _repository = repository; + } + + [HttpGet] + public async Task GetAll() + { + var categories = await _repository.GetAllAsync(); + return Ok(categories); + } + + [HttpGet("{id}")] + public async Task GetById(int id) + { + var category = await _repository.GetByIdAsync(id); + if (category == null) return NotFound(); + return Ok(category); + } + + [HttpPost] + public async Task Create(Category category) + { + var id = await _repository.AddAsync(category); + category.Id = id; + return CreatedAtAction(nameof(GetById), new { id }, category); + } + + [HttpPut("{id}")] + public async Task Update(int id, Category category) + { + if (id != category.Id) return BadRequest(); + await _repository.UpdateAsync(category); + return NoContent(); + } + + [HttpDelete("{id}")] + public async Task Delete(int id) + { + await _repository.DeleteAsync(id); + return NoContent(); + } +} diff --git a/src/SIGCM.API/Controllers/OperationsController.cs b/src/SIGCM.API/Controllers/OperationsController.cs new file mode 100644 index 0000000..bb7c63b --- /dev/null +++ b/src/SIGCM.API/Controllers/OperationsController.cs @@ -0,0 +1,47 @@ +using Microsoft.AspNetCore.Mvc; +using SIGCM.Domain.Entities; +using SIGCM.Domain.Interfaces; + +namespace SIGCM.API.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class OperationsController : ControllerBase +{ + private readonly IOperationRepository _repository; + + public OperationsController(IOperationRepository repository) + { + _repository = repository; + } + + [HttpGet] + public async Task GetAll() + { + var operations = await _repository.GetAllAsync(); + return Ok(operations); + } + + [HttpGet("{id}")] + public async Task GetById(int id) + { + var operation = await _repository.GetByIdAsync(id); + if (operation == null) return NotFound(); + return Ok(operation); + } + + [HttpPost] + public async Task Create(Operation operation) + { + var id = await _repository.AddAsync(operation); + operation.Id = id; + return CreatedAtAction(nameof(GetById), new { id }, operation); + } + + [HttpDelete("{id}")] + public async Task Delete(int id) + { + await _repository.DeleteAsync(id); + return NoContent(); + } +} diff --git a/src/SIGCM.API/Program.cs b/src/SIGCM.API/Program.cs new file mode 100644 index 0000000..2c9d849 --- /dev/null +++ b/src/SIGCM.API/Program.cs @@ -0,0 +1,35 @@ +using SIGCM.Infrastructure; +using SIGCM.Infrastructure.Data; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.Services.AddInfrastructure(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +// Initialize DB +using (var scope = app.Services.CreateScope()) +{ + var initializer = scope.ServiceProvider.GetRequiredService(); + await initializer.InitializeAsync(); +} + +app.Run(); diff --git a/src/SIGCM.API/Properties/launchSettings.json b/src/SIGCM.API/Properties/launchSettings.json new file mode 100644 index 0000000..8d6c719 --- /dev/null +++ b/src/SIGCM.API/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5176", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7034;http://localhost:5176", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/SIGCM.API/SIGCM.API.csproj b/src/SIGCM.API/SIGCM.API.csproj new file mode 100644 index 0000000..f8c07f9 --- /dev/null +++ b/src/SIGCM.API/SIGCM.API.csproj @@ -0,0 +1,20 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + + + + diff --git a/src/SIGCM.API/SIGCM.API.http b/src/SIGCM.API/SIGCM.API.http new file mode 100644 index 0000000..8ffa92c --- /dev/null +++ b/src/SIGCM.API/SIGCM.API.http @@ -0,0 +1,6 @@ +@SIGCM.API_HostAddress = http://localhost:5176 + +GET {{SIGCM.API_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/src/SIGCM.API/appsettings.json b/src/SIGCM.API/appsettings.json new file mode 100644 index 0000000..ef918f3 --- /dev/null +++ b/src/SIGCM.API/appsettings.json @@ -0,0 +1,17 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "Server=TECNICA3;Database=SIGCM;User Id=sigcmApi;Password=@Diego550@;TrustServerCertificate=True" + }, + "Jwt": { + "Key": "badb1a38d221c9e23bcf70958840ca7f5a5dc54f2047dadf7ce45b578b5bc3e2", + "Issuer": "SIGCMApi", + "Audience": "SIGCMAdmin" + } +} \ No newline at end of file diff --git a/src/SIGCM.Application/Class1.cs b/src/SIGCM.Application/Class1.cs new file mode 100644 index 0000000..d0159fe --- /dev/null +++ b/src/SIGCM.Application/Class1.cs @@ -0,0 +1,6 @@ +namespace SIGCM.Application; + +public class Class1 +{ + +} diff --git a/src/SIGCM.Application/DTOs/LoginDto.cs b/src/SIGCM.Application/DTOs/LoginDto.cs new file mode 100644 index 0000000..948e751 --- /dev/null +++ b/src/SIGCM.Application/DTOs/LoginDto.cs @@ -0,0 +1,7 @@ +namespace SIGCM.Application.DTOs; + +public class LoginDto +{ + public required string Username { get; set; } + public required string Password { get; set; } +} diff --git a/src/SIGCM.Application/Interfaces/IAuthService.cs b/src/SIGCM.Application/Interfaces/IAuthService.cs new file mode 100644 index 0000000..06941b8 --- /dev/null +++ b/src/SIGCM.Application/Interfaces/IAuthService.cs @@ -0,0 +1,6 @@ +namespace SIGCM.Application.Interfaces; + +public interface IAuthService +{ + Task LoginAsync(string username, string password); +} diff --git a/src/SIGCM.Application/Interfaces/ITokenService.cs b/src/SIGCM.Application/Interfaces/ITokenService.cs new file mode 100644 index 0000000..477683c --- /dev/null +++ b/src/SIGCM.Application/Interfaces/ITokenService.cs @@ -0,0 +1,7 @@ +namespace SIGCM.Application.Interfaces; +using SIGCM.Domain.Entities; + +public interface ITokenService +{ + string GenerateToken(User user); +} diff --git a/src/SIGCM.Application/SIGCM.Application.csproj b/src/SIGCM.Application/SIGCM.Application.csproj new file mode 100644 index 0000000..b08ec68 --- /dev/null +++ b/src/SIGCM.Application/SIGCM.Application.csproj @@ -0,0 +1,13 @@ + + + + + + + + net10.0 + enable + enable + + + diff --git a/src/SIGCM.Domain/Class1.cs b/src/SIGCM.Domain/Class1.cs new file mode 100644 index 0000000..46b2bdb --- /dev/null +++ b/src/SIGCM.Domain/Class1.cs @@ -0,0 +1,6 @@ +namespace SIGCM.Domain; + +public class Class1 +{ + +} diff --git a/src/SIGCM.Domain/Entities/Category.cs b/src/SIGCM.Domain/Entities/Category.cs new file mode 100644 index 0000000..6cf616b --- /dev/null +++ b/src/SIGCM.Domain/Entities/Category.cs @@ -0,0 +1,10 @@ +namespace SIGCM.Domain.Entities; + +public class Category +{ + public int Id { get; set; } + public int? ParentId { get; set; } + public required string Name { get; set; } + public required string Slug { get; set; } + public bool Active { get; set; } = true; +} diff --git a/src/SIGCM.Domain/Entities/Operation.cs b/src/SIGCM.Domain/Entities/Operation.cs new file mode 100644 index 0000000..272a678 --- /dev/null +++ b/src/SIGCM.Domain/Entities/Operation.cs @@ -0,0 +1,7 @@ +namespace SIGCM.Domain.Entities; + +public class Operation +{ + public int Id { get; set; } + public required string Name { get; set; } +} diff --git a/src/SIGCM.Domain/Entities/User.cs b/src/SIGCM.Domain/Entities/User.cs new file mode 100644 index 0000000..c73a535 --- /dev/null +++ b/src/SIGCM.Domain/Entities/User.cs @@ -0,0 +1,11 @@ +namespace SIGCM.Domain.Entities; + +public class User +{ + public int Id { get; set; } + public required string Username { get; set; } + public required string PasswordHash { get; set; } + public required string Role { get; set; } + public string? Email { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/src/SIGCM.Domain/Interfaces/ICategoryRepository.cs b/src/SIGCM.Domain/Interfaces/ICategoryRepository.cs new file mode 100644 index 0000000..2e32457 --- /dev/null +++ b/src/SIGCM.Domain/Interfaces/ICategoryRepository.cs @@ -0,0 +1,12 @@ +namespace SIGCM.Domain.Interfaces; +using SIGCM.Domain.Entities; + +public interface ICategoryRepository +{ + Task> GetAllAsync(); + Task GetByIdAsync(int id); + Task AddAsync(Category category); + Task UpdateAsync(Category category); + Task DeleteAsync(int id); + Task> GetSubCategoriesAsync(int parentId); +} diff --git a/src/SIGCM.Domain/Interfaces/IOperationRepository.cs b/src/SIGCM.Domain/Interfaces/IOperationRepository.cs new file mode 100644 index 0000000..38ea278 --- /dev/null +++ b/src/SIGCM.Domain/Interfaces/IOperationRepository.cs @@ -0,0 +1,10 @@ +namespace SIGCM.Domain.Interfaces; +using SIGCM.Domain.Entities; + +public interface IOperationRepository +{ + Task> GetAllAsync(); + Task GetByIdAsync(int id); + Task AddAsync(Operation operation); + Task DeleteAsync(int id); +} diff --git a/src/SIGCM.Domain/Interfaces/IUserRepository.cs b/src/SIGCM.Domain/Interfaces/IUserRepository.cs new file mode 100644 index 0000000..27251ce --- /dev/null +++ b/src/SIGCM.Domain/Interfaces/IUserRepository.cs @@ -0,0 +1,8 @@ +namespace SIGCM.Domain.Interfaces; +using SIGCM.Domain.Entities; + +public interface IUserRepository +{ + Task GetByUsernameAsync(string username); + Task CreateAsync(User user); +} diff --git a/src/SIGCM.Domain/SIGCM.Domain.csproj b/src/SIGCM.Domain/SIGCM.Domain.csproj new file mode 100644 index 0000000..b760144 --- /dev/null +++ b/src/SIGCM.Domain/SIGCM.Domain.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + diff --git a/src/SIGCM.Infrastructure/Class1.cs b/src/SIGCM.Infrastructure/Class1.cs new file mode 100644 index 0000000..c98680d --- /dev/null +++ b/src/SIGCM.Infrastructure/Class1.cs @@ -0,0 +1,6 @@ +namespace SIGCM.Infrastructure; + +public class Class1 +{ + +} diff --git a/src/SIGCM.Infrastructure/Data/DbConnectionFactory.cs b/src/SIGCM.Infrastructure/Data/DbConnectionFactory.cs new file mode 100644 index 0000000..5e82b7f --- /dev/null +++ b/src/SIGCM.Infrastructure/Data/DbConnectionFactory.cs @@ -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); + } +} diff --git a/src/SIGCM.Infrastructure/Data/DbInitializer.cs b/src/SIGCM.Infrastructure/Data/DbInitializer.cs new file mode 100644 index 0000000..1742009 --- /dev/null +++ b/src/SIGCM.Infrastructure/Data/DbInitializer.cs @@ -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); + } +} diff --git a/src/SIGCM.Infrastructure/DependencyInjection.cs b/src/SIGCM.Infrastructure/DependencyInjection.cs new file mode 100644 index 0000000..894812b --- /dev/null +++ b/src/SIGCM.Infrastructure/DependencyInjection.cs @@ -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(); + services.AddSingleton(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + return services; + } +} + + + diff --git a/src/SIGCM.Infrastructure/Repositories/CategoryRepository.cs b/src/SIGCM.Infrastructure/Repositories/CategoryRepository.cs new file mode 100644 index 0000000..f178ea6 --- /dev/null +++ b/src/SIGCM.Infrastructure/Repositories/CategoryRepository.cs @@ -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> GetAllAsync() + { + using var conn = _connectionFactory.CreateConnection(); + return await conn.QueryAsync("SELECT * FROM Categories"); + } + + public async Task GetByIdAsync(int id) + { + using var conn = _connectionFactory.CreateConnection(); + return await conn.QueryFirstOrDefaultAsync("SELECT * FROM Categories WHERE Id = @Id", new { Id = id }); + } + + public async Task 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(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> GetSubCategoriesAsync(int parentId) + { + using var conn = _connectionFactory.CreateConnection(); + return await conn.QueryAsync("SELECT * FROM Categories WHERE ParentId = @ParentId", new { ParentId = parentId }); + } +} diff --git a/src/SIGCM.Infrastructure/Repositories/OperationRepository.cs b/src/SIGCM.Infrastructure/Repositories/OperationRepository.cs new file mode 100644 index 0000000..bd75a8c --- /dev/null +++ b/src/SIGCM.Infrastructure/Repositories/OperationRepository.cs @@ -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> GetAllAsync() + { + using var conn = _connectionFactory.CreateConnection(); + return await conn.QueryAsync("SELECT * FROM Operations"); + } + + public async Task GetByIdAsync(int id) + { + using var conn = _connectionFactory.CreateConnection(); + return await conn.QueryFirstOrDefaultAsync("SELECT * FROM Operations WHERE Id = @Id", new { Id = id }); + } + + public async Task 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(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 }); + } +} diff --git a/src/SIGCM.Infrastructure/Repositories/UserRepository.cs b/src/SIGCM.Infrastructure/Repositories/UserRepository.cs new file mode 100644 index 0000000..32c5877 --- /dev/null +++ b/src/SIGCM.Infrastructure/Repositories/UserRepository.cs @@ -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 GetByUsernameAsync(string username) + { + using var conn = _connectionFactory.CreateConnection(); + return await conn.QuerySingleOrDefaultAsync( + "SELECT * FROM Users WHERE Username = @Username", + new { Username = username }); + } + + public async Task 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(sql, user); + } +} diff --git a/src/SIGCM.Infrastructure/SIGCM.Infrastructure.csproj b/src/SIGCM.Infrastructure/SIGCM.Infrastructure.csproj new file mode 100644 index 0000000..8a45b5b --- /dev/null +++ b/src/SIGCM.Infrastructure/SIGCM.Infrastructure.csproj @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + net10.0 + enable + enable + + + diff --git a/src/SIGCM.Infrastructure/Services/AuthService.cs b/src/SIGCM.Infrastructure/Services/AuthService.cs new file mode 100644 index 0000000..9344451 --- /dev/null +++ b/src/SIGCM.Infrastructure/Services/AuthService.cs @@ -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 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); + } +} diff --git a/src/SIGCM.Infrastructure/Services/TokenService.cs b/src/SIGCM.Infrastructure/Services/TokenService.cs new file mode 100644 index 0000000..80d7191 --- /dev/null +++ b/src/SIGCM.Infrastructure/Services/TokenService.cs @@ -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); + } +} diff --git a/src/SIGCM.sln b/src/SIGCM.sln new file mode 100644 index 0000000..96c934d --- /dev/null +++ b/src/SIGCM.sln @@ -0,0 +1,76 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SIGCM.Domain", "SIGCM.Domain\SIGCM.Domain.csproj", "{0224DD39-C953-4D75-A5AE-D7FB5DC07DFD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SIGCM.Application", "SIGCM.Application\SIGCM.Application.csproj", "{8B9C32EC-AE46-4882-96B0-62358FD40C84}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SIGCM.Infrastructure", "SIGCM.Infrastructure\SIGCM.Infrastructure.csproj", "{895726EC-FDEF-490D-A78E-98591AC26BA4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SIGCM.API", "SIGCM.API\SIGCM.API.csproj", "{6AE3C4DB-EBFC-46B4-8231-54CE76BC9D90}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0224DD39-C953-4D75-A5AE-D7FB5DC07DFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0224DD39-C953-4D75-A5AE-D7FB5DC07DFD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0224DD39-C953-4D75-A5AE-D7FB5DC07DFD}.Debug|x64.ActiveCfg = Debug|Any CPU + {0224DD39-C953-4D75-A5AE-D7FB5DC07DFD}.Debug|x64.Build.0 = Debug|Any CPU + {0224DD39-C953-4D75-A5AE-D7FB5DC07DFD}.Debug|x86.ActiveCfg = Debug|Any CPU + {0224DD39-C953-4D75-A5AE-D7FB5DC07DFD}.Debug|x86.Build.0 = Debug|Any CPU + {0224DD39-C953-4D75-A5AE-D7FB5DC07DFD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0224DD39-C953-4D75-A5AE-D7FB5DC07DFD}.Release|Any CPU.Build.0 = Release|Any CPU + {0224DD39-C953-4D75-A5AE-D7FB5DC07DFD}.Release|x64.ActiveCfg = Release|Any CPU + {0224DD39-C953-4D75-A5AE-D7FB5DC07DFD}.Release|x64.Build.0 = Release|Any CPU + {0224DD39-C953-4D75-A5AE-D7FB5DC07DFD}.Release|x86.ActiveCfg = Release|Any CPU + {0224DD39-C953-4D75-A5AE-D7FB5DC07DFD}.Release|x86.Build.0 = Release|Any CPU + {8B9C32EC-AE46-4882-96B0-62358FD40C84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B9C32EC-AE46-4882-96B0-62358FD40C84}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B9C32EC-AE46-4882-96B0-62358FD40C84}.Debug|x64.ActiveCfg = Debug|Any CPU + {8B9C32EC-AE46-4882-96B0-62358FD40C84}.Debug|x64.Build.0 = Debug|Any CPU + {8B9C32EC-AE46-4882-96B0-62358FD40C84}.Debug|x86.ActiveCfg = Debug|Any CPU + {8B9C32EC-AE46-4882-96B0-62358FD40C84}.Debug|x86.Build.0 = Debug|Any CPU + {8B9C32EC-AE46-4882-96B0-62358FD40C84}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B9C32EC-AE46-4882-96B0-62358FD40C84}.Release|Any CPU.Build.0 = Release|Any CPU + {8B9C32EC-AE46-4882-96B0-62358FD40C84}.Release|x64.ActiveCfg = Release|Any CPU + {8B9C32EC-AE46-4882-96B0-62358FD40C84}.Release|x64.Build.0 = Release|Any CPU + {8B9C32EC-AE46-4882-96B0-62358FD40C84}.Release|x86.ActiveCfg = Release|Any CPU + {8B9C32EC-AE46-4882-96B0-62358FD40C84}.Release|x86.Build.0 = Release|Any CPU + {895726EC-FDEF-490D-A78E-98591AC26BA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {895726EC-FDEF-490D-A78E-98591AC26BA4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {895726EC-FDEF-490D-A78E-98591AC26BA4}.Debug|x64.ActiveCfg = Debug|Any CPU + {895726EC-FDEF-490D-A78E-98591AC26BA4}.Debug|x64.Build.0 = Debug|Any CPU + {895726EC-FDEF-490D-A78E-98591AC26BA4}.Debug|x86.ActiveCfg = Debug|Any CPU + {895726EC-FDEF-490D-A78E-98591AC26BA4}.Debug|x86.Build.0 = Debug|Any CPU + {895726EC-FDEF-490D-A78E-98591AC26BA4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {895726EC-FDEF-490D-A78E-98591AC26BA4}.Release|Any CPU.Build.0 = Release|Any CPU + {895726EC-FDEF-490D-A78E-98591AC26BA4}.Release|x64.ActiveCfg = Release|Any CPU + {895726EC-FDEF-490D-A78E-98591AC26BA4}.Release|x64.Build.0 = Release|Any CPU + {895726EC-FDEF-490D-A78E-98591AC26BA4}.Release|x86.ActiveCfg = Release|Any CPU + {895726EC-FDEF-490D-A78E-98591AC26BA4}.Release|x86.Build.0 = Release|Any CPU + {6AE3C4DB-EBFC-46B4-8231-54CE76BC9D90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6AE3C4DB-EBFC-46B4-8231-54CE76BC9D90}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6AE3C4DB-EBFC-46B4-8231-54CE76BC9D90}.Debug|x64.ActiveCfg = Debug|Any CPU + {6AE3C4DB-EBFC-46B4-8231-54CE76BC9D90}.Debug|x64.Build.0 = Debug|Any CPU + {6AE3C4DB-EBFC-46B4-8231-54CE76BC9D90}.Debug|x86.ActiveCfg = Debug|Any CPU + {6AE3C4DB-EBFC-46B4-8231-54CE76BC9D90}.Debug|x86.Build.0 = Debug|Any CPU + {6AE3C4DB-EBFC-46B4-8231-54CE76BC9D90}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6AE3C4DB-EBFC-46B4-8231-54CE76BC9D90}.Release|Any CPU.Build.0 = Release|Any CPU + {6AE3C4DB-EBFC-46B4-8231-54CE76BC9D90}.Release|x64.ActiveCfg = Release|Any CPU + {6AE3C4DB-EBFC-46B4-8231-54CE76BC9D90}.Release|x64.Build.0 = Release|Any CPU + {6AE3C4DB-EBFC-46B4-8231-54CE76BC9D90}.Release|x86.ActiveCfg = Release|Any CPU + {6AE3C4DB-EBFC-46B4-8231-54CE76BC9D90}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal