From dd7eeb9eea03e0b12ae1da6bfcf388625640a3fa Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Mon, 9 Jun 2025 23:54:37 +0300 Subject: [PATCH] Seeding, cleanup, fix nginx proxying --- nginx/nginx.conf | 11 +- .../Imprink.Application.csproj | 6 - .../{Query => }/GetProductsHandler.cs | 2 +- .../{Product => Products}/Category.cs | 0 .../Entities/{Product => Products}/Product.cs | 0 .../{Product => Products}/ProductVariant.cs | 0 src/Imprink.Domain/Imprink.Domain.csproj | 4 - .../{ => Products}/ICategoryRepository.cs | 0 .../{ => Products}/IProductRepository.cs | 0 .../IProductVariantRepository.cs | 0 .../{ => Users}/IRoleRepository.cs | 0 .../{ => Users}/IUserRepository.cs | 11 - .../{ => Users}/IUserRoleRepository.cs | 0 .../Configuration/Users/RoleConfiguration.cs | 1 - ...> 20250609202250_InitialSetup.Designer.cs} | 2 +- ...etup.cs => 20250609202250_InitialSetup.cs} | 0 .../{ => Products}/CategoryRepository.cs | 0 .../{ => Products}/ProductRepository.cs | 65 ++-- .../ProductVariantRepository.cs | 0 .../{ => Users}/RoleRepository.cs | 0 .../{ => Users}/UserRepository.cs | 52 +-- .../{ => Users}/UserRoleRepository.cs | 0 .../Products/CategoriesController.cs | 33 +- .../Products/ProductVariantsController.cs | 34 +- .../Products/ProductsController.cs | 57 +-- .../Controllers/Products/SeedingController.cs | 17 + .../Controllers/Users/UserController.cs | 14 +- src/Imprink.WebApi/Imprink.WebApi.csproj | 4 - .../Middleware/RequestTimingMiddleware.cs | 4 +- src/Imprink.WebApi/Program.cs | 6 +- src/Imprink.WebApi/Seeder.cs | 349 ++++++++++++++++++ src/Imprink.WebApi/Startup.cs | 7 +- 32 files changed, 439 insertions(+), 240 deletions(-) rename src/Imprink.Application/Products/{Query => }/GetProductsHandler.cs (97%) rename src/Imprink.Domain/Entities/{Product => Products}/Category.cs (100%) rename src/Imprink.Domain/Entities/{Product => Products}/Product.cs (100%) rename src/Imprink.Domain/Entities/{Product => Products}/ProductVariant.cs (100%) rename src/Imprink.Domain/Repositories/{ => Products}/ICategoryRepository.cs (100%) rename src/Imprink.Domain/Repositories/{ => Products}/IProductRepository.cs (100%) rename src/Imprink.Domain/Repositories/{ => Products}/IProductVariantRepository.cs (100%) rename src/Imprink.Domain/Repositories/{ => Users}/IRoleRepository.cs (100%) rename src/Imprink.Domain/Repositories/{ => Users}/IUserRepository.cs (62%) rename src/Imprink.Domain/Repositories/{ => Users}/IUserRoleRepository.cs (100%) rename src/Imprink.Infrastructure/Migrations/{20250608204903_InitialSetup.Designer.cs => 20250609202250_InitialSetup.Designer.cs} (99%) rename src/Imprink.Infrastructure/Migrations/{20250608204903_InitialSetup.cs => 20250609202250_InitialSetup.cs} (100%) rename src/Imprink.Infrastructure/Repositories/{ => Products}/CategoryRepository.cs (100%) rename src/Imprink.Infrastructure/Repositories/{ => Products}/ProductRepository.cs (93%) rename src/Imprink.Infrastructure/Repositories/{ => Products}/ProductVariantRepository.cs (100%) rename src/Imprink.Infrastructure/Repositories/{ => Users}/RoleRepository.cs (100%) rename src/Imprink.Infrastructure/Repositories/{ => Users}/UserRepository.cs (64%) rename src/Imprink.Infrastructure/Repositories/{ => Users}/UserRoleRepository.cs (100%) create mode 100644 src/Imprink.WebApi/Controllers/Products/SeedingController.cs create mode 100644 src/Imprink.WebApi/Seeder.cs diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 1465088..f8b7a8b 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -43,7 +43,16 @@ http { add_header X-Content-Type-Options nosniff always; location /api/ { - proxy_pass http://webapi/; + proxy_pass http://webapi; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /swagger { + proxy_pass http://webapi; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; diff --git a/src/Imprink.Application/Imprink.Application.csproj b/src/Imprink.Application/Imprink.Application.csproj index 892e1ef..7d1c2ec 100644 --- a/src/Imprink.Application/Imprink.Application.csproj +++ b/src/Imprink.Application/Imprink.Application.csproj @@ -17,10 +17,4 @@ - - - - - - diff --git a/src/Imprink.Application/Products/Query/GetProductsHandler.cs b/src/Imprink.Application/Products/GetProductsHandler.cs similarity index 97% rename from src/Imprink.Application/Products/Query/GetProductsHandler.cs rename to src/Imprink.Application/Products/GetProductsHandler.cs index de8cfaa..48b5cb0 100644 --- a/src/Imprink.Application/Products/Query/GetProductsHandler.cs +++ b/src/Imprink.Application/Products/GetProductsHandler.cs @@ -2,7 +2,7 @@ using Imprink.Application.Products.Dtos; using Imprink.Domain.Common.Models; using MediatR; -namespace Imprink.Application.Products.Query; +namespace Imprink.Application.Products; public class GetProductsQuery : IRequest> { diff --git a/src/Imprink.Domain/Entities/Product/Category.cs b/src/Imprink.Domain/Entities/Products/Category.cs similarity index 100% rename from src/Imprink.Domain/Entities/Product/Category.cs rename to src/Imprink.Domain/Entities/Products/Category.cs diff --git a/src/Imprink.Domain/Entities/Product/Product.cs b/src/Imprink.Domain/Entities/Products/Product.cs similarity index 100% rename from src/Imprink.Domain/Entities/Product/Product.cs rename to src/Imprink.Domain/Entities/Products/Product.cs diff --git a/src/Imprink.Domain/Entities/Product/ProductVariant.cs b/src/Imprink.Domain/Entities/Products/ProductVariant.cs similarity index 100% rename from src/Imprink.Domain/Entities/Product/ProductVariant.cs rename to src/Imprink.Domain/Entities/Products/ProductVariant.cs diff --git a/src/Imprink.Domain/Imprink.Domain.csproj b/src/Imprink.Domain/Imprink.Domain.csproj index 17f66ee..3302df9 100644 --- a/src/Imprink.Domain/Imprink.Domain.csproj +++ b/src/Imprink.Domain/Imprink.Domain.csproj @@ -6,10 +6,6 @@ enable - - - - diff --git a/src/Imprink.Domain/Repositories/ICategoryRepository.cs b/src/Imprink.Domain/Repositories/Products/ICategoryRepository.cs similarity index 100% rename from src/Imprink.Domain/Repositories/ICategoryRepository.cs rename to src/Imprink.Domain/Repositories/Products/ICategoryRepository.cs diff --git a/src/Imprink.Domain/Repositories/IProductRepository.cs b/src/Imprink.Domain/Repositories/Products/IProductRepository.cs similarity index 100% rename from src/Imprink.Domain/Repositories/IProductRepository.cs rename to src/Imprink.Domain/Repositories/Products/IProductRepository.cs diff --git a/src/Imprink.Domain/Repositories/IProductVariantRepository.cs b/src/Imprink.Domain/Repositories/Products/IProductVariantRepository.cs similarity index 100% rename from src/Imprink.Domain/Repositories/IProductVariantRepository.cs rename to src/Imprink.Domain/Repositories/Products/IProductVariantRepository.cs diff --git a/src/Imprink.Domain/Repositories/IRoleRepository.cs b/src/Imprink.Domain/Repositories/Users/IRoleRepository.cs similarity index 100% rename from src/Imprink.Domain/Repositories/IRoleRepository.cs rename to src/Imprink.Domain/Repositories/Users/IRoleRepository.cs diff --git a/src/Imprink.Domain/Repositories/IUserRepository.cs b/src/Imprink.Domain/Repositories/Users/IUserRepository.cs similarity index 62% rename from src/Imprink.Domain/Repositories/IUserRepository.cs rename to src/Imprink.Domain/Repositories/Users/IUserRepository.cs index 59b6f5d..e5a0231 100644 --- a/src/Imprink.Domain/Repositories/IUserRepository.cs +++ b/src/Imprink.Domain/Repositories/Users/IUserRepository.cs @@ -10,18 +10,7 @@ public interface IUserRepository Task GetUserByEmailAsync(string email, CancellationToken cancellationToken = default); Task> GetAllUsersAsync(CancellationToken cancellationToken = default); Task> GetActiveUsersAsync(CancellationToken cancellationToken = default); - - Task CreateUserAsync(User user, CancellationToken cancellationToken = default); - Task UpdateUserAsync(User user, CancellationToken cancellationToken = default); - Task DeleteUserAsync(string userId, CancellationToken cancellationToken = default); - Task UserExistsAsync(string userId, CancellationToken cancellationToken = default); - Task EmailExistsAsync(string email, CancellationToken cancellationToken = default); - - Task ActivateUserAsync(string userId, CancellationToken cancellationToken = default); - Task DeactivateUserAsync(string userId, CancellationToken cancellationToken = default); - Task> GetUsersByRoleAsync(Guid roleId, CancellationToken cancellationToken = default); - Task GetUserWithAllRelatedDataAsync(string userId, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Imprink.Domain/Repositories/IUserRoleRepository.cs b/src/Imprink.Domain/Repositories/Users/IUserRoleRepository.cs similarity index 100% rename from src/Imprink.Domain/Repositories/IUserRoleRepository.cs rename to src/Imprink.Domain/Repositories/Users/IUserRoleRepository.cs diff --git a/src/Imprink.Infrastructure/Configuration/Users/RoleConfiguration.cs b/src/Imprink.Infrastructure/Configuration/Users/RoleConfiguration.cs index d1acb4b..f5667d9 100644 --- a/src/Imprink.Infrastructure/Configuration/Users/RoleConfiguration.cs +++ b/src/Imprink.Infrastructure/Configuration/Users/RoleConfiguration.cs @@ -23,7 +23,6 @@ public class RoleConfiguration : IEntityTypeConfiguration .HasDatabaseName("IX_Role_RoleName"); builder.HasData( - new Role { Id = Guid.Parse("11111111-1111-1111-1111-111111111111"), RoleName = "User" }, new Role { Id = Guid.Parse("22222222-2222-2222-2222-222222222222"), RoleName = "Merchant" }, new Role { Id = Guid.Parse("33333333-3333-3333-3333-333333333333"), RoleName = "Admin" } ); diff --git a/src/Imprink.Infrastructure/Migrations/20250608204903_InitialSetup.Designer.cs b/src/Imprink.Infrastructure/Migrations/20250609202250_InitialSetup.Designer.cs similarity index 99% rename from src/Imprink.Infrastructure/Migrations/20250608204903_InitialSetup.Designer.cs rename to src/Imprink.Infrastructure/Migrations/20250609202250_InitialSetup.Designer.cs index c07716e..8d719f1 100644 --- a/src/Imprink.Infrastructure/Migrations/20250608204903_InitialSetup.Designer.cs +++ b/src/Imprink.Infrastructure/Migrations/20250609202250_InitialSetup.Designer.cs @@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Imprink.Infrastructure.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20250608204903_InitialSetup")] + [Migration("20250609202250_InitialSetup")] partial class InitialSetup { /// diff --git a/src/Imprink.Infrastructure/Migrations/20250608204903_InitialSetup.cs b/src/Imprink.Infrastructure/Migrations/20250609202250_InitialSetup.cs similarity index 100% rename from src/Imprink.Infrastructure/Migrations/20250608204903_InitialSetup.cs rename to src/Imprink.Infrastructure/Migrations/20250609202250_InitialSetup.cs diff --git a/src/Imprink.Infrastructure/Repositories/CategoryRepository.cs b/src/Imprink.Infrastructure/Repositories/Products/CategoryRepository.cs similarity index 100% rename from src/Imprink.Infrastructure/Repositories/CategoryRepository.cs rename to src/Imprink.Infrastructure/Repositories/Products/CategoryRepository.cs diff --git a/src/Imprink.Infrastructure/Repositories/ProductRepository.cs b/src/Imprink.Infrastructure/Repositories/Products/ProductRepository.cs similarity index 93% rename from src/Imprink.Infrastructure/Repositories/ProductRepository.cs rename to src/Imprink.Infrastructure/Repositories/Products/ProductRepository.cs index 742184e..f0bf4c7 100644 --- a/src/Imprink.Infrastructure/Repositories/ProductRepository.cs +++ b/src/Imprink.Infrastructure/Repositories/Products/ProductRepository.cs @@ -8,35 +8,9 @@ namespace Imprink.Infrastructure.Repositories; public class ProductRepository(ApplicationDbContext context) : IProductRepository { - public async Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default) - { - return await context.Products - .FirstOrDefaultAsync(p => p.Id == id, cancellationToken); - } - - public async Task GetByIdWithVariantsAsync(Guid id, CancellationToken cancellationToken = default) - { - return await context.Products - .Include(p => p.ProductVariants.Where(pv => pv.IsActive)) - .FirstOrDefaultAsync(p => p.Id == id, cancellationToken); - } - - public async Task GetByIdWithCategoryAsync(Guid id, CancellationToken cancellationToken = default) - { - return await context.Products - .Include(p => p.Category) - .FirstOrDefaultAsync(p => p.Id == id, cancellationToken); - } - - public async Task GetByIdFullAsync(Guid id, CancellationToken cancellationToken = default) - { - return await context.Products - .Include(p => p.Category) - .Include(p => p.ProductVariants.Where(pv => pv.IsActive)) - .FirstOrDefaultAsync(p => p.Id == id, cancellationToken); - } - - public async Task> GetPagedAsync(ProductFilterParameters filterParameters, CancellationToken cancellationToken = default) + public async Task> GetPagedAsync( + ProductFilterParameters filterParameters, + CancellationToken cancellationToken = default) { var query = context.Products .Include(p => p.Category) @@ -49,8 +23,9 @@ public class ProductRepository(ApplicationDbContext context) : IProductRepositor if (!string.IsNullOrEmpty(filterParameters.SearchTerm)) { - query = query.Where(p => p.Name.Contains(filterParameters.SearchTerm) || - (p.Description != null && p.Description.Contains(filterParameters.SearchTerm))); + query = query.Where( + p => p.Name.Contains(filterParameters.SearchTerm) + || (p.Description != null && p.Description.Contains(filterParameters.SearchTerm))); } if (filterParameters.CategoryId.HasValue) @@ -101,6 +76,34 @@ public class ProductRepository(ApplicationDbContext context) : IProductRepositor PageSize = filterParameters.PageSize }; } + + public async Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default) + { + return await context.Products + .FirstOrDefaultAsync(p => p.Id == id, cancellationToken); + } + + public async Task GetByIdWithVariantsAsync(Guid id, CancellationToken cancellationToken = default) + { + return await context.Products + .Include(p => p.ProductVariants.Where(pv => pv.IsActive)) + .FirstOrDefaultAsync(p => p.Id == id, cancellationToken); + } + + public async Task GetByIdWithCategoryAsync(Guid id, CancellationToken cancellationToken = default) + { + return await context.Products + .Include(p => p.Category) + .FirstOrDefaultAsync(p => p.Id == id, cancellationToken); + } + + public async Task GetByIdFullAsync(Guid id, CancellationToken cancellationToken = default) + { + return await context.Products + .Include(p => p.Category) + .Include(p => p.ProductVariants.Where(pv => pv.IsActive)) + .FirstOrDefaultAsync(p => p.Id == id, cancellationToken); + } public async Task> GetByCategoryAsync(Guid categoryId, CancellationToken cancellationToken = default) { diff --git a/src/Imprink.Infrastructure/Repositories/ProductVariantRepository.cs b/src/Imprink.Infrastructure/Repositories/Products/ProductVariantRepository.cs similarity index 100% rename from src/Imprink.Infrastructure/Repositories/ProductVariantRepository.cs rename to src/Imprink.Infrastructure/Repositories/Products/ProductVariantRepository.cs diff --git a/src/Imprink.Infrastructure/Repositories/RoleRepository.cs b/src/Imprink.Infrastructure/Repositories/Users/RoleRepository.cs similarity index 100% rename from src/Imprink.Infrastructure/Repositories/RoleRepository.cs rename to src/Imprink.Infrastructure/Repositories/Users/RoleRepository.cs diff --git a/src/Imprink.Infrastructure/Repositories/UserRepository.cs b/src/Imprink.Infrastructure/Repositories/Users/UserRepository.cs similarity index 64% rename from src/Imprink.Infrastructure/Repositories/UserRepository.cs rename to src/Imprink.Infrastructure/Repositories/Users/UserRepository.cs index fb6e1d0..abf12d9 100644 --- a/src/Imprink.Infrastructure/Repositories/UserRepository.cs +++ b/src/Imprink.Infrastructure/Repositories/Users/UserRepository.cs @@ -68,62 +68,12 @@ public class UserRepository(ApplicationDbContext context) : IUserRepository .ToListAsync(cancellationToken); } - public async Task CreateUserAsync(User user, CancellationToken cancellationToken = default) - { - context.Users.Add(user); - await context.SaveChangesAsync(cancellationToken); - return user; - } - - public async Task UpdateUserAsync(User user, CancellationToken cancellationToken = default) - { - context.Users.Update(user); - await context.SaveChangesAsync(cancellationToken); - return user; - } - - public async Task DeleteUserAsync(string userId, CancellationToken cancellationToken = default) - { - var user = await context.Users.FindAsync(new object[] { userId }, cancellationToken); - if (user != null) - { - context.Users.Remove(user); - await context.SaveChangesAsync(cancellationToken); - } - } - public async Task UserExistsAsync(string userId, CancellationToken cancellationToken = default) { return await context.Users .AnyAsync(u => u.Id == userId, cancellationToken); } - - public async Task EmailExistsAsync(string email, CancellationToken cancellationToken = default) - { - return await context.Users - .AnyAsync(u => u.Email == email, cancellationToken); - } - - public async Task ActivateUserAsync(string userId, CancellationToken cancellationToken = default) - { - var user = await context.Users.FindAsync(new object[] { userId }, cancellationToken); - if (user == null) return false; - - user.IsActive = true; - await context.SaveChangesAsync(cancellationToken); - return true; - } - - public async Task DeactivateUserAsync(string userId, CancellationToken cancellationToken = default) - { - var user = await context.Users.FindAsync(new object[] { userId }, cancellationToken); - if (user == null) return false; - - user.IsActive = false; - await context.SaveChangesAsync(cancellationToken); - return true; - } - + public async Task> GetUsersByRoleAsync(Guid roleId, CancellationToken cancellationToken = default) { return await context.Users diff --git a/src/Imprink.Infrastructure/Repositories/UserRoleRepository.cs b/src/Imprink.Infrastructure/Repositories/Users/UserRoleRepository.cs similarity index 100% rename from src/Imprink.Infrastructure/Repositories/UserRoleRepository.cs rename to src/Imprink.Infrastructure/Repositories/Users/UserRoleRepository.cs diff --git a/src/Imprink.WebApi/Controllers/Products/CategoriesController.cs b/src/Imprink.WebApi/Controllers/Products/CategoriesController.cs index fb6d872..65be479 100644 --- a/src/Imprink.WebApi/Controllers/Products/CategoriesController.cs +++ b/src/Imprink.WebApi/Controllers/Products/CategoriesController.cs @@ -1,5 +1,3 @@ -using Imprink.Application.Products.Create; -using Imprink.Application.Products.Delete; using Imprink.Application.Products.Dtos; using Imprink.Application.Products.Query; using MediatR; @@ -8,40 +6,13 @@ using Microsoft.AspNetCore.Mvc; namespace Imprink.WebApi.Controllers; [ApiController] -[Route("api/[controller]")] +[Route("api/products/categories")] public class CategoriesController(IMediator mediator) : ControllerBase { [HttpGet] - public async Task>> GetCategories( - [FromQuery] bool? isActive = null, - [FromQuery] bool rootCategoriesOnly = false) + public async Task>> GetCategories([FromQuery] GetCategoriesQuery query) { - var query = new GetCategoriesQuery - { - IsActive = isActive, - RootCategoriesOnly = rootCategoriesOnly - }; - var result = await mediator.Send(query); return Ok(result); } - - [HttpPost] - public async Task> CreateCategory([FromBody] CreateCategoryCommand command) - { - var result = await mediator.Send(command); - return CreatedAtAction(nameof(CreateCategory), new { id = result.Id }, result); - } - - [HttpDelete("{id:guid}")] - public async Task DeleteCategory(Guid id) - { - var command = new DeleteCategoryCommand { Id = id }; - var result = await mediator.Send(command); - - if (!result) - return NotFound(); - - return NoContent(); - } } \ No newline at end of file diff --git a/src/Imprink.WebApi/Controllers/Products/ProductVariantsController.cs b/src/Imprink.WebApi/Controllers/Products/ProductVariantsController.cs index 7f2835b..1bacafc 100644 --- a/src/Imprink.WebApi/Controllers/Products/ProductVariantsController.cs +++ b/src/Imprink.WebApi/Controllers/Products/ProductVariantsController.cs @@ -1,5 +1,3 @@ -using Imprink.Application.Products.Create; -using Imprink.Application.Products.Delete; using Imprink.Application.Products.Dtos; using Imprink.Application.Products.Query; using MediatR; @@ -8,42 +6,14 @@ using Microsoft.AspNetCore.Mvc; namespace Imprink.WebApi.Controllers; [ApiController] -[Route("api/[controller]")] +[Route("/api/products/variants")] public class ProductVariantsController(IMediator mediator) : ControllerBase { [HttpGet] public async Task>> GetProductVariants( - [FromQuery] Guid? productId = null, - [FromQuery] bool? isActive = null, - [FromQuery] bool inStockOnly = false) + [FromQuery] GetProductVariantsQuery query) { - var query = new GetProductVariantsQuery - { - ProductId = productId, - IsActive = isActive, - InStockOnly = inStockOnly - }; - var result = await mediator.Send(query); return Ok(result); } - - [HttpPost] - public async Task> CreateProductVariant([FromBody] CreateProductVariantCommand command) - { - var result = await mediator.Send(command); - return CreatedAtAction(nameof(CreateProductVariant), new { id = result.Id }, result); - } - - [HttpDelete("{id:guid}")] - public async Task DeleteProductVariant(Guid id) - { - var command = new DeleteProductVariantCommand { Id = id }; - var result = await mediator.Send(command); - - if (!result) - return NotFound(); - - return NoContent(); - } } \ No newline at end of file diff --git a/src/Imprink.WebApi/Controllers/Products/ProductsController.cs b/src/Imprink.WebApi/Controllers/Products/ProductsController.cs index 00024ea..da950d7 100644 --- a/src/Imprink.WebApi/Controllers/Products/ProductsController.cs +++ b/src/Imprink.WebApi/Controllers/Products/ProductsController.cs @@ -1,66 +1,23 @@ -using Imprink.Application.Products.Create; -using Imprink.Application.Products.Delete; +using Imprink.Application.Products; using Imprink.Application.Products.Dtos; -using Imprink.Application.Products.Query; using Imprink.Domain.Common.Models; using MediatR; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Imprink.WebApi.Controllers; [ApiController] -[Route("api/[controller]")] +[Route("/api/products")] public class ProductsController(IMediator mediator) : ControllerBase { + [HttpGet] + [AllowAnonymous] public async Task>> GetProducts( - [FromQuery] int pageNumber = 1, - [FromQuery] int pageSize = 10, - [FromQuery] string? searchTerm = null, - [FromQuery] Guid? categoryId = null, - [FromQuery] decimal? minPrice = null, - [FromQuery] decimal? maxPrice = null, - [FromQuery] bool? isActive = true, - [FromQuery] bool? isCustomizable = null, - [FromQuery] string sortBy = "Name", - [FromQuery] string sortDirection = "ASC") + [FromQuery] ProductFilterParameters filterParameters) { - var filterParameters = new ProductFilterParameters - { - PageNumber = pageNumber, - PageSize = pageSize, - SearchTerm = searchTerm, - CategoryId = categoryId, - MinPrice = minPrice, - MaxPrice = maxPrice, - IsActive = isActive, - IsCustomizable = isCustomizable, - SortBy = sortBy, - SortDirection = sortDirection - }; - - var query = new GetProductsQuery { FilterParameters = filterParameters }; - var result = await mediator.Send(query); - + var result = await mediator.Send(new GetProductsQuery { FilterParameters = filterParameters }); return Ok(result); } - - [HttpPost] - public async Task> CreateProduct([FromBody] CreateProductCommand command) - { - var result = await mediator.Send(command); - return CreatedAtAction(nameof(CreateProduct), new { id = result.Id }, result); - } - - [HttpDelete("{id:guid}")] - public async Task DeleteProduct(Guid id) - { - var command = new DeleteProductCommand { Id = id }; - var result = await mediator.Send(command); - - if (!result) - return NotFound(); - - return NoContent(); - } } \ No newline at end of file diff --git a/src/Imprink.WebApi/Controllers/Products/SeedingController.cs b/src/Imprink.WebApi/Controllers/Products/SeedingController.cs new file mode 100644 index 0000000..009b765 --- /dev/null +++ b/src/Imprink.WebApi/Controllers/Products/SeedingController.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Imprink.WebApi.Controllers; + +[ApiController] +[Route("/api/products/seed")] +public class SeedingController(Seeder seeder) : ControllerBase +{ + [HttpGet] + [Authorize(Roles = "Admin")] + public async Task> Seed() + { + await seeder.SeedAsync(); + return Ok(); + } +} \ No newline at end of file diff --git a/src/Imprink.WebApi/Controllers/Users/UserController.cs b/src/Imprink.WebApi/Controllers/Users/UserController.cs index 89615b0..f41e26e 100644 --- a/src/Imprink.WebApi/Controllers/Users/UserController.cs +++ b/src/Imprink.WebApi/Controllers/Users/UserController.cs @@ -8,22 +8,22 @@ using Microsoft.AspNetCore.Mvc; namespace Imprink.WebApi.Controllers.Users; [ApiController] -[Route("/users")] +[Route("/api/users")] public class UserController(IMediator mediator) : ControllerBase { [Authorize] [HttpPost("sync")] public async Task Sync() { - var enumerable = User.Claims as Claim[] ?? User.Claims.ToArray(); + var claims = User.Claims as Claim[] ?? User.Claims.ToArray(); var auth0User = new Auth0User { - Sub = enumerable.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value ?? string.Empty, - Name = enumerable.FirstOrDefault(c => c.Type == "name")?.Value ?? string.Empty, - Nickname = enumerable.FirstOrDefault(c => c.Type == "nickname")?.Value ?? string.Empty, - Email = enumerable.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value ?? string.Empty, - EmailVerified = bool.TryParse(enumerable.FirstOrDefault(c => c.Type == "email_verified")?.Value, out var emailVerified) && emailVerified + Sub = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value ?? string.Empty, + Name = claims.FirstOrDefault(c => c.Type == "name")?.Value ?? string.Empty, + Nickname = claims.FirstOrDefault(c => c.Type == "nickname")?.Value ?? string.Empty, + Email = claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value ?? string.Empty, + EmailVerified = bool.TryParse(claims.FirstOrDefault(c => c.Type == "email_verified")?.Value, out var emailVerified) && emailVerified }; await mediator.Send(new SyncUserCommand(auth0User)); diff --git a/src/Imprink.WebApi/Imprink.WebApi.csproj b/src/Imprink.WebApi/Imprink.WebApi.csproj index f0f6e50..a49bd5c 100644 --- a/src/Imprink.WebApi/Imprink.WebApi.csproj +++ b/src/Imprink.WebApi/Imprink.WebApi.csproj @@ -31,8 +31,4 @@ - - - - diff --git a/src/Imprink.WebApi/Middleware/RequestTimingMiddleware.cs b/src/Imprink.WebApi/Middleware/RequestTimingMiddleware.cs index 5cd5dde..cd81e30 100644 --- a/src/Imprink.WebApi/Middleware/RequestTimingMiddleware.cs +++ b/src/Imprink.WebApi/Middleware/RequestTimingMiddleware.cs @@ -30,8 +30,8 @@ public class RequestTimingMiddleware(RequestDelegate next, ILogger(); + builder.UseMiddleware(); } } \ No newline at end of file diff --git a/src/Imprink.WebApi/Program.cs b/src/Imprink.WebApi/Program.cs index c536a6a..5d86d4b 100644 --- a/src/Imprink.WebApi/Program.cs +++ b/src/Imprink.WebApi/Program.cs @@ -1,13 +1,9 @@ using Imprink.WebApi; using Serilog; -using Serilog.Events; var builder = WebApplication.CreateBuilder(args); -Log.Logger = new LoggerConfiguration() - .ReadFrom.Configuration(builder.Configuration) - .CreateLogger(); - +Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(builder.Configuration).CreateLogger(); builder.Host.UseSerilog(); Startup.ConfigureServices(builder); diff --git a/src/Imprink.WebApi/Seeder.cs b/src/Imprink.WebApi/Seeder.cs new file mode 100644 index 0000000..14f4016 --- /dev/null +++ b/src/Imprink.WebApi/Seeder.cs @@ -0,0 +1,349 @@ +using Imprink.Domain.Entities.Product; +using Imprink.Infrastructure.Database; +using Microsoft.EntityFrameworkCore; + +namespace Imprink.WebApi; + +public class Seeder(ApplicationDbContext context) +{ + private readonly Random _random = new(); + + private readonly string[] _categoryImages = + [ + "https://images.unsplash.com/photo-1441986300917-64674bd600d8?w=500", + "https://images.unsplash.com/photo-1560472354-b33ff0c44a43?w=500", + "https://images.unsplash.com/photo-1586953208448-b95a79798f07?w=500", + "https://images.unsplash.com/photo-1503602642458-232111445657?w=500" + ]; + + private readonly string[] _textileImages = + [ + "https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=500", + "https://images.unsplash.com/photo-1583743814966-8936f37f4ad2?w=500", + "https://images.unsplash.com/photo-1571945153237-4929e783af4a?w=500", + "https://images.unsplash.com/photo-1618354691373-d851c5c3a990?w=500", + "https://images.unsplash.com/photo-1576566588028-4147f3842f27?w=500" + ]; + + private readonly string[] _hardSurfaceImages = + [ + "https://images.unsplash.com/photo-1586023492125-27b2c045efd7?w=500", + "https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=500", + "https://images.unsplash.com/photo-1544966503-7cc5ac882d2e?w=500", + "https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=500" + ]; + + private readonly string[] _paperImages = + [ + "https://images.unsplash.com/photo-1586281010691-79ab3d0f2102?w=500", + "https://images.unsplash.com/photo-1594736797933-d0401ba2fe65?w=500", + "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=500", + "https://images.unsplash.com/photo-1584464491033-06628f3a6b7b?w=500" + ]; + + public async Task SeedAsync() + { + try + { + Console.WriteLine("Starting database seeding..."); + + var categories = await SeedCategories(); + Console.WriteLine($"Created {categories.Count} categories"); + + var products = await SeedProducts(categories); + Console.WriteLine($"Created {products.Count} products"); + + await SeedProductVariants(products); + Console.WriteLine("Created product variants"); + + Console.WriteLine("Database seeding completed successfully!"); + } + catch (Exception ex) + { + Console.WriteLine($"Error during seeding: {ex.Message}"); + throw; + } + } + + private async Task> SeedCategories() + { + var categories = new List(); + var now = DateTime.UtcNow; + + var categoryData = new Dictionary> + { + ["Textile"] = + [ + "T-Shirts", "Hoodies", "Tank Tops", "Long Sleeves", "Polo Shirts", + "Sweatshirts", "Jackets", "Caps & Hats", "Bags", "Towels", + "Aprons", "Baby Clothing", "Youth Clothing", "Women's Apparel", "Men's Apparel" + ], + ["Hard Surfaces"] = + [ + "Mugs", "Water Bottles", "Phone Cases", "Laptop Cases", "Keychains", + "Mouse Pads", "Coasters", "Picture Frames", "Awards & Trophies", "Signs", + "Magnets", "Buttons & Pins", "Clocks", "Tiles", "Metal Prints" + ], + ["Paper"] = + [ + "Business Cards", "Flyers", "Brochures", "Posters", "Banners", + "Stickers", "Labels", "Notebooks", "Calendars", "Greeting Cards", + "Postcards", "Bookmarks", "Menu Cards", "Invitations", "Certificates" + ] + }; + + var existingMainCategories = await context.Set() + .Where(c => c.ParentCategoryId == null) + .ToListAsync(); + + var mainCategoryMap = existingMainCategories.ToDictionary(c => c.Name, c => c); + + foreach (var mainCategoryName in categoryData.Keys) + { + Category mainCategory; + if (mainCategoryMap.TryGetValue(mainCategoryName, out var value)) + { + mainCategory = value; + } + else + { + mainCategory = new Category + { + Id = Guid.NewGuid(), + Name = mainCategoryName, + Description = $"{mainCategoryName} products and materials", + ImageUrl = _categoryImages[_random.Next(_categoryImages.Length)], + SortOrder = categories.Count + 1, + IsActive = true, + CreatedAt = now, + ModifiedAt = now, + CreatedBy = "seeder@system.com", + ModifiedBy = "seeder@system.com" + }; + context.Set().Add(mainCategory); + await context.SaveChangesAsync(); + } + + categories.Add(mainCategory); + + var subcategoryNames = categoryData[mainCategoryName]; + for (var i = 0; i < subcategoryNames.Count; i++) + { + var subcategory = new Category + { + Id = Guid.NewGuid(), + Name = subcategoryNames[i], + Description = $"High-quality {subcategoryNames[i].ToLower()} for custom printing", + ImageUrl = GetImageForCategory(mainCategoryName), + SortOrder = i + 1, + IsActive = true, + ParentCategoryId = mainCategory.Id, + CreatedAt = now, + ModifiedAt = now, + CreatedBy = "seeder@system.com", + ModifiedBy = "seeder@system.com" + }; + + categories.Add(subcategory); + context.Set().Add(subcategory); + } + } + + await context.SaveChangesAsync(); + return categories; + } + + private async Task> SeedProducts(List categories) + { + var products = new List(); + var now = DateTime.UtcNow; + + var subcategories = categories.Where(c => c.ParentCategoryId.HasValue).ToList(); + + foreach (var category in subcategories) + { + var productCount = _random.Next(15, 35); + + for (var i = 0; i < productCount; i++) + { + var product = new Product + { + Id = Guid.NewGuid(), + Name = GenerateProductName(category.Name, i + 1), + Description = GenerateProductDescription(category.Name), + BasePrice = GenerateBasePrice(category.Name), + IsCustomizable = _random.NextDouble() > 0.2, + IsActive = _random.NextDouble() > 0.05, + ImageUrl = GetImageForCategory(GetMainCategoryName(category)), + CategoryId = category.Id, + Category = category, + CreatedAt = now.AddDays(-_random.Next(0, 365)), + ModifiedAt = now.AddDays(-_random.Next(0, 30)), + CreatedBy = "seeder@system.com", + ModifiedBy = "seeder@system.com" + }; + + products.Add(product); + context.Set().Add(product); + } + } + + await context.SaveChangesAsync(); + return products; + } + + private async Task SeedProductVariants(List products) + { + var now = DateTime.UtcNow; + var skuCounter = 1; + + foreach (var variant in from product in products let mainCategory = GetMainCategoryName(product.Category) select GenerateVariantsForProduct(product, mainCategory, ref skuCounter, now) into variants from variant in variants select variant) + { + context.Set().Add(variant); + } + + await context.SaveChangesAsync(); + } + + private List GenerateVariantsForProduct(Product product, string mainCategory, ref int skuCounter, DateTime now) + { + var variants = new List(); + + var sizes = GetSizesForCategory(mainCategory); + var colors = GetColorsForCategory(); + + var variantCount = Math.Min(_random.Next(3, 9), sizes.Count * colors.Count); + var usedCombinations = new HashSet(); + + for (var i = 0; i < variantCount; i++) + { + string size, color; + string combination; + + do + { + size = sizes[_random.Next(sizes.Count)]; + color = colors[_random.Next(colors.Count)]; + combination = $"{size}-{color}"; + } while (usedCombinations.Contains(combination)); + + usedCombinations.Add(combination); + + var variant = new ProductVariant + { + Id = Guid.NewGuid(), + ProductId = product.Id, + Product = product, + Size = size, + Color = color, + Price = product.BasePrice + (_random.Next(-500, 1500) / 100m), + ImageUrl = GetImageForCategory(mainCategory), + Sku = $"SKU{skuCounter:D6}", + StockQuantity = _random.Next(0, 500), + IsActive = _random.NextDouble() > 0.03, + CreatedAt = now.AddDays(-_random.Next(0, 300)), + ModifiedAt = now.AddDays(-_random.Next(0, 30)), + CreatedBy = "seeder@system.com", + ModifiedBy = "seeder@system.com" + }; + + variants.Add(variant); + skuCounter++; + } + + return variants; + } + + private string GenerateProductName(string categoryName, int index) + { + var adjectives = new[] { "Premium", "Classic", "Modern", "Vintage", "Professional", "Casual", "Deluxe", "Standard", "Economy", "Luxury" }; + var materials = new Dictionary + { + ["T-Shirts"] = ["Cotton", "Blend", "Organic", "Bamboo", "Performance"], + ["Mugs"] = ["Ceramic", "Stainless", "Glass", "Enamel", "Porcelain"], + ["Business Cards"] = ["Matte", "Glossy", "Textured", "Recycled", "Premium"] + }; + + var adjective = adjectives[_random.Next(adjectives.Length)]; + var material = materials.TryGetValue(categoryName, out var value) ? value[_random.Next(value.Length)] : + "Quality"; + + return $"{adjective} {material} {categoryName.TrimEnd('s')} #{index:D3}"; + } + + private string GenerateProductDescription(string categoryName) + { + var descriptions = new[] + { + $"High-quality {categoryName.ToLower()} perfect for custom printing and personalization.", + $"Professional-grade {categoryName.ToLower()} designed for durability and print quality.", + $"Premium {categoryName.ToLower()} suitable for both small and large print runs.", + $"Versatile {categoryName.ToLower()} ideal for promotional materials and custom designs.", + $"Top-tier {categoryName.ToLower()} offering excellent print adhesion and longevity." + }; + + return descriptions[_random.Next(descriptions.Length)]; + } + + private decimal GenerateBasePrice(string categoryName) + { + var priceRanges = new Dictionary + { + ["T-Shirts"] = (8.99m, 24.99m), + ["Hoodies"] = (19.99m, 49.99m), + ["Mugs"] = (4.99m, 15.99m), + ["Business Cards"] = (9.99m, 39.99m), + ["Phone Cases"] = (12.99m, 29.99m) + }; + + var (min, max) = priceRanges.TryGetValue(categoryName, value: out var range) ? + range : (5.99m, 29.99m); + + return Math.Round(min + (max - min) * (decimal)_random.NextDouble(), 2); + } + + private string GetImageForCategory(string mainCategory) + { + return mainCategory switch + { + "Textile" => _textileImages[_random.Next(_textileImages.Length)], + "Hard Surfaces" => _hardSurfaceImages[_random.Next(_hardSurfaceImages.Length)], + "Paper" => _paperImages[_random.Next(_paperImages.Length)], + _ => _categoryImages[_random.Next(_categoryImages.Length)] + }; + } + + private static string GetMainCategoryName(Category category) + { + if (category.ParentCategoryId == null) + return category.Name; + + var textileCategories = new[] { "T-Shirts", "Hoodies", "Tank Tops", "Long Sleeves", "Polo Shirts", "Sweatshirts", "Jackets", "Caps & Hats", "Bags", "Towels", "Aprons", "Baby Clothing", "Youth Clothing", "Women's Apparel", "Men's Apparel" }; + var hardSurfaceCategories = new[] { "Mugs", "Water Bottles", "Phone Cases", "Laptop Cases", "Keychains", "Mouse Pads", "Coasters", "Picture Frames", "Awards & Trophies", "Signs", "Magnets", "Buttons & Pins", "Clocks", "Tiles", "Metal Prints" }; + var paperCategories = new[] { "Business Cards", "Flyers", "Brochures", "Posters", "Banners", "Stickers", "Labels", "Notebooks", "Calendars", "Greeting Cards", "Postcards", "Bookmarks", "Menu Cards", "Invitations", "Certificates" }; + + if (textileCategories.Contains(category.Name)) return "Textile"; + if (hardSurfaceCategories.Contains(category.Name)) return "Hard Surfaces"; + return paperCategories.Contains(category.Name) ? "Paper" : "Textile"; + } + + private static List GetSizesForCategory(string mainCategory) + { + return mainCategory switch + { + "Textile" => ["XS", "S", "M", "L", "XL", "XXL", "XXXL"], + "Hard Surfaces" => ["Small", "Medium", "Large", "XL", "Standard"], + "Paper" => ["A4", "A5", "Letter", "Legal", "Custom", "Standard"], + _ => ["S", "M", "L", "XL"] + }; + } + + private static List GetColorsForCategory() + { + return + [ + "White", "Black", "Red", "Blue", "Green", "Yellow", "Purple", "Orange", + "Pink", "Gray", "Navy", "Maroon", "Forest", "Royal", "Sky", "Lime" + ]; + } +} \ No newline at end of file diff --git a/src/Imprink.WebApi/Startup.cs b/src/Imprink.WebApi/Startup.cs index 820fd1e..6cc1243 100644 --- a/src/Imprink.WebApi/Startup.cs +++ b/src/Imprink.WebApi/Startup.cs @@ -24,6 +24,8 @@ public static class Startup services.AddScoped(); services.AddScoped(); services.AddScoped(); + + services.AddScoped(); services.AddDbContext(options => options.UseSqlServer( @@ -61,13 +63,14 @@ public static class Startup if (string.IsNullOrEmpty(userId)) return Task.CompletedTask; var identity = context.Principal!.Identity as ClaimsIdentity; - var roles = (from ur in dbContext?.UserRole + var roles = ( + from ur in dbContext?.UserRole join r in dbContext?.Roles on ur.RoleId equals r.Id where ur.UserId == userId select r.RoleName).ToList(); foreach (var role in roles) identity!.AddClaim(new Claim(ClaimTypes.Role, role)); - + identity!.AddClaim(new Claim(ClaimTypes.Role, "User")); return Task.CompletedTask; } };