diff --git a/src/Printbase.Application/Products/Handlers/GetCategoriesHandler.cs b/src/Printbase.Application/Products/Handlers/GetCategoriesHandler.cs new file mode 100644 index 0000000..4e96654 --- /dev/null +++ b/src/Printbase.Application/Products/Handlers/GetCategoriesHandler.cs @@ -0,0 +1,40 @@ +using MediatR; +using Printbase.Application.Products.Dtos; +using Printbase.Application.Products.Queries; + +namespace Printbase.Application.Products.Handlers; + +public class GetCategoriesHandler(IUnitOfWork unitOfWork) + : IRequestHandler> +{ + public async Task> Handle(GetCategoriesQuery request, CancellationToken cancellationToken) + { + IEnumerable categories; + + if (request.RootCategoriesOnly) + { + categories = await unitOfWork.CategoryRepository.GetRootCategoriesAsync(cancellationToken); + } + else if (request.IsActive.HasValue && request.IsActive.Value) + { + categories = await unitOfWork.CategoryRepository.GetActiveAsync(cancellationToken); + } + else + { + categories = await unitOfWork.CategoryRepository.GetAllAsync(cancellationToken); + } + + return categories.Select(c => new CategoryDto + { + Id = c.Id, + Name = c.Name, + Description = c.Description, + ImageUrl = c.ImageUrl, + SortOrder = c.SortOrder, + IsActive = c.IsActive, + ParentCategoryId = c.ParentCategoryId, + CreatedAt = c.CreatedAt, + ModifiedAt = c.ModifiedAt + }); + } +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/Handlers/GetProductVariantsHandler.cs b/src/Printbase.Application/Products/Handlers/GetProductVariantsHandler.cs new file mode 100644 index 0000000..5940b21 --- /dev/null +++ b/src/Printbase.Application/Products/Handlers/GetProductVariantsHandler.cs @@ -0,0 +1,62 @@ +using MediatR; +using Printbase.Application.Products.Dtos; +using Printbase.Application.Products.Queries; + +namespace Printbase.Application.Products.Handlers; + +public class GetProductVariantsHandler(IUnitOfWork unitOfWork) + : IRequestHandler> +{ + public async Task> Handle(GetProductVariantsQuery request, CancellationToken cancellationToken) + { + IEnumerable variants; + + if (request.ProductId.HasValue) + { + if (request.InStockOnly) + { + variants = await unitOfWork.ProductVariantRepository.GetInStockByProductIdAsync(request.ProductId.Value, cancellationToken); + } + else if (request.IsActive.HasValue && request.IsActive.Value) + { + variants = await unitOfWork.ProductVariantRepository.GetActiveByProductIdAsync(request.ProductId.Value, cancellationToken); + } + else + { + variants = await unitOfWork.ProductVariantRepository.GetByProductIdAsync(request.ProductId.Value, cancellationToken); + } + } + else + { + variants = new List(); + } + + return variants.Select(pv => new ProductVariantDto + { + Id = pv.Id, + ProductId = pv.ProductId, + Size = pv.Size, + Color = pv.Color, + Price = pv.Price, + ImageUrl = pv.ImageUrl, + Sku = pv.Sku, + StockQuantity = pv.StockQuantity, + IsActive = pv.IsActive, + Product = new ProductDto + { + Id = pv.Product.Id, + Name = pv.Product.Name, + Description = pv.Product.Description, + BasePrice = pv.Product.BasePrice, + IsCustomizable = pv.Product.IsCustomizable, + IsActive = pv.Product.IsActive, + ImageUrl = pv.Product.ImageUrl, + CategoryId = pv.Product.CategoryId, + CreatedAt = pv.Product.CreatedAt, + ModifiedAt = pv.Product.ModifiedAt + }, + CreatedAt = pv.CreatedAt, + ModifiedAt = pv.ModifiedAt + }); + } +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/Queries/GetCategoriesQuery.cs b/src/Printbase.Application/Products/Queries/GetCategoriesQuery.cs new file mode 100644 index 0000000..28f94ea --- /dev/null +++ b/src/Printbase.Application/Products/Queries/GetCategoriesQuery.cs @@ -0,0 +1,10 @@ +using MediatR; +using Printbase.Application.Products.Dtos; + +namespace Printbase.Application.Products.Queries; + +public class GetCategoriesQuery : IRequest> +{ + public bool? IsActive { get; set; } + public bool RootCategoriesOnly { get; set; } = false; +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/Queries/GetProductVariantsQuery.cs b/src/Printbase.Application/Products/Queries/GetProductVariantsQuery.cs new file mode 100644 index 0000000..0af041d --- /dev/null +++ b/src/Printbase.Application/Products/Queries/GetProductVariantsQuery.cs @@ -0,0 +1,11 @@ +using MediatR; +using Printbase.Application.Products.Dtos; + +namespace Printbase.Application.Products.Queries; + +public class GetProductVariantsQuery : IRequest> +{ + public Guid? ProductId { get; set; } + public bool? IsActive { get; set; } + public bool InStockOnly { get; set; } = false; +} \ No newline at end of file diff --git a/src/Printbase.WebApi/Controllers/CategoriesController.cs b/src/Printbase.WebApi/Controllers/CategoriesController.cs new file mode 100644 index 0000000..5246dd3 --- /dev/null +++ b/src/Printbase.WebApi/Controllers/CategoriesController.cs @@ -0,0 +1,30 @@ +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Printbase.Application.Products.Commands; +using Printbase.Application.Products.Dtos; + +namespace Printbase.WebApi.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class CategoriesController(IMediator mediator) : ControllerBase +{ + [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/Printbase.WebApi/Controllers/ProductVariantsController.cs b/src/Printbase.WebApi/Controllers/ProductVariantsController.cs new file mode 100644 index 0000000..8236427 --- /dev/null +++ b/src/Printbase.WebApi/Controllers/ProductVariantsController.cs @@ -0,0 +1,30 @@ +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Printbase.Application.Products.Commands; +using Printbase.Application.Products.Dtos; + +namespace Printbase.WebApi.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class ProductVariantsController(IMediator mediator) : ControllerBase +{ + [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}")] + 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/Printbase.WebApi/Controllers/ProductsController.cs b/src/Printbase.WebApi/Controllers/ProductsController.cs new file mode 100644 index 0000000..5e71a54 --- /dev/null +++ b/src/Printbase.WebApi/Controllers/ProductsController.cs @@ -0,0 +1,65 @@ +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Printbase.Application.Products.Commands; +using Printbase.Application.Products.Dtos; +using Printbase.Application.Products.Queries; +using Printbase.Domain.Common.Models; + +namespace Printbase.WebApi.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class ProductsController(IMediator mediator) : ControllerBase +{ + [HttpGet] + 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") + { + 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); + + 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/Printbase.WebApi/Startup.cs b/src/Printbase.WebApi/Startup.cs index 3775071..1f59262 100644 --- a/src/Printbase.WebApi/Startup.cs +++ b/src/Printbase.WebApi/Startup.cs @@ -1,8 +1,12 @@ using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; +using Printbase.Application; using Printbase.Application.Products.Handlers; using Printbase.Domain.Entities.Users; +using Printbase.Domain.Repositories; +using Printbase.Infrastructure; using Printbase.Infrastructure.Database; +using Printbase.Infrastructure.Repositories; namespace Printbase.WebApi; @@ -12,6 +16,11 @@ public static class Startup { var services = builder.Services; + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddDbContext(options => options.UseSqlServer( builder.Configuration.GetConnectionString("DefaultConnection"), @@ -67,6 +76,9 @@ public static class Startup policy.RequireRole("Administrator", "ProductManager")) .AddPolicy("CustomerPolicy", policy => policy.RequireRole("Customer", "Administrator", "OrderManager", "ProductManager")); + + services.AddControllers(); + services.AddSwaggerGen(); } public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) @@ -86,18 +98,22 @@ public static class Startup } } - if (env.IsDevelopment()) - { - app.UseSwagger(); - app.UseSwaggerUI(); - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Error"); - app.UseHsts(); - app.UseHttpsRedirection(); - } + // if (env.IsDevelopment()) + // { + // app.UseSwagger(); + // app.UseSwaggerUI(); + // app.UseDeveloperExceptionPage(); + // } + // else + // { + // app.UseExceptionHandler("/Error"); + // app.UseHsts(); + // app.UseHttpsRedirection(); + // } + + app.UseSwagger(); + app.UseSwaggerUI(); + app.UseDeveloperExceptionPage(); app.UseRouting();