diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4ef74ab --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +root = true + +[*] +charset = utf-8 +end_of_line = crlf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{cs,cshtml}] +indent_style = space +indent_size = 4 +dotnet_diagnostic.IDE0055.severity = suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = false:none + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..901f471 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,12 @@ +# CODEOWNERS file +# Propietarios por área. Reemplaza o añade GitHub handles reales según corresponda. + +# Responsable principal mantiene todo +* @adrianmesasacasas + +# Áreas específicas (asignadas temporalmente al responsable principal) +/src/OTManager.Api/ @adrianmesasacasas +/src/OTManager.Web/ @adrianmesasacasas +/src/OTManager.Data/ @adrianmesasacasas + +# Nota: para añadir más revisores automáticos, reemplaza los handles anteriores por los nombres reales de los colaboradores o equipos (p. ej. @org/eq-api). diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..4f36d2f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,29 @@ +--- +name: Bug report +about: Reporta un error encontrado en el proyecto +title: "bug: " +labels: bug +assignees: '' +--- + +**Descripcin breve** +Una descripcin clara y concisa del problema. + +**Pasos para reproducir** +1. +2. +3. + +**Comportamiento esperado** + +**Comportamiento observado** + +**Datos adicionales** +- Versin de .NET: .NET 9 +- Sistema operativo: +- Logs: + +**Prioridad** +- [ ] baja +- [ ] media +- [ ] alta diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..6fb6b4e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,23 @@ +--- +name: Feature request +about: Proponer una nueva funcionalidad +title: "feat: " +labels: enhancement +assignees: '' +--- + +**Propuesta** +Describe la funcionalidad solicitada y el problema que resuelve. + +**Casos de uso** +- + +**Propuesta de diseo** +- Endpoints/API propuestos: +- Cambios en UI: + +**Impacto** +- Es breaking change? (s/no) +- Compatibilidad hacia atrs: + +**Notas adicionales** diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..2b9597e --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,23 @@ +# Tipo de cambio +- [ ] feat +- [ ] fix +- [ ] docs +- [ ] style +- [ ] refactor +- [ ] test +- [ ] chore + +## Descripcin +Explica brevemente qu hace este PR y por qu es necesario. + +## Cmo probar +Instrucciones para reproducir y validar los cambios. + +## Checklist +- [ ] El cdigo compila y pasa las pruebas locales +- [ ] Se agregaron/actualizaron pruebas cuando aplica +- [ ] Se actualiz la documentacin si aplica +- [ ] No se aadieron secretos ni datos sensibles + +## Issue relacionada +Closes # (issue) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..051bab3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,120 @@ +CONTRIBUTING +=========== + +Gracias por tu inters en contribuir a OTManager.Wapi. Este documento proporciona las pautas esenciales para colaborar en este proyecto de software libre. Para evitar duplicar informacin, las secciones sobre seguridad y conducta se encuentran en SECURITY.md y Code_Of_Conduct.md respectivamente. + +1. Resumen +--------- +OTManager.Wapi es un proyecto en .NET 9 (API y Blazor) que utiliza Entity Framework Core para persistencia. Intentamos mantener contribuciones claras, probadas y acordes con las reglas de estilo del proyecto. + +2. Cdigo de conducta +--------------------- +Revisa `Code_Of_Conduct.md` para las normas de comportamiento y cmo reportar incumplimientos. + +3. Cmo reportar un bug +----------------------- +- Abre una Issue nueva usando la plantilla "Bug report" (usa 'New issue' -> seleccionar plantilla). +- Incluye pasos para reproducir, comportamiento esperado, comportamiento real, versin de .NET (debe ser .NET 9), sistema operativo, y logs relevantes. + +4. Cmo proponer una caracterstica +---------------------------------- +- Abre una Issue usando la plantilla "Feature request" describiendo el caso de uso y la solucin propuesta. + +5. Flujo de trabajo para contribuir (fork & PR) +----------------------------------------------- +1. Haz fork del repositorio y clona tu fork: + ```bash + git clone https://github.com/tu-usuario/OTManager.Wapi.git + cd OTManager.Wapi + ``` +2. Crea una rama para tu trabajo (nombres sugeridos): + - feature/ + - fix/ + - docs/ + + ```bash + git checkout -b feature/mi-nueva-funcionalidad + ``` +3. Asegrate de que tu rama est actualizada con la rama principal upstream: + ```bash + git remote add upstream https://github.com/organizacion/OTManager.Wapi.git + git fetch upstream + git rebase upstream/main + ``` +4. Realiza cambios pequeos y con commits atmicos. +5. Ejecuta pruebas y formatea el cdigo (ver seccin herramientas). +6. Publica tu rama y abre un Pull Request usando la plantilla PR disponible. + +6. Requisitos y herramientas para el desarrollo +---------------------------------------------- +- .NET 9 SDK instalado +- SQL Server (o proveedor EF Core compatible) para ejecutar migraciones locales +- dotnet-ef (si trabajas con migraciones): + ```bash + dotnet tool install --global dotnet-ef + ``` + +7. Ejecutar el proyecto localmente +--------------------------------- +- Restaurar dependencias: + ```bash + dotnet restore + ``` +- Aplicar migraciones (si corresponde): + ```bash + dotnet ef database update --project src/OTManager.Data/OTManager.Data.csproj --startup-project src/OTManager.Api/OTManager.Api.csproj + ``` +- Ejecutar la API y la UI: + ```bash + dotnet run --project src/OTManager.Api/OTManager.Api.csproj + dotnet run --project src/OTManager.Web/OTManager.Web.csproj + ``` + +8. Estilo de cdigo y linters +---------------------------- +- Sigue las reglas de C# y .editorconfig del repositorio (si existe). +- Usa dotnet format para formatear tu cdigo antes de subirlo: + ```bash + dotnet tool restore + dotnet format + ``` + +9. Pruebas +---------- +- Aade/actualiza pruebas unitarias para cualquier cambio en la lgica de negocio. +- Ejecuta todas las pruebas antes de abrir PR: + ```bash + dotnet test + ``` + +10. Convenciones de commits +--------------------------- +Usa mensajes claros y preferiblemente el estndar "Conventional Commits": +- feat: Nueva funcionalidad +- fix: Correccin de bug +- docs: Cambios en documentacin +- style: Formateo, espacios, punto y coma +- refactor: Refactorizacin sin cambiar comportamiento +- test: Aadir/ajustar pruebas +- chore: Cambios en build o herramientas + +11. Proceso de Pull Request +--------------------------- +- Crea PR contra la rama main (o la rama de desarrollo si el proyecto la usa). +- Usa la plantilla de PR ubicada en `.github/PULL_REQUEST_TEMPLATE.md`. +- Describe qu hace el cambio, por qu es necesario y cmo probarlo. + +Checklist sugerida para PRs (ver plantilla): +- [ ] El cdigo compila y pasa las pruebas locales +- [ ] Agregado/actualizado tests +- [ ] Documentacin actualizada si aplica +- [ ] Se formate el cdigo +- [ ] No incluye secretos en el commit + +12. Seguridad +------------- +Revisa `SECURITY.md` para el proceso de reporte de vulnerabilidades y prcticas de seguridad. + +13. Contacto +----------- +Si tienes dudas sobre cmo contribuir, abre una Issue etiquetada como "help wanted" o contacta a los mantenedores. diff --git a/Code_Of_Conduct.md b/Code_Of_Conduct.md new file mode 100644 index 0000000..20205dd --- /dev/null +++ b/Code_Of_Conduct.md @@ -0,0 +1,38 @@ +Cdigo de Conducta (Code of Conduct) +===================================== + +Nuestro objetivo es crear un proyecto inclusivo y respetuoso donde todas las personas se sientan bienvenidas y con disposicin para colaborar. + +1. Normas de comportamiento +--------------------------- +- Trata a todas las personas con respeto y profesionalismo. +- S constructivo en la crtica: enfcate en el cdigo y el contenido, no en la persona. +- Evita lenguaje ofensivo, despectivo, discriminatorio o sexualmente explcito. +- Respeta la diversidad de orgenes, opiniones, identidades y niveles de experiencia. + +2. Alcance +--------- +Este cdigo aplica a todos los mbitos relacionados con el proyecto: Issues, Pull Requests, revisiones de cdigo, discusiones en repositorios, canales de chat vinculados, listas de correo y eventos asociados al proyecto. + +3. Cmo reportar un incumplimiento +--------------------------------- +Si eres testigo o vctima de una conducta que viola este cdigo: +- Abre una Issue privada (si tu plataforma lo permite) etiquetada como `conduct` o `confidential`. +- Alternativamente, contacta a los mantenedores por correo electrnico (aade la direccin de contacto en el README o en la seccin de mantenedores). +- Proporciona informacin suficiente: descripcin del incidente, participantes, fecha/hora, enlaces relevantes y, si es posible, evidencia. + +4. Proceso de resolucin +------------------------ +- Los mantenedores revisarn el reporte y podrn solicitar informacin adicional. +- En casos claros de violacin, se podr pedir disculpas pblicas/privadas, remover contenido, suspender permisos de colaboracin o expulsar del proyecto. +- Las decisiones se aplicarn con proporcionalidad y registro interno. + +5. Confidencialidad +------------------- +Los reportes y la informacin sensible se manejarn de forma confidencial en la medida de lo posible. + +6. Implementacin y actualizacin +--------------------------------- +Este documento puede actualizarse con el tiempo. Los mantenedores publicarn la fecha de la ltima revisin y la versin del documento. + +Gracias por contribuir a un entorno seguro y respetuoso. \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt index c7b086f..8cfa5aa 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Adrian Mesa +Copyright (c) 2024 Adrian Mesa Sacasas Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/OTManager.Data/Account/ApplicationRole.cs b/OTManager.Data/Account/ApplicationRole.cs new file mode 100644 index 0000000..55a7991 --- /dev/null +++ b/OTManager.Data/Account/ApplicationRole.cs @@ -0,0 +1,7 @@ +using Microsoft.AspNetCore.Identity; + +namespace OTManager.Data.Account; + +public class ApplicationRole : IdentityRole +{ +} diff --git a/OTManager.Data/Account/ApplicationUser.cs b/OTManager.Data/Account/ApplicationUser.cs new file mode 100644 index 0000000..653cf08 --- /dev/null +++ b/OTManager.Data/Account/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace OTManager.Data.Account; + +public class ApplicationUser : IdentityUser +{ + +} \ No newline at end of file diff --git a/OTManager.Data/Audites/Audited.cs b/OTManager.Data/Audites/Audited.cs index ad926d8..9a263a0 100644 --- a/OTManager.Data/Audites/Audited.cs +++ b/OTManager.Data/Audites/Audited.cs @@ -1,7 +1,5 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; -using OTManager.Core.Entities.Abstracts; -using System; namespace OTManager.Data.Audites; @@ -51,4 +49,4 @@ public static void AplicarAuditoria(ChangeTracker changeTracker, string userName } } -public interface IAuditableEntity {} +public interface IAuditableEntity { } diff --git a/OTManager.Data/Configs/ClientConfig.cs b/OTManager.Data/Configs/ClientConfig.cs index 5cbf4b8..90b5f8f 100644 --- a/OTManager.Data/Configs/ClientConfig.cs +++ b/OTManager.Data/Configs/ClientConfig.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; + using OTManager.Core.Entities.OT; namespace OTManager.Data.Configs; diff --git a/OTManager.Data/Configs/FactureConfig.cs b/OTManager.Data/Configs/FactureConfig.cs index 1fd709b..b376b81 100644 --- a/OTManager.Data/Configs/FactureConfig.cs +++ b/OTManager.Data/Configs/FactureConfig.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; + using OTManager.Core.Entities.OT; namespace OTManager.Data.Configs; diff --git a/OTManager.Data/Configs/MaterialConfig.cs b/OTManager.Data/Configs/MaterialConfig.cs index 9225f41..0354d9d 100644 --- a/OTManager.Data/Configs/MaterialConfig.cs +++ b/OTManager.Data/Configs/MaterialConfig.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; + using OTManager.Core.Entities.OT; namespace OTManager.Data.Configs; diff --git a/OTManager.Data/Configs/MaterialCostConfig.cs b/OTManager.Data/Configs/MaterialCostConfig.cs index 8933c40..99b7e3c 100644 --- a/OTManager.Data/Configs/MaterialCostConfig.cs +++ b/OTManager.Data/Configs/MaterialCostConfig.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; + using OTManager.Core.Entities.OT; namespace OTManager.Data.Configs; diff --git a/OTManager.Data/Configs/WorkerConfig.cs b/OTManager.Data/Configs/WorkerConfig.cs index 2e067be..5446568 100644 --- a/OTManager.Data/Configs/WorkerConfig.cs +++ b/OTManager.Data/Configs/WorkerConfig.cs @@ -14,6 +14,6 @@ public void Configure(EntityTypeBuilder builder) builder.HasIndex(s => s.Name); builder.HasIndex(s => s.CreatedAt); - builder.Property(s => s.HourlyRate).HasColumnType("decimal(2,4)").HasDefaultValue(0.00); + builder.Property(s => s.HourlyRate).HasColumnType("decimal(2,2)").HasDefaultValue(0.00); } } diff --git a/OTManager.Data/Configs/WorkerCostConfig.cs b/OTManager.Data/Configs/WorkerCostConfig.cs index 8407e9d..d8fe4aa 100644 --- a/OTManager.Data/Configs/WorkerCostConfig.cs +++ b/OTManager.Data/Configs/WorkerCostConfig.cs @@ -14,8 +14,8 @@ public void Configure(EntityTypeBuilder builder) builder.HasIndex(s => s.Name); builder.HasIndex(s => s.CreatedAt); - builder.Property(s => s.HourlyRate).HasColumnType("decimal(2,4)").HasDefaultValue(0.00); - builder.Property(s => s.HoursWorked).HasColumnType("decimal(2,4)").HasDefaultValue(0.00); - builder.Property(s => s.TotalCost).HasColumnType("decimal(2,4)").HasDefaultValue(0.00); + builder.Property(s => s.HourlyRate).HasColumnType("decimal(2,2)").HasDefaultValue(0.00); + builder.Property(s => s.HoursWorked).HasColumnType("decimal(2,2)").HasDefaultValue(0.00); + builder.Property(s => s.TotalCost).HasColumnType("decimal(2,2)").HasDefaultValue(0.00); } } diff --git a/OTManager.Data/Context/ApplicationDbContext.cs b/OTManager.Data/Context/ApplicationDbContext.cs index 78f6e19..a4f14d1 100644 --- a/OTManager.Data/Context/ApplicationDbContext.cs +++ b/OTManager.Data/Context/ApplicationDbContext.cs @@ -1,26 +1,38 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; -using OTManager.Data.Audites; using OTManager.Core.Entities.OT; +using OTManager.Data.Account; +using OTManager.Data.Audites; namespace OTManager.Data.Context; -public class ApplicationDbContext(DbContextOptions options) - : DbContext(options) +public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext, Guid>(options) { public DbSet Clients => Set(); public DbSet Features => Set(); public DbSet Materials => Set(); - public DbSet MaterialCosts => Set(); public DbSet Orders => Set(); public DbSet Services => Set(); - public DbSet ServiceCosts => Set(); public DbSet Workers => Set(); - public DbSet WorkerCosts => Set(); - protected override void OnModelCreating(ModelBuilder modelBuilder) + protected override void OnModelCreating(ModelBuilder builder) { - modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly); + builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly); + + builder.Entity>(e => + { + e.HasKey(l => new { l.LoginProvider, l.ProviderKey }); + }); + builder.Entity>(e => + { + e.HasKey(r => new { r.UserId, r.RoleId }); + }); + builder.Entity>(e => + { + e.HasKey(t => new { t.UserId, t.LoginProvider, t.Name }); + }); } public override int SaveChanges() diff --git a/OTManager.Data/Migrations/20251009232432_InitialCommit.Designer.cs b/OTManager.Data/Migrations/20251015220442_AuthenticationCommit.Designer.cs similarity index 68% rename from OTManager.Data/Migrations/20251009232432_InitialCommit.Designer.cs rename to OTManager.Data/Migrations/20251015220442_AuthenticationCommit.Designer.cs index b6593d7..e30b766 100644 --- a/OTManager.Data/Migrations/20251009232432_InitialCommit.Designer.cs +++ b/OTManager.Data/Migrations/20251015220442_AuthenticationCommit.Designer.cs @@ -12,8 +12,8 @@ namespace OTManager.Data.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20251009232432_InitialCommit")] - partial class InitialCommit + [Migration("20251015220442_AuthenticationCommit")] + partial class AuthenticationCommit { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -25,6 +25,101 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("RoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("UserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.ToTable("UserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "RoleId"); + + b.ToTable("UserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("UserTokens"); + }); + modelBuilder.Entity("OTManager.Core.Entities.OT.Client", b => { b.Property("Id") @@ -41,7 +136,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("CreatedBy") .IsRequired() - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.Property("Name") .IsRequired() @@ -52,17 +148,19 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("datetime2"); b.Property("UpdatedBy") - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.HasKey("Id"); - b.HasIndex("Code"); + b.HasIndex("Code") + .IsUnique(); b.HasIndex("CreatedAt"); b.HasIndex("Name"); - b.ToTable("Clients"); + b.ToTable("Client", (string)null); }); modelBuilder.Entity("OTManager.Core.Entities.OT.Facture", b => @@ -84,7 +182,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("CreatedBy") .IsRequired() - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.Property("OrderId") .HasColumnType("uniqueidentifier"); @@ -92,25 +191,27 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("TotalPrice") .ValueGeneratedOnAdd() .HasColumnType("decimal(18,2)") - .HasDefaultValue(0m); + .HasDefaultValue(0.00m); b.Property("UpdatedAt") .HasColumnType("datetime2"); b.Property("UpdatedBy") - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.HasKey("Id"); b.HasIndex("ClientId"); - b.HasIndex("Code"); + b.HasIndex("Code") + .IsUnique(); b.HasIndex("CreatedAt"); b.HasIndex("OrderId"); - b.ToTable("Features"); + b.ToTable("Facture", (string)null); }); modelBuilder.Entity("OTManager.Core.Entities.OT.Material", b => @@ -129,7 +230,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("CreatedBy") .IsRequired() - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.Property("MeasureUnit") .IsRequired() @@ -142,23 +244,25 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("UnitCost") .ValueGeneratedOnAdd() .HasColumnType("decimal(18,2)") - .HasDefaultValue(0m); + .HasDefaultValue(0.00m); b.Property("UpdatedAt") .HasColumnType("datetime2"); b.Property("UpdatedBy") - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.HasKey("Id"); - b.HasIndex("Code"); + b.HasIndex("Code") + .IsUnique(); b.HasIndex("CreatedAt"); b.HasIndex("Name"); - b.ToTable("Materials"); + b.ToTable("Material", (string)null); }); modelBuilder.Entity("OTManager.Core.Entities.OT.MaterialCost", b => @@ -177,7 +281,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("CreatedBy") .IsRequired() - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.Property("MeasureUnit") .IsRequired() @@ -193,27 +298,29 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Quantity") .ValueGeneratedOnAdd() .HasColumnType("decimal(18,6)") - .HasDefaultValue(0m); + .HasDefaultValue(0.00m); b.Property("TotalCost") .ValueGeneratedOnAdd() .HasColumnType("decimal(18,2)") - .HasDefaultValue(0m); + .HasDefaultValue(0.00m); b.Property("UnitCost") .ValueGeneratedOnAdd() .HasColumnType("decimal(18,2)") - .HasDefaultValue(0m); + .HasDefaultValue(0.00m); b.Property("UpdatedAt") .HasColumnType("datetime2"); b.Property("UpdatedBy") - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.HasKey("Id"); - b.HasIndex("Code"); + b.HasIndex("Code") + .IsUnique(); b.HasIndex("CreatedAt"); @@ -221,7 +328,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("OrderId"); - b.ToTable("MaterialCosts"); + b.ToTable("MaterialCost", (string)null); }); modelBuilder.Entity("OTManager.Core.Entities.OT.Order", b => @@ -238,7 +345,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("CreatedBy") .IsRequired() - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.Property("OrderNumber") .IsRequired() @@ -254,13 +362,14 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("TotalCost") .ValueGeneratedOnAdd() .HasColumnType("decimal(18,2)") - .HasDefaultValue(0m); + .HasDefaultValue(0.00m); b.Property("UpdatedAt") .HasColumnType("datetime2"); b.Property("UpdatedBy") - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.HasKey("Id"); @@ -268,12 +377,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("CreatedAt"); - b.HasIndex("OrderNumber"); + b.HasIndex("OrderNumber") + .IsUnique(); - b.ToTable("Orders"); + b.ToTable("Order", (string)null); }); - modelBuilder.Entity("OTManager.Core.Entities.OT.OrderService", b => + modelBuilder.Entity("OTManager.Core.Entities.OT.ServiceCost", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -294,11 +404,22 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("nvarchar(450)"); + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + b.Property("Price") .ValueGeneratedOnAdd() .HasColumnType("decimal(18,2)") .HasDefaultValue(0m); + b.Property("Quantity") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("TotalPrice") + .HasColumnType("decimal(18,2)"); + b.Property("UpdatedAt") .HasColumnType("datetime2"); @@ -311,10 +432,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("Name"); - b.ToTable("Services"); + b.HasIndex("OrderId"); + + b.ToTable("ServiceCost"); }); - modelBuilder.Entity("OTManager.Core.Entities.OT.ServiceCost", b => + modelBuilder.Entity("OTManager.Core.Entities.OT.ServiceOrder", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -335,22 +458,11 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("nvarchar(450)"); - b.Property("OrderId") - .HasColumnType("uniqueidentifier"); - b.Property("Price") .ValueGeneratedOnAdd() .HasColumnType("decimal(18,2)") .HasDefaultValue(0m); - b.Property("Quantity") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(1); - - b.Property("TotalPrice") - .HasColumnType("decimal(18,2)"); - b.Property("UpdatedAt") .HasColumnType("datetime2"); @@ -363,9 +475,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("Name"); - b.HasIndex("OrderId"); - - b.ToTable("ServiceCosts"); + b.ToTable("Services"); }); modelBuilder.Entity("OTManager.Core.Entities.OT.Worker", b => @@ -383,7 +493,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("HourlyRate") .ValueGeneratedOnAdd() - .HasColumnType("decimal(2,4)") + .HasColumnType("decimal(2,2)") .HasDefaultValue(0m); b.Property("Name") @@ -420,12 +530,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("HourlyRate") .ValueGeneratedOnAdd() - .HasColumnType("decimal(2,4)") + .HasColumnType("decimal(2,2)") .HasDefaultValue(0m); b.Property("HoursWorked") .ValueGeneratedOnAdd() - .HasColumnType("decimal(2,4)") + .HasColumnType("decimal(2,2)") .HasDefaultValue(0m); b.Property("Name") @@ -437,7 +547,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("TotalCost") .ValueGeneratedOnAdd() - .HasColumnType("decimal(2,4)") + .HasColumnType("decimal(2,2)") .HasDefaultValue(0m); b.Property("UpdatedAt") @@ -454,7 +564,80 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("OrderId"); - b.ToTable("WorkerCosts"); + b.ToTable("WorkerCost"); + }); + + modelBuilder.Entity("OTManager.Data.Context.ApplicationRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("NormalizedName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Roles"); + }); + + modelBuilder.Entity("OTManager.Data.Context.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasColumnType("nvarchar(max)"); + + b.Property("NormalizedUserName") + .HasColumnType("nvarchar(max)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Users"); }); modelBuilder.Entity("OTManager.Core.Entities.OT.Facture", b => diff --git a/OTManager.Data/Migrations/20251009232432_InitialCommit.cs b/OTManager.Data/Migrations/20251015220442_AuthenticationCommit.cs similarity index 54% rename from OTManager.Data/Migrations/20251009232432_InitialCommit.cs rename to OTManager.Data/Migrations/20251015220442_AuthenticationCommit.cs index 383862f..9d6a96c 100644 --- a/OTManager.Data/Migrations/20251009232432_InitialCommit.cs +++ b/OTManager.Data/Migrations/20251015220442_AuthenticationCommit.cs @@ -1,50 +1,78 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable namespace OTManager.Data.Migrations { /// - public partial class InitialCommit : Migration + public partial class AuthenticationCommit : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( - name: "Clients", + name: "Client", columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), Code = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), CreatedAt = table.Column(type: "datetime2", nullable: true), - CreatedBy = table.Column(type: "nvarchar(max)", nullable: false), + CreatedBy = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), UpdatedAt = table.Column(type: "datetime2", nullable: true), - UpdatedBy = table.Column(type: "nvarchar(max)", nullable: true) + UpdatedBy = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true) }, constraints: table => { - table.PrimaryKey("PK_Clients", x => x.Id); + table.PrimaryKey("PK_Client", x => x.Id); }); migrationBuilder.CreateTable( - name: "Materials", + name: "Material", columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), Code = table.Column(type: "nvarchar(32)", maxLength: 32, nullable: false), Name = table.Column(type: "nvarchar(450)", nullable: false), MeasureUnit = table.Column(type: "nvarchar(max)", nullable: false), - UnitCost = table.Column(type: "decimal(18,2)", nullable: false, defaultValue: 0m), + UnitCost = table.Column(type: "decimal(18,2)", nullable: false, defaultValue: 0.00m), CreatedAt = table.Column(type: "datetime2", nullable: true), - CreatedBy = table.Column(type: "nvarchar(max)", nullable: false), + CreatedBy = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), UpdatedAt = table.Column(type: "datetime2", nullable: true), - UpdatedBy = table.Column(type: "nvarchar(max)", nullable: true) + UpdatedBy = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Material", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "uniqueidentifier", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_Materials", x => x.Id); + table.PrimaryKey("PK_RoleClaims", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Roles", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(max)", nullable: true), + NormalizedName = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Roles", x => x.Id); }); migrationBuilder.CreateTable( @@ -65,13 +93,93 @@ protected override void Up(MigrationBuilder migrationBuilder) table.PrimaryKey("PK_Services", x => x.Id); }); + migrationBuilder.CreateTable( + name: "UserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "uniqueidentifier", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_UserClaims", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "uniqueidentifier", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserLogins", x => new { x.LoginProvider, x.ProviderKey }); + }); + + migrationBuilder.CreateTable( + name: "UserRoles", + columns: table => new + { + UserId = table.Column(type: "uniqueidentifier", nullable: false), + RoleId = table.Column(type: "uniqueidentifier", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserRoles", x => new { x.UserId, x.RoleId }); + }); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + UserName = table.Column(type: "nvarchar(max)", nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(max)", nullable: true), + Email = table.Column(type: "nvarchar(max)", nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(max)", nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UserTokens", + columns: table => new + { + UserId = table.Column(type: "uniqueidentifier", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_UserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + }); + migrationBuilder.CreateTable( name: "Workers", columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), Name = table.Column(type: "nvarchar(450)", nullable: false), - HourlyRate = table.Column(type: "decimal(2,4)", nullable: false, defaultValue: 0m), + HourlyRate = table.Column(type: "decimal(2,2)", nullable: false, defaultValue: 0m), CreatedAt = table.Column(type: "datetime2", nullable: true), CreatedBy = table.Column(type: "nvarchar(max)", nullable: false), UpdatedAt = table.Column(type: "datetime2", nullable: true), @@ -83,7 +191,7 @@ protected override void Up(MigrationBuilder migrationBuilder) }); migrationBuilder.CreateTable( - name: "Orders", + name: "Order", columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), @@ -91,84 +199,84 @@ protected override void Up(MigrationBuilder migrationBuilder) OrderNumber = table.Column(type: "nvarchar(10)", maxLength: 10, nullable: false), ClientId = table.Column(type: "uniqueidentifier", nullable: false), Status = table.Column(type: "bit", nullable: false), - TotalCost = table.Column(type: "decimal(18,2)", nullable: false, defaultValue: 0m), + TotalCost = table.Column(type: "decimal(18,2)", nullable: false, defaultValue: 0.00m), CreatedAt = table.Column(type: "datetime2", nullable: true), - CreatedBy = table.Column(type: "nvarchar(max)", nullable: false), + CreatedBy = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), UpdatedAt = table.Column(type: "datetime2", nullable: true), - UpdatedBy = table.Column(type: "nvarchar(max)", nullable: true) + UpdatedBy = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true) }, constraints: table => { - table.PrimaryKey("PK_Orders", x => x.Id); + table.PrimaryKey("PK_Order", x => x.Id); table.ForeignKey( - name: "FK_Orders_Clients_ClientId", + name: "FK_Order_Client_ClientId", column: x => x.ClientId, - principalTable: "Clients", + principalTable: "Client", principalColumn: "Id", onDelete: ReferentialAction.Restrict); }); migrationBuilder.CreateTable( - name: "Features", + name: "Facture", columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), Code = table.Column(type: "nvarchar(10)", maxLength: 10, nullable: false), ClientId = table.Column(type: "uniqueidentifier", nullable: false), OrderId = table.Column(type: "uniqueidentifier", nullable: false), - TotalPrice = table.Column(type: "decimal(18,2)", nullable: false, defaultValue: 0m), + TotalPrice = table.Column(type: "decimal(18,2)", nullable: false, defaultValue: 0.00m), CreatedAt = table.Column(type: "datetime2", nullable: true), - CreatedBy = table.Column(type: "nvarchar(max)", nullable: false), + CreatedBy = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), UpdatedAt = table.Column(type: "datetime2", nullable: true), - UpdatedBy = table.Column(type: "nvarchar(max)", nullable: true) + UpdatedBy = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true) }, constraints: table => { - table.PrimaryKey("PK_Features", x => x.Id); + table.PrimaryKey("PK_Facture", x => x.Id); table.ForeignKey( - name: "FK_Features_Clients_ClientId", + name: "FK_Facture_Client_ClientId", column: x => x.ClientId, - principalTable: "Clients", + principalTable: "Client", principalColumn: "Id", onDelete: ReferentialAction.Restrict); table.ForeignKey( - name: "FK_Features_Orders_OrderId", + name: "FK_Facture_Order_OrderId", column: x => x.OrderId, - principalTable: "Orders", + principalTable: "Order", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "MaterialCosts", + name: "MaterialCost", columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), Code = table.Column(type: "nvarchar(32)", maxLength: 32, nullable: false), Name = table.Column(type: "nvarchar(450)", nullable: false), MeasureUnit = table.Column(type: "nvarchar(max)", nullable: false), - UnitCost = table.Column(type: "decimal(18,2)", nullable: false, defaultValue: 0m), + UnitCost = table.Column(type: "decimal(18,2)", nullable: false, defaultValue: 0.00m), OrderId = table.Column(type: "uniqueidentifier", nullable: false), - Quantity = table.Column(type: "decimal(18,6)", nullable: false, defaultValue: 0m), - TotalCost = table.Column(type: "decimal(18,2)", nullable: false, defaultValue: 0m), + Quantity = table.Column(type: "decimal(18,6)", nullable: false, defaultValue: 0.00m), + TotalCost = table.Column(type: "decimal(18,2)", nullable: false, defaultValue: 0.00m), CreatedAt = table.Column(type: "datetime2", nullable: true), - CreatedBy = table.Column(type: "nvarchar(max)", nullable: false), + CreatedBy = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), UpdatedAt = table.Column(type: "datetime2", nullable: true), - UpdatedBy = table.Column(type: "nvarchar(max)", nullable: true) + UpdatedBy = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true) }, constraints: table => { - table.PrimaryKey("PK_MaterialCosts", x => x.Id); + table.PrimaryKey("PK_MaterialCost", x => x.Id); table.ForeignKey( - name: "FK_MaterialCosts_Orders_OrderId", + name: "FK_MaterialCost_Order_OrderId", column: x => x.OrderId, - principalTable: "Orders", + principalTable: "Order", principalColumn: "Id", onDelete: ReferentialAction.Restrict); }); migrationBuilder.CreateTable( - name: "ServiceCosts", + name: "ServiceCost", columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), @@ -185,25 +293,25 @@ protected override void Up(MigrationBuilder migrationBuilder) }, constraints: table => { - table.PrimaryKey("PK_ServiceCosts", x => x.Id); + table.PrimaryKey("PK_ServiceCost", x => x.Id); table.ForeignKey( - name: "FK_ServiceCosts_Orders_OrderId", + name: "FK_ServiceCost_Order_OrderId", column: x => x.OrderId, - principalTable: "Orders", + principalTable: "Order", principalColumn: "Id", onDelete: ReferentialAction.Restrict); }); migrationBuilder.CreateTable( - name: "WorkerCosts", + name: "WorkerCost", columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), Name = table.Column(type: "nvarchar(450)", nullable: false), - HourlyRate = table.Column(type: "decimal(2,4)", nullable: false, defaultValue: 0m), + HourlyRate = table.Column(type: "decimal(2,2)", nullable: false, defaultValue: 0m), OrderId = table.Column(type: "uniqueidentifier", nullable: false), - HoursWorked = table.Column(type: "decimal(2,4)", nullable: false, defaultValue: 0m), - TotalCost = table.Column(type: "decimal(2,4)", nullable: false, defaultValue: 0m), + HoursWorked = table.Column(type: "decimal(2,2)", nullable: false, defaultValue: 0m), + TotalCost = table.Column(type: "decimal(2,2)", nullable: false, defaultValue: 0m), CreatedAt = table.Column(type: "datetime2", nullable: true), CreatedBy = table.Column(type: "nvarchar(max)", nullable: false), UpdatedAt = table.Column(type: "datetime2", nullable: true), @@ -211,113 +319,118 @@ protected override void Up(MigrationBuilder migrationBuilder) }, constraints: table => { - table.PrimaryKey("PK_WorkerCosts", x => x.Id); + table.PrimaryKey("PK_WorkerCost", x => x.Id); table.ForeignKey( - name: "FK_WorkerCosts_Orders_OrderId", + name: "FK_WorkerCost_Order_OrderId", column: x => x.OrderId, - principalTable: "Orders", + principalTable: "Order", principalColumn: "Id", onDelete: ReferentialAction.Restrict); }); migrationBuilder.CreateIndex( - name: "IX_Clients_Code", - table: "Clients", - column: "Code"); + name: "IX_Client_Code", + table: "Client", + column: "Code", + unique: true); migrationBuilder.CreateIndex( - name: "IX_Clients_CreatedAt", - table: "Clients", + name: "IX_Client_CreatedAt", + table: "Client", column: "CreatedAt"); migrationBuilder.CreateIndex( - name: "IX_Clients_Name", - table: "Clients", + name: "IX_Client_Name", + table: "Client", column: "Name"); migrationBuilder.CreateIndex( - name: "IX_Features_ClientId", - table: "Features", + name: "IX_Facture_ClientId", + table: "Facture", column: "ClientId"); migrationBuilder.CreateIndex( - name: "IX_Features_Code", - table: "Features", - column: "Code"); + name: "IX_Facture_Code", + table: "Facture", + column: "Code", + unique: true); migrationBuilder.CreateIndex( - name: "IX_Features_CreatedAt", - table: "Features", + name: "IX_Facture_CreatedAt", + table: "Facture", column: "CreatedAt"); migrationBuilder.CreateIndex( - name: "IX_Features_OrderId", - table: "Features", + name: "IX_Facture_OrderId", + table: "Facture", column: "OrderId"); migrationBuilder.CreateIndex( - name: "IX_MaterialCosts_Code", - table: "MaterialCosts", - column: "Code"); + name: "IX_Material_Code", + table: "Material", + column: "Code", + unique: true); migrationBuilder.CreateIndex( - name: "IX_MaterialCosts_CreatedAt", - table: "MaterialCosts", + name: "IX_Material_CreatedAt", + table: "Material", column: "CreatedAt"); migrationBuilder.CreateIndex( - name: "IX_MaterialCosts_Name", - table: "MaterialCosts", + name: "IX_Material_Name", + table: "Material", column: "Name"); migrationBuilder.CreateIndex( - name: "IX_MaterialCosts_OrderId", - table: "MaterialCosts", - column: "OrderId"); - - migrationBuilder.CreateIndex( - name: "IX_Materials_Code", - table: "Materials", - column: "Code"); + name: "IX_MaterialCost_Code", + table: "MaterialCost", + column: "Code", + unique: true); migrationBuilder.CreateIndex( - name: "IX_Materials_CreatedAt", - table: "Materials", + name: "IX_MaterialCost_CreatedAt", + table: "MaterialCost", column: "CreatedAt"); migrationBuilder.CreateIndex( - name: "IX_Materials_Name", - table: "Materials", + name: "IX_MaterialCost_Name", + table: "MaterialCost", column: "Name"); migrationBuilder.CreateIndex( - name: "IX_Orders_ClientId", - table: "Orders", + name: "IX_MaterialCost_OrderId", + table: "MaterialCost", + column: "OrderId"); + + migrationBuilder.CreateIndex( + name: "IX_Order_ClientId", + table: "Order", column: "ClientId"); migrationBuilder.CreateIndex( - name: "IX_Orders_CreatedAt", - table: "Orders", + name: "IX_Order_CreatedAt", + table: "Order", column: "CreatedAt"); migrationBuilder.CreateIndex( - name: "IX_Orders_OrderNumber", - table: "Orders", - column: "OrderNumber"); + name: "IX_Order_OrderNumber", + table: "Order", + column: "OrderNumber", + unique: true); migrationBuilder.CreateIndex( - name: "IX_ServiceCosts_CreatedAt", - table: "ServiceCosts", + name: "IX_ServiceCost_CreatedAt", + table: "ServiceCost", column: "CreatedAt"); migrationBuilder.CreateIndex( - name: "IX_ServiceCosts_Name", - table: "ServiceCosts", + name: "IX_ServiceCost_Name", + table: "ServiceCost", column: "Name"); migrationBuilder.CreateIndex( - name: "IX_ServiceCosts_OrderId", - table: "ServiceCosts", + name: "IX_ServiceCost_OrderId", + table: "ServiceCost", column: "OrderId"); migrationBuilder.CreateIndex( @@ -331,18 +444,18 @@ protected override void Up(MigrationBuilder migrationBuilder) column: "Name"); migrationBuilder.CreateIndex( - name: "IX_WorkerCosts_CreatedAt", - table: "WorkerCosts", + name: "IX_WorkerCost_CreatedAt", + table: "WorkerCost", column: "CreatedAt"); migrationBuilder.CreateIndex( - name: "IX_WorkerCosts_Name", - table: "WorkerCosts", + name: "IX_WorkerCost_Name", + table: "WorkerCost", column: "Name"); migrationBuilder.CreateIndex( - name: "IX_WorkerCosts_OrderId", - table: "WorkerCosts", + name: "IX_WorkerCost_OrderId", + table: "WorkerCost", column: "OrderId"); migrationBuilder.CreateIndex( @@ -360,31 +473,52 @@ protected override void Up(MigrationBuilder migrationBuilder) protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( - name: "Features"); + name: "Facture"); + + migrationBuilder.DropTable( + name: "Material"); + + migrationBuilder.DropTable( + name: "MaterialCost"); migrationBuilder.DropTable( - name: "MaterialCosts"); + name: "RoleClaims"); migrationBuilder.DropTable( - name: "Materials"); + name: "Roles"); migrationBuilder.DropTable( - name: "ServiceCosts"); + name: "ServiceCost"); migrationBuilder.DropTable( name: "Services"); migrationBuilder.DropTable( - name: "WorkerCosts"); + name: "UserClaims"); + + migrationBuilder.DropTable( + name: "UserLogins"); + + migrationBuilder.DropTable( + name: "UserRoles"); + + migrationBuilder.DropTable( + name: "Users"); + + migrationBuilder.DropTable( + name: "UserTokens"); + + migrationBuilder.DropTable( + name: "WorkerCost"); migrationBuilder.DropTable( name: "Workers"); migrationBuilder.DropTable( - name: "Orders"); + name: "Order"); migrationBuilder.DropTable( - name: "Clients"); + name: "Client"); } } } diff --git a/OTManager.Data/Migrations/ApplicationDbContextModelSnapshot.cs b/OTManager.Data/Migrations/ApplicationDbContextModelSnapshot.cs index d12d53e..5fc0719 100644 --- a/OTManager.Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/OTManager.Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -22,6 +22,101 @@ protected override void BuildModel(ModelBuilder modelBuilder) SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("RoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("UserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.ToTable("UserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "RoleId"); + + b.ToTable("UserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("UserTokens"); + }); + modelBuilder.Entity("OTManager.Core.Entities.OT.Client", b => { b.Property("Id") @@ -38,7 +133,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("CreatedBy") .IsRequired() - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.Property("Name") .IsRequired() @@ -49,17 +145,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("datetime2"); b.Property("UpdatedBy") - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.HasKey("Id"); - b.HasIndex("Code"); + b.HasIndex("Code") + .IsUnique(); b.HasIndex("CreatedAt"); b.HasIndex("Name"); - b.ToTable("Clients"); + b.ToTable("Client", (string)null); }); modelBuilder.Entity("OTManager.Core.Entities.OT.Facture", b => @@ -81,7 +179,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("CreatedBy") .IsRequired() - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.Property("OrderId") .HasColumnType("uniqueidentifier"); @@ -89,25 +188,27 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("TotalPrice") .ValueGeneratedOnAdd() .HasColumnType("decimal(18,2)") - .HasDefaultValue(0m); + .HasDefaultValue(0.00m); b.Property("UpdatedAt") .HasColumnType("datetime2"); b.Property("UpdatedBy") - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.HasKey("Id"); b.HasIndex("ClientId"); - b.HasIndex("Code"); + b.HasIndex("Code") + .IsUnique(); b.HasIndex("CreatedAt"); b.HasIndex("OrderId"); - b.ToTable("Features"); + b.ToTable("Facture", (string)null); }); modelBuilder.Entity("OTManager.Core.Entities.OT.Material", b => @@ -126,7 +227,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("CreatedBy") .IsRequired() - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.Property("MeasureUnit") .IsRequired() @@ -139,23 +241,25 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("UnitCost") .ValueGeneratedOnAdd() .HasColumnType("decimal(18,2)") - .HasDefaultValue(0m); + .HasDefaultValue(0.00m); b.Property("UpdatedAt") .HasColumnType("datetime2"); b.Property("UpdatedBy") - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.HasKey("Id"); - b.HasIndex("Code"); + b.HasIndex("Code") + .IsUnique(); b.HasIndex("CreatedAt"); b.HasIndex("Name"); - b.ToTable("Materials"); + b.ToTable("Material", (string)null); }); modelBuilder.Entity("OTManager.Core.Entities.OT.MaterialCost", b => @@ -174,7 +278,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("CreatedBy") .IsRequired() - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.Property("MeasureUnit") .IsRequired() @@ -190,27 +295,29 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Quantity") .ValueGeneratedOnAdd() .HasColumnType("decimal(18,6)") - .HasDefaultValue(0m); + .HasDefaultValue(0.00m); b.Property("TotalCost") .ValueGeneratedOnAdd() .HasColumnType("decimal(18,2)") - .HasDefaultValue(0m); + .HasDefaultValue(0.00m); b.Property("UnitCost") .ValueGeneratedOnAdd() .HasColumnType("decimal(18,2)") - .HasDefaultValue(0m); + .HasDefaultValue(0.00m); b.Property("UpdatedAt") .HasColumnType("datetime2"); b.Property("UpdatedBy") - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.HasKey("Id"); - b.HasIndex("Code"); + b.HasIndex("Code") + .IsUnique(); b.HasIndex("CreatedAt"); @@ -218,7 +325,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("OrderId"); - b.ToTable("MaterialCosts"); + b.ToTable("MaterialCost", (string)null); }); modelBuilder.Entity("OTManager.Core.Entities.OT.Order", b => @@ -235,7 +342,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("CreatedBy") .IsRequired() - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.Property("OrderNumber") .IsRequired() @@ -251,13 +359,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("TotalCost") .ValueGeneratedOnAdd() .HasColumnType("decimal(18,2)") - .HasDefaultValue(0m); + .HasDefaultValue(0.00m); b.Property("UpdatedAt") .HasColumnType("datetime2"); b.Property("UpdatedBy") - .HasColumnType("nvarchar(max)"); + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); b.HasKey("Id"); @@ -265,12 +374,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("CreatedAt"); - b.HasIndex("OrderNumber"); + b.HasIndex("OrderNumber") + .IsUnique(); - b.ToTable("Orders"); + b.ToTable("Order", (string)null); }); - modelBuilder.Entity("OTManager.Core.Entities.OT.OrderService", b => + modelBuilder.Entity("OTManager.Core.Entities.OT.ServiceCost", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -291,11 +401,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("nvarchar(450)"); + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + b.Property("Price") .ValueGeneratedOnAdd() .HasColumnType("decimal(18,2)") .HasDefaultValue(0m); + b.Property("Quantity") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("TotalPrice") + .HasColumnType("decimal(18,2)"); + b.Property("UpdatedAt") .HasColumnType("datetime2"); @@ -308,10 +429,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("Name"); - b.ToTable("Services"); + b.HasIndex("OrderId"); + + b.ToTable("ServiceCost"); }); - modelBuilder.Entity("OTManager.Core.Entities.OT.ServiceCost", b => + modelBuilder.Entity("OTManager.Core.Entities.OT.ServiceOrder", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -332,22 +455,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("nvarchar(450)"); - b.Property("OrderId") - .HasColumnType("uniqueidentifier"); - b.Property("Price") .ValueGeneratedOnAdd() .HasColumnType("decimal(18,2)") .HasDefaultValue(0m); - b.Property("Quantity") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(1); - - b.Property("TotalPrice") - .HasColumnType("decimal(18,2)"); - b.Property("UpdatedAt") .HasColumnType("datetime2"); @@ -360,9 +472,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("Name"); - b.HasIndex("OrderId"); - - b.ToTable("ServiceCosts"); + b.ToTable("Services"); }); modelBuilder.Entity("OTManager.Core.Entities.OT.Worker", b => @@ -380,7 +490,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("HourlyRate") .ValueGeneratedOnAdd() - .HasColumnType("decimal(2,4)") + .HasColumnType("decimal(2,2)") .HasDefaultValue(0m); b.Property("Name") @@ -417,12 +527,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("HourlyRate") .ValueGeneratedOnAdd() - .HasColumnType("decimal(2,4)") + .HasColumnType("decimal(2,2)") .HasDefaultValue(0m); b.Property("HoursWorked") .ValueGeneratedOnAdd() - .HasColumnType("decimal(2,4)") + .HasColumnType("decimal(2,2)") .HasDefaultValue(0m); b.Property("Name") @@ -434,7 +544,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("TotalCost") .ValueGeneratedOnAdd() - .HasColumnType("decimal(2,4)") + .HasColumnType("decimal(2,2)") .HasDefaultValue(0m); b.Property("UpdatedAt") @@ -451,7 +561,80 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("OrderId"); - b.ToTable("WorkerCosts"); + b.ToTable("WorkerCost"); + }); + + modelBuilder.Entity("OTManager.Data.Context.ApplicationRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("NormalizedName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Roles"); + }); + + modelBuilder.Entity("OTManager.Data.Context.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasColumnType("nvarchar(max)"); + + b.Property("NormalizedUserName") + .HasColumnType("nvarchar(max)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Users"); }); modelBuilder.Entity("OTManager.Core.Entities.OT.Facture", b => diff --git a/OTManager.Data/OTManager.Data.csproj b/OTManager.Data/OTManager.Data.csproj index 2dbbf68..8ef5a3a 100644 --- a/OTManager.Data/OTManager.Data.csproj +++ b/OTManager.Data/OTManager.Data.csproj @@ -7,10 +7,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -20,4 +20,8 @@ + + + + diff --git a/OTManager.Data/Repositories/Implements/ClientRepository.cs b/OTManager.Data/Repositories/Implements/ClientRepository.cs index 045ec6b..de479ee 100644 --- a/OTManager.Data/Repositories/Implements/ClientRepository.cs +++ b/OTManager.Data/Repositories/Implements/ClientRepository.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; + using OTManager.Core.Entities.OT; using OTManager.Core.QueryParams; using OTManager.Data.Context; diff --git a/OTManager.Data/Repositories/Implements/FactureRepository.cs b/OTManager.Data/Repositories/Implements/FactureRepository.cs index c0303a4..e26e066 100644 --- a/OTManager.Data/Repositories/Implements/FactureRepository.cs +++ b/OTManager.Data/Repositories/Implements/FactureRepository.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; + using OTManager.Core.Entities.OT; using OTManager.Core.QueryParams; using OTManager.Data.Context; diff --git a/OTManager.Data/Repositories/Implements/MaterialCostRepository.cs b/OTManager.Data/Repositories/Implements/MaterialCostRepository.cs deleted file mode 100644 index ca70fd4..0000000 --- a/OTManager.Data/Repositories/Implements/MaterialCostRepository.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using OTManager.Core.Entities.OT; -using OTManager.Core.QueryParams; -using OTManager.Data.Context; -using OTManager.Data.Repositories.Interface; - -namespace OTManager.Data.Repositories.Implements; - -public class MaterialCostRepository(ApplicationDbContext context) : IMaterialCostRepository -{ - private readonly ApplicationDbContext _context = context; - - public async Task AddAsync(MaterialCost entity) - { - await _context.MaterialCosts.AddAsync(entity); - await _context.SaveChangesAsync(); - } - - public async Task DeleteAsync(Guid id) - { - var entity = await GetByIdAsync(id); - if (entity is null) return; - _context.MaterialCosts.Remove(entity); - await _context.SaveChangesAsync(); - } - - public async Task> GetAllAsync() - => await _context.MaterialCosts.ToListAsync(); - - public async Task GetByIdAsync(Guid id) - => await _context.MaterialCosts.FirstOrDefaultAsync(f => f.Id == id); - - public async Task<(IEnumerable Items, int TotalCount)> GetFilteredAsync(MaterialCostQueryParams query) - { - var q = _context.MaterialCosts.AsQueryable(); - if (!string.IsNullOrWhiteSpace(query.Search)) - q = q.Where(m => m.Name.Contains(query.Search) || m.Code.Contains(query.Search)); - if (!string.IsNullOrWhiteSpace(query.Code)) - q = q.Where(m => m.Code == query.Code); - if (!string.IsNullOrWhiteSpace(query.Name)) - q = q.Where(m => m.Name == query.Name); - if (query.CreatedAt.HasValue) - q = q.Where(m => m.CreatedAt == query.CreatedAt); - var total = await q.CountAsync(); - if (query.Page > 0 && query.PageSize > 0) - q = q.Skip((query.Page - 1) * query.PageSize).Take(query.PageSize); - var items = await q.ToListAsync(); - return (items, total); - } - - public async Task UpdateAsync(MaterialCost entity) - { - _context.MaterialCosts.Update(entity); - await _context.SaveChangesAsync(); - } -} diff --git a/OTManager.Data/Repositories/Implements/MaterialRepository.cs b/OTManager.Data/Repositories/Implements/MaterialRepository.cs index 728059c..3d70a3d 100644 --- a/OTManager.Data/Repositories/Implements/MaterialRepository.cs +++ b/OTManager.Data/Repositories/Implements/MaterialRepository.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; + using OTManager.Core.Entities.OT; using OTManager.Core.QueryParams; using OTManager.Data.Context; diff --git a/OTManager.Data/Repositories/Implements/OrderRepository.cs b/OTManager.Data/Repositories/Implements/OrderRepository.cs index c9d21e4..01a4ee1 100644 --- a/OTManager.Data/Repositories/Implements/OrderRepository.cs +++ b/OTManager.Data/Repositories/Implements/OrderRepository.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; + using OTManager.Core.Entities.OT; using OTManager.Core.QueryParams; using OTManager.Data.Context; diff --git a/OTManager.Data/Repositories/Implements/OrderServiceRepository.cs b/OTManager.Data/Repositories/Implements/OrderServiceRepository.cs index 57ae18c..0b40f61 100644 --- a/OTManager.Data/Repositories/Implements/OrderServiceRepository.cs +++ b/OTManager.Data/Repositories/Implements/OrderServiceRepository.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; + using OTManager.Core.Entities.OT; using OTManager.Core.QueryParams; using OTManager.Data.Context; diff --git a/OTManager.Data/Repositories/Implements/ServiceCostRepository.cs b/OTManager.Data/Repositories/Implements/ServiceCostRepository.cs deleted file mode 100644 index f2d8042..0000000 --- a/OTManager.Data/Repositories/Implements/ServiceCostRepository.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using OTManager.Core.Entities.OT; -using OTManager.Core.QueryParams; -using OTManager.Data.Context; -using OTManager.Data.Repositories.Interface; - -namespace OTManager.Data.Repositories.Implements; - -public class ServiceCostRepository(ApplicationDbContext context) : IServiceCostRepository -{ - private readonly ApplicationDbContext _context = context; - - public async Task AddAsync(ServiceCost entity) - { - await _context.ServiceCosts.AddAsync(entity); - await _context.SaveChangesAsync(); - } - - public async Task DeleteAsync(Guid id) - { - var entity = await GetByIdAsync(id); - if (entity is null) return; - _context.ServiceCosts.Remove(entity); - await _context.SaveChangesAsync(); - } - - public async Task> GetAllAsync() - => await _context.ServiceCosts.ToListAsync(); - - public async Task GetByIdAsync(Guid id) - => await _context.ServiceCosts.FirstOrDefaultAsync(f => f.Id == id); - - public async Task<(IEnumerable Items, int TotalCount)> GetFilteredAsync(ServiceCostQueryParams query) - { - var q = _context.ServiceCosts.AsQueryable(); - if (!string.IsNullOrWhiteSpace(query.Search)) - q = q.Where(s => s.Name.Contains(query.Search)); - if (!string.IsNullOrWhiteSpace(query.Name)) - q = q.Where(s => s.Name == query.Name); - if (query.CreatedAt.HasValue) - q = q.Where(s => s.CreatedAt == query.CreatedAt); - var total = await q.CountAsync(); - if (query.Page > 0 && query.PageSize > 0) - q = q.Skip((query.Page - 1) * query.PageSize).Take(query.PageSize); - var items = await q.ToListAsync(); - return (items, total); - } - - public async Task UpdateAsync(ServiceCost entity) - { - _context.ServiceCosts.Update(entity); - await _context.SaveChangesAsync(); - } -} diff --git a/OTManager.Data/Repositories/Implements/WorkerCostRepository.cs b/OTManager.Data/Repositories/Implements/WorkerCostRepository.cs deleted file mode 100644 index da115a9..0000000 --- a/OTManager.Data/Repositories/Implements/WorkerCostRepository.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using OTManager.Core.Entities.OT; -using OTManager.Core.QueryParams; -using OTManager.Data.Context; -using OTManager.Data.Repositories.Interface; - -namespace OTManager.Data.Repositories.Implements; - -public class WorkerCostRepository(ApplicationDbContext context) : IWorkerCostRepository -{ - private readonly ApplicationDbContext _context = context; - - public async Task AddAsync(WorkerCost entity) - { - await _context.WorkerCosts.AddAsync(entity); - await _context.SaveChangesAsync(); - } - - public async Task DeleteAsync(Guid id) - { - var entity = await GetByIdAsync(id); - if (entity is null) return; - _context.WorkerCosts.Remove(entity); - await _context.SaveChangesAsync(); - } - - public async Task> GetAllAsync() - => await _context.WorkerCosts.ToListAsync(); - - public async Task GetByIdAsync(Guid id) - => await _context.WorkerCosts.FirstOrDefaultAsync(f => f.Id == id); - - public async Task<(IEnumerable Items, int TotalCount)> GetFilteredAsync(WorkerCostQueryParams query) - { - var q = _context.WorkerCosts.AsQueryable(); - if (!string.IsNullOrWhiteSpace(query.Search)) - q = q.Where(w => w.Name.Contains(query.Search)); - if (!string.IsNullOrWhiteSpace(query.Name)) - q = q.Where(w => w.Name == query.Name); - if (query.CreatedAt.HasValue) - q = q.Where(w => w.CreatedAt == query.CreatedAt); - var total = await q.CountAsync(); - if (query.Page > 0 && query.PageSize > 0) - q = q.Skip((query.Page - 1) * query.PageSize).Take(query.PageSize); - var items = await q.ToListAsync(); - return (items, total); - } - - public async Task UpdateAsync(WorkerCost entity) - { - _context.WorkerCosts.Update(entity); - await _context.SaveChangesAsync(); - } -} diff --git a/OTManager.Data/Repositories/Implements/WorkerRepository.cs b/OTManager.Data/Repositories/Implements/WorkerRepository.cs index dd33a5c..7ce9ffb 100644 --- a/OTManager.Data/Repositories/Implements/WorkerRepository.cs +++ b/OTManager.Data/Repositories/Implements/WorkerRepository.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; + using OTManager.Core.Entities.OT; using OTManager.Core.QueryParams; using OTManager.Data.Context; diff --git a/OTManager.Data/Repositories/Interface/IMaterialCostRepository.cs b/OTManager.Data/Repositories/Interface/IMaterialCostRepository.cs deleted file mode 100644 index 18ab5a2..0000000 --- a/OTManager.Data/Repositories/Interface/IMaterialCostRepository.cs +++ /dev/null @@ -1,10 +0,0 @@ -using OTManager.Core.Entities.OT; -using OTManager.Core.QueryParams; -using OTManager.Data.Repositories.Abstracts; - -namespace OTManager.Data.Repositories.Interface; - -public interface IMaterialCostRepository : IRepository -{ - Task<(IEnumerable Items, int TotalCount)> GetFilteredAsync(MaterialCostQueryParams query); -} diff --git a/OTManager.Data/Repositories/Interface/IServiceCostRepository.cs b/OTManager.Data/Repositories/Interface/IServiceCostRepository.cs deleted file mode 100644 index 39e1acc..0000000 --- a/OTManager.Data/Repositories/Interface/IServiceCostRepository.cs +++ /dev/null @@ -1,10 +0,0 @@ -using OTManager.Core.Entities.OT; -using OTManager.Core.QueryParams; -using OTManager.Data.Repositories.Abstracts; - -namespace OTManager.Data.Repositories.Interface; - -public interface IServiceCostRepository : IRepository -{ - Task<(IEnumerable Items, int TotalCount)> GetFilteredAsync(ServiceCostQueryParams query); -} diff --git a/OTManager.Data/Repositories/Interface/IWorkerCostRepository.cs b/OTManager.Data/Repositories/Interface/IWorkerCostRepository.cs deleted file mode 100644 index bb930b2..0000000 --- a/OTManager.Data/Repositories/Interface/IWorkerCostRepository.cs +++ /dev/null @@ -1,10 +0,0 @@ -using OTManager.Core.Entities.OT; -using OTManager.Core.QueryParams; -using OTManager.Data.Repositories.Abstracts; - -namespace OTManager.Data.Repositories.Interface; - -public interface IWorkerCostRepository : IRepository -{ - Task<(IEnumerable Items, int TotalCount)> GetFilteredAsync(WorkerCostQueryParams query); -} diff --git a/OTManager.Data/Repositories/RepositoryExtension.cs b/OTManager.Data/Repositories/RepositoryExtension.cs index b3813c3..3063f46 100644 --- a/OTManager.Data/Repositories/RepositoryExtension.cs +++ b/OTManager.Data/Repositories/RepositoryExtension.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; + using OTManager.Data.Repositories.Implements; using OTManager.Data.Repositories.Interface; @@ -13,10 +14,7 @@ public static IServiceCollection AddRepositoryExtension(this IServiceCollection services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); return services; } } diff --git a/OTManager.Data/UoW/IUnitOfWork.cs b/OTManager.Data/UoW/IUnitOfWork.cs index 493f825..c070160 100644 --- a/OTManager.Data/UoW/IUnitOfWork.cs +++ b/OTManager.Data/UoW/IUnitOfWork.cs @@ -13,9 +13,6 @@ public interface IUnitOfWork : IDisposable, IAsyncDisposable IClientRepository Clients { get; } IOrderRepository Orders { get; } IFactureRepository Factures { get; } - IServiceCostRepository ServiceCosts { get; } - IMaterialCostRepository MaterialCosts { get; } - IWorkerCostRepository WorkerCosts { get; } IMaterialRepository Materials { get; } IWorkerRepository Workers { get; } IOrderServiceRepository OrderServices { get; } diff --git a/OTManager.Data/UoW/UnitOfWork.cs b/OTManager.Data/UoW/UnitOfWork.cs index deef99f..8ba30b1 100644 --- a/OTManager.Data/UoW/UnitOfWork.cs +++ b/OTManager.Data/UoW/UnitOfWork.cs @@ -1,10 +1,9 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; + using OTManager.Data.Context; using OTManager.Data.Repositories.Implements; using OTManager.Data.Repositories.Interface; -using System; -using System.Threading.Tasks; namespace OTManager.Data.UoW { @@ -33,12 +32,6 @@ public UnitOfWork(ApplicationDbContext context) /// public IFactureRepository Factures => new FactureRepository(_context); /// - public IServiceCostRepository ServiceCosts => new ServiceCostRepository(_context); - /// - public IMaterialCostRepository MaterialCosts => new MaterialCostRepository(_context); - /// - public IWorkerCostRepository WorkerCosts => new WorkerCostRepository(_context); - /// public IMaterialRepository Materials => new MaterialRepository(_context); /// public IWorkerRepository Workers => new WorkerRepository(_context); diff --git a/OTManager.Wapi.sln b/OTManager.Wapi.sln index e83de12..35c2f51 100644 --- a/OTManager.Wapi.sln +++ b/OTManager.Wapi.sln @@ -19,6 +19,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{C5CFB38B-0E9 README.md = README.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OTManager.Web", "src\OTManager.Web\OTManager.Web.csproj", "{9F3A116A-1006-4770-81E4-D57CD3583D4C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OTManager.WebComp", "src\OTManager.WebComp\OTManager.WebComp.csproj", "{73746EFB-FC7D-4B7C-949B-CC2FDEB2D7F6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dist", "dist", "{0E9CFBD1-9DFF-4F8E-8A2B-C184A3E7A6D7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OTManager.AppHost", "dist\OTManager.AppHost\OTManager.AppHost.csproj", "{8D8DC712-1E28-47F9-809F-C7A58300ED83}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OTManager.ServiceDefaults", "dist\OTManager.ServiceDefaults\OTManager.ServiceDefaults.csproj", "{E799EFE6-12B5-7F10-CF22-5A9260D5D08C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,6 +51,22 @@ Global {C450D449-89E9-43D4-A52D-A0F1DF8FE517}.Debug|Any CPU.Build.0 = Debug|Any CPU {C450D449-89E9-43D4-A52D-A0F1DF8FE517}.Release|Any CPU.ActiveCfg = Release|Any CPU {C450D449-89E9-43D4-A52D-A0F1DF8FE517}.Release|Any CPU.Build.0 = Release|Any CPU + {9F3A116A-1006-4770-81E4-D57CD3583D4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F3A116A-1006-4770-81E4-D57CD3583D4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F3A116A-1006-4770-81E4-D57CD3583D4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F3A116A-1006-4770-81E4-D57CD3583D4C}.Release|Any CPU.Build.0 = Release|Any CPU + {73746EFB-FC7D-4B7C-949B-CC2FDEB2D7F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {73746EFB-FC7D-4B7C-949B-CC2FDEB2D7F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {73746EFB-FC7D-4B7C-949B-CC2FDEB2D7F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {73746EFB-FC7D-4B7C-949B-CC2FDEB2D7F6}.Release|Any CPU.Build.0 = Release|Any CPU + {8D8DC712-1E28-47F9-809F-C7A58300ED83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D8DC712-1E28-47F9-809F-C7A58300ED83}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D8DC712-1E28-47F9-809F-C7A58300ED83}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D8DC712-1E28-47F9-809F-C7A58300ED83}.Release|Any CPU.Build.0 = Release|Any CPU + {E799EFE6-12B5-7F10-CF22-5A9260D5D08C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E799EFE6-12B5-7F10-CF22-5A9260D5D08C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E799EFE6-12B5-7F10-CF22-5A9260D5D08C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E799EFE6-12B5-7F10-CF22-5A9260D5D08C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -50,6 +76,10 @@ Global {F113E42C-0222-4D95-9D54-4943F73716DA} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {4A32B4C2-DEF6-40BA-AE40-1DEF2D26D002} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {C450D449-89E9-43D4-A52D-A0F1DF8FE517} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {9F3A116A-1006-4770-81E4-D57CD3583D4C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {73746EFB-FC7D-4B7C-949B-CC2FDEB2D7F6} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {8D8DC712-1E28-47F9-809F-C7A58300ED83} = {0E9CFBD1-9DFF-4F8E-8A2B-C184A3E7A6D7} + {E799EFE6-12B5-7F10-CF22-5A9260D5D08C} = {0E9CFBD1-9DFF-4F8E-8A2B-C184A3E7A6D7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8434D880-E4C5-4974-9EE1-67CD8E257B50} diff --git a/README.md b/README.md index 56ac08a..51e6162 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,22 @@ -# OTManager.Wapi +# OTManager OTManager.Wapi es una solucin para la gestin de rdenes de trabajo, clientes, materiales, servicios y costos en entornos empresariales. El proyecto est desarrollado en .NET 9 y sigue una arquitectura limpia y modular. +Para evitar duplicar informacin, la documentacin detallada sobre contribucin, seguridad y conducta se mantiene en archivos dedicados en la raz del repositorio: + +- CONTRIBUTING.md Pautas para contribuir, flujo de trabajo y checklist de PR. +- SECURITY.md Pautas para reportar vulnerabilidades y prcticas mnimas de seguridad. +- Code_Of_Conduct.md Cdigo de conducta del proyecto. + +Tambin se incluyen plantillas de Issue y Pull Request en .github/ para estandarizar contribuciones. + ## Caractersticas principales -- **Gestin de rdenes de Trabajo**: Creacin, actualizacin y seguimiento de rdenes de trabajo. -- **Gestin de Clientes**: Administracin de informacin de clientes y su historial de rdenes. -- **Control de Materiales y Servicios**: Registro y control de materiales y servicios asociados a cada orden. -- **Costos y Facturacin**: Clculo y gestin de costos de materiales, servicios y mano de obra, as como generacin de facturas. -- **Auditora**: Seguimiento de cambios y acciones realizadas sobre las entidades principales. -- **API RESTful**: Exposicin de endpoints seguros y documentados para integracin con otros sistemas. -- **Documentacin interactiva (Swagger)**: Acceso restringido a la documentacin interactiva en entornos de desarrollo. -- **Unit of Work y Repositorios**: Patrn Unit of Work y repositorios para una gestin eficiente y desacoplada de la persistencia. -- **Arquitectura modular**: Separacin clara entre capa de datos, lgica de negocio, API y aplicacin. - -## Patrones de diseo implementados - -- **Unit of Work**: Para la gestin de transacciones y persistencia de datos de forma atmica. -- **Repository**: Para el acceso desacoplado a la capa de datos. -- **DTO (Data Transfer Object)**: Para la transferencia de datos entre capas y la API. -- **Dependency Injection**: Para la inyeccin de dependencias y facilitar el testing y la extensibilidad. -- **Controller/Endpoint**: Para la exposicin de la lgica de negocio a travs de una API RESTful. -- **Mapper**: Para la conversin entre entidades de dominio y DTOs. -- **Service Layer**: Para la encapsulacin de la lgica de negocio y orquestacin de operaciones. -- **Auditable Entity**: Para el seguimiento de cambios y auditora en las entidades principales. +- Gestin de rdenes de Trabajo, Clientes, Materiales y Servicios. +- Costos y Facturacin. +- Auditora y trazabilidad de cambios. +- API RESTful con documentacin (Swagger) en entornos de desarrollo. +- Arquitectura modular con Unit of Work, Repositories y Service Layer. ## Estructura del proyecto @@ -31,10 +24,29 @@ OTManager.Wapi es una soluci - `OTManager.Core`: Entidades de dominio y contratos. - `OTManager.App`: Lgica de negocio y servicios de aplicacin. - `OTManager.Api`: API RESTful y documentacin Swagger. - -## Requisitos -- .NET 9 SDK -- SQL Server u otro proveedor compatible con Entity Framework Core +- `OTManager.Web`: Interfaz de usuario basada en Blazor. +- `OTManager.WebComp`: Componentes compartidos para la interfaz de usuario. + +## Cmo empezar (resumen) + +1. Clona el repositorio: + ```bash + git clone https://github.com/tu-usuario/OTManager.Wapi.git + cd OTManager.Wapi + ``` +2. Restaura dependencias y ejecuta: + ```bash + dotnet restore + dotnet run --project src/OTManager.Api/OTManager.Api.csproj + dotnet run --project src/OTManager.Web/OTManager.Web.csproj + ``` +3. Configura la base de datos en `appsettings.json` y aplica migraciones si corresponde: + ```bash + dotnet ef database update --project src/OTManager.Data/OTManager.Data.csproj --startup-project src/OTManager.Api/OTManager.Api.csproj + ``` + +Para informacin detallada sobre contribuciones, seguridad, plantillas y cdigo de conducta revisa los archivos mencionados al inicio. ## Licencia -Este proyecto est bajo la Licencia MIT. Consulta el archivo LICENSE.txt para ms detalles. \ No newline at end of file + +Este proyecto est bajo la Licencia MIT. Consulta el archivo [LICENSE.txt](./LICENSE.txt) para ms detalles. \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..4a86b29 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,48 @@ +Security +======== + +Este documento describe el proceso y pautas esenciales para reportar vulnerabilidades y las prcticas mnimas de seguridad que el proyecto debe seguir. + +1. Reporte de vulnerabilidades +------------------------------ +- No publiques vulnerabilidades en Issues pblicas. +- Para reportes confidenciales, utiliza la seccin "Security Advisories" de GitHub o contacta a los mantenedores por correo electrnico: adrianmesasacasas@outlok.com. +- Incluye en el reporte: resumen, pasos para reproducir, impacto potencial, versin afectada, y mitigaciones temporales si existen. + +2. Respuesta a incidentes +------------------------- +- Los mantenedores confirmarn recepcin en un plazo mximo de 72 horas. +- Se evaluar la severidad y se priorizar la correccin. +- Se coordinar la divulgacin responsable y se publicar un advisory pblico una vez exista parche disponible. + +3. Prcticas recomendadas de desarrollo +--------------------------------------- +- No incluir secretos en el repositorio (claves, contraseas, tokens). Usa archivos de configuracin locales y variables de entorno. +- Aadir un archivo .gitignore apropiado para evitar subir archivos sensibles. +- Usar HTTPS y TLS para comunicaciones en produccin. +- Validar y sanitizar todas las entradas de usuarios (servidor y cliente). +- Limitar exposicin de informacin sensible en errores y logs. +- Implementar autenticacin y autorizacin robusta (p. ej. JWT con expiracin, refresh tokens controlados, y verificacin de scopes/roles). +- Proteger endpoints administrativos y documentacin (Swagger) en entornos no-privados. +- Usar cifrado para datos sensibles en reposo y en trnsito cuando sea necesario. + +4. Gestin de dependencias +-------------------------- +- Mantener dependencias actualizadas y aplicar parches de seguridad. +- Ejecutar anlisis de vulnerabilidades en dependencias (p. ej. `dotnet list package --vulnerable` o herramientas como Dependabot). + +5. Pruebas y validaciones +------------------------- +- Incluir pruebas de seguridad automatizadas cuando sea posible (anlisis esttico, escaneo de dependencias, pruebas de penetracin en etapas de CI). +- Revisar entradas y lmites para evitar inyeccin SQL, XSS, CSRF, etc. + +6. Poltica de divulgacin +-------------------------- +- Se seguir una divulgacin responsable: notificar a los mantenedores, permitir un periodo razonable para parche, luego divulgar pblicamente los detalles. + +7. Contacto +----------- +- Reportes de seguridad: adrianmesasacasas@outlok.com +- Si no se proporciona un correo, usa GitHub "Security Advisories". + +Gracias por ayudar a mantener la seguridad del proyecto. \ No newline at end of file diff --git a/dist/OTManager.AppHost/AppHost.cs b/dist/OTManager.AppHost/AppHost.cs new file mode 100644 index 0000000..d656c21 --- /dev/null +++ b/dist/OTManager.AppHost/AppHost.cs @@ -0,0 +1,9 @@ +var builder = DistributedApplication.CreateBuilder(args); + +var api = builder.AddProject("ot-manager-api"); + +builder.AddProject("ot-manager-web") + .WithHttpEndpoint() + .WaitFor(api); + +builder.Build().Run(); diff --git a/dist/OTManager.AppHost/OTManager.AppHost.csproj b/dist/OTManager.AppHost/OTManager.AppHost.csproj new file mode 100644 index 0000000..8e8b655 --- /dev/null +++ b/dist/OTManager.AppHost/OTManager.AppHost.csproj @@ -0,0 +1,22 @@ + + + + + + Exe + net9.0 + enable + enable + 98426b1f-635d-4719-85eb-84f0805785c2 + + + + + + + + + + + + diff --git a/dist/OTManager.AppHost/Properties/launchSettings.json b/dist/OTManager.AppHost/Properties/launchSettings.json new file mode 100644 index 0000000..beaed5f --- /dev/null +++ b/dist/OTManager.AppHost/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17254;http://localhost:15040", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21031", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22124" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15040", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19040", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20248" + } + } + } +} diff --git a/dist/OTManager.AppHost/appsettings.Development.json b/dist/OTManager.AppHost/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/dist/OTManager.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/dist/OTManager.AppHost/appsettings.json b/dist/OTManager.AppHost/appsettings.json new file mode 100644 index 0000000..31c092a --- /dev/null +++ b/dist/OTManager.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/dist/OTManager.ServiceDefaults/Extensions.cs b/dist/OTManager.ServiceDefaults/Extensions.cs new file mode 100644 index 0000000..112c128 --- /dev/null +++ b/dist/OTManager.ServiceDefaults/Extensions.cs @@ -0,0 +1,127 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ServiceDiscovery; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + private const string HealthEndpointPath = "/health"; + private const string AlivenessEndpointPath = "/alive"; + + public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + // Uncomment the following to restrict the allowed schemes for service discovery. + // builder.Services.Configure(options => + // { + // options.AllowedSchemes = ["https"]; + // }); + + return builder; + } + + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddSource(builder.Environment.ApplicationName) + .AddAspNetCoreInstrumentation(tracing => + // Exclude health check requests from tracing + tracing.Filter = context => + !context.Request.Path.StartsWithSegments(HealthEndpointPath) + && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath) + ) + // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) + //.AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} + + return builder; + } + + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks(HealthEndpointPath); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} diff --git a/dist/OTManager.ServiceDefaults/OTManager.ServiceDefaults.csproj b/dist/OTManager.ServiceDefaults/OTManager.ServiceDefaults.csproj new file mode 100644 index 0000000..233173d --- /dev/null +++ b/dist/OTManager.ServiceDefaults/OTManager.ServiceDefaults.csproj @@ -0,0 +1,22 @@ + + + + net9.0 + enable + enable + true + + + + + + + + + + + + + + + diff --git a/src/OTManager.Api/Account/Controllers/SessionController.cs b/src/OTManager.Api/Account/Controllers/SessionController.cs new file mode 100644 index 0000000..2faa7ea --- /dev/null +++ b/src/OTManager.Api/Account/Controllers/SessionController.cs @@ -0,0 +1,93 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; + +using OTManager.Api.Account.Dto; +using OTManager.Api.Account.Records; +using OTManager.Api.Account.Services.Interface; +using OTManager.Data.Account; + +namespace OTManager.Api.Account.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class SessionController(UserManager userManager, IConfiguration config, SignInManager signInManager, ISessionContext session, IJwtTokenService jwt) : ControllerBase +{ + private readonly UserManager _userManager = userManager; + private readonly SignInManager _signInManager = signInManager; + private readonly ISessionContext _session = session; + private readonly IConfiguration _config = config; + private readonly IJwtTokenService _jwt = jwt; + + public record RegisterDto(string Email, string Password); + public record LoginDto(string Email, string Password); + + [AllowAnonymous] + [HttpPost("register")] + public async Task Register([FromBody] RegisterDto model) + { + if (await _userManager.FindByEmailAsync(model.Email) != null) + { + return BadRequest("El correo ya está registrado."); + } + + var user = new ApplicationUser + { + UserName = model.Email, + Email = model.Email + }; + + var result = await _userManager.CreateAsync(user, model.Password); + + if (!result.Succeeded) + { + return BadRequest(result.Errors); + } + + // Asignar rol por defecto + await _userManager.AddToRoleAsync(user, "User"); + + return Ok("Usuario registrado correctamente"); + } + + [AllowAnonymous] + [HttpPost("login")] + public async Task>> Login([FromBody] LoginRequestDto req) + { + var correlationId = _session.GetCorrelationId(); + + var user = await _userManager.FindByNameAsync(req.Username); + if (user is null || !user.EmailConfirmed) + { + return Unauthorized(SessionResponse.Failure("Credenciales inválidas o usuario inactivo", null, correlationId)); + } + + var ok = await _userManager.CheckPasswordAsync(user, req.Password); + if (!ok) + { + // Bloqueo temporal después de varios intentos fallidos + await _userManager.AccessFailedAsync(user); + if (await _userManager.IsLockedOutAsync(user)) + { + return Unauthorized(SessionResponse.Failure("Cuenta bloqueada temporalmente por múltiples intentos fallidos", null, correlationId)); + } + + return Unauthorized(SessionResponse.Failure("Credenciales inválidas", null, correlationId)); + } + + // Reiniciar contador de accesos fallidos + await _userManager.ResetAccessFailedCountAsync(user); + + var (token, sessionInfo) = await _jwt.CreateTokenAsync(user); + return Ok(SessionResponse.Success(token, sessionInfo, correlationId)); + } + + [Authorize] + [HttpPost("logout")] + public async Task Logout() + { + // Invalidar el token si se utiliza almacenamiento de tokens + await _signInManager.SignOutAsync(); + return Ok(new { Message = "Sesión cerrada correctamente" }); + } +} diff --git a/src/OTManager.Api/Account/Controllers/UserController.cs b/src/OTManager.Api/Account/Controllers/UserController.cs new file mode 100644 index 0000000..25e7e56 --- /dev/null +++ b/src/OTManager.Api/Account/Controllers/UserController.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; + +using OTManager.Data.Account; + +namespace OTManager.Api.Account.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class UserController(UserManager userManager, IConfiguration config, SignInManager signInManager) : ControllerBase +{ + private readonly UserManager _userManager = userManager; + private readonly SignInManager _signInManager = signInManager; + private readonly IConfiguration _config = config; +} diff --git a/src/OTManager.Api/Account/Dto/LoginRequestDto.cs b/src/OTManager.Api/Account/Dto/LoginRequestDto.cs new file mode 100644 index 0000000..70ac4bc --- /dev/null +++ b/src/OTManager.Api/Account/Dto/LoginRequestDto.cs @@ -0,0 +1,3 @@ +namespace OTManager.Api.Account.Dto; + +public record LoginRequestDto(string Username, string Password); diff --git a/src/OTManager.Api/Account/Extensions/IdentityExtension.cs b/src/OTManager.Api/Account/Extensions/IdentityExtension.cs new file mode 100644 index 0000000..00d28e2 --- /dev/null +++ b/src/OTManager.Api/Account/Extensions/IdentityExtension.cs @@ -0,0 +1,72 @@ +using System.Security.Claims; +using System.Text; + +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Identity; +using Microsoft.IdentityModel.Tokens; + +using OTManager.Api.Account.Services.Implement; +using OTManager.Api.Account.Services.Interface; +using OTManager.Data.Account; +using OTManager.Data.Context; + +namespace OTManager.Api.Account.Extensions; + +public static class IdentityExtension +{ + public static IServiceCollection AddAppIdentityAuthentication(this IServiceCollection services, IConfiguration configuration) + { + // Info: Add Identity + services.AddIdentityCore(opts => + { + // ToDo: Cambiar para ambientes de producción + opts.User.RequireUniqueEmail = true; + opts.Password.RequiredLength = 8; + opts.Password.RequireNonAlphanumeric = false; + opts.Password.RequireUppercase = false; + }) + .AddRoles>() + .AddEntityFrameworkStores() + .AddSignInManager>() + .AddDefaultTokenProviders(); + + // Info: Configurar Jwt + var jwtSettings = configuration.GetSection("Jwt"); + var key = Encoding.UTF8.GetBytes(jwtSettings["Key"]!); + + services.AddAuthentication(opt => + { + opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(opts => + { + opts.RequireHttpsMetadata = true; + opts.SaveToken = true; + opts.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = jwtSettings["Issuer"], + ValidateAudience = true, + ValidAudience = jwtSettings["Audience"], + ValidateLifetime = true, + IssuerSigningKey = new SymmetricSecurityKey(key), + ValidateIssuerSigningKey = true, + ClockSkew = TimeSpan.FromMinutes(1), + NameClaimType = ClaimTypes.Name, + RoleClaimType = ClaimTypes.Role + }; + }); + + // Info: Permisos dinámicos + // ! + //services.AddSingleton(); + //services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + services.AddHttpContextAccessor(); + + return services; + } +} diff --git a/src/OTManager.Api/Account/Records/SessionInfo.cs b/src/OTManager.Api/Account/Records/SessionInfo.cs new file mode 100644 index 0000000..737ac1e --- /dev/null +++ b/src/OTManager.Api/Account/Records/SessionInfo.cs @@ -0,0 +1,5 @@ +namespace OTManager.Api.Account.Records; + +public record SessionInfo +(Guid UserId, string Username, IReadOnlyList Roles, IReadOnlyList Permissions); + diff --git a/src/OTManager.Api/Account/Records/SessionResponse.cs b/src/OTManager.Api/Account/Records/SessionResponse.cs new file mode 100644 index 0000000..0ecfd90 --- /dev/null +++ b/src/OTManager.Api/Account/Records/SessionResponse.cs @@ -0,0 +1,17 @@ +namespace OTManager.Api.Account.Records +{ + public class SessionResponse + { + public bool IsSuccess { get; init; } + public string? Message { get; init; } + public T? Data { get; private init; } + public SessionInfo? Session { get; init; } + public string? CorrelationId { get; init; } + + public static SessionResponse Success(T data, SessionInfo session, string? cid = null) + => new() { IsSuccess = true, Data = data, Session = session, CorrelationId = cid }; + + public static SessionResponse Failure(string message, SessionInfo? session, string? cid = null) + => new() { IsSuccess = false, Message = message, Session = session, CorrelationId = cid }; + } +} diff --git a/src/OTManager.Api/Account/Records/TokenResponse.cs b/src/OTManager.Api/Account/Records/TokenResponse.cs new file mode 100644 index 0000000..1279b22 --- /dev/null +++ b/src/OTManager.Api/Account/Records/TokenResponse.cs @@ -0,0 +1,4 @@ +namespace OTManager.Api.Account.Records; + +public record TokenResponse +(string AccessToken, DateTime ExpireAtUtc); diff --git a/src/OTManager.Api/Account/Services/Implement/JwtTokenService.cs b/src/OTManager.Api/Account/Services/Implement/JwtTokenService.cs new file mode 100644 index 0000000..f9c9b40 --- /dev/null +++ b/src/OTManager.Api/Account/Services/Implement/JwtTokenService.cs @@ -0,0 +1,76 @@ + +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; + +using Microsoft.AspNetCore.Identity; +using Microsoft.IdentityModel.Tokens; + +using OTManager.Api.Account.Records; +using OTManager.Api.Account.Services.Interface; +using OTManager.Data.Account; + +namespace OTManager.Api.Account.Services.Implement; + +public class JwtTokenService( + IConfiguration configuration, + UserManager users, + RoleManager> roles) : IJwtTokenService +{ + public async Task<(TokenResponse token, SessionInfo session)> CreateTokenAsync(ApplicationUser user) + { + var jwtSettings = configuration.GetSection("Jwt"); + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["Key"]!)); + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + var now = DateTime.UtcNow; + + var userClaims = await users.GetClaimsAsync(user); + var roleNames = await users.GetRolesAsync(user); + var roleClaims = new List(); + + foreach (var roleName in roleNames) + { + var role = await roles.FindByNameAsync(roleName); + if (role is not null) + roleClaims.AddRange(await roles.GetClaimsAsync(role)); + } + + // Normaliza permisos: tipo "perm" + var perms = userClaims.Where(c => c.Type == "perm").Select(c => c.Value) + .Concat(roleClaims.Where(c => c.Type == "perm").Select(c => c.Value)) + .Distinct() + .ToList(); + + var claims = new List + { + new("sub", user.Id.ToString()), + new("name", user.UserName ?? ""), + new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), + new(JwtRegisteredClaimNames.Iat, EpochTime.GetIntDate(now).ToString(), ClaimValueTypes.Integer64) + }; + + // Roles + claims.AddRange(roleNames.Select(r => new Claim(ClaimTypes.Role, r))); + // Permisos + claims.AddRange(perms.Select(p => new Claim("perm", p))); + + var token = new JwtSecurityToken( + issuer: jwtSettings["Issuer"], + audience: jwtSettings["Audience"], + claims: claims, + notBefore: now, + expires: now.AddMinutes(60), + signingCredentials: creds); + + var jwt = new JwtSecurityTokenHandler().WriteToken(token); + + var session = new SessionInfo( + user.Id, + user.UserName ?? string.Empty, + [.. roleNames], + perms); + + return (new TokenResponse(jwt, token.ValidTo), session); + } +} diff --git a/src/OTManager.Api/Account/Services/Implement/SessionContext.cs b/src/OTManager.Api/Account/Services/Implement/SessionContext.cs new file mode 100644 index 0000000..3512b16 --- /dev/null +++ b/src/OTManager.Api/Account/Services/Implement/SessionContext.cs @@ -0,0 +1,44 @@ +using System.Security.Claims; + +using OTManager.Api.Account.Records; +using OTManager.Api.Account.Services.Interface; + +namespace OTManager.Api.Account.Services.Implement; + +public class SessionContext(IHttpContextAccessor http) : ISessionContext +{ + private const string PermissionClaimType = "perm"; + public SessionInfo? GetCurrent() + { + + var u = http.HttpContext?.User; + if (u == null || !(u.Identity?.IsAuthenticated ?? false)) return null; + + var sub = u.FindFirstValue(ClaimTypes.NameIdentifier) + ?? u.FindFirstValue("sub"); + if (!Guid.TryParse(sub, out var userId)) return null; + + + var userName = u.Identity?.Name + ?? u.FindFirstValue(ClaimTypes.Name) + ?? string.Empty; + + var roles = u.FindAll(ClaimTypes.Role) + .Select(x => x.Value).Distinct().ToArray(); + + var perms = u.FindAll(PermissionClaimType) + .Select(x => x.Value).Distinct().ToArray(); + + return new SessionInfo + ( + userId, + userName, + roles, + perms + ); + + } + + public string? GetCorrelationId() => http.HttpContext?.Items["CorrelationId"] as string; + +} diff --git a/src/OTManager.Api/Account/Services/Interface/IJwtTokenService.cs b/src/OTManager.Api/Account/Services/Interface/IJwtTokenService.cs new file mode 100644 index 0000000..2334d18 --- /dev/null +++ b/src/OTManager.Api/Account/Services/Interface/IJwtTokenService.cs @@ -0,0 +1,9 @@ +using OTManager.Api.Account.Records; +using OTManager.Data.Account; + +namespace OTManager.Api.Account.Services.Interface; + +public interface IJwtTokenService +{ + Task<(TokenResponse token, SessionInfo session)> CreateTokenAsync(ApplicationUser user); +} diff --git a/src/OTManager.Api/Account/Services/Interface/ISessionContext.cs b/src/OTManager.Api/Account/Services/Interface/ISessionContext.cs new file mode 100644 index 0000000..53cae2e --- /dev/null +++ b/src/OTManager.Api/Account/Services/Interface/ISessionContext.cs @@ -0,0 +1,9 @@ +using OTManager.Api.Account.Records; + +namespace OTManager.Api.Account.Services.Interface; + +public interface ISessionContext +{ + SessionInfo? GetCurrent(); + string? GetCorrelationId(); +} diff --git a/src/OTManager.Api/Controllers/MaterialController.cs b/src/OTManager.Api/Controllers/MaterialController.cs index 721a71a..2ad7e84 100644 --- a/src/OTManager.Api/Controllers/MaterialController.cs +++ b/src/OTManager.Api/Controllers/MaterialController.cs @@ -1,25 +1,24 @@ using Microsoft.AspNetCore.Mvc; -using OTManager.App.Services.Interfaces; + using OTManager.App.Dtos.Materials; +using OTManager.App.Services.Interfaces; +using OTManager.Core.QueryParams; namespace OTManager.Api.Controllers; [ApiController] [Route("api/[controller]")] -public class MaterialController : ControllerBase +//[Authorize] +public class MaterialController(IMaterialService materialService) : ControllerBase { - private readonly IMaterialService _materialService; + private readonly IMaterialService _materialService = materialService; - public MaterialController(IMaterialService materialService) + [HttpPost("filter")] + public async Task GetAll([FromBody] MaterialQueryParams query) { - _materialService = materialService; - } - - [HttpGet] - public async Task GetAll() - { - var result = await _materialService.GetAllAsync(); - return Ok(result); + //var (Items, TotalCount) = await _materialService.GetFilteredAsync(query); + var Items = await _materialService.GetFilteredAsync(query); + return Ok(Items.Items); } [HttpGet("{id}")] diff --git a/src/OTManager.Api/Controllers/ServiceOrderController.cs b/src/OTManager.Api/Controllers/ServiceOrderController.cs index ea6b717..0e6e880 100644 --- a/src/OTManager.Api/Controllers/ServiceOrderController.cs +++ b/src/OTManager.Api/Controllers/ServiceOrderController.cs @@ -1,11 +1,15 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using OTManager.App.Services.Interfaces; + using OTManager.App.Dtos.Orders; +using OTManager.App.Services.Interfaces; +using OTManager.Core.QueryParams; namespace OTManager.Api.Controllers; [ApiController] [Route("api/[controller]")] +[Authorize] public class ServiceOrderController : ControllerBase { private readonly IOrderServiceAppService _serviceOrderService; @@ -16,10 +20,10 @@ public ServiceOrderController(IOrderServiceAppService serviceOrderService) } [HttpGet] - public async Task GetAll() + public async Task GetAll([FromQuery] OrderServiceQueryParams query) { - var result = await _serviceOrderService.GetAllAsync(); - return Ok(result); + var (Items, TotalCount) = await _serviceOrderService.GetFilteredAsync(query); + return Ok(new { Items, TotalCount }); } [HttpGet("{id}")] diff --git a/src/OTManager.Api/Endpoints/OrderServiceEndpoints/CreateOrderServiceEndpoint.cs b/src/OTManager.Api/Endpoints/OrderServiceEndpoints/CreateOrderServiceEndpoint.cs deleted file mode 100644 index f0dfcca..0000000 --- a/src/OTManager.Api/Endpoints/OrderServiceEndpoints/CreateOrderServiceEndpoint.cs +++ /dev/null @@ -1,30 +0,0 @@ -using FastEndpoints; -using OTManager.App.Dtos.Orders; -using OTManager.App.Services.Interfaces; - -namespace OTManager.Api.Endpoints.OrderServiceEndpoints; - -public class CreateOrderServiceEndpoint : Endpoint -{ - private readonly IOrderServiceAppService _service; - public CreateOrderServiceEndpoint(IOrderServiceAppService service) => _service = service; - - public override void Configure() - { - Post("/orderservices"); - AllowAnonymous(); - Summary(s => - { - s.Summary = "Crea un nuevo servicio de orden."; - }); - } - - public override async Task HandleAsync(ReadOrderServiceDto req, CancellationToken ct) - { - if (string.IsNullOrWhiteSpace(req.Name)) AddError(r => r.Name, "El nombre es obligatorio."); - if (req.Price < 0) AddError(r => r.Price, "El precio debe ser mayor o igual a 0."); - if (ValidationFailed) return; - var created = await _service.CreateAsync(req); - await HttpContext.Response.SendAsync(created, 201, null, ct); - } -} diff --git a/src/OTManager.Api/Endpoints/OrderServiceEndpoints/DeleteOrderServiceEndpoint.cs b/src/OTManager.Api/Endpoints/OrderServiceEndpoints/DeleteOrderServiceEndpoint.cs deleted file mode 100644 index 42433d6..0000000 --- a/src/OTManager.Api/Endpoints/OrderServiceEndpoints/DeleteOrderServiceEndpoint.cs +++ /dev/null @@ -1,39 +0,0 @@ -using FastEndpoints; -using OTManager.App.Dtos.Orders; -using OTManager.App.Services.Interfaces; - -namespace OTManager.Api.Endpoints.OrderServiceEndpoints; - -public class DeleteOrderServiceEndpoint : EndpointWithoutRequest -{ - private readonly IOrderServiceAppService _service; - public DeleteOrderServiceEndpoint(IOrderServiceAppService service) => _service = service; - - public override void Configure() - { - Delete("/orderservices/{id:guid}"); - AllowAnonymous(); - Summary(s => - { - s.Summary = "Elimina un servicio de orden por su identificador."; - }); - } - - public override async Task HandleAsync(CancellationToken ct) - { - var id = Route("id"); - if (id == Guid.Empty) - { - AddError("id", "El id proporcionado no es vlido."); - await HttpContext.Response.SendErrorsAsync(ValidationFailures, 400, null, ct); - return; - } - var deleted = await _service.DeleteAsync(id); - if (!deleted) - { - await HttpContext.Response.SendAsync(null, 404, null, ct); - return; - } - await HttpContext.Response.SendAsync(new { deleted }, 200, null, ct); - } -} diff --git a/src/OTManager.Api/Endpoints/OrderServiceEndpoints/GetOrderServiceByIdEndpoint.cs b/src/OTManager.Api/Endpoints/OrderServiceEndpoints/GetOrderServiceByIdEndpoint.cs deleted file mode 100644 index 487fad3..0000000 --- a/src/OTManager.Api/Endpoints/OrderServiceEndpoints/GetOrderServiceByIdEndpoint.cs +++ /dev/null @@ -1,39 +0,0 @@ -using FastEndpoints; -using OTManager.App.Dtos.Orders; -using OTManager.App.Services.Interfaces; - -namespace OTManager.Api.Endpoints.OrderServiceEndpoints; - -public class GetOrderServiceByIdEndpoint : EndpointWithoutRequest -{ - private readonly IOrderServiceAppService _service; - public GetOrderServiceByIdEndpoint(IOrderServiceAppService service) => _service = service; - - public override void Configure() - { - Get("/orderservices/{id:guid}"); - AllowAnonymous(); - Summary(s => - { - s.Summary = "Obtiene un servicio de orden por Id."; - }); - } - - public override async Task HandleAsync(CancellationToken ct) - { - var id = Route("id"); - if (id == Guid.Empty) - { - AddError("id", "El id proporcionado no es vlido."); - await HttpContext.Response.SendErrorsAsync(ValidationFailures, 400, null, ct); - return; - } - var result = await _service.GetByIdAsync(id); - if (result is null) - { - await HttpContext.Response.SendAsync(null, 404, null, ct); - return; - } - await HttpContext.Response.SendAsync(result, 200, null, ct); - } -} diff --git a/src/OTManager.Api/Endpoints/OrderServiceEndpoints/GetOrderServiceEndpoint.cs b/src/OTManager.Api/Endpoints/OrderServiceEndpoints/GetOrderServiceEndpoint.cs deleted file mode 100644 index 30c6bd9..0000000 --- a/src/OTManager.Api/Endpoints/OrderServiceEndpoints/GetOrderServiceEndpoint.cs +++ /dev/null @@ -1,27 +0,0 @@ -using FastEndpoints; -using OTManager.App.Dtos.Orders; -using OTManager.App.Services.Interfaces; - -namespace OTManager.Api.Endpoints.OrderServiceEndpoints; - -public class GetOrderServiceEndpoint : EndpointWithoutRequest -{ - private readonly IOrderServiceAppService _service; - public GetOrderServiceEndpoint(IOrderServiceAppService service) => _service = service; - - public override void Configure() - { - Get("/orderservices"); - AllowAnonymous(); - Summary(s => - { - s.Summary = "Obtiene todos los servicios de orden."; - }); - } - - public override async Task HandleAsync(CancellationToken ct) - { - var result = await _service.GetAllAsync(); - await HttpContext.Response.SendAsync(result, 200, null, ct); - } -} diff --git a/src/OTManager.Api/Endpoints/OrderServiceEndpoints/UpdateOrderServiceEndpoint.cs b/src/OTManager.Api/Endpoints/OrderServiceEndpoints/UpdateOrderServiceEndpoint.cs deleted file mode 100644 index 3506ce3..0000000 --- a/src/OTManager.Api/Endpoints/OrderServiceEndpoints/UpdateOrderServiceEndpoint.cs +++ /dev/null @@ -1,42 +0,0 @@ -using FastEndpoints; -using OTManager.App.Dtos.Orders; -using OTManager.App.Services.Interfaces; - -namespace OTManager.Api.Endpoints.OrderServiceEndpoints; - -public class UpdateOrderServiceEndpoint : Endpoint -{ - private readonly IOrderServiceAppService _service; - public UpdateOrderServiceEndpoint(IOrderServiceAppService service) => _service = service; - - public override void Configure() - { - Put("/orderservices/{id:guid}"); - AllowAnonymous(); - Summary(s => - { - s.Summary = "Actualiza un servicio de orden existente."; - }); - } - - public override async Task HandleAsync(ReadOrderServiceDto req, CancellationToken ct) - { - var id = Route("id"); - if (id == Guid.Empty) - { - AddError("id", "El id proporcionado no es vlido."); - await HttpContext.Response.SendErrorsAsync(ValidationFailures, 400, null, ct); - return; - } - if (string.IsNullOrWhiteSpace(req.Name)) AddError(r => r.Name, "El nombre es obligatorio."); - if (req.Price < 0) AddError(r => r.Price, "El precio debe ser mayor o igual a 0."); - if (ValidationFailed) return; - var updated = await _service.UpdateAsync(id, req); - if (!updated) - { - await HttpContext.Response.SendAsync(null, 404, null, ct); - return; - } - await HttpContext.Response.SendAsync(new { updated }, 200, null, ct); - } -} diff --git a/src/OTManager.Api/Extensions/ApplicationDependencyInjection.cs b/src/OTManager.Api/Extensions/ApplicationDependencyInjection.cs index 6c422b3..650d1a9 100644 --- a/src/OTManager.Api/Extensions/ApplicationDependencyInjection.cs +++ b/src/OTManager.Api/Extensions/ApplicationDependencyInjection.cs @@ -1,9 +1,13 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Configuration; +using System.Threading.RateLimiting; + +using Microsoft.AspNetCore.RateLimiting; + +using OTManager.Api.Account.Extensions; +using OTManager.App.Mappers; +using OTManager.App.Services; using OTManager.Data.Context; using OTManager.Data.Repositories; using OTManager.Data.UoW; -using OTManager.App.Services; namespace OTManager.Api.Extensions; @@ -11,10 +15,47 @@ public static class ApplicationDependencyInjection { public static IServiceCollection AddApplicationDependency(this IServiceCollection services, IConfiguration configuration) { + services.AddAppIdentityAuthentication(configuration); services.AddDbContextExtension(configuration); + services.AddSwaggerExtension(configuration); services.AddRepositoryExtension(); services.AddUnitOfWorkExtension(); - services.AddAppServices(); // <-- Registro de servicios de aplicacin + services.AddControllers(); + services.AddAppServices(); + services.AddAppMappers(); + + services.AddRateLimiter(static _ => _ + .AddFixedWindowLimiter("fixed", opt => + { + opt.PermitLimit = 4; + opt.Window = TimeSpan.FromSeconds(12); + opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; + opt.QueueLimit = 2; + }) + .AddSlidingWindowLimiter("sliding", opt => + { + opt.PermitLimit = 4; + opt.Window = TimeSpan.FromSeconds(12); + opt.SegmentsPerWindow = 2; + opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; + opt.QueueLimit = 2; + }) + .AddTokenBucketLimiter("token", opt => + { + opt.TokenLimit = 4; + opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; + opt.QueueLimit = 4; + opt.ReplenishmentPeriod = TimeSpan.FromSeconds(5); + opt.TokensPerPeriod = 2; + opt.AutoReplenishment = true; + }) + .AddConcurrencyLimiter("concurrency", opt => + { + opt.PermitLimit = 5; + opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; + opt.QueueLimit = 3; + })); + return services; } } diff --git a/src/OTManager.Api/Extensions/DependencyInjection.cs b/src/OTManager.Api/Extensions/DependencyInjection.cs deleted file mode 100644 index 464c7ae..0000000 --- a/src/OTManager.Api/Extensions/DependencyInjection.cs +++ /dev/null @@ -1,24 +0,0 @@ -using OTManager.Data.Context; -using OTManager.Data.Repositories; -using OTManager.Data.UoW; - -namespace OTManager.Api.Extensions; - -public static class DependencyInjection -{ - public static IServiceCollection AddApplicationDependency(this IServiceCollection services, IConfiguration configuration) - { - // ToDo: Add services here - services.AddControllers(); - //services.AddMapperExtension(); - services.AddSwaggerExtension(configuration); - services.AddDbContextExtension(configuration); - - services.AddScoped(); - - services.AddRepositoryExtension(); - //services.AddServicesExtension(); - - return services; - } -} diff --git a/src/OTManager.Api/OTManager.Api.csproj b/src/OTManager.Api/OTManager.Api.csproj index 258feb9..7c289ef 100644 --- a/src/OTManager.Api/OTManager.Api.csproj +++ b/src/OTManager.Api/OTManager.Api.csproj @@ -8,9 +8,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -18,6 +18,7 @@ + diff --git a/src/OTManager.Api/Program.cs b/src/OTManager.Api/Program.cs index b828342..0494c74 100644 --- a/src/OTManager.Api/Program.cs +++ b/src/OTManager.Api/Program.cs @@ -1,14 +1,16 @@ using OTManager.Api.Extensions; -using FastEndpoints; var builder = WebApplication.CreateBuilder(args); -// Usar el nombre de clase esttico para evitar ambigedad +builder.AddServiceDefaults(); + +// Usar el nombre de clase estático para evitar ambigüedad ApplicationDependencyInjection.AddApplicationDependency(builder.Services, builder.Configuration); -builder.Services.AddFastEndpoints(); var app = builder.Build(); +app.MapDefaultEndpoints(); + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { @@ -19,6 +21,10 @@ app.UseHttpsRedirection(); -app.UseFastEndpoints(); +app.UseAuthentication(); + +app.UseAuthorization(); + +app.MapControllers(); app.Run(); diff --git a/src/OTManager.App/Dtos/Clients/ReadClientDto.cs b/src/OTManager.App/Dtos/Clients/ReadClientDto.cs index 72b2f29..77e268e 100644 --- a/src/OTManager.App/Dtos/Clients/ReadClientDto.cs +++ b/src/OTManager.App/Dtos/Clients/ReadClientDto.cs @@ -1,6 +1,5 @@ using OTManager.App.Dtos.Factures; using OTManager.App.Dtos.Orders; -using System.Collections.Generic; namespace OTManager.App.Dtos.Clients; diff --git a/src/OTManager.App/Mappers/MapperExtension.cs b/src/OTManager.App/Mappers/MapperExtension.cs new file mode 100644 index 0000000..2b5ef42 --- /dev/null +++ b/src/OTManager.App/Mappers/MapperExtension.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.DependencyInjection; + +using OTManager.App.Mappers.Implements; +using OTManager.App.Mappers.Interfaces; + +namespace OTManager.App.Mappers; + +public static class MapperExtension +{ + public static IServiceCollection AddAppMappers(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + return services; + } +} diff --git a/src/OTManager.App/Services/Implements/MaterialCostService.cs b/src/OTManager.App/Services/Implements/MaterialCostService.cs deleted file mode 100644 index 20e21d6..0000000 --- a/src/OTManager.App/Services/Implements/MaterialCostService.cs +++ /dev/null @@ -1,102 +0,0 @@ -using OTManager.App.Dtos.MaterialCosts; -using OTManager.App.Mappers.Interfaces; -using OTManager.App.Services.Interfaces; -using OTManager.Core.QueryParams; -using OTManager.Data.UoW; - -namespace OTManager.App.Services.Implements; - -public class MaterialCostService : IMaterialCostService -{ - private readonly IUnitOfWork _unitOfWork; - private readonly IMaterialCostMapper _mapper; - - public MaterialCostService(IUnitOfWork unitOfWork, IMaterialCostMapper mapper) - { - _unitOfWork = unitOfWork; - _mapper = mapper; - } - - public async Task> GetAllAsync() - { - var items = await _unitOfWork.MaterialCosts.GetAllAsync(); - return items.Select(_mapper.ToEntityDto); - } - - public async Task<(IEnumerable Items, int TotalCount)> GetFilteredAsync(MaterialCostQueryParams query) - { - var (items, total) = await _unitOfWork.MaterialCosts.GetFilteredAsync(query); - return (items.Select(_mapper.ToEntityDto), total); - } - - public async Task GetByIdAsync(Guid id) - { - var item = await _unitOfWork.MaterialCosts.GetByIdAsync(id); - return item is null ? null : _mapper.ToEntityDto(item); - } - - public async Task CreateAsync(WriteMaterialCostDto dto) - { - await _unitOfWork.BeginTransactionAsync(); - try - { - var entity = _mapper.FromWriteDto(dto); - await _unitOfWork.MaterialCosts.AddAsync(entity); - await _unitOfWork.SaveChangesAsync(); - await _unitOfWork.CommitTransactionAsync(); - return _mapper.ToEntityDto(entity); - } - catch - { - await _unitOfWork.RollbackTransactionAsync(); - throw; - } - } - - public async Task UpdateAsync(Guid id, UpdateMaterialCostDto dto) - { - await _unitOfWork.BeginTransactionAsync(); - try - { - var entity = await _unitOfWork.MaterialCosts.GetByIdAsync(id); - if (entity is null) - { - await _unitOfWork.RollbackTransactionAsync(); - return false; - } - _mapper.FromUpdateDto(entity, dto); - await _unitOfWork.MaterialCosts.UpdateAsync(entity); - await _unitOfWork.SaveChangesAsync(); - await _unitOfWork.CommitTransactionAsync(); - return true; - } - catch - { - await _unitOfWork.RollbackTransactionAsync(); - throw; - } - } - - public async Task DeleteAsync(Guid id) - { - await _unitOfWork.BeginTransactionAsync(); - try - { - var entity = await _unitOfWork.MaterialCosts.GetByIdAsync(id); - if (entity is null) - { - await _unitOfWork.RollbackTransactionAsync(); - return false; - } - await _unitOfWork.MaterialCosts.DeleteAsync(id); - await _unitOfWork.SaveChangesAsync(); - await _unitOfWork.CommitTransactionAsync(); - return true; - } - catch - { - await _unitOfWork.RollbackTransactionAsync(); - throw; - } - } -} diff --git a/src/OTManager.App/Services/Implements/OrderServiceService.cs b/src/OTManager.App/Services/Implements/OrderServiceService.cs index 76090e0..b3d2490 100644 --- a/src/OTManager.App/Services/Implements/OrderServiceService.cs +++ b/src/OTManager.App/Services/Implements/OrderServiceService.cs @@ -1,8 +1,8 @@ using OTManager.App.Dtos.Orders; using OTManager.App.Services.Interfaces; +using OTManager.Core.Entities.OT; using OTManager.Core.QueryParams; using OTManager.Data.UoW; -using OTManager.Core.Entities.OT; namespace OTManager.App.Services.Implements; diff --git a/src/OTManager.App/Services/Implements/ServiceCostService.cs b/src/OTManager.App/Services/Implements/ServiceCostService.cs deleted file mode 100644 index 5afb6d5..0000000 --- a/src/OTManager.App/Services/Implements/ServiceCostService.cs +++ /dev/null @@ -1,102 +0,0 @@ -using OTManager.App.Dtos.ServiceCosts; -using OTManager.App.Mappers.Interfaces; -using OTManager.App.Services.Interfaces; -using OTManager.Core.QueryParams; -using OTManager.Data.UoW; - -namespace OTManager.App.Services.Implements; - -public class ServiceCostService : IServiceCostService -{ - private readonly IUnitOfWork _unitOfWork; - private readonly IServiceCostMapper _mapper; - - public ServiceCostService(IUnitOfWork unitOfWork, IServiceCostMapper mapper) - { - _unitOfWork = unitOfWork; - _mapper = mapper; - } - - public async Task> GetAllAsync() - { - var items = await _unitOfWork.ServiceCosts.GetAllAsync(); - return items.Select(_mapper.ToEntityDto); - } - - public async Task<(IEnumerable Items, int TotalCount)> GetFilteredAsync(ServiceCostQueryParams query) - { - var (items, total) = await _unitOfWork.ServiceCosts.GetFilteredAsync(query); - return (items.Select(_mapper.ToEntityDto), total); - } - - public async Task GetByIdAsync(Guid id) - { - var item = await _unitOfWork.ServiceCosts.GetByIdAsync(id); - return item is null ? null : _mapper.ToEntityDto(item); - } - - public async Task CreateAsync(WriteServiceCostDto dto) - { - await _unitOfWork.BeginTransactionAsync(); - try - { - var entity = _mapper.FromWriteDto(dto); - await _unitOfWork.ServiceCosts.AddAsync(entity); - await _unitOfWork.SaveChangesAsync(); - await _unitOfWork.CommitTransactionAsync(); - return _mapper.ToEntityDto(entity); - } - catch - { - await _unitOfWork.RollbackTransactionAsync(); - throw; - } - } - - public async Task UpdateAsync(Guid id, UpdateServiceCostDto dto) - { - await _unitOfWork.BeginTransactionAsync(); - try - { - var entity = await _unitOfWork.ServiceCosts.GetByIdAsync(id); - if (entity is null) - { - await _unitOfWork.RollbackTransactionAsync(); - return false; - } - _mapper.FromUpdateDto(entity, dto); - await _unitOfWork.ServiceCosts.UpdateAsync(entity); - await _unitOfWork.SaveChangesAsync(); - await _unitOfWork.CommitTransactionAsync(); - return true; - } - catch - { - await _unitOfWork.RollbackTransactionAsync(); - throw; - } - } - - public async Task DeleteAsync(Guid id) - { - await _unitOfWork.BeginTransactionAsync(); - try - { - var entity = await _unitOfWork.ServiceCosts.GetByIdAsync(id); - if (entity is null) - { - await _unitOfWork.RollbackTransactionAsync(); - return false; - } - await _unitOfWork.ServiceCosts.DeleteAsync(id); - await _unitOfWork.SaveChangesAsync(); - await _unitOfWork.CommitTransactionAsync(); - return true; - } - catch - { - await _unitOfWork.RollbackTransactionAsync(); - throw; - } - } -} diff --git a/src/OTManager.App/Services/Implements/WorkerCostService.cs b/src/OTManager.App/Services/Implements/WorkerCostService.cs deleted file mode 100644 index 52dc795..0000000 --- a/src/OTManager.App/Services/Implements/WorkerCostService.cs +++ /dev/null @@ -1,102 +0,0 @@ -using OTManager.App.Dtos.WorkerCosts; -using OTManager.App.Mappers.Interfaces; -using OTManager.App.Services.Interfaces; -using OTManager.Core.QueryParams; -using OTManager.Data.UoW; - -namespace OTManager.App.Services.Implements; - -public class WorkerCostService : IWorkerCostService -{ - private readonly IUnitOfWork _unitOfWork; - private readonly IWorkerCostMapper _mapper; - - public WorkerCostService(IUnitOfWork unitOfWork, IWorkerCostMapper mapper) - { - _unitOfWork = unitOfWork; - _mapper = mapper; - } - - public async Task> GetAllAsync() - { - var items = await _unitOfWork.WorkerCosts.GetAllAsync(); - return items.Select(_mapper.ToEntityDto); - } - - public async Task<(IEnumerable Items, int TotalCount)> GetFilteredAsync(WorkerCostQueryParams query) - { - var (items, total) = await _unitOfWork.WorkerCosts.GetFilteredAsync(query); - return (items.Select(_mapper.ToEntityDto), total); - } - - public async Task GetByIdAsync(Guid id) - { - var item = await _unitOfWork.WorkerCosts.GetByIdAsync(id); - return item is null ? null : _mapper.ToEntityDto(item); - } - - public async Task CreateAsync(WriteWorkerCostDto dto) - { - await _unitOfWork.BeginTransactionAsync(); - try - { - var entity = _mapper.FromWriteDto(dto); - await _unitOfWork.WorkerCosts.AddAsync(entity); - await _unitOfWork.SaveChangesAsync(); - await _unitOfWork.CommitTransactionAsync(); - return _mapper.ToEntityDto(entity); - } - catch - { - await _unitOfWork.RollbackTransactionAsync(); - throw; - } - } - - public async Task UpdateAsync(Guid id, UpdateWorkerCostDto dto) - { - await _unitOfWork.BeginTransactionAsync(); - try - { - var entity = await _unitOfWork.WorkerCosts.GetByIdAsync(id); - if (entity is null) - { - await _unitOfWork.RollbackTransactionAsync(); - return false; - } - _mapper.FromUpdateDto(entity, dto); - await _unitOfWork.WorkerCosts.UpdateAsync(entity); - await _unitOfWork.SaveChangesAsync(); - await _unitOfWork.CommitTransactionAsync(); - return true; - } - catch - { - await _unitOfWork.RollbackTransactionAsync(); - throw; - } - } - - public async Task DeleteAsync(Guid id) - { - await _unitOfWork.BeginTransactionAsync(); - try - { - var entity = await _unitOfWork.WorkerCosts.GetByIdAsync(id); - if (entity is null) - { - await _unitOfWork.RollbackTransactionAsync(); - return false; - } - await _unitOfWork.WorkerCosts.DeleteAsync(id); - await _unitOfWork.SaveChangesAsync(); - await _unitOfWork.CommitTransactionAsync(); - return true; - } - catch - { - await _unitOfWork.RollbackTransactionAsync(); - throw; - } - } -} diff --git a/src/OTManager.App/Services/Interfaces/IMaterialCostService.cs b/src/OTManager.App/Services/Interfaces/IMaterialCostService.cs deleted file mode 100644 index 0cc945c..0000000 --- a/src/OTManager.App/Services/Interfaces/IMaterialCostService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using OTManager.App.Dtos.MaterialCosts; -using OTManager.Core.QueryParams; - -namespace OTManager.App.Services.Interfaces; - -public interface IMaterialCostService -{ - Task> GetAllAsync(); - Task<(IEnumerable Items, int TotalCount)> GetFilteredAsync(MaterialCostQueryParams query); - Task GetByIdAsync(Guid id); - Task CreateAsync(WriteMaterialCostDto dto); - Task UpdateAsync(Guid id, UpdateMaterialCostDto dto); - Task DeleteAsync(Guid id); -} diff --git a/src/OTManager.App/Services/Interfaces/IOrderServiceService.cs b/src/OTManager.App/Services/Interfaces/IOrderServiceAppService.cs similarity index 100% rename from src/OTManager.App/Services/Interfaces/IOrderServiceService.cs rename to src/OTManager.App/Services/Interfaces/IOrderServiceAppService.cs diff --git a/src/OTManager.App/Services/Interfaces/IServiceCostService.cs b/src/OTManager.App/Services/Interfaces/IServiceCostService.cs deleted file mode 100644 index 02c84e4..0000000 --- a/src/OTManager.App/Services/Interfaces/IServiceCostService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using OTManager.App.Dtos.ServiceCosts; -using OTManager.Core.QueryParams; - -namespace OTManager.App.Services.Interfaces; - -public interface IServiceCostService -{ - Task> GetAllAsync(); - Task<(IEnumerable Items, int TotalCount)> GetFilteredAsync(ServiceCostQueryParams query); - Task GetByIdAsync(Guid id); - Task CreateAsync(WriteServiceCostDto dto); - Task UpdateAsync(Guid id, UpdateServiceCostDto dto); - Task DeleteAsync(Guid id); -} diff --git a/src/OTManager.App/Services/Interfaces/IWorkerCostService.cs b/src/OTManager.App/Services/Interfaces/IWorkerCostService.cs deleted file mode 100644 index 5bcc54e..0000000 --- a/src/OTManager.App/Services/Interfaces/IWorkerCostService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using OTManager.App.Dtos.WorkerCosts; -using OTManager.Core.QueryParams; - -namespace OTManager.App.Services.Interfaces; - -public interface IWorkerCostService -{ - Task> GetAllAsync(); - Task<(IEnumerable Items, int TotalCount)> GetFilteredAsync(WorkerCostQueryParams query); - Task GetByIdAsync(Guid id); - Task CreateAsync(WriteWorkerCostDto dto); - Task UpdateAsync(Guid id, UpdateWorkerCostDto dto); - Task DeleteAsync(Guid id); -} diff --git a/src/OTManager.App/Services/ServiceExtension.cs b/src/OTManager.App/Services/ServiceExtension.cs index f1e7ded..da445ef 100644 --- a/src/OTManager.App/Services/ServiceExtension.cs +++ b/src/OTManager.App/Services/ServiceExtension.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; -using OTManager.App.Services.Interfaces; + using OTManager.App.Services.Implements; +using OTManager.App.Services.Interfaces; namespace OTManager.App.Services; @@ -8,8 +9,13 @@ public static class ServiceExtension { public static IServiceCollection AddAppServices(this IServiceCollection services) { + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); - // Agrega aqu otros servicios segn sea necesario + services.AddScoped(); + services.AddScoped(); + return services; } } diff --git a/src/OTManager.Core/Entities/Abstracts/Auditable.cs b/src/OTManager.Core/Entities/Abstracts/Auditable.cs index 3be8212..da1de2d 100644 --- a/src/OTManager.Core/Entities/Abstracts/Auditable.cs +++ b/src/OTManager.Core/Entities/Abstracts/Auditable.cs @@ -4,7 +4,7 @@ /// Interfaz para entidades auditables. /// /// Tipo de la clave primaria. - public interface IAuditableEntity + public interface IAuditableEntity where TKey : IEquatable { TKey Id { get; set; } DateTime? CreatedAt { get; set; } @@ -16,7 +16,7 @@ public interface IAuditableEntity // string? DeletedBy { get; set; } } - public class Auditable : IAuditableEntity + public class Auditable : IAuditableEntity where TKey : IEquatable { public required TKey Id { get; set; } public DateTime? CreatedAt { get; set; } diff --git a/src/OTManager.Core/Entities/Abstracts/IEntity.cs b/src/OTManager.Core/Entities/Abstracts/IEntity.cs index 27c5699..8a2d9a3 100644 --- a/src/OTManager.Core/Entities/Abstracts/IEntity.cs +++ b/src/OTManager.Core/Entities/Abstracts/IEntity.cs @@ -1,6 +1,6 @@ namespace OTManager.Core.Entities.Abstracts; -public interface IEntity +public interface IEntity where TKey : IEquatable { - T Id { get; set; } + TKey Id { get; set; } } diff --git a/src/OTManager.Core/Entities/OT/MaterialCost.cs b/src/OTManager.Core/Entities/OT/MaterialCost.cs index 789f515..dbdfba3 100644 --- a/src/OTManager.Core/Entities/OT/MaterialCost.cs +++ b/src/OTManager.Core/Entities/OT/MaterialCost.cs @@ -7,17 +7,17 @@ public class MaterialCost : Auditable public string Code { get; set; } = string.Empty!; public string Name { get; set; } = string.Empty!; public string MeasureUnit { get; set; } = string.Empty!; - public decimal UnitCost - { - get => _unitCost; - set { _unitCost = value; RecalculateTotalCost(); } + public decimal UnitCost + { + get => _unitCost; + set { _unitCost = value; RecalculateTotalCost(); } } public Guid OrderId { get; set; } public Order? Order { get; set; } - public decimal Quantity - { - get => _quantity; - set { _quantity = value; RecalculateTotalCost(); } + public decimal Quantity + { + get => _quantity; + set { _quantity = value; RecalculateTotalCost(); } } public decimal TotalCost { get; private set; } diff --git a/src/OTManager.Core/Entities/OT/ServiceCost.cs b/src/OTManager.Core/Entities/OT/ServiceCost.cs index 88003ea..f3f69ca 100644 --- a/src/OTManager.Core/Entities/OT/ServiceCost.cs +++ b/src/OTManager.Core/Entities/OT/ServiceCost.cs @@ -6,17 +6,17 @@ public class ServiceCost : Auditable { public string Name { get; set; } = string.Empty!; public string Description { get; set; } = string.Empty!; - public decimal Price - { - get => _price; - set { _price = value; RecalculateTotalPrice(); } + public decimal Price + { + get => _price; + set { _price = value; RecalculateTotalPrice(); } } public Guid OrderId { get; set; } public Order? Order { get; set; } - public int Quantity - { - get => _quantity; - set { _quantity = value; RecalculateTotalPrice(); } + public int Quantity + { + get => _quantity; + set { _quantity = value; RecalculateTotalPrice(); } } public decimal TotalPrice { get; private set; } diff --git a/src/OTManager.Core/Entities/OT/WorkerCost.cs b/src/OTManager.Core/Entities/OT/WorkerCost.cs index b18a38a..f7c663f 100644 --- a/src/OTManager.Core/Entities/OT/WorkerCost.cs +++ b/src/OTManager.Core/Entities/OT/WorkerCost.cs @@ -5,17 +5,17 @@ namespace OTManager.Core.Entities.OT; public class WorkerCost : Auditable { public string Name { get; set; } = string.Empty!; - public decimal HourlyRate - { - get => _hourlyRate; - set { _hourlyRate = value; RecalculateTotalCost(); } + public decimal HourlyRate + { + get => _hourlyRate; + set { _hourlyRate = value; RecalculateTotalCost(); } } public Guid OrderId { get; set; } public Order? Order { get; set; } - public decimal HoursWorked - { - get => _hoursWorked; - set { _hoursWorked = value; RecalculateTotalCost(); } + public decimal HoursWorked + { + get => _hoursWorked; + set { _hoursWorked = value; RecalculateTotalCost(); } } public decimal TotalCost { get; private set; } diff --git a/src/OTManager.Web/ClientServices/Clients/ClientService.cs b/src/OTManager.Web/ClientServices/Clients/ClientService.cs new file mode 100644 index 0000000..e947425 --- /dev/null +++ b/src/OTManager.Web/ClientServices/Clients/ClientService.cs @@ -0,0 +1,6 @@ +namespace OTManager.Web.ClientServices.Clients; + +public class ClientService(HttpClient httpClient) +{ + private readonly HttpClient _httpClient = httpClient; +} diff --git a/src/OTManager.Web/ClientServices/Materials/MaterialService.cs b/src/OTManager.Web/ClientServices/Materials/MaterialService.cs new file mode 100644 index 0000000..c079395 --- /dev/null +++ b/src/OTManager.Web/ClientServices/Materials/MaterialService.cs @@ -0,0 +1,29 @@ +using OTManager.Core.QueryParams; +using OTManager.Web.ClientServices.Materials.Records; + +namespace OTManager.Web.ClientServices.Materials; + +public class MaterialService(HttpClient httpClient) +{ + private readonly HttpClient _httpClient = httpClient; + + public async Task> GetFilteredAsync(MaterialQueryParams materialQuery) + { + var result = await _httpClient.PostAsJsonAsync($"material/filter", materialQuery); + //var response = await _httpClient.GetFromJsonAsync>($"material"); + + List? materials = []; + + if (result.IsSuccessStatusCode) + { + materials = await result.Content.ReadFromJsonAsync>(); + } + + return materials!; + } + public async Task GetByIdAsync(string id) + { + var response = await _httpClient.GetFromJsonAsync($"material/{id}"); + return response!; + } +} diff --git a/src/OTManager.Web/ClientServices/Materials/Records/MaterialReadDto.cs b/src/OTManager.Web/ClientServices/Materials/Records/MaterialReadDto.cs new file mode 100644 index 0000000..cd9e468 --- /dev/null +++ b/src/OTManager.Web/ClientServices/Materials/Records/MaterialReadDto.cs @@ -0,0 +1,8 @@ +namespace OTManager.Web.ClientServices.Materials.Records; + +public record MaterialReadDto +(Guid Id, +string Code, +string Name, +string MeasureUnit, +decimal UnitCost); diff --git a/src/OTManager.Web/Components/Account/Pages/Auth/Login.razor b/src/OTManager.Web/Components/Account/Pages/Auth/Login.razor new file mode 100644 index 0000000..b30c2c9 --- /dev/null +++ b/src/OTManager.Web/Components/Account/Pages/Auth/Login.razor @@ -0,0 +1,36 @@ +@page "/login" +@using Microsoft.AspNetCore.Components.Authorization + +@inject NavigationManager Nav +@inject AuthenticationStateProvider AuthStateProvider + +

Login

+ + + +@* *@ +

@message

+ +@code { + private string username = string.Empty; + private string password = string.Empty; + private string message = string.Empty; + + // private async Task HandleLogin() + // { + // var res = await AuthService.LoginAsync(new OTManager.Web.ClientServices.DTOs.Identity.LoginRequest(username, password)); + // if (res is null || !res.Success) + // { + // message = res?.Message ?? "Error en login"; + // return; + // } + + // // Notify provider in case not already done + // if (AuthStateProvider is OTManager.Web.ClientServices.Authorize.AuthenticationService.ApiAuthenticationStateProvider provider) + // { + // provider.NotifyUserAuthentication(res.Token!); + // } + + // Nav.NavigateTo("/"); + // } +} diff --git a/src/OTManager.Web/Components/Account/Pages/Auth/Register.razor b/src/OTManager.Web/Components/Account/Pages/Auth/Register.razor new file mode 100644 index 0000000..c950608 --- /dev/null +++ b/src/OTManager.Web/Components/Account/Pages/Auth/Register.razor @@ -0,0 +1,29 @@ +@page "/register" + +@inject NavigationManager Nav + +

Register

+ + + +@* *@ +

@message

+ +@code { + private string email = string.Empty; + private string password = string.Empty; + private string message = string.Empty; + + // private async Task HandleRegister() + // { + // var ok = await AuthService.RegisterAsync(new OTManager.Web.ClientServices.DTOs.Identity.RegisterRequest(email, password)); + // if (ok) + // { + // Nav.NavigateTo("/login"); + // } + // else + // { + // message = "Error registrando usuario"; + // } + // } +} diff --git a/src/OTManager.Web/Components/Account/Pages/User/Profile.razor b/src/OTManager.Web/Components/Account/Pages/User/Profile.razor new file mode 100644 index 0000000..b99f49b --- /dev/null +++ b/src/OTManager.Web/Components/Account/Pages/User/Profile.razor @@ -0,0 +1,37 @@ +@page "/profile" +@using Microsoft.AspNetCore.Components.Authorization +@using System.Security.Claims +@inject AuthenticationStateProvider AuthStateProvider + + + +

Perfil de Usuario

+

Username: @userName

+

Email: @email

+

Roles: @string.Join(", ", roles)

+
+ +

No autorizado. Login

+
+
+ +@code { + private string userName = string.Empty; + private string email = string.Empty; + private List roles = new(); + + // protected override async Task OnInitializedAsync() + // { + // var authState = await AuthStateProvider.GetAuthenticationStateAsync(); + // var user = authState.User; + // if (user.Identity?.IsAuthenticated == true) + // { + // userName = user.Identity.Name ?? user.Claims.FirstOrDefault(c => c.Type == "unique_name")?.Value ?? string.Empty; + // email = user.Claims.FirstOrDefault(c => c.Type == "email")?.Value ?? string.Empty; + // roles = user.Claims + // .Where(c => c.Type == "role" || c.Type == "roles" || c.Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role") + // .Select(c => c.Value) + // .ToList(); + // } + // } +} diff --git a/src/OTManager.Web/Components/App.razor b/src/OTManager.Web/Components/App.razor new file mode 100644 index 0000000..7cda35b --- /dev/null +++ b/src/OTManager.Web/Components/App.razor @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/OTManager.Web/Components/Clients/Components/ClientListViewComponent.razor b/src/OTManager.Web/Components/Clients/Components/ClientListViewComponent.razor new file mode 100644 index 0000000..b1187c3 --- /dev/null +++ b/src/OTManager.Web/Components/Clients/Components/ClientListViewComponent.razor @@ -0,0 +1,55 @@ + + + + + + + + + + + @Content + + + @Username | @LastModification | @Status + + + + + + + + + + + + +@code { + [Parameter] + public string Content { get; set; } = string.Empty; + [Parameter] + public string Username { get; set; } = string.Empty; + [Parameter] + public string Status { get; set; } = string.Empty; + [Parameter] + public DateTime LastModification { get; set; } + [Parameter] + public EventCallback OnOverseeEvent { get; set; } + [Parameter] + public EventCallback OnEditEvent { get; set; } + [Parameter] + public EventCallback OnDeleteEvent { get; set; } +} diff --git a/src/OTManager.Web/Components/Clients/Components/OverseeMenuComponent.razor b/src/OTManager.Web/Components/Clients/Components/OverseeMenuComponent.razor new file mode 100644 index 0000000..c5b4012 --- /dev/null +++ b/src/OTManager.Web/Components/Clients/Components/OverseeMenuComponent.razor @@ -0,0 +1,8 @@ + + @ChildContent + + +@code{ + [Parameter] + public RenderFragment? ChildContent { get; set; } +} \ No newline at end of file diff --git a/src/OTManager.Web/Components/Clients/Pages/ClientsIndex.razor b/src/OTManager.Web/Components/Clients/Pages/ClientsIndex.razor new file mode 100644 index 0000000..a10f591 --- /dev/null +++ b/src/OTManager.Web/Components/Clients/Pages/ClientsIndex.razor @@ -0,0 +1,42 @@ +@page "/Clients/Index" + +Clients | Index + + + + + Clients + + + + + + + + + + @if (Items is null) + { + +

Add some

+
+ } + else + { + foreach (Item item in Items) + { + + + + + + } + } +
diff --git a/src/OTManager.Web/Components/Clients/Pages/ClientsIndex.razor.cs b/src/OTManager.Web/Components/Clients/Pages/ClientsIndex.razor.cs new file mode 100644 index 0000000..d398aaf --- /dev/null +++ b/src/OTManager.Web/Components/Clients/Pages/ClientsIndex.razor.cs @@ -0,0 +1,43 @@ +namespace OTManager.Web.Components.Clients.Pages; + +public partial class ClientsIndex +{ + private IQueryable? Items { get; set; } + + private Item? NewItem { get; set; } + + protected override async Task OnInitializedAsync() + { + await Task.Delay(500); + Items = GetDemoItems(); + } + + private void OnDeleteEvent() + { + + } + private void OnEditEvent() + { + + } + private void OnOverseeEvent() + { + + } + + public static IQueryable GetDemoItems() => new List{ + new ("001", "Client 1", "U", 13.5m ), + new ("002", "Client 2", "U", 13.5m), + new ("003", "Client 3", "U", 13.5m), + new ("004", "Client 4", "U", 13.5m), + new ("005", "Client 5", "U", 13.5m), + new ("006", "Client 6", "U", 13.5m) + }.AsQueryable(); + + public record Item( + string Code, + string Name, + string MeasureUnit, + decimal UnitCost + ); +} diff --git a/src/OTManager.Web/Components/Clients/_Imports.razor b/src/OTManager.Web/Components/Clients/_Imports.razor new file mode 100644 index 0000000..146f66b --- /dev/null +++ b/src/OTManager.Web/Components/Clients/_Imports.razor @@ -0,0 +1,2 @@ +@using OTManager.Web.Components.Clients.Components +@using OTManager.WebComp.Shared \ No newline at end of file diff --git a/src/OTManager.Web/Components/Dashboards/Pages/DashboardIndex.razor b/src/OTManager.Web/Components/Dashboards/Pages/DashboardIndex.razor new file mode 100644 index 0000000..8194c7a --- /dev/null +++ b/src/OTManager.Web/Components/Dashboards/Pages/DashboardIndex.razor @@ -0,0 +1,57 @@ +@page "/Home" +@using OTManager.Web.Components.Settings.Resources.Enums + +Dashboard + + + + + + + BlaBla + + + BlaBla + + + + + + + + + + + + + + + zfdsgsrgergergergaergaergerg + + + zfdsgsrgergergergaergaergerg + + + + + + + zfdsgsrgergergergaergaergerg 1 + + + + + zfdsgsrgergergergaergaergerg 2 + + + + + +@code { + public LocalizerSelect? localizer { get; set; } +} diff --git a/src/OTManager.Web/Components/Layout/MainLayout.razor b/src/OTManager.Web/Components/Layout/MainLayout.razor new file mode 100644 index 0000000..074cbd4 --- /dev/null +++ b/src/OTManager.Web/Components/Layout/MainLayout.razor @@ -0,0 +1,42 @@ +@inherits LayoutComponentBase + + + + OTManager + + + + + + + + +
+ @Body +
+
+ + + + +
+ + Documentation and demos + + About Blazor + +
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
+ +@code { + // Ensure this layout is accessible globally + public static Type LayoutType => typeof(MainLayout); +} diff --git a/src/OTManager.Web/Components/Layout/NavMenu.razor b/src/OTManager.Web/Components/Layout/NavMenu.razor new file mode 100644 index 0000000..557c070 --- /dev/null +++ b/src/OTManager.Web/Components/Layout/NavMenu.razor @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/src/OTManager.Web/Components/Materials/Components/MaterialListViewComponent.razor b/src/OTManager.Web/Components/Materials/Components/MaterialListViewComponent.razor new file mode 100644 index 0000000..ef8ca8e --- /dev/null +++ b/src/OTManager.Web/Components/Materials/Components/MaterialListViewComponent.razor @@ -0,0 +1,41 @@ + + + + Icono + + + + @Content + + + @Username | @LastModification | @Status + + + + + + + + + + +@code { + [Parameter] + public string Content { get; set; } = string.Empty; + [Parameter] + public string Username { get; set; } = string.Empty; + [Parameter] + public string Status { get; set; } = string.Empty; + [Parameter] + public DateTime LastModification { get; set; } + [Parameter] + public EventCallback OnClickEvent { get; set; } +} \ No newline at end of file diff --git a/src/OTManager.Web/Components/Materials/Layout/MaterialLayout.razor b/src/OTManager.Web/Components/Materials/Layout/MaterialLayout.razor new file mode 100644 index 0000000..4dd275f --- /dev/null +++ b/src/OTManager.Web/Components/Materials/Layout/MaterialLayout.razor @@ -0,0 +1,22 @@ +@layout MainLayout +@inherits LayoutComponentBase + + + + + Materials + + + + + + + + + + + + @Body + + diff --git a/src/OTManager.Web/Components/Materials/Pages/StocksIndex.razor b/src/OTManager.Web/Components/Materials/Pages/StocksIndex.razor new file mode 100644 index 0000000..d31d22a --- /dev/null +++ b/src/OTManager.Web/Components/Materials/Pages/StocksIndex.razor @@ -0,0 +1,33 @@ +@page "/Materials/Index" + +@layout MaterialLayout + +@inject MaterialService _materialService; + +Materials | Index + + + @if (Items is null) + { + + + + } + else + { + foreach (MaterialReadDto item in Items) + { + + + + + + } + } + diff --git a/src/OTManager.Web/Components/Materials/Pages/StocksIndex.razor.cs b/src/OTManager.Web/Components/Materials/Pages/StocksIndex.razor.cs new file mode 100644 index 0000000..8d0d591 --- /dev/null +++ b/src/OTManager.Web/Components/Materials/Pages/StocksIndex.razor.cs @@ -0,0 +1,47 @@ +using OTManager.Core.QueryParams; +using OTManager.Web.ClientServices.Materials.Records; + +namespace OTManager.Web.Components.Materials.Pages; + +public partial class StocksIndex +{ + private MaterialQueryParams query = new( + Search: "PitBoy", + Code: string.Empty, + Name: string.Empty, + CreatedAt: null, + SortBy: "Name", + Descending: false, + Page: 1, + PageSize: 3); + + private IQueryable? Items { get; set; } + private MaterialQueryParams Query { get => query; set => query = value; } + private MaterialReadDto? Item { get; set; } + + protected override async Task OnInitializedAsync() + { + await Task.Delay(500); + await GetItemsAsync(); + } + + public async Task GetItemsAsync() + { + var response = await _materialService.GetFilteredAsync(Query); + Items = response.AsQueryable(); + } + + private async Task GetItemByIdAsync(string id) + { + var response = await _materialService.GetByIdAsync(id); + if (response is not null) + { + Item = response; + } + else + { + Item = null; + } + } +} + diff --git a/src/OTManager.Web/Components/Materials/_Imports.razor b/src/OTManager.Web/Components/Materials/_Imports.razor new file mode 100644 index 0000000..6e288f4 --- /dev/null +++ b/src/OTManager.Web/Components/Materials/_Imports.razor @@ -0,0 +1,5 @@ +@using OTManager.Web.Components.Materials.Components +@using OTManager.Web.ClientServices.Materials +@using OTManager.Web.ClientServices.Materials.Records +@using OTManager.Web.Components.Materials.Layout +@using OTManager.Web.Components.Layout \ No newline at end of file diff --git a/src/OTManager.Web/Components/Pages/Error.razor b/src/OTManager.Web/Components/Pages/Error.razor new file mode 100644 index 0000000..576cc2d --- /dev/null +++ b/src/OTManager.Web/Components/Pages/Error.razor @@ -0,0 +1,36 @@ +@page "/Error" +@using System.Diagnostics + +Error + +

Error.

+

An error occurred while processing your request.

+ +@if (ShowRequestId) +{ +

+ Request ID: @RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

+ +@code{ + [CascadingParameter] + private HttpContext? HttpContext { get; set; } + + private string? RequestId { get; set; } + private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + protected override void OnInitialized() => + RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; +} diff --git a/src/OTManager.Web/Components/Pages/Home.razor b/src/OTManager.Web/Components/Pages/Home.razor new file mode 100644 index 0000000..db63207 --- /dev/null +++ b/src/OTManager.Web/Components/Pages/Home.razor @@ -0,0 +1,27 @@ +@page "/" + +@* @inject HttpClient http *@ + +Home + +

Hello, world!

+ +Welcome to your new Fluent Blazor app. + +@* @if (markdown == null) +{ +

cargando

+} +else +{ + +} + +@code{ + private string? markdown; + + protected override async Task OnInitializedAsync() + { + markdown = await http.GetStringAsync("Docs/README.md"); + } +} *@ \ No newline at end of file diff --git a/src/OTManager.Web/Components/Routes.razor b/src/OTManager.Web/Components/Routes.razor new file mode 100644 index 0000000..646d973 --- /dev/null +++ b/src/OTManager.Web/Components/Routes.razor @@ -0,0 +1,15 @@ +@using Microsoft.AspNetCore.Components.Authorization + + + + + + + + + +

Sorry, there's nothing at this address.

+
+
+
+
diff --git a/src/OTManager.Web/Components/Settings/Controllers/Localization/CultureController.cs b/src/OTManager.Web/Components/Settings/Controllers/Localization/CultureController.cs new file mode 100644 index 0000000..c53de3f --- /dev/null +++ b/src/OTManager.Web/Components/Settings/Controllers/Localization/CultureController.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Localization; +using Microsoft.AspNetCore.Mvc; + +namespace OTManager.Web.Components.Settings.Controllers.Localization; + +[Route("[controller]/[action]")] +public class CultureController : Controller +{ + public IActionResult Set(string culture, string redirectUri) + { + if (culture != null) + { + var requestCulture = new RequestCulture(culture, culture); + var cookieName = CookieRequestCultureProvider.DefaultCookieName; + var cookieValue = CookieRequestCultureProvider.MakeCookieValue(requestCulture); + + HttpContext.Response.Cookies.Append(cookieName, cookieValue); + } + return LocalRedirect(redirectUri); + } +} diff --git a/src/OTManager.Web/Components/Settings/Layout/SettingsLayout.razor b/src/OTManager.Web/Components/Settings/Layout/SettingsLayout.razor new file mode 100644 index 0000000..143064f --- /dev/null +++ b/src/OTManager.Web/Components/Settings/Layout/SettingsLayout.razor @@ -0,0 +1,46 @@ +@layout MainLayout +@inherits LayoutComponentBase + + + + Settings + + + + + + + + + + + + + + + + +
+ @Body +
+ + +@code { + +} diff --git a/src/OTManager.Web/Components/Settings/Pages/SettingDataPage.razor b/src/OTManager.Web/Components/Settings/Pages/SettingDataPage.razor new file mode 100644 index 0000000..b67d8c4 --- /dev/null +++ b/src/OTManager.Web/Components/Settings/Pages/SettingDataPage.razor @@ -0,0 +1,107 @@ +@page "/settings/data" + +@layout SettingsLayout + + + + + Data Settings + + + Home + Settings + Data + + + + + + + + + Audith data settings + + + + + + + + + + + + + + + Audith data settings + + + + + + + + + + + + + + + records data settings + + + + + + + + + + + + + + + Db Context + + + + Status + Connected + + + Healthy + Good + + + + + + +@code{ + +} diff --git a/src/OTManager.Web/Components/Settings/Pages/SettingLanguagePage.razor b/src/OTManager.Web/Components/Settings/Pages/SettingLanguagePage.razor new file mode 100644 index 0000000..9064962 --- /dev/null +++ b/src/OTManager.Web/Components/Settings/Pages/SettingLanguagePage.razor @@ -0,0 +1,85 @@ +@page "/settings/language" + +@layout SettingsLayout +@inject NavigationManager NavigationManager +@inject IStringLocalizer UiLang + + + + + @UiLang["SettingsLangTitle"] + + + @UiLang["NavHome"] + @UiLang["NavSettings"] + @UiLang["NavLanguage"] + + + + + + + + + + @UiLang["LangApiDescription"] + + + + + + + + +
+ +
+ @UiLang["LangUiDescription"] + @localizer.GetValueOrDefault() +
+
+ +
+ +@code { + public LocalizerSelect? localizer { get; set; } + + protected override void OnInitialized() + { + Culture = CultureInfo.CurrentCulture; + } + + private CultureInfo Culture + { + get { return CultureInfo.CurrentCulture; } + set + { + if (CultureInfo.CurrentCulture != value) + { + var uri = new Uri(NavigationManager.Uri) + .GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped); + var cultureEscaped = Uri.EscapeDataString(value.Name); + var uriEscaped = Uri.EscapeDataString(uri); + + NavigationManager.NavigateTo($"Culture/Set?culture={cultureEscaped}&redirectUri={uriEscaped}", forceLoad: true); + } + } + } +} \ No newline at end of file diff --git a/src/OTManager.Web/Components/Settings/Pages/SettingThemePage.razor b/src/OTManager.Web/Components/Settings/Pages/SettingThemePage.razor new file mode 100644 index 0000000..1255176 --- /dev/null +++ b/src/OTManager.Web/Components/Settings/Pages/SettingThemePage.razor @@ -0,0 +1,82 @@ +@page "/settings/theme" + +@layout SettingsLayout + + + + Theme Settings + + + Home + Settings + Theme + + + + + + + + + + Selected the theme in 3 options + System: follow the system theme + Light: a light theme + Dark: a dark theme + + + + + + + + + + @context + + + + Select the options of color from your preference + + + + + + Get random style + Feeling lucky? + + + + + + +@code { + public DesignThemeModes Mode { get; set; } + + public OfficeColor? OfficeColor { get; set; } + + void PickRandomColor() + { + OfficeColor = OfficeColorUtilities.GetRandom(); + } +} \ No newline at end of file diff --git a/src/OTManager.Web/Components/Settings/Pages/SettingUserPage.razor b/src/OTManager.Web/Components/Settings/Pages/SettingUserPage.razor new file mode 100644 index 0000000..4bc125c --- /dev/null +++ b/src/OTManager.Web/Components/Settings/Pages/SettingUserPage.razor @@ -0,0 +1,40 @@ +@page "/settings/users" + +@layout SettingsLayout + + + + Users Settings + +
+ Add + Export + Print +
+ + + Home + Settings + Users + +
+ + + @* @if (users == null) + { +

Loading...

+ } + else + { + + + + + + } *@ +
\ No newline at end of file diff --git a/src/OTManager.Web/Components/Settings/Pages/SettingUserPage.razor.cs b/src/OTManager.Web/Components/Settings/Pages/SettingUserPage.razor.cs new file mode 100644 index 0000000..ed5e7dd --- /dev/null +++ b/src/OTManager.Web/Components/Settings/Pages/SettingUserPage.razor.cs @@ -0,0 +1,30 @@ + +namespace OTManager.Web.Components.Settings.Pages; + +public partial class SettingUserPage +{ + //public IQueryable? users; + + //protected override async Task OnInitializedAsync() + //{ + // // Simulate asynchronous loading to demonstrate a loading indicator + // await Task.Delay(500); + // await GetAllAsync(); + + //} + + //private async Task GetAllAsync() + //{ + // //await uService.GetAll(); + + // //var response = await uService.GetAll(); + // //if (response is not null) + // //{ + // // users = await uService.GetAll(); + // //} + // //else + // //{ + // // users = null; + // //} + //} +} diff --git a/src/OTManager.Web/Components/Settings/Pages/SettingsPage.razor b/src/OTManager.Web/Components/Settings/Pages/SettingsPage.razor new file mode 100644 index 0000000..ca83c26 --- /dev/null +++ b/src/OTManager.Web/Components/Settings/Pages/SettingsPage.razor @@ -0,0 +1,33 @@ +@page "/Settings" + + +@layout SettingsLayout + + + + Overview + + + Home + Settings + + + + + + Some fancy text + + + + +@code { + +} diff --git a/src/OTManager.Web/Components/Settings/Pages/_Imports.razor b/src/OTManager.Web/Components/Settings/Pages/_Imports.razor new file mode 100644 index 0000000..00ae358 --- /dev/null +++ b/src/OTManager.Web/Components/Settings/Pages/_Imports.razor @@ -0,0 +1,6 @@ +@using System.Globalization +@using Microsoft.Extensions.Localization +@using Microsoft.FluentUI.AspNetCore.Components.Extensions +@using OTManager.Web.Components.Settings.Layout +@using OTManager.Web.Components.Settings.Resources.Enums +@using OTManager.Web.Components.Settings.Resources.Localization diff --git a/src/OTManager.Web/Components/Settings/Resources/Enums/LocalizerSelect.cs b/src/OTManager.Web/Components/Settings/Resources/Enums/LocalizerSelect.cs new file mode 100644 index 0000000..024cd8f --- /dev/null +++ b/src/OTManager.Web/Components/Settings/Resources/Enums/LocalizerSelect.cs @@ -0,0 +1,11 @@ +using System.ComponentModel; + +namespace OTManager.Web.Components.Settings.Resources.Enums; + +public enum LocalizerSelect +{ + [Description("es-ES")] + Español, + [Description("en-EU")] + English +} diff --git a/src/OTManager.Web/Components/Settings/Resources/Enums/StyleColor.cs b/src/OTManager.Web/Components/Settings/Resources/Enums/StyleColor.cs new file mode 100644 index 0000000..54e74ae --- /dev/null +++ b/src/OTManager.Web/Components/Settings/Resources/Enums/StyleColor.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; + +namespace OTManager.Web.Components.Settings.Resources.Enums; + +public enum StyleColor +{ + // + // Resumen: + // The default Fluent UI accent color + [Description("default")] + Default, + [Description("#a4373a")] + Access, + [Description("#0078d4")] + Exchange, + [Description("#217346")] + Excel, + [Description("#d83b01")] + Office +} diff --git a/src/OTManager.Web/Components/Settings/Resources/Localization/UiLanguage.Designer.cs b/src/OTManager.Web/Components/Settings/Resources/Localization/UiLanguage.Designer.cs new file mode 100644 index 0000000..1d94cfd --- /dev/null +++ b/src/OTManager.Web/Components/Settings/Resources/Localization/UiLanguage.Designer.cs @@ -0,0 +1,126 @@ +//------------------------------------------------------------------------------ +// +// Este código fue generado por una herramienta. +// Versión de runtime:4.0.30319.42000 +// +// Los cambios en este archivo podrían causar un comportamiento incorrecto y se perderán si +// se vuelve a generar el código. +// +//------------------------------------------------------------------------------ + +namespace OTManager.Web.Components.Settings.Resources.Localization { + using System; + + + /// + /// Clase de recurso fuertemente tipado, para buscar cadenas traducidas, etc. + /// + // StronglyTypedResourceBuilder generó automáticamente esta clase + // a través de una herramienta como ResGen o Visual Studio. + // Para agregar o quitar un miembro, edite el archivo .ResX y, a continuación, vuelva a ejecutar ResGen + // con la opción /str o recompile su proyecto de VS. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class UiLanguage { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal UiLanguage() { + } + + /// + /// Devuelve la instancia de ResourceManager almacenada en caché utilizada por esta clase. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("OTManager.Web.Components.Settings.Resources.Localization.UiLanguage", typeof(UiLanguage).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Reemplaza la propiedad CurrentUICulture del subproceso actual para todas las + /// búsquedas de recursos mediante esta clase de recurso fuertemente tipado. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Busca una cadena traducida similar a Manage the language for the notifications ands responses from the server default [en-EU]. + /// + public static string LangApiDescription { + get { + return ResourceManager.GetString("LangApiDescription", resourceCulture); + } + } + + /// + /// Busca una cadena traducida similar a Manage the language for the UI applications and is manage from the user preference . + /// + public static string LangUiDescription { + get { + return ResourceManager.GetString("LangUiDescription", resourceCulture); + } + } + + /// + /// Busca una cadena traducida similar a Home. + /// + public static string NavHome { + get { + return ResourceManager.GetString("NavHome", resourceCulture); + } + } + + /// + /// Busca una cadena traducida similar a Language. + /// + public static string NavLanguage { + get { + return ResourceManager.GetString("NavLanguage", resourceCulture); + } + } + + /// + /// Busca una cadena traducida similar a Settings. + /// + public static string NavSettings { + get { + return ResourceManager.GetString("NavSettings", resourceCulture); + } + } + + /// + /// Busca una cadena traducida similar a Pong. + /// + public static string ping { + get { + return ResourceManager.GetString("ping", resourceCulture); + } + } + + /// + /// Busca una cadena traducida similar a Language. + /// + public static string SettingsLangTitle { + get { + return ResourceManager.GetString("SettingsLangTitle", resourceCulture); + } + } + } +} diff --git a/src/OTManager.Web/Components/Settings/Resources/Localization/UiLanguage.en-US.resx b/src/OTManager.Web/Components/Settings/Resources/Localization/UiLanguage.en-US.resx new file mode 100644 index 0000000..4464322 --- /dev/null +++ b/src/OTManager.Web/Components/Settings/Resources/Localization/UiLanguage.en-US.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Manage the language for the notifications ands responses from the server, default [en-EU]. + + + Manage the language for the UI applications and is manage from the user preference. + + + Home + + + Language + + + Settings + + + Pong en-US + + + Language Settings + + \ No newline at end of file diff --git a/src/OTManager.Web/Components/Settings/Resources/Localization/UiLanguage.es-ES.resx b/src/OTManager.Web/Components/Settings/Resources/Localization/UiLanguage.es-ES.resx new file mode 100644 index 0000000..0590ac5 --- /dev/null +++ b/src/OTManager.Web/Components/Settings/Resources/Localization/UiLanguage.es-ES.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Gestiona el idioma de las notificaciones y respuestas desde el servidor, por defecto [en-EU]. + + + Gestiona el idioma de la interface según las preferencias de usuario. + + + Inicio + + + Idioma + + + Configuración + + + Pong es-ES + + + Configuración de idioma + + \ No newline at end of file diff --git a/src/OTManager.Web/Components/Settings/Resources/Localization/UiLanguage.resx b/src/OTManager.Web/Components/Settings/Resources/Localization/UiLanguage.resx new file mode 100644 index 0000000..5c636c9 --- /dev/null +++ b/src/OTManager.Web/Components/Settings/Resources/Localization/UiLanguage.resx @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Manage the language for the notifications ands responses from the server default [en-EU] + + + Manage the language for the UI applications and is manage from the user preference + + + Home + + + Language + + + Settings + + + Pong + Test resource string + + + Language + Settings Language Title + + \ No newline at end of file diff --git a/src/OTManager.Web/Components/Settings/_Imports.razor b/src/OTManager.Web/Components/Settings/_Imports.razor new file mode 100644 index 0000000..208c16a --- /dev/null +++ b/src/OTManager.Web/Components/Settings/_Imports.razor @@ -0,0 +1 @@ +@using OTManager.Web.Components.Layout \ No newline at end of file diff --git a/src/OTManager.Web/Components/_Imports.razor b/src/OTManager.Web/Components/_Imports.razor new file mode 100644 index 0000000..d8c46e1 --- /dev/null +++ b/src/OTManager.Web/Components/_Imports.razor @@ -0,0 +1,12 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.FluentUI.AspNetCore.Components +@using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons +@using Microsoft.JSInterop +@using OTManager.Web +@using OTManager.Web.Components diff --git a/src/OTManager.Web/Extensions/ApplicationDependencyInjection.cs b/src/OTManager.Web/Extensions/ApplicationDependencyInjection.cs new file mode 100644 index 0000000..a0a3e86 --- /dev/null +++ b/src/OTManager.Web/Extensions/ApplicationDependencyInjection.cs @@ -0,0 +1,32 @@ +using Microsoft.FluentUI.AspNetCore.Components; + +namespace OTManager.Web.Extensions; + +public static class ApplicationDependencyInjection +{ + public static IServiceCollection AddApplicationDependency(this IServiceCollection services, IConfiguration configuration) + { + services.AddRazorComponents() + .AddInteractiveServerComponents(); + services.AddFluentUIComponents(); + + services.AddLocalization(); + services.AddControllers(); + + return services; + } + + public static IApplicationBuilder AddLocalizationBuilder(this IApplicationBuilder app) + { + string[] supportedCultures = ["es-ES", "en-US"]; + + var localizationOptions = new RequestLocalizationOptions() + .SetDefaultCulture(supportedCultures[0]) + .AddSupportedCultures(supportedCultures) + .AddSupportedUICultures(supportedCultures); + + app.UseRequestLocalization(localizationOptions); + + return app; + } +} diff --git a/src/OTManager.Web/OTManager.Web.csproj b/src/OTManager.Web/OTManager.Web.csproj new file mode 100644 index 0000000..cc618a2 --- /dev/null +++ b/src/OTManager.Web/OTManager.Web.csproj @@ -0,0 +1,19 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + + + diff --git a/src/OTManager.Web/Program.cs b/src/OTManager.Web/Program.cs new file mode 100644 index 0000000..b1bc5df --- /dev/null +++ b/src/OTManager.Web/Program.cs @@ -0,0 +1,59 @@ +using OTManager.Web.ClientServices.Materials; +using OTManager.Web.Components; +using OTManager.Web.Extensions; + +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +// Add services to the container. + +ApplicationDependencyInjection.AddApplicationDependency(builder.Services, builder.Configuration); + +// ToDo: move the logic to extension class +builder.Services.AddHttpClient("ApiClient", client => +{ + client.BaseAddress = new Uri("https://localhost:7257/api/"); +}); + +builder.Services.AddScoped(sp => +{ + var factory = sp.GetRequiredService(); + var client = factory.CreateClient("ApiClient"); + return new MaterialService(client); +}); + +// ^^ move the logic to extension class ^^ + +builder.Services.AddOptions(); +builder.Services.AddAuthorizationCore(); // Use AddAuthorizationCore for Blazor + +var app = builder.Build(); + +app.MapDefaultEndpoints(); + +app.AddLocalizationBuilder(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error", createScopeForErrors: true); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +app.UseHttpsRedirection(); + +app.UseAntiforgery(); + +app.MapStaticAssets(); +app.MapRazorComponents() + .AddInteractiveServerRenderMode(); + +app.UseAuthentication(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/src/OTManager.Web/Properties/launchSettings.json b/src/OTManager.Web/Properties/launchSettings.json new file mode 100644 index 0000000..ae7aa43 --- /dev/null +++ b/src/OTManager.Web/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5200", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7230;http://localhost:5200", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } + } diff --git a/src/OTManager.Web/appsettings.Development.json b/src/OTManager.Web/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/src/OTManager.Web/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/OTManager.Web/appsettings.json b/src/OTManager.Web/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/src/OTManager.Web/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/OTManager.Web/wwwroot/app.css b/src/OTManager.Web/wwwroot/app.css new file mode 100644 index 0000000..f4e0c44 --- /dev/null +++ b/src/OTManager.Web/wwwroot/app.css @@ -0,0 +1,204 @@ +@import '_content/Microsoft.FluentUI.AspNetCore.Components/css/reboot.css'; + +body { + --body-font: "Segoe UI Variable", "Segoe UI", sans-serif; + font-family: var(--body-font); + font-size: var(--type-ramp-base-font-size); + line-height: var(--type-ramp-base-line-height); + margin: 0; +} + +.navmenu-icon { + display: none; +} + +.main { + min-height: calc(100dvh - 86px); + color: var(--neutral-foreground-rest); + align-items: stretch !important; +} + +.body-content { + align-self: stretch; + height: calc(100dvh - 86px) !important; + display: flex; +} + +.content { + padding: 0.5rem 1.5rem; + align-self: stretch !important; + width: 100%; +} + +.manage { + width: 100dvw; +} + +footer { + background: var(--neutral-layer-4); + color: var(--neutral-foreground-rest); + align-items: center; + padding: 10px 10px; +} + + footer a { + color: var(--neutral-foreground-rest); + text-decoration: none; + } + + footer a:focus { + outline: 1px dashed; + outline-offset: 3px; + } + + footer a:hover { + text-decoration: underline; + } + +.alert { + border: 1px dashed var(--accent-fill-rest); + padding: 5px; +} + + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; + margin: 20px 0; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::before { + content: "An error has occurred. " + } + +.loading-progress { + position: relative; + display: block; + width: 8rem; + height: 8rem; + margin: 20vh auto 1rem auto; +} + + .loading-progress circle { + fill: none; + stroke: #e0e0e0; + stroke-width: 0.6rem; + transform-origin: 50% 50%; + transform: rotate(-90deg); + } + + .loading-progress circle:last-child { + stroke: #1b6ec2; + stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; + transition: stroke-dasharray 0.05s ease-in-out; + } + +.loading-progress-text { + position: absolute; + text-align: center; + font-weight: bold; + inset: calc(20vh + 3.25rem) 0 auto 0.2rem; +} + + .loading-progress-text:after { + content: var(--blazor-load-percentage-text, "Loading"); + } + +code { + color: #c02d76; +} + +@media (max-width: 600px) { + .header-gutters { + margin: 0.5rem 3rem 0.5rem 1.5rem !important; + } + + [dir="rtl"] .header-gutters { + margin: 0.5rem 1.5rem 0.5rem 3rem !important; + } + + .main { + flex-direction: column !important; + row-gap: 0 !important; + } + + nav.sitenav { + width: 100%; + height: 100%; + } + + #main-menu { + width: 100% !important; + } + + #main-menu > div:first-child:is(.expander) { + display: none; + } + + .navmenu { + width: 100%; + } + + #navmenu-toggle { + appearance: none; + } + + #navmenu-toggle ~ nav { + display: none; + } + + #navmenu-toggle:checked ~ nav { + display: block; + } + + .navmenu-icon { + cursor: pointer; + z-index: 10; + display: block; + position: absolute; + top: 15px; + left: unset; + right: 20px; + width: 20px; + height: 20px; + border: none; + } + + [dir="rtl"] .navmenu-icon { + left: 20px; + right: unset; + } +} + +.glass-panel { + background: rgba(255,255,255,0.2); + backdrop-filter: blur(10px) saturate(150%); + -webkit-backdrop-filter: blur(10px) saturate(150%); + box-shadow: 0 4px 20px rgba(0,0,0,0.2); + transition: all 0.3s ease-in-out; +} + + .glass-panel:hover { + background: rgba(255,255,255,0.3); + transform: translateY(-2px); + } diff --git a/src/OTManager.Web/wwwroot/css/RHStiles.css b/src/OTManager.Web/wwwroot/css/RHStiles.css new file mode 100644 index 0000000..e02abfc --- /dev/null +++ b/src/OTManager.Web/wwwroot/css/RHStiles.css @@ -0,0 +1 @@ + diff --git a/src/OTManager.WebComp/OTManager.WebComp.csproj b/src/OTManager.WebComp/OTManager.WebComp.csproj new file mode 100644 index 0000000..a6675cc --- /dev/null +++ b/src/OTManager.WebComp/OTManager.WebComp.csproj @@ -0,0 +1,22 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + + + + + + diff --git a/src/OTManager.WebComp/Shared/ContextMenuComponent.razor b/src/OTManager.WebComp/Shared/ContextMenuComponent.razor new file mode 100644 index 0000000..2530854 --- /dev/null +++ b/src/OTManager.WebComp/Shared/ContextMenuComponent.razor @@ -0,0 +1,46 @@ + + + + + + + Oversee + + + + + + + + Edite + + + + + + + + + Delete + + + + + +@code{ + [Parameter] + public EventCallback OnOverseeEvent { get; set; } + [Parameter] + public EventCallback OnEditEvent { get; set; } + [Parameter] + public EventCallback OnDeleteEvent { get; set; } +} \ No newline at end of file diff --git a/src/OTManager.WebComp/_Imports.razor b/src/OTManager.WebComp/_Imports.razor new file mode 100644 index 0000000..02d7739 --- /dev/null +++ b/src/OTManager.WebComp/_Imports.razor @@ -0,0 +1,4 @@ +@using Microsoft.AspNetCore.Components.Web + +@using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons +@using Microsoft.FluentUI.AspNetCore.Components \ No newline at end of file diff --git a/src/OTManager.WebComp/wwwroot/background.png b/src/OTManager.WebComp/wwwroot/background.png new file mode 100644 index 0000000..e15a3bd Binary files /dev/null and b/src/OTManager.WebComp/wwwroot/background.png differ diff --git a/src/OTManager.WebComp/wwwroot/exampleJsInterop.js b/src/OTManager.WebComp/wwwroot/exampleJsInterop.js new file mode 100644 index 0000000..ea8d76a --- /dev/null +++ b/src/OTManager.WebComp/wwwroot/exampleJsInterop.js @@ -0,0 +1,6 @@ +// This is a JavaScript module that is loaded on demand. It can export any number of +// functions, and may import other JavaScript modules if required. + +export function showPrompt(message) { + return prompt(message, 'Type anything here'); +}