Initial commit - ECommerce project

This commit is contained in:
2026-08-05 23:58:33 +03:00
commit bcebac18e7
63 changed files with 4543 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentEmail.Core" Version="3.0.2" />
<PackageReference Include="FluentEmail.Smtp" Version="3.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.12.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../ECommerce.Contracts/ECommerce.Contracts.csproj" />
<ProjectReference Include="../ECommerce.Domain/ECommerce.Domain.csproj" />
<ProjectReference Include="..\..\..\..\..\SharedLogic\Generic\Generic.csproj" />
</ItemGroup>
</Project>
+236
View File
@@ -0,0 +1,236 @@
using System;
using Commerce.Contracts.DTOs;
using Commerce.Domain;
using Commerce.Domain.Entities;
using Generic.Contracts.Generics;
using Generic.Services;
using Microsoft.EntityFrameworkCore;
namespace Commerce.Core.Services;
public class CategoryService
{
private readonly Context context;
public CategoryService(Context context)
{
this.context = context;
}
public async Task CreateCategory(CategoryVM categoryVM)
{
var category = Category.ToEntity(categoryVM);
await context.Categories.AddAsync(category);
await context.SaveChangesAsync();
}
public async Task UpdateCategory(CategoryVM categoryVM)
{
if (categoryVM.Id == null) throw new ArgumentException("Category ID is required for update.");
var existingCategory = await context.Categories
.FirstOrDefaultAsync(x => x.Id == categoryVM.Id);
if (existingCategory == null) throw new KeyNotFoundException("Category not found.");
// Update properties
existingCategory.Translations = categoryVM.Translations?.Select(t => new CategoryTranslation
{
Language = t.Language,
Info = new CategoryInfo
{
Name = t.Info.Name,
Description = t.Info.Description,
TechnicalDetails = t.Info.TechnicalDetails?.Select(td => new Commerce.Domain.Entities.TechnicalDetail { Key = td.Key, Value = td.Value }).ToList() ?? new()
}
}).ToList() ?? new();
existingCategory.Photos = categoryVM.Photos ?? Array.Empty<string>();
existingCategory.ParentCategoryId = categoryVM.ParentCategoryId;
existingCategory.IsBundle = categoryVM.IsBundle;
await context.SaveChangesAsync();
}
public async Task RemoveCategory(Guid id)
{
var category = await context.Categories.FindAsync(id);
if (category != null) context.Categories.Remove(category);
await context.SaveChangesAsync();
}
public async Task<CategoryVM> GetCategory(Guid id)
{
var category = await context.Categories.FindAsync(id);
return category != null ? Category.ToVM(category) : new CategoryVM();
}
public async Task<(List<CategoryVM>, int)> FetchCategory(
CategoryFilter categoryFilter,
Pagination pagination
)
{
var query = context.Categories.AsQueryable();
query = query.ApplyAdvancedFilter(categoryFilter);
// query = query.ApplySorting(sortBy);
if (categoryFilter.Random ?? false)
{
query = query.OrderBy(x => Guid.NewGuid());
}
else
{
// query = query.ApplySorting(sortBy);
}
var count = query.Count();
query = query.Paginate(pagination);
var lang = categoryFilter.Language;
return (await query.Select(x => Category.ToVM(x, lang)).ToListAsync(), count);
}
public async Task<List<Category>> GetAllCategories(string name = "")
{
var categories = await context.Categories.ToListAsync();
if (!string.IsNullOrEmpty(name))
categories = categories.Where(x => x.Translations.Any(t => t.Info.Name.Contains(name, StringComparison.OrdinalIgnoreCase))).ToList();
return categories;
}
public async Task<List<Category>> GetAllCategories_Tree(string name = "")
{
var cate = await context.Categories
.ToListAsync();
if (!string.IsNullOrEmpty(name))
{
cate = cate.Where(x => x.Translations.Any(t => t.Info.Name.Contains(name, StringComparison.OrdinalIgnoreCase))).ToList();
}
var lista = cate.Where(x => x.ParentCategoryId == null).ToList();
Console.WriteLine($"{lista.Count}");
foreach (var cat in lista)
{
cat.ChildCategories = NewMethod(cate, cat.Id);
}
Console.WriteLine($"{lista.Count}");
return lista;
// if (!string.IsNullOrEmpty(name))
// categories = categories.Where(x => x.Name.Contains(name));
// var lista = await categories.ToListAsync();
// return lista;
}
private static List<Category> NewMethod(List<Category> categories, Guid parent)
{
var toreturn = new List<Category>();
var children = categories.Where(x => x.ParentCategoryId == parent).ToList();
foreach (var item in children)
{
item.ChildCategories = NewMethod(categories, item.Id);
}
return children;
}
public async Task<List<Category>> GetChildCategories(Guid categoryId)
{
var categories = new List<Category>();
categories = await context.Categories.Where(x => x.ParentCategoryId == categoryId).ToListAsync();
if (categories.Count != 0)
{
foreach (var category in categories)
{
categories.AddRange(await GetChildCategories(categoryId));
}
}
return categories;
}
public async Task<List<Category>> GetChildrenList(Guid categoryId)
{
return await context.Categories.Where(x => x.Id == categoryId)
.Include(x => x.ChildCategories)
.SelectMany(x => x.ChildCategories)
.ToListAsync();
}
// public async Task<List<Category>> GetRandomCategories(int categoriesNeeded, int productsNeeded)
// {
// var count = await context.Categories.CountAsync();
// var categories = new List<Category>();
// var random = new Random();
// var randoms = new List<int>();
// for (int i = 0; i < categoriesNeeded; i++)
// {
// var newRandom = random.Next(count);
// if (!randoms.Contains(newRandom))
// {
// randoms.Add(newRandom);
// categories.Add(await context.Categories
// .Skip(random.Next(count))
// .Take(1)
// .Include(x => x.Products)
// .FirstOrDefaultAsync()
// );
// }
// }
// return categories;
// }
// public async Task<Category> GetCategoryAsync(int id)
// {
// return await context.Categories.FirstOrDefaultAsync(c => c.Id == id);
// }
// public async Task<List<Category>> GetCategoriesPaginated(Pagination pagination)
// {
// var query = context.Categories.AsQueryable();
// query = query.Skip((pagination.CurrentPage - 1) * pagination.PageSize)
// .Take(pagination.PageSize);
// return await query.ToListAsync();
// }
// public async Task<List<CategoryVM>> GetRandomCategories(int count = 4)
// {
// return await context
// .Categorys.OrderBy(x => Guid.NewGuid())
// .Take(count)
// .Select(x => Category.ToVM(x))
// .ToListAsync();
// }
}
+138
View File
@@ -0,0 +1,138 @@
using System;
using Commerce.Contracts.DTOs;
using Commerce.Domain;
using Commerce.Domain.Entities;
using Generic.Contracts.Generics;
using Generic.Services;
using Microsoft.EntityFrameworkCore;
namespace Commerce.Core.Services;
public class ProductService
{
private readonly Context context;
public ProductService(Context context)
{
this.context = context;
}
public async Task CreateProduct(ProductVM productVM)
{
var product = Product.ToEntity(productVM);
if (productVM.ChildProducts?.Any() == true)
{
var childIds = productVM.ChildProducts.Select(x => x.Id).ToList();
var existingChildren = await context.Products.Where(x => childIds.Contains(x.Id)).ToListAsync();
product.ChildProducts = existingChildren;
}
await context.Products.AddAsync(product);
await context.SaveChangesAsync();
}
public async Task UpdateProduct(ProductVM productVM)
{
if (productVM.Id == null) throw new ArgumentException("Product ID is required for update.");
var existingProduct = await context.Products
.Include(x => x.ChildProducts)
.FirstOrDefaultAsync(x => x.Id == productVM.Id);
if (existingProduct == null) throw new KeyNotFoundException("Product not found.");
// Update properties
existingProduct.Translations = productVM.Translations?.Select(t => new ProductTranslation
{
Language = t.Language,
Info = new ProductInfo
{
Name = t.Info.Name,
Description = t.Info.Description,
TechnicalDetails = t.Info.TechnicalDetails?.Select(td => new Commerce.Domain.Entities.TechnicalDetail { Key = td.Key, Value = td.Value }).ToList() ?? new()
}
}).ToList() ?? new();
existingProduct.Photos = productVM.Photos ?? Array.Empty<string>();
existingProduct.Price = productVM.Price ?? 0;
existingProduct.Discount = productVM.Discount ?? 0;
existingProduct.CodeName = productVM.CodeName ?? string.Empty;
existingProduct.CategoryId = productVM.CategoryId ?? Guid.Empty;
existingProduct.IsBundle = productVM.IsBundle;
// Update ChildProducts collection
if (productVM.IsBundle)
{
var targetChildIds = productVM.ChildProducts?.Select(x => x.Id ?? Guid.Empty).ToList() ?? new List<Guid>();
// Remove items no longer in the list
var itemsToRemove = existingProduct.ChildProducts.Where(x => !targetChildIds.Contains(x.Id)).ToList();
foreach (var item in itemsToRemove) existingProduct.ChildProducts.Remove(item);
// Add new items
var currentChildIds = existingProduct.ChildProducts.Select(x => x.Id).ToList();
var idsToAdd = targetChildIds.Where(id => !currentChildIds.Contains(id)).ToList();
if (idsToAdd.Any())
{
var newChildren = await context.Products.Where(x => idsToAdd.Contains(x.Id)).ToListAsync();
foreach (var child in newChildren) existingProduct.ChildProducts.Add(child);
}
}
else
{
existingProduct.ChildProducts.Clear();
}
await context.SaveChangesAsync();
}
public async Task RemoveProduct(Guid id)
{
var product = await context.Products.FindAsync(id);
if (product != null) context.Products.Remove(product);
await context.SaveChangesAsync();
}
public async Task<ProductVM> GetProduct(Guid id)
{
var product = await context.Products
.Include(x => x.ChildProducts)
.FirstOrDefaultAsync(x => x.Id == id);
return product != null ? Product.ToVM(product) : new ProductVM();
}
public async Task<(List<ProductVM>, int)> FetchProduct(
ProductFilter productFilter,
Pagination pagination)
{
var query = context.Products
.Include(x => x.ChildProducts)
.AsQueryable();
query = query.ApplyAdvancedFilter(productFilter);
// query = query.ApplySorting(sortBy);
if (productFilter.Random ?? false)
{
query = query.OrderBy(x => Guid.NewGuid());
}
else
{
// query = query.ApplySorting(sortBy);
}
var count = query.Count();
query = query.Paginate(pagination);
var lang = productFilter.Language;
return (await query.Select(x => Product.ToVM(x, lang)).ToListAsync(), count);
}
// public async Task<List<ProductVM>> GetRandomProducts(int count = 6)
// {
// return await context
// .Products.OrderBy(x => Guid.NewGuid())
// .Take(count)
// .Select(x => Product.ToVM(x))
// .ToListAsync();
// }
}
+126
View File
@@ -0,0 +1,126 @@
// using System;
// using System.Collections.Generic;
// using System.Linq;
// using System.Linq.Expressions;
// using System.Reflection;
// using System.Threading.Tasks;
// namespace Commerce.Core.Utility;
// public static class FilterExtension
// {
// public static IQueryable<T> ApplyFilter<T, TFilter>(this IQueryable<T> query, TFilter filter)
// where TFilter : class
// {
// if (filter == null)
// return query;
// var parameter = Expression.Parameter(typeof(T), "x");
// Expression finalExpression = null;
// var filterProperties = typeof(TFilter)
// .GetProperties()
// .Where(p => p.GetValue(filter) != null);
// foreach (var filterProperty in filterProperties)
// {
// var filterValue = filterProperty.GetValue(filter);
// if (filterValue == null)
// continue;
// var propertyName = filterProperty.Name;
// PropertyInfo entityProperty;
// Expression propertyExpression = null;
// Expression comparison = null;
// // Handle special suffix cases
// if (propertyName.EndsWith("_Start") || propertyName.EndsWith("_Min"))
// {
// entityProperty = typeof(T).GetProperty(
// propertyName.Substring(0, propertyName.LastIndexOf('_'))
// );
// if (entityProperty != null)
// {
// propertyExpression = Expression.Property(parameter, entityProperty);
// comparison = Expression.GreaterThanOrEqual(
// propertyExpression,
// Expression.Constant(filterValue, entityProperty.PropertyType)
// );
// }
// }
// else if (propertyName.EndsWith("_End") || propertyName.EndsWith("_Max"))
// {
// entityProperty = typeof(T).GetProperty(
// propertyName.Substring(0, propertyName.LastIndexOf('_'))
// );
// if (entityProperty != null)
// {
// propertyExpression = Expression.Property(parameter, entityProperty);
// comparison = Expression.LessThanOrEqual(
// propertyExpression,
// Expression.Constant(filterValue, entityProperty.PropertyType)
// );
// }
// }
// else if (propertyName.EndsWith("Name", StringComparison.OrdinalIgnoreCase))
// {
// entityProperty = typeof(T).GetProperty(propertyName);
// if (entityProperty != null && entityProperty.PropertyType == typeof(string))
// {
// propertyExpression = Expression.Property(parameter, entityProperty);
// // For string Contains operation
// var containsMethod = typeof(string).GetMethod(
// "Contains",
// new[] { typeof(string) }
// );
// var filterValueString = filterValue.ToString();
// comparison = Expression.Call(
// propertyExpression,
// containsMethod,
// Expression.Constant(filterValueString, typeof(string))
// );
// }
// else if (entityProperty != null)
// {
// // Fall back to equality comparison if it's not a string
// propertyExpression = Expression.Property(parameter, entityProperty);
// comparison = Expression.Equal(
// propertyExpression,
// Expression.Constant(filterValue, entityProperty.PropertyType)
// );
// }
// }
// else
// {
// entityProperty = typeof(T).GetProperty(propertyName);
// if (entityProperty != null)
// {
// propertyExpression = Expression.Property(parameter, entityProperty);
// comparison = Expression.Equal(
// propertyExpression,
// Expression.Constant(filterValue, entityProperty.PropertyType)
// );
// }
// }
// if (comparison != null)
// {
// finalExpression =
// finalExpression == null
// ? comparison
// : Expression.AndAlso(finalExpression, comparison);
// }
// }
// if (finalExpression != null)
// {
// var lambda = Expression.Lambda<Func<T, bool>>(finalExpression, parameter);
// query = query.Where(lambda);
// }
// return query;
// }
// }
@@ -0,0 +1,27 @@
// using System;
// using System.Collections.Generic;
// using System.Linq;
// using System.Threading.Tasks;
// namespace Commerce.Core.Utility;
// public static class IEnumerableExtensions
// {
// public static IEnumerable<List<T>> Batch<T>(this IEnumerable<T> source, int batchSize)
// {
// var batch = new List<T>(batchSize);
// foreach (var item in source)
// {
// batch.Add(item);
// if (batch.Count == batchSize)
// {
// yield return batch;
// batch = new List<T>(batchSize);
// }
// }
// if (batch.Count > 0)
// {
// yield return batch;
// }
// }
// }
+19
View File
@@ -0,0 +1,19 @@
// using System;
// using System.Collections.Generic;
// using System.Linq;
// using System.Threading.Tasks;
// using Contracts.DTOs;
// using Contracts.DTOs.Generic;
// namespace Commerce.Core.Utility;
// public static class Pagination<T>
// where T : class
// {
// public static IQueryable<T> Paginate(IQueryable<T> query, Pagination pagination)
// {
// return query
// .Skip((pagination.CurrentPage.Value - 1) * pagination.PageSize.Value)
// .Take(pagination.PageSize.Value);
// }
// }
@@ -0,0 +1,21 @@
// using System;
// using System.Collections.Generic;
// using System.Linq;
// using System.Threading.Tasks;
// using Contracts.DTOs;
// using Contracts.DTOs.Generic;
// namespace Commerce.Core.Utility;
// public static class PaginationExtension
// {
// public static IQueryable<T> Paginate<T>(this IQueryable<T> query, Pagination pagination)
// {
// if (pagination.CurrentPage.HasValue && pagination.PageSize.HasValue)
// return query
// .Skip((pagination.CurrentPage.Value - 1) * pagination.PageSize.Value)
// .Take(pagination.PageSize.Value);
// else
// return query;
// }
// }
@@ -0,0 +1,31 @@
// using System;
// using System.Collections.Generic;
// using System.Linq;
// using System.Linq.Expressions;
// using System.Threading.Tasks;
// using Contracts.DTOs.Generic;
// namespace Commerce.Core.Utility;
// public static class QueryableExtensions
// {
// public static IQueryable<T> ApplySorting<T>(this IQueryable<T> source, SortModel sortBy)
// {
// // Parameter for lambda expression, e.g., "p" in "p => p.Property"
// var parameter = Expression.Parameter(typeof(T), "p");
// // Access the property on the parameter, e.g., "p.Property"
// var property = Expression.Property(parameter, sortBy.SortBy);
// // Cast property access to an object (boxing value types)
// var converted = Expression.Convert(property, typeof(object));
// // Create the lambda expression, e.g., "p => (object)p.Property"
// var keySelector = Expression.Lambda<Func<T, object>>(converted, parameter);
// // Apply OrderBy or OrderByDescending based on the ascending flag
// return sortBy.SortDirection == SortDir.Ascending
// ? source.OrderBy(keySelector)
// : source.OrderByDescending(keySelector);
// }
// }
+45
View File
@@ -0,0 +1,45 @@
// using System;
// using System.Collections.Generic;
// using System.Linq;
// using System.Linq.Expressions;
// using System.Reflection;
// using System.Threading.Tasks;
// using Contracts.DTOs.Generic;
// using Microsoft.EntityFrameworkCore;
// namespace Commerce.Core.Utility;
// public static class SortExt
// {
// public static IQueryable<T> ApplySort<T>(this IQueryable<T> query, SortModel sortModel)
// {
// if (string.IsNullOrWhiteSpace(sortModel?.SortBy))
// return query;
// // Get the property info
// var propertyInfo = typeof(T).GetProperty(
// sortModel.SortBy,
// BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance
// );
// if (propertyInfo == null)
// throw new ArgumentException(
// $"No property '{sortModel.SortBy}' on type '{typeof(T).Name}'"
// );
// // Create x => x.PropertyName
// var parameter = Expression.Parameter(typeof(T), "x");
// var property = Expression.Property(parameter, propertyInfo);
// var lambda = Expression.Lambda(property, parameter);
// // Call OrderBy or OrderByDescending dynamically
// string methodName = sortModel.SortBy?.ToLower() == "desc" ? "OrderByDescending" : "OrderBy";
// var result = typeof(Queryable)
// .GetMethods()
// .First(m => m.Name == methodName && m.GetParameters().Length == 2)
// .MakeGenericMethod(typeof(T), propertyInfo.PropertyType)
// .Invoke(null, new object[] { query, lambda });
// return (IQueryable<T>)result;
// }
// }