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;
}
};