Remove almost everything :(

This commit is contained in:
lumijiez
2025-05-26 11:42:52 +03:00
parent ce452e0df4
commit e52cef083b
35 changed files with 1 additions and 1935 deletions

View File

@@ -17,8 +17,4 @@
<ProjectReference Include="..\Printbase.Domain\Printbase.Domain.csproj" /> <ProjectReference Include="..\Printbase.Domain\Printbase.Domain.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Mappings\" />
</ItemGroup>
</Project> </Project>

View File

@@ -1,12 +0,0 @@
using MediatR;
using Printbase.Application.Products.Dtos;
namespace Printbase.Application.Products.Commands.CreateProduct;
public class CreateProductCommand : IRequest<ProductDto>
{
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public Guid TypeId { get; set; }
public List<CreateProductVariantDto>? Variants { get; set; }
}

View File

@@ -1,95 +0,0 @@
using MediatR;
using Printbase.Application.Products.Dtos;
using Printbase.Domain.Entities.Products;
using Printbase.Domain.Repositories;
namespace Printbase.Application.Products.Commands.CreateProduct;
public class CreateProductCommandHandler(
IProductRepository productRepository,
IProductVariantRepository variantRepository,
IProductTypeRepository typeRepository)
: IRequestHandler<CreateProductCommand, ProductDto>
{
private readonly IProductRepository _productRepository = productRepository ?? throw new ArgumentNullException(nameof(productRepository));
private readonly IProductVariantRepository _variantRepository = variantRepository ?? throw new ArgumentNullException(nameof(variantRepository));
private readonly IProductTypeRepository _typeRepository = typeRepository ?? throw new ArgumentNullException(nameof(typeRepository));
public async Task<ProductDto> Handle(CreateProductCommand request, CancellationToken cancellationToken)
{
var productType = await _typeRepository.GetByIdAsync(request.TypeId, includeRelations: true, cancellationToken);
if (productType == null)
{
throw new ArgumentException($"Product type with ID {request.TypeId} not found");
}
var product = new Product
{
Id = Guid.NewGuid(),
Name = request.Name,
Description = request.Description,
TypeId = request.TypeId,
CreatedAt = DateTime.UtcNow,
IsActive = true
};
var createdProduct = await _productRepository.AddAsync(product, cancellationToken);
var productVariants = new List<ProductVariant>();
if (request.Variants != null && request.Variants.Count != 0)
{
foreach (var variant in request.Variants.Select(variantDto => new ProductVariant
{
Id = Guid.NewGuid(),
ProductId = createdProduct.Id,
Color = variantDto.Color,
Size = variantDto.Size,
Price = variantDto.Price,
Discount = variantDto.Discount,
Stock = variantDto.Stock,
SKU = variantDto.SKU ?? GenerateSku(createdProduct.Name, variantDto.Color, variantDto.Size),
CreatedAt = DateTime.UtcNow,
IsActive = true
}))
{
var createdVariant = await _variantRepository.AddAsync(variant, cancellationToken);
productVariants.Add(createdVariant);
}
}
var productDto = new ProductDto
{
Id = createdProduct.Id,
Name = createdProduct.Name,
Description = createdProduct.Description,
TypeId = createdProduct.TypeId,
TypeName = productType.Name,
GroupName = productType.Group.Name,
CreatedAt = createdProduct.CreatedAt,
IsActive = createdProduct.IsActive,
Variants = productVariants.Select(v => new ProductVariantDto
{
Id = v.Id,
Color = v.Color,
Size = v.Size,
Price = v.Price,
Discount = v.Discount,
Stock = v.Stock,
SKU = v.SKU,
IsActive = v.IsActive
}).ToList()
};
return productDto;
}
public static string GenerateSku(string productName, string? color, string? size)
{
var prefix = productName.Length >= 3 ? productName[..3].ToUpper() : productName.ToUpper();
var colorPart = !string.IsNullOrEmpty(color) ? color[..Math.Min(3, color.Length)].ToUpper() : "XXX";
var sizePart = !string.IsNullOrEmpty(size) ? size.ToUpper() : "OS";
var randomPart = new Random().Next(100, 999).ToString();
return $"{prefix}-{colorPart}-{sizePart}-{randomPart}";
}
}

View File

@@ -1,11 +0,0 @@
namespace Printbase.Application.Products.Commands.CreateProduct;
public class CreateProductVariantDto
{
public string? Color { get; set; }
public string? Size { get; set; }
public decimal Price { get; set; }
public decimal? Discount { get; set; }
public int Stock { get; set; }
public string? SKU { get; set; }
}

View File

@@ -1,6 +0,0 @@
namespace Printbase.Application.Products.Dtos;
public class AllProductsDto
{
public ICollection<ProductDto> Products {get;set;}
}

View File

@@ -1,15 +0,0 @@
namespace Printbase.Application.Products.Dtos;
public class ProductDto
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public Guid TypeId { get; set; }
public string TypeName { get; set; } = string.Empty;
public string GroupName { get; set; } = string.Empty;
public ICollection<ProductVariantDto> Variants { get; set; } = new List<ProductVariantDto>();
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public bool IsActive { get; set; }
}

View File

@@ -1,14 +0,0 @@
namespace Printbase.Application.Products.Dtos;
public class ProductVariantDto
{
public Guid Id { get; set; }
public string? Color { get; set; }
public string? Size { get; set; }
public decimal Price { get; set; }
public decimal? Discount { get; set; }
public decimal DiscountedPrice => Discount is > 0 ? Price - Price * Discount.Value / 100m : Price;
public int Stock { get; set; }
public string? SKU { get; set; }
public bool IsActive { get; set; }
}

View File

@@ -1,9 +0,0 @@
using MediatR;
using Printbase.Application.Products.Dtos;
namespace Printbase.Application.Products.Queries.GetAllProducts;
public class GetAllProductsQuery(bool includeVariants = true) : IRequest<AllProductsDto?>
{
public bool IncludeVariants { get; } = includeVariants;
}

View File

@@ -1,48 +0,0 @@
using MediatR;
using Printbase.Application.Products.Dtos;
using Printbase.Domain.Repositories;
namespace Printbase.Application.Products.Queries.GetAllProducts;
public class GetAllProductsQueryHandler(IProductRepository productRepository)
: IRequestHandler<GetAllProductsQuery, AllProductsDto?>
{
private readonly IProductRepository _productRepository = productRepository
?? throw new ArgumentNullException(nameof(productRepository));
public async Task<AllProductsDto?> Handle(GetAllProductsQuery request, CancellationToken cancellationToken)
{
var products = await _productRepository.GetAllAsync(true, cancellationToken);
var allProducts = new AllProductsDto
{
Products = products.Select(p => new ProductDto
{
Id = p.Id,
Name = p.Name,
Description = p.Description,
TypeId = p.TypeId,
TypeName = p.Type.Name,
GroupName = p.Type.Group.Name,
CreatedAt = p.CreatedAt,
UpdatedAt = p.UpdatedAt,
IsActive = p.IsActive,
Variants = request.IncludeVariants
? p.Variants.Select(v => new ProductVariantDto
{
Id = v.Id,
Color = v.Color,
Size = v.Size,
Price = v.Price,
Discount = v.Discount,
Stock = v.Stock,
SKU = v.SKU,
IsActive = v.IsActive
}).ToList()
: []
}).ToList()
};
return allProducts;
}
}

View File

@@ -1,10 +0,0 @@
using MediatR;
using Printbase.Application.Products.Dtos;
namespace Printbase.Application.Products.Queries.GetProductById;
public class GetProductByIdQuery(Guid id, bool includeVariants = true) : IRequest<ProductDto?>
{
public Guid Id { get; } = id;
public bool IncludeVariants { get; } = includeVariants;
}

View File

@@ -1,56 +0,0 @@
using AutoMapper;
using MediatR;
using Printbase.Application.Products.Dtos;
using Printbase.Domain.Repositories;
namespace Printbase.Application.Products.Queries.GetProductById;
public class GetProductByIdQueryHandler(IProductRepository productRepository, IMapper mapper)
: IRequestHandler<GetProductByIdQuery, ProductDto?>
{
private readonly IProductRepository _productRepository = productRepository
?? throw new ArgumentNullException(nameof(productRepository));
private readonly IMapper _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
public async Task<ProductDto?> Handle(GetProductByIdQuery request, CancellationToken cancellationToken)
{
var product = await _productRepository.GetByIdAsync(request.Id, includeRelations: true, cancellationToken);
if (product == null)
{
return null;
}
var productDto = new ProductDto
{
Id = product.Id,
Name = product.Name,
Description = product.Description,
TypeId = product.TypeId,
TypeName = product.Type.Name,
GroupName = product.Type.Group.Name,
CreatedAt = product.CreatedAt,
UpdatedAt = product.UpdatedAt,
IsActive = product.IsActive
};
if (request.IncludeVariants)
{
productDto.Variants = product.Variants
.Select(v => new ProductVariantDto
{
Id = v.Id,
Color = v.Color,
Size = v.Size,
Price = v.Price,
Discount = v.Discount,
Stock = v.Stock,
SKU = v.SKU,
IsActive = v.IsActive
})
.ToList();
}
return productDto;
}
}

View File

@@ -1,14 +0,0 @@
namespace Printbase.Domain.Entities.Products;
public class Product
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public Guid TypeId { get; set; }
public ProductType Type { get; set; } = null!;
public ICollection<ProductVariant> Variants { get; set; } = new List<ProductVariant>();
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public bool IsActive { get; set; }
}

View File

@@ -1,12 +0,0 @@
namespace Printbase.Domain.Entities.Products;
public class ProductGroup
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public ICollection<ProductType> Types { get; set; } = new List<ProductType>();
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public bool IsActive { get; set; }
}

View File

@@ -1,14 +0,0 @@
namespace Printbase.Domain.Entities.Products;
public class ProductType
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public Guid GroupId { get; set; }
public ProductGroup Group { get; set; } = null!;
public ICollection<Product> Products { get; set; } = new List<Product>();
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public bool IsActive { get; set; }
}

View File

@@ -1,27 +0,0 @@
namespace Printbase.Domain.Entities.Products;
public class ProductVariant
{
public Guid Id { get; set; }
public Guid ProductId { get; set; }
public string? Color { get; set; }
public string? Size { get; set; }
public decimal Price { get; set; }
public decimal? Discount { get; set; }
public int Stock { get; set; }
public string? SKU { get; set; }
public Product Product { get; set; } = null!;
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public bool IsActive { get; set; }
public decimal GetDiscountedPrice()
{
if (Discount is > 0)
{
return Price - Price * Discount.Value / 100m;
}
return Price;
}
}

View File

@@ -1,13 +0,0 @@
using Printbase.Domain.Entities.Products;
namespace Printbase.Domain.Repositories;
public interface IProductGroupRepository
{
Task<ProductGroup?> GetByIdAsync(Guid id, bool includeRelations = false, CancellationToken cancellationToken = default);
Task<IEnumerable<ProductGroup>> GetAllAsync(bool includeRelations = false, CancellationToken cancellationToken = default);
Task<ProductGroup> AddAsync(ProductGroup group, CancellationToken cancellationToken = default);
Task<ProductGroup> UpdateAsync(ProductGroup group, CancellationToken cancellationToken = default);
Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default);
Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default);
}

View File

@@ -1,14 +0,0 @@
using Printbase.Domain.Entities.Products;
namespace Printbase.Domain.Repositories;
public interface IProductRepository
{
Task<Product?> GetByIdAsync(Guid id, bool includeRelations = false, CancellationToken cancellationToken = default);
Task<IEnumerable<Product>> GetAllAsync(bool includeRelations = false, CancellationToken cancellationToken = default);
Task<IEnumerable<Product>> GetByTypeIdAsync(Guid typeId, bool includeRelations = false, CancellationToken cancellationToken = default);
Task<Product> AddAsync(Product product, CancellationToken cancellationToken = default);
Task<Product> UpdateAsync(Product product, CancellationToken cancellationToken = default);
Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default);
Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default);
}

View File

@@ -1,14 +0,0 @@
using Printbase.Domain.Entities.Products;
namespace Printbase.Domain.Repositories;
public interface IProductTypeRepository
{
Task<ProductType?> GetByIdAsync(Guid id, bool includeRelations = false, CancellationToken cancellationToken = default);
Task<IEnumerable<ProductType>> GetAllAsync(bool includeRelations = false, CancellationToken cancellationToken = default);
Task<IEnumerable<ProductType>> GetByGroupIdAsync(Guid groupId, CancellationToken cancellationToken = default);
Task<ProductType> AddAsync(ProductType type, CancellationToken cancellationToken = default);
Task<ProductType> UpdateAsync(ProductType type, CancellationToken cancellationToken = default);
Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default);
Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default);
}

View File

@@ -1,14 +0,0 @@
using Printbase.Domain.Entities.Products;
namespace Printbase.Domain.Repositories;
public interface IProductVariantRepository
{
Task<ProductVariant?> GetByIdAsync(Guid id, bool includeRelations = false, CancellationToken cancellationToken = default);
Task<IEnumerable<ProductVariant>> GetAllAsync(bool includeRelations = false, CancellationToken cancellationToken = default);
Task<IEnumerable<ProductVariant>> GetByProductIdAsync(Guid productId, CancellationToken cancellationToken = default);
Task<ProductVariant> AddAsync(ProductVariant variant, CancellationToken cancellationToken = default);
Task<ProductVariant> UpdateAsync(ProductVariant variant, CancellationToken cancellationToken = default);
Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default);
Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default);
}

View File

@@ -5,39 +5,4 @@ namespace Printbase.Infrastructure.Database;
public class ApplicationDbContext(DbContextOptions<ApplicationDbContext>? options) : DbContext(options) public class ApplicationDbContext(DbContextOptions<ApplicationDbContext>? options) : DbContext(options)
{ {
public DbSet<ProductDbEntity> Products { get; set; } = null!;
public DbSet<ProductVariantDbEntity> ProductVariants { get; set; } = null!;
public DbSet<ProductTypeDbEntity> ProductTypes { get; set; } = null!;
public DbSet<ProductGroupDbEntity> ProductGroups { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ProductDbEntity>()
.HasMany(p => p.Variants)
.WithOne(v => v.Product)
.HasForeignKey(v => v.ProductId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<ProductDbEntity>()
.HasOne(p => p.Type)
.WithMany(t => t.Products)
.HasForeignKey(p => p.TypeId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<ProductTypeDbEntity>()
.HasOne(t => t.Group)
.WithMany(g => g.Types)
.HasForeignKey(t => t.GroupId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<ProductVariantDbEntity>()
.Property(v => v.Price)
.HasPrecision(18, 2);
modelBuilder.Entity<ProductVariantDbEntity>()
.Property(v => v.Discount)
.HasPrecision(18, 2);
}
} }

View File

@@ -1,34 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Printbase.Infrastructure.DbEntities.Products;
[Table("Products")]
public class ProductDbEntity
{
[Key, Required]
public Guid Id { get; set; }
[MaxLength(50), Required]
public required string Name { get; set; }
[MaxLength(1000)]
public string? Description { get; set; }
[Required]
public Guid TypeId { get; set; }
[Required]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
[Required]
public bool IsActive { get; set; } = true;
[ForeignKey(nameof(TypeId)), Required]
public required ProductTypeDbEntity Type { get; set; }
[InverseProperty(nameof(ProductVariantDbEntity.Product)), Required]
public required ICollection<ProductVariantDbEntity> Variants { get; set; } = [];
}

View File

@@ -1,30 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace Printbase.Infrastructure.DbEntities.Products;
[Table("ProductGroups")]
[Index(nameof(Name), IsUnique = true)]
public class ProductGroupDbEntity
{
[Key, Required]
public Guid Id { get; set; }
[MaxLength(50), Required]
public required string Name { get; set; }
[MaxLength(255)]
public string? Description { get; set; }
[InverseProperty(nameof(ProductTypeDbEntity.Group)), Required]
public required ICollection<ProductTypeDbEntity> Types { get; set; } = [];
[Required]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
[Required]
public bool IsActive { get; set; } = true;
}

View File

@@ -1,36 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace Printbase.Infrastructure.DbEntities.Products;
[Table("ProductTypes")]
[Index(nameof(Name), nameof(GroupId), IsUnique = true)]
public class ProductTypeDbEntity
{
[Key, Required]
public Guid Id { get; set; }
[MaxLength(50), Required]
public required string Name { get; set; }
[MaxLength(255)]
public string? Description { get; set; }
[Required]
public Guid GroupId { get; set; }
[ForeignKey(nameof(GroupId)), Required]
public required ProductGroupDbEntity Group { get; set; }
[InverseProperty(nameof(ProductDbEntity.Type)), Required]
public required ICollection<ProductDbEntity> Products { get; set; } = [];
[Required]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
[Required]
public bool IsActive { get; set; } = true;
}

View File

@@ -1,47 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace Printbase.Infrastructure.DbEntities.Products;
[Table("ProductVariants")]
[Index(nameof(ProductId), nameof(Color), nameof(Size), IsUnique = true)]
public class ProductVariantDbEntity
{
[Key, Required]
public Guid Id { get; set; }
[Required]
public Guid ProductId { get; set; }
[MaxLength(50)]
public string? Color { get; set; }
[MaxLength(20)]
public string? Size { get; set; }
[Column(TypeName = "decimal(18,2)"), Required]
[Range(0.01, 9999999.99)]
public decimal Price { get; set; }
[Range(0, 100)]
public decimal? Discount { get; set; }
[Required]
[Range(0, int.MaxValue)]
public int Stock { get; set; }
[MaxLength(50)]
public string? SKU { get; set; }
[ForeignKey(nameof(ProductId)), Required]
public required ProductDbEntity Product { get; set; }
[Required]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
[Required]
public bool IsActive { get; set; } = true;
}

View File

@@ -1,61 +0,0 @@
using AutoMapper;
using Printbase.Application.Products.Commands.CreateProduct;
using Printbase.Application.Products.Dtos;
using Printbase.Domain.Entities.Products;
using Printbase.Infrastructure.DbEntities.Products;
namespace Printbase.Infrastructure.Mappings;
public class ProductMappingProfile : Profile
{
public ProductMappingProfile()
{
// Product DbEntity -> Domain Entity
CreateMap<ProductDbEntity, Product>()
.ForMember(dest => dest.Type,
opt => opt.MapFrom(src => src.Type))
.ForMember(dest => dest.Variants,
opt => opt.MapFrom(src => src.Variants));
// ProductGroup DbEntity -> Domain Entity
CreateMap<ProductGroupDbEntity, ProductGroup>()
.ForMember(dest => dest.Types,
opt => opt.MapFrom(src => src.Types));
// ProductType DbEntity -> Domain Entity
CreateMap<ProductTypeDbEntity, ProductType>()
.ForMember(dest => dest.Group,
opt => opt.MapFrom(src => src.Group))
.ForMember(dest => dest.Products,
opt => opt.MapFrom(src => src.Products));
// ProductVariant DbEntity -> Domain Entity
CreateMap<ProductVariantDbEntity, ProductVariant>()
.ForMember(dest => dest.Product,
opt => opt.MapFrom(src => src.Product));
// Product Domain Entity -> DbEntity
CreateMap<Product, ProductDbEntity>()
.ForMember(dest => dest.Type,
opt => opt.Ignore()) // in repo
.ForMember(dest => dest.Variants,
opt => opt.Ignore()); // in repo
// ProductVariant Domain Entity -> DbEntity
CreateMap<ProductVariant, ProductVariantDbEntity>()
.ForMember(dest => dest.Product,
opt => opt.Ignore()); // in repo
// ProductType Domain Entity -> DbEntity
CreateMap<ProductType, ProductTypeDbEntity>()
.ForMember(dest => dest.Group,
opt => opt.Ignore()) // in repo
.ForMember(dest => dest.Products,
opt => opt.Ignore()); // in repo
// ProductGroup Domain Entity -> DbEntity
CreateMap<ProductGroup, ProductGroupDbEntity>()
.ForMember(dest => dest.Types,
opt => opt.Ignore()); // in repo
}
}

View File

@@ -1,231 +0,0 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Printbase.Infrastructure.Database;
#nullable disable
namespace Printbase.Infrastructure.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20250504210542_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.4")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductDbEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Description")
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<Guid>("TypeId")
.HasColumnType("uniqueidentifier");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("TypeId");
b.ToTable("Products");
});
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductGroupDbEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Description")
.HasMaxLength(255)
.HasColumnType("nvarchar(255)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("Name")
.IsUnique();
b.ToTable("ProductGroups");
});
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductTypeDbEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Description")
.HasMaxLength(255)
.HasColumnType("nvarchar(255)");
b.Property<Guid>("GroupId")
.HasColumnType("uniqueidentifier");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("GroupId");
b.HasIndex("Name", "GroupId")
.IsUnique();
b.ToTable("ProductTypes");
});
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductVariantDbEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Color")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<decimal?>("Discount")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<decimal>("Price")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<Guid>("ProductId")
.HasColumnType("uniqueidentifier");
b.Property<string>("SKU")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("Size")
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.Property<int>("Stock")
.HasColumnType("int");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("ProductId", "Color", "Size")
.IsUnique()
.HasFilter("[Color] IS NOT NULL AND [Size] IS NOT NULL");
b.ToTable("ProductVariants");
});
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductDbEntity", b =>
{
b.HasOne("Printbase.Infrastructure.DbEntities.Products.ProductTypeDbEntity", "Type")
.WithMany("Products")
.HasForeignKey("TypeId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Type");
});
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductTypeDbEntity", b =>
{
b.HasOne("Printbase.Infrastructure.DbEntities.Products.ProductGroupDbEntity", "Group")
.WithMany("Types")
.HasForeignKey("GroupId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Group");
});
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductVariantDbEntity", b =>
{
b.HasOne("Printbase.Infrastructure.DbEntities.Products.ProductDbEntity", "Product")
.WithMany("Variants")
.HasForeignKey("ProductId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Product");
});
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductDbEntity", b =>
{
b.Navigation("Variants");
});
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductGroupDbEntity", b =>
{
b.Navigation("Types");
});
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductTypeDbEntity", b =>
{
b.Navigation("Products");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,149 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Printbase.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ProductGroups",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
Description = table.Column<string>(type: "nvarchar(255)", maxLength: 255, nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
IsActive = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ProductGroups", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ProductTypes",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
Description = table.Column<string>(type: "nvarchar(255)", maxLength: 255, nullable: true),
GroupId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
IsActive = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ProductTypes", x => x.Id);
table.ForeignKey(
name: "FK_ProductTypes_ProductGroups_GroupId",
column: x => x.GroupId,
principalTable: "ProductGroups",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Products",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
Description = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
TypeId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
IsActive = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Products", x => x.Id);
table.ForeignKey(
name: "FK_Products_ProductTypes_TypeId",
column: x => x.TypeId,
principalTable: "ProductTypes",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "ProductVariants",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ProductId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Color = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
Size = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
Price = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
Discount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: true),
Stock = table.Column<int>(type: "int", nullable: false),
SKU = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
IsActive = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ProductVariants", x => x.Id);
table.ForeignKey(
name: "FK_ProductVariants_Products_ProductId",
column: x => x.ProductId,
principalTable: "Products",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_ProductGroups_Name",
table: "ProductGroups",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Products_TypeId",
table: "Products",
column: "TypeId");
migrationBuilder.CreateIndex(
name: "IX_ProductTypes_GroupId",
table: "ProductTypes",
column: "GroupId");
migrationBuilder.CreateIndex(
name: "IX_ProductTypes_Name_GroupId",
table: "ProductTypes",
columns: new[] { "Name", "GroupId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ProductVariants_ProductId_Color_Size",
table: "ProductVariants",
columns: new[] { "ProductId", "Color", "Size" },
unique: true,
filter: "[Color] IS NOT NULL AND [Size] IS NOT NULL");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ProductVariants");
migrationBuilder.DropTable(
name: "Products");
migrationBuilder.DropTable(
name: "ProductTypes");
migrationBuilder.DropTable(
name: "ProductGroups");
}
}
}

View File

@@ -1,228 +0,0 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Printbase.Infrastructure.Database;
#nullable disable
namespace Printbase.Infrastructure.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.4")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductDbEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Description")
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<Guid>("TypeId")
.HasColumnType("uniqueidentifier");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("TypeId");
b.ToTable("Products");
});
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductGroupDbEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Description")
.HasMaxLength(255)
.HasColumnType("nvarchar(255)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("Name")
.IsUnique();
b.ToTable("ProductGroups");
});
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductTypeDbEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Description")
.HasMaxLength(255)
.HasColumnType("nvarchar(255)");
b.Property<Guid>("GroupId")
.HasColumnType("uniqueidentifier");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("GroupId");
b.HasIndex("Name", "GroupId")
.IsUnique();
b.ToTable("ProductTypes");
});
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductVariantDbEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Color")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<decimal?>("Discount")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<decimal>("Price")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<Guid>("ProductId")
.HasColumnType("uniqueidentifier");
b.Property<string>("SKU")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("Size")
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.Property<int>("Stock")
.HasColumnType("int");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("ProductId", "Color", "Size")
.IsUnique()
.HasFilter("[Color] IS NOT NULL AND [Size] IS NOT NULL");
b.ToTable("ProductVariants");
});
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductDbEntity", b =>
{
b.HasOne("Printbase.Infrastructure.DbEntities.Products.ProductTypeDbEntity", "Type")
.WithMany("Products")
.HasForeignKey("TypeId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Type");
});
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductTypeDbEntity", b =>
{
b.HasOne("Printbase.Infrastructure.DbEntities.Products.ProductGroupDbEntity", "Group")
.WithMany("Types")
.HasForeignKey("GroupId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Group");
});
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductVariantDbEntity", b =>
{
b.HasOne("Printbase.Infrastructure.DbEntities.Products.ProductDbEntity", "Product")
.WithMany("Variants")
.HasForeignKey("ProductId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Product");
});
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductDbEntity", b =>
{
b.Navigation("Variants");
});
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductGroupDbEntity", b =>
{
b.Navigation("Types");
});
modelBuilder.Entity("Printbase.Infrastructure.DbEntities.Products.ProductTypeDbEntity", b =>
{
b.Navigation("Products");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,80 +0,0 @@
using AutoMapper;
using Microsoft.EntityFrameworkCore;
using Printbase.Domain.Entities.Products;
using Printbase.Domain.Repositories;
using Printbase.Infrastructure.Database;
using Printbase.Infrastructure.DbEntities.Products;
namespace Printbase.Infrastructure.Repositories;
public class ProductGroupRepository(ApplicationDbContext dbContext, IMapper mapper) : IProductGroupRepository
{
private readonly ApplicationDbContext _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
private readonly IMapper _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
public async Task<ProductGroup?> GetByIdAsync(Guid id, bool includeRelations = false, CancellationToken cancellationToken = default)
{
IQueryable<ProductGroupDbEntity> query = _dbContext.ProductGroups;
if (includeRelations) query = query.Include(g => g.Types);
var dbEntity = await query.FirstOrDefaultAsync(g => g.Id == id, cancellationToken);
return dbEntity == null ? null : _mapper.Map<ProductGroup>(dbEntity);
}
public async Task<IEnumerable<ProductGroup>> GetAllAsync(bool includeRelations = false, CancellationToken cancellationToken = default)
{
IQueryable<ProductGroupDbEntity> query = _dbContext.ProductGroups;
if (includeRelations) query = query.Include(g => g.Types);
var dbEntities = await query.ToListAsync(cancellationToken);
return _mapper.Map<IEnumerable<ProductGroup>>(dbEntities);
}
public async Task<ProductGroup> AddAsync(ProductGroup group, CancellationToken cancellationToken = default)
{
var dbEntity = _mapper.Map<ProductGroupDbEntity>(group);
_dbContext.ProductGroups.Add(dbEntity);
await _dbContext.SaveChangesAsync(cancellationToken);
return _mapper.Map<ProductGroup>(dbEntity);
}
public async Task<ProductGroup> UpdateAsync(ProductGroup group, CancellationToken cancellationToken = default)
{
var existingEntity = await _dbContext.ProductGroups
.Include(g => g.Types)
.FirstOrDefaultAsync(g => g.Id == group.Id, cancellationToken);
if (existingEntity == null) throw new KeyNotFoundException($"ProductGroup with ID {group.Id} not found");
_mapper.Map(group, existingEntity);
existingEntity.UpdatedAt = DateTime.UtcNow;
await _dbContext.SaveChangesAsync(cancellationToken);
return _mapper.Map<ProductGroup>(existingEntity);
}
public async Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default)
{
var entity = await _dbContext.ProductGroups.FindAsync([id], cancellationToken);
if (entity == null) return false;
_dbContext.ProductGroups.Remove(entity);
await _dbContext.SaveChangesAsync(cancellationToken);
return true;
}
public async Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default)
{
return await _dbContext.ProductGroups.AnyAsync(g => g.Id == id, cancellationToken);
}
}

View File

@@ -1,119 +0,0 @@
using AutoMapper;
using Microsoft.EntityFrameworkCore;
using Printbase.Domain.Entities.Products;
using Printbase.Domain.Repositories;
using Printbase.Infrastructure.Database;
using Printbase.Infrastructure.DbEntities.Products;
namespace Printbase.Infrastructure.Repositories;
public class ProductRepository(ApplicationDbContext dbContext, IMapper mapper) : IProductRepository
{
private readonly ApplicationDbContext _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
private readonly IMapper _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
public async Task<Product?> GetByIdAsync(Guid id, bool includeRelations = false, CancellationToken cancellationToken = default)
{
IQueryable<ProductDbEntity> query = _dbContext.Products;
if (includeRelations)
{
query = query
.Include(p => p.Type)
.ThenInclude(t => t.Group)
.Include(p => p.Variants);
}
var dbEntity = await query.FirstOrDefaultAsync(p => p.Id == id, cancellationToken);
return dbEntity == null ? null : _mapper.Map<Product>(dbEntity);
}
public async Task<IEnumerable<Product>> GetAllAsync(bool includeRelations = false, CancellationToken cancellationToken = default)
{
IQueryable<ProductDbEntity> query = _dbContext.Products;
if (includeRelations)
{
query = query
.Include(p => p.Type)
.ThenInclude(t => t.Group)
.Include(p => p.Variants);
}
var dbEntities = await query.ToListAsync(cancellationToken);
return _mapper.Map<IEnumerable<Product>>(dbEntities);
}
public async Task<IEnumerable<Product>> GetByTypeIdAsync(Guid typeId, bool includeRelations = false, CancellationToken cancellationToken = default)
{
var query = _dbContext.Products.Where(p => p.TypeId == typeId);
if (includeRelations)
{
query = query
.Include(p => p.Type)
.ThenInclude(t => t.Group)
.Include(p => p.Variants);
}
var dbEntities = await query.ToListAsync(cancellationToken);
return _mapper.Map<IEnumerable<Product>>(dbEntities);
}
public async Task<Product> AddAsync(Product product, CancellationToken cancellationToken = default)
{
var dbEntity = _mapper.Map<ProductDbEntity>(product);
dbEntity.Type = await _dbContext.ProductTypes.FindAsync([product.TypeId], cancellationToken)
?? throw new InvalidOperationException($"ProductType with ID {product.TypeId} not found");
_dbContext.Products.Add(dbEntity);
await _dbContext.SaveChangesAsync(cancellationToken);
return _mapper.Map<Product>(dbEntity);
}
public async Task<Product> UpdateAsync(Product product, CancellationToken cancellationToken = default)
{
var existingEntity = await _dbContext.Products
.Include(p => p.Variants)
.FirstOrDefaultAsync(p => p.Id == product.Id, cancellationToken);
if (existingEntity == null) throw new KeyNotFoundException($"Product with ID {product.Id} not found");
_mapper.Map(product, existingEntity);
if (existingEntity.TypeId != product.TypeId)
{
existingEntity.Type = await _dbContext.ProductTypes.FindAsync([product.TypeId], cancellationToken)
?? throw new InvalidOperationException($"ProductType with ID {product.TypeId} not found");
existingEntity.TypeId = product.TypeId;
}
existingEntity.UpdatedAt = DateTime.UtcNow;
await _dbContext.SaveChangesAsync(cancellationToken);
return _mapper.Map<Product>(existingEntity);
}
public async Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default)
{
var entity = await _dbContext.Products.FindAsync([id], cancellationToken);
if (entity == null) return false;
_dbContext.Products.Remove(entity);
await _dbContext.SaveChangesAsync(cancellationToken);
return true;
}
public async Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default)
{
return await _dbContext.Products.AnyAsync(p => p.Id == id, cancellationToken);
}
}

View File

@@ -1,112 +0,0 @@
using AutoMapper;
using Microsoft.EntityFrameworkCore;
using Printbase.Domain.Entities.Products;
using Printbase.Domain.Repositories;
using Printbase.Infrastructure.Database;
using Printbase.Infrastructure.DbEntities.Products;
namespace Printbase.Infrastructure.Repositories;
public class ProductTypeRepository(ApplicationDbContext dbContext, IMapper mapper) : IProductTypeRepository
{
private readonly ApplicationDbContext _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
private readonly IMapper _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
public async Task<ProductType?> GetByIdAsync(Guid id, bool includeRelations = false, CancellationToken cancellationToken = default)
{
IQueryable<ProductTypeDbEntity> query = _dbContext.ProductTypes;
if (includeRelations)
{
query = query
.Include(t => t.Group)
.Include(t => t.Products);
}
var dbEntity = await query.FirstOrDefaultAsync(t => t.Id == id, cancellationToken);
return dbEntity == null ? null : _mapper.Map<ProductType>(dbEntity);
}
public async Task<IEnumerable<ProductType>> GetAllAsync(bool includeRelations = false, CancellationToken cancellationToken = default)
{
IQueryable<ProductTypeDbEntity> query = _dbContext.ProductTypes;
if (includeRelations)
{
query = query
.Include(t => t.Group)
.Include(t => t.Products);
}
var dbEntities = await query.ToListAsync(cancellationToken);
return _mapper.Map<IEnumerable<ProductType>>(dbEntities);
}
public async Task<IEnumerable<ProductType>> GetByGroupIdAsync(Guid groupId, CancellationToken cancellationToken = default)
{
var dbEntities = await _dbContext.ProductTypes
.Where(t => t.GroupId == groupId)
.ToListAsync(cancellationToken);
return _mapper.Map<IEnumerable<ProductType>>(dbEntities);
}
public async Task<ProductType> AddAsync(ProductType type, CancellationToken cancellationToken = default)
{
var dbEntity = _mapper.Map<ProductTypeDbEntity>(type);
dbEntity.Group = await _dbContext.ProductGroups.FindAsync([type.GroupId], cancellationToken)
?? throw new InvalidOperationException($"ProductGroup with ID {type.GroupId} not found");
_dbContext.ProductTypes.Add(dbEntity);
await _dbContext.SaveChangesAsync(cancellationToken);
return _mapper.Map<ProductType>(dbEntity);
}
public async Task<ProductType> UpdateAsync(ProductType type, CancellationToken cancellationToken = default)
{
var existingEntity = await _dbContext.ProductTypes
.Include(t => t.Products)
.FirstOrDefaultAsync(t => t.Id == type.Id, cancellationToken);
if (existingEntity == null) throw new KeyNotFoundException($"ProductType with ID {type.Id} not found");
_mapper.Map(type, existingEntity);
if (existingEntity.GroupId != type.GroupId)
{
existingEntity.Group = await _dbContext.ProductGroups.FindAsync([type.GroupId], cancellationToken)
?? throw new InvalidOperationException($"ProductGroup with ID {type.GroupId} not found");
existingEntity.GroupId = type.GroupId;
}
existingEntity.UpdatedAt = DateTime.UtcNow;
await _dbContext.SaveChangesAsync(cancellationToken);
return _mapper.Map<ProductType>(existingEntity);
}
public async Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default)
{
var entity = await _dbContext.ProductTypes.FindAsync([id], cancellationToken);
if (entity == null)
{
return false;
}
_dbContext.ProductTypes.Remove(entity);
await _dbContext.SaveChangesAsync(cancellationToken);
return true;
}
public async Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default)
{
return await _dbContext.ProductTypes.AnyAsync(t => t.Id == id, cancellationToken);
}
}

View File

@@ -1,102 +0,0 @@
using AutoMapper;
using Microsoft.EntityFrameworkCore;
using Printbase.Domain.Entities.Products;
using Printbase.Domain.Repositories;
using Printbase.Infrastructure.Database;
using Printbase.Infrastructure.DbEntities.Products;
namespace Printbase.Infrastructure.Repositories;
public class ProductVariantRepository(ApplicationDbContext dbContext, IMapper mapper) : IProductVariantRepository
{
private readonly ApplicationDbContext _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
private readonly IMapper _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
public async Task<ProductVariant?> GetByIdAsync(Guid id, bool includeRelations = false, CancellationToken cancellationToken = default)
{
IQueryable<ProductVariantDbEntity> query = _dbContext.ProductVariants;
if (includeRelations)
query = query.Include(v => v.Product)
.ThenInclude(p => p.Type);
var dbEntity = await query.FirstOrDefaultAsync(v => v.Id == id, cancellationToken);
return dbEntity == null ? null : _mapper.Map<ProductVariant>(dbEntity);
}
public async Task<IEnumerable<ProductVariant>> GetAllAsync(bool includeRelations = false, CancellationToken cancellationToken = default)
{
IQueryable<ProductVariantDbEntity> query = _dbContext.ProductVariants;
if (includeRelations)
query = query.Include(v => v.Product)
.ThenInclude(p => p.Type);
var dbEntities = await query.ToListAsync(cancellationToken);
return _mapper.Map<IEnumerable<ProductVariant>>(dbEntities);
}
public async Task<IEnumerable<ProductVariant>> GetByProductIdAsync(Guid productId, CancellationToken cancellationToken = default)
{
var dbEntities = await _dbContext.ProductVariants
.Where(v => v.ProductId == productId)
.ToListAsync(cancellationToken);
return _mapper.Map<IEnumerable<ProductVariant>>(dbEntities);
}
public async Task<ProductVariant> AddAsync(ProductVariant variant, CancellationToken cancellationToken = default)
{
var dbEntity = _mapper.Map<ProductVariantDbEntity>(variant);
dbEntity.Product = await _dbContext.Products.FindAsync([variant.ProductId], cancellationToken)
?? throw new InvalidOperationException($"Product with ID {variant.ProductId} not found");
_dbContext.ProductVariants.Add(dbEntity);
await _dbContext.SaveChangesAsync(cancellationToken);
return _mapper.Map<ProductVariant>(dbEntity);
}
public async Task<ProductVariant> UpdateAsync(ProductVariant variant, CancellationToken cancellationToken = default)
{
var existingEntity = await _dbContext.ProductVariants
.FindAsync([variant.Id], cancellationToken);
if (existingEntity == null) throw new KeyNotFoundException($"ProductVariant with ID {variant.Id} not found");
_mapper.Map(variant, existingEntity);
if (existingEntity.ProductId != variant.ProductId)
{
existingEntity.Product = await _dbContext.Products.FindAsync([variant.ProductId], cancellationToken)
?? throw new InvalidOperationException($"Product with ID {variant.ProductId} not found");
existingEntity.ProductId = variant.ProductId;
}
existingEntity.UpdatedAt = DateTime.UtcNow;
await _dbContext.SaveChangesAsync(cancellationToken);
return _mapper.Map<ProductVariant>(existingEntity);
}
public async Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default)
{
var entity = await _dbContext.ProductVariants.FindAsync([id], cancellationToken);
if (entity == null) return false;
_dbContext.ProductVariants.Remove(entity);
await _dbContext.SaveChangesAsync(cancellationToken);
return true;
}
public async Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default)
{
return await _dbContext.ProductVariants.AnyAsync(v => v.Id == id, cancellationToken);
}
}

View File

@@ -1,56 +0,0 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Printbase.Application.Products.Commands.CreateProduct;
using Printbase.Application.Products.Queries.GetAllProducts;
using Printbase.Application.Products.Queries.GetProductById;
namespace Printbase.WebApi.Controllers;
[ApiController]
[Route("api/[controller]")]
public class ProductsController(IMediator mediator) : ControllerBase
{
private readonly IMediator _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetAllProducts()
{
var query = new GetAllProductsQuery();
var result = await _mediator.Send(query);
if (result == null) return NotFound();
return Ok(result);
}
[HttpGet("{id:guid}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetProductById(Guid id, [FromQuery] bool includeVariants = true)
{
var query = new GetProductByIdQuery(id, includeVariants);
var result = await _mediator.Send(query);
if (result == null) return NotFound();
return Ok(result);
}
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateProduct([FromBody] CreateProductCommand command)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
try
{
var result = await _mediator.Send(command);
return CreatedAtAction(nameof(GetProductById), new { id = result.Id }, result);
}
catch (ArgumentException ex)
{
return BadRequest(ex.Message);
}
}
}

View File

@@ -1,10 +1,5 @@
using System.Reflection;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Printbase.Application.Products.Commands.CreateProduct;
using Printbase.Domain.Repositories;
using Printbase.Infrastructure.Database; using Printbase.Infrastructure.Database;
using Printbase.Infrastructure.Mappings;
using Printbase.Infrastructure.Repositories;
namespace Printbase.WebApi; namespace Printbase.WebApi;
@@ -13,29 +8,11 @@ public static class Startup
public static void ConfigureServices(WebApplicationBuilder builder) public static void ConfigureServices(WebApplicationBuilder builder)
{ {
var services = builder.Services; var services = builder.Services;
services.AddDbContext<ApplicationDbContext>(options => services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer( options.UseSqlServer(
builder.Configuration.GetConnectionString("DefaultConnection"), builder.Configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName))); b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)));
services.AddMediatR(cfg => {
cfg.RegisterServicesFromAssembly(typeof(CreateProductCommand).Assembly);
cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
});
services.AddScoped<IProductRepository, ProductRepository>();
services.AddScoped<IProductVariantRepository, ProductVariantRepository>();
services.AddScoped<IProductTypeRepository, ProductTypeRepository>();
services.AddScoped<IProductGroupRepository, ProductGroupRepository>();
services.AddAutoMapper(cfg =>
{
cfg.AddProfile<ProductMappingProfile>();
}, typeof(ProductMappingProfile).Assembly);
services.AddSwaggerGen();
services.AddControllers();
services.AddOpenApi();
} }
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
@@ -57,14 +34,12 @@ public static class Startup
if (env.IsDevelopment()) if (env.IsDevelopment())
{ {
Console.WriteLine("Development environment variables applied");
app.UseSwagger(); app.UseSwagger();
app.UseSwaggerUI(); app.UseSwaggerUI();
app.UseDeveloperExceptionPage(); app.UseDeveloperExceptionPage();
} }
else else
{ {
Console.WriteLine("Production environment variables applied");
app.UseExceptionHandler("/Error"); app.UseExceptionHandler("/Error");
app.UseHsts(); app.UseHsts();
app.UseHttpsRedirection(); app.UseHttpsRedirection();

View File

@@ -1,187 +0,0 @@
using Printbase.Application.Products.Commands.CreateProduct;
namespace Printbase.Application.Tests.Products.Commands.CreateProduct;
using Xunit;
public class GenerateSkuTests
{
[Fact]
public void GenerateSku_WithValidInputs_ReturnsCorrectFormat()
{
// Arrange
const string productName = "Shirt";
const string color = "Blue";
const string size = "Medium";
// Act
var sku = CreateProductCommandHandler.GenerateSku(productName, color, size);
var parts = sku.Split('-');
// Assert
Assert.Equal(4, parts.Length);
Assert.Equal("SHI", parts[0]);
Assert.Equal("BLU", parts[1]);
Assert.Equal("MEDIUM", parts[2]);
Assert.Matches(@"^\d{3}$", parts[3]);
}
[Fact]
public void GenerateSku_WithShortProductName_UsesEntireProductName()
{
// Arrange
const string productName = "CU";
const string color = "Black";
const string size = "Large";
// Act
var sku = CreateProductCommandHandler.GenerateSku(productName, color, size);
var parts = sku.Split('-');
// Assert
Assert.Equal("CU", parts[0]);
Assert.Equal("BLA", parts[1]);
Assert.Equal("LARGE", parts[2]);
}
[Fact]
public void GenerateSku_WithShortColor_UsesEntireColor()
{
// Arrange
const string productName = "Mug";
const string color = "Red";
const string size = "Small";
// Act
var sku = CreateProductCommandHandler.GenerateSku(productName, color, size);
var parts = sku.Split('-');
// Assert
Assert.Equal("MUG", parts[0]);
Assert.Equal("RED", parts[1]);
Assert.Equal("SMALL", parts[2]);
}
[Fact]
public void GenerateSku_WithNullColor_UsesXXX()
{
// Arrange
const string productName = "Case";
string? color = null;
const string size = "Standard";
// Act
var sku = CreateProductCommandHandler.GenerateSku(productName, color, size);
var parts = sku.Split('-');
// Assert
Assert.Equal("CAS", parts[0]);
Assert.Equal("XXX", parts[1]);
Assert.Equal("STANDARD", parts[2]);
}
[Fact]
public void GenerateSku_WithEmptyColor_UsesXXX()
{
// Arrange
const string productName = "Notebook";
const string color = "";
const string size = "Standard";
// Act
var sku = CreateProductCommandHandler.GenerateSku(productName, color, size);
var parts = sku.Split('-');
// Assert
Assert.Equal("NOT", parts[0]);
Assert.Equal("XXX", parts[1]);
Assert.Equal("STANDARD", parts[2]);
}
[Fact]
public void GenerateSku_WithNullSize_UsesOS()
{
// Arrange
const string productName = "Watch";
const string color = "Silver";
string? size = null;
// Act
var sku = CreateProductCommandHandler.GenerateSku(productName, color, size);
var parts = sku.Split('-');
// Assert
Assert.Equal("WAT", parts[0]);
Assert.Equal("SIL", parts[1]);
Assert.Equal("OS", parts[2]);
}
[Fact]
public void GenerateSku_WithEmptySize_UsesOS()
{
// Arrange
const string productName = "Shirt";
const string color = "Black";
const string size = "";
// Act
var sku = CreateProductCommandHandler.GenerateSku(productName, color, size);
var parts = sku.Split('-');
// Assert
Assert.Equal("SHI", parts[0]);
Assert.Equal("BLA", parts[1]);
Assert.Equal("OS", parts[2]);
}
[Fact]
public void GenerateSku_CaseInsensitivity_OutputsUppercase()
{
// Arrange
const string productName = "hat";
const string color = "red";
const string size = "small";
// Act
var sku = CreateProductCommandHandler.GenerateSku(productName, color, size);
var parts = sku.Split('-');
// Assert
Assert.Equal("HAT", parts[0]);
Assert.Equal("RED", parts[1]);
Assert.Equal("SMALL", parts[2]);
}
[Fact]
public void GenerateSku_RandomPartIsInRange()
{
// Arrange
const string productName = "Book";
const string color = "Green";
const string size = "Medium";
// Act
var sku = CreateProductCommandHandler.GenerateSku(productName, color, size);
var parts = sku.Split('-');
var randomPart = int.Parse(parts[3]);
// Assert
Assert.InRange(randomPart, 100, 999);
}
[Fact]
public void GenerateSku_GeneratesUniqueSKUs()
{
// Arrange
const string productName = "Mug";
const string color = "White";
const string size = "Regular";
// Act
var sku1 = CreateProductCommandHandler.GenerateSku(productName, color, size);
var sku2 = CreateProductCommandHandler.GenerateSku(productName, color, size);
// Assert
Assert.NotEqual(sku1, sku2);
}
}