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
+30
View File
@@ -0,0 +1,30 @@
# Build results
bin/
obj/
[BBb]uild/
[Oo]bj/
[Dd]ebug/
[Rr]elease/
# NuGet
*.nupkg
*.snupkg
packages/
# IDE
.vs/
.vscode/
*.user
*.suo
# Test results
TestResults/
*.trx
coverage*/
# OS
.DS_Store
Thumbs.db
# Uploads
wwwroot/Uploads/
+52
View File
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
namespace Commerce.Config;
public static class Authentication
{
public static IServiceCollection Authenticate(this IServiceCollection services, ConfigurationManager configuration)
{
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
var jwtKey = configuration["JWT:Key"] ?? throw new InvalidOperationException("JWT:Key is missing in configuration.");
var Key = Encoding.UTF8.GetBytes(jwtKey);
o.SaveToken = true;
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false, // on production make it true
ValidateAudience = false, // on production make it true
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = configuration["JWT:Issuer"],
ValidAudience = configuration["JWT:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Key),
ClockSkew = TimeSpan.Zero
};
o.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Append("IS-TOKEN-EXPIRED", "true");
}
return Task.CompletedTask;
}
};
});
return services;
}
}
+22
View File
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Commerce.Config;
public static class Authorization
{
public static IServiceCollection Authorize(this IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy("Admin", policy => policy.RequireRole("ADMIN"));
options.AddPolicy("Moderator", policy => policy.RequireRole("Moderator"));
options.AddPolicy("Seller", policy => policy.RequireRole("Seller"));
});
return services;
}
}
@@ -0,0 +1,55 @@
// using System;
// using System.Collections.Generic;
// using System.Linq;
// using System.Threading.Tasks;
// using Microsoft.AspNetCore.Authorization;
// using Core.Services;
// using System.Security.Claims;
// using Microsoft.AspNetCore.Identity;
// using System.Threading.Tasks;
// namespace Commerce.Config.AuthorizeHandlers;
// public class OrderOwnerOrAdminHandler : AuthorizationHandler<OrderOwnerOrAdminRequirement, Guid>
// {
// // private readonly IServiceProvider _serviceProvider;
// // public OrderOwnerOrAdminHandler(IServiceProvider serviceProvider)
// // {
// // _serviceProvider = serviceProvider;
// // }
// private readonly IServiceProvider _serviceProvider;
// public OrderOwnerOrAdminHandler(IServiceProvider serviceProvider)
// {
// _serviceProvider = serviceProvider;
// }
// protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
// OrderOwnerOrAdminRequirement requirement, Guid orderId)
// {
// using var scope = _serviceProvider.CreateScope();
// var orderService = scope.ServiceProvider.GetRequiredService<OrderService>();
// var user = context.User;
// var userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
// var isAdmin = user.IsInRole("Admin");
// if (isAdmin)
// {
// context.Succeed(requirement);
// return;
// }
// // // var order = await orderService.GetOrderById(orderId.ToString());
// // if (order != null && order.CustomerId == userId)
// // {
// // context.Succeed(requirement);
// // }
// }
// }
// public class OrderOwnerOrAdminRequirement : IAuthorizationRequirement { }
+47
View File
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Commerce.Config;
public static class Cors
{
public static IServiceCollection AddCustomCors(this IServiceCollection services)
{
services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
{
builder
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
.WithExposedHeaders("X-Pagination"); // This line!
// .AllowCredentials()
// .SetIsOriginAllowedToAllowWildcardSubdomains() // For SameSite=None
// .WithExposedHeaders("*");
});
options.AddPolicy(
"default",
builder =>
{
builder
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
.WithExposedHeaders("X-Pagination"); // This line!
// .AllowCredentials()
// .SetIsOriginAllowedToAllowWildcardSubdomains() // For SameSite=None
// .WithExposedHeaders("*");
}
);
});
return services;
}
}
+27
View File
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Commerce.Config;
public static class FluentEmailExtensions
{
public static void RegFluentEmail(
this IServiceCollection services,
ConfigurationManager configuration
)
{
var emailSettings = configuration.GetSection("MailSettings");
var defaultFromEmail = emailSettings["Mail"];
var host = emailSettings["Host"];
var port = emailSettings.GetValue<int>("Port");
var userName = emailSettings["UserName"];
var password = emailSettings["Password"];
services
.AddFluentEmail(defaultFromEmail)
.AddSmtpSender(host, port, userName, password)
.AddRazorRenderer();
}
}
+21
View File
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Commerce.Core.Services;
using Microsoft.AspNetCore.Authorization;
namespace Commerce.Config;
public static class RegisteringServices
{
public static IServiceCollection RegisterServices(this IServiceCollection services)
{
//test
services.AddScoped<ProductService>();
services.AddScoped<CategoryService>();
return services;
}
}
+29
View File
@@ -0,0 +1,29 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Scalar.AspNetCore;
namespace Commerce.Config;
public static class Swagger
{
public static IServiceCollection AddSwag(this IServiceCollection services)
{
services.AddOpenApi(options =>
{
options.AddDocumentTransformer((document, context, cancellationToken) =>
{
document.Info.Title = "Commerce API";
document.Info.Version = "v1";
return Task.CompletedTask;
});
});
return services;
}
public static IEndpointRouteBuilder UseSwag(this IEndpointRouteBuilder app)
{
app.MapScalarApiReference();
return app;
}
}
+57
View File
@@ -0,0 +1,57 @@
FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS base
# WORKDIR /app
# ENV DOTNET_URLS=http://*:2000
#ENV ConnectionStrings__PostgreDB="Host=db;Database=ECommerce;Username=said;Password=aaa111!!!AAA;Port=5435;"
ENV ASPNETCORE_ENVIRONMENT=Production
FROM base AS build
WORKDIR /src
RUN dotnet tool install --global dotnet-ef --version 9.0.*
ENV PATH="$PATH:/root/.dotnet/tools"
COPY ./Core/ ./Core
COPY ./Contracts/ ./Contracts
COPY ./Domain/ ./Domain
COPY ./ECommerce_API/ ./ECommerce_API
RUN dotnet ef migrations script --startup-project ECommerce_API --project Domain -o sqlscript.sql
WORKDIR /src/ECommerce_API
RUN dotnet restore
RUN dotnet build -c Release -o /build
WORKDIR /src
FROM build AS publish
WORKDIR /src/ECommerce_API
RUN dotnet publish -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine3.21-amd64 AS runtime
WORKDIR /app
COPY --from=publish /app/publish .
COPY --from=build /src/sqlscript.sql .
# ENV PATH="$PATH:/root/.dotnet/tools"
ENV ASPNETCORE_URLS=http://*:80
# COPY --from=publish /src/Core.Contracts/EmailTemplates/. ./EmailTemplates
ENTRYPOINT ["dotnet", "ECommerce_API.dll"]
+35
View File
@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentEmail.Core" Version="3.0.2" />
<PackageReference Include="FluentEmail.Razor" Version="3.0.2" />
<PackageReference Include="FluentEmail.Smtp" Version="3.0.2" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.5" />
<PackageReference Include="Scalar.AspNetCore" Version="2.12.11" />
<!-- <PackageReference Include="Npgsql" Version="8.0.3" /> -->
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.12.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.5" />
<!-- <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" /> -->
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../ECommerce.Core/ECommerce.Core.csproj" />
<ProjectReference Include="../ECommerce.Contracts/ECommerce.Contracts.csproj" />
<!-- <ProjectReference Include="..\Domain\Domain.csproj" /> -->
</ItemGroup>
</Project>
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Commerce.Endpoints.v1;
namespace Commerce.Endpoints;
public static class RegisterEndpoints
{
public static void MapAPIv1(this IEndpointRouteBuilder app)
{
//hello
app.MapGroup("api/v1/").ProductEndpoints().WithTags("Product").RequireCors("default");
app.MapGroup("api/v1/").CategoryEndpoints().WithTags("Category").RequireCors("default");
}
public static void MapAPIv2(this IEndpointRouteBuilder app)
{
//hello
app.MapGroup("api/v1/").ProductEndpoints().WithTags("Product").RequireCors("default");
app.MapGroup("api/v1/").CategoryEndpoints().WithTags("Category").RequireCors("default");
}
}
+90
View File
@@ -0,0 +1,90 @@
using Commerce.Contracts.DTOs;
using Commerce.Core.Services;
using Commerce.Domain.Entities;
using Generic.Contracts.Generics;
using Microsoft.AspNetCore.Mvc;
namespace Commerce.Endpoints.v1;
public static class CategoryMap
{
public static RouteGroupBuilder CategoryEndpoints(this RouteGroupBuilder group)
{
// No Fetch
group.MapGet(
"categories",
async (
[AsParameters] CategoryFilter categoryFilter,
[AsParameters] Pagination pagination,
CategoryService categoryService,
HttpContext httpContext
) =>
{
var result = await categoryService.FetchCategory(categoryFilter, pagination);
httpContext.Response.Headers["X-Pagination"] = result.Item2.ToString();
return result.Item1;
}
);
group.MapGet(
"categories/tree",
async (string? name, CategoryService categoryService) =>
{
var result = await categoryService.GetAllCategories_Tree(name ?? "");
return result.Select(x => Category.ToVM(x)).ToList();
}
);
// No Get
group.MapGet(
"category/{id}",
async (Guid id, CategoryService categoryService) =>
{
return await categoryService.GetCategory(id);
}
);
// No Delete
group
.MapDelete(
"category/{id}",
async (Guid id, HttpContext httpContext, CategoryService categoryService) =>
{
var userId = httpContext.User.Claims.First(x => x.Type == "uid").Value;
await categoryService.RemoveCategory(id);
}
)
.RequireAuthorization();
// No Put
group.MapPut(
"category",
async ([FromBody] CategoryVM category, CategoryService categoryService) =>
{
await categoryService.UpdateCategory(category);
}
);
// No Post
group.MapPost(
"category",
async ([FromBody] CategoryVM category, CategoryService categoryService) =>
{
await categoryService.CreateCategory(category);
}
);
return group;
}
}
+77
View File
@@ -0,0 +1,77 @@
using Commerce.Contracts.DTOs;
using Commerce.Core.Services;
using Generic.Contracts.Generics;
using Microsoft.AspNetCore.Mvc;
namespace Commerce.Endpoints.v1;
public static class ProductMap
{
public static RouteGroupBuilder ProductEndpoints(this RouteGroupBuilder group)
{
// No Fetch
group.MapGet(
"products",
async (
[AsParameters] ProductFilter productFilter,
[AsParameters] Pagination pagination,
HttpContext httpContext,
ProductService productService
) =>
{
var result = await productService.FetchProduct(productFilter, pagination);
httpContext.Response.Headers["X-Pagination"] = result.Item2.ToString();
return result.Item1;
}
);
// No Get
group.MapGet(
"product/{id}",
async (Guid id, ProductService productService) =>
{
return await productService.GetProduct(id);
}
);
// No Delete
group.MapDelete(
"product/{id}",
async (Guid id, HttpContext httpContext, ProductService productService) =>
{
var userId = httpContext.User.Claims.First(x => x.Type == "uid").Value;
await productService.RemoveProduct(id);
}
)
.RequireAuthorization();
// No Put
group.MapPut(
"product",
async ([FromBody] ProductVM product, ProductService productService) =>
{
await productService.UpdateProduct(product);
}
);
// No Post
group.MapPost(
"product",
async ([FromBody] ProductVM product, ProductService productService) =>
{
await productService.CreateProduct(product);
}
);
return group;
}
}
+103
View File
@@ -0,0 +1,103 @@
using Commerce.Config;
using Commerce.Endpoints;
using Commerce.Contracts.Options;
using Commerce.Domain;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
#pragma warning disable CS0618
Npgsql.NpgsqlConnection.GlobalTypeMapper.EnableDynamicJson();
#pragma warning restore CS0618
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
// builder.Services.AddSwaggerGen();
builder.Services.Configure<JWT>(builder.Configuration.GetSection("JWT"));
// builder.Services.AddDbContext<Context>(
// options => options.(builder.Configuration["ConnectionStrings:maria"], ServerVersion.AutoDetect(builder.Configuration["ConnectionStrings:maria"])));
var connectionString = builder.Configuration.GetConnectionString("PostgreDB");
var dataSourceBuilder = new Npgsql.NpgsqlDataSourceBuilder(connectionString);
dataSourceBuilder.EnableDynamicJson();
var dataSource = dataSourceBuilder.Build();
builder.Services.AddDbContext<Context>(options =>
options.UseNpgsql(dataSource)
);
// builder.Services.AddCustomCors();
builder.Services.RegFluentEmail(builder.Configuration);
builder.Services.AddCustomCors();
builder.Services.AddSwag();
builder.Services.Authenticate(builder.Configuration);
builder.Services.Authorize();
builder.Services.RegisterServices();
// builder.Services.AddAntiforgery();
builder.WebHost.ConfigureKestrel(options =>
{
options.Limits.MaxRequestBodySize = 512 * 1024 * 1024; // 512 MB
});
// Configure form options for multipart body length
builder.Services.Configure<FormOptions>(options =>
{
options.MultipartBodyLengthLimit = 512 * 1024 * 1024; // 512 MB
});
// builder.WebHost.UseUrls("http://*:80"); // Explicit HTTP binding
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<Context>();
db.Database.Migrate(); // Applies pending migrations
}
// if (args.Contains("--migrate"))
// {
// using var scope = app.Services.CreateScope();
// var db = scope.ServiceProvider.GetRequiredService<Context>();
// db.Database.Migrate();
// }
app.UseRouting();
app.UseStaticFiles();
app.UseCors("default");
app.UseAuthentication();
app.UseAuthorization();
// Configure the HTTP request pipeline.
// if (app.Environment.IsDevelopment())
// {
// app.UseSwagger();
// app.UseSwaggerUI();
// }
// app.UseAntiforgery();
// app.UseHttpsRedirection();
app.MapOpenApi();
if (app.Environment.IsDevelopment())
{
app.UseSwag();
}
app.MapAPIv1();
app.Run();
@@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:20864",
"sslPort": 44342
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:2001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7153;http://localhost:5141",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
+56
View File
@@ -0,0 +1,56 @@
Using launch settings from /mnt/Dataa/Work/Programming/MainProgram/Backend/APIs/Commerce/Commerce.API/Properties/launchSettings.json...
Building...
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (22ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT "MigrationId", "ProductVersion"
FROM "__EFMigrationsHistory"
ORDER BY "MigrationId";
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (11ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
"MigrationId" character varying(150) NOT NULL,
"ProductVersion" character varying(32) NOT NULL,
CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId")
);
info: Microsoft.EntityFrameworkCore.Migrations[20411]
Acquiring an exclusive lock for migration application. See https://aka.ms/efcore-docs-migrations-lock for more information if this takes too long.
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
LOCK TABLE "__EFMigrationsHistory" IN ACCESS EXCLUSIVE MODE
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT "MigrationId", "ProductVersion"
FROM "__EFMigrationsHistory"
ORDER BY "MigrationId";
info: Microsoft.EntityFrameworkCore.Migrations[20405]
No migrations were applied. The database is already up to date.
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:2001
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: /mnt/Dataa/Work/Programming/MainProgram/Backend/APIs/Commerce/Commerce.API
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (2ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT count(*)::int
FROM "Products" AS p
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (13ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT p."Id", p."CategoryId", p."CategoryName", p."CodeName", p."Created", p."Description", p."Discount", p."IsBundle", p."Name", p."Photos", p."Price", p."TechnicalDetails"
FROM "Products" AS p
ORDER BY gen_random_uuid()
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (2ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT count(*)::int
FROM "Products" AS p
warn: Microsoft.EntityFrameworkCore.Query[10102]
The query uses a row limiting operator ('Skip'/'Take') without an 'OrderBy' operator. This may lead to unpredictable results. If the 'Distinct' operator is used after 'OrderBy', then make sure to use the 'OrderBy' operator after 'Distinct' as the ordering would otherwise get erased.
warn: Microsoft.EntityFrameworkCore.Query[10102]
The query uses a row limiting operator ('Skip'/'Take') without an 'OrderBy' operator. This may lead to unpredictable results. If the 'Distinct' operator is used after 'OrderBy', then make sure to use the 'OrderBy' operator after 'Distinct' as the ordering would otherwise get erased.
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (14ms) [Parameters=[@__p_1='?' (DbType = Int32), @__p_0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SELECT p."Id", p."CategoryId", p."CategoryName", p."CodeName", p."Created", p."Description", p."Discount", p."IsBundle", p."Name", p."Photos", p."Price", p."TechnicalDetails"
FROM "Products" AS p
LIMIT @__p_1 OFFSET @__p_0
@@ -0,0 +1,32 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"maria": "UserID=root;Password=ama111AMA!!!;Host=localhost;Port=3306;Database=ECommerce;Protocol=TCP;",
"PostgreDB": "Host=localhost;Database=ECommerce;Username=test;Password=test;Port=5432;IncludeErrorDetail=true;"
},
"JWT": {
"Key": "thisisasecretKeyIneedtoprovideenoughletterstomakeitwork",
"Issuer": "https://hello.com",
"Audience": "hello.com",
"DurationInMinutes": 10
},
"EmailTemplate": {
"EmailConfirmation": "placeHolder",
"ResetPassword": "placeHolder"
},
"hostname": "placeHolder",
"MailSettings": {
"Mail": "merge1942@gmail.com",
"DisplayName": "Merge",
"UserName": "merge1942@gmail.com",
"Password": "ftyqwyqpqtixalxp",
"Host": "smtp.gmail.com",
"Port": 587
}
}
+33
View File
@@ -0,0 +1,33 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"maria": "UserID=root;Password=ama111AMA!!!;Host=localhost;Port=3306;Database=ECommerce;Protocol=TCP;",
"PostgreDB": "Host=localhost;Database=ECommerce;Username=test;Password=test;Port=5432;"
},
"Domain": "https://api.mass-tech.net",
"JWT": {
"Key": "thisisasecretKeyIneedtoprovideenoughletterstomakeitwork",
"Issuer": "https://hello.com",
"Audience": "hello.com",
"DurationInMinutes": 10
},
"EmailTemplate": {
"EmailConfirmation": "placeHolder",
"ResetPassword": "placeHolder"
},
"hostname": "placeHolder",
"MailSettings": {
"Mail": "merge1942@gmail.com",
"DisplayName": "Merge",
"UserName": "merge1942@gmail.com",
"Password": "ftyqwyqpqtixalxp",
"Host": "smtp.gmail.com",
"Port": 587
}
}
View File
+11
View File
@@ -0,0 +1,11 @@
namespace Commerce.Contracts.Constants;
public static class AppCon
{
public static class ConnectionStrings
{
public const string SQLServer = "server=(localdb)\\MSSQLLocalDB;database=Ecommerce;Integrated Security=true";
public const string MariaDB = "UserID=said;Password=aaa111!!!AAA;server=db;Port=3310;Database=ecommercedb;Protocol=TCP";
public const string Postgresql = "Host=localhost;Database=ecommerce;Username=said1996;Password=a44781680;";
}
}
+63
View File
@@ -0,0 +1,63 @@
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace Commerce.Contracts.Constants;
public static class AppEnum
{
public enum OrderStatus
{
FillingCart,
// Paid,
InQueue,
DeliveryInProgress,
Delivered,
UserNotFound,
Delayed,
// Reserved
}
public enum QueryOrder
{
[Display(Name = "Popular")]
Name,
Recent,
Oldest,
[Display(Name = "Lowest Price")]
Lowest,
[Display(Name = "Highest Price")]
Highest
}
public enum Roles
{
SuperAdmin,
Admin,
Moderator,
Standard
}
public enum ShippingMethod
{
[Display(Name = "Standard shipping - $10.00")] NormalShipping,
[Display(Name = "Fast shipping - $50.00")] FastShipping,
}
public enum SalesOrPurchases
{
None,
Purchase,
Sales,
}
}
@@ -0,0 +1,10 @@
namespace Commerce.Contracts.DTOs;
public class CategoryFilter
{
public string? Name { get; set; }
public bool? IsBundle { get; set; }
public string? Language { get; set; }
public bool? Random { get; set; } = false;
}
+25
View File
@@ -0,0 +1,25 @@
namespace Commerce.Contracts.DTOs;
public class CategoryVM
{
public Guid? Id { get; set; }
public string[]? Photos { get; set; }
public Guid? ParentCategoryId { get; set; }
public ICollection<CategoryVM>? ChildCategories { get; set; }
public bool IsBundle { get; set; }
public List<CategoryTranslationVM> Translations { get; set; } = new();
}
public class CategoryTranslationVM
{
public string Language { get; set; } = string.Empty;
public CategoryInfoVM Info { get; set; } = new();
}
public class CategoryInfoVM
{
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public List<TechnicalDetailVM> TechnicalDetails { get; set; } = new();
}
+14
View File
@@ -0,0 +1,14 @@
namespace Commerce.Contracts.DTOs;
public class ProductFilter
{
public string? Name { get; set; }
public double? Price { get; set; }
public double? Discount { get; set; }
public bool? IsBundle { get; set; }
public Guid? CategoryId { get; set; }
public string? Language { get; set; }
public bool? Random { get; set; } = false;
}
+38
View File
@@ -0,0 +1,38 @@
namespace Commerce.Contracts.DTOs;
public class ProductVM
{
public Guid? Id { get; set; }
public string[]? Photos { get; set; }
public double? Price { get; set; }
public double? Discount { get; set; }
public DateTime? Created { get; set; }
public string? CodeName { get; set; }
public string? CategoryName { get; set; }
public bool IsBundle { get; set; }
public ICollection<ProductVM>? ChildProducts { get; set; }
public Guid? CategoryId { get; set; }
public Guid? ParentCategoryId { get; set; }
public ICollection<CategoryVM>? ChildCategories { get; set; }
public List<ProductTranslationVM>? Translations { get; set; } = new();
}
public class ProductTranslationVM
{
public string Language { get; set; } = string.Empty;
public ProductInfoVM Info { get; set; } = new();
}
public class ProductInfoVM
{
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public List<TechnicalDetailVM> TechnicalDetails { get; set; } = new();
}
public class TechnicalDetailVM
{
public string Key { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
}
@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Commerce.Contracts.DTOs;
public class TechnicalDetail
{
public string Key { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
}
+18
View File
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation" Version="12.1.1" />
</ItemGroup>
<!-- <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components" Version="8.0.11" />
</ItemGroup> -->
</Project>
+13
View File
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Commerce.Contracts.Exceptions;
public class NotFoundException : Exception
{
public NotFoundException(string message, Exception innerException) : base(message, innerException)
{
}
}
+13
View File
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Commerce.Contracts.Options;
public class JWT
{
public string Key { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public double DurationInMinutes { get; set; }
}
@@ -0,0 +1,12 @@
using FluentValidation;
using Commerce.Contracts.DTOs;
namespace Commerce.Contracts.Validators;
public class CategoryVMValidator : AbstractValidator<CategoryVM>
{
public CategoryVMValidator()
{
RuleFor(x => x.Translations).NotEmpty().WithMessage("At least one translation is required");
}
}
@@ -0,0 +1,14 @@
using FluentValidation;
using Commerce.Contracts.DTOs;
namespace Commerce.Contracts.Validators;
public class ProductVMValidator : AbstractValidator<ProductVM>
{
public ProductVMValidator()
{
RuleFor(x => x.Translations).NotEmpty().WithMessage("At least one translation is required");
RuleFor(x => x.Price).GreaterThan(0).When(x => x.Price.HasValue).WithMessage("Price must be greater than 0");
RuleFor(x => x.CategoryId).NotEmpty().When(x => !x.IsBundle).WithMessage("Category is required for products");
}
}
@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Commerce.Contracts.DTOs;
using Commerce.Core.Services;
using Commerce.Domain;
using Commerce.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Xunit;
namespace Commerce.Core.Tests;
public class CategoryServiceTests
{
private Context GetContext()
{
var options = new DbContextOptionsBuilder<Context>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
return new Context(options);
}
[Fact]
public async Task GetAllCategories_ShouldFilterByLocalizedName()
{
// Arrange
using var context = GetContext();
await context.Database.EnsureCreatedAsync();
var id1 = Guid.NewGuid();
context.Categories.AddRange(new List<Category>
{
new Category {
Id = id1,
Translations = new List<CategoryTranslation>
{
new CategoryTranslation { Language = "en", Info = new CategoryInfo { Name = "Electronics", Description = "Desc" } },
new CategoryTranslation { Language = "ar", Info = new CategoryInfo { Name = "إلكترونيات", Description = "Desc" } }
},
Photos = Array.Empty<string>()
},
new Category {
Id = Guid.NewGuid(),
Translations = new List<CategoryTranslation>
{
new CategoryTranslation { Language = "en", Info = new CategoryInfo { Name = "Fashion", Description = "Desc" } }
},
Photos = Array.Empty<string>()
}
});
await context.SaveChangesAsync();
var service = new CategoryService(context);
// Act - Search for Arabic name
var resultsAr = await service.GetAllCategories("إلكترونيات");
// Act - Search for English name
var resultsEn = await service.GetAllCategories("Elec");
// Assert
Assert.Single(resultsAr);
Assert.Equal(id1, resultsAr.First().Id);
Assert.Single(resultsEn);
Assert.Equal(id1, resultsEn.First().Id);
}
}
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Commerce.Core\Commerce.Core.csproj" />
<ProjectReference Include="..\Commerce.Contracts\Commerce.Contracts.csproj" />
<ProjectReference Include="..\Commerce.Domain\Commerce.Domain.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Commerce.Contracts.DTOs;
using Commerce.Domain.Entities;
using Xunit;
using TechnicalDetail = Commerce.Domain.Entities.TechnicalDetail;
namespace Commerce.Core.Tests;
public class LocalizationMappingTests
{
[Fact]
public void Product_ToVM_ShouldMapIndependentTechnicalDetails()
{
// Arrange
var product = new Product
{
Id = Guid.NewGuid(),
Translations = new List<ProductTranslation>
{
new ProductTranslation
{
Language = "en",
Info = new ProductInfo
{
Name = "Product 1",
TechnicalDetails = new List<TechnicalDetail> { new TechnicalDetail { Key = "Color", Value = "Red" } }
}
},
new ProductTranslation
{
Language = "ar",
Info = new ProductInfo
{
Name = "منتج 1",
TechnicalDetails = new List<TechnicalDetail> { new TechnicalDetail { Key = "اللون", Value = "أحمر" } }
}
}
}
};
// Act
var vm = Product.ToVM(product);
// Assert
Assert.NotNull(vm.Translations);
Assert.Equal(2, vm.Translations.Count);
var enTrans = vm.Translations.First(t => t.Language == "en");
Assert.Single(enTrans.Info.TechnicalDetails);
Assert.Equal("Color", enTrans.Info.TechnicalDetails[0].Key);
Assert.Equal("Red", enTrans.Info.TechnicalDetails[0].Value);
var arTrans = vm.Translations.First(t => t.Language == "ar");
Assert.Single(arTrans.Info.TechnicalDetails);
Assert.Equal("اللون", arTrans.Info.TechnicalDetails[0].Key);
Assert.Equal("أحمر", arTrans.Info.TechnicalDetails[0].Value);
}
[Fact]
public void Product_ToEntity_ShouldMapIndependentTechnicalDetails()
{
// Arrange
var vm = new ProductVM
{
Id = Guid.NewGuid(),
Translations = new List<ProductTranslationVM>
{
new ProductTranslationVM
{
Language = "en",
Info = new ProductInfoVM
{
Name = "Product 1",
TechnicalDetails = new List<TechnicalDetailVM> { new TechnicalDetailVM { Key = "Weight", Value = "1kg" } }
}
}
}
};
// Act
var entity = Product.ToEntity(vm);
// Assert
Assert.NotNull(entity.Translations);
Assert.Single(entity.Translations.First(t => t.Language == "en").Info.TechnicalDetails);
Assert.Equal("Weight", entity.Translations.First(t => t.Language == "en").Info.TechnicalDetails[0].Key);
Assert.Equal("1kg", entity.Translations.First(t => t.Language == "en").Info.TechnicalDetails[0].Value);
}
[Fact]
public void Category_ToVM_ShouldMapIndependentTechnicalDetails()
{
// Arrange
var category = new Category
{
Id = Guid.NewGuid(),
Translations = new List<CategoryTranslation>
{
new CategoryTranslation
{
Language = "en",
Info = new CategoryInfo
{
Name = "Category 1",
TechnicalDetails = new List<TechnicalDetail> { new TechnicalDetail { Key = "Material", Value = "Leather" } }
}
}
}
};
// Act
var vm = Category.ToVM(category);
// Assert
Assert.NotNull(vm.Translations);
Assert.Single(vm.Translations.First(t => t.Language == "en").Info.TechnicalDetails);
Assert.Equal("Material", vm.Translations.First(t => t.Language == "en").Info.TechnicalDetails[0].Key);
Assert.Equal("Leather", vm.Translations.First(t => t.Language == "en").Info.TechnicalDetails[0].Value);
}
[Fact]
public void Category_ToEntity_ShouldMapIndependentTechnicalDetails()
{
// Arrange
var vm = new CategoryVM
{
Id = Guid.NewGuid(),
Translations = new List<CategoryTranslationVM>
{
new CategoryTranslationVM
{
Language = "en",
Info = new CategoryInfoVM
{
Name = "Category 1",
TechnicalDetails = new List<TechnicalDetailVM> { new TechnicalDetailVM { Key = "Power", Value = "100W" } }
}
}
}
};
// Act
var entity = Category.ToEntity(vm);
// Assert
Assert.NotNull(entity.Translations);
Assert.Single(entity.Translations.First(t => t.Language == "en").Info.TechnicalDetails);
Assert.Equal("Power", entity.Translations.First(t => t.Language == "en").Info.TechnicalDetails[0].Key);
Assert.Equal("100W", entity.Translations.First(t => t.Language == "en").Info.TechnicalDetails[0].Value);
}
}
+10
View File
@@ -0,0 +1,10 @@
namespace Commerce.Core.Tests;
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}
+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;
// }
// }
+70
View File
@@ -0,0 +1,70 @@
using System.Text.Json;
using Bogus;
using Commerce.Contracts.DTOs;
using Commerce.Domain.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace Commerce.Domain;
public class Context : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
public Context(DbContextOptions<Context> options)
: base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
if (Database.IsNpgsql())
{
modelBuilder.Entity<Product>(entity =>
{
entity.OwnsMany(e => e.Translations, b =>
{
b.ToJson();
b.OwnsOne(t => t.Info, i =>
{
i.OwnsMany(ti => ti.TechnicalDetails);
});
});
});
modelBuilder.Entity<Category>(entity =>
{
entity.OwnsMany(e => e.Translations, b =>
{
b.ToJson();
b.OwnsOne(t => t.Info, i =>
{
i.OwnsMany(ti => ti.TechnicalDetails);
});
});
});
}
else
{
var productTranslationsConverter = new Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter<List<ProductTranslation>, string>(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
v => JsonSerializer.Deserialize<List<ProductTranslation>>(v, (JsonSerializerOptions?)null) ?? new List<ProductTranslation>());
var categoryTranslationsConverter = new Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter<List<CategoryTranslation>, string>(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
v => JsonSerializer.Deserialize<List<CategoryTranslation>>(v, (JsonSerializerOptions?)null) ?? new List<CategoryTranslation>());
modelBuilder.Entity<Product>(entity =>
{
entity.Property(e => e.Translations).HasConversion(productTranslationsConverter);
});
modelBuilder.Entity<Category>(entity =>
{
entity.Property(e => e.Translations).HasConversion(categoryTranslationsConverter);
});
}
}
}
+30
View File
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Bogus" Version="35.6.3" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
<!-- <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" /> -->
<!-- <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" /> -->
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../ECommerce.Contracts/ECommerce.Contracts.csproj" />
</ItemGroup>
</Project>
+108
View File
@@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Commerce.Contracts.DTOs;
namespace Commerce.Domain.Entities;
public class Category
{
public Guid Id { get; set; }
public string[] Photos { get; set; } = Array.Empty<string>();
public Guid? ParentCategoryId { get; set; }
public Category? ParentCategory { get; set; }
public ICollection<Category> ChildCategories { get; set; } = new List<Category>();
public bool IsBundle { get; set; }
// New nested structure for JSONB
public List<CategoryTranslation> Translations { get; set; } = new();
public static CategoryVM ToVM(Category category, string? language = null)
{
var translations = category.Translations?.ToList() ?? new List<CategoryTranslation>();
List<CategoryTranslationVM> selectedTranslations;
if (string.IsNullOrEmpty(language))
{
selectedTranslations = translations
.Select(t => new CategoryTranslationVM
{
Language = t.Language,
Info = new CategoryInfoVM
{
Name = t.Info.Name,
Description = t.Info.Description,
TechnicalDetails = t.Info.TechnicalDetails?.Select(td => new TechnicalDetailVM { Key = td.Key, Value = td.Value }).ToList() ?? new()
}
}).ToList();
}
else
{
var matchedTranslation = translations
.FirstOrDefault(t => t.Language == language)
?? translations.FirstOrDefault(t => t.Language == "en");
selectedTranslations = matchedTranslation != null
? new List<CategoryTranslationVM>
{
new CategoryTranslationVM
{
Language = matchedTranslation.Language,
Info = new CategoryInfoVM
{
Name = matchedTranslation.Info.Name,
Description = matchedTranslation.Info.Description,
TechnicalDetails = matchedTranslation.Info.TechnicalDetails?.Select(td => new TechnicalDetailVM { Key = td.Key, Value = td.Value }).ToList() ?? new()
}
}
}
: new List<CategoryTranslationVM>();
}
return new CategoryVM
{
Id = category.Id,
Photos = category.Photos,
ParentCategoryId = category.ParentCategoryId,
IsBundle = category.IsBundle,
Translations = selectedTranslations,
ChildCategories = category.ChildCategories?.Select(x => Category.ToVM(x, language)).ToList() ?? new List<CategoryVM>(),
};
}
public static Category ToEntity(CategoryVM categoryVM)
{
return new Category
{
Id = categoryVM.Id ?? Guid.NewGuid(),
Photos = categoryVM.Photos ?? Array.Empty<string>(),
ParentCategoryId = categoryVM.ParentCategoryId,
IsBundle = categoryVM.IsBundle,
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 TechnicalDetail { Key = td.Key, Value = td.Value }).ToList() ?? new()
}
}).ToList() ?? new(),
ChildCategories = categoryVM.ChildCategories?.Select(x => Category.ToEntity(x)).ToList() ?? new List<Category>(),
};
}
}
public class CategoryTranslation
{
public string Language { get; set; } = string.Empty;
public CategoryInfo Info { get; set; } = new();
}
public class CategoryInfo
{
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public List<TechnicalDetail> TechnicalDetails { get; set; } = new();
}
+135
View File
@@ -0,0 +1,135 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations.Schema;
using Commerce.Contracts.DTOs;
namespace Commerce.Domain.Entities;
public class Product
{
public Guid Id { get; set; }
public double Price { get; set; }
public double Discount { get; set; }
public DateTime Created { get; set; }
public string CodeName { get; set; } = string.Empty;
public bool IsBundle { get; set; }
public string? CategoryName { get; set; }
public Guid? CategoryId { get; set; }
public string[] Photos { get; set; } = Array.Empty<string>();
[ForeignKey("CategoryId")]
public virtual Category Category { get; set; } = null!;
public virtual List<Product> ChildProducts { get; set; } = new();
public virtual List<Product>? ParentBundles { get; set; }
// New nested structure for JSONB
public List<ProductTranslation> Translations { get; set; } = new();
public static ProductVM ToVM(Product product, string? language = null)
{
var translations = product.Translations?.ToList() ?? new List<ProductTranslation>();
List<ProductTranslationVM> selectedTranslations;
if (string.IsNullOrEmpty(language))
{
selectedTranslations = translations
.Select(t => new ProductTranslationVM
{
Language = t.Language,
Info = new ProductInfoVM
{
Name = t.Info.Name,
Description = t.Info.Description,
TechnicalDetails = t.Info.TechnicalDetails?.Select(td => new TechnicalDetailVM { Key = td.Key, Value = td.Value }).ToList() ?? new()
}
}).ToList();
}
else
{
var matchedTranslation = translations
.FirstOrDefault(t => t.Language == language)
?? translations.FirstOrDefault(t => t.Language == "en");
selectedTranslations = matchedTranslation != null
? new List<ProductTranslationVM>
{
new ProductTranslationVM
{
Language = matchedTranslation.Language,
Info = new ProductInfoVM
{
Name = matchedTranslation.Info.Name,
Description = matchedTranslation.Info.Description,
TechnicalDetails = matchedTranslation.Info.TechnicalDetails?.Select(td => new TechnicalDetailVM { Key = td.Key, Value = td.Value }).ToList() ?? new()
}
}
}
: new List<ProductTranslationVM>();
}
return new ProductVM
{
Id = product.Id,
Price = product.Price,
Discount = product.Discount,
Created = product.Created,
CodeName = product.CodeName,
CategoryName = product.CategoryName,
IsBundle = product.IsBundle,
CategoryId = product.CategoryId,
Photos = product.Photos,
Translations = selectedTranslations,
ChildProducts = product.ChildProducts?.Select(x => Product.ToVM(x, language)).ToList() ?? new List<ProductVM>(),
};
}
public static Product ToEntity(ProductVM productVM)
{
return new Product
{
Id = productVM.Id ?? Guid.NewGuid(),
Price = productVM.Price ?? 0,
Discount = productVM.Discount ?? 0,
Created = productVM.Created ?? DateTime.UtcNow,
CodeName = productVM.CodeName ?? string.Empty,
CategoryName = productVM.CategoryName,
IsBundle = productVM.IsBundle,
CategoryId = productVM.CategoryId ?? Guid.Empty,
Photos = productVM.Photos ?? Array.Empty<string>(),
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 TechnicalDetail { Key = td.Key, Value = td.Value }).ToList() ?? new()
}
}).ToList() ?? new(),
ChildProducts = new List<Product>(),
};
}
}
public class ProductTranslation
{
public string Language { get; set; } = string.Empty;
public ProductInfo Info { get; set; } = new();
}
public class ProductInfo
{
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public List<TechnicalDetail> TechnicalDetails { get; set; } = new();
}
public class TechnicalDetail
{
public string Key { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
}
@@ -0,0 +1,153 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using Commerce.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Commerce.Domain.Migrations
{
[DbContext(typeof(Context))]
[Migration("20260105142147_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore");
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("IsBundle")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<Guid?>("ParentCategoryId")
.HasColumnType("uuid");
b.PrimitiveCollection<string[]>("Photos")
.IsRequired()
.HasColumnType("text[]");
b.HasKey("Id");
b.HasIndex("ParentCategoryId");
b.ToTable("Categorys");
});
modelBuilder.Entity("Commerce.Domain.Entities.Product", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("CategoryId")
.HasColumnType("uuid");
b.Property<string>("CategoryName")
.HasColumnType("text");
b.Property<string>("CodeName")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("Created")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<double>("Discount")
.HasColumnType("double precision");
b.Property<bool>("IsBundle")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.PrimitiveCollection<string[]>("Photos")
.IsRequired()
.HasColumnType("text[]");
b.Property<double>("Price")
.HasColumnType("double precision");
b.Property<Guid?>("ProductId")
.HasColumnType("uuid");
b.Property<Dictionary<string, string>>("TechnicalDetails")
.IsRequired()
.HasColumnType("hstore");
b.HasKey("Id");
b.HasIndex("CategoryId");
b.HasIndex("ProductId");
b.ToTable("Products");
});
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.HasOne("Commerce.Domain.Entities.Category", "ParentCategory")
.WithMany("ChildCategories")
.HasForeignKey("ParentCategoryId");
b.Navigation("ParentCategory");
});
modelBuilder.Entity("Commerce.Domain.Entities.Product", b =>
{
b.HasOne("Commerce.Domain.Entities.Category", "Category")
.WithMany()
.HasForeignKey("CategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Commerce.Domain.Entities.Product", null)
.WithMany("ChildProducts")
.HasForeignKey("ProductId");
b.Navigation("Category");
});
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.Navigation("ChildCategories");
});
modelBuilder.Entity("Commerce.Domain.Entities.Product", b =>
{
b.Navigation("ChildProducts");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Commerce.Domain.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("Npgsql:PostgresExtension:hstore", ",,");
migrationBuilder.CreateTable(
name: "Categorys",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Name = table.Column<string>(type: "text", nullable: false),
Description = table.Column<string>(type: "text", nullable: false),
Photos = table.Column<string[]>(type: "text[]", nullable: false),
ParentCategoryId = table.Column<Guid>(type: "uuid", nullable: true),
IsBundle = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Categorys", x => x.Id);
table.ForeignKey(
name: "FK_Categorys_Categorys_ParentCategoryId",
column: x => x.ParentCategoryId,
principalTable: "Categorys",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "Products",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Name = table.Column<string>(type: "text", nullable: false),
Description = table.Column<string>(type: "text", nullable: false),
Photos = table.Column<string[]>(type: "text[]", nullable: false),
TechnicalDetails = table.Column<Dictionary<string, string>>(type: "hstore", nullable: false),
Price = table.Column<double>(type: "double precision", nullable: false),
Discount = table.Column<double>(type: "double precision", nullable: false),
Created = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
CodeName = table.Column<string>(type: "text", nullable: false),
IsBundle = table.Column<bool>(type: "boolean", nullable: false),
CategoryName = table.Column<string>(type: "text", nullable: true),
CategoryId = table.Column<Guid>(type: "uuid", nullable: false),
ProductId = table.Column<Guid>(type: "uuid", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Products", x => x.Id);
table.ForeignKey(
name: "FK_Products_Categorys_CategoryId",
column: x => x.CategoryId,
principalTable: "Categorys",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Products_Products_ProductId",
column: x => x.ProductId,
principalTable: "Products",
principalColumn: "Id");
});
migrationBuilder.CreateIndex(
name: "IX_Categorys_ParentCategoryId",
table: "Categorys",
column: "ParentCategoryId");
migrationBuilder.CreateIndex(
name: "IX_Products_CategoryId",
table: "Products",
column: "CategoryId");
migrationBuilder.CreateIndex(
name: "IX_Products_ProductId",
table: "Products",
column: "ProductId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Products");
migrationBuilder.DropTable(
name: "Categorys");
}
}
}
@@ -0,0 +1,153 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using Commerce.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Commerce.Domain.Migrations
{
[DbContext(typeof(Context))]
[Migration("20260110093153_ModelUpdate")]
partial class ModelUpdate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore");
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("IsBundle")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<Guid?>("ParentCategoryId")
.HasColumnType("uuid");
b.PrimitiveCollection<string[]>("Photos")
.IsRequired()
.HasColumnType("text[]");
b.HasKey("Id");
b.HasIndex("ParentCategoryId");
b.ToTable("Categories");
});
modelBuilder.Entity("Commerce.Domain.Entities.Product", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("CategoryId")
.HasColumnType("uuid");
b.Property<string>("CategoryName")
.HasColumnType("text");
b.Property<string>("CodeName")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("Created")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<double>("Discount")
.HasColumnType("double precision");
b.Property<bool>("IsBundle")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.PrimitiveCollection<string[]>("Photos")
.IsRequired()
.HasColumnType("text[]");
b.Property<double>("Price")
.HasColumnType("double precision");
b.Property<Guid?>("ProductId")
.HasColumnType("uuid");
b.Property<Dictionary<string, string>>("TechnicalDetails")
.IsRequired()
.HasColumnType("hstore");
b.HasKey("Id");
b.HasIndex("CategoryId");
b.HasIndex("ProductId");
b.ToTable("Products");
});
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.HasOne("Commerce.Domain.Entities.Category", "ParentCategory")
.WithMany("ChildCategories")
.HasForeignKey("ParentCategoryId");
b.Navigation("ParentCategory");
});
modelBuilder.Entity("Commerce.Domain.Entities.Product", b =>
{
b.HasOne("Commerce.Domain.Entities.Category", "Category")
.WithMany()
.HasForeignKey("CategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Commerce.Domain.Entities.Product", null)
.WithMany("ChildProducts")
.HasForeignKey("ProductId");
b.Navigation("Category");
});
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.Navigation("ChildCategories");
});
modelBuilder.Entity("Commerce.Domain.Entities.Product", b =>
{
b.Navigation("ChildProducts");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,100 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Commerce.Domain.Migrations
{
/// <inheritdoc />
public partial class ModelUpdate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Categorys_Categorys_ParentCategoryId",
table: "Categorys");
migrationBuilder.DropForeignKey(
name: "FK_Products_Categorys_CategoryId",
table: "Products");
migrationBuilder.DropPrimaryKey(
name: "PK_Categorys",
table: "Categorys");
migrationBuilder.RenameTable(
name: "Categorys",
newName: "Categories");
migrationBuilder.RenameIndex(
name: "IX_Categorys_ParentCategoryId",
table: "Categories",
newName: "IX_Categories_ParentCategoryId");
migrationBuilder.AddPrimaryKey(
name: "PK_Categories",
table: "Categories",
column: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Categories_Categories_ParentCategoryId",
table: "Categories",
column: "ParentCategoryId",
principalTable: "Categories",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Products_Categories_CategoryId",
table: "Products",
column: "CategoryId",
principalTable: "Categories",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Categories_Categories_ParentCategoryId",
table: "Categories");
migrationBuilder.DropForeignKey(
name: "FK_Products_Categories_CategoryId",
table: "Products");
migrationBuilder.DropPrimaryKey(
name: "PK_Categories",
table: "Categories");
migrationBuilder.RenameTable(
name: "Categories",
newName: "Categorys");
migrationBuilder.RenameIndex(
name: "IX_Categories_ParentCategoryId",
table: "Categorys",
newName: "IX_Categorys_ParentCategoryId");
migrationBuilder.AddPrimaryKey(
name: "PK_Categorys",
table: "Categorys",
column: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Categorys_Categorys_ParentCategoryId",
table: "Categorys",
column: "ParentCategoryId",
principalTable: "Categorys",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Products_Categorys_CategoryId",
table: "Products",
column: "CategoryId",
principalTable: "Categorys",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}
@@ -0,0 +1,152 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using Commerce.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Commerce.Domain.Migrations
{
[DbContext(typeof(Context))]
[Migration("20260119105451_MultiLanguageSupport")]
partial class MultiLanguageSupport
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Dictionary<string, string>>("Description")
.IsRequired()
.HasColumnType("jsonb");
b.Property<bool>("IsBundle")
.HasColumnType("boolean");
b.Property<Dictionary<string, string>>("Name")
.IsRequired()
.HasColumnType("jsonb");
b.Property<Guid?>("ParentCategoryId")
.HasColumnType("uuid");
b.PrimitiveCollection<string[]>("Photos")
.IsRequired()
.HasColumnType("text[]");
b.HasKey("Id");
b.HasIndex("ParentCategoryId");
b.ToTable("Categories");
});
modelBuilder.Entity("Commerce.Domain.Entities.Product", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("CategoryId")
.HasColumnType("uuid");
b.Property<string>("CategoryName")
.HasColumnType("text");
b.Property<string>("CodeName")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("Created")
.HasColumnType("timestamp with time zone");
b.Property<Dictionary<string, string>>("Description")
.IsRequired()
.HasColumnType("jsonb");
b.Property<double>("Discount")
.HasColumnType("double precision");
b.Property<bool>("IsBundle")
.HasColumnType("boolean");
b.Property<Dictionary<string, string>>("Name")
.IsRequired()
.HasColumnType("jsonb");
b.PrimitiveCollection<string[]>("Photos")
.IsRequired()
.HasColumnType("text[]");
b.Property<double>("Price")
.HasColumnType("double precision");
b.Property<Guid?>("ProductId")
.HasColumnType("uuid");
b.Property<Dictionary<string, string>>("TechnicalDetails")
.IsRequired()
.HasColumnType("jsonb");
b.HasKey("Id");
b.HasIndex("CategoryId");
b.HasIndex("ProductId");
b.ToTable("Products");
});
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.HasOne("Commerce.Domain.Entities.Category", "ParentCategory")
.WithMany("ChildCategories")
.HasForeignKey("ParentCategoryId");
b.Navigation("ParentCategory");
});
modelBuilder.Entity("Commerce.Domain.Entities.Product", b =>
{
b.HasOne("Commerce.Domain.Entities.Category", "Category")
.WithMany()
.HasForeignKey("CategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Commerce.Domain.Entities.Product", null)
.WithMany("ChildProducts")
.HasForeignKey("ProductId");
b.Navigation("Category");
});
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.Navigation("ChildCategories");
});
modelBuilder.Entity("Commerce.Domain.Entities.Product", b =>
{
b.Navigation("ChildProducts");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,73 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Commerce.Domain.Migrations
{
/// <inheritdoc />
public partial class MultiLanguageSupport : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.OldAnnotation("Npgsql:PostgresExtension:hstore", ",,");
// Use Sql for all column type changes to include the USING clause
migrationBuilder.Sql("ALTER TABLE \"Products\" ALTER COLUMN \"TechnicalDetails\" TYPE jsonb USING \"TechnicalDetails\"::jsonb;");
migrationBuilder.Sql("ALTER TABLE \"Products\" ALTER COLUMN \"Name\" TYPE jsonb USING jsonb_build_object('en', \"Name\");");
migrationBuilder.Sql("ALTER TABLE \"Products\" ALTER COLUMN \"Description\" TYPE jsonb USING jsonb_build_object('en', \"Description\");");
migrationBuilder.Sql("ALTER TABLE \"Categories\" ALTER COLUMN \"Name\" TYPE jsonb USING jsonb_build_object('en', \"Name\");");
migrationBuilder.Sql("ALTER TABLE \"Categories\" ALTER COLUMN \"Description\" TYPE jsonb USING jsonb_build_object('en', \"Description\");");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("Npgsql:PostgresExtension:hstore", ",,");
migrationBuilder.AlterColumn<Dictionary<string, string>>(
name: "TechnicalDetails",
table: "Products",
type: "hstore",
nullable: false,
oldClrType: typeof(Dictionary<string, string>),
oldType: "jsonb");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "Products",
type: "text",
nullable: false,
oldClrType: typeof(Dictionary<string, string>),
oldType: "jsonb");
migrationBuilder.AlterColumn<string>(
name: "Description",
table: "Products",
type: "text",
nullable: false,
oldClrType: typeof(Dictionary<string, string>),
oldType: "jsonb");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "Categories",
type: "text",
nullable: false,
oldClrType: typeof(Dictionary<string, string>),
oldType: "jsonb");
migrationBuilder.AlterColumn<string>(
name: "Description",
table: "Categories",
type: "text",
nullable: false,
oldClrType: typeof(Dictionary<string, string>),
oldType: "jsonb");
}
}
}
@@ -0,0 +1,171 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using Commerce.Contracts.DTOs;
using Commerce.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Commerce.Domain.Migrations
{
[DbContext(typeof(Context))]
[Migration("20260119121446_TechnicalDetailsForCategories")]
partial class TechnicalDetailsForCategories
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Dictionary<string, string>>("Description")
.IsRequired()
.HasColumnType("jsonb");
b.Property<bool>("IsBundle")
.HasColumnType("boolean");
b.Property<Dictionary<string, string>>("Name")
.IsRequired()
.HasColumnType("jsonb");
b.Property<Guid?>("ParentCategoryId")
.HasColumnType("uuid");
b.PrimitiveCollection<string[]>("Photos")
.IsRequired()
.HasColumnType("text[]");
b.Property<Dictionary<string, List<TechnicalDetail>>>("TechnicalDetails")
.IsRequired()
.HasColumnType("jsonb");
b.HasKey("Id");
b.HasIndex("ParentCategoryId");
b.ToTable("Categories");
});
modelBuilder.Entity("Commerce.Domain.Entities.Product", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid?>("CategoryId")
.HasColumnType("uuid");
b.Property<string>("CategoryName")
.HasColumnType("text");
b.Property<string>("CodeName")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("Created")
.HasColumnType("timestamp with time zone");
b.Property<Dictionary<string, string>>("Description")
.IsRequired()
.HasColumnType("jsonb");
b.Property<double>("Discount")
.HasColumnType("double precision");
b.Property<bool>("IsBundle")
.HasColumnType("boolean");
b.Property<Dictionary<string, string>>("Name")
.IsRequired()
.HasColumnType("jsonb");
b.PrimitiveCollection<string[]>("Photos")
.IsRequired()
.HasColumnType("text[]");
b.Property<double>("Price")
.HasColumnType("double precision");
b.Property<Dictionary<string, List<TechnicalDetail>>>("TechnicalDetails")
.IsRequired()
.HasColumnType("jsonb");
b.HasKey("Id");
b.HasIndex("CategoryId");
b.ToTable("Products");
});
modelBuilder.Entity("ProductProduct", b =>
{
b.Property<Guid>("ChildProductsId")
.HasColumnType("uuid");
b.Property<Guid>("ParentBundlesId")
.HasColumnType("uuid");
b.HasKey("ChildProductsId", "ParentBundlesId");
b.HasIndex("ParentBundlesId");
b.ToTable("ProductProduct");
});
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.HasOne("Commerce.Domain.Entities.Category", "ParentCategory")
.WithMany("ChildCategories")
.HasForeignKey("ParentCategoryId");
b.Navigation("ParentCategory");
});
modelBuilder.Entity("Commerce.Domain.Entities.Product", b =>
{
b.HasOne("Commerce.Domain.Entities.Category", "Category")
.WithMany()
.HasForeignKey("CategoryId");
b.Navigation("Category");
});
modelBuilder.Entity("ProductProduct", b =>
{
b.HasOne("Commerce.Domain.Entities.Product", null)
.WithMany()
.HasForeignKey("ChildProductsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Commerce.Domain.Entities.Product", null)
.WithMany()
.HasForeignKey("ParentBundlesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.Navigation("ChildCategories");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using Commerce.Contracts.DTOs;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Commerce.Domain.Migrations
{
/// <inheritdoc />
public partial class TechnicalDetailsForCategories : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Products_Categories_CategoryId",
table: "Products");
migrationBuilder.DropForeignKey(
name: "FK_Products_Products_ProductId",
table: "Products");
migrationBuilder.DropIndex(
name: "IX_Products_ProductId",
table: "Products");
migrationBuilder.DropColumn(
name: "ProductId",
table: "Products");
migrationBuilder.AlterColumn<Guid>(
name: "CategoryId",
table: "Products",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid");
migrationBuilder.AddColumn<Dictionary<string, List<TechnicalDetail>>>(
name: "TechnicalDetails",
table: "Categories",
type: "jsonb",
nullable: true);
migrationBuilder.CreateTable(
name: "ProductProduct",
columns: table => new
{
ChildProductsId = table.Column<Guid>(type: "uuid", nullable: false),
ParentBundlesId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ProductProduct", x => new { x.ChildProductsId, x.ParentBundlesId });
table.ForeignKey(
name: "FK_ProductProduct_Products_ChildProductsId",
column: x => x.ChildProductsId,
principalTable: "Products",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_ProductProduct_Products_ParentBundlesId",
column: x => x.ParentBundlesId,
principalTable: "Products",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_ProductProduct_ParentBundlesId",
table: "ProductProduct",
column: "ParentBundlesId");
migrationBuilder.AddForeignKey(
name: "FK_Products_Categories_CategoryId",
table: "Products",
column: "CategoryId",
principalTable: "Categories",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Products_Categories_CategoryId",
table: "Products");
migrationBuilder.DropTable(
name: "ProductProduct");
migrationBuilder.DropColumn(
name: "TechnicalDetails",
table: "Categories");
migrationBuilder.AlterColumn<Guid>(
name: "CategoryId",
table: "Products",
type: "uuid",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AddColumn<Guid>(
name: "ProductId",
table: "Products",
type: "uuid",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_Products_ProductId",
table: "Products",
column: "ProductId");
migrationBuilder.AddForeignKey(
name: "FK_Products_Categories_CategoryId",
table: "Products",
column: "CategoryId",
principalTable: "Categories",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Products_Products_ProductId",
table: "Products",
column: "ProductId",
principalTable: "Products",
principalColumn: "Id");
}
}
}
@@ -0,0 +1,169 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using Commerce.Contracts.DTOs;
using Commerce.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Commerce.Domain.Migrations
{
[DbContext(typeof(Context))]
[Migration("20260119180718_MakeTechnicalDetailsNullable")]
partial class MakeTechnicalDetailsNullable
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Dictionary<string, string>>("Description")
.IsRequired()
.HasColumnType("jsonb");
b.Property<bool>("IsBundle")
.HasColumnType("boolean");
b.Property<Dictionary<string, string>>("Name")
.IsRequired()
.HasColumnType("jsonb");
b.Property<Guid?>("ParentCategoryId")
.HasColumnType("uuid");
b.PrimitiveCollection<string[]>("Photos")
.IsRequired()
.HasColumnType("text[]");
b.Property<Dictionary<string, List<TechnicalDetail>>>("TechnicalDetails")
.HasColumnType("jsonb");
b.HasKey("Id");
b.HasIndex("ParentCategoryId");
b.ToTable("Categories");
});
modelBuilder.Entity("Commerce.Domain.Entities.Product", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid?>("CategoryId")
.HasColumnType("uuid");
b.Property<string>("CategoryName")
.HasColumnType("text");
b.Property<string>("CodeName")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("Created")
.HasColumnType("timestamp with time zone");
b.Property<Dictionary<string, string>>("Description")
.IsRequired()
.HasColumnType("jsonb");
b.Property<double>("Discount")
.HasColumnType("double precision");
b.Property<bool>("IsBundle")
.HasColumnType("boolean");
b.Property<Dictionary<string, string>>("Name")
.IsRequired()
.HasColumnType("jsonb");
b.PrimitiveCollection<string[]>("Photos")
.IsRequired()
.HasColumnType("text[]");
b.Property<double>("Price")
.HasColumnType("double precision");
b.Property<Dictionary<string, List<TechnicalDetail>>>("TechnicalDetails")
.HasColumnType("jsonb");
b.HasKey("Id");
b.HasIndex("CategoryId");
b.ToTable("Products");
});
modelBuilder.Entity("ProductProduct", b =>
{
b.Property<Guid>("ChildProductsId")
.HasColumnType("uuid");
b.Property<Guid>("ParentBundlesId")
.HasColumnType("uuid");
b.HasKey("ChildProductsId", "ParentBundlesId");
b.HasIndex("ParentBundlesId");
b.ToTable("ProductProduct");
});
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.HasOne("Commerce.Domain.Entities.Category", "ParentCategory")
.WithMany("ChildCategories")
.HasForeignKey("ParentCategoryId");
b.Navigation("ParentCategory");
});
modelBuilder.Entity("Commerce.Domain.Entities.Product", b =>
{
b.HasOne("Commerce.Domain.Entities.Category", "Category")
.WithMany()
.HasForeignKey("CategoryId");
b.Navigation("Category");
});
modelBuilder.Entity("ProductProduct", b =>
{
b.HasOne("Commerce.Domain.Entities.Product", null)
.WithMany()
.HasForeignKey("ChildProductsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Commerce.Domain.Entities.Product", null)
.WithMany()
.HasForeignKey("ParentBundlesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.Navigation("ChildCategories");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,54 @@
using System.Collections.Generic;
using Commerce.Contracts.DTOs;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Commerce.Domain.Migrations
{
/// <inheritdoc />
public partial class MakeTechnicalDetailsNullable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<Dictionary<string, List<TechnicalDetail>>>(
name: "TechnicalDetails",
table: "Products",
type: "jsonb",
nullable: true,
oldClrType: typeof(Dictionary<string, List<TechnicalDetail>>),
oldType: "jsonb");
migrationBuilder.AlterColumn<Dictionary<string, List<TechnicalDetail>>>(
name: "TechnicalDetails",
table: "Categories",
type: "jsonb",
nullable: true,
oldClrType: typeof(Dictionary<string, List<TechnicalDetail>>),
oldType: "jsonb");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<Dictionary<string, List<TechnicalDetail>>>(
name: "TechnicalDetails",
table: "Products",
type: "jsonb",
nullable: false,
oldClrType: typeof(Dictionary<string, List<TechnicalDetail>>),
oldType: "jsonb",
oldNullable: true);
migrationBuilder.AlterColumn<Dictionary<string, List<TechnicalDetail>>>(
name: "TechnicalDetails",
table: "Categories",
type: "jsonb",
nullable: false,
oldClrType: typeof(Dictionary<string, List<TechnicalDetail>>),
oldType: "jsonb",
oldNullable: true);
}
}
}
@@ -0,0 +1,155 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using Commerce.Domain;
using Commerce.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Commerce.Domain.Migrations
{
[DbContext(typeof(Context))]
[Migration("20260120122747_NestedTranslations")]
partial class NestedTranslations
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("IsBundle")
.HasColumnType("boolean");
b.Property<Guid?>("ParentCategoryId")
.HasColumnType("uuid");
b.PrimitiveCollection<string[]>("Photos")
.IsRequired()
.HasColumnType("text[]");
b.Property<List<CategoryTranslation>>("Translations")
.IsRequired()
.HasColumnType("jsonb");
b.HasKey("Id");
b.HasIndex("ParentCategoryId");
b.ToTable("Categories");
});
modelBuilder.Entity("Commerce.Domain.Entities.Product", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid?>("CategoryId")
.HasColumnType("uuid");
b.Property<string>("CategoryName")
.HasColumnType("text");
b.Property<string>("CodeName")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("Created")
.HasColumnType("timestamp with time zone");
b.Property<double>("Discount")
.HasColumnType("double precision");
b.Property<bool>("IsBundle")
.HasColumnType("boolean");
b.PrimitiveCollection<string[]>("Photos")
.IsRequired()
.HasColumnType("text[]");
b.Property<double>("Price")
.HasColumnType("double precision");
b.Property<List<ProductTranslation>>("Translations")
.IsRequired()
.HasColumnType("jsonb");
b.HasKey("Id");
b.HasIndex("CategoryId");
b.ToTable("Products");
});
modelBuilder.Entity("ProductProduct", b =>
{
b.Property<Guid>("ChildProductsId")
.HasColumnType("uuid");
b.Property<Guid>("ParentBundlesId")
.HasColumnType("uuid");
b.HasKey("ChildProductsId", "ParentBundlesId");
b.HasIndex("ParentBundlesId");
b.ToTable("ProductProduct");
});
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.HasOne("Commerce.Domain.Entities.Category", "ParentCategory")
.WithMany("ChildCategories")
.HasForeignKey("ParentCategoryId");
b.Navigation("ParentCategory");
});
modelBuilder.Entity("Commerce.Domain.Entities.Product", b =>
{
b.HasOne("Commerce.Domain.Entities.Category", "Category")
.WithMany()
.HasForeignKey("CategoryId");
b.Navigation("Category");
});
modelBuilder.Entity("ProductProduct", b =>
{
b.HasOne("Commerce.Domain.Entities.Product", null)
.WithMany()
.HasForeignKey("ChildProductsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Commerce.Domain.Entities.Product", null)
.WithMany()
.HasForeignKey("ParentBundlesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.Navigation("ChildCategories");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,118 @@
using System.Collections.Generic;
using Commerce.Contracts.DTOs;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Commerce.Domain.Migrations
{
/// <inheritdoc />
public partial class NestedTranslations : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "Name",
table: "Products",
newName: "Translations");
migrationBuilder.RenameColumn(
name: "Name",
table: "Categories",
newName: "Translations");
// Data Transformation for Products
migrationBuilder.Sql(@"
UPDATE ""Products""
SET ""Translations"" = (
SELECT jsonb_agg(
jsonb_build_object(
'Language', key,
'Info', jsonb_build_object(
'Name', value,
'Description', COALESCE(""Description""->>key, ''),
'TechnicalDetails', COALESCE(""TechnicalDetails""->key, '[]'::jsonb)
)
)
)
FROM jsonb_each_text(""Translations"")
)
WHERE jsonb_typeof(""Translations"") = 'object';
");
// Data Transformation for Categories
migrationBuilder.Sql(@"
UPDATE ""Categories""
SET ""Translations"" = (
SELECT jsonb_agg(
jsonb_build_object(
'Language', key,
'Info', jsonb_build_object(
'Name', value,
'Description', COALESCE(""Description""->>key, ''),
'TechnicalDetails', COALESCE(""TechnicalDetails""->key, '[]'::jsonb)
)
)
)
FROM jsonb_each_text(""Translations"")
)
WHERE jsonb_typeof(""Translations"") = 'object';
");
migrationBuilder.DropColumn(
name: "Description",
table: "Products");
migrationBuilder.DropColumn(
name: "TechnicalDetails",
table: "Products");
migrationBuilder.DropColumn(
name: "Description",
table: "Categories");
migrationBuilder.DropColumn(
name: "TechnicalDetails",
table: "Categories");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "Translations",
table: "Products",
newName: "Name");
migrationBuilder.RenameColumn(
name: "Translations",
table: "Categories",
newName: "Name");
migrationBuilder.AddColumn<Dictionary<string, string>>(
name: "Description",
table: "Products",
type: "jsonb",
nullable: false);
migrationBuilder.AddColumn<Dictionary<string, List<TechnicalDetail>>>(
name: "TechnicalDetails",
table: "Products",
type: "jsonb",
nullable: true);
migrationBuilder.AddColumn<Dictionary<string, string>>(
name: "Description",
table: "Categories",
type: "jsonb",
nullable: false);
migrationBuilder.AddColumn<Dictionary<string, List<TechnicalDetail>>>(
name: "TechnicalDetails",
table: "Categories",
type: "jsonb",
nullable: true);
}
}
}
@@ -0,0 +1,309 @@
// <auto-generated />
using System;
using Commerce.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Commerce.Domain.Migrations
{
[DbContext(typeof(Context))]
[Migration("20260120132648_QueryableTranslations")]
partial class QueryableTranslations
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("IsBundle")
.HasColumnType("boolean");
b.Property<Guid?>("ParentCategoryId")
.HasColumnType("uuid");
b.PrimitiveCollection<string[]>("Photos")
.IsRequired()
.HasColumnType("text[]");
b.HasKey("Id");
b.HasIndex("ParentCategoryId");
b.ToTable("Categories");
});
modelBuilder.Entity("Commerce.Domain.Entities.Product", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid?>("CategoryId")
.HasColumnType("uuid");
b.Property<string>("CategoryName")
.HasColumnType("text");
b.Property<string>("CodeName")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("Created")
.HasColumnType("timestamp with time zone");
b.Property<double>("Discount")
.HasColumnType("double precision");
b.Property<bool>("IsBundle")
.HasColumnType("boolean");
b.PrimitiveCollection<string[]>("Photos")
.IsRequired()
.HasColumnType("text[]");
b.Property<double>("Price")
.HasColumnType("double precision");
b.HasKey("Id");
b.HasIndex("CategoryId");
b.ToTable("Products");
});
modelBuilder.Entity("ProductProduct", b =>
{
b.Property<Guid>("ChildProductsId")
.HasColumnType("uuid");
b.Property<Guid>("ParentBundlesId")
.HasColumnType("uuid");
b.HasKey("ChildProductsId", "ParentBundlesId");
b.HasIndex("ParentBundlesId");
b.ToTable("ProductProduct");
});
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.HasOne("Commerce.Domain.Entities.Category", "ParentCategory")
.WithMany("ChildCategories")
.HasForeignKey("ParentCategoryId");
b.OwnsMany("Commerce.Domain.Entities.CategoryTranslation", "Translations", b1 =>
{
b1.Property<Guid>("CategoryId")
.HasColumnType("uuid");
b1.Property<int>("__synthesizedOrdinal")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
b1.Property<string>("Language")
.IsRequired()
.HasColumnType("text");
b1.HasKey("CategoryId", "__synthesizedOrdinal");
b1.ToTable("Categories");
b1.ToJson("Translations");
b1.WithOwner()
.HasForeignKey("CategoryId");
b1.OwnsOne("Commerce.Domain.Entities.CategoryInfo", "Info", b2 =>
{
b2.Property<Guid>("CategoryTranslationCategoryId")
.HasColumnType("uuid");
b2.Property<int>("CategoryTranslation__synthesizedOrdinal")
.HasColumnType("integer");
b2.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b2.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b2.HasKey("CategoryTranslationCategoryId", "CategoryTranslation__synthesizedOrdinal");
b2.ToTable("Categories");
b2.WithOwner()
.HasForeignKey("CategoryTranslationCategoryId", "CategoryTranslation__synthesizedOrdinal");
b2.OwnsMany("Commerce.Domain.Entities.TechnicalDetail", "TechnicalDetails", b3 =>
{
b3.Property<Guid>("CategoryInfoCategoryTranslationCategoryId")
.HasColumnType("uuid");
b3.Property<int>("CategoryInfoCategoryTranslation__synthesizedOrdinal")
.HasColumnType("integer");
b3.Property<int>("__synthesizedOrdinal")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
b3.Property<string>("Key")
.IsRequired()
.HasColumnType("text");
b3.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b3.HasKey("CategoryInfoCategoryTranslationCategoryId", "CategoryInfoCategoryTranslation__synthesizedOrdinal", "__synthesizedOrdinal");
b3.ToTable("Categories");
b3.WithOwner()
.HasForeignKey("CategoryInfoCategoryTranslationCategoryId", "CategoryInfoCategoryTranslation__synthesizedOrdinal");
});
b2.Navigation("TechnicalDetails");
});
b1.Navigation("Info")
.IsRequired();
});
b.Navigation("ParentCategory");
b.Navigation("Translations");
});
modelBuilder.Entity("Commerce.Domain.Entities.Product", b =>
{
b.HasOne("Commerce.Domain.Entities.Category", "Category")
.WithMany()
.HasForeignKey("CategoryId");
b.OwnsMany("Commerce.Domain.Entities.ProductTranslation", "Translations", b1 =>
{
b1.Property<Guid>("ProductId")
.HasColumnType("uuid");
b1.Property<int>("__synthesizedOrdinal")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
b1.Property<string>("Language")
.IsRequired()
.HasColumnType("text");
b1.HasKey("ProductId", "__synthesizedOrdinal");
b1.ToTable("Products");
b1.ToJson("Translations");
b1.WithOwner()
.HasForeignKey("ProductId");
b1.OwnsOne("Commerce.Domain.Entities.ProductInfo", "Info", b2 =>
{
b2.Property<Guid>("ProductTranslationProductId")
.HasColumnType("uuid");
b2.Property<int>("ProductTranslation__synthesizedOrdinal")
.HasColumnType("integer");
b2.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b2.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b2.HasKey("ProductTranslationProductId", "ProductTranslation__synthesizedOrdinal");
b2.ToTable("Products");
b2.WithOwner()
.HasForeignKey("ProductTranslationProductId", "ProductTranslation__synthesizedOrdinal");
b2.OwnsMany("Commerce.Domain.Entities.TechnicalDetail", "TechnicalDetails", b3 =>
{
b3.Property<Guid>("ProductInfoProductTranslationProductId")
.HasColumnType("uuid");
b3.Property<int>("ProductInfoProductTranslation__synthesizedOrdinal")
.HasColumnType("integer");
b3.Property<int>("__synthesizedOrdinal")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
b3.Property<string>("Key")
.IsRequired()
.HasColumnType("text");
b3.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b3.HasKey("ProductInfoProductTranslationProductId", "ProductInfoProductTranslation__synthesizedOrdinal", "__synthesizedOrdinal");
b3.ToTable("Products");
b3.WithOwner()
.HasForeignKey("ProductInfoProductTranslationProductId", "ProductInfoProductTranslation__synthesizedOrdinal");
});
b2.Navigation("TechnicalDetails");
});
b1.Navigation("Info")
.IsRequired();
});
b.Navigation("Category");
b.Navigation("Translations");
});
modelBuilder.Entity("ProductProduct", b =>
{
b.HasOne("Commerce.Domain.Entities.Product", null)
.WithMany()
.HasForeignKey("ChildProductsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Commerce.Domain.Entities.Product", null)
.WithMany()
.HasForeignKey("ParentBundlesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.Navigation("ChildCategories");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,54 @@
using System.Collections.Generic;
using Commerce.Domain.Entities;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Commerce.Domain.Migrations
{
/// <inheritdoc />
public partial class QueryableTranslations : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Translations",
table: "Products",
type: "jsonb",
nullable: true,
oldClrType: typeof(List<ProductTranslation>),
oldType: "jsonb");
migrationBuilder.AlterColumn<string>(
name: "Translations",
table: "Categories",
type: "jsonb",
nullable: true,
oldClrType: typeof(List<CategoryTranslation>),
oldType: "jsonb");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<List<ProductTranslation>>(
name: "Translations",
table: "Products",
type: "jsonb",
nullable: false,
oldClrType: typeof(string),
oldType: "jsonb",
oldNullable: true);
migrationBuilder.AlterColumn<List<CategoryTranslation>>(
name: "Translations",
table: "Categories",
type: "jsonb",
nullable: false,
oldClrType: typeof(string),
oldType: "jsonb",
oldNullable: true);
}
}
}
@@ -0,0 +1,306 @@
// <auto-generated />
using System;
using Commerce.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Commerce.Domain.Migrations
{
[DbContext(typeof(Context))]
partial class ContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("IsBundle")
.HasColumnType("boolean");
b.Property<Guid?>("ParentCategoryId")
.HasColumnType("uuid");
b.PrimitiveCollection<string[]>("Photos")
.IsRequired()
.HasColumnType("text[]");
b.HasKey("Id");
b.HasIndex("ParentCategoryId");
b.ToTable("Categories");
});
modelBuilder.Entity("Commerce.Domain.Entities.Product", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid?>("CategoryId")
.HasColumnType("uuid");
b.Property<string>("CategoryName")
.HasColumnType("text");
b.Property<string>("CodeName")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("Created")
.HasColumnType("timestamp with time zone");
b.Property<double>("Discount")
.HasColumnType("double precision");
b.Property<bool>("IsBundle")
.HasColumnType("boolean");
b.PrimitiveCollection<string[]>("Photos")
.IsRequired()
.HasColumnType("text[]");
b.Property<double>("Price")
.HasColumnType("double precision");
b.HasKey("Id");
b.HasIndex("CategoryId");
b.ToTable("Products");
});
modelBuilder.Entity("ProductProduct", b =>
{
b.Property<Guid>("ChildProductsId")
.HasColumnType("uuid");
b.Property<Guid>("ParentBundlesId")
.HasColumnType("uuid");
b.HasKey("ChildProductsId", "ParentBundlesId");
b.HasIndex("ParentBundlesId");
b.ToTable("ProductProduct");
});
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.HasOne("Commerce.Domain.Entities.Category", "ParentCategory")
.WithMany("ChildCategories")
.HasForeignKey("ParentCategoryId");
b.OwnsMany("Commerce.Domain.Entities.CategoryTranslation", "Translations", b1 =>
{
b1.Property<Guid>("CategoryId")
.HasColumnType("uuid");
b1.Property<int>("__synthesizedOrdinal")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
b1.Property<string>("Language")
.IsRequired()
.HasColumnType("text");
b1.HasKey("CategoryId", "__synthesizedOrdinal");
b1.ToTable("Categories");
b1.ToJson("Translations");
b1.WithOwner()
.HasForeignKey("CategoryId");
b1.OwnsOne("Commerce.Domain.Entities.CategoryInfo", "Info", b2 =>
{
b2.Property<Guid>("CategoryTranslationCategoryId")
.HasColumnType("uuid");
b2.Property<int>("CategoryTranslation__synthesizedOrdinal")
.HasColumnType("integer");
b2.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b2.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b2.HasKey("CategoryTranslationCategoryId", "CategoryTranslation__synthesizedOrdinal");
b2.ToTable("Categories");
b2.WithOwner()
.HasForeignKey("CategoryTranslationCategoryId", "CategoryTranslation__synthesizedOrdinal");
b2.OwnsMany("Commerce.Domain.Entities.TechnicalDetail", "TechnicalDetails", b3 =>
{
b3.Property<Guid>("CategoryInfoCategoryTranslationCategoryId")
.HasColumnType("uuid");
b3.Property<int>("CategoryInfoCategoryTranslation__synthesizedOrdinal")
.HasColumnType("integer");
b3.Property<int>("__synthesizedOrdinal")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
b3.Property<string>("Key")
.IsRequired()
.HasColumnType("text");
b3.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b3.HasKey("CategoryInfoCategoryTranslationCategoryId", "CategoryInfoCategoryTranslation__synthesizedOrdinal", "__synthesizedOrdinal");
b3.ToTable("Categories");
b3.WithOwner()
.HasForeignKey("CategoryInfoCategoryTranslationCategoryId", "CategoryInfoCategoryTranslation__synthesizedOrdinal");
});
b2.Navigation("TechnicalDetails");
});
b1.Navigation("Info")
.IsRequired();
});
b.Navigation("ParentCategory");
b.Navigation("Translations");
});
modelBuilder.Entity("Commerce.Domain.Entities.Product", b =>
{
b.HasOne("Commerce.Domain.Entities.Category", "Category")
.WithMany()
.HasForeignKey("CategoryId");
b.OwnsMany("Commerce.Domain.Entities.ProductTranslation", "Translations", b1 =>
{
b1.Property<Guid>("ProductId")
.HasColumnType("uuid");
b1.Property<int>("__synthesizedOrdinal")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
b1.Property<string>("Language")
.IsRequired()
.HasColumnType("text");
b1.HasKey("ProductId", "__synthesizedOrdinal");
b1.ToTable("Products");
b1.ToJson("Translations");
b1.WithOwner()
.HasForeignKey("ProductId");
b1.OwnsOne("Commerce.Domain.Entities.ProductInfo", "Info", b2 =>
{
b2.Property<Guid>("ProductTranslationProductId")
.HasColumnType("uuid");
b2.Property<int>("ProductTranslation__synthesizedOrdinal")
.HasColumnType("integer");
b2.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b2.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b2.HasKey("ProductTranslationProductId", "ProductTranslation__synthesizedOrdinal");
b2.ToTable("Products");
b2.WithOwner()
.HasForeignKey("ProductTranslationProductId", "ProductTranslation__synthesizedOrdinal");
b2.OwnsMany("Commerce.Domain.Entities.TechnicalDetail", "TechnicalDetails", b3 =>
{
b3.Property<Guid>("ProductInfoProductTranslationProductId")
.HasColumnType("uuid");
b3.Property<int>("ProductInfoProductTranslation__synthesizedOrdinal")
.HasColumnType("integer");
b3.Property<int>("__synthesizedOrdinal")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
b3.Property<string>("Key")
.IsRequired()
.HasColumnType("text");
b3.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b3.HasKey("ProductInfoProductTranslationProductId", "ProductInfoProductTranslation__synthesizedOrdinal", "__synthesizedOrdinal");
b3.ToTable("Products");
b3.WithOwner()
.HasForeignKey("ProductInfoProductTranslationProductId", "ProductInfoProductTranslation__synthesizedOrdinal");
});
b2.Navigation("TechnicalDetails");
});
b1.Navigation("Info")
.IsRequired();
});
b.Navigation("Category");
b.Navigation("Translations");
});
modelBuilder.Entity("ProductProduct", b =>
{
b.HasOne("Commerce.Domain.Entities.Product", null)
.WithMany()
.HasForeignKey("ChildProductsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Commerce.Domain.Entities.Product", null)
.WithMany()
.HasForeignKey("ParentBundlesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Commerce.Domain.Entities.Category", b =>
{
b.Navigation("ChildCategories");
});
#pragma warning restore 612, 618
}
}
}