Add CRUDs and validation/controllers

This commit is contained in:
lumijiez
2025-06-17 15:16:02 +03:00
parent 922021d088
commit 40906bea78
48 changed files with 653 additions and 27 deletions

View File

@@ -2,7 +2,7 @@ using Imprink.Application.Products.Dtos;
using Imprink.Domain.Entities.Product;
using MediatR;
namespace Imprink.Application.Products.Create;
namespace Imprink.Application.Domains.Categories;
public class CreateCategoryCommand : IRequest<CategoryDto>
{

View File

@@ -1,6 +1,6 @@
using MediatR;
namespace Imprink.Application.Products.Delete;
namespace Imprink.Application.Domains.Categories;
public class DeleteCategoryCommand : IRequest<bool>
{

View File

@@ -2,7 +2,7 @@ using Imprink.Application.Products.Dtos;
using Imprink.Domain.Entities.Product;
using MediatR;
namespace Imprink.Application.Products.Query;
namespace Imprink.Application.Domains.Categories;
public class GetCategoriesQuery : IRequest<IEnumerable<CategoryDto>>
{

View File

@@ -0,0 +1,62 @@
using Imprink.Application.Exceptions;
using Imprink.Application.Products.Dtos;
using MediatR;
namespace Imprink.Application.Domains.Categories;
public class UpdateCategoryCommand : IRequest<CategoryDto>
{
public Guid Id { get; set; }
public string Name { get; set; } = null!;
public string Description { get; set; } = null!;
public string? ImageUrl { get; set; }
public int SortOrder { get; set; }
public bool IsActive { get; set; }
public Guid? ParentCategoryId { get; set; }
}
public class UpdateCategoryHandler(IUnitOfWork unitOfWork) : IRequestHandler<UpdateCategoryCommand, CategoryDto>
{
public async Task<CategoryDto> Handle(UpdateCategoryCommand request, CancellationToken cancellationToken)
{
await unitOfWork.BeginTransactionAsync(cancellationToken);
try
{
var existingCategory = await unitOfWork.CategoryRepository.GetByIdAsync(request.Id, cancellationToken);
if (existingCategory == null)
{
throw new NotFoundException($"Category with ID {request.Id} not found.");
}
existingCategory.Name = request.Name;
existingCategory.Description = request.Description;
existingCategory.ImageUrl = request.ImageUrl;
existingCategory.SortOrder = request.SortOrder;
existingCategory.IsActive = request.IsActive;
existingCategory.ParentCategoryId = request.ParentCategoryId;
var updatedCategory = await unitOfWork.CategoryRepository.UpdateAsync(existingCategory, cancellationToken);
await unitOfWork.CommitTransactionAsync(cancellationToken);
return new CategoryDto
{
Id = updatedCategory.Id,
Name = updatedCategory.Name,
Description = updatedCategory.Description,
ImageUrl = updatedCategory.ImageUrl,
SortOrder = updatedCategory.SortOrder,
IsActive = updatedCategory.IsActive,
ParentCategoryId = updatedCategory.ParentCategoryId,
CreatedAt = updatedCategory.CreatedAt,
ModifiedAt = updatedCategory.ModifiedAt
};
}
catch
{
await unitOfWork.RollbackTransactionAsync(cancellationToken);
throw;
}
}
}

View File

@@ -2,7 +2,7 @@ using Imprink.Application.Products.Dtos;
using Imprink.Domain.Entities.Product;
using MediatR;
namespace Imprink.Application.Products.Create;
namespace Imprink.Application.Domains.ProductVariants;
public class CreateProductVariantCommand : IRequest<ProductVariantDto>
{

View File

@@ -1,6 +1,6 @@
using MediatR;
namespace Imprink.Application.Products.Delete;
namespace Imprink.Application.Domains.ProductVariants;
public class DeleteProductVariantCommand : IRequest<bool>
{

View File

@@ -2,7 +2,7 @@ using Imprink.Application.Products.Dtos;
using Imprink.Domain.Entities.Product;
using MediatR;
namespace Imprink.Application.Products.Query;
namespace Imprink.Application.Domains.ProductVariants;
public class GetProductVariantsQuery : IRequest<IEnumerable<ProductVariantDto>>
{

View File

@@ -0,0 +1,69 @@
using Imprink.Application.Exceptions;
using Imprink.Application.Products.Dtos;
using MediatR;
namespace Imprink.Application.Domains.ProductVariants;
public class UpdateProductVariantCommand : IRequest<ProductVariantDto>
{
public Guid Id { get; set; }
public Guid ProductId { get; set; }
public string Size { get; set; } = null!;
public string? Color { get; set; }
public decimal Price { get; set; }
public string? ImageUrl { get; set; }
public string Sku { get; set; } = null!;
public int StockQuantity { get; set; }
public bool IsActive { get; set; }
}
public class UpdateProductVariantHandler(IUnitOfWork unitOfWork)
: IRequestHandler<UpdateProductVariantCommand, ProductVariantDto>
{
public async Task<ProductVariantDto> Handle(UpdateProductVariantCommand request, CancellationToken cancellationToken)
{
await unitOfWork.BeginTransactionAsync(cancellationToken);
try
{
var existingVariant = await unitOfWork.ProductVariantRepository.GetByIdAsync(request.Id, cancellationToken);
if (existingVariant == null)
{
throw new NotFoundException($"Product variant with ID {request.Id} not found.");
}
existingVariant.ProductId = request.ProductId;
existingVariant.Size = request.Size;
existingVariant.Color = request.Color;
existingVariant.Price = request.Price;
existingVariant.ImageUrl = request.ImageUrl;
existingVariant.Sku = request.Sku;
existingVariant.StockQuantity = request.StockQuantity;
existingVariant.IsActive = request.IsActive;
var updatedVariant = await unitOfWork.ProductVariantRepository.UpdateAsync(existingVariant, cancellationToken);
await unitOfWork.CommitTransactionAsync(cancellationToken);
return new ProductVariantDto
{
Id = updatedVariant.Id,
ProductId = updatedVariant.ProductId,
Size = updatedVariant.Size,
Color = updatedVariant.Color,
Price = updatedVariant.Price,
ImageUrl = updatedVariant.ImageUrl,
Sku = updatedVariant.Sku,
StockQuantity = updatedVariant.StockQuantity,
IsActive = updatedVariant.IsActive,
CreatedAt = updatedVariant.CreatedAt,
ModifiedAt = updatedVariant.ModifiedAt
};
}
catch
{
await unitOfWork.RollbackTransactionAsync(cancellationToken);
throw;
}
}
}

View File

@@ -2,7 +2,7 @@ using Imprink.Application.Products.Dtos;
using Imprink.Domain.Entities.Product;
using MediatR;
namespace Imprink.Application.Products.Create;
namespace Imprink.Application.Domains.Products;
public class CreateProductCommand : IRequest<ProductDto>
{

View File

@@ -1,6 +1,6 @@
using MediatR;
namespace Imprink.Application.Products.Delete;
namespace Imprink.Application.Domains.Products;
public class DeleteProductCommand : IRequest<bool>
{

View File

@@ -2,7 +2,7 @@ using Imprink.Application.Products.Dtos;
using Imprink.Domain.Models;
using MediatR;
namespace Imprink.Application.Products;
namespace Imprink.Application.Domains.Products;
public class GetProductsQuery : IRequest<PagedResultDto<ProductDto>>
{

View File

@@ -0,0 +1,78 @@
using Imprink.Application.Exceptions;
using Imprink.Application.Products.Dtos;
using MediatR;
namespace Imprink.Application.Domains.Products;
public class UpdateProductCommand : IRequest<ProductDto>
{
public Guid Id { get; set; }
public string Name { get; set; } = null!;
public string? Description { get; set; }
public decimal BasePrice { get; set; }
public bool IsCustomizable { get; set; }
public bool IsActive { get; set; }
public string? ImageUrl { get; set; }
public Guid? CategoryId { get; set; }
}
public class UpdateProductHandler(IUnitOfWork unitOfWork) : IRequestHandler<UpdateProductCommand, ProductDto>
{
public async Task<ProductDto> Handle(UpdateProductCommand request, CancellationToken cancellationToken)
{
await unitOfWork.BeginTransactionAsync(cancellationToken);
try
{
var existingProduct = await unitOfWork.ProductRepository.GetByIdAsync(request.Id, cancellationToken);
if (existingProduct == null)
throw new NotFoundException($"Product with ID {request.Id} not found.");
existingProduct.Name = request.Name;
existingProduct.Description = request.Description;
existingProduct.BasePrice = request.BasePrice;
existingProduct.IsCustomizable = request.IsCustomizable;
existingProduct.IsActive = request.IsActive;
existingProduct.ImageUrl = request.ImageUrl;
existingProduct.CategoryId = request.CategoryId;
var updatedProduct = await unitOfWork.ProductRepository.UpdateAsync(existingProduct, cancellationToken);
var categoryDto = new CategoryDto
{
Id = updatedProduct.Category.Id,
Name = updatedProduct.Category.Name,
Description = updatedProduct.Category.Description,
ImageUrl = updatedProduct.Category.ImageUrl,
SortOrder = updatedProduct.Category.SortOrder,
IsActive = updatedProduct.Category.IsActive,
ParentCategoryId = updatedProduct.Category.ParentCategoryId,
CreatedAt = updatedProduct.Category.CreatedAt,
ModifiedAt = updatedProduct.Category.ModifiedAt
};
await unitOfWork.CommitTransactionAsync(cancellationToken);
return new ProductDto
{
Id = updatedProduct.Id,
Name = updatedProduct.Name,
Description = updatedProduct.Description,
BasePrice = updatedProduct.BasePrice,
IsCustomizable = updatedProduct.IsCustomizable,
IsActive = updatedProduct.IsActive,
ImageUrl = updatedProduct.ImageUrl,
CategoryId = updatedProduct.CategoryId,
Category = categoryDto,
CreatedAt = updatedProduct.CreatedAt,
ModifiedAt = updatedProduct.ModifiedAt
};
}
catch
{
await unitOfWork.RollbackTransactionAsync(cancellationToken);
throw;
}
}
}

View File

@@ -1,9 +1,8 @@
using Imprink.Application.Exceptions;
using Imprink.Application.Users.Dtos;
using Imprink.Domain.Entities.Users;
using MediatR;
namespace Imprink.Application.Users;
namespace Imprink.Application.Domains.Users;
public record DeleteUserRoleCommand(string Sub, Guid RoleId) : IRequest<UserRoleDto?>;

View File

@@ -2,7 +2,7 @@ using Imprink.Application.Exceptions;
using Imprink.Application.Users.Dtos;
using MediatR;
namespace Imprink.Application.Users;
namespace Imprink.Application.Domains.Users;
public record GetUserRolesCommand(string Sub) : IRequest<IEnumerable<RoleDto>>;

View File

@@ -1,9 +1,9 @@
using Imprink.Application.Exceptions;
using Imprink.Application.Service;
using Imprink.Application.Services;
using Imprink.Application.Users.Dtos;
using MediatR;
namespace Imprink.Application.Users;
namespace Imprink.Application.Domains.Users;
public record SetUserFullNameCommand(string FirstName, string LastName) : IRequest<UserDto?>;

View File

@@ -1,9 +1,9 @@
using Imprink.Application.Exceptions;
using Imprink.Application.Service;
using Imprink.Application.Services;
using Imprink.Application.Users.Dtos;
using MediatR;
namespace Imprink.Application.Users;
namespace Imprink.Application.Domains.Users;
public record SetUserPhoneCommand(string PhoneNumber) : IRequest<UserDto?>;

View File

@@ -3,7 +3,7 @@ using Imprink.Application.Users.Dtos;
using Imprink.Domain.Entities.Users;
using MediatR;
namespace Imprink.Application.Users;
namespace Imprink.Application.Domains.Users;
public record SetUserRoleCommand(string Sub, Guid RoleId) : IRequest<UserRoleDto?>;

View File

@@ -2,7 +2,7 @@ using Imprink.Application.Users.Dtos;
using Imprink.Domain.Models;
using MediatR;
namespace Imprink.Application.Users;
namespace Imprink.Application.Domains.Users;
public record SyncUserCommand(Auth0User User) : IRequest<UserDto?>;

View File

@@ -20,7 +20,7 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Validation\Users\" />
<Folder Include="Domains\Orders\" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,4 @@
namespace Imprink.Application.Service;
namespace Imprink.Application.Services;
public interface ICurrentUserService
{

View File

@@ -0,0 +1,34 @@
using FluentValidation;
using Imprink.Application.Domains.Categories;
namespace Imprink.Application.Validation.Categories;
public class CreateCategoryCommandValidator : AbstractValidator<CreateCategoryCommand>
{
public CreateCategoryCommandValidator()
{
RuleFor(x => x.Name)
.NotEmpty().WithMessage("Name is required.")
.Length(1, 100).WithMessage("Name must be between 1 and 100 characters.");
RuleFor(x => x.Description)
.NotEmpty().WithMessage("Description is required.")
.Length(1, 500).WithMessage("Description must be between 1 and 500 characters.");
RuleFor(x => x.ImageUrl)
.Must(BeValidUrl).When(x => !string.IsNullOrWhiteSpace(x.ImageUrl))
.WithMessage("ImageUrl must be a valid URL.");
RuleFor(x => x.SortOrder)
.GreaterThanOrEqualTo(0).WithMessage("SortOrder cannot be negative.");
RuleFor(x => x.ParentCategoryId)
.NotEqual(Guid.Empty).When(x => x.ParentCategoryId.HasValue)
.WithMessage("ParentCategoryId must be a valid GUID.");
}
private static bool BeValidUrl(string? url)
{
return Uri.TryCreate(url, UriKind.Absolute, out _);
}
}

View File

@@ -0,0 +1,14 @@
using FluentValidation;
using Imprink.Application.Domains.Categories;
namespace Imprink.Application.Validation.Categories;
public class DeleteCategoryCommandValidator : AbstractValidator<DeleteCategoryCommand>
{
public DeleteCategoryCommandValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.")
.NotEqual(Guid.Empty).WithMessage("Id must be a valid GUID.");
}
}

View File

@@ -0,0 +1,40 @@
using FluentValidation;
using Imprink.Application.Domains.Categories;
namespace Imprink.Application.Validation.Categories;
public class UpdateCategoryCommandValidator : AbstractValidator<UpdateCategoryCommand>
{
public UpdateCategoryCommandValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.")
.NotEqual(Guid.Empty).WithMessage("Id must be a valid GUID.");
RuleFor(x => x.Name)
.NotEmpty().WithMessage("Name is required.")
.Length(1, 100).WithMessage("Name must be between 1 and 100 characters.");
RuleFor(x => x.Description)
.NotEmpty().WithMessage("Description is required.")
.Length(1, 500).WithMessage("Description must be between 1 and 500 characters.");
RuleFor(x => x.ImageUrl)
.Must(BeValidUrl).When(x => !string.IsNullOrWhiteSpace(x.ImageUrl))
.WithMessage("ImageUrl must be a valid URL.");
RuleFor(x => x.SortOrder)
.GreaterThanOrEqualTo(0).WithMessage("SortOrder cannot be negative.");
RuleFor(x => x.ParentCategoryId)
.NotEqual(Guid.Empty).When(x => x.ParentCategoryId.HasValue)
.WithMessage("ParentCategoryId must be a valid GUID.")
.Must((command, parentId) => parentId != command.Id).When(x => x.ParentCategoryId.HasValue)
.WithMessage("Category cannot be its own parent.");
}
private static bool BeValidUrl(string? url)
{
return Uri.TryCreate(url, UriKind.Absolute, out _);
}
}

View File

@@ -0,0 +1,41 @@
using FluentValidation;
using Imprink.Application.Domains.ProductVariants;
namespace Imprink.Application.Validation.ProductVariants;
public class CreateProductVariantCommandValidator : AbstractValidator<CreateProductVariantCommand>
{
public CreateProductVariantCommandValidator()
{
RuleFor(x => x.ProductId)
.NotEmpty().WithMessage("ProductId is required.")
.NotEqual(Guid.Empty).WithMessage("ProductId must be a valid GUID.");
RuleFor(x => x.Size)
.NotEmpty().WithMessage("Size is required.")
.Length(1, 50).WithMessage("Size must be between 1 and 50 characters.");
RuleFor(x => x.Color)
.Length(1, 50).WithMessage("Color must be between 1 and 50 characters.")
.When(x => !string.IsNullOrWhiteSpace(x.Color));
RuleFor(x => x.Price)
.GreaterThan(0).WithMessage("Price must be greater than 0.");
RuleFor(x => x.ImageUrl)
.Must(BeValidUrl).When(x => !string.IsNullOrWhiteSpace(x.ImageUrl))
.WithMessage("ImageUrl must be a valid URL.");
RuleFor(x => x.Sku)
.NotEmpty().WithMessage("SKU is required.")
.Length(1, 50).WithMessage("SKU must be between 1 and 50 characters.");
RuleFor(x => x.StockQuantity)
.GreaterThanOrEqualTo(0).WithMessage("StockQuantity cannot be negative.");
}
private static bool BeValidUrl(string? url)
{
return Uri.TryCreate(url, UriKind.Absolute, out _);
}
}

View File

@@ -0,0 +1,14 @@
using FluentValidation;
using Imprink.Application.Domains.ProductVariants;
namespace Imprink.Application.Validation.ProductVariants;
public class DeleteProductVariantCommandValidator : AbstractValidator<DeleteProductVariantCommand>
{
public DeleteProductVariantCommandValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.")
.NotEqual(Guid.Empty).WithMessage("Id must be a valid GUID.");
}
}

View File

@@ -0,0 +1,14 @@
using FluentValidation;
using Imprink.Application.Domains.ProductVariants;
namespace Imprink.Application.Validation.ProductVariants;
public class GetProductVariantsQueryValidator : AbstractValidator<GetProductVariantsQuery>
{
public GetProductVariantsQueryValidator()
{
RuleFor(x => x.ProductId)
.NotEqual(Guid.Empty).When(x => x.ProductId.HasValue)
.WithMessage("ProductId must be a valid GUID when provided.");
}
}

View File

@@ -0,0 +1,45 @@
using FluentValidation;
using Imprink.Application.Domains.ProductVariants;
namespace Imprink.Application.Validation.ProductVariants;
public class UpdateProductVariantCommandValidator : AbstractValidator<UpdateProductVariantCommand>
{
public UpdateProductVariantCommandValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.")
.NotEqual(Guid.Empty).WithMessage("Id must be a valid GUID.");
RuleFor(x => x.ProductId)
.NotEmpty().WithMessage("ProductId is required.")
.NotEqual(Guid.Empty).WithMessage("ProductId must be a valid GUID.");
RuleFor(x => x.Size)
.NotEmpty().WithMessage("Size is required.")
.Length(1, 50).WithMessage("Size must be between 1 and 50 characters.");
RuleFor(x => x.Color)
.Length(1, 50).WithMessage("Color must be between 1 and 50 characters.")
.When(x => !string.IsNullOrWhiteSpace(x.Color));
RuleFor(x => x.Price)
.GreaterThan(0).WithMessage("Price must be greater than 0.");
RuleFor(x => x.ImageUrl)
.Must(BeValidUrl).When(x => !string.IsNullOrWhiteSpace(x.ImageUrl))
.WithMessage("ImageUrl must be a valid URL.");
RuleFor(x => x.Sku)
.NotEmpty().WithMessage("SKU is required.")
.Length(1, 50).WithMessage("SKU must be between 1 and 50 characters.");
RuleFor(x => x.StockQuantity)
.GreaterThanOrEqualTo(0).WithMessage("StockQuantity cannot be negative.");
}
private static bool BeValidUrl(string? url)
{
return Uri.TryCreate(url, UriKind.Absolute, out _);
}
}

View File

@@ -0,0 +1,34 @@
using FluentValidation;
using Imprink.Application.Domains.Products;
namespace Imprink.Application.Validation.Products;
public class CreateProductCommandValidator : AbstractValidator<CreateProductCommand>
{
public CreateProductCommandValidator()
{
RuleFor(x => x.Name)
.NotEmpty().WithMessage("Name is required.")
.Length(1, 100).WithMessage("Name must be between 1 and 100 characters.");
RuleFor(x => x.Description)
.Length(1, 1000).WithMessage("Description must be between 1 and 1000 characters.")
.When(x => !string.IsNullOrWhiteSpace(x.Description));
RuleFor(x => x.BasePrice)
.GreaterThan(0).WithMessage("BasePrice must be greater than 0.");
RuleFor(x => x.ImageUrl)
.Must(BeValidUrl).When(x => !string.IsNullOrWhiteSpace(x.ImageUrl))
.WithMessage("ImageUrl must be a valid URL.");
RuleFor(x => x.CategoryId)
.NotEqual(Guid.Empty).When(x => x.CategoryId.HasValue)
.WithMessage("CategoryId must be a valid GUID.");
}
private static bool BeValidUrl(string? url)
{
return Uri.TryCreate(url, UriKind.Absolute, out _);
}
}

View File

@@ -0,0 +1,14 @@
using FluentValidation;
using Imprink.Application.Domains.Products;
namespace Imprink.Application.Validation.Products;
public class DeleteProductCommandValidator : AbstractValidator<DeleteProductCommand>
{
public DeleteProductCommandValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.")
.NotEqual(Guid.Empty).WithMessage("Id must be a valid GUID.");
}
}

View File

@@ -1,4 +1,5 @@
using FluentValidation;
using Imprink.Application.Domains.Products;
using Imprink.Application.Products;
using Imprink.Application.Validation.Models;

View File

@@ -0,0 +1,38 @@
using FluentValidation;
using Imprink.Application.Domains.Products;
namespace Imprink.Application.Validation.Products;
public class UpdateProductCommandValidator : AbstractValidator<UpdateProductCommand>
{
public UpdateProductCommandValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.")
.NotEqual(Guid.Empty).WithMessage("Id must be a valid GUID.");
RuleFor(x => x.Name)
.NotEmpty().WithMessage("Name is required.")
.Length(1, 100).WithMessage("Name must be between 1 and 100 characters.");
RuleFor(x => x.Description)
.Length(1, 1000).WithMessage("Description must be between 1 and 1000 characters.")
.When(x => !string.IsNullOrWhiteSpace(x.Description));
RuleFor(x => x.BasePrice)
.GreaterThan(0).WithMessage("BasePrice must be greater than 0.");
RuleFor(x => x.ImageUrl)
.Must(BeValidUrl).When(x => !string.IsNullOrWhiteSpace(x.ImageUrl))
.WithMessage("ImageUrl must be a valid URL.");
RuleFor(x => x.CategoryId)
.NotEqual(Guid.Empty).When(x => x.CategoryId.HasValue)
.WithMessage("CategoryId must be a valid GUID.");
}
private static bool BeValidUrl(string? url)
{
return Uri.TryCreate(url, UriKind.Absolute, out _);
}
}

View File

@@ -0,0 +1,18 @@
using FluentValidation;
using Imprink.Application.Domains.Users;
namespace Imprink.Application.Validation.Users;
public class SetUserFullNameCommandValidator : AbstractValidator<SetUserFullNameCommand>
{
public SetUserFullNameCommandValidator()
{
RuleFor(x => x.FirstName)
.NotEmpty().WithMessage("FirstName is required.")
.Length(1, 50).WithMessage("FirstName must be between 1 and 50 characters.");
RuleFor(x => x.LastName)
.NotEmpty().WithMessage("LastName is required.")
.Length(1, 50).WithMessage("LastName must be between 1 and 50 characters.");
}
}

View File

@@ -0,0 +1,14 @@
using FluentValidation;
using Imprink.Application.Domains.Users;
namespace Imprink.Application.Validation.Users;
public class SetUserPhoneCommandValidator : AbstractValidator<SetUserPhoneCommand>
{
public SetUserPhoneCommandValidator()
{
RuleFor(x => x.PhoneNumber)
.NotEmpty().WithMessage("PhoneNumber is required.")
.Matches(@"^\+?[1-9]\d{1,14}$").WithMessage("PhoneNumber must be a valid phone number format.");
}
}

View File

@@ -10,4 +10,8 @@
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="9.0.4" />
</ItemGroup>
<ItemGroup>
<Folder Include="Repositories\Orders\" />
</ItemGroup>
</Project>

View File

@@ -25,6 +25,7 @@
<ItemGroup>
<Folder Include="Migrations\" />
<Folder Include="Repositories\Orders\" />
</ItemGroup>
</Project>

View File

@@ -1,5 +1,5 @@
using System.Security.Claims;
using Imprink.Application.Service;
using Imprink.Application.Services;
using Microsoft.AspNetCore.Http;
namespace Imprink.Infrastructure.Services;

View File

@@ -1,6 +1,7 @@
using Imprink.Application.Domains.Categories;
using Imprink.Application.Products.Dtos;
using Imprink.Application.Products.Query;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Imprink.WebApi.Controllers;
@@ -10,8 +11,38 @@ namespace Imprink.WebApi.Controllers;
public class CategoriesController(IMediator mediator) : ControllerBase
{
[HttpGet]
public async Task<ActionResult<IEnumerable<CategoryDto>>> GetCategories([FromQuery] GetCategoriesQuery query)
[AllowAnonymous]
public async Task<ActionResult<IEnumerable<CategoryDto>>> GetCategories(
[FromQuery] GetCategoriesQuery query)
{
return Ok(await mediator.Send(query));
}
[HttpPost]
[Authorize(Roles = "Admin")]
public async Task<ActionResult<CategoryDto>> CreateCategory(
[FromBody] CreateCategoryCommand command)
{
var result = await mediator.Send(command);
return CreatedAtAction(nameof(GetCategories), new { id = result.Id }, result);
}
[HttpPut("{id:guid}")]
[Authorize(Roles = "Admin")]
public async Task<ActionResult<CategoryDto>> UpdateCategory(
Guid id,
[FromBody] UpdateCategoryCommand command)
{
if (id != command.Id) return BadRequest("ID mismatch");
return Ok(await mediator.Send(command));
}
[HttpDelete("{id:guid}")]
[Authorize(Roles = "Admin")]
public async Task<ActionResult> DeleteCategory(Guid id)
{
await mediator.Send(new DeleteCategoryCommand { Id = id });
return NoContent();
}
}

View File

@@ -1,6 +1,7 @@
using Imprink.Application.Domains.ProductVariants;
using Imprink.Application.Products.Dtos;
using Imprink.Application.Products.Query;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Imprink.WebApi.Controllers;
@@ -10,9 +11,38 @@ namespace Imprink.WebApi.Controllers;
public class ProductVariantsController(IMediator mediator) : ControllerBase
{
[HttpGet]
[AllowAnonymous]
public async Task<ActionResult<IEnumerable<ProductVariantDto>>> GetProductVariants(
[FromQuery] GetProductVariantsQuery query)
{
return Ok(await mediator.Send(query));
}
[HttpPost]
[Authorize(Roles = "Admin")]
public async Task<ActionResult<ProductVariantDto>> CreateProductVariant(
[FromBody] CreateProductVariantCommand command)
{
var result = await mediator.Send(command);
return CreatedAtAction(nameof(GetProductVariants), new { id = result.Id }, result);
}
[HttpPut("{id:guid}")]
[Authorize(Roles = "Admin")]
public async Task<ActionResult<ProductVariantDto>> UpdateProductVariant(
Guid id,
[FromBody] UpdateProductVariantCommand command)
{
if (id != command.Id) return BadRequest("ID mismatch");
return Ok(await mediator.Send(command));
}
[HttpDelete("{id:guid}")]
[Authorize(Roles = "Admin")]
public async Task<ActionResult> DeleteProductVariant(Guid id)
{
await mediator.Send(new DeleteProductVariantCommand { Id = id });
return NoContent();
}
}

View File

@@ -1,3 +1,4 @@
using Imprink.Application.Domains.Products;
using Imprink.Application.Products;
using Imprink.Application.Products.Dtos;
using Imprink.Domain.Models;
@@ -20,4 +21,33 @@ public class ProductsController(IMediator mediator) : ControllerBase
var result = await mediator.Send(new GetProductsQuery { FilterParameters = filterParameters});
return Ok(result);
}
[HttpPost]
[Authorize(Roles = "Admin")]
public async Task<ActionResult<PagedResultDto<ProductDto>>> CreateProduct(
[FromBody] CreateProductCommand command)
{
var result = await mediator.Send(command);
return Ok(result);
}
[HttpPut("{id:guid}")]
[Authorize(Roles = "Admin")]
public async Task<ActionResult<ProductDto>> UpdateProduct(
Guid id,
[FromBody] UpdateProductCommand command)
{
if (id != command.Id) return BadRequest("ID mismatch");
var result = await mediator.Send(command);
return Ok(result);
}
[HttpDelete("{id:guid}")]
[Authorize(Roles = "Admin")]
public async Task<ActionResult> DeleteProduct(Guid id)
{
await mediator.Send(new DeleteProductCommand { Id = id });
return NoContent();
}
}

View File

@@ -1,5 +1,5 @@
using System.Security.Claims;
using Imprink.Application.Users;
using Imprink.Application.Domains.Users;
using Imprink.Domain.Models;
using MediatR;
using Microsoft.AspNetCore.Authorization;

View File

@@ -1,8 +1,9 @@
using System.Security.Claims;
using FluentValidation;
using Imprink.Application;
using Imprink.Application.Products.Create;
using Imprink.Application.Service;
using Imprink.Application.Domains.Products;
using Imprink.Application.Products;
using Imprink.Application.Services;
using Imprink.Application.Validation.Models;
using Imprink.Domain.Repositories;
using Imprink.Domain.Repositories.Products;