Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c2ce74032 |
@@ -1,8 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Seed.Configuration;
|
|
||||||
|
|
||||||
public class CdnSettings
|
|
||||||
{
|
|
||||||
public string? BaseCdn { get; set; }
|
|
||||||
|
|
||||||
public string[]? BookCovers { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
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.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
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();
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Tests.Common;
|
|
||||||
|
|
||||||
public class IntegrationFactAttribute : FactAttribute
|
|
||||||
{
|
|
||||||
public IntegrationFactAttribute()
|
|
||||||
{
|
|
||||||
if(!Debugger.IsAttached)
|
|
||||||
Skip = "This test requires the debugger to be attached.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-7
@@ -1,8 +1,7 @@
|
|||||||
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.Common;
|
namespace LiteCharms.Features.MidrandBooks.Tests;
|
||||||
|
|
||||||
public class Fixture : IDisposable
|
public class Fixture : IDisposable
|
||||||
{
|
{
|
||||||
@@ -12,10 +11,6 @@ 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()
|
||||||
@@ -28,12 +23,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>();
|
||||||
@@ -39,8 +39,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Using Include="System.Text.Json" />
|
|
||||||
<Using Include="System.Diagnostics" />
|
|
||||||
<Using Include="Xunit" />
|
<Using Include="Xunit" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -1,196 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
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()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
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,6 +5,12 @@
|
|||||||
"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"
|
||||||
|
|||||||
@@ -7,21 +7,23 @@ namespace LiteCharms.Features.MidrandBooks.AuthorBooks;
|
|||||||
|
|
||||||
public sealed 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 = default)
|
public async ValueTask<Result> UpdateBookStatusAsync(long bookId, bool isEnabled, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var rowsUpdated = await context.Books
|
var book = await context.Books.FirstOrDefaultAsync(b => b.Id == bookId, cancellationToken);
|
||||||
.Where(b => b.Id == bookId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(b => b.Enabled, isEnabled)
|
|
||||||
.SetProperty(b => b.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
if (book is null)
|
||||||
|
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($"Book with ID {bookId} not found"));
|
: Result.Fail(new Error($"Failed to change status of book with ID {bookId}"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -43,9 +45,8 @@ public sealed class BooksService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
|
|
||||||
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
|
||||||
@@ -67,8 +68,7 @@ public sealed class BooksService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
var book = await context.Books
|
var book = await context.Books
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(b => b.Author)
|
.Include(b => b.Author)
|
||||||
.Include(b => b.Product)
|
.Include(b => b.Product!.Price)
|
||||||
.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 sealed class BooksService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
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,8 +94,7 @@ public sealed class BooksService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
var books = await context.Books
|
var books = await context.Books
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(b => b.Author)
|
.Include(b => b.Author)
|
||||||
.Include(b => b.Product)
|
.Include(b => b.Product!.Price)
|
||||||
.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);
|
||||||
@@ -119,10 +118,9 @@ public sealed class BooksService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
var books = await context.Books
|
var books = await context.Books
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(b => b.Author)
|
.Include(b => b.Author)
|
||||||
.Include(b => b.Product)
|
.Include(b => b.Product!.Price)
|
||||||
.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; }
|
public virtual Author Author { get; set; } = new();
|
||||||
|
|
||||||
public new virtual Product? Product { get; set; }
|
public new virtual Product? Product { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
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;
|
||||||
@@ -8,21 +9,53 @@ namespace LiteCharms.Features.MidrandBooks.Authors;
|
|||||||
|
|
||||||
public sealed class AuthorService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
public sealed class AuthorService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
||||||
{
|
{
|
||||||
public async ValueTask<Result> UpdateAuthorStatusAsync(long authorId, bool isEnabled, CancellationToken cancellationToken = default)
|
public async ValueTask<Result<AuthorBook[]>> GetAuthorBooksAsync(long authorId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var rowsUpdated = await context.Authors
|
var author = await context.Authors.FirstOrDefaultAsync(a => a.Id == authorId, cancellationToken);
|
||||||
.Where(a => a.Id == authorId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(a => a.Enabled, isEnabled)
|
|
||||||
.SetProperty(a => a.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
if (author is null)
|
||||||
|
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($"Author with ID {authorId} not found"));
|
: Result.Fail(new Error($"Failed to change status of author with ID {authorId}"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -48,12 +81,12 @@ public sealed class AuthorService(IDbContextFactory<MidrandBooksDbContext> conte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<Result<Author[]>> GetAuthorsAsync(DateRange range, CancellationToken cancellationToken = default)
|
public async ValueTask<Result<Author[]>> GetAuthors(DateRange range, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var fromDate = range.From.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc);
|
var fromDate = range.From.ToDateTime(TimeOnly.MinValue);
|
||||||
var toDate = range.To.ToDateTime(TimeOnly.MaxValue, DateTimeKind.Utc);
|
var toDate = range.To.ToDateTime(TimeOnly.MaxValue);
|
||||||
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
@@ -74,12 +107,18 @@ public sealed class AuthorService(IDbContextFactory<MidrandBooksDbContext> conte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<Result> UpdateAuthorAsync(long authorId, UpdateAuthor request, CancellationToken cancellationToken = default)
|
public async ValueTask<Result> UpdateAuthorAsync(long authorId, UpdateAuthor request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
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)
|
||||||
@@ -112,12 +151,12 @@ public sealed class AuthorService(IDbContextFactory<MidrandBooksDbContext> conte
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
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
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ public sealed 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 ICollection<SocialMedia>? SocialMedia { get; set; }
|
public SocialMedia[]? SocialMedia { get; set; }
|
||||||
|
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public sealed class CustomerService(IDbContextFactory<MidrandBooksDbContext> con
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
if (await context.Customers.AnyAsync(c => EF.Functions.ILike(c.Email!, $"%{request.Email}%"), cancellationToken))
|
if (await context.Customers.AnyAsync(c => c.Email!.Equals(request.Email, StringComparison.OrdinalIgnoreCase), 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 sealed class CustomerService(IDbContextFactory<MidrandBooksDbContext> con
|
|||||||
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 && EF.Functions.ILike(cc.Email!, $"%{request.Email}%"), cancellationToken))
|
if (await context.Contacts.AnyAsync(cc => cc.CustomerId == customerId && cc.Email!.Equals(request.Email, StringComparison.OrdinalIgnoreCase), 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,19 +139,21 @@ public sealed class CustomerService(IDbContextFactory<MidrandBooksDbContext> con
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var rowsUpdated = await context.Contacts
|
var contact = await context.Contacts.FirstOrDefaultAsync(cc => cc.Id == contactId, cancellationToken);
|
||||||
.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);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
if (contact is null)
|
||||||
|
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($"Contact with ID '{contactId}' does not exist."));
|
: Result.Fail(new Error("Failed to update customer contact."));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -165,23 +167,25 @@ public sealed class CustomerService(IDbContextFactory<MidrandBooksDbContext> con
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var rowsUpdated = await context.Addresses
|
var address = await context.Addresses.FirstOrDefaultAsync(a => a.Id == addressId, cancellationToken);
|
||||||
.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);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
if (address is null)
|
||||||
|
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($"Address with ID '{addressId}' does not exist."));
|
: Result.Fail(new Error("Failed to update customer address."));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -195,15 +199,17 @@ public sealed class CustomerService(IDbContextFactory<MidrandBooksDbContext> con
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var rowsUpdated = await context.Customers
|
var customer = await context.Customers.FirstOrDefaultAsync(c => c.Id == customerId, cancellationToken);
|
||||||
.Where(c => c.Id == customerId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(c => c.Enabled, enabled)
|
|
||||||
.SetProperty(c => c.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
if (customer is null)
|
||||||
|
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($"Customer with ID '{customerId}' does not exist."));
|
: Result.Fail(new Error("Failed to update customer status."));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -217,16 +223,18 @@ public sealed class CustomerService(IDbContextFactory<MidrandBooksDbContext> con
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var rowsUpdated = await context.Contacts
|
var contact = await context.Contacts.FirstOrDefaultAsync(cc => cc.Id == contactId, cancellationToken);
|
||||||
.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);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
if (contact is null)
|
||||||
|
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($"Contact with ID '{contactId}' does not exist."));
|
: Result.Fail(new Error("Failed to update customer contact status."));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -240,16 +248,18 @@ public sealed class CustomerService(IDbContextFactory<MidrandBooksDbContext> con
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var rowsUpdated = await context.Addresses
|
var address = await context.Addresses.FirstOrDefaultAsync(a => a.Id == addressId, cancellationToken);
|
||||||
.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);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
if (address is null)
|
||||||
|
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($"Address with ID '{addressId}' does not exist."));
|
: Result.Fail(new Error("Failed to update customer address status."));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ public sealed 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 ICollection<SocialMedia>? SocialMedia { get; set; }
|
public SocialMedia[]? SocialMedia { get; set; }
|
||||||
|
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public record CreateCustomer
|
|||||||
|
|
||||||
public string? Phone { get; set; }
|
public string? Phone { get; set; }
|
||||||
|
|
||||||
public ICollection<SocialMedia>? SocialMedia { get; set; }
|
public SocialMedia[]? SocialMedia { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed record UpdateCustomer : CreateCustomer;
|
public sealed record UpdateCustomer : CreateCustomer;
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ public static class Mappers
|
|||||||
Name = entity.Name,
|
Name = entity.Name,
|
||||||
Type = entity.Type,
|
Type = entity.Type,
|
||||||
Price = entity.Price,
|
Price = entity.Price,
|
||||||
Enabled = entity.Enabled,
|
Enabled = entity.Enabled
|
||||||
TrackingUrl = entity.TrackingUrl,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static Shipping ToModel(this Orders.Entities.Shipping entity) => new()
|
public static Shipping ToModel(this Orders.Entities.Shipping entity) => new()
|
||||||
|
|||||||
@@ -8,18 +8,8 @@ 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(dataSource));
|
options.UseNpgsql(configuration.GetConnectionString(MidrandBooksDbConfigName)));
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,19 @@ namespace LiteCharms.Features.MidrandBooks.Extensions;
|
|||||||
|
|
||||||
public static class Shop
|
public static class Shop
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddShopServices(this IServiceCollection services)
|
public static IServiceCollection AddShopServices(this IServiceCollection services, Assembly assembly, ServiceLifetime serviceLifetime)
|
||||||
{
|
{
|
||||||
var serviceType = typeof(IService);
|
var serviceType = typeof(IService);
|
||||||
|
|
||||||
var implementations = Assembly.GetExecutingAssembly().GetTypes()
|
var implementations = assembly.GetTypes()
|
||||||
.Where(t => serviceType.IsAssignableFrom(t) && t.IsClass && !t.IsAbstract);
|
.Where(t => serviceType.IsAssignableFrom(t) && t.IsClass && !t.IsAbstract);
|
||||||
|
|
||||||
foreach (var implementation in implementations)
|
foreach (var implementation in implementations)
|
||||||
services.AddScoped(implementation);
|
{
|
||||||
|
var descriptor = new ServiceDescriptor(serviceType, implementation, serviceLifetime);
|
||||||
|
|
||||||
|
services.Add(descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public sealed class OrderConfiguration : IEntityTypeConfiguration<Order>
|
|||||||
builder.Property(o => o.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
builder.Property(o => o.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
||||||
builder.Property(o => o.UpdatedAt).HasDefaultValueSql("now()");
|
builder.Property(o => o.UpdatedAt).HasDefaultValueSql("now()");
|
||||||
builder.Property(o => o.Status).IsRequired();
|
builder.Property(o => o.Status).IsRequired();
|
||||||
builder.Property(o => o.Total).IsRequired().HasPrecision(18, 2);
|
builder.Property(o => o.Total).IsRequired().HasColumnType("decimal(18,2)");
|
||||||
builder.Property(o => o.Notes).HasMaxLength(1000);
|
builder.Property(o => o.Notes).HasMaxLength(1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||||
|
|
||||||
public sealed record CreateOrder(decimal TotalPrice, string? Notes);
|
public sealed record CreateOrder(long CustomerId, decimal TotalPrice, string? Notes);
|
||||||
|
|
||||||
public sealed record CreateOrderItem(long AuthorBookId, long ProductPriceId, int Quantity);
|
public sealed record CreateOrderItem(long AuthorBookId, long ProductPriceId, int Quantity);
|
||||||
|
|
||||||
public sealed record CreateShipping(long AddressId, long ShippingProviderId, string? TrackingNumber = null);
|
public sealed record CreateShipping(long OrderId, long AddressId, long ShippingProviderId, string? TrackingNumber);
|
||||||
|
|
||||||
public sealed record CreateShippingProvider(ShippingProviderTypes Type, string Name, decimal Price, string TrackingUrl);
|
public sealed record CreateShippingProvider(ShippingProviderTypes Type, string Name, decimal Price, string TrackingUrl);
|
||||||
|
|
||||||
|
|||||||
@@ -49,17 +49,6 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
if (!await context.Prices.AnyAsync(pp => pp.Id == request.ProductPriceId, cancellationToken))
|
if (!await context.Prices.AnyAsync(pp => pp.Id == request.ProductPriceId, cancellationToken))
|
||||||
return Result.Fail<long>("Product price not found.");
|
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
|
var orderItem = context.OrderItems.Add(new Entities.OrderItem
|
||||||
{
|
{
|
||||||
OrderId = orderId,
|
OrderId = orderId,
|
||||||
@@ -89,6 +78,9 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
|
|
||||||
if (!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken))
|
if (!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken))
|
||||||
return Result.Fail("Order not found.");
|
return Result.Fail("Order not found.");
|
||||||
|
|
||||||
|
var existingItems = context.OrderItems.Where(oi => oi.OrderId == orderId);
|
||||||
|
context.OrderItems.RemoveRange(existingItems);
|
||||||
|
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
@@ -98,23 +90,18 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
if (!await context.Prices.AnyAsync(pp => pp.Id == item.ProductPriceId, cancellationToken))
|
if (!await context.Prices.AnyAsync(pp => pp.Id == item.ProductPriceId, cancellationToken))
|
||||||
return Result.Fail($"Product price with ID {item.ProductPriceId} not found.");
|
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);
|
context.OrderItems.Add(new Entities.OrderItem
|
||||||
|
{
|
||||||
if (existingItem is not null)
|
OrderId = orderId,
|
||||||
existingItem.Quantity += item.Quantity;
|
AuthorBookId = item.AuthorBookId,
|
||||||
else
|
ProductPriceId = item.ProductPriceId,
|
||||||
context.OrderItems.Add(new Entities.OrderItem
|
Quantity = item.Quantity
|
||||||
{
|
});
|
||||||
OrderId = orderId,
|
|
||||||
AuthorBookId = item.AuthorBookId,
|
|
||||||
ProductPriceId = item.ProductPriceId,
|
|
||||||
Quantity = item.Quantity
|
|
||||||
});
|
|
||||||
|
|
||||||
await context.SaveChangesAsync(cancellationToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.Ok();
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Failed to add items to order.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -128,13 +115,16 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var rowsDeleted = await context.OrderItems
|
var orderItem = await context.OrderItems.FirstOrDefaultAsync(oi => oi.Id == orderItemId && oi.OrderId == orderId, cancellationToken);
|
||||||
.Where(oi => oi.Id == orderItemId && oi.OrderId == orderId)
|
|
||||||
.ExecuteDeleteAsync(cancellationToken);
|
|
||||||
|
|
||||||
return rowsDeleted > 0
|
if (orderItem is null)
|
||||||
|
return Result.Fail("Order item not found.");
|
||||||
|
|
||||||
|
context.OrderItems.Remove(orderItem);
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail("Order item not found or failed to remove.");
|
: Result.Fail("Failed to remove item from order.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -142,14 +132,15 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<Result> ClearOrderItemsAsync(long orderId, CancellationToken cancellationToken = default)
|
public async ValueTask<Result> ClearOrderItemasAsync(long orderId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var deletedItems = await context.OrderItems.Where(oi => oi.OrderId == orderId)
|
var orderItems = context.OrderItems.Where(oi => oi.OrderId == orderId);
|
||||||
.ExecuteDeleteAsync(cancellationToken);
|
|
||||||
|
context.OrderItems.RemoveRange(orderItems);
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
@@ -208,15 +199,12 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
{
|
{
|
||||||
try
|
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);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var orders = await context.Orders
|
var orders = await context.Orders
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(o => o.CreatedAt >= fromDate && o.CreatedAt <= toDate)
|
.Where(o => o.CreatedAt >= range.From.ToDateTime(TimeOnly.MinValue) && o.CreatedAt <= range.To.ToDateTime(TimeOnly.MaxValue))
|
||||||
.Skip(index).Take(range.MaxRecords)
|
.Skip(index * range.MaxRecords)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
return Result.Ok(orders.Select(o => o.ToModel()).ToArray());
|
return Result.Ok(orders.Select(o => o.ToModel()).ToArray());
|
||||||
@@ -233,15 +221,17 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var rowsUpdated = await context.Orders
|
var order = await context.Orders.FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken);
|
||||||
.Where(o => o.Id == orderId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(o => o.Status, newStatus)
|
|
||||||
.SetProperty(o => o.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
if (order is null)
|
||||||
|
return Result.Fail("Order not found.");
|
||||||
|
|
||||||
|
order.UpdatedAt = DateTime.UtcNow;
|
||||||
|
order.Status = newStatus;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail("Order not found or status update failed.");
|
: Result.Fail("Failed to update order status.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -249,7 +239,7 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<Result<long>> AddShippingToOrderAsync(long orderId, CreateShipping request, CancellationToken cancellationToken = default)
|
public async ValueTask<Result> AddShippingToOrderAsync(long orderId, CreateShipping request, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -292,16 +282,17 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var rowsUpdated = await context.Shippings
|
var shipping = await context.Shippings.FirstOrDefaultAsync(s => s.OrderId == orderId, cancellationToken);
|
||||||
.Where(s => s.OrderId == orderId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(s => s.Status, newStatus)
|
|
||||||
.SetProperty(s => s.UpdatedAt, DateTime.UtcNow),
|
|
||||||
cancellationToken);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
if (shipping is null)
|
||||||
|
return Result.Fail("Shipping not found for this order.");
|
||||||
|
|
||||||
|
shipping.UpdatedAt = DateTime.UtcNow;
|
||||||
|
shipping.Status = newStatus;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail("Shipping not found for this order or status update failed.");
|
: Result.Fail("Failed to update shipping status.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -334,14 +325,21 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
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
|
if(!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken))
|
||||||
|
return Result.Fail("Order not found.");
|
||||||
|
|
||||||
|
var shipping = await context.Shippings.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(s => s.OrderId == orderId && s.Id == shippingId, cancellationToken);
|
||||||
|
|
||||||
|
if (shipping is null)
|
||||||
|
return Result.Fail("Shipping not found for this order.");
|
||||||
|
|
||||||
|
context.Shippings.Remove(shipping);
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail("Shipping record not found for this order.");
|
: Result.Fail("Failed to remove shipping from order.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -355,15 +353,17 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var rowsUpdated = await context.Shippings
|
var shipping = await context.Shippings.FirstOrDefaultAsync(s => s.OrderId == orderId && s.Id == shippingId, cancellationToken);
|
||||||
.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
|
if (shipping is null)
|
||||||
|
return Result.Fail("Shipping not found for this order.");
|
||||||
|
|
||||||
|
shipping.UpdatedAt = DateTime.UtcNow;
|
||||||
|
shipping.TrackingNumber = trackingNumber;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail("Shipping record not found for this order.");
|
: Result.Fail("Failed to update shipping tracking number.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -371,7 +371,7 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<Result<long>> CreateShippingProviderAsync(CreateShippingProvider request, CancellationToken cancellationToken = default)
|
public async ValueTask<Result> CreateShippingProviderAsync(CreateShippingProvider request, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -382,7 +382,6 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
|
|
||||||
var shippingProvider = context.ShippingProviders.Add(new Entities.ShippingProvider
|
var shippingProvider = context.ShippingProviders.Add(new Entities.ShippingProvider
|
||||||
{
|
{
|
||||||
CreatedAt = DateTime.UtcNow,
|
|
||||||
Name = request.Name,
|
Name = request.Name,
|
||||||
Type = request.Type,
|
Type = request.Type,
|
||||||
Price = request.Price,
|
Price = request.Price,
|
||||||
@@ -390,7 +389,7 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
});
|
});
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
? Result.Ok(shippingProvider.Entity.Id)
|
? Result.Ok()
|
||||||
: Result.Fail("Failed to create shipping provider.");
|
: Result.Fail("Failed to create shipping provider.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -441,22 +440,24 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var rowsUpdated = await context.ShippingProviders
|
var provider = await context.ShippingProviders.FirstOrDefaultAsync(sp => sp.Id == request.ProviderId, cancellationToken);
|
||||||
.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
|
if (provider is null)
|
||||||
|
return Result.Fail("Shipping provider not found.");
|
||||||
|
|
||||||
|
provider.UpdatedAt = DateTime.UtcNow;
|
||||||
|
provider.Enabled = request.Enabled;
|
||||||
|
provider.Name = request.Name;
|
||||||
|
provider.Price = request.Price;
|
||||||
|
provider.TrackingUrl = request.TrackingUrl;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail("Shipping provider not found.");
|
: Result.Fail("Failed to update shipping provider status.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,10 @@ public sealed 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 ICollection<PageReference>? References { get; set; }
|
public PageReference[]? References { get; set; }
|
||||||
|
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-3
@@ -14,7 +14,5 @@ public class CreateBookPage
|
|||||||
|
|
||||||
public string[]? Notes { get; set; }
|
public string[]? Notes { get; set; }
|
||||||
|
|
||||||
public ICollection<PageReference>? References { get; set; }
|
public PageReference[]? References { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class UpdateBookPage : CreateBookPage;
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Pages.Models;
|
||||||
|
|
||||||
|
public sealed class UpdateBookPage : CreateBookPage;
|
||||||
@@ -13,13 +13,19 @@ public sealed class PageService(IDbContextFactory<MidrandBooksDbContext> context
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var rowsDeleted = await context.Pages
|
if (!await context.Books.AnyAsync(b => b.Id == authorBookId, cancellationToken))
|
||||||
.Where(p => p.AuthorBookId == authorBookId)
|
return Result.Fail("Book not found");
|
||||||
.ExecuteDeleteAsync(cancellationToken);
|
|
||||||
|
|
||||||
return rowsDeleted > 0
|
var pages = await context.Pages.Where(p => p.AuthorBookId == authorBookId).ToListAsync(cancellationToken);
|
||||||
? Result.Ok()
|
|
||||||
: Result.Fail("No pages found for the specified book");
|
if (pages.Count == 0)
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
@@ -33,13 +39,16 @@ public sealed class PageService(IDbContextFactory<MidrandBooksDbContext> context
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var rowsDeleted = await context.Pages
|
var page = await context.Pages.FirstOrDefaultAsync(p => p.AuthorBookId == authorBookId && p.Number == pageNumber && p.Type == pageType, cancellationToken);
|
||||||
.Where(p => p.AuthorBookId == authorBookId && p.Number == pageNumber && p.Type == pageType)
|
|
||||||
.ExecuteDeleteAsync(cancellationToken);
|
|
||||||
|
|
||||||
return rowsDeleted > 0
|
if (page is null)
|
||||||
|
return Result.Fail("Page not found");
|
||||||
|
|
||||||
|
context.Pages.Remove(page);
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail("Page not found");
|
: Result.Fail("Failed to delete page");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -53,15 +62,17 @@ public sealed class PageService(IDbContextFactory<MidrandBooksDbContext> context
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var rowsUpdated = await context.Pages
|
var page = await context.Pages.FirstOrDefaultAsync(p => p.Id == bookPageId, cancellationToken);
|
||||||
.Where(p => p.Id == bookPageId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(p => p.Enabled, enabled)
|
|
||||||
.SetProperty(p => p.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
if (page is null)
|
||||||
|
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("Page not found");
|
: Result.Fail("Failed to update page status");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -75,13 +86,16 @@ public sealed class PageService(IDbContextFactory<MidrandBooksDbContext> context
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var rowsDeleted = await context.Pages
|
var page = await context.Pages.FirstOrDefaultAsync(p => p.Id == bookPageId, cancellationToken);
|
||||||
.Where(p => p.Id == bookPageId)
|
|
||||||
.ExecuteDeleteAsync(cancellationToken);
|
|
||||||
|
|
||||||
return rowsDeleted > 0
|
if (page is null)
|
||||||
|
return Result.Fail("Page not found");
|
||||||
|
|
||||||
|
context.Pages.Remove(page);
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
? Result.Ok()
|
? Result.Ok()
|
||||||
: Result.Fail("Page not found");
|
: Result.Fail("Failed to delete page");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -95,20 +109,22 @@ public sealed class PageService(IDbContextFactory<MidrandBooksDbContext> context
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var rowsUpdated = await context.Pages
|
var page = await context.Pages.FirstOrDefaultAsync(p => p.Id == bookPageId, cancellationToken);
|
||||||
.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);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
if (page is null)
|
||||||
|
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("Page not found");
|
: Result.Fail("Failed to update page");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
+66
-129
@@ -1,6 +1,7 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using System;
|
using System;
|
||||||
using LiteCharms.Features.MidrandBooks.Postgres;
|
using LiteCharms.Features.MidrandBooks.Postgres;
|
||||||
|
using LiteCharms.Features.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
@@ -12,7 +13,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(MidrandBooksDbContext))]
|
[DbContext(typeof(MidrandBooksDbContext))]
|
||||||
[Migration("20260529070104_Init")]
|
[Migration("20260527070840_Init")]
|
||||||
partial class Init
|
partial class Init
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -111,6 +112,9 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
b.Property<int>("PublisherType")
|
b.Property<int>("PublisherType")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<SocialMedia[]>("SocialMedia")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
b.Property<string>("ThumbnailImageUrl")
|
b.Property<string>("ThumbnailImageUrl")
|
||||||
.HasMaxLength(2048)
|
.HasMaxLength(2048)
|
||||||
.HasColumnType("character varying(2048)");
|
.HasColumnType("character varying(2048)");
|
||||||
@@ -287,6 +291,9 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<SocialMedia[]>("SocialMedia")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
b.Property<DateTime?>("UpdatedAt")
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -331,8 +338,7 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
b.Property<decimal>("Total")
|
b.Property<decimal>("Total")
|
||||||
.HasPrecision(18, 2)
|
.HasColumnType("decimal(18,2)");
|
||||||
.HasColumnType("numeric(18,2)");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("UpdatedAt")
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -488,14 +494,17 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
.HasColumnType("boolean")
|
.HasColumnType("boolean")
|
||||||
.HasDefaultValue(true);
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
b.PrimitiveCollection<string[]>("Notes")
|
b.PrimitiveCollection<string>("Notes")
|
||||||
.HasColumnType("text[]");
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
b.Property<int>("Number")
|
b.Property<int>("Number")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("integer")
|
.HasColumnType("integer")
|
||||||
.HasDefaultValue(0);
|
.HasDefaultValue(0);
|
||||||
|
|
||||||
|
b.Property<PageReference[]>("References")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
b.Property<int>("Type")
|
b.Property<int>("Type")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
@@ -561,8 +570,8 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
b.PrimitiveCollection<string[]>("Categories")
|
b.PrimitiveCollection<string>("Categories")
|
||||||
.HasColumnType("text[]");
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAt")
|
b.Property<DateTime>("CreatedAt")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -582,6 +591,9 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
.HasMaxLength(1024)
|
.HasMaxLength(1024)
|
||||||
.HasColumnType("character varying(1024)");
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.Property<ProductMetadata>("Metadata")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(255)
|
.HasMaxLength(255)
|
||||||
@@ -592,8 +604,8 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
.HasMaxLength(512)
|
.HasMaxLength(512)
|
||||||
.HasColumnType("character varying(512)");
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
b.PrimitiveCollection<string[]>("ThumbnailUrls")
|
b.PrimitiveCollection<string>("ThumbnailUrls")
|
||||||
.HasColumnType("text[]");
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
b.Property<int>("Type")
|
b.Property<int>("Type")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -649,6 +661,40 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
b.ToTable("Prices", (string)null);
|
b.ToTable("Prices", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Models.ProductPrice", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<decimal>("Discount")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<long>("ProductId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("ProductPrice");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", "Author")
|
b.HasOne("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", "Author")
|
||||||
@@ -668,38 +714,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
b.Navigation("Product");
|
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 =>
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Address", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", "Customer")
|
b.HasOne("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", "Customer")
|
||||||
@@ -722,38 +736,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
b.Navigation("Customer");
|
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 =>
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.OrderItem", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", "AuthorBook")
|
b.HasOne("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", "AuthorBook")
|
||||||
@@ -816,34 +798,7 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
.OnDelete(DeleteBehavior.NoAction)
|
.OnDelete(DeleteBehavior.NoAction)
|
||||||
.IsRequired();
|
.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("Book");
|
||||||
|
|
||||||
b.Navigation("References");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Payments.Entities.Refund", b =>
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Payments.Entities.Refund", b =>
|
||||||
@@ -857,35 +812,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
b.Navigation("Order");
|
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 =>
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", "Product")
|
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", "Product")
|
||||||
@@ -897,6 +823,15 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
b.Navigation("Product");
|
b.Navigation("Product");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Models.ProductPrice", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", null)
|
||||||
|
.WithOne("Price")
|
||||||
|
.HasForeignKey("LiteCharms.Features.MidrandBooks.Products.Models.ProductPrice", "ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Pages");
|
b.Navigation("Pages");
|
||||||
@@ -930,6 +865,8 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
||||||
{
|
{
|
||||||
|
b.Navigation("Price");
|
||||||
|
|
||||||
b.Navigation("Prices");
|
b.Navigation("Prices");
|
||||||
});
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
+46
-12
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using LiteCharms.Features.Models;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
@@ -30,8 +31,8 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
Website = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
Website = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||||
ImageUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: false),
|
ImageUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: false),
|
||||||
ThumbnailImageUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
|
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<SocialMedia[]>(type: "jsonb", nullable: true),
|
||||||
SocialMedia = table.Column<string>(type: "jsonb", nullable: true)
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@@ -51,8 +52,8 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
Email = table.Column<string>(type: "text", nullable: false),
|
Email = table.Column<string>(type: "text", nullable: false),
|
||||||
Website = table.Column<string>(type: "text", nullable: false),
|
Website = table.Column<string>(type: "text", nullable: false),
|
||||||
Phone = 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<SocialMedia[]>(type: "jsonb", nullable: true),
|
||||||
SocialMedia = table.Column<string>(type: "jsonb", nullable: true)
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@@ -69,7 +70,7 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
CustomerId = table.Column<long>(type: "bigint", nullable: false),
|
CustomerId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
Status = table.Column<int>(type: "integer", nullable: false),
|
Status = table.Column<int>(type: "integer", nullable: false),
|
||||||
Total = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
Total = table.Column<decimal>(type: "numeric(18,2)", nullable: false),
|
||||||
Notes = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
|
Notes = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
|
||||||
InvoiceUrl = table.Column<string>(type: "text", nullable: true)
|
InvoiceUrl = table.Column<string>(type: "text", nullable: true)
|
||||||
},
|
},
|
||||||
@@ -91,10 +92,10 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
Summary = table.Column<string>(type: "character varying(512)", maxLength: 512, 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),
|
Description = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||||
ImageUrl = 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),
|
ThumbnailUrls = table.Column<string>(type: "jsonb", nullable: true),
|
||||||
Categories = table.Column<string[]>(type: "text[]", nullable: true),
|
Categories = table.Column<string>(type: "jsonb", nullable: true),
|
||||||
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
|
Metadata = table.Column<ProductMetadata>(type: "jsonb", nullable: true),
|
||||||
Metadata = table.Column<string>(type: "jsonb", nullable: true)
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@@ -259,6 +260,30 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
onDelete: ReferentialAction.Restrict);
|
onDelete: ReferentialAction.Restrict);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ProductPrice",
|
||||||
|
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),
|
||||||
|
ProductId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Amount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||||
|
Discount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ProductPrice", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ProductPrice_Products_ProductId",
|
||||||
|
column: x => x.ProductId,
|
||||||
|
principalTable: "Products",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Shippings",
|
name: "Shippings",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
@@ -309,9 +334,9 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
ContentType = 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),
|
Number = table.Column<int>(type: "integer", nullable: false, defaultValue: 0),
|
||||||
Content = table.Column<byte[]>(type: "bytea", nullable: false),
|
Content = table.Column<byte[]>(type: "bytea", nullable: false),
|
||||||
Notes = table.Column<string[]>(type: "text[]", nullable: true),
|
Notes = table.Column<string>(type: "jsonb", nullable: true),
|
||||||
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
|
References = table.Column<PageReference[]>(type: "jsonb", nullable: true),
|
||||||
References = table.Column<string>(type: "jsonb", nullable: true)
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@@ -403,6 +428,12 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
table: "Prices",
|
table: "Prices",
|
||||||
column: "ProductId");
|
column: "ProductId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ProductPrice_ProductId",
|
||||||
|
table: "ProductPrice",
|
||||||
|
column: "ProductId",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Refunds_OrderId",
|
name: "IX_Refunds_OrderId",
|
||||||
table: "Refunds",
|
table: "Refunds",
|
||||||
@@ -437,6 +468,9 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "OrderItems");
|
name: "OrderItems");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "ProductPrice");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Refunds");
|
name: "Refunds");
|
||||||
|
|
||||||
+65
-128
@@ -1,6 +1,7 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using System;
|
using System;
|
||||||
using LiteCharms.Features.MidrandBooks.Postgres;
|
using LiteCharms.Features.MidrandBooks.Postgres;
|
||||||
|
using LiteCharms.Features.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
@@ -108,6 +109,9 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
b.Property<int>("PublisherType")
|
b.Property<int>("PublisherType")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<SocialMedia[]>("SocialMedia")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
b.Property<string>("ThumbnailImageUrl")
|
b.Property<string>("ThumbnailImageUrl")
|
||||||
.HasMaxLength(2048)
|
.HasMaxLength(2048)
|
||||||
.HasColumnType("character varying(2048)");
|
.HasColumnType("character varying(2048)");
|
||||||
@@ -284,6 +288,9 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<SocialMedia[]>("SocialMedia")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
b.Property<DateTime?>("UpdatedAt")
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -328,8 +335,7 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
b.Property<decimal>("Total")
|
b.Property<decimal>("Total")
|
||||||
.HasPrecision(18, 2)
|
.HasColumnType("decimal(18,2)");
|
||||||
.HasColumnType("numeric(18,2)");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("UpdatedAt")
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -485,14 +491,17 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
.HasColumnType("boolean")
|
.HasColumnType("boolean")
|
||||||
.HasDefaultValue(true);
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
b.PrimitiveCollection<string[]>("Notes")
|
b.PrimitiveCollection<string>("Notes")
|
||||||
.HasColumnType("text[]");
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
b.Property<int>("Number")
|
b.Property<int>("Number")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("integer")
|
.HasColumnType("integer")
|
||||||
.HasDefaultValue(0);
|
.HasDefaultValue(0);
|
||||||
|
|
||||||
|
b.Property<PageReference[]>("References")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
b.Property<int>("Type")
|
b.Property<int>("Type")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
@@ -558,8 +567,8 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
b.PrimitiveCollection<string[]>("Categories")
|
b.PrimitiveCollection<string>("Categories")
|
||||||
.HasColumnType("text[]");
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAt")
|
b.Property<DateTime>("CreatedAt")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -579,6 +588,9 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
.HasMaxLength(1024)
|
.HasMaxLength(1024)
|
||||||
.HasColumnType("character varying(1024)");
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.Property<ProductMetadata>("Metadata")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(255)
|
.HasMaxLength(255)
|
||||||
@@ -589,8 +601,8 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
.HasMaxLength(512)
|
.HasMaxLength(512)
|
||||||
.HasColumnType("character varying(512)");
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
b.PrimitiveCollection<string[]>("ThumbnailUrls")
|
b.PrimitiveCollection<string>("ThumbnailUrls")
|
||||||
.HasColumnType("text[]");
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
b.Property<int>("Type")
|
b.Property<int>("Type")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -646,6 +658,40 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
b.ToTable("Prices", (string)null);
|
b.ToTable("Prices", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Models.ProductPrice", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<decimal>("Discount")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<long>("ProductId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("ProductPrice");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", "Author")
|
b.HasOne("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", "Author")
|
||||||
@@ -665,38 +711,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
b.Navigation("Product");
|
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 =>
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Address", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", "Customer")
|
b.HasOne("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", "Customer")
|
||||||
@@ -719,38 +733,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
b.Navigation("Customer");
|
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 =>
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.OrderItem", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", "AuthorBook")
|
b.HasOne("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", "AuthorBook")
|
||||||
@@ -813,34 +795,7 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
.OnDelete(DeleteBehavior.NoAction)
|
.OnDelete(DeleteBehavior.NoAction)
|
||||||
.IsRequired();
|
.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("Book");
|
||||||
|
|
||||||
b.Navigation("References");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Payments.Entities.Refund", b =>
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Payments.Entities.Refund", b =>
|
||||||
@@ -854,35 +809,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
b.Navigation("Order");
|
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 =>
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", "Product")
|
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", "Product")
|
||||||
@@ -894,6 +820,15 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
b.Navigation("Product");
|
b.Navigation("Product");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Models.ProductPrice", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", null)
|
||||||
|
.WithOne("Price")
|
||||||
|
.HasForeignKey("LiteCharms.Features.MidrandBooks.Products.Models.ProductPrice", "ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Pages");
|
b.Navigation("Pages");
|
||||||
@@ -927,6 +862,8 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
||||||
{
|
{
|
||||||
|
b.Navigation("Price");
|
||||||
|
|
||||||
b.Navigation("Prices");
|
b.Navigation("Prices");
|
||||||
});
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ 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);
|
||||||
@@ -16,10 +14,9 @@ public sealed 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; }
|
public virtual Product Product { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Products.Models;
|
||||||
|
|
||||||
|
public sealed class CreateProductPrice
|
||||||
|
{
|
||||||
|
public long ProductId { get; set; }
|
||||||
|
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
|
||||||
|
public decimal Discount { get; set; }
|
||||||
|
}
|
||||||
@@ -20,10 +20,3 @@ public sealed 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; }
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -14,15 +14,17 @@ public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> cont
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var rowsUpdated = await context.Prices
|
var productPrice = await context.Prices.FirstOrDefaultAsync(p => p.Id == productPriceId, cancellationToken);
|
||||||
.Where(p => p.Id == productPriceId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(p => p.Enabled, isEnabled)
|
|
||||||
.SetProperty(p => p.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
if (productPrice is null)
|
||||||
|
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($"Product price with ID {productPriceId} not found"));
|
: Result.Fail(new Error($"Failed to change status of product price with ID {productPriceId}"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -36,15 +38,17 @@ public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> cont
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var rowsUpdated = await context.Products
|
var product = await context.Products.FirstOrDefaultAsync(p => p.Id == productId, cancellationToken);
|
||||||
.Where(p => p.Id == productId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(p => p.Enabled, isEnabled)
|
|
||||||
.SetProperty(p => p.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
if (product is null)
|
||||||
|
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($"Product with ID {productId} not found"));
|
: Result.Fail(new Error($"Failed to change status of product with ID {productId}"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -60,22 +64,17 @@ public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> cont
|
|||||||
|
|
||||||
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 => EF.Functions.ILike(p.Name!, $"%{filter.Title}%"));
|
query = query.Where(p => p.Name!.Contains(filter.Title));
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(filter.Category))
|
if (!string.IsNullOrWhiteSpace(filter.Category))
|
||||||
query = query.Where(p => p.Categories.Contains(filter.Category));
|
query = query.Where(p => p.Categories!.Any(c => c == filter.Category));
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(filter.Manufacturer))
|
if (!string.IsNullOrWhiteSpace(filter.Manufacturer))
|
||||||
query = query.Where(p => EF.Functions.ILike(p.Metadata!.Manufacturer!, $"%{filter.Manufacturer}%"));
|
query = query.Where(p => p.Metadata!.Manufacturer == filter.Manufacturer);
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(filter.SerialNumber))
|
if (!string.IsNullOrWhiteSpace(filter.SerialNumber))
|
||||||
query = query.Where(p => EF.Functions.ILike(p.Metadata!.SerialNumber!, $"%{filter.SerialNumber}%"));
|
query = query.Where(p => 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));
|
||||||
@@ -166,7 +165,7 @@ public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> cont
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<Result<ProductPrice>> GetProductPriceAsync(long productId, CancellationToken cancellationToken = default)
|
public async ValueTask<Result<ProductPrice[]>> GetProductPriceAsync(long productId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -176,16 +175,16 @@ public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> cont
|
|||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.OrderByDescending(p => p.CreatedAt)
|
.OrderByDescending(p => p.CreatedAt)
|
||||||
.ThenBy(p => p.UpdatedAt)
|
.ThenBy(p => p.UpdatedAt)
|
||||||
.FirstOrDefaultAsync(p => p.ProductId == productId && p.Enabled, cancellationToken);
|
.FirstOrDefaultAsync(p => p.ProductId == productId, cancellationToken);
|
||||||
|
|
||||||
return product is not null
|
return product is not null
|
||||||
? Result.Ok(product.ToModel())
|
? Result.Ok(new[] { 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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,8 +228,8 @@ public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> cont
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var fromDate = range.From.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc);
|
var fromDate = range.From.ToDateTime(TimeOnly.MinValue);
|
||||||
var toDate = range.To.ToDateTime(TimeOnly.MaxValue, DateTimeKind.Utc);
|
var toDate = range.To.ToDateTime(TimeOnly.MaxValue);
|
||||||
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
@@ -245,7 +244,7 @@ public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> cont
|
|||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
.ToArrayAsync(cancellationToken);
|
.ToArrayAsync(cancellationToken);
|
||||||
|
|
||||||
return products?.Length > 0
|
return await context.SaveChangesAsync(cancellationToken) > 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."));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,8 +93,7 @@ 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
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
<Project Path="LiteCharms.Features/LiteCharms.Features.csproj" />
|
<Project Path="LiteCharms.Features/LiteCharms.Features.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/tests/">
|
<Folder Name="/tests/">
|
||||||
<Project Path="LiteCharms.Features.MidrandBooks.Seed/LiteCharms.Features.MidrandBooks.Seed.csproj" Id="aa80643a-28dc-431f-b163-053a94e5c77c" />
|
|
||||||
<Project Path="LiteCharms.Features.MidrandBooks.Tests/LiteCharms.Features.MidrandBooks.Tests.csproj" Id="cac2f738-dbb5-4538-8565-3c2bd6f65259" />
|
<Project Path="LiteCharms.Features.MidrandBooks.Tests/LiteCharms.Features.MidrandBooks.Tests.csproj" Id="cac2f738-dbb5-4538-8565-3c2bd6f65259" />
|
||||||
<Project Path="LiteCharms.Features.TechShop.Tests/LiteCharms.Features.TechShop.Tests.csproj" Id="0e0967c2-7f28-4668-a387-2fc437ab066f" />
|
<Project Path="LiteCharms.Features.TechShop.Tests/LiteCharms.Features.TechShop.Tests.csproj" Id="0e0967c2-7f28-4668-a387-2fc437ab066f" />
|
||||||
<Project Path="LiteCharms.Features.Tests/LiteCharms.Features.Tests.csproj" Id="0696323f-7148-4ab9-9145-68b7b5df5415" />
|
<Project Path="LiteCharms.Features.Tests/LiteCharms.Features.Tests.csproj" Id="0696323f-7148-4ab9-9145-68b7b5df5415" />
|
||||||
|
|||||||
Reference in New Issue
Block a user