diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fe1152b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/ShopSolution.sln b/ShopSolution.sln new file mode 100644 index 0000000..8eabfe1 --- /dev/null +++ b/ShopSolution.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.10.35122.118 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Webshop.Api", "Webshop.Api\Webshop.Api.csproj", "{CFC6D39C-5455-4091-8831-A07E34E1EF5F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Webshop.Domain", "Webshop.Domain\Webshop.Domain.csproj", "{959C9FF8-13B2-43AD-9795-878D497EB975}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Webshop.Application", "Webshop.Application\Webshop.Application.csproj", "{48E0F59D-F70D-4AA7-895C-671AD939A4B9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Webshop.Infrastructure", "Webshop.Infrastructure\Webshop.Infrastructure.csproj", "{546165FB-7B73-40A5-B774-15CEDFEBB9E8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CFC6D39C-5455-4091-8831-A07E34E1EF5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CFC6D39C-5455-4091-8831-A07E34E1EF5F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CFC6D39C-5455-4091-8831-A07E34E1EF5F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CFC6D39C-5455-4091-8831-A07E34E1EF5F}.Release|Any CPU.Build.0 = Release|Any CPU + {959C9FF8-13B2-43AD-9795-878D497EB975}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {959C9FF8-13B2-43AD-9795-878D497EB975}.Debug|Any CPU.Build.0 = Debug|Any CPU + {959C9FF8-13B2-43AD-9795-878D497EB975}.Release|Any CPU.ActiveCfg = Release|Any CPU + {959C9FF8-13B2-43AD-9795-878D497EB975}.Release|Any CPU.Build.0 = Release|Any CPU + {48E0F59D-F70D-4AA7-895C-671AD939A4B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48E0F59D-F70D-4AA7-895C-671AD939A4B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48E0F59D-F70D-4AA7-895C-671AD939A4B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48E0F59D-F70D-4AA7-895C-671AD939A4B9}.Release|Any CPU.Build.0 = Release|Any CPU + {546165FB-7B73-40A5-B774-15CEDFEBB9E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {546165FB-7B73-40A5-B774-15CEDFEBB9E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {546165FB-7B73-40A5-B774-15CEDFEBB9E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {546165FB-7B73-40A5-B774-15CEDFEBB9E8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A1FA3277-2297-416D-90D0-6DAE385449BE} + EndGlobalSection +EndGlobal diff --git a/Webshop.Api/Controllers/ProductsController.cs b/Webshop.Api/Controllers/ProductsController.cs new file mode 100644 index 0000000..c0854ff --- /dev/null +++ b/Webshop.Api/Controllers/ProductsController.cs @@ -0,0 +1,26 @@ +// src/Webshop.Api/Controllers/ProductsController.cs +using Microsoft.AspNetCore.Mvc; +using Webshop.Application.DTOs; +using Webshop.Application.Services; + +namespace Webshop.Api.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class ProductsController : ControllerBase + { + private readonly ProductService _productService; + + public ProductsController(ProductService productService) + { + _productService = productService; + } + + [HttpGet] + public async Task>> GetAllProducts() + { + var products = await _productService.GetAllProductsAsync(); + return Ok(products); + } + } +} \ No newline at end of file diff --git a/Webshop.Api/Dockerfile b/Webshop.Api/Dockerfile new file mode 100644 index 0000000..8911894 --- /dev/null +++ b/Webshop.Api/Dockerfile @@ -0,0 +1,25 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["Webshop.Api/Webshop.Api.csproj", "Webshop.Api/"] +RUN dotnet restore "./Webshop.Api/Webshop.Api.csproj" +COPY . . +WORKDIR "/src/Webshop.Api" +RUN dotnet build "./Webshop.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./Webshop.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Webshop.Api.dll"] \ No newline at end of file diff --git a/Webshop.Api/Program.cs b/Webshop.Api/Program.cs new file mode 100644 index 0000000..7e7b92a --- /dev/null +++ b/Webshop.Api/Program.cs @@ -0,0 +1,87 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using System.Text; +using Webshop.Application.Services; +using Webshop.Domain.Interfaces; +using Webshop.Infrastructure.Data; +using Webshop.Infrastructure.Repositories; + + +var builder = WebApplication.CreateBuilder(args); + +// --- START: DIENSTE ZUM CONTAINER HINZUFÜGEN --- + +// 1. Datenbank-Kontext (DbContext) registrieren +// Sagt der Anwendung, wie sie sich mit der PostgreSQL-Datenbank verbinden soll. +builder.Services.AddDbContext(options => + options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); + +// 2. ASP.NET Core Identity für Benutzerverwaltung registrieren +// Verwendet unseren DbContext, um Benutzer- und Rollen-Daten zu speichern. +builder.Services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + +// 3. JWT-Authentifizierung konfigurieren +// Richtet ein, wie die API die von Benutzern gesendeten Tokens validieren soll. +var jwtSettings = builder.Configuration.GetSection("JwtSettings"); +var secretKey = jwtSettings["Secret"] ?? throw new InvalidOperationException("JWT Secret not found"); + +builder.Services.AddAuthentication(options => +{ + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; +}) +.AddJwtBearer(options => +{ + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = jwtSettings["Issuer"], + ValidAudience = jwtSettings["Audience"], + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)) + }; +}); + +// 4. Unsere eigenen Interfaces und Klassen registrieren (Dependency Injection) +// Sagt: "Immer wenn jemand nach 'IProductRepository' fragt, gib ihm eine neue 'ProductRepository'-Instanz." +builder.Services.AddScoped(); + +# region Services +builder.Services.AddScoped(); +# endregion +// 5. Controller und Swagger/OpenAPI hinzufügen +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +// --- ENDE: DIENSTE ZUM CONTAINER HINZUFÜGEN --- + +var app = builder.Build(); + +// --- START: HTTP REQUEST PIPELINE KONFIGURIEREN --- + +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +// WICHTIG: Die Reihenfolge ist entscheidend! +// Zuerst prüfen, wer der Benutzer ist (Authentifizierung)... +app.UseAuthentication(); +// ...dann prüfen, was der Benutzer darf (Autorisierung). +app.UseAuthorization(); + +app.MapControllers(); + +// --- ENDE: HTTP REQUEST PIPELINE KONFIGURIEREN --- + +app.Run(); \ No newline at end of file diff --git a/Webshop.Api/Properties/launchSettings.json b/Webshop.Api/Properties/launchSettings.json new file mode 100644 index 0000000..5b9bbf7 --- /dev/null +++ b/Webshop.Api/Properties/launchSettings.json @@ -0,0 +1,52 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5176" + }, + "https": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:7270;http://localhost:5176" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Container (Dockerfile)": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", + "environmentVariables": { + "ASPNETCORE_HTTPS_PORTS": "8081", + "ASPNETCORE_HTTP_PORTS": "8080" + }, + "publishAllPorts": true, + "useSSL": true + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:60457", + "sslPort": 44372 + } + } +} \ No newline at end of file diff --git a/Webshop.Api/Webshop.Api.csproj b/Webshop.Api/Webshop.Api.csproj new file mode 100644 index 0000000..5fa4bdb --- /dev/null +++ b/Webshop.Api/Webshop.Api.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + enable + enable + 461c56b2-b056-42a9-8ce5-ccc441ef8a37 + Linux + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + diff --git a/Webshop.Api/Webshop.Api.http b/Webshop.Api/Webshop.Api.http new file mode 100644 index 0000000..c216666 --- /dev/null +++ b/Webshop.Api/Webshop.Api.http @@ -0,0 +1,6 @@ +@Webshop.Api_HostAddress = http://localhost:5176 + +GET {{Webshop.Api_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/Webshop.Api/appsettings.Development.json b/Webshop.Api/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/Webshop.Api/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Webshop.Api/appsettings.json b/Webshop.Api/appsettings.json new file mode 100644 index 0000000..01d6f9f --- /dev/null +++ b/Webshop.Api/appsettings.json @@ -0,0 +1,18 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "Host=postgres-db;Port=5432;Database=webshopdb;Username=postgres;Password=MEIN_DEBUG_PASSWORT" + }, + "JwtSettings": { + "Secret": "MEIN_DEBUG_PASSWORT", + "Issuer": "https://dein-webshop.com", + "Audience": "webshop-users", + "ExpirationMinutes": 120 + } +} \ No newline at end of file diff --git a/Webshop.Application/Class1.cs b/Webshop.Application/Class1.cs new file mode 100644 index 0000000..e568a2b --- /dev/null +++ b/Webshop.Application/Class1.cs @@ -0,0 +1,7 @@ +namespace Webshop.Application +{ + public class Class1 + { + + } +} diff --git a/Webshop.Application/DTOs/ProductDto.cs b/Webshop.Application/DTOs/ProductDto.cs new file mode 100644 index 0000000..a410176 --- /dev/null +++ b/Webshop.Application/DTOs/ProductDto.cs @@ -0,0 +1,12 @@ +// src/Webshop.Application/DTOs/ProductDto.cs +namespace Webshop.Application.DTOs +{ + public class ProductDto + { + public Guid Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public decimal Price { get; set; } + public string Sku { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Webshop.Application/Services/ProductService.cs b/Webshop.Application/Services/ProductService.cs new file mode 100644 index 0000000..242007f --- /dev/null +++ b/Webshop.Application/Services/ProductService.cs @@ -0,0 +1,32 @@ +// src/Webshop.Application/Services/ProductService.cs +using Webshop.Application.DTOs; +using Webshop.Domain.Interfaces; + +namespace Webshop.Application.Services +{ + public class ProductService + { + private readonly IProductRepository _productRepository; + + public ProductService(IProductRepository productRepository) + { + _productRepository = productRepository; + } + + public async Task> GetAllProductsAsync() + { + var productsFromDb = await _productRepository.GetAllProductsAsync(); + + var productDtos = productsFromDb.Select(p => new ProductDto + { + Id = p.Id, + Name = p.Name, + Description = p.Description, + Price = p.Price, + Sku = p.SKU + }); + + return productDtos; + } + } +} \ No newline at end of file diff --git a/Webshop.Application/Webshop.Application.csproj b/Webshop.Application/Webshop.Application.csproj new file mode 100644 index 0000000..2e530f3 --- /dev/null +++ b/Webshop.Application/Webshop.Application.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/Webshop.Domain/Class1.cs b/Webshop.Domain/Class1.cs new file mode 100644 index 0000000..136ac40 --- /dev/null +++ b/Webshop.Domain/Class1.cs @@ -0,0 +1,7 @@ +namespace Webshop.Domain +{ + public class Class1 + { + + } +} diff --git a/Webshop.Domain/Entities/Adress.cs b/Webshop.Domain/Entities/Adress.cs new file mode 100644 index 0000000..3f430f2 --- /dev/null +++ b/Webshop.Domain/Entities/Adress.cs @@ -0,0 +1,56 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Webshop.Domain.Entities; + +/// +/// Verwendet für Kundenadressen, Rechnungs- und Lieferadressen von Bestellungen oder Lieferanten. +/// +public class Address +{ + [Key] + public Guid Id { get; set; } + + [ForeignKey(nameof(Customer))] + public Guid? CustomerId { get; set; } + + [MaxLength(50)] + public string? AddressType { get; set; } // z.B. "Billing", "Shipping" + + [Required] + [MaxLength(255)] + public string Street { get; set; } + + [MaxLength(255)] + public string? Street2 { get; set; } + + [Required] + [MaxLength(100)] + public string City { get; set; } + + [MaxLength(100)] + public string? State { get; set; } + + [Required] + [MaxLength(20)] + public string PostalCode { get; set; } + + [Required] + [MaxLength(100)] + public string Country { get; set; } + + [MaxLength(255)] + public string? CompanyName { get; set; } + + [Required] + [MaxLength(100)] + public string FirstName { get; set; } + + [Required] + [MaxLength(100)] + public string LastName { get; set; } + + // Navigation Property + public virtual Customer? Customer { get; set; } +} \ No newline at end of file diff --git a/Webshop.Domain/Entities/Category.cs b/Webshop.Domain/Entities/Category.cs new file mode 100644 index 0000000..b6bd12d --- /dev/null +++ b/Webshop.Domain/Entities/Category.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Webshop.Domain.Entities; + +/// +/// Zum Gruppieren und Organisieren von Produkten. +/// +public class Category +{ + [Key] + public Guid Id { get; set; } + + [Required] + [MaxLength(255)] + public string Name { get; set; } + + [MaxLength(1000)] + public string? Description { get; set; } + + // Unique-Constraint wird typischerweise via Fluent API konfiguriert + [Required] + [MaxLength(255)] + public string Slug { get; set; } + + [ForeignKey(nameof(ParentCategory))] + public Guid? ParentCategoryId { get; set; } + + [MaxLength(2000)] + public string? ImageUrl { get; set; } + + [Required] + public bool IsActive { get; set; } + + [Required] + public int DisplayOrder { get; set; } + + // Navigation Properties + public virtual Category? ParentCategory { get; set; } + public virtual ICollection SubCategories { get; set; } = new List(); + public virtual ICollection ProductCategories { get; set; } = new List(); + public virtual ICollection CategoryDiscounts { get; set; } = new List(); +} \ No newline at end of file diff --git a/Webshop.Domain/Entities/CategoryDiscount.cs b/Webshop.Domain/Entities/CategoryDiscount.cs new file mode 100644 index 0000000..0464794 --- /dev/null +++ b/Webshop.Domain/Entities/CategoryDiscount.cs @@ -0,0 +1,23 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Webshop.Domain.Entities; + +/// +/// Verknüpfungstabelle für die Viele-zu-Viele-Beziehung zwischen Category und Discount. +/// +public class CategoryDiscount +{ + [Required] + [ForeignKey(nameof(Category))] + public Guid CategoryId { get; set; } + + [Required] + [ForeignKey(nameof(Discount))] + public Guid DiscountId { get; set; } + + // Navigation Properties + public virtual Category Category { get; set; } + public virtual Discount Discount { get; set; } +} \ No newline at end of file diff --git a/Webshop.Domain/Entities/Customer.cs b/Webshop.Domain/Entities/Customer.cs new file mode 100644 index 0000000..cb306f9 --- /dev/null +++ b/Webshop.Domain/Entities/Customer.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Webshop.Domain.Entities; + +/// +/// Für registrierte Nutzer des Webshops. Dies ist die Schnittstelle zu ASP.NET Core Identity. +/// +public class Customer +{ + [Key] + public Guid Id { get; set; } + + // Unique-Constraint und Foreign Key werden via Fluent API konfiguriert + [Required] + [MaxLength(450)] + public string AspNetUserId { get; set; } + + [Required] + [MaxLength(100)] + public string FirstName { get; set; } + + [Required] + [MaxLength(100)] + public string LastName { get; set; } + + // Unique-Constraint wird von Identity verwaltet + [Required] + [MaxLength(256)] + [EmailAddress] + public string Email { get; set; } + + [MaxLength(20)] + [Phone] + public string? PhoneNumber { get; set; } + + [Required] + public DateTimeOffset CreatedDate { get; set; } + + public DateTimeOffset? LastLoginDate { get; set; } + + [Required] + public bool IsActive { get; set; } + + // Navigation Properties + public virtual ICollection
Addresses { get; set; } = new List
(); + public virtual ICollection Orders { get; set; } = new List(); + public virtual ICollection Reviews { get; set; } = new List(); +} \ No newline at end of file diff --git a/Webshop.Domain/Entities/Discount.cs b/Webshop.Domain/Entities/Discount.cs new file mode 100644 index 0000000..620e895 --- /dev/null +++ b/Webshop.Domain/Entities/Discount.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Webshop.Domain.Entities; + +/// +/// Verwaltung von Rabatten auf Produkte oder Kategorien. +/// +public class Discount +{ + [Key] + public Guid Id { get; set; } + + [Required] + [MaxLength(255)] + public string Name { get; set; } + + [Required] + [MaxLength(50)] + public string DiscountType { get; set; } // "Percentage", "FixedAmount" + + // Precision wird via Fluent API konfiguriert + [Required] + public decimal DiscountValue { get; set; } + + [Required] + public DateTimeOffset StartDate { get; set; } + + public DateTimeOffset? EndDate { get; set; } + + [Required] + public bool IsActive { get; set; } + + [Required] + public bool RequiresCouponCode { get; set; } + + // Unique-Constraint wird via Fluent API konfiguriert + [MaxLength(50)] + public string? CouponCode { get; set; } + + public decimal? MinimumOrderAmount { get; set; } + + public int? MaximumUsageCount { get; set; } + + [Required] + public int CurrentUsageCount { get; set; } + + [MaxLength(1000)] + public string? Description { get; set; } + + // Navigation Properties + public virtual ICollection ProductDiscounts { get; set; } = new List(); + public virtual ICollection CategoryDiscounts { get; set; } = new List(); +} \ No newline at end of file diff --git a/Webshop.Domain/Entities/Order.cs b/Webshop.Domain/Entities/Order.cs new file mode 100644 index 0000000..b811b7e --- /dev/null +++ b/Webshop.Domain/Entities/Order.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Webshop.Domain.Entities; + +/// +/// Die Details jeder Kundenbestellung, inklusive Gastbestellungen. +/// +public class Order +{ + [Key] + public Guid Id { get; set; } + + // Unique-Constraint wird via Fluent API konfiguriert + [Required] + [MaxLength(50)] + public string OrderNumber { get; set; } + + [ForeignKey(nameof(Customer))] + public Guid? CustomerId { get; set; } + + [MaxLength(256)] + [EmailAddress] + public string? GuestEmail { get; set; } + + [MaxLength(20)] + [Phone] + public string? GuestPhoneNumber { get; set; } + + [Required] + public DateTimeOffset OrderDate { get; set; } + + [Required] + [MaxLength(50)] + public string OrderStatus { get; set; } + + // Precision wird via Fluent API konfiguriert + [Required] + public decimal OrderTotal { get; set; } + + [Required] + public decimal ShippingCost { get; set; } + + [Required] + public decimal TaxAmount { get; set; } + + [Required] + public decimal DiscountAmount { get; set; } + + [Required] + [MaxLength(50)] + public string PaymentStatus { get; set; } + + [Required] + [MaxLength(100)] + public string PaymentMethod { get; set; } + + [ForeignKey(nameof(PaymentMethodInfo))] + public Guid? PaymentMethodId { get; set; } + + [ForeignKey(nameof(ShippingMethodInfo))] + public Guid? ShippingMethodId { get; set; } + + [MaxLength(255)] + public string? TransactionId { get; set; } + + [Required] + [ForeignKey(nameof(BillingAddress))] + public Guid BillingAddressId { get; set; } + + [Required] + [ForeignKey(nameof(ShippingAddress))] + public Guid ShippingAddressId { get; set; } + + [MaxLength(1000)] + public string? CustomerNotes { get; set; } + + [MaxLength(1000)] + public string? AdminNotes { get; set; } + + // Navigation Properties + public virtual Customer? Customer { get; set; } + public virtual Address BillingAddress { get; set; } + public virtual Address ShippingAddress { get; set; } + public virtual PaymentMethod? PaymentMethodInfo { get; set; } + public virtual ShippingMethod? ShippingMethodInfo { get; set; } + public virtual ICollection OrderItems { get; set; } = new List(); +} \ No newline at end of file diff --git a/Webshop.Domain/Entities/OrderItem.cs b/Webshop.Domain/Entities/OrderItem.cs new file mode 100644 index 0000000..82e8344 --- /dev/null +++ b/Webshop.Domain/Entities/OrderItem.cs @@ -0,0 +1,47 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Webshop.Domain.Entities; + +/// +/// Die einzelnen Artikel innerhalb einer Bestellung. +/// +public class OrderItem +{ + [Key] + public Guid Id { get; set; } + + [Required] + [ForeignKey(nameof(Order))] + public Guid OrderId { get; set; } + + [ForeignKey(nameof(Product))] + public Guid? ProductId { get; set; } + + [ForeignKey(nameof(ProductVariant))] + public Guid? ProductVariantId { get; set; } + + [Required] + [MaxLength(255)] + public string ProductName { get; set; } // Snapshot + + [Required] + [MaxLength(50)] + public string ProductSKU { get; set; } // Snapshot + + [Required] + public int Quantity { get; set; } + + // Precision wird via Fluent API konfiguriert + [Required] + public decimal UnitPrice { get; set; } + + [Required] + public decimal TotalPrice { get; set; } + + // Navigation Properties + public virtual Order Order { get; set; } + public virtual Product? Product { get; set; } + public virtual ProductVariant? ProductVariant { get; set; } +} \ No newline at end of file diff --git a/Webshop.Domain/Entities/PaymentMethod.cs b/Webshop.Domain/Entities/PaymentMethod.cs new file mode 100644 index 0000000..2296324 --- /dev/null +++ b/Webshop.Domain/Entities/PaymentMethod.cs @@ -0,0 +1,29 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Webshop.Domain.Entities; + +/// +/// Konfigurierbare Zahlungsoptionen für den Checkout. +/// +public class PaymentMethod +{ + [Key] + public Guid Id { get; set; } + + [Required] + [MaxLength(100)] + public string Name { get; set; } + + [MaxLength(500)] + public string? Description { get; set; } + + [Required] + public bool IsActive { get; set; } + + [Required] + [MaxLength(50)] + public string PaymentGatewayType { get; set; } // "Stripe", "PayPal", etc. + + public decimal? ProcessingFee { get; set; } +} \ No newline at end of file diff --git a/Webshop.Domain/Entities/Product.cs b/Webshop.Domain/Entities/Product.cs new file mode 100644 index 0000000..8ce49ec --- /dev/null +++ b/Webshop.Domain/Entities/Product.cs @@ -0,0 +1,86 @@ +using System.ComponentModel.DataAnnotations.Schema; + +using System.ComponentModel.DataAnnotations; + +namespace Webshop.Domain.Entities; + +/// +/// 1. Das zentrale Modell für deine Verkaufsartikel. +/// +public class Product +{ + [Key] + public Guid Id { get; set; } + + [Required] + [MaxLength(255)] + public string Name { get; set; } + + [MaxLength(4000)] + public string? Description { get; set; } + + [MaxLength(500)] + public string? ShortDescription { get; set; } + + // Unique-Constraint wird typischerweise via Fluent API konfiguriert: + // modelBuilder.Entity().HasIndex(p => p.SKU).IsUnique(); + [Required] + [MaxLength(50)] + public string SKU { get; set; } + + // Precision wird typischerweise via Fluent API konfiguriert: + // modelBuilder.Entity().Property(p => p.Price).HasPrecision(18, 2); + [Required] + public decimal Price { get; set; } + + // modelBuilder.Entity().Property(p => p.OldPrice).HasPrecision(18, 2); + public decimal? OldPrice { get; set; } + + [Required] + public bool IsActive { get; set; } + + [Required] + public bool IsInStock { get; set; } + + [Required] + public int StockQuantity { get; set; } + + // modelBuilder.Entity().Property(p => p.Weight).HasPrecision(18, 3); + public decimal? Weight { get; set; } + + // modelBuilder.Entity().Property(p => p.Width).HasPrecision(18, 2); + public decimal? Width { get; set; } + + // modelBuilder.Entity().Property(p => p.Height).HasPrecision(18, 2); + public decimal? Height { get; set; } + + // modelBuilder.Entity().Property(p => p.Length).HasPrecision(18, 2); + public decimal? Length { get; set; } + + [MaxLength(2000)] + public string? ImageUrl { get; set; } + + // Unique-Constraint wird typischerweise via Fluent API konfiguriert: + // modelBuilder.Entity().HasIndex(p => p.Slug).IsUnique(); + [Required] + [MaxLength(255)] + public string Slug { get; set; } + + [Required] + public DateTimeOffset CreatedDate { get; set; } + + public DateTimeOffset? LastModifiedDate { get; set; } + + [ForeignKey(nameof(Supplier))] + public Guid? SupplierId { get; set; } + + // modelBuilder.Entity().Property(p => p.PurchasePrice).HasPrecision(18, 2); + public decimal? PurchasePrice { get; set; } + + // Navigation Properties + public virtual Supplier? Supplier { get; set; } + public virtual ICollection Variants { get; set; } = new List(); + public virtual ICollection Reviews { get; set; } = new List(); + public virtual ICollection ProductDiscounts { get; set; } = new List(); + public virtual ICollection ProductCategories { get; set; } = new List(); +} diff --git a/Webshop.Domain/Entities/ProductCategory.cs b/Webshop.Domain/Entities/ProductCategory.cs new file mode 100644 index 0000000..eb679c7 --- /dev/null +++ b/Webshop.Domain/Entities/ProductCategory.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Webshop.Domain.Entities; + +/// +/// Verknüpfungstabelle für die Viele-zu-Viele-Beziehung zwischen Category und Discount. +/// +public class ProductCategory +{ + // Composite Primary Key wird via Fluent API in Ihrem DbContext konfiguriert: + // modelBuilder.Entity().HasKey(pc => new { pc.ProductId, pc.CategoryId }); + + [ForeignKey(nameof(Product))] + public Guid ProductId { get; set; } + + [ForeignKey(nameof(Category))] + public Guid CategoryId { get; set; } + + // Navigation Properties + public virtual Product Product { get; set; } + public virtual Category Category { get; set; } +} \ No newline at end of file diff --git a/Webshop.Domain/Entities/ProductDiscount.cs b/Webshop.Domain/Entities/ProductDiscount.cs new file mode 100644 index 0000000..cdd9ad7 --- /dev/null +++ b/Webshop.Domain/Entities/ProductDiscount.cs @@ -0,0 +1,23 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Webshop.Domain.Entities; + +/// +/// Verknüpfungstabelle für die Viele-zu-Viele-Beziehung zwischen Product und Discount. +/// +public class ProductDiscount +{ + [Required] + [ForeignKey(nameof(Product))] + public Guid ProductId { get; set; } + + [Required] + [ForeignKey(nameof(Discount))] + public Guid DiscountId { get; set; } + + // Navigation Properties + public virtual Product Product { get; set; } + public virtual Discount Discount { get; set; } +} \ No newline at end of file diff --git a/Webshop.Domain/Entities/ProductVariant.cs b/Webshop.Domain/Entities/ProductVariant.cs new file mode 100644 index 0000000..fe8e846 --- /dev/null +++ b/Webshop.Domain/Entities/ProductVariant.cs @@ -0,0 +1,46 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Webshop.Domain.Entities; + +/// +/// Für Produkte mit unterschiedlichen Attributen (z.B. Größe, Farbe) und eigenem Lagerbestand. +/// +public class ProductVariant +{ + [Key] + public Guid Id { get; set; } + + [Required] + [ForeignKey(nameof(Product))] + public Guid ProductId { get; set; } + + [Required] + [MaxLength(100)] + public string Name { get; set; } // z.B. "Farbe", "Größe" + + [Required] + [MaxLength(100)] + public string Value { get; set; } // z.B. "Rot", "XL" + + // Unique-Constraint wird typischerweise via Fluent API konfiguriert + [MaxLength(50)] + public string? SKU { get; set; } + + // Precision wird via Fluent API konfiguriert + [Required] + public decimal PriceAdjustment { get; set; } + + [Required] + public int StockQuantity { get; set; } + + [MaxLength(2000)] + public string? ImageUrl { get; set; } + + [Required] + public bool IsActive { get; set; } + + // Navigation Property + public virtual Product Product { get; set; } +} \ No newline at end of file diff --git a/Webshop.Domain/Entities/Review.cs b/Webshop.Domain/Entities/Review.cs new file mode 100644 index 0000000..d72efa4 --- /dev/null +++ b/Webshop.Domain/Entities/Review.cs @@ -0,0 +1,41 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Webshop.Domain.Entities; + +/// +/// Kundenbewertungen für Produkte. +/// +public class Review +{ + [Key] + public Guid Id { get; set; } + + [Required] + [ForeignKey(nameof(Product))] + public Guid ProductId { get; set; } + + [ForeignKey(nameof(Customer))] + public Guid? CustomerId { get; set; } + + [Required] + [Range(1, 5)] + public int Rating { get; set; } + + [MaxLength(100)] + public string? Title { get; set; } + + [MaxLength(2000)] + public string? Comment { get; set; } + + [Required] + public DateTimeOffset ReviewDate { get; set; } + + [Required] + public bool IsApproved { get; set; } + + // Navigation Properties + public virtual Product Product { get; set; } + public virtual Customer? Customer { get; set; } +} \ No newline at end of file diff --git a/Webshop.Domain/Entities/Setting.cs b/Webshop.Domain/Entities/Setting.cs new file mode 100644 index 0000000..0466de2 --- /dev/null +++ b/Webshop.Domain/Entities/Setting.cs @@ -0,0 +1,30 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Webshop.Domain.Entities; + +/// +/// Generische Tabelle für flexible Shop-Einstellungen. +/// +public class Setting +{ + [Key] + public Guid Id { get; set; } + + // Unique-Constraint wird via Fluent API konfiguriert + [Required] + [MaxLength(255)] + public string Key { get; set; } + + [MaxLength(2000)] + public string? Value { get; set; } + + [MaxLength(500)] + public string? Description { get; set; } + + [Required] + public bool IsActive { get; set; } + + [MaxLength(100)] + public string? Group { get; set; } // "Shipping", "Order Processing", etc. +} \ No newline at end of file diff --git a/Webshop.Domain/Entities/ShippingMethod.cs b/Webshop.Domain/Entities/ShippingMethod.cs new file mode 100644 index 0000000..caddd44 --- /dev/null +++ b/Webshop.Domain/Entities/ShippingMethod.cs @@ -0,0 +1,35 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Webshop.Domain.Entities; + +/// +/// Konfigurierbare Versandoptionen für den Checkout. +/// +public class ShippingMethod +{ + [Key] + public Guid Id { get; set; } + + [Required] + [MaxLength(100)] + public string Name { get; set; } + + [MaxLength(500)] + public string? Description { get; set; } + + // Precision wird via Fluent API konfiguriert + [Required] + public decimal BaseCost { get; set; } + + public decimal? MinimumOrderAmount { get; set; } + + [Required] + public bool IsActive { get; set; } + + [MaxLength(100)] + public string? EstimatedDeliveryTime { get; set; } + + [Required] + public bool RequiresTracking { get; set; } +} \ No newline at end of file diff --git a/Webshop.Domain/Entities/Supplier.cs b/Webshop.Domain/Entities/Supplier.cs new file mode 100644 index 0000000..916015a --- /dev/null +++ b/Webshop.Domain/Entities/Supplier.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Webshop.Domain.Entities; + +/// +/// Details zu den Produktlieferanten. +/// +public class Supplier +{ + [Key] + public Guid Id { get; set; } + + [Required] + [MaxLength(255)] + public string Name { get; set; } + + [MaxLength(255)] + public string? ContactPerson { get; set; } + + [MaxLength(256)] + [EmailAddress] + public string? Email { get; set; } + + [MaxLength(50)] + [Phone] + public string? PhoneNumber { get; set; } + + [ForeignKey(nameof(Address))] + public Guid? AddressId { get; set; } + + [MaxLength(1000)] + public string? Notes { get; set; } + + // Navigation Properties + public virtual Address? Address { get; set; } + public virtual ICollection Products { get; set; } = new List(); +} \ No newline at end of file diff --git a/Webshop.Domain/Enums/AddressType.cs b/Webshop.Domain/Enums/AddressType.cs new file mode 100644 index 0000000..6dde0d5 --- /dev/null +++ b/Webshop.Domain/Enums/AddressType.cs @@ -0,0 +1,6 @@ +public enum AddressType +{ + Billing, + Shipping, + CustomerDefault +} \ No newline at end of file diff --git a/Webshop.Domain/Enums/DiscountType.cs b/Webshop.Domain/Enums/DiscountType.cs new file mode 100644 index 0000000..1c10ca7 --- /dev/null +++ b/Webshop.Domain/Enums/DiscountType.cs @@ -0,0 +1 @@ +public enum DiscountType { Percentage, FixedAmount } \ No newline at end of file diff --git a/Webshop.Domain/Enums/OrderStatus.cs b/Webshop.Domain/Enums/OrderStatus.cs new file mode 100644 index 0000000..9158c42 --- /dev/null +++ b/Webshop.Domain/Enums/OrderStatus.cs @@ -0,0 +1 @@ +public enum OrderStatus { Pending, Processing, Shipped, Delivered, Cancelled, Refunded } \ No newline at end of file diff --git a/Webshop.Domain/Enums/PaymentGatewayType.cs b/Webshop.Domain/Enums/PaymentGatewayType.cs new file mode 100644 index 0000000..9270078 --- /dev/null +++ b/Webshop.Domain/Enums/PaymentGatewayType.cs @@ -0,0 +1,7 @@ +public enum PaymentGatewayType +{ + Stripe, + PayPal, + Klarna, + Manual // z.B. Vorkasse +} \ No newline at end of file diff --git a/Webshop.Domain/Enums/PaymentStatus.cs b/Webshop.Domain/Enums/PaymentStatus.cs new file mode 100644 index 0000000..242f2cc --- /dev/null +++ b/Webshop.Domain/Enums/PaymentStatus.cs @@ -0,0 +1 @@ +public enum PaymentStatus { Pending, Paid, Failed, Refunded } \ No newline at end of file diff --git a/Webshop.Domain/Interfaces/IProductRepository.cs b/Webshop.Domain/Interfaces/IProductRepository.cs new file mode 100644 index 0000000..878c86c --- /dev/null +++ b/Webshop.Domain/Interfaces/IProductRepository.cs @@ -0,0 +1,14 @@ +// src/Webshop.Domain/Interfaces/IProductRepository.cs +using Webshop.Domain.Entities; + +namespace Webshop.Domain.Interfaces +{ + public interface IProductRepository + { + Task GetProductByIdAsync(Guid id); + Task> GetAllProductsAsync(); + Task AddProductAsync(Product product); + Task UpdateProductAsync(Product product); + Task DeleteProductAsync(Guid id); + } +} \ No newline at end of file diff --git a/Webshop.Domain/Webshop.Domain.csproj b/Webshop.Domain/Webshop.Domain.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/Webshop.Domain/Webshop.Domain.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/Webshop.Infrastructure/Data/ApplicationDbContext.cs b/Webshop.Infrastructure/Data/ApplicationDbContext.cs new file mode 100644 index 0000000..e09275b --- /dev/null +++ b/Webshop.Infrastructure/Data/ApplicationDbContext.cs @@ -0,0 +1,127 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Webshop.Domain.Entities; + +namespace Webshop.Infrastructure.Data +{ + public class ApplicationDbContext : IdentityDbContext + { + public ApplicationDbContext(DbContextOptions options) : base(options) + { + } + + public DbSet Products { get; set; } = default!; + public DbSet ProductVariants { get; set; } = default!; + public DbSet Categories { get; set; } = default!; + public DbSet Customers { get; set; } = default!; + public DbSet
Addresses { get; set; } = default!; + public DbSet Orders { get; set; } = default!; + public DbSet OrderItems { get; set; } = default!; + public DbSet Reviews { get; set; } = default!; + public DbSet Discounts { get; set; } = default!; + public DbSet Suppliers { get; set; } = default!; + public DbSet ShippingMethods { get; set; } = default!; + public DbSet PaymentMethods { get; set; } = default!; + public DbSet Settings { get; set; } = default!; + + public DbSet ProductCategories { get; set; } = default!; + public DbSet ProductDiscounts { get; set; } = default!; + public DbSet CategoryDiscounts { get; set; } = default!; + + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + foreach (var entity in modelBuilder.Model.GetEntityTypes()) + { + var tableName = entity.GetTableName(); + + if (tableName != null && tableName.StartsWith("AspNet")) + { + entity.SetTableName(tableName.Substring(6)); + } + } + + modelBuilder.Entity().HasKey(pc => new { pc.ProductId, pc.CategoryId }); + modelBuilder.Entity().HasKey(pd => new { pd.ProductId, pd.DiscountId }); + modelBuilder.Entity().HasKey(cd => new { cd.CategoryId, cd.DiscountId }); + + modelBuilder.Entity().HasIndex(p => p.SKU).IsUnique(); + modelBuilder.Entity().HasIndex(p => p.Slug).IsUnique(); + modelBuilder.Entity().HasIndex(c => c.Slug).IsUnique(); + modelBuilder.Entity().HasIndex(d => d.CouponCode).IsUnique().HasFilter("\"CouponCode\" IS NOT NULL"); + modelBuilder.Entity().HasIndex(s => s.Key).IsUnique(); + modelBuilder.Entity().HasIndex(o => o.OrderNumber).IsUnique(); + + modelBuilder.Entity(e => { + e.Property(p => p.Price).HasPrecision(18, 2); + e.Property(p => p.OldPrice).HasPrecision(18, 2); + e.Property(p => p.PurchasePrice).HasPrecision(18, 2); + e.Property(p => p.Weight).HasPrecision(18, 3); + e.Property(p => p.Width).HasPrecision(18, 2); + e.Property(p => p.Height).HasPrecision(18, 2); + e.Property(p => p.Length).HasPrecision(18, 2); + }); + + modelBuilder.Entity() + .Property(pv => pv.PriceAdjustment).HasPrecision(18, 2); + + modelBuilder.Entity(e => { + e.Property(o => o.OrderTotal).HasPrecision(18, 2); + e.Property(o => o.ShippingCost).HasPrecision(18, 2); + e.Property(o => o.TaxAmount).HasPrecision(18, 2); + e.Property(o => o.DiscountAmount).HasPrecision(18, 2); + }); + + modelBuilder.Entity(e => { + e.Property(oi => oi.UnitPrice).HasPrecision(18, 2); + e.Property(oi => oi.TotalPrice).HasPrecision(18, 2); + }); + + modelBuilder.Entity(e => { + e.Property(d => d.DiscountValue).HasPrecision(18, 2); + e.Property(d => d.MinimumOrderAmount).HasPrecision(18, 2); + }); + + modelBuilder.Entity(e => { + e.Property(sm => sm.BaseCost).HasPrecision(18, 2); + e.Property(sm => sm.MinimumOrderAmount).HasPrecision(18, 2); + }); + + modelBuilder.Entity() + .Property(pm => pm.ProcessingFee).HasPrecision(18, 2); + + modelBuilder.Entity() + .HasOne(c => c.ParentCategory) + .WithMany(c => c.SubCategories) + .HasForeignKey(c => c.ParentCategoryId) + .OnDelete(DeleteBehavior.Restrict); + + modelBuilder.Entity() + .HasOne(oi => oi.Product) + .WithMany() + .HasForeignKey(oi => oi.ProductId) + .OnDelete(DeleteBehavior.SetNull); + + modelBuilder.Entity() + .HasOne(oi => oi.ProductVariant) + .WithMany() + .HasForeignKey(oi => oi.ProductVariantId) + .OnDelete(DeleteBehavior.SetNull); + + modelBuilder.Entity() + .HasOne(o => o.BillingAddress) + .WithMany() + .HasForeignKey(o => o.BillingAddressId) + .OnDelete(DeleteBehavior.Restrict); + + modelBuilder.Entity() + .HasOne(o => o.ShippingAddress) + .WithMany() + .HasForeignKey(o => o.ShippingAddressId) + .OnDelete(DeleteBehavior.Restrict); + } + } +} \ No newline at end of file diff --git a/Webshop.Infrastructure/Migrations/20250721175118_InitialCreate.Designer.cs b/Webshop.Infrastructure/Migrations/20250721175118_InitialCreate.Designer.cs new file mode 100644 index 0000000..b565f0f --- /dev/null +++ b/Webshop.Infrastructure/Migrations/20250721175118_InitialCreate.Designer.cs @@ -0,0 +1,1220 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Webshop.Infrastructure.Data; + +#nullable disable + +namespace Webshop.Infrastructure.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250721175118_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.18") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("Roles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("RoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("UserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("UserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("UserTokens", (string)null); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Address", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddressType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CompanyName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("State") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Street") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Street2") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.ToTable("Addresses"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("ImageUrl") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ParentCategoryId") + .HasColumnType("uuid"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("ParentCategoryId"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.CategoryDiscount", b => + { + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("DiscountId") + .HasColumnType("uuid"); + + b.HasKey("CategoryId", "DiscountId"); + + b.HasIndex("DiscountId"); + + b.ToTable("CategoryDiscounts"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AspNetUserId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("CreatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PhoneNumber") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Discount", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CouponCode") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CurrentUsageCount") + .HasColumnType("integer"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("DiscountType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DiscountValue") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("MaximumUsageCount") + .HasColumnType("integer"); + + b.Property("MinimumOrderAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RequiresCouponCode") + .HasColumnType("boolean"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CouponCode") + .IsUnique() + .HasFilter("\"CouponCode\" IS NOT NULL"); + + b.ToTable("Discounts"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminNotes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("BillingAddressId") + .HasColumnType("uuid"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("CustomerNotes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("GuestEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GuestPhoneNumber") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("OrderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrderNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrderStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrderTotal") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PaymentMethodId") + .HasColumnType("uuid"); + + b.Property("PaymentStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ShippingAddressId") + .HasColumnType("uuid"); + + b.Property("ShippingCost") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("ShippingMethodId") + .HasColumnType("uuid"); + + b.Property("TaxAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("TransactionId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("BillingAddressId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("OrderNumber") + .IsUnique(); + + b.HasIndex("PaymentMethodId"); + + b.HasIndex("ShippingAddressId"); + + b.HasIndex("ShippingMethodId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("ProductName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProductSKU") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ProductVariantId") + .HasColumnType("uuid"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.Property("TotalPrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("UnitPrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("ProductId"); + + b.HasIndex("ProductVariantId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.PaymentMethod", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PaymentGatewayType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ProcessingFee") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.HasKey("Id"); + + b.ToTable("PaymentMethods"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("Height") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("ImageUrl") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsInStock") + .HasColumnType("boolean"); + + b.Property("LastModifiedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Length") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("OldPrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("Price") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("PurchasePrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("SKU") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ShortDescription") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StockQuantity") + .HasColumnType("integer"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("Weight") + .HasPrecision(18, 3) + .HasColumnType("numeric(18,3)"); + + b.Property("Width") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("SKU") + .IsUnique(); + + b.HasIndex("Slug") + .IsUnique(); + + b.HasIndex("SupplierId"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.ProductCategory", b => + { + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.HasKey("ProductId", "CategoryId"); + + b.HasIndex("CategoryId"); + + b.ToTable("ProductCategories"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.ProductDiscount", b => + { + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("DiscountId") + .HasColumnType("uuid"); + + b.HasKey("ProductId", "DiscountId"); + + b.HasIndex("DiscountId"); + + b.ToTable("ProductDiscounts"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.ProductVariant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ImageUrl") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PriceAdjustment") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("SKU") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("StockQuantity") + .HasColumnType("integer"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.ToTable("ProductVariants"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Review", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Comment") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("IsApproved") + .HasColumnType("boolean"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("Rating") + .HasColumnType("integer"); + + b.Property("ReviewDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Title") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("ProductId"); + + b.ToTable("Reviews"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Setting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Group") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Value") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.ShippingMethod", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BaseCost") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("EstimatedDeliveryTime") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("MinimumOrderAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("RequiresTracking") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("ShippingMethods"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddressId") + .HasColumnType("uuid"); + + b.Property("ContactPerson") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("PhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("AddressId"); + + b.ToTable("Suppliers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Address", b => + { + b.HasOne("Webshop.Domain.Entities.Customer", "Customer") + .WithMany("Addresses") + .HasForeignKey("CustomerId"); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Category", b => + { + b.HasOne("Webshop.Domain.Entities.Category", "ParentCategory") + .WithMany("SubCategories") + .HasForeignKey("ParentCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("ParentCategory"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.CategoryDiscount", b => + { + b.HasOne("Webshop.Domain.Entities.Category", "Category") + .WithMany("CategoryDiscounts") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Webshop.Domain.Entities.Discount", "Discount") + .WithMany("CategoryDiscounts") + .HasForeignKey("DiscountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Discount"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Order", b => + { + b.HasOne("Webshop.Domain.Entities.Address", "BillingAddress") + .WithMany() + .HasForeignKey("BillingAddressId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Webshop.Domain.Entities.Customer", "Customer") + .WithMany("Orders") + .HasForeignKey("CustomerId"); + + b.HasOne("Webshop.Domain.Entities.PaymentMethod", "PaymentMethodInfo") + .WithMany() + .HasForeignKey("PaymentMethodId"); + + b.HasOne("Webshop.Domain.Entities.Address", "ShippingAddress") + .WithMany() + .HasForeignKey("ShippingAddressId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Webshop.Domain.Entities.ShippingMethod", "ShippingMethodInfo") + .WithMany() + .HasForeignKey("ShippingMethodId"); + + b.Navigation("BillingAddress"); + + b.Navigation("Customer"); + + b.Navigation("PaymentMethodInfo"); + + b.Navigation("ShippingAddress"); + + b.Navigation("ShippingMethodInfo"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.OrderItem", b => + { + b.HasOne("Webshop.Domain.Entities.Order", "Order") + .WithMany("OrderItems") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Webshop.Domain.Entities.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Webshop.Domain.Entities.ProductVariant", "ProductVariant") + .WithMany() + .HasForeignKey("ProductVariantId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Order"); + + b.Navigation("Product"); + + b.Navigation("ProductVariant"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Product", b => + { + b.HasOne("Webshop.Domain.Entities.Supplier", "Supplier") + .WithMany("Products") + .HasForeignKey("SupplierId"); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.ProductCategory", b => + { + b.HasOne("Webshop.Domain.Entities.Category", "Category") + .WithMany("ProductCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Webshop.Domain.Entities.Product", "Product") + .WithMany("ProductCategories") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.ProductDiscount", b => + { + b.HasOne("Webshop.Domain.Entities.Discount", "Discount") + .WithMany("ProductDiscounts") + .HasForeignKey("DiscountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Webshop.Domain.Entities.Product", "Product") + .WithMany("ProductDiscounts") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Discount"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.ProductVariant", b => + { + b.HasOne("Webshop.Domain.Entities.Product", "Product") + .WithMany("Variants") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Review", b => + { + b.HasOne("Webshop.Domain.Entities.Customer", "Customer") + .WithMany("Reviews") + .HasForeignKey("CustomerId"); + + b.HasOne("Webshop.Domain.Entities.Product", "Product") + .WithMany("Reviews") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Supplier", b => + { + b.HasOne("Webshop.Domain.Entities.Address", "Address") + .WithMany() + .HasForeignKey("AddressId"); + + b.Navigation("Address"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Category", b => + { + b.Navigation("CategoryDiscounts"); + + b.Navigation("ProductCategories"); + + b.Navigation("SubCategories"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Customer", b => + { + b.Navigation("Addresses"); + + b.Navigation("Orders"); + + b.Navigation("Reviews"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Discount", b => + { + b.Navigation("CategoryDiscounts"); + + b.Navigation("ProductDiscounts"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Order", b => + { + b.Navigation("OrderItems"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Product", b => + { + b.Navigation("ProductCategories"); + + b.Navigation("ProductDiscounts"); + + b.Navigation("Reviews"); + + b.Navigation("Variants"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Supplier", b => + { + b.Navigation("Products"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Webshop.Infrastructure/Migrations/20250721175118_InitialCreate.cs b/Webshop.Infrastructure/Migrations/20250721175118_InitialCreate.cs new file mode 100644 index 0000000..d58ae5b --- /dev/null +++ b/Webshop.Infrastructure/Migrations/20250721175118_InitialCreate.cs @@ -0,0 +1,818 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Webshop.Infrastructure.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Categories", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + Slug = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + ParentCategoryId = table.Column(type: "uuid", nullable: true), + ImageUrl = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + IsActive = table.Column(type: "boolean", nullable: false), + DisplayOrder = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Categories", x => x.Id); + table.ForeignKey( + name: "FK_Categories_Categories_ParentCategoryId", + column: x => x.ParentCategoryId, + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Customers", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + AspNetUserId = table.Column(type: "character varying(450)", maxLength: 450, nullable: false), + FirstName = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + LastName = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Email = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + PhoneNumber = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + CreatedDate = table.Column(type: "timestamp with time zone", nullable: false), + LastLoginDate = table.Column(type: "timestamp with time zone", nullable: true), + IsActive = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Customers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Discounts", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + DiscountType = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + DiscountValue = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + StartDate = table.Column(type: "timestamp with time zone", nullable: false), + EndDate = table.Column(type: "timestamp with time zone", nullable: true), + IsActive = table.Column(type: "boolean", nullable: false), + RequiresCouponCode = table.Column(type: "boolean", nullable: false), + CouponCode = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + MinimumOrderAmount = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true), + MaximumUsageCount = table.Column(type: "integer", nullable: true), + CurrentUsageCount = table.Column(type: "integer", nullable: false), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Discounts", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PaymentMethods", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Description = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + IsActive = table.Column(type: "boolean", nullable: false), + PaymentGatewayType = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + ProcessingFee = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PaymentMethods", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Roles", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Roles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Settings", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Key = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + Value = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + Description = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + IsActive = table.Column(type: "boolean", nullable: false), + Group = table.Column(type: "character varying(100)", maxLength: 100, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Settings", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ShippingMethods", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Description = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + BaseCost = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + MinimumOrderAmount = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true), + IsActive = table.Column(type: "boolean", nullable: false), + EstimatedDeliveryTime = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + RequiresTracking = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ShippingMethods", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + UserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "boolean", nullable: false), + PasswordHash = table.Column(type: "text", nullable: true), + SecurityStamp = table.Column(type: "text", nullable: true), + ConcurrencyStamp = table.Column(type: "text", nullable: true), + PhoneNumber = table.Column(type: "text", nullable: true), + PhoneNumberConfirmed = table.Column(type: "boolean", nullable: false), + TwoFactorEnabled = table.Column(type: "boolean", nullable: false), + LockoutEnd = table.Column(type: "timestamp with time zone", nullable: true), + LockoutEnabled = table.Column(type: "boolean", nullable: false), + AccessFailedCount = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Addresses", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CustomerId = table.Column(type: "uuid", nullable: true), + AddressType = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + Street = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + Street2 = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + City = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + State = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + PostalCode = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Country = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + CompanyName = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + FirstName = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + LastName = table.Column(type: "character varying(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Addresses", x => x.Id); + table.ForeignKey( + name: "FK_Addresses_Customers_CustomerId", + column: x => x.CustomerId, + principalTable: "Customers", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "CategoryDiscounts", + columns: table => new + { + CategoryId = table.Column(type: "uuid", nullable: false), + DiscountId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CategoryDiscounts", x => new { x.CategoryId, x.DiscountId }); + table.ForeignKey( + name: "FK_CategoryDiscounts_Categories_CategoryId", + column: x => x.CategoryId, + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CategoryDiscounts_Discounts_DiscountId", + column: x => x.DiscountId, + principalTable: "Discounts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RoleClaims", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + RoleId = table.Column(type: "text", nullable: false), + ClaimType = table.Column(type: "text", nullable: true), + ClaimValue = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_RoleClaims_Roles_RoleId", + column: x => x.RoleId, + principalTable: "Roles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "UserClaims", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "text", nullable: false), + ClaimType = table.Column(type: "text", nullable: true), + ClaimValue = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_UserClaims", x => x.Id); + table.ForeignKey( + name: "FK_UserClaims_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "UserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "text", nullable: false), + ProviderKey = table.Column(type: "text", nullable: false), + ProviderDisplayName = table.Column(type: "text", nullable: true), + UserId = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_UserLogins_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "UserRoles", + columns: table => new + { + UserId = table.Column(type: "text", nullable: false), + RoleId = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_UserRoles_Roles_RoleId", + column: x => x.RoleId, + principalTable: "Roles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_UserRoles_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "UserTokens", + columns: table => new + { + UserId = table.Column(type: "text", nullable: false), + LoginProvider = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false), + Value = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_UserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_UserTokens_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + OrderNumber = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + CustomerId = table.Column(type: "uuid", nullable: true), + GuestEmail = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + GuestPhoneNumber = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + OrderDate = table.Column(type: "timestamp with time zone", nullable: false), + OrderStatus = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + OrderTotal = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + ShippingCost = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + TaxAmount = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + DiscountAmount = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + PaymentStatus = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + PaymentMethod = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + PaymentMethodId = table.Column(type: "uuid", nullable: true), + ShippingMethodId = table.Column(type: "uuid", nullable: true), + TransactionId = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + BillingAddressId = table.Column(type: "uuid", nullable: false), + ShippingAddressId = table.Column(type: "uuid", nullable: false), + CustomerNotes = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + AdminNotes = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + table.ForeignKey( + name: "FK_Orders_Addresses_BillingAddressId", + column: x => x.BillingAddressId, + principalTable: "Addresses", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Orders_Addresses_ShippingAddressId", + column: x => x.ShippingAddressId, + principalTable: "Addresses", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Orders_Customers_CustomerId", + column: x => x.CustomerId, + principalTable: "Customers", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_Orders_PaymentMethods_PaymentMethodId", + column: x => x.PaymentMethodId, + principalTable: "PaymentMethods", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_Orders_ShippingMethods_ShippingMethodId", + column: x => x.ShippingMethodId, + principalTable: "ShippingMethods", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "Suppliers", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + ContactPerson = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + Email = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + PhoneNumber = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + AddressId = table.Column(type: "uuid", nullable: true), + Notes = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Suppliers", x => x.Id); + table.ForeignKey( + name: "FK_Suppliers_Addresses_AddressId", + column: x => x.AddressId, + principalTable: "Addresses", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "Products", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + Description = table.Column(type: "character varying(4000)", maxLength: 4000, nullable: true), + ShortDescription = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + SKU = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + Price = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + OldPrice = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true), + IsActive = table.Column(type: "boolean", nullable: false), + IsInStock = table.Column(type: "boolean", nullable: false), + StockQuantity = table.Column(type: "integer", nullable: false), + Weight = table.Column(type: "numeric(18,3)", precision: 18, scale: 3, nullable: true), + Width = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true), + Height = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true), + Length = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true), + ImageUrl = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + Slug = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + CreatedDate = table.Column(type: "timestamp with time zone", nullable: false), + LastModifiedDate = table.Column(type: "timestamp with time zone", nullable: true), + SupplierId = table.Column(type: "uuid", nullable: true), + PurchasePrice = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Products", x => x.Id); + table.ForeignKey( + name: "FK_Products_Suppliers_SupplierId", + column: x => x.SupplierId, + principalTable: "Suppliers", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "ProductCategories", + columns: table => new + { + ProductId = table.Column(type: "uuid", nullable: false), + CategoryId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProductCategories", x => new { x.ProductId, x.CategoryId }); + table.ForeignKey( + name: "FK_ProductCategories_Categories_CategoryId", + column: x => x.CategoryId, + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProductCategories_Products_ProductId", + column: x => x.ProductId, + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ProductDiscounts", + columns: table => new + { + ProductId = table.Column(type: "uuid", nullable: false), + DiscountId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProductDiscounts", x => new { x.ProductId, x.DiscountId }); + table.ForeignKey( + name: "FK_ProductDiscounts_Discounts_DiscountId", + column: x => x.DiscountId, + principalTable: "Discounts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProductDiscounts_Products_ProductId", + column: x => x.ProductId, + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ProductVariants", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + ProductId = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Value = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + SKU = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + PriceAdjustment = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + StockQuantity = table.Column(type: "integer", nullable: false), + ImageUrl = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + IsActive = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProductVariants", x => x.Id); + table.ForeignKey( + name: "FK_ProductVariants_Products_ProductId", + column: x => x.ProductId, + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Reviews", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + ProductId = table.Column(type: "uuid", nullable: false), + CustomerId = table.Column(type: "uuid", nullable: true), + Rating = table.Column(type: "integer", nullable: false), + Title = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + Comment = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + ReviewDate = table.Column(type: "timestamp with time zone", nullable: false), + IsApproved = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Reviews", x => x.Id); + table.ForeignKey( + name: "FK_Reviews_Customers_CustomerId", + column: x => x.CustomerId, + principalTable: "Customers", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_Reviews_Products_ProductId", + column: x => x.ProductId, + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + OrderId = table.Column(type: "uuid", nullable: false), + ProductId = table.Column(type: "uuid", nullable: true), + ProductVariantId = table.Column(type: "uuid", nullable: true), + ProductName = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + ProductSKU = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + Quantity = table.Column(type: "integer", nullable: false), + UnitPrice = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + TotalPrice = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_OrderItems_ProductVariants_ProductVariantId", + column: x => x.ProductVariantId, + principalTable: "ProductVariants", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_OrderItems_Products_ProductId", + column: x => x.ProductId, + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateIndex( + name: "IX_Addresses_CustomerId", + table: "Addresses", + column: "CustomerId"); + + migrationBuilder.CreateIndex( + name: "IX_Categories_ParentCategoryId", + table: "Categories", + column: "ParentCategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_Categories_Slug", + table: "Categories", + column: "Slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_CategoryDiscounts_DiscountId", + table: "CategoryDiscounts", + column: "DiscountId"); + + migrationBuilder.CreateIndex( + name: "IX_Discounts_CouponCode", + table: "Discounts", + column: "CouponCode", + unique: true, + filter: "\"CouponCode\" IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_ProductId", + table: "OrderItems", + column: "ProductId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_ProductVariantId", + table: "OrderItems", + column: "ProductVariantId"); + + migrationBuilder.CreateIndex( + name: "IX_Orders_BillingAddressId", + table: "Orders", + column: "BillingAddressId"); + + migrationBuilder.CreateIndex( + name: "IX_Orders_CustomerId", + table: "Orders", + column: "CustomerId"); + + migrationBuilder.CreateIndex( + name: "IX_Orders_OrderNumber", + table: "Orders", + column: "OrderNumber", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Orders_PaymentMethodId", + table: "Orders", + column: "PaymentMethodId"); + + migrationBuilder.CreateIndex( + name: "IX_Orders_ShippingAddressId", + table: "Orders", + column: "ShippingAddressId"); + + migrationBuilder.CreateIndex( + name: "IX_Orders_ShippingMethodId", + table: "Orders", + column: "ShippingMethodId"); + + migrationBuilder.CreateIndex( + name: "IX_ProductCategories_CategoryId", + table: "ProductCategories", + column: "CategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_ProductDiscounts_DiscountId", + table: "ProductDiscounts", + column: "DiscountId"); + + migrationBuilder.CreateIndex( + name: "IX_Products_SKU", + table: "Products", + column: "SKU", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Products_Slug", + table: "Products", + column: "Slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Products_SupplierId", + table: "Products", + column: "SupplierId"); + + migrationBuilder.CreateIndex( + name: "IX_ProductVariants_ProductId", + table: "ProductVariants", + column: "ProductId"); + + migrationBuilder.CreateIndex( + name: "IX_Reviews_CustomerId", + table: "Reviews", + column: "CustomerId"); + + migrationBuilder.CreateIndex( + name: "IX_Reviews_ProductId", + table: "Reviews", + column: "ProductId"); + + migrationBuilder.CreateIndex( + name: "IX_RoleClaims_RoleId", + table: "RoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "Roles", + column: "NormalizedName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Settings_Key", + table: "Settings", + column: "Key", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Suppliers_AddressId", + table: "Suppliers", + column: "AddressId"); + + migrationBuilder.CreateIndex( + name: "IX_UserClaims_UserId", + table: "UserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_UserLogins_UserId", + table: "UserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_UserRoles_RoleId", + table: "UserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "Users", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "Users", + column: "NormalizedUserName", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CategoryDiscounts"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "ProductCategories"); + + migrationBuilder.DropTable( + name: "ProductDiscounts"); + + migrationBuilder.DropTable( + name: "Reviews"); + + migrationBuilder.DropTable( + name: "RoleClaims"); + + migrationBuilder.DropTable( + name: "Settings"); + + migrationBuilder.DropTable( + name: "UserClaims"); + + migrationBuilder.DropTable( + name: "UserLogins"); + + migrationBuilder.DropTable( + name: "UserRoles"); + + migrationBuilder.DropTable( + name: "UserTokens"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropTable( + name: "ProductVariants"); + + migrationBuilder.DropTable( + name: "Categories"); + + migrationBuilder.DropTable( + name: "Discounts"); + + migrationBuilder.DropTable( + name: "Roles"); + + migrationBuilder.DropTable( + name: "Users"); + + migrationBuilder.DropTable( + name: "PaymentMethods"); + + migrationBuilder.DropTable( + name: "ShippingMethods"); + + migrationBuilder.DropTable( + name: "Products"); + + migrationBuilder.DropTable( + name: "Suppliers"); + + migrationBuilder.DropTable( + name: "Addresses"); + + migrationBuilder.DropTable( + name: "Customers"); + } + } +} diff --git a/Webshop.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs b/Webshop.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 0000000..1d74d7a --- /dev/null +++ b/Webshop.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,1217 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Webshop.Infrastructure.Data; + +#nullable disable + +namespace Webshop.Infrastructure.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.18") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("Roles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("RoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("UserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("UserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("UserTokens", (string)null); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Address", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddressType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CompanyName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("State") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Street") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Street2") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.ToTable("Addresses"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("ImageUrl") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ParentCategoryId") + .HasColumnType("uuid"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("ParentCategoryId"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.CategoryDiscount", b => + { + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("DiscountId") + .HasColumnType("uuid"); + + b.HasKey("CategoryId", "DiscountId"); + + b.HasIndex("DiscountId"); + + b.ToTable("CategoryDiscounts"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AspNetUserId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("CreatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PhoneNumber") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Discount", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CouponCode") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CurrentUsageCount") + .HasColumnType("integer"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("DiscountType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DiscountValue") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("MaximumUsageCount") + .HasColumnType("integer"); + + b.Property("MinimumOrderAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RequiresCouponCode") + .HasColumnType("boolean"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CouponCode") + .IsUnique() + .HasFilter("\"CouponCode\" IS NOT NULL"); + + b.ToTable("Discounts"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminNotes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("BillingAddressId") + .HasColumnType("uuid"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("CustomerNotes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("GuestEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GuestPhoneNumber") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("OrderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrderNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrderStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrderTotal") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PaymentMethodId") + .HasColumnType("uuid"); + + b.Property("PaymentStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ShippingAddressId") + .HasColumnType("uuid"); + + b.Property("ShippingCost") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("ShippingMethodId") + .HasColumnType("uuid"); + + b.Property("TaxAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("TransactionId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("BillingAddressId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("OrderNumber") + .IsUnique(); + + b.HasIndex("PaymentMethodId"); + + b.HasIndex("ShippingAddressId"); + + b.HasIndex("ShippingMethodId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("ProductName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProductSKU") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ProductVariantId") + .HasColumnType("uuid"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.Property("TotalPrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("UnitPrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("ProductId"); + + b.HasIndex("ProductVariantId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.PaymentMethod", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PaymentGatewayType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ProcessingFee") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.HasKey("Id"); + + b.ToTable("PaymentMethods"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("Height") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("ImageUrl") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsInStock") + .HasColumnType("boolean"); + + b.Property("LastModifiedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Length") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("OldPrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("Price") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("PurchasePrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("SKU") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ShortDescription") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StockQuantity") + .HasColumnType("integer"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("Weight") + .HasPrecision(18, 3) + .HasColumnType("numeric(18,3)"); + + b.Property("Width") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("SKU") + .IsUnique(); + + b.HasIndex("Slug") + .IsUnique(); + + b.HasIndex("SupplierId"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.ProductCategory", b => + { + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.HasKey("ProductId", "CategoryId"); + + b.HasIndex("CategoryId"); + + b.ToTable("ProductCategories"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.ProductDiscount", b => + { + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("DiscountId") + .HasColumnType("uuid"); + + b.HasKey("ProductId", "DiscountId"); + + b.HasIndex("DiscountId"); + + b.ToTable("ProductDiscounts"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.ProductVariant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ImageUrl") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PriceAdjustment") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("SKU") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("StockQuantity") + .HasColumnType("integer"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.ToTable("ProductVariants"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Review", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Comment") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("IsApproved") + .HasColumnType("boolean"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("Rating") + .HasColumnType("integer"); + + b.Property("ReviewDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Title") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("ProductId"); + + b.ToTable("Reviews"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Setting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Group") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Value") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.ShippingMethod", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BaseCost") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("EstimatedDeliveryTime") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("MinimumOrderAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("RequiresTracking") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("ShippingMethods"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddressId") + .HasColumnType("uuid"); + + b.Property("ContactPerson") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("PhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("AddressId"); + + b.ToTable("Suppliers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Address", b => + { + b.HasOne("Webshop.Domain.Entities.Customer", "Customer") + .WithMany("Addresses") + .HasForeignKey("CustomerId"); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Category", b => + { + b.HasOne("Webshop.Domain.Entities.Category", "ParentCategory") + .WithMany("SubCategories") + .HasForeignKey("ParentCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("ParentCategory"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.CategoryDiscount", b => + { + b.HasOne("Webshop.Domain.Entities.Category", "Category") + .WithMany("CategoryDiscounts") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Webshop.Domain.Entities.Discount", "Discount") + .WithMany("CategoryDiscounts") + .HasForeignKey("DiscountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Discount"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Order", b => + { + b.HasOne("Webshop.Domain.Entities.Address", "BillingAddress") + .WithMany() + .HasForeignKey("BillingAddressId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Webshop.Domain.Entities.Customer", "Customer") + .WithMany("Orders") + .HasForeignKey("CustomerId"); + + b.HasOne("Webshop.Domain.Entities.PaymentMethod", "PaymentMethodInfo") + .WithMany() + .HasForeignKey("PaymentMethodId"); + + b.HasOne("Webshop.Domain.Entities.Address", "ShippingAddress") + .WithMany() + .HasForeignKey("ShippingAddressId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Webshop.Domain.Entities.ShippingMethod", "ShippingMethodInfo") + .WithMany() + .HasForeignKey("ShippingMethodId"); + + b.Navigation("BillingAddress"); + + b.Navigation("Customer"); + + b.Navigation("PaymentMethodInfo"); + + b.Navigation("ShippingAddress"); + + b.Navigation("ShippingMethodInfo"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.OrderItem", b => + { + b.HasOne("Webshop.Domain.Entities.Order", "Order") + .WithMany("OrderItems") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Webshop.Domain.Entities.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Webshop.Domain.Entities.ProductVariant", "ProductVariant") + .WithMany() + .HasForeignKey("ProductVariantId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Order"); + + b.Navigation("Product"); + + b.Navigation("ProductVariant"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Product", b => + { + b.HasOne("Webshop.Domain.Entities.Supplier", "Supplier") + .WithMany("Products") + .HasForeignKey("SupplierId"); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.ProductCategory", b => + { + b.HasOne("Webshop.Domain.Entities.Category", "Category") + .WithMany("ProductCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Webshop.Domain.Entities.Product", "Product") + .WithMany("ProductCategories") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.ProductDiscount", b => + { + b.HasOne("Webshop.Domain.Entities.Discount", "Discount") + .WithMany("ProductDiscounts") + .HasForeignKey("DiscountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Webshop.Domain.Entities.Product", "Product") + .WithMany("ProductDiscounts") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Discount"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.ProductVariant", b => + { + b.HasOne("Webshop.Domain.Entities.Product", "Product") + .WithMany("Variants") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Review", b => + { + b.HasOne("Webshop.Domain.Entities.Customer", "Customer") + .WithMany("Reviews") + .HasForeignKey("CustomerId"); + + b.HasOne("Webshop.Domain.Entities.Product", "Product") + .WithMany("Reviews") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Supplier", b => + { + b.HasOne("Webshop.Domain.Entities.Address", "Address") + .WithMany() + .HasForeignKey("AddressId"); + + b.Navigation("Address"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Category", b => + { + b.Navigation("CategoryDiscounts"); + + b.Navigation("ProductCategories"); + + b.Navigation("SubCategories"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Customer", b => + { + b.Navigation("Addresses"); + + b.Navigation("Orders"); + + b.Navigation("Reviews"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Discount", b => + { + b.Navigation("CategoryDiscounts"); + + b.Navigation("ProductDiscounts"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Order", b => + { + b.Navigation("OrderItems"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Product", b => + { + b.Navigation("ProductCategories"); + + b.Navigation("ProductDiscounts"); + + b.Navigation("Reviews"); + + b.Navigation("Variants"); + }); + + modelBuilder.Entity("Webshop.Domain.Entities.Supplier", b => + { + b.Navigation("Products"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Webshop.Infrastructure/Repositories/ProductRepository.cs b/Webshop.Infrastructure/Repositories/ProductRepository.cs new file mode 100644 index 0000000..fae5337 --- /dev/null +++ b/Webshop.Infrastructure/Repositories/ProductRepository.cs @@ -0,0 +1,51 @@ +// src/Webshop.Infrastructure/Repositories/ProductRepository.cs +using Microsoft.EntityFrameworkCore; +using Webshop.Domain.Entities; +using Webshop.Domain.Interfaces; +using Webshop.Infrastructure.Data; + +namespace Webshop.Infrastructure.Repositories +{ + public class ProductRepository : IProductRepository + { + private readonly ApplicationDbContext _context; + + // Wir lassen uns den DbContext per Dependency Injection geben + public ProductRepository(ApplicationDbContext context) + { + _context = context; + } + + public async Task> GetAllProductsAsync() + { + return await _context.Products.ToListAsync(); + } + + public async Task GetProductByIdAsync(Guid id) + { + return await _context.Products.FindAsync(id); + } + + public async Task AddProductAsync(Product product) + { + _context.Products.Add(product); + await _context.SaveChangesAsync(); // Wichtig: Änderungen speichern + } + + public async Task UpdateProductAsync(Product product) + { + _context.Products.Update(product); + await _context.SaveChangesAsync(); + } + + public async Task DeleteProductAsync(Guid id) + { + var product = await _context.Products.FindAsync(id); + if (product != null) + { + _context.Products.Remove(product); + await _context.SaveChangesAsync(); + } + } + } +} \ No newline at end of file diff --git a/Webshop.Infrastructure/Webshop.Infrastructure.csproj b/Webshop.Infrastructure/Webshop.Infrastructure.csproj new file mode 100644 index 0000000..07beb5a --- /dev/null +++ b/Webshop.Infrastructure/Webshop.Infrastructure.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + +