Compare commits
54
Commits
20b747e89c
...
1.50.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
466458e230 | ||
|
|
60fcc70e98 | ||
|
|
141d32f591 | ||
|
|
38e765203d | ||
|
|
d9e7f225ae | ||
|
|
9eb3526a2e | ||
|
|
4397976ed8 | ||
|
|
2546c34ffc | ||
|
|
4e53ff8a37 | ||
|
|
2a0b34c730 | ||
|
|
6ae63e2ad1 | ||
|
|
902942eee6 | ||
|
|
70860efcfb | ||
|
|
f5efdde37c | ||
|
|
1592d5dc8f | ||
|
|
50a8a59d92 | ||
|
|
b70d9559b0 | ||
|
|
81d5e8f07c | ||
|
|
20a53942b5 | ||
|
|
9edb2aa4aa | ||
|
|
6ed023f2cf | ||
|
|
2c9f5a846c | ||
|
|
41f7c05be3 | ||
|
|
1a03355e84 | ||
|
|
7743c3178e | ||
|
|
ab3d8e6e9a | ||
|
|
db4c348288 | ||
|
|
6683234642 | ||
|
|
6ddbb9479a | ||
|
|
6c7349a0f8 | ||
|
|
e97fd6cd3f | ||
|
|
184c7c252a | ||
|
|
bfe8c458d6 | ||
|
|
e6e0475db1 | ||
|
|
5090c60797 | ||
|
|
9432252e15 | ||
|
|
47111a1a3a | ||
|
|
6eb3d50375 | ||
|
|
4deb732804 | ||
|
|
20d9387d0b | ||
|
|
9f6d0ccaa0 | ||
|
|
1acbc4d213 | ||
|
|
8c99668fac | ||
|
|
ad44f46204 | ||
|
|
49d999c1e3 | ||
|
|
9ed4777a18 | ||
|
|
0cf44f68cc | ||
|
|
41ed5a4288 | ||
|
|
bbcba5e06c | ||
|
|
502cc326dd | ||
|
|
4675d4c5fc | ||
|
|
f80bb2fff9 | ||
|
|
6767906b0d | ||
|
|
a344af4498 |
@@ -3,6 +3,45 @@ root = true
|
|||||||
|
|
||||||
# C# files
|
# C# files
|
||||||
[*.cs]
|
[*.cs]
|
||||||
|
# IDE0250: Prefer make struct 'readonly'
|
||||||
|
dotnet_diagnostic.IDE0250.severity = warning
|
||||||
|
|
||||||
|
# IDE0060: Remove unused parameters (Good cleanup pairing)
|
||||||
|
dotnet_diagnostic.IDE0060.severity = warning
|
||||||
|
|
||||||
|
# CA1852: Seal internal types (Available in modern .NET)
|
||||||
|
dotnet_diagnostic.CA1852.severity = warning
|
||||||
|
|
||||||
|
# MA0018: Add sealed modifier to types that are never inherited
|
||||||
|
dotnet_diagnostic.MA0018.severity = warning
|
||||||
|
|
||||||
|
# Enforce that classes should be sealed
|
||||||
|
dotnet_diagnostic.MA0053.severity = warning
|
||||||
|
|
||||||
|
# CRITICAL: Force the analyzer to also flag PUBLIC classes, not just internal ones
|
||||||
|
meziantou_analyzer.MA0053.public_class_should_be_sealed = true
|
||||||
|
MA0053.public_class_should_be_sealed = true
|
||||||
|
|
||||||
|
# Keep the rule active as a warning by default
|
||||||
|
dotnet_diagnostic.MA0048.severity = warning
|
||||||
|
|
||||||
|
# Specific exclusions for Meziantou.Analyzer MA0048
|
||||||
|
# Disable the rule for enums
|
||||||
|
meziantou_analyzer.MA0048.exclude_enums = true
|
||||||
|
|
||||||
|
# Disable the rule for records
|
||||||
|
meziantou_analyzer.MA0048.exclude_records = true
|
||||||
|
|
||||||
|
#EXCLUDE specific files that are meant to hold grouped enums/records
|
||||||
|
dotnet_diagnostic.MA0048.severity = warning
|
||||||
|
|
||||||
|
# Disable the requirement to specify ConfigureAwait(false)
|
||||||
|
dotnet_diagnostic.MA0004.severity = none
|
||||||
|
|
||||||
|
# ALTERNATIVE: Exclude any file ending with 'Enums.cs' or 'Records.cs'
|
||||||
|
# (e.g., BillingEnums.cs, CustomerRecords.cs)
|
||||||
|
[**/*{Enums,Records}.cs]
|
||||||
|
dotnet_diagnostic.MA0048.severity = none
|
||||||
|
|
||||||
#### Core EditorConfig Options ####
|
#### Core EditorConfig Options ####
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Seed.Configuration;
|
||||||
|
|
||||||
|
public class CdnSettings
|
||||||
|
{
|
||||||
|
public string? BaseCdn { get; set; }
|
||||||
|
|
||||||
|
public string[]? BookCovers { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<UserSecretsId>5c3bc894-8654-4691-99e8-f90d3414843f</UserSecretsId>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- Quartz Scheduler-->
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Bogus" Version="35.6.5" />
|
||||||
|
<PackageReference Include="Meziantou.Analyzer" Version="3.0.96">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="OpenTelemetry" Version="1.15.3" />
|
||||||
|
<PackageReference Include="Quartz" Version="3.18.1" />
|
||||||
|
<PackageReference Include="Quartz.Plugins" Version="3.18.1" />
|
||||||
|
<PackageReference Include="Quartz.Plugins.TimeZoneConverter" Version="3.18.1" />
|
||||||
|
<PackageReference Include="Quartz.Serialization.SystemTextJson" Version="3.18.1" />
|
||||||
|
<PackageReference Include="Quartz.AspNetCore" Version="3.18.1" />
|
||||||
|
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.18.1" />
|
||||||
|
|
||||||
|
<!-- Global Usings -->
|
||||||
|
<Using Include="Quartz" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Configuration -->
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.8" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.8" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.8" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.8" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.8" />
|
||||||
|
|
||||||
|
<!-- Global Usings -->
|
||||||
|
<Using Include="Microsoft.Extensions.Configuration" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Health Checks -->
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" />
|
||||||
|
<PackageReference Include="AspNetCore.HealthChecks.UI" Version="9.0.0" />
|
||||||
|
<PackageReference Include="AspNetCore.HealthChecks.UI.Core" Version="9.0.0" />
|
||||||
|
<PackageReference Include="AspNetCore.HealthChecks.UI.Data" Version="9.0.0" />
|
||||||
|
<PackageReference Include="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="9.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.8" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="10.0.8" />
|
||||||
|
|
||||||
|
<!-- Global Usings -->
|
||||||
|
<Using Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
|
||||||
|
<Using Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Open Telemetry -->
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.2" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.15.1" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.1" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.15.3" />
|
||||||
|
|
||||||
|
<!-- Global Usings -->
|
||||||
|
<Using Include="OpenTelemetry.Resources" />
|
||||||
|
<Using Include="OpenTelemetry.Exporter" />
|
||||||
|
<Using Include="OpenTelemetry.Logs" />
|
||||||
|
<Using Include="OpenTelemetry.Metrics" />
|
||||||
|
<Using Include="OpenTelemetry.Trace" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Database -->
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.8" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.8">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.8" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.8">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
|
||||||
|
|
||||||
|
<!-- Global Usings -->
|
||||||
|
<Using Include="Npgsql" />
|
||||||
|
<Using Include="Microsoft.EntityFrameworkCore" />
|
||||||
|
<Using Include="Microsoft.EntityFrameworkCore.Design" />
|
||||||
|
<Using Include="Microsoft.EntityFrameworkCore.Metadata.Builders" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Email -->
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="MailKit" Version="4.16.0" />
|
||||||
|
<PackageReference Include="MimeKit" Version="4.16.0" />
|
||||||
|
|
||||||
|
<!-- Global Usings-->
|
||||||
|
<Using Include="MimeKit" />
|
||||||
|
<Using Include="MailKit.Net.Smtp" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- CQRS -->
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FluentResults" Version="4.0.0" />
|
||||||
|
<PackageReference Include="Mediator.Abstractions" Version="3.0.2" />
|
||||||
|
|
||||||
|
<!-- Global Usings -->
|
||||||
|
<Using Include="FluentResults" />
|
||||||
|
<Using Include="Mediator" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Amazon S3 SDK -->
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AWSSDK.Extensions.NetCore.Setup" Version="4.0.4.1" />
|
||||||
|
<PackageReference Include="AWSSDK.S3" Version="4.0.23.4" />
|
||||||
|
<ProjectReference Include="..\LiteCharms.Features\LiteCharms.Features.csproj" />
|
||||||
|
|
||||||
|
<!-- global Usings -->
|
||||||
|
<Using Include="Amazon.S3" />
|
||||||
|
<Using Include="Amazon.S3.Model" />
|
||||||
|
<Using Include="Amazon.Runtime" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Shared Usings -->
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Bogus" />
|
||||||
|
<Using Include="System.Globalization" />
|
||||||
|
<Using Include="System.Reflection" />
|
||||||
|
<Using Include="Microsoft.AspNetCore.Builder" />
|
||||||
|
<Using Include="Microsoft.Extensions.Hosting" />
|
||||||
|
<Using Include="System.Text" />
|
||||||
|
<Using Include="System.Text.Json" />
|
||||||
|
<Using Include="System.Threading.Channels" />
|
||||||
|
<Using Include="System.Collections.ObjectModel" />
|
||||||
|
<Using Include="System.Diagnostics" />
|
||||||
|
<Using Include="System.Diagnostics.Metrics" />
|
||||||
|
<Using Include="Microsoft.Extensions.DependencyInjection" />
|
||||||
|
<Using Include="System.Security.Cryptography" />
|
||||||
|
<Using Include="Microsoft.Extensions.Options" />
|
||||||
|
<Using Include="Microsoft.Extensions.Logging" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\LiteCharms.Features.MidrandBooks\LiteCharms.Features.MidrandBooks.csproj" />
|
||||||
|
<ProjectReference Include="..\LiteCharms.Features\LiteCharms.Features.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="appsettings.json">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.AuthorBooks;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Authors;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Products;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Seed.Configuration;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Seed;
|
||||||
|
|
||||||
|
public class ProductsSeederService(ProductService productService, AuthorService authorService, BooksService booksService,
|
||||||
|
IOptions<CdnSettings> options, ILogger<ProductsSeederService> logger) : BackgroundService
|
||||||
|
{
|
||||||
|
private readonly CdnSettings cdnSettings = options.Value;
|
||||||
|
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Product Seeding started");
|
||||||
|
|
||||||
|
if (cdnSettings.BookCovers is null || cdnSettings.BookCovers.Length == 0)
|
||||||
|
{
|
||||||
|
logger.LogWarning("No book covers found in CDN settings. Seeding aborted.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Bogus Faker engine
|
||||||
|
var faker = new Faker();
|
||||||
|
var culture = CultureInfo.InvariantCulture;
|
||||||
|
|
||||||
|
// Ensure repeatable data sets if run multiple times by anchoring the seed
|
||||||
|
Randomizer.Seed = new Random(42);
|
||||||
|
|
||||||
|
foreach (var bookCover in cdnSettings.BookCovers)
|
||||||
|
{
|
||||||
|
if (stoppingToken.IsCancellationRequested) break;
|
||||||
|
|
||||||
|
// Generate beautifully mixed eclectic topics on the fly
|
||||||
|
var bookTopic = faker.PickRandom(
|
||||||
|
// --- Tech & IT ---
|
||||||
|
"C# 12 & Modern .NET Architecture",
|
||||||
|
"PostgreSQL Database Optimization",
|
||||||
|
"Docker & Kubernetes in Production",
|
||||||
|
"Domain-Driven Design Paradigms",
|
||||||
|
"Artificial Intelligence with Python",
|
||||||
|
|
||||||
|
// --- Sci-Fi & Fantasy ---
|
||||||
|
"The Chronicles of the Quantum Nebula",
|
||||||
|
"Legends of the Lost Cybernetic Kingdom",
|
||||||
|
"Parallel Dimensions and Rogue Time Streams",
|
||||||
|
"The Last Android in Neo-Johannesburg",
|
||||||
|
|
||||||
|
// --- Thrillers, Mystery & Crime ---
|
||||||
|
"The Midnight Code Cryptograph",
|
||||||
|
"Shadows in the Highveld",
|
||||||
|
"The Silent Witness of Midrand",
|
||||||
|
"Deception on the 14th Floor",
|
||||||
|
|
||||||
|
// --- Business, Finance & Wealth ---
|
||||||
|
"Mastering the South African Tech Market",
|
||||||
|
"The Modern Entrepreneur's Blueprint",
|
||||||
|
"Generational Wealth and Venture Capital",
|
||||||
|
"Negotiation Tactics for High-Stakes Deals",
|
||||||
|
|
||||||
|
// --- Self-Help & Personal Growth ---
|
||||||
|
"The Art of Relentless Focus",
|
||||||
|
"Building High-Performance Habits",
|
||||||
|
"The Mindfulness Guide for Software Engineers",
|
||||||
|
"Unlocking Creative Flow Under Pressure"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Defensive Length Processing to avoid Entity Framework / Postgres string truncation crashes
|
||||||
|
var rawTitle = $"{faker.Company.CatchPhrase()} with {bookTopic}";
|
||||||
|
var bookTitle = rawTitle.Length > 255 ? rawTitle[..252] + "..." : rawTitle;
|
||||||
|
|
||||||
|
var rawSummary = $"A comprehensive guide to mastering {bookTopic}. Learn modern implementation techniques through real-world software engineering paradigms.";
|
||||||
|
var bookSummary = rawSummary.Length > 512 ? rawSummary[..509] + "..." : rawSummary;
|
||||||
|
|
||||||
|
// Generating a single concise paragraph ensures a rich text description falling safely well under 1024
|
||||||
|
var rawDescription = faker.Lorem.Paragraph(3);
|
||||||
|
var bookDescription = rawDescription.Length > 1024 ? rawDescription[..1021] + "..." : rawDescription;
|
||||||
|
|
||||||
|
var authorFirstName = faker.Name.FirstName();
|
||||||
|
var authorLastName = faker.Name.LastName();
|
||||||
|
var publisherCompany = faker.Company.CompanyName();
|
||||||
|
|
||||||
|
// Step 1: Add Product
|
||||||
|
var productCreateResult = await productService.CreateProductAsync(new Products.Models.CreateProduct
|
||||||
|
{
|
||||||
|
Name = bookTitle,
|
||||||
|
Summary = bookSummary,
|
||||||
|
Description = bookDescription,
|
||||||
|
ImageUrl = $"{cdnSettings.BaseCdn}{bookCover}",
|
||||||
|
Type = ProductTypes.Book,
|
||||||
|
Metadata = new Models.ProductMetadata
|
||||||
|
{
|
||||||
|
CopyrightInfo = $"© {DateTime.UtcNow.Year} {publisherCompany}. All rights reserved.",
|
||||||
|
ManufactureDate = faker.Date.Past(3).ToString("yyyy-MM-dd", culture),
|
||||||
|
Manufacturer = $"{authorFirstName} {authorLastName} / {publisherCompany}",
|
||||||
|
SerialNumber = faker.Phone.PhoneNumber("978-##########")
|
||||||
|
},
|
||||||
|
Categories = ["Coding", "Computers", "IT"]
|
||||||
|
}, stoppingToken);
|
||||||
|
|
||||||
|
if (productCreateResult.IsFailed)
|
||||||
|
{
|
||||||
|
logger.LogError("Failed to create product: {Error}", productCreateResult.Errors[0].Message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Enable product so it can show on the shop
|
||||||
|
var enableProductResult = await productService.UpdateProductStatusAsync(productId: productCreateResult.Value, isEnabled: true, stoppingToken);
|
||||||
|
|
||||||
|
if (enableProductResult.IsFailed)
|
||||||
|
{
|
||||||
|
logger.LogError("Failed to enable created product: {Error}", enableProductResult.Errors[0].Message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Create Product Price
|
||||||
|
var productPriceCreateResult = await productService.CreateProductPriceAsync(productId: productCreateResult.Value, request: new Products.Models.CreateProductPrice
|
||||||
|
{
|
||||||
|
// Generates fair, dynamic prices in Rands between R150 and R650, snapped neatly to integers
|
||||||
|
Amount = Math.Round(faker.Random.Decimal(150m, 650m), 2),
|
||||||
|
Discount = 0.0m
|
||||||
|
}, stoppingToken);
|
||||||
|
|
||||||
|
if (productPriceCreateResult.IsFailed)
|
||||||
|
{
|
||||||
|
logger.LogError("Failed to create product price: {Error}", productPriceCreateResult.Errors[0].Message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Create Author
|
||||||
|
var authorCreateResult = await authorService.CreateAuthorAsync(request: new Authors.Models.CreateAuthor
|
||||||
|
{
|
||||||
|
Name = authorFirstName,
|
||||||
|
LastName = authorLastName,
|
||||||
|
Company = publisherCompany,
|
||||||
|
VatNumber = faker.Random.Bool() ? faker.Phone.PhoneNumber("4#########") : "",
|
||||||
|
PublisherType = faker.PickRandom<PublisherTypes>(),
|
||||||
|
Email = faker.Internet.Email(authorFirstName, authorLastName),
|
||||||
|
Website = faker.Internet.Url(),
|
||||||
|
ImageUrl = faker.Internet.Avatar(),
|
||||||
|
SocialMedia =
|
||||||
|
[
|
||||||
|
new Models.SocialMedia
|
||||||
|
{
|
||||||
|
Name = "LinkedIn",
|
||||||
|
ImageUrl = "https://cdn.example.com/icons/linkedin.png",
|
||||||
|
Type = SocialMediaTypes.LinkedIn,
|
||||||
|
Url = $"https://linkedin.com/in/{authorFirstName.ToLower(culture)}-{authorLastName.ToLower(culture)}"
|
||||||
|
},
|
||||||
|
new Models.SocialMedia
|
||||||
|
{
|
||||||
|
Name = "GitHub",
|
||||||
|
ImageUrl = "https://cdn.example.com/icons/github.png",
|
||||||
|
Type = SocialMediaTypes.GitHub,
|
||||||
|
Url = $"https://github.com/tech-{authorFirstName.ToLower(culture)}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
Biography = $"{authorFirstName} {authorLastName} is a veteran technologist and systems architect with over a decade of domain expertise. " + faker.Lorem.Paragraph(2),
|
||||||
|
ThumbnailImageUrl = null
|
||||||
|
}, stoppingToken);
|
||||||
|
|
||||||
|
if (authorCreateResult.IsFailed)
|
||||||
|
{
|
||||||
|
logger.LogError("Failed to create author: {Error}", authorCreateResult.Errors[0].Message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 5: Create Author-Book link (product linkage)
|
||||||
|
var authorBookCreateResult = await booksService.CreateBookAsync(authorId: authorCreateResult.Value, productId: productCreateResult.Value, stoppingToken);
|
||||||
|
|
||||||
|
if (authorBookCreateResult.IsFailed)
|
||||||
|
{
|
||||||
|
logger.LogError("Failed to create author-book linkage: {Error}", authorBookCreateResult.Errors[0].Message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var enableAuthorBookResult = await booksService.UpdateBookStatusAsync(bookId: authorBookCreateResult.Value, isEnabled: true, stoppingToken);
|
||||||
|
|
||||||
|
if (enableAuthorBookResult.IsFailed)
|
||||||
|
{
|
||||||
|
logger.LogError("Failed to enable author-book link: {Error}", enableAuthorBookResult.Errors[0].Message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogInformation("Successfully seeded book product: {Title}", bookTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogInformation("Product Seeding completed successfully.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Extensions;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Seed;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Seed.Configuration;
|
||||||
|
|
||||||
|
var builder = Host.CreateApplicationBuilder(args);
|
||||||
|
|
||||||
|
builder.Configuration
|
||||||
|
.AddJsonFile("appsettings.json")
|
||||||
|
.AddUserSecrets(typeof(Program).Assembly);
|
||||||
|
|
||||||
|
builder.Services
|
||||||
|
.AddLogging()
|
||||||
|
.AddShopServices()
|
||||||
|
.AddHostedService<ProductsSeederService>()
|
||||||
|
.AddMidrandShopDatabase(builder.Configuration);
|
||||||
|
|
||||||
|
builder.Services.Configure<CdnSettings>(options => builder.Configuration.GetSection(nameof(CdnSettings)).Bind(options));
|
||||||
|
|
||||||
|
using var host = builder.Build();
|
||||||
|
|
||||||
|
await host.RunAsync();
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"CdnSettings": {
|
||||||
|
"BaseCdn": "https://bookshop.cdn.khongisa.co.za/design/",
|
||||||
|
"BookCovers": [
|
||||||
|
"144e0314-0bd8-4e2c-8814-2b34608c0600_1764780116467.webp",
|
||||||
|
"2bf1f9a2-7b25-4fcf-9aa7-08941ea21e6c_1764838499686.webp",
|
||||||
|
"3cd172b9-416e-4b3a-b613-7ff0a3aecc4c_1764780129459.webp",
|
||||||
|
"762947b6-63ac-436e-98fb-91dd94de1d82_1764780126141.webp",
|
||||||
|
"7b42f23f-666c-4dd9-82e1-9b928e1b4db7_1764780186376.webp",
|
||||||
|
"8e281eea-8910-473d-b650-43f539995d9e_1764780152316.webp",
|
||||||
|
"91d18e2c-6ee6-44b4-84a5-8692db5dbfe4_1764780114484.webp",
|
||||||
|
"94fdf403-4d10-4cb4-a537-fc914a1f5ba8_1764780198423.webp",
|
||||||
|
"9b881786-75e1-47f7-9bc9-27188d46d1ec_1764780122458.webp",
|
||||||
|
"c417236d-a628-45eb-82c2-ec1cb0326e4f_1765006317965.webp",
|
||||||
|
"clpqjsgi71jpr1i6392e449l2.webp",
|
||||||
|
"clps1mk9f0m1z1i32grvq17tg.webp",
|
||||||
|
"clq550xxy2dab1ywb6mdv4acv.webp",
|
||||||
|
"clq5535o52dj51yw557ml49c1.webp",
|
||||||
|
"clq55455w2dcm1yvd8d8258d8.webp",
|
||||||
|
"clq558fkh2djy1ywbghwcawnh.webp",
|
||||||
|
"clq55cvqh2dqi1yyratl123wq.webp",
|
||||||
|
"clrhnk94e115k1y00bg8h9ixb.webp",
|
||||||
|
"d44a3c04-f124-4f0b-8301-3841ae2fd439_1764780121224.webp",
|
||||||
|
"e6ba52f208914285bcdf1966cfb08f6f.jpg",
|
||||||
|
"fa9cbbe6-f947-4f83-8e98-61d2661f43e0_1764841636705.webp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Authors;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Authors.Models;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Tests.Common;
|
||||||
|
using LiteCharms.Features.Models;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Tests;
|
||||||
|
|
||||||
|
public class AuthorServiceFeatureTests(Fixture fixture, ITestOutputHelper output) : IClassFixture<Fixture>
|
||||||
|
{
|
||||||
|
private readonly AuthorService authorService = fixture.Services.GetRequiredService<AuthorService>();
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task CreateAuthorAsync_ShouldReturn_ResultWithAuthorId()
|
||||||
|
{
|
||||||
|
var request = new CreateAuthor
|
||||||
|
{
|
||||||
|
Name = "John",
|
||||||
|
LastName = "Doe",
|
||||||
|
Company = "Solo Publishers",
|
||||||
|
Email = "solo@publishers.co.za",
|
||||||
|
PublisherType = PublisherTypes.Independent,
|
||||||
|
ImageUrl = ""
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await authorService.CreateAuthorAsync(request, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.True(result.Value > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task UpdateAuthorAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
var request = new UpdateAuthor
|
||||||
|
{
|
||||||
|
Name = "Jane",
|
||||||
|
LastName = "Doe",
|
||||||
|
Company = "Solo Publishers",
|
||||||
|
Email = "solo@publishers.co.za",
|
||||||
|
PublisherType = PublisherTypes.Independent,
|
||||||
|
ImageUrl = ""
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await authorService.UpdateAuthorAsync(1, request, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetAuthors_ShouldReturn_ResultWithAuthorList()
|
||||||
|
{
|
||||||
|
var range = new DateRange
|
||||||
|
{
|
||||||
|
From = DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-7)),
|
||||||
|
To = DateOnly.FromDateTime(DateTime.UtcNow),
|
||||||
|
MaxRecords = 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await authorService.GetAuthorsAsync(range, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotEmpty(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetAuthorAsync_ShouldReturn_ResultWithAuthor()
|
||||||
|
{
|
||||||
|
var result = await authorService.GetAuthorAsync(1, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotNull(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task UpdateAuthorStatusAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
var result = await authorService.UpdateAuthorStatusAsync(1, true, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.AuthorBooks;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Tests.Common;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Tests;
|
||||||
|
|
||||||
|
public class BooksServiceFeatureTests(Fixture fixture) : IClassFixture<Fixture>
|
||||||
|
{
|
||||||
|
private readonly BooksService bookService = fixture.Services.GetRequiredService<BooksService>();
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task CreateBookAsync_ShouldReturn_ResultWithBookId()
|
||||||
|
{
|
||||||
|
var result = await bookService.CreateBookAsync(1, 2, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetBookAsync_ShouldReturn_ResultWithBook()
|
||||||
|
{
|
||||||
|
var result = await bookService.GetBookAsync(1, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotNull(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetBooksByAuthorAsync_ShouldReturn_ResultWithAuthorBooks()
|
||||||
|
{
|
||||||
|
var result = await bookService.GetBooksByAuthorAsync(1, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotEmpty(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetPublishedBooksAsync_ShouldReturn_ResultWithBublishedBooks()
|
||||||
|
{
|
||||||
|
var result = await bookService.GetPublishedBooksAsync(0, 1000, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotEmpty(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task UpdateBookStatusAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
var result = await bookService.UpdateBookStatusAsync(1, true, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-2
@@ -1,7 +1,8 @@
|
|||||||
using LiteCharms.Features.Extensions;
|
using LiteCharms.Features.Extensions;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Abstractions;
|
||||||
using LiteCharms.Features.MidrandBooks.Extensions;
|
using LiteCharms.Features.MidrandBooks.Extensions;
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Tests;
|
namespace LiteCharms.Features.MidrandBooks.Tests.Common;
|
||||||
|
|
||||||
public class Fixture : IDisposable
|
public class Fixture : IDisposable
|
||||||
{
|
{
|
||||||
@@ -11,6 +12,10 @@ public class Fixture : IDisposable
|
|||||||
|
|
||||||
public IMediator Mediator { get; set; }
|
public IMediator Mediator { get; set; }
|
||||||
|
|
||||||
|
private readonly CancellationTokenSource cancellationTokenSource = new();
|
||||||
|
|
||||||
|
public CancellationToken CancellationToken => cancellationTokenSource.Token;
|
||||||
|
|
||||||
public Fixture()
|
public Fixture()
|
||||||
{
|
{
|
||||||
Configuration = new ConfigurationBuilder()
|
Configuration = new ConfigurationBuilder()
|
||||||
@@ -23,12 +28,12 @@ public class Fixture : IDisposable
|
|||||||
Services = new ServiceCollection()
|
Services = new ServiceCollection()
|
||||||
.AddMediator()
|
.AddMediator()
|
||||||
.AddLogging()
|
.AddLogging()
|
||||||
//.AddMidrandShopServices()
|
|
||||||
.AddEmailServiceBus()
|
.AddEmailServiceBus()
|
||||||
.AddGarageS3(Configuration)
|
.AddGarageS3(Configuration)
|
||||||
.AddMidrandShopDatabase(Configuration)
|
.AddMidrandShopDatabase(Configuration)
|
||||||
.AddEmailServices(Configuration)
|
.AddEmailServices(Configuration)
|
||||||
.AddSingleton(Configuration)
|
.AddSingleton(Configuration)
|
||||||
|
.AddShopServices()
|
||||||
.BuildServiceProvider();
|
.BuildServiceProvider();
|
||||||
|
|
||||||
Mediator = Services.GetRequiredService<IMediator>();
|
Mediator = Services.GetRequiredService<IMediator>();
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Tests.Common;
|
||||||
|
|
||||||
|
public class IntegrationFactAttribute : FactAttribute
|
||||||
|
{
|
||||||
|
public IntegrationFactAttribute()
|
||||||
|
{
|
||||||
|
if(!Debugger.IsAttached)
|
||||||
|
Skip = "This test requires the debugger to be attached.";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Customers;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Customers.Models;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Tests.Common;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Tests;
|
||||||
|
|
||||||
|
public class CustomerServiceFeatureTests(Fixture fixture) : IClassFixture<Fixture>
|
||||||
|
{
|
||||||
|
private readonly CustomerService customerService = fixture.Services.GetRequiredService<CustomerService>();
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task CreateCustomerAsync_ShouldReturn_ResultWithCustomerId()
|
||||||
|
{
|
||||||
|
var request = new CreateCustomer
|
||||||
|
{
|
||||||
|
Company = "Book Lovers",
|
||||||
|
Email = "hank@booklovers.com",
|
||||||
|
Phone = "555 1245 8577",
|
||||||
|
Website = "https://www.booklovers.com"
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await customerService.CreateCustomerAsync(request, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.True(result.Value > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task CreateCustomerContactAsync_ShouldReturn_ResultWithCustomerContactId()
|
||||||
|
{
|
||||||
|
var request = new CreateCustomerContact
|
||||||
|
{
|
||||||
|
Name = "Sipho",
|
||||||
|
LastName = "Madlanga",
|
||||||
|
Phone = "0710857365",
|
||||||
|
Email = "sipho@madlanga.africa",
|
||||||
|
Type = ContactTypes.Business
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await customerService.CreateCustomerContactAsync(1, request, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.True(result.Value > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task CreateCustomerAddressAsync_ShouldReturn_ResultWithCustomerAddressId()
|
||||||
|
{
|
||||||
|
var request = new CreateCustomerAddress
|
||||||
|
{
|
||||||
|
Name = "Business",
|
||||||
|
BuildingType = AddressBuildingTypes.MixedUse,
|
||||||
|
Type = AddressType.Shipping,
|
||||||
|
Street = "123 Building 4, XYZ Suburb, Some Region",
|
||||||
|
City = "Johannesburg",
|
||||||
|
State = "Gauteng",
|
||||||
|
Country = "South Africa",
|
||||||
|
IsPrimary = true,
|
||||||
|
Enabled = true,
|
||||||
|
PostalCode = "12345"
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await customerService.CreateCustomerAddressAsync(1, request, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.True(result.Value > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task UpdateCustomerAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
var request = new UpdateCustomer
|
||||||
|
{
|
||||||
|
Company = "Book Lovers",
|
||||||
|
Email = "hank@booklovers.com",
|
||||||
|
Phone = "555 1245 8578",
|
||||||
|
Website = "https://www.booklovers.com"
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await customerService.UpdateCustomerAsync(1, request, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task UpdateCustomerContactAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
var request = new UpdateCustomerContact
|
||||||
|
{
|
||||||
|
Name = "Sipho",
|
||||||
|
LastName = "Madlanga",
|
||||||
|
Phone = "0710857366",
|
||||||
|
Email = "sipho@madlanga.africa",
|
||||||
|
Type = ContactTypes.Business
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await customerService.UpdateCustomerContactAsync(1, request, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task UpdateCustomerAddressAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
var request = new UpdateCustomerAddress
|
||||||
|
{
|
||||||
|
Name = "Business",
|
||||||
|
BuildingType = AddressBuildingTypes.MixedUse,
|
||||||
|
Type = AddressType.Shipping,
|
||||||
|
Street = "123 Building 4, XYZ Suburb, Some Region",
|
||||||
|
City = "Johannesburg",
|
||||||
|
State = "Gauteng",
|
||||||
|
Country = "South Africa",
|
||||||
|
IsPrimary = true,
|
||||||
|
Enabled = true,
|
||||||
|
PostalCode = "12346"
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await customerService.UpdateCustomerAddressAsync(1, request, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task UpdateCustomerStatusAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
var result = await customerService.UpdateCustomerStatusAsync(1, true, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task UpdateCustomerContactStatusAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
var result = await customerService.UpdateCustomerContactStatusAsync(1, true, true, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task UpdateCustomerAddressStatusAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
var result = await customerService.UpdateCustomerAddressStatusAsync(1, true, true, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetCustomersAsync_ShouldReturn_ResultWithCustomerList()
|
||||||
|
{
|
||||||
|
var result = await customerService.GetCustomersAsync(fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotEmpty(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetCustomerContactsAsync_ShouldReturn_ResultWithCustomerContactList()
|
||||||
|
{
|
||||||
|
var result = await customerService.GetCustomerContactsAsync(1, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotEmpty(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetCustomerAddressesAsync_ShouldReturn_ResultWithCustomerAddressList()
|
||||||
|
{
|
||||||
|
var result = await customerService.GetCustomerAddressesAsync(1, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotEmpty(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetCustomerAsync_ShouldReturn_ResultWithCustomer()
|
||||||
|
{
|
||||||
|
var result = await customerService.GetCustomerAsync(1, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotNull(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetCustomerContactAsync_ShouldReturn_ResultWithCustomerContact()
|
||||||
|
{
|
||||||
|
var result = await customerService.GetCustomerContactsAsync(1, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotNull(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetCustomerAddressAsync_ShouldReturn_ResultWithCustomerAddress()
|
||||||
|
{
|
||||||
|
var result = await customerService.GetCustomerAddressAsync(1, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotNull(result.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,6 +39,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Using Include="System.Text.Json" />
|
||||||
|
<Using Include="System.Diagnostics" />
|
||||||
<Using Include="Xunit" />
|
<Using Include="Xunit" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Orders;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Tests.Common;
|
||||||
|
using LiteCharms.Features.Models;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Tests;
|
||||||
|
|
||||||
|
public class OrderServiceFeatureTests(Fixture fixture) : IClassFixture<Fixture>
|
||||||
|
{
|
||||||
|
private readonly OrderService orderService = fixture.Services.GetRequiredService<OrderService>();
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task CreateOrderAsync_ShouldReturn_ResultWithOrderId()
|
||||||
|
{
|
||||||
|
var request = new CreateOrder(250, "At the intercomm, dial 1 then option 2");
|
||||||
|
|
||||||
|
var result = await orderService.CreateOrderAsync(1, request, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.True(result.Value > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task AddItemToOrderAsync_ShouldReturn_ResultWithOrderItemId()
|
||||||
|
{
|
||||||
|
var request = new CreateOrderItem(1, 1, 2);
|
||||||
|
|
||||||
|
var result = await orderService.AddItemToOrderAsync(1, request, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.True(result.Value > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task AddItemsToOrderAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
var requests = new List<CreateOrderItem>
|
||||||
|
{
|
||||||
|
new(1, 1, 1),
|
||||||
|
new(1, 1, 3)
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await orderService.AddItemsToOrderAsync(1, [.. requests], fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task RemoveItemFromOrderAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
var result = await orderService.RemoveItemFromOrderAsync(1, 5, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task ClearOrderItemsAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
var result = await orderService.ClearOrderItemsAsync(1, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task CancelOrderAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
var result = await orderService.CancelOrderAsync(1, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetOrderAsync_ShouldReturn_ResultWithOrder()
|
||||||
|
{
|
||||||
|
var result = await orderService.GetOrderAsync(1, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotNull(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetOrdersByCustomerAsync_ShouldReturn_ResultWithOrderList()
|
||||||
|
{
|
||||||
|
var result = await orderService.GetOrdersByCustomerAsync(1, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotEmpty(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetOrdersAsync_ShouldReturn_ResultWithOrderList()
|
||||||
|
{
|
||||||
|
var range = new DateRange
|
||||||
|
{
|
||||||
|
From = DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-7)),
|
||||||
|
To = DateOnly.FromDateTime(DateTime.UtcNow),
|
||||||
|
MaxRecords = 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await orderService.GetOrdersAsync(range, 0, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotEmpty(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task UpdateOrderStatusAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
var result = await orderService.UpdateOrderStatusAsync(1, OrderStatus.Pending, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task AddShippingToOrderAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
var request = new CreateShipping(1, 2);
|
||||||
|
|
||||||
|
var result = await orderService.AddShippingToOrderAsync(1, request, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.True(result.Value > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task UpdateShippingStatusAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
var result = await orderService.UpdateShippingStatusAsync(1, ShippingStatuses.Shipped, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetShippingByOrderIdAsync_ShouldReturn_ResultWithShipping()
|
||||||
|
{
|
||||||
|
var result = await orderService.GetShippingByOrderIdAsync(1, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotNull(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task RemoveShippingFromOrderAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
var result = await orderService.RemoveShippingFromOrderAsync(1, 1, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task UpdateShippingTrackingNumberAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
var result = await orderService.UpdateShippingTrackingNumberAsync(1, 2, "NA0009969397");
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task CreateShippingProviderAsync_ShouldReturn_ResultWithShippingProviderId()
|
||||||
|
{
|
||||||
|
var request = new CreateShippingProvider(ShippingProviderTypes.FastWay, "FastWay Couriers", 50, "https://www.fastway.co.za/our-services/track-your-parcel");
|
||||||
|
|
||||||
|
var result = await orderService.CreateShippingProviderAsync(request, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.True(result.Value > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetShippingProvidersAsync_ShouldReturn_ResultWithShippingProviderList()
|
||||||
|
{
|
||||||
|
var result = await orderService.GetShippingProvidersAsync(true, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotEmpty(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetShippingProviderAsync_ShouldReturn_ResultWithShippingProvider()
|
||||||
|
{
|
||||||
|
var result = await orderService.GetShippingProviderAsync(2, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotNull(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task UpdateShippingProviderAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
var request = new UpdateShippingProvider(2,true, "FastWay Couriers", 50, "https://www.fastway.co.za/our-services/track-your-parcel");
|
||||||
|
|
||||||
|
var result = await orderService.UpdateShippingProviderAsync(request, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Pages;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Tests.Common;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Tests;
|
||||||
|
|
||||||
|
public class PageServiceFeatureTests(Fixture fixture) : IClassFixture<Fixture>
|
||||||
|
{
|
||||||
|
private readonly PageService pageService = fixture.Services.GetRequiredService<PageService>();
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task CreatePageAsync_ShouldReturn_ResultWithPageId()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetPagesAsync_ByBookId_ShouldReturn_ResultWithPageList()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetPageAsync_ShouldReturn_ResultWithPage()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetPageByNumberAsync_ById_And_BookPageNumber_ShouldReturn_ResultWithPage()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task UpdatePageAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task DeletePageAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task DeleteByPageTypeAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task DeleteAllAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task UpdatePageStatusAsync_ShouldReturn_ResultWithSuccess()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Products;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Products.Models;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Tests.Common;
|
||||||
|
using LiteCharms.Features.Models;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Tests;
|
||||||
|
|
||||||
|
public class ProductServiceFeatureTests(Fixture fixture, ITestOutputHelper output) : IClassFixture<Fixture>
|
||||||
|
{
|
||||||
|
private readonly ProductService productService = fixture.Services.GetRequiredService<ProductService>();
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetProductPriceAsync_ShouldReturn_RetultOneProductPrice()
|
||||||
|
{
|
||||||
|
var result = await productService.GetProductPriceAsync(2, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotNull(result.Value);
|
||||||
|
|
||||||
|
output.WriteLine(JsonSerializer.Serialize(result.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetProductPricesAsync_ShouldReturn_RetultProductPriceList()
|
||||||
|
{
|
||||||
|
var result = await productService.GetProductPricesAsync(2, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotEmpty(result.Value);
|
||||||
|
|
||||||
|
output.WriteLine(JsonSerializer.Serialize(result.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task SearchProductsAsync_ShouldReturn_RetultMatchingProducts()
|
||||||
|
{
|
||||||
|
var filter = new ProductFilter
|
||||||
|
{
|
||||||
|
Name = "system",
|
||||||
|
Manufacturer = "techwave",
|
||||||
|
SerialNumber = "2024",
|
||||||
|
MinPrice = 10,
|
||||||
|
MaxPrice = 30
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await productService.SearchProductsAsync(filter, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotEmpty(result.Value);
|
||||||
|
|
||||||
|
output.WriteLine(JsonSerializer.Serialize(result.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetProductAsync_ShouldReturn_RetultOneProduct()
|
||||||
|
{
|
||||||
|
var result = await productService.GetProductAsync(2, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotNull(result.Value);
|
||||||
|
|
||||||
|
output.WriteLine(JsonSerializer.Serialize(result.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task GetProductsAsync_ShouldReturn_RetultProducts()
|
||||||
|
{
|
||||||
|
var range = new DateRange
|
||||||
|
{
|
||||||
|
From = DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-7)),
|
||||||
|
To = DateOnly.FromDateTime(DateTime.UtcNow),
|
||||||
|
MaxRecords = 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await productService.GetProductsAsync(0, range, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotEmpty(result.Value);
|
||||||
|
|
||||||
|
output.WriteLine(JsonSerializer.Serialize(result.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task UpdateProductStatusAsync_ShouldReturn_ResultTrue()
|
||||||
|
{
|
||||||
|
var result = await productService.UpdateProductStatusAsync(2, true, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task UpdateProductPriceStatusAsync_ShouldReturn_ResultTrue()
|
||||||
|
{
|
||||||
|
var result = await productService.UpdateProductPriceStatusAsync(2, true, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task CreateProductPriceAsync_Should_Return_NewProductPriceId()
|
||||||
|
{
|
||||||
|
var request = new CreateProductPrice
|
||||||
|
{
|
||||||
|
Amount = 29.99m,
|
||||||
|
Discount = 0.00m
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await productService.CreateProductPriceAsync(2, request, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess, "Product price creation should be successful.");
|
||||||
|
Assert.True(result.Value > 0, "New ProductPriceId should be greater than 0.");
|
||||||
|
|
||||||
|
output.WriteLine($"Created ProductPriceId: {result.Value}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntegrationFact]
|
||||||
|
public async Task CreateProductAsync_Result_Returns_ProductId()
|
||||||
|
{
|
||||||
|
var request = new CreateProduct
|
||||||
|
{
|
||||||
|
Name = "Systems Rewired",
|
||||||
|
Description = "[Design], <Code>, AND /CHAORS/ IN ***SYNC***",
|
||||||
|
Summary = "A comprehensive guide to systems thinking and design.",
|
||||||
|
ImageUrl = "https://bookshop.cdn.khongisa.co.za/design/2bf1f9a2-7b25-4fcf-9aa7-08941ea21e6c_1764838499686.webp",
|
||||||
|
Type = ProductTypes.Book,
|
||||||
|
Categories = ["Systems Thinking", "Design", "Programming"],
|
||||||
|
Metadata = new ProductMetadata
|
||||||
|
{
|
||||||
|
CopyrightInfo = "© 2024 John Doe. All rights reserved.",
|
||||||
|
ManufactureDate = "2024-06-01",
|
||||||
|
Manufacturer = "TechWave Publishing",
|
||||||
|
SerialNumber = "SR-2024-0001"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await productService.CreateProductAsync(request, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess, "Product creation should be successful.");
|
||||||
|
Assert.True(result.Value > 0, "ProductId should be greater than 0.");
|
||||||
|
|
||||||
|
output.WriteLine($"Created ProductId: {result.Value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,12 +5,6 @@
|
|||||||
"BucketName": "bookshop",
|
"BucketName": "bookshop",
|
||||||
"CdnBaseUrl": "https://bookshop.cdn.khongisa.co.za"
|
"CdnBaseUrl": "https://bookshop.cdn.khongisa.co.za"
|
||||||
},
|
},
|
||||||
"BookshopQuotesS3Settings": {
|
|
||||||
"ServiceUrl": "http://192.168.1.177:30900",
|
|
||||||
"Region": "garage",
|
|
||||||
"BucketName": "bookshop.quotes",
|
|
||||||
"CdnBaseUrl": "https://bookshop.quotes.cdn.khongisa.co.za"
|
|
||||||
},
|
|
||||||
"Email": {
|
"Email": {
|
||||||
"Credentials": {
|
"Credentials": {
|
||||||
"Username": "shop@litecharms.co.za"
|
"Username": "shop@litecharms.co.za"
|
||||||
|
|||||||
@@ -5,25 +5,23 @@ using LiteCharms.Features.MidrandBooks.Postgres;
|
|||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.AuthorBooks;
|
namespace LiteCharms.Features.MidrandBooks.AuthorBooks;
|
||||||
|
|
||||||
public class BooksService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
public sealed class BooksService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
||||||
{
|
{
|
||||||
public async ValueTask<Result> UpdateBookStatusAsync(long bookId, bool isEnabled, CancellationToken cancellationToken)
|
public async ValueTask<Result> UpdateBookStatusAsync(long bookId, bool isEnabled, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var book = await context.Books.FirstOrDefaultAsync(b => b.Id == bookId, cancellationToken);
|
var rowsUpdated = await context.Books
|
||||||
|
.Where(b => b.Id == bookId)
|
||||||
|
.ExecuteUpdateAsync(setters => setters
|
||||||
|
.SetProperty(b => b.Enabled, isEnabled)
|
||||||
|
.SetProperty(b => b.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||||
|
|
||||||
if (book is null)
|
return rowsUpdated > 0
|
||||||
return Result.Fail(new Error($"Book with ID {bookId} not found"));
|
|
||||||
|
|
||||||
book.UpdatedAt = DateTime.UtcNow;
|
|
||||||
book.Enabled = isEnabled;
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail(new Error($"Failed to change status of book with ID {bookId}"));
|
: Result.Fail(new Error($"Book with ID {bookId} not found"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -45,8 +43,9 @@ public class BooksService(IDbContextFactory<MidrandBooksDbContext> contextFactor
|
|||||||
|
|
||||||
var book = context.Books.Add(new Entities.AuthorBook
|
var book = context.Books.Add(new Entities.AuthorBook
|
||||||
{
|
{
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
AuthorId = authorId,
|
AuthorId = authorId,
|
||||||
ProductId = productId,
|
ProductId = productId
|
||||||
});
|
});
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
@@ -68,7 +67,8 @@ public class BooksService(IDbContextFactory<MidrandBooksDbContext> contextFactor
|
|||||||
var book = await context.Books
|
var book = await context.Books
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(b => b.Author)
|
.Include(b => b.Author)
|
||||||
.Include(b => b.Product!.Price)
|
.Include(b => b.Product)
|
||||||
|
.ThenInclude(b => b!.Prices)
|
||||||
.Include(b => b.Pages)
|
.Include(b => b.Pages)
|
||||||
.FirstOrDefaultAsync(b => b.Id == bookId, cancellationToken);
|
.FirstOrDefaultAsync(b => b.Id == bookId, cancellationToken);
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ public class BooksService(IDbContextFactory<MidrandBooksDbContext> contextFactor
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
if(!await context.Authors.AnyAsync(a => a.Id == authorId, cancellationToken))
|
if(!await context.Authors.AnyAsync(a => a.Id == authorId, cancellationToken))
|
||||||
return Result.Fail<AuthorBook[]>(new Error($"Author with ID {authorId} not found"));
|
return Result.Fail<AuthorBook[]>(new Error($"Author with ID {authorId} not found"));
|
||||||
@@ -94,7 +94,8 @@ public class BooksService(IDbContextFactory<MidrandBooksDbContext> contextFactor
|
|||||||
var books = await context.Books
|
var books = await context.Books
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(b => b.Author)
|
.Include(b => b.Author)
|
||||||
.Include(b => b.Product!.Price)
|
.Include(b => b.Product)
|
||||||
|
.ThenInclude(b => b.Prices)
|
||||||
.OrderByDescending(b => b.CreatedAt)
|
.OrderByDescending(b => b.CreatedAt)
|
||||||
.Where(b => b.AuthorId == authorId)
|
.Where(b => b.AuthorId == authorId)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
@@ -118,9 +119,10 @@ public class BooksService(IDbContextFactory<MidrandBooksDbContext> contextFactor
|
|||||||
var books = await context.Books
|
var books = await context.Books
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(b => b.Author)
|
.Include(b => b.Author)
|
||||||
.Include(b => b.Product!.Price)
|
.Include(b => b.Product)
|
||||||
|
.ThenInclude(b => b!.Prices)
|
||||||
.Include(b => b.Pages)
|
.Include(b => b.Pages)
|
||||||
.Where(b => b.Enabled && b.Product!.Enabled && b.Author.Enabled)
|
.Where(b => b.Enabled && b.Product!.Enabled && b.Author!.Enabled)
|
||||||
.OrderByDescending(b => b.Ranking)
|
.OrderByDescending(b => b.Ranking)
|
||||||
.ThenByDescending(b => b.Ranking)
|
.ThenByDescending(b => b.Ranking)
|
||||||
.ThenByDescending(b => b.CreatedAt)
|
.ThenByDescending(b => b.CreatedAt)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace LiteCharms.Features.MidrandBooks.AuthorBooks.Entities;
|
|||||||
|
|
||||||
public class AuthorBook : Models.AuthorBook
|
public class AuthorBook : Models.AuthorBook
|
||||||
{
|
{
|
||||||
public virtual Author Author { get; set; } = new();
|
public virtual Author? Author { get; set; }
|
||||||
|
|
||||||
public new virtual Product? Product { get; set; }
|
public new virtual Product? Product { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.AuthorBooks.Entities;
|
namespace LiteCharms.Features.MidrandBooks.AuthorBooks.Entities;
|
||||||
|
|
||||||
public class AuthorBookConfiguration : IEntityTypeConfiguration<AuthorBook>
|
public sealed class AuthorBookConfiguration : IEntityTypeConfiguration<AuthorBook>
|
||||||
{
|
{
|
||||||
public void Configure(EntityTypeBuilder<AuthorBook> builder)
|
public void Configure(EntityTypeBuilder<AuthorBook> builder)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.Abstractions;
|
using LiteCharms.Features.MidrandBooks.Abstractions;
|
||||||
using LiteCharms.Features.MidrandBooks.AuthorBooks.Models;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Authors.Models;
|
using LiteCharms.Features.MidrandBooks.Authors.Models;
|
||||||
using LiteCharms.Features.MidrandBooks.Extensions;
|
using LiteCharms.Features.MidrandBooks.Extensions;
|
||||||
using LiteCharms.Features.MidrandBooks.Postgres;
|
using LiteCharms.Features.MidrandBooks.Postgres;
|
||||||
@@ -7,55 +6,23 @@ using LiteCharms.Features.Models;
|
|||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Authors;
|
namespace LiteCharms.Features.MidrandBooks.Authors;
|
||||||
|
|
||||||
public class AuthorService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
public sealed class AuthorService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
||||||
{
|
{
|
||||||
public async ValueTask<Result<AuthorBook[]>> GetAuthorBooksAsync(long authorId, CancellationToken cancellationToken)
|
public async ValueTask<Result> UpdateAuthorStatusAsync(long authorId, bool isEnabled, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var author = await context.Authors.FirstOrDefaultAsync(a => a.Id == authorId, cancellationToken);
|
var rowsUpdated = await context.Authors
|
||||||
|
.Where(a => a.Id == authorId)
|
||||||
|
.ExecuteUpdateAsync(setters => setters
|
||||||
|
.SetProperty(a => a.Enabled, isEnabled)
|
||||||
|
.SetProperty(a => a.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||||
|
|
||||||
if (author is null)
|
return rowsUpdated > 0
|
||||||
return Result.Fail<AuthorBook[]>(new Error($"Author with ID {authorId} not found"));
|
|
||||||
|
|
||||||
var books = await context.Books
|
|
||||||
.AsNoTracking()
|
|
||||||
.Include(b => b.Author)
|
|
||||||
.Include(b => b.Product!.Price)
|
|
||||||
.OrderByDescending(b => b.CreatedAt)
|
|
||||||
.Where(p => p.AuthorId == authorId)
|
|
||||||
.AsSplitQuery()
|
|
||||||
.ToArrayAsync(cancellationToken);
|
|
||||||
|
|
||||||
return books?.Length > 0
|
|
||||||
? Result.Ok(books.Select(b => b.ToModel()).ToArray())
|
|
||||||
: Result.Fail<AuthorBook[]>(new Error($"No books found for author with ID {authorId}"));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<AuthorBook[]>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result> UpdateAuthorStatusAsync(long authorId, bool isEnabled, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var author = await context.Authors.FirstOrDefaultAsync(a => a.Id == authorId, cancellationToken);
|
|
||||||
|
|
||||||
if (author is null)
|
|
||||||
return Result.Fail(new Error($"Author with ID {authorId} not found"));
|
|
||||||
|
|
||||||
author.UpdatedAt = DateTime.UtcNow;
|
|
||||||
author.Enabled = isEnabled;
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail(new Error($"Failed to change status of author with ID {authorId}"));
|
: Result.Fail(new Error($"Author with ID {authorId} not found"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -81,12 +48,12 @@ public class AuthorService(IDbContextFactory<MidrandBooksDbContext> contextFacto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<Result<Author[]>> GetAuthors(DateRange range, CancellationToken cancellationToken)
|
public async ValueTask<Result<Author[]>> GetAuthorsAsync(DateRange range, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var fromDate = range.From.ToDateTime(TimeOnly.MinValue);
|
var fromDate = range.From.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc);
|
||||||
var toDate = range.To.ToDateTime(TimeOnly.MaxValue);
|
var toDate = range.To.ToDateTime(TimeOnly.MaxValue, DateTimeKind.Utc);
|
||||||
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
@@ -107,18 +74,12 @@ public class AuthorService(IDbContextFactory<MidrandBooksDbContext> contextFacto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<Result> UpdateAuthorAsync(long authorId, UpdateAuthor request, CancellationToken cancellationToken)
|
public async ValueTask<Result> UpdateAuthorAsync(long authorId, UpdateAuthor request, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
if (await context.Authors.AnyAsync(a => a.Name == request.Name && a.LastName == request.LastName, cancellationToken))
|
|
||||||
return Result.Fail(new Error($"An author with the name {request.Name} {request.LastName} already exists"));
|
|
||||||
|
|
||||||
if (await context.Authors.AnyAsync(a => a.Email == request.Email, cancellationToken))
|
|
||||||
return Result.Fail(new Error($"An author with the email {request.Email} already exists"));
|
|
||||||
|
|
||||||
var author = await context.Authors.FirstOrDefaultAsync(a => a.Id == authorId, cancellationToken);
|
var author = await context.Authors.FirstOrDefaultAsync(a => a.Id == authorId, cancellationToken);
|
||||||
|
|
||||||
if (author is null)
|
if (author is null)
|
||||||
@@ -151,12 +112,12 @@ public class AuthorService(IDbContextFactory<MidrandBooksDbContext> contextFacto
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
if(await context.Authors.AnyAsync(a => a.Name == request.Name && a.LastName == request.LastName, cancellationToken))
|
if (await context.Authors.AnyAsync(a => a.Name == request.Name && a.LastName == request.LastName, cancellationToken))
|
||||||
return Result.Fail<long>(new Error($"An author with the name {request.Name} {request.LastName} already exists"));
|
return Result.Fail<long>(new Error($"An author with the name {request.Name} {request.LastName} already exists"));
|
||||||
|
|
||||||
if(await context.Authors.AnyAsync(a => a.Email == request.Email, cancellationToken))
|
if (await context.Authors.AnyAsync(a => a.Email == request.Email, cancellationToken))
|
||||||
return Result.Fail<long>(new Error($"An author with the email {request.Email} already exists"));
|
return Result.Fail<long>(new Error($"An author with the email {request.Email} already exists"));
|
||||||
|
|
||||||
var newAuthor = context.Authors.Add(new Entities.Author
|
var newAuthor = context.Authors.Add(new Entities.Author
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Authors.Entities;
|
namespace LiteCharms.Features.MidrandBooks.Authors.Entities;
|
||||||
|
|
||||||
[EntityTypeConfiguration<AuthorConfiguration, Author>]
|
[EntityTypeConfiguration<AuthorConfiguration, Author>]
|
||||||
public class Author : Models.Author
|
public sealed class Author : Models.Author
|
||||||
{
|
{
|
||||||
public ICollection<AuthorBook> Books { get; set; } = [];
|
public ICollection<AuthorBook> Books { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Authors.Entities;
|
namespace LiteCharms.Features.MidrandBooks.Authors.Entities;
|
||||||
|
|
||||||
public class AuthorConfiguration : IEntityTypeConfiguration<Author>
|
public sealed class AuthorConfiguration : IEntityTypeConfiguration<Author>
|
||||||
{
|
{
|
||||||
public void Configure(EntityTypeBuilder<Author> builder)
|
public void Configure(EntityTypeBuilder<Author> builder)
|
||||||
{
|
{
|
||||||
@@ -18,7 +18,8 @@ public class AuthorConfiguration : IEntityTypeConfiguration<Author>
|
|||||||
builder.Property(f => f.Website).IsRequired(false).HasMaxLength(1024);
|
builder.Property(f => f.Website).IsRequired(false).HasMaxLength(1024);
|
||||||
builder.Property(f => f.ImageUrl).IsRequired().HasMaxLength(2048);
|
builder.Property(f => f.ImageUrl).IsRequired().HasMaxLength(2048);
|
||||||
builder.Property(f => f.ThumbnailImageUrl).IsRequired(false).HasMaxLength(2048);
|
builder.Property(f => f.ThumbnailImageUrl).IsRequired(false).HasMaxLength(2048);
|
||||||
builder.Property(f => f.SocialMedia).IsRequired(false).HasColumnType("jsonb");
|
|
||||||
builder.Property(f => f.Enabled).HasDefaultValue(true);
|
builder.Property(f => f.Enabled).HasDefaultValue(true);
|
||||||
|
|
||||||
|
builder.OwnsMany(f => f.SocialMedia, b => { b.ToJson(); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public class Author
|
|||||||
|
|
||||||
public string? ThumbnailImageUrl { get; set; }
|
public string? ThumbnailImageUrl { get; set; }
|
||||||
|
|
||||||
public SocialMedia[]? SocialMedia { get; set; }
|
public ICollection<SocialMedia>? SocialMedia { get; set; }
|
||||||
|
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Authors.Models;
|
namespace LiteCharms.Features.MidrandBooks.Authors.Models;
|
||||||
|
|
||||||
public record UpdateAuthor : CreateAuthor;
|
public sealed record UpdateAuthor : CreateAuthor;
|
||||||
|
|
||||||
public record CreateAuthor
|
public record CreateAuthor
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using LiteCharms.Features.MidrandBooks.Postgres;
|
|||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Customers;
|
namespace LiteCharms.Features.MidrandBooks.Customers;
|
||||||
|
|
||||||
public class CustomerService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
public sealed class CustomerService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
||||||
{
|
{
|
||||||
public async ValueTask<Result<long>> CreateCustomerAsync(CreateCustomer request, CancellationToken cancellationToken = default)
|
public async ValueTask<Result<long>> CreateCustomerAsync(CreateCustomer request, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
@@ -13,7 +13,7 @@ public class CustomerService(IDbContextFactory<MidrandBooksDbContext> contextFac
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
if (await context.Customers.AnyAsync(c => c.Email!.Equals(request.Email, StringComparison.OrdinalIgnoreCase), cancellationToken))
|
if (await context.Customers.AnyAsync(c => EF.Functions.ILike(c.Email!, $"%{request.Email}%"), cancellationToken))
|
||||||
return Result.Fail<long>(new Error($"Customer with email '{request.Email}' already exists."));
|
return Result.Fail<long>(new Error($"Customer with email '{request.Email}' already exists."));
|
||||||
|
|
||||||
var customer = context.Customers.Add(new Entities.Customer
|
var customer = context.Customers.Add(new Entities.Customer
|
||||||
@@ -46,7 +46,7 @@ public class CustomerService(IDbContextFactory<MidrandBooksDbContext> contextFac
|
|||||||
if (!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken))
|
if (!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken))
|
||||||
return Result.Fail<long>(new Error($"Customer with ID '{customerId}' does not exist."));
|
return Result.Fail<long>(new Error($"Customer with ID '{customerId}' does not exist."));
|
||||||
|
|
||||||
if (await context.Contacts.AnyAsync(cc => cc.CustomerId == customerId && cc.Email!.Equals(request.Email, StringComparison.OrdinalIgnoreCase), cancellationToken))
|
if (await context.Contacts.AnyAsync(cc => cc.CustomerId == customerId && EF.Functions.ILike(cc.Email!, $"%{request.Email}%"), cancellationToken))
|
||||||
return Result.Fail<long>(new Error($"Contact with email '{request.Email}' already exists for this customer."));
|
return Result.Fail<long>(new Error($"Contact with email '{request.Email}' already exists for this customer."));
|
||||||
|
|
||||||
var contact = context.Contacts.Add(new Entities.Contact
|
var contact = context.Contacts.Add(new Entities.Contact
|
||||||
@@ -139,21 +139,19 @@ public class CustomerService(IDbContextFactory<MidrandBooksDbContext> contextFac
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var contact = await context.Contacts.FirstOrDefaultAsync(cc => cc.Id == contactId, cancellationToken);
|
var rowsUpdated = await context.Contacts
|
||||||
|
.Where(cc => cc.Id == contactId)
|
||||||
|
.ExecuteUpdateAsync(setters => setters
|
||||||
|
.SetProperty(cc => cc.Name, request.Name)
|
||||||
|
.SetProperty(cc => cc.LastName, request.LastName)
|
||||||
|
.SetProperty(cc => cc.Email, request.Email)
|
||||||
|
.SetProperty(cc => cc.Phone, request.Phone)
|
||||||
|
.SetProperty(cc => cc.Type, request.Type)
|
||||||
|
.SetProperty(cc => cc.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||||
|
|
||||||
if (contact is null)
|
return rowsUpdated > 0
|
||||||
return Result.Fail(new Error($"Contact with ID '{contactId}' does not exist."));
|
|
||||||
|
|
||||||
contact.UpdatedAt = DateTime.UtcNow;
|
|
||||||
contact.Name = request.Name;
|
|
||||||
contact.LastName = request.LastName;
|
|
||||||
contact.Email = request.Email;
|
|
||||||
contact.Phone = request.Phone;
|
|
||||||
contact.Type = request.Type;
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail(new Error("Failed to update customer contact."));
|
: Result.Fail(new Error($"Contact with ID '{contactId}' does not exist."));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -167,25 +165,23 @@ public class CustomerService(IDbContextFactory<MidrandBooksDbContext> contextFac
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var address = await context.Addresses.FirstOrDefaultAsync(a => a.Id == addressId, cancellationToken);
|
var rowsUpdated = await context.Addresses
|
||||||
|
.Where(a => a.Id == addressId)
|
||||||
|
.ExecuteUpdateAsync(setters => setters
|
||||||
|
.SetProperty(a => a.Street, request.Street)
|
||||||
|
.SetProperty(a => a.City, request.City)
|
||||||
|
.SetProperty(a => a.State, request.State)
|
||||||
|
.SetProperty(a => a.PostalCode, request.PostalCode)
|
||||||
|
.SetProperty(a => a.Country, request.Country)
|
||||||
|
.SetProperty(a => a.Type, request.Type)
|
||||||
|
.SetProperty(a => a.BuildingType, request.BuildingType)
|
||||||
|
.SetProperty(a => a.IsPrimary, request.IsPrimary)
|
||||||
|
.SetProperty(a => a.Name, request.Name)
|
||||||
|
.SetProperty(a => a.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||||
|
|
||||||
if (address is null)
|
return rowsUpdated > 0
|
||||||
return Result.Fail(new Error($"Address with ID '{addressId}' does not exist."));
|
|
||||||
|
|
||||||
address.UpdatedAt = DateTime.UtcNow;
|
|
||||||
address.Street = request.Street;
|
|
||||||
address.City = request.City;
|
|
||||||
address.State = request.State;
|
|
||||||
address.PostalCode = request.PostalCode;
|
|
||||||
address.Country = request.Country;
|
|
||||||
address.Type = request.Type;
|
|
||||||
address.BuildingType = request.BuildingType;
|
|
||||||
address.IsPrimary = request.IsPrimary;
|
|
||||||
address.Name = request.Name;
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail(new Error("Failed to update customer address."));
|
: Result.Fail(new Error($"Address with ID '{addressId}' does not exist."));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -199,17 +195,15 @@ public class CustomerService(IDbContextFactory<MidrandBooksDbContext> contextFac
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var customer = await context.Customers.FirstOrDefaultAsync(c => c.Id == customerId, cancellationToken);
|
var rowsUpdated = await context.Customers
|
||||||
|
.Where(c => c.Id == customerId)
|
||||||
|
.ExecuteUpdateAsync(setters => setters
|
||||||
|
.SetProperty(c => c.Enabled, enabled)
|
||||||
|
.SetProperty(c => c.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||||
|
|
||||||
if (customer is null)
|
return rowsUpdated > 0
|
||||||
return Result.Fail(new Error($"Customer with ID '{customerId}' does not exist."));
|
|
||||||
|
|
||||||
customer.Enabled = enabled;
|
|
||||||
customer.UpdatedAt = DateTime.UtcNow;
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail(new Error("Failed to update customer status."));
|
: Result.Fail(new Error($"Customer with ID '{customerId}' does not exist."));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -223,18 +217,16 @@ public class CustomerService(IDbContextFactory<MidrandBooksDbContext> contextFac
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var contact = await context.Contacts.FirstOrDefaultAsync(cc => cc.Id == contactId, cancellationToken);
|
var rowsUpdated = await context.Contacts
|
||||||
|
.Where(cc => cc.Id == contactId)
|
||||||
|
.ExecuteUpdateAsync(setters => setters
|
||||||
|
.SetProperty(cc => cc.Enabled, enabled)
|
||||||
|
.SetProperty(cc => cc.IsPrimary, isPrimary)
|
||||||
|
.SetProperty(cc => cc.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||||
|
|
||||||
if (contact is null)
|
return rowsUpdated > 0
|
||||||
return Result.Fail(new Error($"Contact with ID '{contactId}' does not exist."));
|
|
||||||
|
|
||||||
contact.Enabled = enabled;
|
|
||||||
contact.IsPrimary = isPrimary;
|
|
||||||
contact.UpdatedAt = DateTime.UtcNow;
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail(new Error("Failed to update customer contact status."));
|
: Result.Fail(new Error($"Contact with ID '{contactId}' does not exist."));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -248,18 +240,16 @@ public class CustomerService(IDbContextFactory<MidrandBooksDbContext> contextFac
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var address = await context.Addresses.FirstOrDefaultAsync(a => a.Id == addressId, cancellationToken);
|
var rowsUpdated = await context.Addresses
|
||||||
|
.Where(a => a.Id == addressId)
|
||||||
|
.ExecuteUpdateAsync(setters => setters
|
||||||
|
.SetProperty(a => a.Enabled, enabled)
|
||||||
|
.SetProperty(a => a.IsPrimary, isPrimary)
|
||||||
|
.SetProperty(a => a.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||||
|
|
||||||
if (address is null)
|
return rowsUpdated > 0
|
||||||
return Result.Fail(new Error($"Address with ID '{addressId}' does not exist."));
|
|
||||||
|
|
||||||
address.Enabled = enabled;
|
|
||||||
address.IsPrimary = isPrimary;
|
|
||||||
address.UpdatedAt = DateTime.UtcNow;
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail(new Error("Failed to update customer address status."));
|
: Result.Fail(new Error($"Address with ID '{addressId}' does not exist."));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Customers.Entities;
|
namespace LiteCharms.Features.MidrandBooks.Customers.Entities;
|
||||||
|
|
||||||
public class AddressConfiguration : IEntityTypeConfiguration<Address>
|
public sealed class AddressConfiguration : IEntityTypeConfiguration<Address>
|
||||||
{
|
{
|
||||||
public void Configure(EntityTypeBuilder<Address> builder)
|
public void Configure(EntityTypeBuilder<Address> builder)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Customers.Entities;
|
namespace LiteCharms.Features.MidrandBooks.Customers.Entities;
|
||||||
|
|
||||||
public class ContactConfiguration : IEntityTypeConfiguration<Contact>
|
public sealed class ContactConfiguration : IEntityTypeConfiguration<Contact>
|
||||||
{
|
{
|
||||||
public void Configure(EntityTypeBuilder<Contact> builder)
|
public void Configure(EntityTypeBuilder<Contact> builder)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Customers.Entities;
|
namespace LiteCharms.Features.MidrandBooks.Customers.Entities;
|
||||||
|
|
||||||
public class CustomerConfiguration : IEntityTypeConfiguration<Customer>
|
public sealed class CustomerConfiguration : IEntityTypeConfiguration<Customer>
|
||||||
{
|
{
|
||||||
public void Configure(EntityTypeBuilder<Customer> builder)
|
public void Configure(EntityTypeBuilder<Customer> builder)
|
||||||
{
|
{
|
||||||
@@ -14,7 +14,8 @@ public class CustomerConfiguration : IEntityTypeConfiguration<Customer>
|
|||||||
builder.Property(c => c.Email).IsRequired();
|
builder.Property(c => c.Email).IsRequired();
|
||||||
builder.Property(c => c.Phone).IsRequired();
|
builder.Property(c => c.Phone).IsRequired();
|
||||||
builder.Property(c => c.Website).IsRequired();
|
builder.Property(c => c.Website).IsRequired();
|
||||||
builder.Property(c => c.SocialMedia).IsRequired(false).HasColumnType("jsonb");
|
|
||||||
builder.Property(c => c.Enabled).HasDefaultValue(true);
|
builder.Property(c => c.Enabled).HasDefaultValue(true);
|
||||||
|
|
||||||
|
builder.OwnsMany(f => f.SocialMedia, b => { b.ToJson(); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public class Customer
|
|||||||
|
|
||||||
public string? Phone { get; set; }
|
public string? Phone { get; set; }
|
||||||
|
|
||||||
public SocialMedia[]? SocialMedia { get; set; }
|
public ICollection<SocialMedia>? SocialMedia { get; set; }
|
||||||
|
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ public record CreateCustomer
|
|||||||
|
|
||||||
public string? Phone { get; set; }
|
public string? Phone { get; set; }
|
||||||
|
|
||||||
public SocialMedia[]? SocialMedia { get; set; }
|
public ICollection<SocialMedia>? SocialMedia { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public record UpdateCustomer : CreateCustomer;
|
public sealed record UpdateCustomer : CreateCustomer;
|
||||||
|
|
||||||
public record CreateCustomerContact
|
public record CreateCustomerContact
|
||||||
{
|
{
|
||||||
@@ -32,7 +32,7 @@ public record CreateCustomerContact
|
|||||||
public string? Phone { get; set; }
|
public string? Phone { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public record UpdateCustomerContact : CreateCustomerContact;
|
public sealed record UpdateCustomerContact : CreateCustomerContact;
|
||||||
|
|
||||||
public record CreateCustomerAddress
|
public record CreateCustomerAddress
|
||||||
{
|
{
|
||||||
@@ -57,4 +57,4 @@ public record CreateCustomerAddress
|
|||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public record UpdateCustomerAddress : CreateCustomerAddress;
|
public sealed record UpdateCustomerAddress : CreateCustomerAddress;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.AuthorBooks.Models;
|
using LiteCharms.Features.MidrandBooks.AuthorBooks.Models;
|
||||||
using LiteCharms.Features.MidrandBooks.Authors.Models;
|
using LiteCharms.Features.MidrandBooks.Authors.Models;
|
||||||
using LiteCharms.Features.MidrandBooks.Customers.Models;
|
using LiteCharms.Features.MidrandBooks.Customers.Models;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||||
using LiteCharms.Features.MidrandBooks.Pages.Models;
|
using LiteCharms.Features.MidrandBooks.Pages.Models;
|
||||||
using LiteCharms.Features.MidrandBooks.Products.Models;
|
using LiteCharms.Features.MidrandBooks.Products.Models;
|
||||||
|
|
||||||
@@ -8,6 +9,51 @@ namespace LiteCharms.Features.MidrandBooks.Extensions;
|
|||||||
|
|
||||||
public static class Mappers
|
public static class Mappers
|
||||||
{
|
{
|
||||||
|
public static ShippingProvider ToModel(this Orders.Entities.ShippingProvider entity) => new()
|
||||||
|
{
|
||||||
|
Id = entity.Id,
|
||||||
|
CreatedAt = entity.CreatedAt,
|
||||||
|
UpdatedAt = entity.UpdatedAt,
|
||||||
|
Name = entity.Name,
|
||||||
|
Type = entity.Type,
|
||||||
|
Price = entity.Price,
|
||||||
|
Enabled = entity.Enabled,
|
||||||
|
TrackingUrl = entity.TrackingUrl,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Shipping ToModel(this Orders.Entities.Shipping entity) => new()
|
||||||
|
{
|
||||||
|
Id = entity.Id,
|
||||||
|
CreatedAt = entity.CreatedAt,
|
||||||
|
UpdatedAt = entity.UpdatedAt,
|
||||||
|
OrderId = entity.OrderId,
|
||||||
|
AddressId = entity.AddressId,
|
||||||
|
Status = entity.Status,
|
||||||
|
TrackingNumber = entity.TrackingNumber
|
||||||
|
};
|
||||||
|
|
||||||
|
public static OrderItem ToModel(this Orders.Entities.OrderItem entity) => new()
|
||||||
|
{
|
||||||
|
Id = entity.Id,
|
||||||
|
CreatedAt = entity.CreatedAt,
|
||||||
|
OrderId = entity.OrderId,
|
||||||
|
Quantity = entity.Quantity,
|
||||||
|
AuthorBookId = entity.AuthorBookId,
|
||||||
|
ProductPriceId = entity.ProductPriceId
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Order ToModel(this Orders.Entities.Order entity) => new()
|
||||||
|
{
|
||||||
|
Id = entity.Id,
|
||||||
|
CustomerId = entity.CustomerId,
|
||||||
|
CreatedAt = entity.CreatedAt,
|
||||||
|
UpdatedAt = entity.UpdatedAt,
|
||||||
|
Status = entity.Status,
|
||||||
|
Total = entity.Total,
|
||||||
|
InvoiceUrl = entity.InvoiceUrl,
|
||||||
|
Notes = entity.Notes
|
||||||
|
};
|
||||||
|
|
||||||
public static Customer ToModel(this Customers.Entities.Customer entiry) => new()
|
public static Customer ToModel(this Customers.Entities.Customer entiry) => new()
|
||||||
{
|
{
|
||||||
Id = entiry.Id,
|
Id = entiry.Id,
|
||||||
|
|||||||
@@ -8,8 +8,18 @@ public static class Postgres
|
|||||||
|
|
||||||
public static IServiceCollection AddMidrandShopDatabase(this IServiceCollection services, IConfiguration configuration)
|
public static IServiceCollection AddMidrandShopDatabase(this IServiceCollection services, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
|
var connectionString = configuration.GetConnectionString(MidrandBooksDbConfigName);
|
||||||
|
|
||||||
|
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
|
||||||
|
|
||||||
|
dataSourceBuilder.ConfigureTypeLoading(options => { options.EnableTypeLoading(false); });
|
||||||
|
|
||||||
|
var dataSource = dataSourceBuilder.Build();
|
||||||
|
|
||||||
|
services.AddSingleton(dataSource);
|
||||||
|
|
||||||
services.AddPooledDbContextFactory<MidrandBooksDbContext>(options =>
|
services.AddPooledDbContextFactory<MidrandBooksDbContext>(options =>
|
||||||
options.UseNpgsql(configuration.GetConnectionString(MidrandBooksDbConfigName)));
|
options.UseNpgsql(dataSource));
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Abstractions;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Extensions;
|
||||||
|
|
||||||
|
public static class Shop
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddShopServices(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
var serviceType = typeof(IService);
|
||||||
|
|
||||||
|
var implementations = Assembly.GetExecutingAssembly().GetTypes()
|
||||||
|
.Where(t => serviceType.IsAssignableFrom(t) && t.IsClass && !t.IsAbstract);
|
||||||
|
|
||||||
|
foreach (var implementation in implementations)
|
||||||
|
services.AddScoped(implementation);
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.HealthChecks;
|
namespace LiteCharms.Features.MidrandBooks.HealthChecks;
|
||||||
|
|
||||||
public class MidrandShopQuartzHealthCheck(ISchedulerFactory schedulerFactory) : IHealthCheck
|
public sealed class MidrandShopQuartzHealthCheck(ISchedulerFactory schedulerFactory) : IHealthCheck
|
||||||
{
|
{
|
||||||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.HealthChecks;
|
namespace LiteCharms.Features.MidrandBooks.HealthChecks;
|
||||||
|
|
||||||
public class PostgresMidrandShopHealthCheck(IConfiguration configuration) : IHealthCheck
|
public sealed class PostgresMidrandShopHealthCheck(IConfiguration configuration) : IHealthCheck
|
||||||
{
|
{
|
||||||
private readonly string connectionString = configuration.GetConnectionString(MidrandBooksDbConfigName)!;
|
private readonly string connectionString = configuration.GetConnectionString(MidrandBooksDbConfigName)!;
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,10 @@
|
|||||||
|
|
||||||
<!-- Quartz Scheduler-->
|
<!-- Quartz Scheduler-->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Meziantou.Analyzer" Version="3.0.96">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="OpenTelemetry" Version="1.15.3" />
|
<PackageReference Include="OpenTelemetry" Version="1.15.3" />
|
||||||
<PackageReference Include="Quartz" Version="3.18.1" />
|
<PackageReference Include="Quartz" Version="3.18.1" />
|
||||||
<PackageReference Include="Quartz.Plugins" Version="3.18.1" />
|
<PackageReference Include="Quartz.Plugins" Version="3.18.1" />
|
||||||
@@ -143,6 +147,8 @@
|
|||||||
|
|
||||||
<!-- Shared Usings -->
|
<!-- Shared Usings -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Using Include="System.Globalization" />
|
||||||
|
<Using Include="System.Reflection" />
|
||||||
<Using Include="Microsoft.AspNetCore.Builder" />
|
<Using Include="Microsoft.AspNetCore.Builder" />
|
||||||
<Using Include="Microsoft.Extensions.Hosting" />
|
<Using Include="Microsoft.Extensions.Hosting" />
|
||||||
<Using Include="System.Text" />
|
<Using Include="System.Text" />
|
||||||
@@ -157,14 +163,4 @@
|
|||||||
<Using Include="Microsoft.Extensions.Logging" />
|
<Using Include="Microsoft.Extensions.Logging" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="appsettings.json">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Order\Entities\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Payments.Entities;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||||
|
|
||||||
|
[EntityTypeConfiguration<OrderConfiguration, Order>]
|
||||||
|
public class Order : Models.Order
|
||||||
|
{
|
||||||
|
public virtual Shipping? Shipping { get; set; }
|
||||||
|
|
||||||
|
public virtual ICollection<OrderItem> OrderItems { get; set; } = [];
|
||||||
|
|
||||||
|
public virtual ICollection<Refund> Refunds { get; set; } = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||||
|
|
||||||
|
public sealed class OrderConfiguration : IEntityTypeConfiguration<Order>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<Order> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("Orders");
|
||||||
|
|
||||||
|
builder.HasKey(o => o.Id);
|
||||||
|
builder.Property(o => o.CustomerId).IsRequired();
|
||||||
|
builder.Property(o => o.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
||||||
|
builder.Property(o => o.UpdatedAt).HasDefaultValueSql("now()");
|
||||||
|
builder.Property(o => o.Status).IsRequired();
|
||||||
|
builder.Property(o => o.Total).IsRequired().HasPrecision(18, 2);
|
||||||
|
builder.Property(o => o.Notes).HasMaxLength(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.AuthorBooks.Entities;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Products.Entities;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||||
|
|
||||||
|
[EntityTypeConfiguration<OrderItemConfiguration, OrderItem>]
|
||||||
|
public class OrderItem : Models.OrderItem
|
||||||
|
{
|
||||||
|
public virtual Order? Order { get; set; }
|
||||||
|
|
||||||
|
public virtual AuthorBook? AuthorBook { get; set; }
|
||||||
|
|
||||||
|
public virtual ProductPrice? ProductPrice { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||||
|
|
||||||
|
public sealed class OrderItemConfiguration : IEntityTypeConfiguration<OrderItem>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<OrderItem> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("OrderItems");
|
||||||
|
|
||||||
|
builder.HasKey(oi => oi.Id);
|
||||||
|
builder.Property(oi => oi.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
||||||
|
builder.Property(oi => oi.OrderId).IsRequired();
|
||||||
|
builder.Property(oi => oi.AuthorBookId).IsRequired();
|
||||||
|
builder.Property(oi => oi.ProductPriceId).IsRequired();
|
||||||
|
builder.Property(oi => oi.Quantity).IsRequired();
|
||||||
|
|
||||||
|
builder.HasOne(oi => oi.Order)
|
||||||
|
.WithMany(o => o.OrderItems)
|
||||||
|
.HasForeignKey(oi => oi.OrderId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
builder.HasOne(oi => oi.AuthorBook)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(oi => oi.AuthorBookId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
builder.HasOne(oi => oi.ProductPrice)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(oi => oi.ProductPriceId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Customers.Entities;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||||
|
|
||||||
|
[EntityTypeConfiguration<ShippingConfiguration, Shipping>]
|
||||||
|
public class Shipping : Models.Shipping
|
||||||
|
{
|
||||||
|
public virtual Order? Order { get; set; }
|
||||||
|
|
||||||
|
public virtual Address? Address { get; set; }
|
||||||
|
|
||||||
|
public virtual ShippingProvider? ShippingProvider { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||||
|
|
||||||
|
public sealed class ShippingConfiguration : IEntityTypeConfiguration<Shipping>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<Shipping> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("Shippings");
|
||||||
|
|
||||||
|
builder.HasKey(s => s.Id);
|
||||||
|
builder.Property(s => s.OrderId).IsRequired();
|
||||||
|
builder.Property(s => s.AddressId).IsRequired();
|
||||||
|
builder.Property(s => s.ShippingProviderId).IsRequired();
|
||||||
|
builder.Property(s => s.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
||||||
|
builder.Property(s => s.UpdatedAt).HasDefaultValueSql("now()");
|
||||||
|
builder.Property(s => s.Status).IsRequired();
|
||||||
|
builder.Property(s => s.TrackingNumber).HasMaxLength(255);
|
||||||
|
|
||||||
|
builder.HasOne(s => s.Order)
|
||||||
|
.WithOne(o => o.Shipping)
|
||||||
|
.HasForeignKey<Shipping>(s => s.OrderId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
builder.HasOne(s => s.Address)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(s => s.AddressId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
builder.HasOne(f => f.ShippingProvider)
|
||||||
|
.WithMany(f => f.Shippings)
|
||||||
|
.HasForeignKey(f => f.ShippingProviderId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||||
|
|
||||||
|
public class ShippingProvider : Models.ShippingProvider
|
||||||
|
{
|
||||||
|
public virtual ICollection<Shipping> Shippings { get; set; } = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||||
|
|
||||||
|
public sealed class ShippingProviderConfiguration : IEntityTypeConfiguration<ShippingProvider>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<ShippingProvider> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("ShippingProviders");
|
||||||
|
|
||||||
|
builder.HasKey(f => f.Id);
|
||||||
|
builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
||||||
|
builder.Property(f => f.UpdatedAt).HasDefaultValueSql("now()");
|
||||||
|
builder.Property(f => f.Type).IsRequired();
|
||||||
|
builder.Property(f => f.Name).IsRequired().HasMaxLength(100);
|
||||||
|
builder.Property(f => f.Price).IsRequired().HasPrecision(18, 2);
|
||||||
|
builder.Property(f => f.Enabled).HasDefaultValue(true);
|
||||||
|
builder.Property(f => f.TrackingUrl).HasMaxLength(200);
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-4
@@ -1,4 +1,4 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Order.Models;
|
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||||
|
|
||||||
public class Order
|
public class Order
|
||||||
{
|
{
|
||||||
@@ -14,9 +14,7 @@ public class Order
|
|||||||
|
|
||||||
public decimal Total { get; set; }
|
public decimal Total { get; set; }
|
||||||
|
|
||||||
public string[]? Notes { get; set; }
|
public string? Notes { get; set; }
|
||||||
|
|
||||||
public string[]? Terms { get; set; }
|
|
||||||
|
|
||||||
public string? InvoiceUrl { get; set; }
|
public string? InvoiceUrl { get; set; }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||||
|
|
||||||
|
public class OrderItem
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|
||||||
|
public long OrderId { get; set; }
|
||||||
|
|
||||||
|
public long AuthorBookId { get; set; }
|
||||||
|
|
||||||
|
public long ProductPriceId { get; set; }
|
||||||
|
|
||||||
|
public int Quantity { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||||
|
|
||||||
|
public sealed record CreateOrder(decimal TotalPrice, string? Notes);
|
||||||
|
|
||||||
|
public sealed record CreateOrderItem(long AuthorBookId, long ProductPriceId, int Quantity);
|
||||||
|
|
||||||
|
public sealed record CreateShipping(long AddressId, long ShippingProviderId, string? TrackingNumber = null);
|
||||||
|
|
||||||
|
public sealed record CreateShippingProvider(ShippingProviderTypes Type, string Name, decimal Price, string TrackingUrl);
|
||||||
|
|
||||||
|
public sealed record UpdateShippingProvider(long ProviderId, bool Enabled, string Name, decimal Price, string TrackingUrl);
|
||||||
+7
-1
@@ -1,4 +1,4 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Order.Models;
|
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||||
|
|
||||||
public class Shipping
|
public class Shipping
|
||||||
{
|
{
|
||||||
@@ -10,5 +10,11 @@ public class Shipping
|
|||||||
|
|
||||||
public long OrderId { get; set; }
|
public long OrderId { get; set; }
|
||||||
|
|
||||||
|
public long AddressId { get; set; }
|
||||||
|
|
||||||
|
public long ShippingProviderId { get; set; }
|
||||||
|
|
||||||
|
public string? TrackingNumber { get; set; }
|
||||||
|
|
||||||
public ShippingStatuses Status { get; set; }
|
public ShippingStatuses Status { get; set; }
|
||||||
}
|
}
|
||||||
+3
-1
@@ -1,4 +1,4 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Order.Models;
|
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||||
|
|
||||||
public class ShippingProvider
|
public class ShippingProvider
|
||||||
{
|
{
|
||||||
@@ -14,5 +14,7 @@ public class ShippingProvider
|
|||||||
|
|
||||||
public decimal? Price { get; set; }
|
public decimal? Price { get; set; }
|
||||||
|
|
||||||
|
public string? TrackingUrl { get; set; }
|
||||||
|
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,462 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Abstractions;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Extensions;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Postgres;
|
||||||
|
using LiteCharms.Features.Models;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Orders;
|
||||||
|
|
||||||
|
public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
||||||
|
{
|
||||||
|
public async ValueTask<Result<long>> CreateOrderAsync(long customerId, CreateOrder request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken))
|
||||||
|
return Result.Fail<long>("Customer not found.");
|
||||||
|
|
||||||
|
var order = context.Orders.Add(new Entities.Order
|
||||||
|
{
|
||||||
|
CustomerId = customerId,
|
||||||
|
Status = OrderStatus.Pending,
|
||||||
|
Total = request.TotalPrice
|
||||||
|
});
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok(order.Entity.Id)
|
||||||
|
: Result.Fail<long>("Failed to create order.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<long>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<long>> AddItemToOrderAsync(long orderId, CreateOrderItem request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken))
|
||||||
|
return Result.Fail<long>("Order not found.");
|
||||||
|
|
||||||
|
if(!await context.Books.AnyAsync(ab => ab.Id == request.AuthorBookId, cancellationToken))
|
||||||
|
return Result.Fail<long>("Author book not found.");
|
||||||
|
|
||||||
|
if (!await context.Prices.AnyAsync(pp => pp.Id == request.ProductPriceId, cancellationToken))
|
||||||
|
return Result.Fail<long>("Product price not found.");
|
||||||
|
|
||||||
|
var existingItem = await context.OrderItems.FirstOrDefaultAsync(i => i.ProductPriceId == request.ProductPriceId && i.OrderId == orderId, cancellationToken);
|
||||||
|
|
||||||
|
if(existingItem is not null)
|
||||||
|
{
|
||||||
|
existingItem.Quantity += request.Quantity;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok(existingItem.Id)
|
||||||
|
: Result.Fail<long>("Update existing order item.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var orderItem = context.OrderItems.Add(new Entities.OrderItem
|
||||||
|
{
|
||||||
|
OrderId = orderId,
|
||||||
|
AuthorBookId = request.AuthorBookId,
|
||||||
|
ProductPriceId = request.ProductPriceId,
|
||||||
|
Quantity = request.Quantity
|
||||||
|
});
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok(orderItem.Entity.Id)
|
||||||
|
: Result.Fail<long>("Failed to add item to order.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<long>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> AddItemsToOrderAsync(long orderId, CreateOrderItem[] items, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(items.Length == 0)
|
||||||
|
return Result.Fail("No items to add.");
|
||||||
|
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken))
|
||||||
|
return Result.Fail("Order not found.");
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (!await context.Books.AnyAsync(ab => ab.Id == item.AuthorBookId, cancellationToken))
|
||||||
|
return Result.Fail($"Author book with ID {item.AuthorBookId} not found.");
|
||||||
|
|
||||||
|
if (!await context.Prices.AnyAsync(pp => pp.Id == item.ProductPriceId, cancellationToken))
|
||||||
|
return Result.Fail($"Product price with ID {item.ProductPriceId} not found.");
|
||||||
|
|
||||||
|
var existingItem = await context.OrderItems.FirstOrDefaultAsync(i => i.ProductPriceId == item.ProductPriceId && i.OrderId == orderId, cancellationToken);
|
||||||
|
|
||||||
|
if (existingItem is not null)
|
||||||
|
existingItem.Quantity += item.Quantity;
|
||||||
|
else
|
||||||
|
context.OrderItems.Add(new Entities.OrderItem
|
||||||
|
{
|
||||||
|
OrderId = orderId,
|
||||||
|
AuthorBookId = item.AuthorBookId,
|
||||||
|
ProductPriceId = item.ProductPriceId,
|
||||||
|
Quantity = item.Quantity
|
||||||
|
});
|
||||||
|
|
||||||
|
await context.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> RemoveItemFromOrderAsync(long orderId, long orderItemId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var rowsDeleted = await context.OrderItems
|
||||||
|
.Where(oi => oi.Id == orderItemId && oi.OrderId == orderId)
|
||||||
|
.ExecuteDeleteAsync(cancellationToken);
|
||||||
|
|
||||||
|
return rowsDeleted > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Order item not found or failed to remove.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> ClearOrderItemsAsync(long orderId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var deletedItems = await context.OrderItems.Where(oi => oi.OrderId == orderId)
|
||||||
|
.ExecuteDeleteAsync(cancellationToken);
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Failed to clear order items.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> CancelOrderAsync(long orderId, CancellationToken cancellationToken = default) =>
|
||||||
|
await UpdateOrderStatusAsync(orderId, OrderStatus.Cancelled, cancellationToken);
|
||||||
|
|
||||||
|
public async ValueTask<Result<Order>> GetOrderAsync(long orderId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var order = await context.Orders.AsNoTracking().FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken);
|
||||||
|
|
||||||
|
return order is not null
|
||||||
|
? Result.Ok(order.ToModel())
|
||||||
|
: Result.Fail<Order>("Order not found.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<Order>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<Order[]>> GetOrdersByCustomerAsync(long customerId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if(!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken))
|
||||||
|
return Result.Fail<Order[]>("Customer not found.");
|
||||||
|
|
||||||
|
var orders = await context.Orders
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(o => o.CustomerId == customerId)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return Result.Ok(orders.Select(o => o.ToModel()).ToArray());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<Order[]>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<Order[]>> GetOrdersAsync(DateRange range, int index, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fromDate = range.From.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc);
|
||||||
|
var toDate = range.To.ToDateTime(TimeOnly.MaxValue, DateTimeKind.Utc);
|
||||||
|
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var orders = await context.Orders
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(o => o.CreatedAt >= fromDate && o.CreatedAt <= toDate)
|
||||||
|
.Skip(index).Take(range.MaxRecords)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return Result.Ok(orders.Select(o => o.ToModel()).ToArray());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<Order[]>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> UpdateOrderStatusAsync(long orderId, OrderStatus newStatus, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var rowsUpdated = await context.Orders
|
||||||
|
.Where(o => o.Id == orderId)
|
||||||
|
.ExecuteUpdateAsync(setters => setters
|
||||||
|
.SetProperty(o => o.Status, newStatus)
|
||||||
|
.SetProperty(o => o.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||||
|
|
||||||
|
return rowsUpdated > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Order not found or status update failed.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<long>> AddShippingToOrderAsync(long orderId, CreateShipping request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if(!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken))
|
||||||
|
return Result.Fail("Order not found.");
|
||||||
|
|
||||||
|
if(!await context.Addresses.AnyAsync(a => a.Id == request.AddressId, cancellationToken))
|
||||||
|
return Result.Fail("Address not found.");
|
||||||
|
|
||||||
|
if(!await context.ShippingProviders.AnyAsync(sp => sp.Id == request.ShippingProviderId && sp.Enabled, cancellationToken))
|
||||||
|
return Result.Fail("Shipping provider not found or disabled.");
|
||||||
|
|
||||||
|
if(await context.Shippings.AnyAsync(s => s.OrderId == orderId, cancellationToken))
|
||||||
|
return Result.Fail("Shipping already exists for this order.");
|
||||||
|
|
||||||
|
var shipping = context.Shippings.Add(new Entities.Shipping
|
||||||
|
{
|
||||||
|
OrderId = orderId,
|
||||||
|
AddressId = request.AddressId,
|
||||||
|
ShippingProviderId = request.ShippingProviderId,
|
||||||
|
Status = ShippingStatuses.Pending,
|
||||||
|
TrackingNumber = request.TrackingNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Failed to add shipping to order.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> UpdateShippingStatusAsync(long orderId, ShippingStatuses newStatus, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var rowsUpdated = await context.Shippings
|
||||||
|
.Where(s => s.OrderId == orderId)
|
||||||
|
.ExecuteUpdateAsync(setters => setters
|
||||||
|
.SetProperty(s => s.Status, newStatus)
|
||||||
|
.SetProperty(s => s.UpdatedAt, DateTime.UtcNow),
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
return rowsUpdated > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Shipping not found for this order or status update failed.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<Shipping>> GetShippingByOrderIdAsync(long orderId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var shipping = await context.Shippings
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(s => s.OrderId == orderId, cancellationToken);
|
||||||
|
|
||||||
|
return shipping is not null
|
||||||
|
? Result.Ok(shipping.ToModel())
|
||||||
|
: Result.Fail<Shipping>("Shipping not found for this order.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<Shipping>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> RemoveShippingFromOrderAsync(long orderId, long shippingId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var rowsDeleted = await context.Shippings
|
||||||
|
.Where(s => s.Id == shippingId && s.OrderId == orderId)
|
||||||
|
.ExecuteDeleteAsync(cancellationToken);
|
||||||
|
|
||||||
|
return rowsDeleted > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Shipping record not found for this order.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> UpdateShippingTrackingNumberAsync(long orderId, long shippingId, string trackingNumber, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var rowsUpdated = await context.Shippings
|
||||||
|
.Where(s => s.Id == shippingId && s.OrderId == orderId)
|
||||||
|
.ExecuteUpdateAsync(setters => setters
|
||||||
|
.SetProperty(s => s.TrackingNumber, trackingNumber)
|
||||||
|
.SetProperty(s => s.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||||
|
|
||||||
|
return rowsUpdated > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Shipping record not found for this order.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<long>> CreateShippingProviderAsync(CreateShippingProvider request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if(await context.ShippingProviders.AnyAsync(sp => sp.Type == request.Type, cancellationToken))
|
||||||
|
return Result.Fail("Shipping provider with the same type already exists.");
|
||||||
|
|
||||||
|
var shippingProvider = context.ShippingProviders.Add(new Entities.ShippingProvider
|
||||||
|
{
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
|
Name = request.Name,
|
||||||
|
Type = request.Type,
|
||||||
|
Price = request.Price,
|
||||||
|
TrackingUrl = request.TrackingUrl
|
||||||
|
});
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok(shippingProvider.Entity.Id)
|
||||||
|
: Result.Fail("Failed to create shipping provider.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<ShippingProvider[]>> GetShippingProvidersAsync(bool isEnabled, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var providers = await context.ShippingProviders.AsNoTracking().Where(sp => sp.Enabled == isEnabled)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return Result.Ok(providers.Select(sp => sp.ToModel()).ToArray());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<ShippingProvider[]>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<ShippingProvider>> GetShippingProviderAsync(long providerId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var provider = await context.ShippingProviders.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(sp => sp.Id == providerId, cancellationToken);
|
||||||
|
|
||||||
|
return provider is not null
|
||||||
|
? Result.Ok(provider.ToModel())
|
||||||
|
: Result.Fail<ShippingProvider>("Shipping provider not found.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<ShippingProvider>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> UpdateShippingProviderAsync(UpdateShippingProvider request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var rowsUpdated = await context.ShippingProviders
|
||||||
|
.Where(sp => sp.Id == request.ProviderId)
|
||||||
|
.ExecuteUpdateAsync(setters => setters
|
||||||
|
.SetProperty(sp => sp.Name, request.Name)
|
||||||
|
.SetProperty(sp => sp.Price, request.Price)
|
||||||
|
.SetProperty(sp => sp.TrackingUrl, request.TrackingUrl)
|
||||||
|
.SetProperty(sp => sp.Enabled, request.Enabled)
|
||||||
|
.SetProperty(sp => sp.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||||
|
|
||||||
|
return rowsUpdated > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Shipping provider not found.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Pages.Entities;
|
namespace LiteCharms.Features.MidrandBooks.Pages.Entities;
|
||||||
|
|
||||||
public class BookPageConfiguration : IEntityTypeConfiguration<BookPage>
|
public sealed class BookPageConfiguration : IEntityTypeConfiguration<BookPage>
|
||||||
{
|
{
|
||||||
public void Configure(EntityTypeBuilder<BookPage> builder)
|
public void Configure(EntityTypeBuilder<BookPage> builder)
|
||||||
{
|
{
|
||||||
@@ -13,10 +13,11 @@ public class BookPageConfiguration : IEntityTypeConfiguration<BookPage>
|
|||||||
builder.Property(bp => bp.AuthorBookId).IsRequired();
|
builder.Property(bp => bp.AuthorBookId).IsRequired();
|
||||||
builder.Property(bp => bp.Content).IsRequired();
|
builder.Property(bp => bp.Content).IsRequired();
|
||||||
builder.Property(bp => bp.Type).IsRequired();
|
builder.Property(bp => bp.Type).IsRequired();
|
||||||
builder.Property(bp => bp.ContentType).IsRequired();
|
builder.Property(bp => bp.ContentType).IsRequired();
|
||||||
builder.Property(bp => bp.Notes).IsRequired(false).HasColumnType("jsonb");
|
|
||||||
builder.Property(bp => bp.References).IsRequired(false).HasColumnType("jsonb");
|
|
||||||
builder.Property(bp => bp.Enabled).HasDefaultValue(true);
|
builder.Property(bp => bp.Enabled).HasDefaultValue(true);
|
||||||
|
builder.Property(bp => bp.Notes).IsRequired(false);
|
||||||
|
|
||||||
|
builder.OwnsMany(f => f.References, b => { b.ToJson(); });
|
||||||
|
|
||||||
builder.HasOne(f =>f.Book)
|
builder.HasOne(f =>f.Book)
|
||||||
.WithMany(b => b.Pages)
|
.WithMany(b => b.Pages)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class BookPage
|
|||||||
|
|
||||||
public string[]? Notes { get; set; }
|
public string[]? Notes { get; set; }
|
||||||
|
|
||||||
public PageReference[]? References { get; set; }
|
public ICollection<PageReference>? References { get; set; }
|
||||||
|
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -14,5 +14,7 @@ public class CreateBookPage
|
|||||||
|
|
||||||
public string[]? Notes { get; set; }
|
public string[]? Notes { get; set; }
|
||||||
|
|
||||||
public PageReference[]? References { get; set; }
|
public ICollection<PageReference>? References { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class UpdateBookPage : CreateBookPage;
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Pages.Models;
|
|
||||||
|
|
||||||
public class UpdateBookPage : CreateBookPage;
|
|
||||||
@@ -5,7 +5,7 @@ using LiteCharms.Features.MidrandBooks.Postgres;
|
|||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Pages;
|
namespace LiteCharms.Features.MidrandBooks.Pages;
|
||||||
|
|
||||||
public class PageService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
public sealed class PageService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
||||||
{
|
{
|
||||||
public async ValueTask<Result> DeleteAllAsync(long authorBookId, CancellationToken cancellationToken = default)
|
public async ValueTask<Result> DeleteAllAsync(long authorBookId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
@@ -13,19 +13,13 @@ public class PageService(IDbContextFactory<MidrandBooksDbContext> contextFactory
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
if (!await context.Books.AnyAsync(b => b.Id == authorBookId, cancellationToken))
|
var rowsDeleted = await context.Pages
|
||||||
return Result.Fail("Book not found");
|
.Where(p => p.AuthorBookId == authorBookId)
|
||||||
|
.ExecuteDeleteAsync(cancellationToken);
|
||||||
|
|
||||||
var pages = await context.Pages.Where(p => p.AuthorBookId == authorBookId).ToListAsync(cancellationToken);
|
return rowsDeleted > 0
|
||||||
|
? Result.Ok()
|
||||||
if (pages.Count == 0)
|
: Result.Fail("No pages found for the specified book");
|
||||||
return Result.Fail("No pages found for the specified book");
|
|
||||||
|
|
||||||
context.Pages.RemoveRange(pages);
|
|
||||||
|
|
||||||
await context.SaveChangesAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Result.Ok();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -39,16 +33,13 @@ public class PageService(IDbContextFactory<MidrandBooksDbContext> contextFactory
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var page = await context.Pages.FirstOrDefaultAsync(p => p.AuthorBookId == authorBookId && p.Number == pageNumber && p.Type == pageType, cancellationToken);
|
var rowsDeleted = await context.Pages
|
||||||
|
.Where(p => p.AuthorBookId == authorBookId && p.Number == pageNumber && p.Type == pageType)
|
||||||
|
.ExecuteDeleteAsync(cancellationToken);
|
||||||
|
|
||||||
if (page is null)
|
return rowsDeleted > 0
|
||||||
return Result.Fail("Page not found");
|
|
||||||
|
|
||||||
context.Pages.Remove(page);
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail("Failed to delete page");
|
: Result.Fail("Page not found");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -62,17 +53,15 @@ public class PageService(IDbContextFactory<MidrandBooksDbContext> contextFactory
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var page = await context.Pages.FirstOrDefaultAsync(p => p.Id == bookPageId, cancellationToken);
|
var rowsUpdated = await context.Pages
|
||||||
|
.Where(p => p.Id == bookPageId)
|
||||||
|
.ExecuteUpdateAsync(setters => setters
|
||||||
|
.SetProperty(p => p.Enabled, enabled)
|
||||||
|
.SetProperty(p => p.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||||
|
|
||||||
if (page is null)
|
return rowsUpdated > 0
|
||||||
return Result.Fail("Page not found");
|
|
||||||
|
|
||||||
page.UpdatedAt = DateTime.UtcNow;
|
|
||||||
page.Enabled = enabled;
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail("Failed to update page status");
|
: Result.Fail("Page not found");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -86,16 +75,13 @@ public class PageService(IDbContextFactory<MidrandBooksDbContext> contextFactory
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var page = await context.Pages.FirstOrDefaultAsync(p => p.Id == bookPageId, cancellationToken);
|
var rowsDeleted = await context.Pages
|
||||||
|
.Where(p => p.Id == bookPageId)
|
||||||
|
.ExecuteDeleteAsync(cancellationToken);
|
||||||
|
|
||||||
if (page is null)
|
return rowsDeleted > 0
|
||||||
return Result.Fail("Page not found");
|
|
||||||
|
|
||||||
context.Pages.Remove(page);
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail("Failed to delete page");
|
: Result.Fail("Page not found");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -109,22 +95,20 @@ public class PageService(IDbContextFactory<MidrandBooksDbContext> contextFactory
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var page = await context.Pages.FirstOrDefaultAsync(p => p.Id == bookPageId, cancellationToken);
|
var rowsUpdated = await context.Pages
|
||||||
|
.Where(p => p.Id == bookPageId)
|
||||||
|
.ExecuteUpdateAsync(setters => setters
|
||||||
|
.SetProperty(p => p.Type, request.Type)
|
||||||
|
.SetProperty(p => p.ContentType, request.ContentType)
|
||||||
|
.SetProperty(p => p.Number, request.Number)
|
||||||
|
.SetProperty(p => p.Content, request.Content)
|
||||||
|
.SetProperty(p => p.Notes, request.Notes)
|
||||||
|
.SetProperty(p => p.References, request.References)
|
||||||
|
.SetProperty(p => p.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||||
|
|
||||||
if (page is null)
|
return rowsUpdated > 0
|
||||||
return Result.Fail("Page not found");
|
|
||||||
|
|
||||||
page.UpdatedAt = DateTime.UtcNow;
|
|
||||||
page.Type = request.Type;
|
|
||||||
page.ContentType = request.ContentType;
|
|
||||||
page.Number = request.Number;
|
|
||||||
page.Content = request.Content;
|
|
||||||
page.Notes = request.Notes;
|
|
||||||
page.References = request.References;
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail("Failed to update page");
|
: Result.Fail("Page not found");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Payments.Entities;
|
||||||
|
|
||||||
|
[EntityTypeConfiguration<RefundConfiguration, Refund>]
|
||||||
|
public class Refund : Models.Refund
|
||||||
|
{
|
||||||
|
public virtual Order? Order { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Payments.Entities;
|
||||||
|
|
||||||
|
public sealed class RefundConfiguration : IEntityTypeConfiguration<Refund>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<Refund> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("Refunds");
|
||||||
|
|
||||||
|
builder.HasKey(r => r.Id);
|
||||||
|
builder.Property(r => r.OrderId).IsRequired();
|
||||||
|
builder.Property(o => o.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
||||||
|
builder.Property(o => o.UpdatedAt).HasDefaultValueSql("now()");
|
||||||
|
builder.Property(o => o.Status).IsRequired();
|
||||||
|
builder.Property(r => r.Amount).IsRequired().HasPrecision(18, 2);
|
||||||
|
builder.Property(r => r.Reason).HasMaxLength(1000);
|
||||||
|
|
||||||
|
builder.HasOne(r => r.Order)
|
||||||
|
.WithMany(o => o.Refunds)
|
||||||
|
.HasForeignKey(r => r.OrderId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-3
@@ -1,14 +1,14 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Order.Models;
|
namespace LiteCharms.Features.MidrandBooks.Payments.Models;
|
||||||
|
|
||||||
public class Refund
|
public class Refund
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public long Id { get; set; }
|
||||||
|
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|
||||||
public DateTime? UpdatedAt { get; set; }
|
public DateTime? UpdatedAt { get; set; }
|
||||||
|
|
||||||
public Guid OrderId { get; set; }
|
public long OrderId { get; set; }
|
||||||
|
|
||||||
public RefundTypes Type { get; set; }
|
public RefundTypes Type { get; set; }
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Abstractions;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Payments;
|
||||||
|
|
||||||
|
public sealed class PaymentService : IService
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.AuthorBooks.Entities;
|
using LiteCharms.Features.MidrandBooks.AuthorBooks.Entities;
|
||||||
using LiteCharms.Features.MidrandBooks.Authors.Entities;
|
using LiteCharms.Features.MidrandBooks.Authors.Entities;
|
||||||
using LiteCharms.Features.MidrandBooks.Customers.Entities;
|
using LiteCharms.Features.MidrandBooks.Customers.Entities;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||||
using LiteCharms.Features.MidrandBooks.Pages.Entities;
|
using LiteCharms.Features.MidrandBooks.Pages.Entities;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Payments.Entities;
|
||||||
using LiteCharms.Features.MidrandBooks.Products.Entities;
|
using LiteCharms.Features.MidrandBooks.Products.Entities;
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Postgres;
|
namespace LiteCharms.Features.MidrandBooks.Postgres;
|
||||||
|
|
||||||
public class MidrandBooksDbContext(DbContextOptions<MidrandBooksDbContext> options) : DbContext(options)
|
public sealed class MidrandBooksDbContext(DbContextOptions<MidrandBooksDbContext> options) : DbContext(options)
|
||||||
{
|
{
|
||||||
public DbSet<Author> Authors => Set<Author>();
|
public DbSet<Author> Authors => Set<Author>();
|
||||||
|
|
||||||
@@ -23,4 +25,14 @@ public class MidrandBooksDbContext(DbContextOptions<MidrandBooksDbContext> optio
|
|||||||
public DbSet<Address> Addresses => Set<Address>();
|
public DbSet<Address> Addresses => Set<Address>();
|
||||||
|
|
||||||
public DbSet<Customer> Customers => Set<Customer>();
|
public DbSet<Customer> Customers => Set<Customer>();
|
||||||
|
|
||||||
|
public DbSet<Order> Orders => Set<Order>();
|
||||||
|
|
||||||
|
public DbSet<OrderItem> OrderItems => Set<OrderItem>();
|
||||||
|
|
||||||
|
public DbSet<Refund> Refunds => Set<Refund>();
|
||||||
|
|
||||||
|
public DbSet<Shipping> Shippings => Set<Shipping>();
|
||||||
|
|
||||||
|
public DbSet<ShippingProvider> ShippingProviders => Set<ShippingProvider>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,13 @@
|
|||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Postgres;
|
namespace LiteCharms.Features.MidrandBooks.Postgres;
|
||||||
|
|
||||||
public class MidrandBooksDbContextFactory : IDesignTimeDbContextFactory<MidrandBooksDbContext>
|
public sealed class MidrandBooksDbContextFactory : IDesignTimeDbContextFactory<MidrandBooksDbContext>
|
||||||
{
|
{
|
||||||
public MidrandBooksDbContext CreateDbContext(string[] args)
|
public MidrandBooksDbContext CreateDbContext(string[] args)
|
||||||
{
|
{
|
||||||
var configuration = new ConfigurationBuilder()
|
var configuration = new ConfigurationBuilder()
|
||||||
.SetBasePath(Directory.GetCurrentDirectory())
|
.SetBasePath(Directory.GetCurrentDirectory())
|
||||||
.AddUserSecrets(typeof(MidrandBooksDbContext).Assembly)
|
.AddUserSecrets(typeof(MidrandBooksDbContext).Assembly)
|
||||||
.AddJsonFile("appsettings.json")
|
|
||||||
.AddEnvironmentVariables()
|
.AddEnvironmentVariables()
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
|||||||
+938
@@ -0,0 +1,938 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Postgres;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(MidrandBooksDbContext))]
|
||||||
|
[Migration("20260529070104_Init")]
|
||||||
|
partial class Init
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.8")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AuthorId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<long>("ProductId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("Ranking")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Rating")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorId");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.ToTable("Books");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Biography")
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
|
b.Property<string>("Company")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<string>("ImageUrl")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
|
b.Property<string>("LastName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<int>("PublisherType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("ThumbnailImageUrl")
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("VatNumber")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<string>("Website")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Authors", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Address", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("BuildingType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("City")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Country")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("CustomerId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<bool>("IsPrimary")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PostalCode")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("State")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Street")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CustomerId");
|
||||||
|
|
||||||
|
b.ToTable("Addresses", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Contact", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("CustomerId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<bool>("IsPrimary")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("LastName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CustomerId");
|
||||||
|
|
||||||
|
b.ToTable("Contacts", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Company")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("VatNumber")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Website")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Customers", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("CustomerId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("InvoiceUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("character varying(1000)");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<decimal>("Total")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("numeric(18,2)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Orders", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.OrderItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AuthorBookId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("OrderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("ProductPriceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorBookId");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId");
|
||||||
|
|
||||||
|
b.HasIndex("ProductPriceId");
|
||||||
|
|
||||||
|
b.ToTable("OrderItems", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.Shipping", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AddressId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("OrderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("ShippingProviderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("TrackingNumber")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AddressId");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("ShippingProviderId");
|
||||||
|
|
||||||
|
b.ToTable("Shippings", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.ShippingProvider", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<decimal?>("Price")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<string>("TrackingUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("ShippingProviders");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Pages.Entities.BookPage", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AuthorBookId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Content")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("bytea");
|
||||||
|
|
||||||
|
b.Property<int>("ContentType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("Notes")
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.Property<int>("Number")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasDefaultValue(0);
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorBookId");
|
||||||
|
|
||||||
|
b.ToTable("BookPages", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Payments.Entities.Refund", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("numeric(18,2)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("OrderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Reason")
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("character varying(1000)");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId");
|
||||||
|
|
||||||
|
b.ToTable("Refunds", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("Categories")
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("ImageUrl")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<string>("Summary")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("ThumbnailUrls")
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Products", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("numeric(18,2)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<decimal>("Discount")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("numeric(18,2)");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<long>("ProductId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.ToTable("Prices", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", "Author")
|
||||||
|
.WithMany("Books")
|
||||||
|
.HasForeignKey("AuthorId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", "Product")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Author");
|
||||||
|
|
||||||
|
b.Navigation("Product");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", b =>
|
||||||
|
{
|
||||||
|
b.OwnsMany("LiteCharms.Features.Models.SocialMedia", "SocialMedia", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<long>("AuthorId");
|
||||||
|
|
||||||
|
b1.Property<int>("__synthesizedOrdinal")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b1.Property<string>("ImageUrl");
|
||||||
|
|
||||||
|
b1.Property<string>("Name");
|
||||||
|
|
||||||
|
b1.Property<int>("Type");
|
||||||
|
|
||||||
|
b1.Property<string>("Url");
|
||||||
|
|
||||||
|
b1.HasKey("AuthorId", "__synthesizedOrdinal");
|
||||||
|
|
||||||
|
b1.ToTable("Authors");
|
||||||
|
|
||||||
|
b1
|
||||||
|
.ToJson("SocialMedia")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("AuthorId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("SocialMedia");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Address", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", "Customer")
|
||||||
|
.WithMany("Addresses")
|
||||||
|
.HasForeignKey("CustomerId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Customer");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Contact", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", "Customer")
|
||||||
|
.WithMany("Contacts")
|
||||||
|
.HasForeignKey("CustomerId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Customer");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", b =>
|
||||||
|
{
|
||||||
|
b.OwnsMany("LiteCharms.Features.Models.SocialMedia", "SocialMedia", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<long>("CustomerId");
|
||||||
|
|
||||||
|
b1.Property<int>("__synthesizedOrdinal")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b1.Property<string>("ImageUrl");
|
||||||
|
|
||||||
|
b1.Property<string>("Name");
|
||||||
|
|
||||||
|
b1.Property<int>("Type");
|
||||||
|
|
||||||
|
b1.Property<string>("Url");
|
||||||
|
|
||||||
|
b1.HasKey("CustomerId", "__synthesizedOrdinal");
|
||||||
|
|
||||||
|
b1.ToTable("Customers");
|
||||||
|
|
||||||
|
b1
|
||||||
|
.ToJson("SocialMedia")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("CustomerId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("SocialMedia");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.OrderItem", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", "AuthorBook")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AuthorBookId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", "Order")
|
||||||
|
.WithMany("OrderItems")
|
||||||
|
.HasForeignKey("OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", "ProductPrice")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProductPriceId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("AuthorBook");
|
||||||
|
|
||||||
|
b.Navigation("Order");
|
||||||
|
|
||||||
|
b.Navigation("ProductPrice");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.Shipping", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Customers.Entities.Address", "Address")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AddressId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", "Order")
|
||||||
|
.WithOne("Shipping")
|
||||||
|
.HasForeignKey("LiteCharms.Features.MidrandBooks.Orders.Entities.Shipping", "OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Orders.Entities.ShippingProvider", "ShippingProvider")
|
||||||
|
.WithMany("Shippings")
|
||||||
|
.HasForeignKey("ShippingProviderId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Address");
|
||||||
|
|
||||||
|
b.Navigation("Order");
|
||||||
|
|
||||||
|
b.Navigation("ShippingProvider");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Pages.Entities.BookPage", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", "Book")
|
||||||
|
.WithMany("Pages")
|
||||||
|
.HasForeignKey("AuthorBookId")
|
||||||
|
.OnDelete(DeleteBehavior.NoAction)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.OwnsMany("LiteCharms.Features.Models.PageReference", "References", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<long>("BookPageId");
|
||||||
|
|
||||||
|
b1.Property<int>("__synthesizedOrdinal")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b1.Property<string>("Description");
|
||||||
|
|
||||||
|
b1.Property<string>("Tag");
|
||||||
|
|
||||||
|
b1.Property<string>("Url");
|
||||||
|
|
||||||
|
b1.HasKey("BookPageId", "__synthesizedOrdinal");
|
||||||
|
|
||||||
|
b1.ToTable("BookPages");
|
||||||
|
|
||||||
|
b1
|
||||||
|
.ToJson("References")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("BookPageId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("Book");
|
||||||
|
|
||||||
|
b.Navigation("References");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Payments.Entities.Refund", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", "Order")
|
||||||
|
.WithMany("Refunds")
|
||||||
|
.HasForeignKey("OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Order");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
||||||
|
{
|
||||||
|
b.OwnsOne("LiteCharms.Features.Models.ProductMetadata", "Metadata", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<long>("ProductId");
|
||||||
|
|
||||||
|
b1.Property<string>("CopyrightInfo");
|
||||||
|
|
||||||
|
b1.Property<string>("ManufactureDate");
|
||||||
|
|
||||||
|
b1.Property<string>("Manufacturer");
|
||||||
|
|
||||||
|
b1.Property<string>("SerialNumber");
|
||||||
|
|
||||||
|
b1.HasKey("ProductId");
|
||||||
|
|
||||||
|
b1.ToTable("Products");
|
||||||
|
|
||||||
|
b1
|
||||||
|
.ToJson("Metadata")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("ProductId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("Metadata");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", "Product")
|
||||||
|
.WithMany("Prices")
|
||||||
|
.HasForeignKey("ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Product");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Pages");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Books");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Addresses");
|
||||||
|
|
||||||
|
b.Navigation("Contacts");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("OrderItems");
|
||||||
|
|
||||||
|
b.Navigation("Refunds");
|
||||||
|
|
||||||
|
b.Navigation("Shipping");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.ShippingProvider", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Shippings");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Prices");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,471 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Init : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Authors",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
PublisherType = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Company = table.Column<string>(type: "text", nullable: true),
|
||||||
|
VatNumber = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||||
|
Name = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||||
|
LastName = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||||
|
Biography = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
|
||||||
|
Email = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: false),
|
||||||
|
Website = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||||
|
ImageUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: false),
|
||||||
|
ThumbnailImageUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
|
||||||
|
SocialMedia = table.Column<string>(type: "jsonb", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Authors", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Customers",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
Company = table.Column<string>(type: "text", nullable: true),
|
||||||
|
VatNumber = table.Column<string>(type: "text", nullable: true),
|
||||||
|
Email = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Website = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Phone = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
|
||||||
|
SocialMedia = table.Column<string>(type: "jsonb", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Customers", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Orders",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
CustomerId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Status = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Total = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||||
|
Notes = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
|
||||||
|
InvoiceUrl = table.Column<string>(type: "text", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Orders", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Products",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
Type = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||||
|
Summary = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: false),
|
||||||
|
Description = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||||
|
ImageUrl = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||||
|
ThumbnailUrls = table.Column<string[]>(type: "text[]", nullable: true),
|
||||||
|
Categories = table.Column<string[]>(type: "text[]", nullable: true),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
|
||||||
|
Metadata = table.Column<string>(type: "jsonb", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Products", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ShippingProviders",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
Type = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "text", nullable: true),
|
||||||
|
Price = table.Column<decimal>(type: "numeric", nullable: true),
|
||||||
|
TrackingUrl = table.Column<string>(type: "text", nullable: true),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ShippingProviders", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Addresses",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CustomerId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "text", nullable: false),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
Type = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
BuildingType = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Street = table.Column<string>(type: "text", nullable: false),
|
||||||
|
City = table.Column<string>(type: "text", nullable: false),
|
||||||
|
State = table.Column<string>(type: "text", nullable: false),
|
||||||
|
PostalCode = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Country = table.Column<string>(type: "text", nullable: false),
|
||||||
|
IsPrimary = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Addresses", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Addresses_Customers_CustomerId",
|
||||||
|
column: x => x.CustomerId,
|
||||||
|
principalTable: "Customers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Contacts",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CustomerId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
Type = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "text", nullable: false),
|
||||||
|
LastName = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Email = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Phone = table.Column<string>(type: "text", nullable: false),
|
||||||
|
IsPrimary = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Contacts", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Contacts_Customers_CustomerId",
|
||||||
|
column: x => x.CustomerId,
|
||||||
|
principalTable: "Customers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Refunds",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
OrderId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Type = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Status = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Reason = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
|
||||||
|
Amount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Refunds", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Refunds_Orders_OrderId",
|
||||||
|
column: x => x.OrderId,
|
||||||
|
principalTable: "Orders",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Books",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
AuthorId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
ProductId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Rating = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Ranking = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Books", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Books_Authors_AuthorId",
|
||||||
|
column: x => x.AuthorId,
|
||||||
|
principalTable: "Authors",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Books_Products_ProductId",
|
||||||
|
column: x => x.ProductId,
|
||||||
|
principalTable: "Products",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Prices",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
ProductId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Amount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||||
|
Discount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Prices", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Prices_Products_ProductId",
|
||||||
|
column: x => x.ProductId,
|
||||||
|
principalTable: "Products",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Shippings",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
OrderId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
AddressId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
ShippingProviderId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
TrackingNumber = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||||
|
Status = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Shippings", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Shippings_Addresses_AddressId",
|
||||||
|
column: x => x.AddressId,
|
||||||
|
principalTable: "Addresses",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Shippings_Orders_OrderId",
|
||||||
|
column: x => x.OrderId,
|
||||||
|
principalTable: "Orders",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Shippings_ShippingProviders_ShippingProviderId",
|
||||||
|
column: x => x.ShippingProviderId,
|
||||||
|
principalTable: "ShippingProviders",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "BookPages",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
AuthorBookId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
Type = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
ContentType = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Number = table.Column<int>(type: "integer", nullable: false, defaultValue: 0),
|
||||||
|
Content = table.Column<byte[]>(type: "bytea", nullable: false),
|
||||||
|
Notes = table.Column<string[]>(type: "text[]", nullable: true),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
|
||||||
|
References = table.Column<string>(type: "jsonb", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_BookPages", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_BookPages_Books_AuthorBookId",
|
||||||
|
column: x => x.AuthorBookId,
|
||||||
|
principalTable: "Books",
|
||||||
|
principalColumn: "Id");
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "OrderItems",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
OrderId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
AuthorBookId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
ProductPriceId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Quantity = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_OrderItems", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_OrderItems_Books_AuthorBookId",
|
||||||
|
column: x => x.AuthorBookId,
|
||||||
|
principalTable: "Books",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_OrderItems_Orders_OrderId",
|
||||||
|
column: x => x.OrderId,
|
||||||
|
principalTable: "Orders",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_OrderItems_Prices_ProductPriceId",
|
||||||
|
column: x => x.ProductPriceId,
|
||||||
|
principalTable: "Prices",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Addresses_CustomerId",
|
||||||
|
table: "Addresses",
|
||||||
|
column: "CustomerId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_BookPages_AuthorBookId",
|
||||||
|
table: "BookPages",
|
||||||
|
column: "AuthorBookId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Books_AuthorId",
|
||||||
|
table: "Books",
|
||||||
|
column: "AuthorId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Books_ProductId",
|
||||||
|
table: "Books",
|
||||||
|
column: "ProductId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Contacts_CustomerId",
|
||||||
|
table: "Contacts",
|
||||||
|
column: "CustomerId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_OrderItems_AuthorBookId",
|
||||||
|
table: "OrderItems",
|
||||||
|
column: "AuthorBookId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_OrderItems_OrderId",
|
||||||
|
table: "OrderItems",
|
||||||
|
column: "OrderId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_OrderItems_ProductPriceId",
|
||||||
|
table: "OrderItems",
|
||||||
|
column: "ProductPriceId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Prices_ProductId",
|
||||||
|
table: "Prices",
|
||||||
|
column: "ProductId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Refunds_OrderId",
|
||||||
|
table: "Refunds",
|
||||||
|
column: "OrderId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Shippings_AddressId",
|
||||||
|
table: "Shippings",
|
||||||
|
column: "AddressId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Shippings_OrderId",
|
||||||
|
table: "Shippings",
|
||||||
|
column: "OrderId",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Shippings_ShippingProviderId",
|
||||||
|
table: "Shippings",
|
||||||
|
column: "ShippingProviderId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "BookPages");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Contacts");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "OrderItems");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Refunds");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Shippings");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Books");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Prices");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Addresses");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Orders");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "ShippingProviders");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Authors");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Products");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Customers");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+935
@@ -0,0 +1,935 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Postgres;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(MidrandBooksDbContext))]
|
||||||
|
partial class MidrandBooksDbContextModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.8")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AuthorId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<long>("ProductId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("Ranking")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Rating")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorId");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.ToTable("Books");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Biography")
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
|
b.Property<string>("Company")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<string>("ImageUrl")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
|
b.Property<string>("LastName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<int>("PublisherType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("ThumbnailImageUrl")
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("VatNumber")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<string>("Website")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Authors", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Address", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("BuildingType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("City")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Country")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("CustomerId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<bool>("IsPrimary")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PostalCode")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("State")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Street")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CustomerId");
|
||||||
|
|
||||||
|
b.ToTable("Addresses", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Contact", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("CustomerId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<bool>("IsPrimary")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("LastName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CustomerId");
|
||||||
|
|
||||||
|
b.ToTable("Contacts", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Company")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("VatNumber")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Website")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Customers", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("CustomerId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("InvoiceUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("character varying(1000)");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<decimal>("Total")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("numeric(18,2)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Orders", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.OrderItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AuthorBookId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("OrderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("ProductPriceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorBookId");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId");
|
||||||
|
|
||||||
|
b.HasIndex("ProductPriceId");
|
||||||
|
|
||||||
|
b.ToTable("OrderItems", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.Shipping", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AddressId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("OrderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("ShippingProviderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("TrackingNumber")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AddressId");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("ShippingProviderId");
|
||||||
|
|
||||||
|
b.ToTable("Shippings", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.ShippingProvider", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<decimal?>("Price")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<string>("TrackingUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("ShippingProviders");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Pages.Entities.BookPage", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AuthorBookId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Content")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("bytea");
|
||||||
|
|
||||||
|
b.Property<int>("ContentType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("Notes")
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.Property<int>("Number")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasDefaultValue(0);
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorBookId");
|
||||||
|
|
||||||
|
b.ToTable("BookPages", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Payments.Entities.Refund", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("numeric(18,2)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("OrderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Reason")
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("character varying(1000)");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId");
|
||||||
|
|
||||||
|
b.ToTable("Refunds", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("Categories")
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("ImageUrl")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<string>("Summary")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("ThumbnailUrls")
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Products", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("numeric(18,2)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<decimal>("Discount")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("numeric(18,2)");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<long>("ProductId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.ToTable("Prices", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", "Author")
|
||||||
|
.WithMany("Books")
|
||||||
|
.HasForeignKey("AuthorId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", "Product")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Author");
|
||||||
|
|
||||||
|
b.Navigation("Product");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", b =>
|
||||||
|
{
|
||||||
|
b.OwnsMany("LiteCharms.Features.Models.SocialMedia", "SocialMedia", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<long>("AuthorId");
|
||||||
|
|
||||||
|
b1.Property<int>("__synthesizedOrdinal")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b1.Property<string>("ImageUrl");
|
||||||
|
|
||||||
|
b1.Property<string>("Name");
|
||||||
|
|
||||||
|
b1.Property<int>("Type");
|
||||||
|
|
||||||
|
b1.Property<string>("Url");
|
||||||
|
|
||||||
|
b1.HasKey("AuthorId", "__synthesizedOrdinal");
|
||||||
|
|
||||||
|
b1.ToTable("Authors");
|
||||||
|
|
||||||
|
b1
|
||||||
|
.ToJson("SocialMedia")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("AuthorId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("SocialMedia");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Address", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", "Customer")
|
||||||
|
.WithMany("Addresses")
|
||||||
|
.HasForeignKey("CustomerId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Customer");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Contact", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", "Customer")
|
||||||
|
.WithMany("Contacts")
|
||||||
|
.HasForeignKey("CustomerId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Customer");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", b =>
|
||||||
|
{
|
||||||
|
b.OwnsMany("LiteCharms.Features.Models.SocialMedia", "SocialMedia", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<long>("CustomerId");
|
||||||
|
|
||||||
|
b1.Property<int>("__synthesizedOrdinal")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b1.Property<string>("ImageUrl");
|
||||||
|
|
||||||
|
b1.Property<string>("Name");
|
||||||
|
|
||||||
|
b1.Property<int>("Type");
|
||||||
|
|
||||||
|
b1.Property<string>("Url");
|
||||||
|
|
||||||
|
b1.HasKey("CustomerId", "__synthesizedOrdinal");
|
||||||
|
|
||||||
|
b1.ToTable("Customers");
|
||||||
|
|
||||||
|
b1
|
||||||
|
.ToJson("SocialMedia")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("CustomerId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("SocialMedia");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.OrderItem", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", "AuthorBook")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AuthorBookId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", "Order")
|
||||||
|
.WithMany("OrderItems")
|
||||||
|
.HasForeignKey("OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", "ProductPrice")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProductPriceId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("AuthorBook");
|
||||||
|
|
||||||
|
b.Navigation("Order");
|
||||||
|
|
||||||
|
b.Navigation("ProductPrice");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.Shipping", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Customers.Entities.Address", "Address")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AddressId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", "Order")
|
||||||
|
.WithOne("Shipping")
|
||||||
|
.HasForeignKey("LiteCharms.Features.MidrandBooks.Orders.Entities.Shipping", "OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Orders.Entities.ShippingProvider", "ShippingProvider")
|
||||||
|
.WithMany("Shippings")
|
||||||
|
.HasForeignKey("ShippingProviderId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Address");
|
||||||
|
|
||||||
|
b.Navigation("Order");
|
||||||
|
|
||||||
|
b.Navigation("ShippingProvider");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Pages.Entities.BookPage", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", "Book")
|
||||||
|
.WithMany("Pages")
|
||||||
|
.HasForeignKey("AuthorBookId")
|
||||||
|
.OnDelete(DeleteBehavior.NoAction)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.OwnsMany("LiteCharms.Features.Models.PageReference", "References", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<long>("BookPageId");
|
||||||
|
|
||||||
|
b1.Property<int>("__synthesizedOrdinal")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b1.Property<string>("Description");
|
||||||
|
|
||||||
|
b1.Property<string>("Tag");
|
||||||
|
|
||||||
|
b1.Property<string>("Url");
|
||||||
|
|
||||||
|
b1.HasKey("BookPageId", "__synthesizedOrdinal");
|
||||||
|
|
||||||
|
b1.ToTable("BookPages");
|
||||||
|
|
||||||
|
b1
|
||||||
|
.ToJson("References")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("BookPageId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("Book");
|
||||||
|
|
||||||
|
b.Navigation("References");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Payments.Entities.Refund", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", "Order")
|
||||||
|
.WithMany("Refunds")
|
||||||
|
.HasForeignKey("OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Order");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
||||||
|
{
|
||||||
|
b.OwnsOne("LiteCharms.Features.Models.ProductMetadata", "Metadata", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<long>("ProductId");
|
||||||
|
|
||||||
|
b1.Property<string>("CopyrightInfo");
|
||||||
|
|
||||||
|
b1.Property<string>("ManufactureDate");
|
||||||
|
|
||||||
|
b1.Property<string>("Manufacturer");
|
||||||
|
|
||||||
|
b1.Property<string>("SerialNumber");
|
||||||
|
|
||||||
|
b1.HasKey("ProductId");
|
||||||
|
|
||||||
|
b1.ToTable("Products");
|
||||||
|
|
||||||
|
b1
|
||||||
|
.ToJson("Metadata")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("ProductId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("Metadata");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", "Product")
|
||||||
|
.WithMany("Prices")
|
||||||
|
.HasForeignKey("ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Product");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Pages");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Books");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Addresses");
|
||||||
|
|
||||||
|
b.Navigation("Contacts");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("OrderItems");
|
||||||
|
|
||||||
|
b.Navigation("Refunds");
|
||||||
|
|
||||||
|
b.Navigation("Shipping");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.ShippingProvider", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Shippings");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Prices");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Products.Entities;
|
namespace LiteCharms.Features.MidrandBooks.Products.Entities;
|
||||||
|
|
||||||
public class ProductConfiguration : IEntityTypeConfiguration<Product>
|
public sealed class ProductConfiguration : IEntityTypeConfiguration<Product>
|
||||||
{
|
{
|
||||||
public void Configure(EntityTypeBuilder<Product> builder)
|
public void Configure(EntityTypeBuilder<Product> builder)
|
||||||
{
|
{
|
||||||
|
builder.Ignore(p => p.Price);
|
||||||
|
|
||||||
builder.ToTable("Products");
|
builder.ToTable("Products");
|
||||||
|
|
||||||
builder.HasKey(f => f.Id);
|
builder.HasKey(f => f.Id);
|
||||||
@@ -14,9 +16,10 @@ public class ProductConfiguration : IEntityTypeConfiguration<Product>
|
|||||||
builder.Property(f => f.Summary).IsRequired().HasMaxLength(512);
|
builder.Property(f => f.Summary).IsRequired().HasMaxLength(512);
|
||||||
builder.Property(f => f.Description).HasMaxLength(1024);
|
builder.Property(f => f.Description).HasMaxLength(1024);
|
||||||
builder.Property(f => f.ImageUrl).HasMaxLength(1024);
|
builder.Property(f => f.ImageUrl).HasMaxLength(1024);
|
||||||
builder.Property(f => f.ThumbnailUrls).IsRequired(false).HasColumnType("jsonb");
|
|
||||||
builder.Property(f => f.Metadata).IsRequired(false).HasColumnType("jsonb");
|
|
||||||
builder.Property(f => f.Categories).IsRequired(false).HasColumnType("jsonb");
|
|
||||||
builder.Property(f => f.Enabled).HasDefaultValue(false);
|
builder.Property(f => f.Enabled).HasDefaultValue(false);
|
||||||
|
builder.Property(f => f.Categories).IsRequired(false);
|
||||||
|
builder.Property(f => f.ThumbnailUrls).IsRequired(false);
|
||||||
|
|
||||||
|
builder.OwnsOne(f => f.Metadata, b => { b.ToJson(); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
[EntityTypeConfiguration<ProductPriceConfiguration, ProductPrice>]
|
[EntityTypeConfiguration<ProductPriceConfiguration, ProductPrice>]
|
||||||
public class ProductPrice : Models.ProductPrice
|
public class ProductPrice : Models.ProductPrice
|
||||||
{
|
{
|
||||||
public virtual Product Product { get; set; } = new();
|
public virtual Product? Product { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Products.Entities;
|
namespace LiteCharms.Features.MidrandBooks.Products.Entities;
|
||||||
|
|
||||||
public class ProductPriceConfiguration : IEntityTypeConfiguration<ProductPrice>
|
public sealed class ProductPriceConfiguration : IEntityTypeConfiguration<ProductPrice>
|
||||||
{
|
{
|
||||||
public void Configure(EntityTypeBuilder<ProductPrice> builder)
|
public void Configure(EntityTypeBuilder<ProductPrice> builder)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Products.Models;
|
|
||||||
|
|
||||||
public class CreateProductPrice
|
|
||||||
{
|
|
||||||
public long ProductId { get; set; }
|
|
||||||
|
|
||||||
public decimal Amount { get; set; }
|
|
||||||
|
|
||||||
public decimal Discount { get; set; }
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Products.Models;
|
namespace LiteCharms.Features.MidrandBooks.Products.Models;
|
||||||
|
|
||||||
public record CreateProduct
|
public sealed record CreateProduct
|
||||||
{
|
{
|
||||||
public required ProductTypes Type { get; set; }
|
public required ProductTypes Type { get; set; }
|
||||||
|
|
||||||
@@ -20,3 +20,10 @@ public record CreateProduct
|
|||||||
|
|
||||||
public ProductMetadata? Metadata { get; set; }
|
public ProductMetadata? Metadata { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class CreateProductPrice
|
||||||
|
{
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
|
||||||
|
public decimal Discount { get; set; }
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ using LiteCharms.Features.Models;
|
|||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Products;
|
namespace LiteCharms.Features.MidrandBooks.Products;
|
||||||
|
|
||||||
public class ProductService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
||||||
{
|
{
|
||||||
public async ValueTask<Result> UpdateProductPriceStatusAsync(long productPriceId, bool isEnabled, CancellationToken cancellationToken = default)
|
public async ValueTask<Result> UpdateProductPriceStatusAsync(long productPriceId, bool isEnabled, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
@@ -14,17 +14,15 @@ public class ProductService(IDbContextFactory<MidrandBooksDbContext> contextFact
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var productPrice = await context.Prices.FirstOrDefaultAsync(p => p.Id == productPriceId, cancellationToken);
|
var rowsUpdated = await context.Prices
|
||||||
|
.Where(p => p.Id == productPriceId)
|
||||||
|
.ExecuteUpdateAsync(setters => setters
|
||||||
|
.SetProperty(p => p.Enabled, isEnabled)
|
||||||
|
.SetProperty(p => p.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||||
|
|
||||||
if (productPrice is null)
|
return rowsUpdated > 0
|
||||||
return Result.Fail(new Error($"Product price with ID {productPriceId} not found"));
|
|
||||||
|
|
||||||
productPrice.UpdatedAt = DateTime.UtcNow;
|
|
||||||
productPrice.Enabled = isEnabled;
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail(new Error($"Failed to change status of product price with ID {productPriceId}"));
|
: Result.Fail(new Error($"Product price with ID {productPriceId} not found"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -38,17 +36,15 @@ public class ProductService(IDbContextFactory<MidrandBooksDbContext> contextFact
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var product = await context.Products.FirstOrDefaultAsync(p => p.Id == productId, cancellationToken);
|
var rowsUpdated = await context.Products
|
||||||
|
.Where(p => p.Id == productId)
|
||||||
|
.ExecuteUpdateAsync(setters => setters
|
||||||
|
.SetProperty(p => p.Enabled, isEnabled)
|
||||||
|
.SetProperty(p => p.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||||
|
|
||||||
if (product is null)
|
return rowsUpdated > 0
|
||||||
return Result.Fail(new Error($"Product with ID {productId} not found"));
|
|
||||||
|
|
||||||
product.UpdatedAt = DateTime.UtcNow;
|
|
||||||
product.Enabled = isEnabled;
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail(new Error($"Failed to change status of product with ID {productId}"));
|
: Result.Fail(new Error($"Product with ID {productId} not found"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -64,17 +60,22 @@ public class ProductService(IDbContextFactory<MidrandBooksDbContext> contextFact
|
|||||||
|
|
||||||
var query = context.Products.AsQueryable();
|
var query = context.Products.AsQueryable();
|
||||||
|
|
||||||
|
var cultureInfo = CultureInfo.InvariantCulture;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(filter.Name))
|
||||||
|
query = query.Where(p => EF.Functions.ILike(p.Name!, $"%{filter.Name}%"));
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(filter.Title))
|
if (!string.IsNullOrWhiteSpace(filter.Title))
|
||||||
query = query.Where(p => p.Name!.Contains(filter.Title));
|
query = query.Where(p => EF.Functions.ILike(p.Name!, $"%{filter.Title}%"));
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(filter.Category))
|
if (!string.IsNullOrWhiteSpace(filter.Category))
|
||||||
query = query.Where(p => p.Categories!.Any(c => c == filter.Category));
|
query = query.Where(p => p.Categories.Contains(filter.Category));
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(filter.Manufacturer))
|
if (!string.IsNullOrWhiteSpace(filter.Manufacturer))
|
||||||
query = query.Where(p => p.Metadata!.Manufacturer == filter.Manufacturer);
|
query = query.Where(p => EF.Functions.ILike(p.Metadata!.Manufacturer!, $"%{filter.Manufacturer}%"));
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(filter.SerialNumber))
|
if (!string.IsNullOrWhiteSpace(filter.SerialNumber))
|
||||||
query = query.Where(p => p.Metadata!.SerialNumber == filter.SerialNumber);
|
query = query.Where(p => EF.Functions.ILike(p.Metadata!.SerialNumber!, $"%{filter.SerialNumber}%"));
|
||||||
|
|
||||||
if (filter.MinPrice > 0)
|
if (filter.MinPrice > 0)
|
||||||
query = query.Where(p => p.Prices!.Any(pr => pr.Amount >= filter.MinPrice && pr.Amount <= filter.MaxPrice));
|
query = query.Where(p => p.Prices!.Any(pr => pr.Amount >= filter.MinPrice && pr.Amount <= filter.MaxPrice));
|
||||||
@@ -165,7 +166,7 @@ public class ProductService(IDbContextFactory<MidrandBooksDbContext> contextFact
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<Result<ProductPrice[]>> GetProductPriceAsync(long productId, CancellationToken cancellationToken = default)
|
public async ValueTask<Result<ProductPrice>> GetProductPriceAsync(long productId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -175,16 +176,16 @@ public class ProductService(IDbContextFactory<MidrandBooksDbContext> contextFact
|
|||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.OrderByDescending(p => p.CreatedAt)
|
.OrderByDescending(p => p.CreatedAt)
|
||||||
.ThenBy(p => p.UpdatedAt)
|
.ThenBy(p => p.UpdatedAt)
|
||||||
.FirstOrDefaultAsync(p => p.ProductId == productId, cancellationToken);
|
.FirstOrDefaultAsync(p => p.ProductId == productId && p.Enabled, cancellationToken);
|
||||||
|
|
||||||
return product is not null
|
return product is not null
|
||||||
? Result.Ok(new[] { product.ToModel() })
|
? Result.Ok(product.ToModel())
|
||||||
: Result.Fail<ProductPrice[]>(new Error($"No price found for product with ID {productId}"));
|
: Result.Fail<ProductPrice>(new Error($"No price found for product with ID {productId}"));
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return Result.Fail<ProductPrice[]>(new Error(ex.Message).CausedBy(ex));
|
return Result.Fail<ProductPrice>(new Error(ex.Message).CausedBy(ex));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,8 +229,8 @@ public class ProductService(IDbContextFactory<MidrandBooksDbContext> contextFact
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var fromDate = range.From.ToDateTime(TimeOnly.MinValue);
|
var fromDate = range.From.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc);
|
||||||
var toDate = range.To.ToDateTime(TimeOnly.MaxValue);
|
var toDate = range.To.ToDateTime(TimeOnly.MaxValue, DateTimeKind.Utc);
|
||||||
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
@@ -244,7 +245,7 @@ public class ProductService(IDbContextFactory<MidrandBooksDbContext> contextFact
|
|||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
.ToArrayAsync(cancellationToken);
|
.ToArrayAsync(cancellationToken);
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
return products?.Length > 0
|
||||||
? Result.Ok(products.Select(p => p.ToModel()).ToArray())
|
? Result.Ok(products.Select(p => p.ToModel()).ToArray())
|
||||||
: Result.Fail<Product[]>(new Error("Failed to retrieve products."));
|
: Result.Fail<Product[]>(new Error("Failed to retrieve products."));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"Email": {
|
|
||||||
"Credentials": {
|
|
||||||
"Username": "shop@litecharms.co.za"
|
|
||||||
},
|
|
||||||
"Port": 465,
|
|
||||||
"Host": "mail.litecharms.co.za",
|
|
||||||
"UseSsl": true
|
|
||||||
},
|
|
||||||
"Monitoring": {
|
|
||||||
"ApiKey": "",
|
|
||||||
"Address": "http://aspire-dashboard-service.aspire.svc.cluster.local:18889",
|
|
||||||
"ServiceName": "MidrandBooks"
|
|
||||||
},
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*"
|
|
||||||
}
|
|
||||||
Generated
+1
@@ -1,5 +1,6 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using System;
|
using System;
|
||||||
|
using LiteCharms.Features.Models;
|
||||||
using LiteCharms.Features.TechShop.Postgres;
|
using LiteCharms.Features.TechShop.Postgres;
|
||||||
using LiteCharms.Features.TechShop.Products.Models;
|
using LiteCharms.Features.TechShop.Products.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|||||||
+1
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using LiteCharms.Features.Models;
|
||||||
using LiteCharms.Features.TechShop.Products.Models;
|
using LiteCharms.Features.TechShop.Products.Models;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using System;
|
using System;
|
||||||
|
using LiteCharms.Features.Models;
|
||||||
using LiteCharms.Features.TechShop.Postgres;
|
using LiteCharms.Features.TechShop.Postgres;
|
||||||
using LiteCharms.Features.TechShop.Products.Models;
|
using LiteCharms.Features.TechShop.Products.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace LiteCharms.Features.TechShop.Products.Models;
|
using LiteCharms.Features.Models;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.TechShop.Products.Models;
|
||||||
|
|
||||||
public record CreateProduct
|
public record CreateProduct
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using LiteCharms.Features.TechShop.Extensions;
|
using LiteCharms.Features.Models;
|
||||||
|
using LiteCharms.Features.TechShop.Extensions;
|
||||||
using LiteCharms.Features.TechShop.Postgres;
|
using LiteCharms.Features.TechShop.Postgres;
|
||||||
using LiteCharms.Features.TechShop.Products.Models;
|
using LiteCharms.Features.TechShop.Products.Models;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ public abstract class EventBase
|
|||||||
{
|
{
|
||||||
public Guid Id { get; set; } = Guid.CreateVersion7();
|
public Guid Id { get; set; } = Guid.CreateVersion7();
|
||||||
|
|
||||||
public DateTimeOffset EnqueueAt { get; set; } = SouthAfricanTimeZone.UtcNow();
|
public DateTimeOffset EnqueueAt { get; set; } = (DateTimeOffset)SouthAfricanTimeZone.UtcNow();
|
||||||
|
|
||||||
public string CorrelationId { get; set; } = Guid.CreateVersion7().ToString();
|
public string CorrelationId { get; set; } = Guid.CreateVersion7().ToString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.Email.Configuration;
|
namespace LiteCharms.Features.Email.Configuration;
|
||||||
|
|
||||||
public class Account
|
public sealed class Account
|
||||||
{
|
{
|
||||||
public string? Username { get; set; }
|
public string? Username { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.Email.Configuration;
|
namespace LiteCharms.Features.Email.Configuration;
|
||||||
|
|
||||||
public class SmtpSettings
|
public sealed class SmtpSettings
|
||||||
{
|
{
|
||||||
public Account? Credentials { get; set; }
|
public Account? Credentials { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using LiteCharms.Features.Email.Models;
|
|||||||
|
|
||||||
namespace LiteCharms.Features.Email;
|
namespace LiteCharms.Features.Email;
|
||||||
|
|
||||||
public class EmailService(IOptions<SmtpSettings> options) : IDisposable
|
public sealed class EmailService(IOptions<SmtpSettings> options) : IDisposable
|
||||||
{
|
{
|
||||||
private readonly SmtpSettings settings = options.Value;
|
private readonly SmtpSettings settings = options.Value;
|
||||||
private readonly SmtpClient client = new();
|
private readonly SmtpClient client = new();
|
||||||
@@ -16,6 +16,7 @@ public class EmailService(IOptions<SmtpSettings> options) : IDisposable
|
|||||||
public async ValueTask<Result<Response>> SendEmailAsync(Message message, CancellationToken cancellationToken = default)
|
public async ValueTask<Result<Response>> SendEmailAsync(Message message, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
using var activity = EmailTelemetry.Source.StartActivity("Email Send");
|
using var activity = EmailTelemetry.Source.StartActivity("Email Send");
|
||||||
|
|
||||||
activity?.SetTag("email.recipient", message.Recipient?.Address);
|
activity?.SetTag("email.recipient", message.Recipient?.Address);
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -27,21 +28,7 @@ public class EmailService(IOptions<SmtpSettings> options) : IDisposable
|
|||||||
return Result.Fail<Response>("Smtp service is disconnected.");
|
return Result.Fail<Response>("Smtp service is disconnected.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var email = new MimeMessage();
|
var email = ConstructEmail(message, cancellationToken);
|
||||||
email.From.Add(new MailboxAddress(message.Sender!.Name, message.Sender.Address!));
|
|
||||||
email.To.Add(new MailboxAddress(message.Recipient!.Name, message.Recipient!.Address!));
|
|
||||||
email.Subject = message.Subject!;
|
|
||||||
|
|
||||||
var bodyBuilder = new BodyBuilder();
|
|
||||||
|
|
||||||
if (message.Body!.Properties.HasAttachments)
|
|
||||||
foreach (var attachment in message.Body?.Attachments!)
|
|
||||||
bodyBuilder.Attachments.Add(attachment.Name!, attachment.FileStream!, cancellationToken);
|
|
||||||
|
|
||||||
if (!message.Body.Properties.IsHtml) bodyBuilder.TextBody = message.Body.Message;
|
|
||||||
if (message.Body.Properties.IsHtml) bodyBuilder.HtmlBody = message.Body.Message;
|
|
||||||
|
|
||||||
email.Body = bodyBuilder.ToMessageBody();
|
|
||||||
|
|
||||||
var response = await client.SendAsync(email, cancellationToken);
|
var response = await client.SendAsync(email, cancellationToken);
|
||||||
|
|
||||||
@@ -69,21 +56,9 @@ public class EmailService(IOptions<SmtpSettings> options) : IDisposable
|
|||||||
|
|
||||||
await DisconnectAsync(cancellationToken);
|
await DisconnectAsync(cancellationToken);
|
||||||
|
|
||||||
if (response.Contains("421"))
|
var failCheckResult = HandleNegativeResponse(response);
|
||||||
{
|
|
||||||
Status = EmailStatuses.TooManyConnections;
|
|
||||||
|
|
||||||
return Result.Fail<Response>(response);
|
if (failCheckResult.IsFailed) return failCheckResult;
|
||||||
}
|
|
||||||
|
|
||||||
if (response.Contains("451"))
|
|
||||||
{
|
|
||||||
Status = EmailStatuses.ConnectionAborted;
|
|
||||||
|
|
||||||
return Result.Fail<Response>(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
EmailTelemetry.EmailsFailed.Add(1, new TagList { { "error_message", response } });
|
|
||||||
|
|
||||||
Status = EmailStatuses.Disconnected;
|
Status = EmailStatuses.Disconnected;
|
||||||
|
|
||||||
@@ -100,6 +75,48 @@ public class EmailService(IOptions<SmtpSettings> options) : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static MimeMessage ConstructEmail(Message message, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var email = new MimeMessage();
|
||||||
|
email.From.Add(new MailboxAddress(message.Sender!.Name, message.Sender.Address!));
|
||||||
|
email.To.Add(new MailboxAddress(message.Recipient!.Name, message.Recipient!.Address!));
|
||||||
|
email.Subject = message.Subject!;
|
||||||
|
|
||||||
|
var bodyBuilder = new BodyBuilder();
|
||||||
|
|
||||||
|
if (message.Body!.Properties.HasAttachments)
|
||||||
|
foreach (var attachment in message.Body?.Attachments!)
|
||||||
|
bodyBuilder.Attachments.Add(attachment.Name!, attachment.FileStream!, cancellationToken);
|
||||||
|
|
||||||
|
if (!message.Body.Properties.IsHtml) bodyBuilder.TextBody = message.Body.Message;
|
||||||
|
if (message.Body.Properties.IsHtml) bodyBuilder.HtmlBody = message.Body.Message;
|
||||||
|
|
||||||
|
email.Body = bodyBuilder.ToMessageBody();
|
||||||
|
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result<Response> HandleNegativeResponse(string response)
|
||||||
|
{
|
||||||
|
if (response.Contains("421", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
Status = EmailStatuses.TooManyConnections;
|
||||||
|
|
||||||
|
return Result.Fail<Response>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.Contains("451", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
Status = EmailStatuses.ConnectionAborted;
|
||||||
|
|
||||||
|
return Result.Fail<Response>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
EmailTelemetry.EmailsFailed.Add(1, new TagList { { "error_message", response } });
|
||||||
|
|
||||||
|
return Result.Fail<Response>(response);
|
||||||
|
}
|
||||||
|
|
||||||
public async ValueTask<Result<Response>> ConnectAsync(CancellationToken cancellationToken = default)
|
public async ValueTask<Result<Response>> ConnectAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
using var activity = EmailTelemetry.Source.StartActivity("Email Connect");
|
using var activity = EmailTelemetry.Source.StartActivity("Email Connect");
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.Email.Models;
|
namespace LiteCharms.Features.Email.Models;
|
||||||
|
|
||||||
public class Attachment
|
public sealed class Attachment
|
||||||
{
|
{
|
||||||
public string? Name { get; set; }
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.Email.Models;
|
namespace LiteCharms.Features.Email.Models;
|
||||||
|
|
||||||
public class Body : IDisposable
|
public sealed class Body : IDisposable
|
||||||
{
|
{
|
||||||
public string? Message { get; set; }
|
public string? Message { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.Email.Models;
|
namespace LiteCharms.Features.Email.Models;
|
||||||
|
|
||||||
public class BodyProperties
|
public sealed class BodyProperties
|
||||||
{
|
{
|
||||||
public bool IsHtml { get; set; }
|
public bool IsHtml { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.Email.Models;
|
namespace LiteCharms.Features.Email.Models;
|
||||||
|
|
||||||
public class Message : IDisposable
|
public sealed class Message : IDisposable
|
||||||
{
|
{
|
||||||
public Party? Sender { get; set; }
|
public Party? Sender { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.Email.Models;
|
namespace LiteCharms.Features.Email.Models;
|
||||||
|
|
||||||
public class Party
|
public sealed class Party
|
||||||
{
|
{
|
||||||
public string? Name { get; set; }
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.Email.Models;
|
namespace LiteCharms.Features.Email.Models;
|
||||||
|
|
||||||
public class Response
|
public sealed class Response
|
||||||
{
|
{
|
||||||
public int Code { get; set; }
|
public int Code { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,8 @@ public enum SocialMediaTypes : int
|
|||||||
YouTube = 5,
|
YouTube = 5,
|
||||||
Pinterest = 6,
|
Pinterest = 6,
|
||||||
Reddit = 7,
|
Reddit = 7,
|
||||||
Tumblr = 8
|
Tumblr = 8,
|
||||||
|
GitHub = 9
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum EmailStatuses : int
|
public enum EmailStatuses : int
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
public static class Hash
|
public static class Hash
|
||||||
{
|
{
|
||||||
public static Func<string?, string?> StringToSha256Hash = (input) =>
|
public static readonly Func<string?, string?> StringToSha256Hash = (input) =>
|
||||||
Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(input!)));
|
Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(input!)));
|
||||||
|
|
||||||
public static Func<Stream, string?> StreamToSha256Hash = (stream) =>
|
public static readonly Func<Stream, string?> StreamToSha256Hash = (stream) =>
|
||||||
Convert.ToHexString(SHA256.HashData(stream));
|
Convert.ToHexString(SHA256.HashData(stream));
|
||||||
|
|
||||||
public static Func<byte[], string?> BytesToSha256Hash = (bytes) =>
|
public static readonly Func<byte[], string?> BytesToSha256Hash = (bytes) =>
|
||||||
Convert.ToHexString(SHA256.HashData(bytes));
|
Convert.ToHexString(SHA256.HashData(bytes));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public static class Quartz
|
|||||||
config.UseDefaultThreadPool(options => options.MaxConcurrency = 1);
|
config.UseDefaultThreadPool(options => options.MaxConcurrency = 1);
|
||||||
config.UseTimeZoneConverter();
|
config.UseTimeZoneConverter();
|
||||||
|
|
||||||
config.SetProperty("quartz.jobStore.misfireThreshold", TimeSpan.FromMinutes(2).TotalMilliseconds.ToString());
|
config.SetProperty("quartz.jobStore.misfireThreshold", TimeSpan.FromMinutes(2).TotalMilliseconds.ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
config.UsePersistentStore(storage =>
|
config.UsePersistentStore(storage =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public static class Timezones
|
|||||||
? new DateTimeOffset(sourceDateAdjusted.Ticks, SouthAfricanTimeZone.BaseUtcOffset).LocaliseDateTimeOffset(SouthAfricanTimeZone.BaseUtcOffset)
|
? new DateTimeOffset(sourceDateAdjusted.Ticks, SouthAfricanTimeZone.BaseUtcOffset).LocaliseDateTimeOffset(SouthAfricanTimeZone.BaseUtcOffset)
|
||||||
: new DateTimeOffset(sourceDateAdjusted.Ticks, timezone!.BaseUtcOffset).LocaliseDateTimeOffset(timezone.BaseUtcOffset);
|
: new DateTimeOffset(sourceDateAdjusted.Ticks, timezone!.BaseUtcOffset).LocaliseDateTimeOffset(timezone.BaseUtcOffset);
|
||||||
|
|
||||||
return DateTimeOffset.Parse(localised!);
|
return DateTimeOffset.Parse(localised!, CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DateTime UtcNow(this TimeZoneInfo timezone) => ToDateTimeWithTimeZone(DateTime.Now, timezone).UtcDateTime;
|
public static DateTime UtcNow(this TimeZoneInfo timezone) => ToDateTimeWithTimeZone(DateTime.Now, timezone).UtcDateTime;
|
||||||
|
|||||||
@@ -31,6 +31,10 @@
|
|||||||
|
|
||||||
<!-- Quartz Scheduler-->
|
<!-- Quartz Scheduler-->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Meziantou.Analyzer" Version="3.0.96">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="OpenTelemetry" Version="1.15.3" />
|
<PackageReference Include="OpenTelemetry" Version="1.15.3" />
|
||||||
<PackageReference Include="Quartz" Version="3.18.1" />
|
<PackageReference Include="Quartz" Version="3.18.1" />
|
||||||
<PackageReference Include="Quartz.Plugins" Version="3.18.1" />
|
<PackageReference Include="Quartz.Plugins" Version="3.18.1" />
|
||||||
@@ -142,6 +146,7 @@
|
|||||||
|
|
||||||
<!-- Shared Usings -->
|
<!-- Shared Usings -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Using Include="System.Globalization" />
|
||||||
<Using Include="Microsoft.AspNetCore.Builder" />
|
<Using Include="Microsoft.AspNetCore.Builder" />
|
||||||
<Using Include="Microsoft.Extensions.Hosting" />
|
<Using Include="Microsoft.Extensions.Hosting" />
|
||||||
<Using Include="System.Text" />
|
<Using Include="System.Text" />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.Models;
|
namespace LiteCharms.Features.Models;
|
||||||
|
|
||||||
public class DateRange
|
public sealed class DateRange
|
||||||
{
|
{
|
||||||
public DateOnly From { get; set; }
|
public DateOnly From { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.Models;
|
namespace LiteCharms.Features.Models;
|
||||||
|
|
||||||
public class PageReference
|
public sealed class PageReference
|
||||||
{
|
{
|
||||||
public string? Tag { get; set; }
|
public string? Tag { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.Models;
|
namespace LiteCharms.Features.Models;
|
||||||
|
|
||||||
public class ProductFilter
|
public sealed class ProductFilter
|
||||||
{
|
{
|
||||||
public string? Name { get; set; }
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.Models;
|
namespace LiteCharms.Features.Models;
|
||||||
|
|
||||||
public class ProductMetadata
|
public sealed class ProductMetadata
|
||||||
{
|
{
|
||||||
public string? Manufacturer { get; set; }
|
public string? Manufacturer { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
namespace LiteCharms.Features.Models;
|
namespace LiteCharms.Features.Models;
|
||||||
|
|
||||||
public class SocialMedia
|
public sealed class SocialMedia
|
||||||
{
|
{
|
||||||
public SocialMediaTypes Type { get; set; }
|
public SocialMediaTypes Type { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using LiteCharms.Features.Quartz.Abstractions;
|
|||||||
|
|
||||||
namespace LiteCharms.Features.Quartz;
|
namespace LiteCharms.Features.Quartz;
|
||||||
|
|
||||||
public class JobOrchestrator(ISchedulerFactory schedulerFactory) : IJobOrchestrator
|
public sealed class JobOrchestrator(ISchedulerFactory schedulerFactory) : IJobOrchestrator
|
||||||
{
|
{
|
||||||
public async Task SendAsync<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
|
public async Task SendAsync<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
|
||||||
where TNotification : IEvent
|
where TNotification : IEvent
|
||||||
@@ -11,7 +11,7 @@ public class JobOrchestrator(ISchedulerFactory schedulerFactory) : IJobOrchestra
|
|||||||
var chainedJobGroup = "onetime-jobs";
|
var chainedJobGroup = "onetime-jobs";
|
||||||
|
|
||||||
var scheduler = await schedulerFactory.GetScheduler(cancellationToken);
|
var scheduler = await schedulerFactory.GetScheduler(cancellationToken);
|
||||||
var jobKey = new JobKey($"{notification.Name.ToLower()}-{notification.CorrelationId.ToLower()}", chainedJobGroup);
|
var jobKey = new JobKey($"{notification.Name.ToLower(CultureInfo.InvariantCulture)}-{notification.CorrelationId.ToLower(CultureInfo.InvariantCulture)}", chainedJobGroup);
|
||||||
var triggerKey = new TriggerKey($"{jobKey.Name}-trigger", chainedJobGroup);
|
var triggerKey = new TriggerKey($"{jobKey.Name}-trigger", chainedJobGroup);
|
||||||
|
|
||||||
var job = JobBuilder.Create<MediatorJob<TNotification>>()
|
var job = JobBuilder.Create<MediatorJob<TNotification>>()
|
||||||
@@ -35,7 +35,7 @@ public class JobOrchestrator(ISchedulerFactory schedulerFactory) : IJobOrchestra
|
|||||||
var chainedJobGroup = "scheduled-jobs";
|
var chainedJobGroup = "scheduled-jobs";
|
||||||
|
|
||||||
var scheduler = await schedulerFactory.GetScheduler(cancellationToken);
|
var scheduler = await schedulerFactory.GetScheduler(cancellationToken);
|
||||||
var jobKey = new JobKey($"{notification.Name.ToLower()}", chainedJobGroup);
|
var jobKey = new JobKey($"{notification.Name.ToLower(CultureInfo.InvariantCulture)}", chainedJobGroup);
|
||||||
var triggerKey = new TriggerKey($"{jobKey.Name}-trigger", chainedJobGroup);
|
var triggerKey = new TriggerKey($"{jobKey.Name}-trigger", chainedJobGroup);
|
||||||
|
|
||||||
var job = JobBuilder.Create<MediatorJob<TNotification>>()
|
var job = JobBuilder.Create<MediatorJob<TNotification>>()
|
||||||
@@ -53,7 +53,7 @@ public class JobOrchestrator(ISchedulerFactory schedulerFactory) : IJobOrchestra
|
|||||||
.WithDescription($"Scheduled via Main Job at {now:g}")
|
.WithDescription($"Scheduled via Main Job at {now:g}")
|
||||||
.WithCronSchedule(cronExpression, cron => cron
|
.WithCronSchedule(cronExpression, cron => cron
|
||||||
.WithMisfireHandlingInstructionIgnoreMisfires())
|
.WithMisfireHandlingInstructionIgnoreMisfires())
|
||||||
.StartAt(now)
|
.StartAt((DateTimeOffset)now)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
await scheduler.AddJob(job, replace: true, cancellationToken);
|
await scheduler.AddJob(job, replace: true, cancellationToken);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using LiteCharms.Features.Mediator;
|
|||||||
namespace LiteCharms.Features.Quartz;
|
namespace LiteCharms.Features.Quartz;
|
||||||
|
|
||||||
[DisallowConcurrentExecution]
|
[DisallowConcurrentExecution]
|
||||||
public class MediatorJob<TNotification>(IMediator mediator) : IJob where TNotification : IEvent
|
public sealed class MediatorJob<TNotification>(IMediator mediator) : IJob where TNotification : IEvent
|
||||||
{
|
{
|
||||||
public async Task Execute(IJobExecutionContext context)
|
public async Task Execute(IJobExecutionContext context)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace LiteCharms.Features.Quartz;
|
namespace LiteCharms.Features.Quartz;
|
||||||
|
|
||||||
public class RetryJobListener : IJobListener
|
public sealed class RetryJobListener : IJobListener
|
||||||
{
|
{
|
||||||
public string Name => "RetryJobListener";
|
public string Name => "RetryJobListener";
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public abstract class S3ServiceBase(IAmazonS3 amazonS3)
|
|||||||
if(string.IsNullOrWhiteSpace(fileHash))
|
if(string.IsNullOrWhiteSpace(fileHash))
|
||||||
return Result.Fail<string>("Failed to compute file hash.");
|
return Result.Fail<string>("Failed to compute file hash.");
|
||||||
|
|
||||||
var fileKey = $"{fileHash.ToLower()}{Path.GetExtension(fileName)}";
|
var fileKey = $"{fileHash.ToLower(CultureInfo.InvariantCulture)}{Path.GetExtension(fileName)}";
|
||||||
|
|
||||||
var putRequest = new PutObjectRequest
|
var putRequest = new PutObjectRequest
|
||||||
{
|
{
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user