Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 92c00360a7 | |||
| 38e765203d | |||
| d9e7f225ae | |||
| 9eb3526a2e | |||
| 4397976ed8 | |||
| 2546c34ffc | |||
| 4e53ff8a37 | |||
| 2a0b34c730 | |||
| 6ae63e2ad1 | |||
| f5efdde37c | |||
| 1592d5dc8f | |||
| 50a8a59d92 | |||
| b70d9559b0 | |||
| 81d5e8f07c | |||
| 20a53942b5 | |||
| 9edb2aa4aa | |||
| 6ed023f2cf | |||
| 2c9f5a846c | |||
| 41f7c05be3 | |||
| 1a03355e84 | |||
| 7743c3178e | |||
| ab3d8e6e9a | |||
| db4c348288 | |||
| 6683234642 | |||
| 6ddbb9479a | |||
| 6c7349a0f8 | |||
| e97fd6cd3f | |||
| 184c7c252a | |||
| bfe8c458d6 | |||
| e6e0475db1 | |||
| 5090c60797 | |||
| 9432252e15 | |||
| 47111a1a3a | |||
| 6eb3d50375 | |||
| 4deb732804 | |||
| 20d9387d0b | |||
| 9f6d0ccaa0 | |||
| 1acbc4d213 | |||
| 8c99668fac | |||
| ad44f46204 | |||
| 49d999c1e3 | |||
| 9ed4777a18 | |||
| 0cf44f68cc | |||
| 41ed5a4288 | |||
| bbcba5e06c | |||
| 502cc326dd | |||
| 4675d4c5fc | |||
| f80bb2fff9 | |||
| 6767906b0d | |||
| a344af4498 |
@@ -0,0 +1,81 @@
|
||||
using LiteCharms.Features.MidrandBooks.Authors;
|
||||
using LiteCharms.Features.MidrandBooks.Authors.Models;
|
||||
using LiteCharms.Features.MidrandBooks.Tests.Common;
|
||||
using LiteCharms.Features.Models;
|
||||
|
||||
namespace LiteCharms.Features.MidrandBooks.Tests;
|
||||
|
||||
public class AuthorServiceFeatureTests(Fixture fixture, ITestOutputHelper output) : IClassFixture<Fixture>
|
||||
{
|
||||
private readonly AuthorService authorService = fixture.Services.GetRequiredService<AuthorService>();
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task CreateAuthorAsync_ShouldReturn_ResultWithAuthorId()
|
||||
{
|
||||
var request = new CreateAuthor
|
||||
{
|
||||
Name = "John",
|
||||
LastName = "Doe",
|
||||
Company = "Solo Publishers",
|
||||
Email = "solo@publishers.co.za",
|
||||
PublisherType = PublisherTypes.Independent,
|
||||
ImageUrl = ""
|
||||
};
|
||||
|
||||
var result = await authorService.CreateAuthorAsync(request, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.True(result.Value > 0);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdateAuthorAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
var request = new UpdateAuthor
|
||||
{
|
||||
Name = "Jane",
|
||||
LastName = "Doe",
|
||||
Company = "Solo Publishers",
|
||||
Email = "solo@publishers.co.za",
|
||||
PublisherType = PublisherTypes.Independent,
|
||||
ImageUrl = ""
|
||||
};
|
||||
|
||||
var result = await authorService.UpdateAuthorAsync(1, request, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetAuthors_ShouldReturn_ResultWithAuthorList()
|
||||
{
|
||||
var range = new DateRange
|
||||
{
|
||||
From = DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-7)),
|
||||
To = DateOnly.FromDateTime(DateTime.UtcNow),
|
||||
MaxRecords = 1000
|
||||
};
|
||||
|
||||
var result = await authorService.GetAuthorsAsync(range, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotEmpty(result.Value);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetAuthorAsync_ShouldReturn_ResultWithAuthor()
|
||||
{
|
||||
var result = await authorService.GetAuthorAsync(1, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotNull(result.Value);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdateAuthorStatusAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
var result = await authorService.UpdateAuthorStatusAsync(1, true, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using LiteCharms.Features.MidrandBooks.AuthorBooks;
|
||||
using LiteCharms.Features.MidrandBooks.Tests.Common;
|
||||
|
||||
namespace LiteCharms.Features.MidrandBooks.Tests;
|
||||
|
||||
public class BooksServiceFeatureTests(Fixture fixture) : IClassFixture<Fixture>
|
||||
{
|
||||
private readonly BooksService bookService = fixture.Services.GetRequiredService<BooksService>();
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task CreateBookAsync_ShouldReturn_ResultWithBookId()
|
||||
{
|
||||
var result = await bookService.CreateBookAsync(1, 2, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetBookAsync_ShouldReturn_ResultWithBook()
|
||||
{
|
||||
var result = await bookService.GetBookAsync(1, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotNull(result.Value);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetBooksByAuthorAsync_ShouldReturn_ResultWithAuthorBooks()
|
||||
{
|
||||
var result = await bookService.GetBooksByAuthorAsync(1, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotEmpty(result.Value);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetPublishedBooksAsync_ShouldReturn_ResultWithBublishedBooks()
|
||||
{
|
||||
var result = await bookService.GetPublishedBooksAsync(0, 1000, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotEmpty(result.Value);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdateBookStatusAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
var result = await bookService.UpdateBookStatusAsync(1, true, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
}
|
||||
+7
-2
@@ -1,7 +1,8 @@
|
||||
using LiteCharms.Features.Extensions;
|
||||
using LiteCharms.Features.MidrandBooks.Abstractions;
|
||||
using LiteCharms.Features.MidrandBooks.Extensions;
|
||||
|
||||
namespace LiteCharms.Features.MidrandBooks.Tests;
|
||||
namespace LiteCharms.Features.MidrandBooks.Tests.Common;
|
||||
|
||||
public class Fixture : IDisposable
|
||||
{
|
||||
@@ -11,6 +12,10 @@ public class Fixture : IDisposable
|
||||
|
||||
public IMediator Mediator { get; set; }
|
||||
|
||||
private readonly CancellationTokenSource cancellationTokenSource = new();
|
||||
|
||||
public CancellationToken CancellationToken => cancellationTokenSource.Token;
|
||||
|
||||
public Fixture()
|
||||
{
|
||||
Configuration = new ConfigurationBuilder()
|
||||
@@ -23,12 +28,12 @@ public class Fixture : IDisposable
|
||||
Services = new ServiceCollection()
|
||||
.AddMediator()
|
||||
.AddLogging()
|
||||
//.AddMidrandShopServices()
|
||||
.AddEmailServiceBus()
|
||||
.AddGarageS3(Configuration)
|
||||
.AddMidrandShopDatabase(Configuration)
|
||||
.AddEmailServices(Configuration)
|
||||
.AddSingleton(Configuration)
|
||||
.AddShopServices()
|
||||
.BuildServiceProvider();
|
||||
|
||||
Mediator = Services.GetRequiredService<IMediator>();
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace LiteCharms.Features.MidrandBooks.Tests.Common;
|
||||
|
||||
public class IntegrationFactAttribute : FactAttribute
|
||||
{
|
||||
public IntegrationFactAttribute()
|
||||
{
|
||||
if(!Debugger.IsAttached)
|
||||
Skip = "This test requires the debugger to be attached.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
using LiteCharms.Features.MidrandBooks.Customers;
|
||||
using LiteCharms.Features.MidrandBooks.Customers.Models;
|
||||
using LiteCharms.Features.MidrandBooks.Tests.Common;
|
||||
|
||||
namespace LiteCharms.Features.MidrandBooks.Tests;
|
||||
|
||||
public class CustomerServiceFeatureTests(Fixture fixture) : IClassFixture<Fixture>
|
||||
{
|
||||
private readonly CustomerService customerService = fixture.Services.GetRequiredService<CustomerService>();
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task CreateCustomerAsync_ShouldReturn_ResultWithCustomerId()
|
||||
{
|
||||
var request = new CreateCustomer
|
||||
{
|
||||
Company = "Book Lovers",
|
||||
Email = "hank@booklovers.com",
|
||||
Phone = "555 1245 8577",
|
||||
Website = "https://www.booklovers.com"
|
||||
};
|
||||
|
||||
var result = await customerService.CreateCustomerAsync(request, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.True(result.Value > 0);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task CreateCustomerContactAsync_ShouldReturn_ResultWithCustomerContactId()
|
||||
{
|
||||
var request = new CreateCustomerContact
|
||||
{
|
||||
Name = "Sipho",
|
||||
LastName = "Madlanga",
|
||||
Phone = "0710857365",
|
||||
Email = "sipho@madlanga.africa",
|
||||
Type = ContactTypes.Business
|
||||
};
|
||||
|
||||
var result = await customerService.CreateCustomerContactAsync(1, request, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.True(result.Value > 0);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task CreateCustomerAddressAsync_ShouldReturn_ResultWithCustomerAddressId()
|
||||
{
|
||||
var request = new CreateCustomerAddress
|
||||
{
|
||||
Name = "Business",
|
||||
BuildingType = AddressBuildingTypes.MixedUse,
|
||||
Type = AddressType.Shipping,
|
||||
Street = "123 Building 4, XYZ Suburb, Some Region",
|
||||
City = "Johannesburg",
|
||||
State = "Gauteng",
|
||||
Country = "South Africa",
|
||||
IsPrimary = true,
|
||||
Enabled = true,
|
||||
PostalCode = "12345"
|
||||
};
|
||||
|
||||
var result = await customerService.CreateCustomerAddressAsync(1, request, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.True(result.Value > 0);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdateCustomerAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
var request = new UpdateCustomer
|
||||
{
|
||||
Company = "Book Lovers",
|
||||
Email = "hank@booklovers.com",
|
||||
Phone = "555 1245 8578",
|
||||
Website = "https://www.booklovers.com"
|
||||
};
|
||||
|
||||
var result = await customerService.UpdateCustomerAsync(1, request, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdateCustomerContactAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
var request = new UpdateCustomerContact
|
||||
{
|
||||
Name = "Sipho",
|
||||
LastName = "Madlanga",
|
||||
Phone = "0710857366",
|
||||
Email = "sipho@madlanga.africa",
|
||||
Type = ContactTypes.Business
|
||||
};
|
||||
|
||||
var result = await customerService.UpdateCustomerContactAsync(1, request, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdateCustomerAddressAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
var request = new UpdateCustomerAddress
|
||||
{
|
||||
Name = "Business",
|
||||
BuildingType = AddressBuildingTypes.MixedUse,
|
||||
Type = AddressType.Shipping,
|
||||
Street = "123 Building 4, XYZ Suburb, Some Region",
|
||||
City = "Johannesburg",
|
||||
State = "Gauteng",
|
||||
Country = "South Africa",
|
||||
IsPrimary = true,
|
||||
Enabled = true,
|
||||
PostalCode = "12346"
|
||||
};
|
||||
|
||||
var result = await customerService.UpdateCustomerAddressAsync(1, request, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdateCustomerStatusAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
var result = await customerService.UpdateCustomerStatusAsync(1, true, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdateCustomerContactStatusAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
var result = await customerService.UpdateCustomerContactStatusAsync(1, true, true, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdateCustomerAddressStatusAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
var result = await customerService.UpdateCustomerAddressStatusAsync(1, true, true, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetCustomersAsync_ShouldReturn_ResultWithCustomerList()
|
||||
{
|
||||
var result = await customerService.GetCustomersAsync(fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotEmpty(result.Value);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetCustomerContactsAsync_ShouldReturn_ResultWithCustomerContactList()
|
||||
{
|
||||
var result = await customerService.GetCustomerContactsAsync(1, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotEmpty(result.Value);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetCustomerAddressesAsync_ShouldReturn_ResultWithCustomerAddressList()
|
||||
{
|
||||
var result = await customerService.GetCustomerAddressesAsync(1, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotEmpty(result.Value);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetCustomerAsync_ShouldReturn_ResultWithCustomer()
|
||||
{
|
||||
var result = await customerService.GetCustomerAsync(1, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotNull(result.Value);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetCustomerContactAsync_ShouldReturn_ResultWithCustomerContact()
|
||||
{
|
||||
var result = await customerService.GetCustomerContactsAsync(1, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotNull(result.Value);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetCustomerAddressAsync_ShouldReturn_ResultWithCustomerAddress()
|
||||
{
|
||||
var result = await customerService.GetCustomerAddressAsync(1, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotNull(result.Value);
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="System.Text.Json" />
|
||||
<Using Include="System.Diagnostics" />
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
using LiteCharms.Features.MidrandBooks.Orders;
|
||||
using LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||
using LiteCharms.Features.MidrandBooks.Tests.Common;
|
||||
using LiteCharms.Features.Models;
|
||||
|
||||
namespace LiteCharms.Features.MidrandBooks.Tests;
|
||||
|
||||
public class OrderServiceFeatureTests(Fixture fixture) : IClassFixture<Fixture>
|
||||
{
|
||||
private readonly OrderService orderService = fixture.Services.GetRequiredService<OrderService>();
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task CreateOrderAsync_ShouldReturn_ResultWithOrderId()
|
||||
{
|
||||
var request = new CreateOrder(250, "At the intercomm, dial 1 then option 2");
|
||||
|
||||
var result = await orderService.CreateOrderAsync(1, request, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.True(result.Value > 0);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task AddItemToOrderAsync_ShouldReturn_ResultWithOrderItemId()
|
||||
{
|
||||
var request = new CreateOrderItem(1, 1, 2);
|
||||
|
||||
var result = await orderService.AddItemToOrderAsync(1, request, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.True(result.Value > 0);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task AddItemsToOrderAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
var requests = new List<CreateOrderItem>
|
||||
{
|
||||
new(1, 1, 1),
|
||||
new(1, 1, 3)
|
||||
};
|
||||
|
||||
var result = await orderService.AddItemsToOrderAsync(1, [.. requests], fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task RemoveItemFromOrderAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
var result = await orderService.RemoveItemFromOrderAsync(1, 5, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task ClearOrderItemsAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
var result = await orderService.ClearOrderItemsAsync(1, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task CancelOrderAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
var result = await orderService.CancelOrderAsync(1, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetOrderAsync_ShouldReturn_ResultWithOrder()
|
||||
{
|
||||
var result = await orderService.GetOrderAsync(1, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotNull(result.Value);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetOrdersByCustomerAsync_ShouldReturn_ResultWithOrderList()
|
||||
{
|
||||
var result = await orderService.GetOrdersByCustomerAsync(1, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotEmpty(result.Value);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetOrdersAsync_ShouldReturn_ResultWithOrderList()
|
||||
{
|
||||
var range = new DateRange
|
||||
{
|
||||
From = DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-7)),
|
||||
To = DateOnly.FromDateTime(DateTime.UtcNow),
|
||||
MaxRecords = 1000
|
||||
};
|
||||
|
||||
var result = await orderService.GetOrdersAsync(range, 0, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotEmpty(result.Value);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdateOrderStatusAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
var result = await orderService.UpdateOrderStatusAsync(1, OrderStatus.Pending, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task AddShippingToOrderAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
var request = new CreateShipping(1, 2);
|
||||
|
||||
var result = await orderService.AddShippingToOrderAsync(1, request, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.True(result.Value > 0);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdateShippingStatusAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
var result = await orderService.UpdateShippingStatusAsync(1, ShippingStatuses.Shipped, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetShippingByOrderIdAsync_ShouldReturn_ResultWithShipping()
|
||||
{
|
||||
var result = await orderService.GetShippingByOrderIdAsync(1, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotNull(result.Value);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task RemoveShippingFromOrderAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
var result = await orderService.RemoveShippingFromOrderAsync(1, 1, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdateShippingTrackingNumberAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
var result = await orderService.UpdateShippingTrackingNumberAsync(1, 2, "NA0009969397");
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task CreateShippingProviderAsync_ShouldReturn_ResultWithShippingProviderId()
|
||||
{
|
||||
var request = new CreateShippingProvider(ShippingProviderTypes.FastWay, "FastWay Couriers", 50, "https://www.fastway.co.za/our-services/track-your-parcel");
|
||||
|
||||
var result = await orderService.CreateShippingProviderAsync(request, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.True(result.Value > 0);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetShippingProvidersAsync_ShouldReturn_ResultWithShippingProviderList()
|
||||
{
|
||||
var result = await orderService.GetShippingProvidersAsync(true, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotEmpty(result.Value);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetShippingProviderAsync_ShouldReturn_ResultWithShippingProvider()
|
||||
{
|
||||
var result = await orderService.GetShippingProviderAsync(2, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotNull(result.Value);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdateShippingProviderAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
var request = new UpdateShippingProvider(2,true, "FastWay Couriers", 50, "https://www.fastway.co.za/our-services/track-your-parcel");
|
||||
|
||||
var result = await orderService.UpdateShippingProviderAsync(request, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using LiteCharms.Features.MidrandBooks.Pages;
|
||||
using LiteCharms.Features.MidrandBooks.Tests.Common;
|
||||
|
||||
namespace LiteCharms.Features.MidrandBooks.Tests;
|
||||
|
||||
public class PageServiceFeatureTests(Fixture fixture) : IClassFixture<Fixture>
|
||||
{
|
||||
private readonly PageService pageService = fixture.Services.GetRequiredService<PageService>();
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task CreatePageAsync_ShouldReturn_ResultWithPageId()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetPagesAsync_ByBookId_ShouldReturn_ResultWithPageList()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetPageAsync_ShouldReturn_ResultWithPage()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetPageByNumberAsync_ById_And_BookPageNumber_ShouldReturn_ResultWithPage()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdatePageAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task DeletePageAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task DeleteByPageTypeAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task DeleteAllAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdatePageStatusAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
using LiteCharms.Features.MidrandBooks.Products;
|
||||
using LiteCharms.Features.MidrandBooks.Products.Models;
|
||||
using LiteCharms.Features.MidrandBooks.Tests.Common;
|
||||
using LiteCharms.Features.Models;
|
||||
|
||||
namespace LiteCharms.Features.MidrandBooks.Tests;
|
||||
|
||||
public class ProductServiceFeatureTests(Fixture fixture, ITestOutputHelper output) : IClassFixture<Fixture>
|
||||
{
|
||||
private readonly ProductService productService = fixture.Services.GetRequiredService<ProductService>();
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetProductPriceAsync_ShouldReturn_RetultOneProductPrice()
|
||||
{
|
||||
var result = await productService.GetProductPriceAsync(2, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotNull(result.Value);
|
||||
|
||||
output.WriteLine(JsonSerializer.Serialize(result.Value));
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetProductPricesAsync_ShouldReturn_RetultProductPriceList()
|
||||
{
|
||||
var result = await productService.GetProductPricesAsync(2, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotEmpty(result.Value);
|
||||
|
||||
output.WriteLine(JsonSerializer.Serialize(result.Value));
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task SearchProductsAsync_ShouldReturn_RetultMatchingProducts()
|
||||
{
|
||||
var filter = new ProductFilter
|
||||
{
|
||||
Name = "system",
|
||||
Manufacturer = "techwave",
|
||||
SerialNumber = "2024",
|
||||
MinPrice = 10,
|
||||
MaxPrice = 30
|
||||
};
|
||||
|
||||
var result = await productService.SearchProductsAsync(filter, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotEmpty(result.Value);
|
||||
|
||||
output.WriteLine(JsonSerializer.Serialize(result.Value));
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetProductAsync_ShouldReturn_RetultOneProduct()
|
||||
{
|
||||
var result = await productService.GetProductAsync(2, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotNull(result.Value);
|
||||
|
||||
output.WriteLine(JsonSerializer.Serialize(result.Value));
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetProductsAsync_ShouldReturn_RetultProducts()
|
||||
{
|
||||
var range = new DateRange
|
||||
{
|
||||
From = DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-7)),
|
||||
To = DateOnly.FromDateTime(DateTime.UtcNow),
|
||||
MaxRecords = 1000
|
||||
};
|
||||
|
||||
var result = await productService.GetProductsAsync(0, range, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.NotEmpty(result.Value);
|
||||
|
||||
output.WriteLine(JsonSerializer.Serialize(result.Value));
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdateProductStatusAsync_ShouldReturn_ResultTrue()
|
||||
{
|
||||
var result = await productService.UpdateProductStatusAsync(2, true, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdateProductPriceStatusAsync_ShouldReturn_ResultTrue()
|
||||
{
|
||||
var result = await productService.UpdateProductPriceStatusAsync(2, true, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task CreateProductPriceAsync_Should_Return_NewProductPriceId()
|
||||
{
|
||||
var request = new CreateProductPrice
|
||||
{
|
||||
Amount = 29.99m,
|
||||
Discount = 0.00m
|
||||
};
|
||||
|
||||
var result = await productService.CreateProductPriceAsync(2, request, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess, "Product price creation should be successful.");
|
||||
Assert.True(result.Value > 0, "New ProductPriceId should be greater than 0.");
|
||||
|
||||
output.WriteLine($"Created ProductPriceId: {result.Value}");
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task CreateProductAsync_Result_Returns_ProductId()
|
||||
{
|
||||
var request = new CreateProduct
|
||||
{
|
||||
Name = "Systems Rewired",
|
||||
Description = "[Design], <Code>, AND /CHAORS/ IN ***SYNC***",
|
||||
Summary = "A comprehensive guide to systems thinking and design.",
|
||||
ImageUrl = "https://bookshop.cdn.khongisa.co.za/design/2bf1f9a2-7b25-4fcf-9aa7-08941ea21e6c_1764838499686.webp",
|
||||
Type = ProductTypes.Book,
|
||||
Categories = ["Systems Thinking", "Design", "Programming"],
|
||||
Metadata = new ProductMetadata
|
||||
{
|
||||
CopyrightInfo = "© 2024 John Doe. All rights reserved.",
|
||||
ManufactureDate = "2024-06-01",
|
||||
Manufacturer = "TechWave Publishing",
|
||||
SerialNumber = "SR-2024-0001"
|
||||
}
|
||||
};
|
||||
|
||||
var result = await productService.CreateProductAsync(request, fixture.CancellationToken);
|
||||
|
||||
Assert.True(result.IsSuccess, "Product creation should be successful.");
|
||||
Assert.True(result.Value > 0, "ProductId should be greater than 0.");
|
||||
|
||||
output.WriteLine($"Created ProductId: {result.Value}");
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,6 @@
|
||||
"BucketName": "bookshop",
|
||||
"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": {
|
||||
"Credentials": {
|
||||
"Username": "shop@litecharms.co.za"
|
||||
|
||||
@@ -7,23 +7,21 @@ namespace LiteCharms.Features.MidrandBooks.AuthorBooks;
|
||||
|
||||
public sealed class BooksService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
||||
{
|
||||
public async ValueTask<Result> UpdateBookStatusAsync(long bookId, bool isEnabled, CancellationToken cancellationToken)
|
||||
public async ValueTask<Result> UpdateBookStatusAsync(long bookId, bool isEnabled, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var book = await context.Books.FirstOrDefaultAsync(b => b.Id == bookId, cancellationToken);
|
||||
var rowsUpdated = await context.Books
|
||||
.Where(b => b.Id == bookId)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(b => b.Enabled, isEnabled)
|
||||
.SetProperty(b => b.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||
|
||||
if (book is null)
|
||||
return Result.Fail(new Error($"Book with ID {bookId} not found"));
|
||||
|
||||
book.UpdatedAt = DateTime.UtcNow;
|
||||
book.Enabled = isEnabled;
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
return rowsUpdated > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail(new Error($"Failed to change status of book with ID {bookId}"));
|
||||
: Result.Fail(new Error($"Book with ID {bookId} not found"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -45,8 +43,9 @@ public sealed class BooksService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
|
||||
var book = context.Books.Add(new Entities.AuthorBook
|
||||
{
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
AuthorId = authorId,
|
||||
ProductId = productId,
|
||||
ProductId = productId
|
||||
});
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
@@ -68,7 +67,8 @@ public sealed class BooksService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
var book = await context.Books
|
||||
.AsNoTracking()
|
||||
.Include(b => b.Author)
|
||||
.Include(b => b.Product!.Price)
|
||||
.Include(b => b.Product)
|
||||
.ThenInclude(b => b!.Prices)
|
||||
.Include(b => b.Pages)
|
||||
.FirstOrDefaultAsync(b => b.Id == bookId, cancellationToken);
|
||||
|
||||
@@ -86,7 +86,7 @@ public sealed class BooksService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
{
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
if(!await context.Authors.AnyAsync(a => a.Id == authorId, cancellationToken))
|
||||
return Result.Fail<AuthorBook[]>(new Error($"Author with ID {authorId} not found"));
|
||||
@@ -94,7 +94,8 @@ public sealed class BooksService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
var books = await context.Books
|
||||
.AsNoTracking()
|
||||
.Include(b => b.Author)
|
||||
.Include(b => b.Product!.Price)
|
||||
.Include(b => b.Product)
|
||||
.ThenInclude(b => b.Prices)
|
||||
.OrderByDescending(b => b.CreatedAt)
|
||||
.Where(b => b.AuthorId == authorId)
|
||||
.ToListAsync(cancellationToken);
|
||||
@@ -118,9 +119,10 @@ public sealed class BooksService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
var books = await context.Books
|
||||
.AsNoTracking()
|
||||
.Include(b => b.Author)
|
||||
.Include(b => b.Product!.Price)
|
||||
.Include(b => b.Product)
|
||||
.ThenInclude(b => b!.Prices)
|
||||
.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)
|
||||
.ThenByDescending(b => b.Ranking)
|
||||
.ThenByDescending(b => b.CreatedAt)
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace LiteCharms.Features.MidrandBooks.AuthorBooks.Entities;
|
||||
|
||||
public class AuthorBook : Models.AuthorBook
|
||||
{
|
||||
public virtual Author Author { get; set; } = new();
|
||||
public virtual Author? Author { get; set; }
|
||||
|
||||
public new virtual Product? Product { get; set; }
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using LiteCharms.Features.MidrandBooks.Abstractions;
|
||||
using LiteCharms.Features.MidrandBooks.AuthorBooks.Models;
|
||||
using LiteCharms.Features.MidrandBooks.Authors.Models;
|
||||
using LiteCharms.Features.MidrandBooks.Extensions;
|
||||
using LiteCharms.Features.MidrandBooks.Postgres;
|
||||
@@ -9,53 +8,21 @@ namespace LiteCharms.Features.MidrandBooks.Authors;
|
||||
|
||||
public sealed class AuthorService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
||||
{
|
||||
public async ValueTask<Result<AuthorBook[]>> GetAuthorBooksAsync(long authorId, CancellationToken cancellationToken)
|
||||
public async ValueTask<Result> UpdateAuthorStatusAsync(long authorId, bool isEnabled, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var author = await context.Authors.FirstOrDefaultAsync(a => a.Id == authorId, cancellationToken);
|
||||
var rowsUpdated = await context.Authors
|
||||
.Where(a => a.Id == authorId)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(a => a.Enabled, isEnabled)
|
||||
.SetProperty(a => a.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||
|
||||
if (author is null)
|
||||
return 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
|
||||
return rowsUpdated > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail(new Error($"Failed to change status of author with ID {authorId}"));
|
||||
: Result.Fail(new Error($"Author with ID {authorId} not found"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -81,12 +48,12 @@ public sealed class AuthorService(IDbContextFactory<MidrandBooksDbContext> conte
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Result<Author[]>> GetAuthors(DateRange range, CancellationToken cancellationToken)
|
||||
public async ValueTask<Result<Author[]>> GetAuthorsAsync(DateRange range, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fromDate = range.From.ToDateTime(TimeOnly.MinValue);
|
||||
var toDate = range.To.ToDateTime(TimeOnly.MaxValue);
|
||||
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);
|
||||
|
||||
@@ -107,18 +74,12 @@ public sealed class AuthorService(IDbContextFactory<MidrandBooksDbContext> conte
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Result> UpdateAuthorAsync(long authorId, UpdateAuthor request, CancellationToken cancellationToken)
|
||||
public async ValueTask<Result> UpdateAuthorAsync(long authorId, UpdateAuthor request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
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);
|
||||
|
||||
if (author is null)
|
||||
@@ -151,12 +112,12 @@ public sealed class AuthorService(IDbContextFactory<MidrandBooksDbContext> conte
|
||||
{
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
if(await context.Authors.AnyAsync(a => a.Name == request.Name && a.LastName == request.LastName, cancellationToken))
|
||||
if (await context.Authors.AnyAsync(a => a.Name == request.Name && a.LastName == request.LastName, cancellationToken))
|
||||
return Result.Fail<long>(new Error($"An author with the name {request.Name} {request.LastName} already exists"));
|
||||
|
||||
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"));
|
||||
|
||||
var newAuthor = context.Authors.Add(new Entities.Author
|
||||
|
||||
@@ -18,7 +18,8 @@ public sealed class AuthorConfiguration : IEntityTypeConfiguration<Author>
|
||||
builder.Property(f => f.Website).IsRequired(false).HasMaxLength(1024);
|
||||
builder.Property(f => f.ImageUrl).IsRequired().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.OwnsMany(f => f.SocialMedia, b => { b.ToJson(); });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public class Author
|
||||
|
||||
public string? ThumbnailImageUrl { get; set; }
|
||||
|
||||
public SocialMedia[]? SocialMedia { get; set; }
|
||||
public ICollection<SocialMedia>? SocialMedia { 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);
|
||||
|
||||
if (await context.Customers.AnyAsync(c => c.Email!.Equals(request.Email, StringComparison.OrdinalIgnoreCase), cancellationToken))
|
||||
if (await context.Customers.AnyAsync(c => EF.Functions.ILike(c.Email!, $"%{request.Email}%"), cancellationToken))
|
||||
return Result.Fail<long>(new Error($"Customer with email '{request.Email}' already exists."));
|
||||
|
||||
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))
|
||||
return Result.Fail<long>(new Error($"Customer with ID '{customerId}' does not exist."));
|
||||
|
||||
if (await context.Contacts.AnyAsync(cc => cc.CustomerId == customerId && cc.Email!.Equals(request.Email, StringComparison.OrdinalIgnoreCase), cancellationToken))
|
||||
if (await context.Contacts.AnyAsync(cc => cc.CustomerId == customerId && EF.Functions.ILike(cc.Email!, $"%{request.Email}%"), cancellationToken))
|
||||
return Result.Fail<long>(new Error($"Contact with email '{request.Email}' already exists for this customer."));
|
||||
|
||||
var contact = context.Contacts.Add(new Entities.Contact
|
||||
@@ -139,21 +139,19 @@ public sealed class CustomerService(IDbContextFactory<MidrandBooksDbContext> con
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var contact = await context.Contacts.FirstOrDefaultAsync(cc => cc.Id == contactId, cancellationToken);
|
||||
var rowsUpdated = await context.Contacts
|
||||
.Where(cc => cc.Id == contactId)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(cc => cc.Name, request.Name)
|
||||
.SetProperty(cc => cc.LastName, request.LastName)
|
||||
.SetProperty(cc => cc.Email, request.Email)
|
||||
.SetProperty(cc => cc.Phone, request.Phone)
|
||||
.SetProperty(cc => cc.Type, request.Type)
|
||||
.SetProperty(cc => cc.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||
|
||||
if (contact is null)
|
||||
return 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
|
||||
return rowsUpdated > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail(new Error("Failed to update customer contact."));
|
||||
: Result.Fail(new Error($"Contact with ID '{contactId}' does not exist."));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -167,25 +165,23 @@ public sealed class CustomerService(IDbContextFactory<MidrandBooksDbContext> con
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var address = await context.Addresses.FirstOrDefaultAsync(a => a.Id == addressId, cancellationToken);
|
||||
var rowsUpdated = await context.Addresses
|
||||
.Where(a => a.Id == addressId)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(a => a.Street, request.Street)
|
||||
.SetProperty(a => a.City, request.City)
|
||||
.SetProperty(a => a.State, request.State)
|
||||
.SetProperty(a => a.PostalCode, request.PostalCode)
|
||||
.SetProperty(a => a.Country, request.Country)
|
||||
.SetProperty(a => a.Type, request.Type)
|
||||
.SetProperty(a => a.BuildingType, request.BuildingType)
|
||||
.SetProperty(a => a.IsPrimary, request.IsPrimary)
|
||||
.SetProperty(a => a.Name, request.Name)
|
||||
.SetProperty(a => a.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||
|
||||
if (address is null)
|
||||
return 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
|
||||
return rowsUpdated > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail(new Error("Failed to update customer address."));
|
||||
: Result.Fail(new Error($"Address with ID '{addressId}' does not exist."));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -199,17 +195,15 @@ public sealed class CustomerService(IDbContextFactory<MidrandBooksDbContext> con
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var customer = await context.Customers.FirstOrDefaultAsync(c => c.Id == customerId, cancellationToken);
|
||||
var rowsUpdated = await context.Customers
|
||||
.Where(c => c.Id == customerId)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(c => c.Enabled, enabled)
|
||||
.SetProperty(c => c.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||
|
||||
if (customer is null)
|
||||
return Result.Fail(new Error($"Customer with ID '{customerId}' does not exist."));
|
||||
|
||||
customer.Enabled = enabled;
|
||||
customer.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
return rowsUpdated > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail(new Error("Failed to update customer status."));
|
||||
: Result.Fail(new Error($"Customer with ID '{customerId}' does not exist."));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -223,18 +217,16 @@ public sealed class CustomerService(IDbContextFactory<MidrandBooksDbContext> con
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var contact = await context.Contacts.FirstOrDefaultAsync(cc => cc.Id == contactId, cancellationToken);
|
||||
var rowsUpdated = await context.Contacts
|
||||
.Where(cc => cc.Id == contactId)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(cc => cc.Enabled, enabled)
|
||||
.SetProperty(cc => cc.IsPrimary, isPrimary)
|
||||
.SetProperty(cc => cc.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||
|
||||
if (contact is null)
|
||||
return 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
|
||||
return rowsUpdated > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail(new Error("Failed to update customer contact status."));
|
||||
: Result.Fail(new Error($"Contact with ID '{contactId}' does not exist."));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -248,18 +240,16 @@ public sealed class CustomerService(IDbContextFactory<MidrandBooksDbContext> con
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var address = await context.Addresses.FirstOrDefaultAsync(a => a.Id == addressId, cancellationToken);
|
||||
var rowsUpdated = await context.Addresses
|
||||
.Where(a => a.Id == addressId)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(a => a.Enabled, enabled)
|
||||
.SetProperty(a => a.IsPrimary, isPrimary)
|
||||
.SetProperty(a => a.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||
|
||||
if (address is null)
|
||||
return 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
|
||||
return rowsUpdated > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail(new Error("Failed to update customer address status."));
|
||||
: Result.Fail(new Error($"Address with ID '{addressId}' does not exist."));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -14,7 +14,8 @@ public sealed class CustomerConfiguration : IEntityTypeConfiguration<Customer>
|
||||
builder.Property(c => c.Email).IsRequired();
|
||||
builder.Property(c => c.Phone).IsRequired();
|
||||
builder.Property(c => c.Website).IsRequired();
|
||||
builder.Property(c => c.SocialMedia).IsRequired(false).HasColumnType("jsonb");
|
||||
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 SocialMedia[]? SocialMedia { get; set; }
|
||||
public ICollection<SocialMedia>? SocialMedia { get; set; }
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ public record CreateCustomer
|
||||
|
||||
public string? Phone { get; set; }
|
||||
|
||||
public SocialMedia[]? SocialMedia { get; set; }
|
||||
public ICollection<SocialMedia>? SocialMedia { get; set; }
|
||||
}
|
||||
|
||||
public sealed record UpdateCustomer : CreateCustomer;
|
||||
|
||||
@@ -17,7 +17,8 @@ public static class Mappers
|
||||
Name = entity.Name,
|
||||
Type = entity.Type,
|
||||
Price = entity.Price,
|
||||
Enabled = entity.Enabled
|
||||
Enabled = entity.Enabled,
|
||||
TrackingUrl = entity.TrackingUrl,
|
||||
};
|
||||
|
||||
public static Shipping ToModel(this Orders.Entities.Shipping entity) => new()
|
||||
|
||||
@@ -8,8 +8,18 @@ public static class Postgres
|
||||
|
||||
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 =>
|
||||
options.UseNpgsql(configuration.GetConnectionString(MidrandBooksDbConfigName)));
|
||||
options.UseNpgsql(dataSource));
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
@@ -4,19 +4,15 @@ namespace LiteCharms.Features.MidrandBooks.Extensions;
|
||||
|
||||
public static class Shop
|
||||
{
|
||||
public static IServiceCollection AddShopServices(this IServiceCollection services, Assembly assembly, ServiceLifetime serviceLifetime)
|
||||
public static IServiceCollection AddShopServices(this IServiceCollection services)
|
||||
{
|
||||
var serviceType = typeof(IService);
|
||||
|
||||
var implementations = assembly.GetTypes()
|
||||
var implementations = Assembly.GetExecutingAssembly().GetTypes()
|
||||
.Where(t => serviceType.IsAssignableFrom(t) && t.IsClass && !t.IsAbstract);
|
||||
|
||||
foreach (var implementation in implementations)
|
||||
{
|
||||
var descriptor = new ServiceDescriptor(serviceType, implementation, serviceLifetime);
|
||||
|
||||
services.Add(descriptor);
|
||||
}
|
||||
foreach (var implementation in implementations)
|
||||
services.AddScoped(implementation);
|
||||
|
||||
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.UpdatedAt).HasDefaultValueSql("now()");
|
||||
builder.Property(o => o.Status).IsRequired();
|
||||
builder.Property(o => o.Total).IsRequired().HasColumnType("decimal(18,2)");
|
||||
builder.Property(o => o.Total).IsRequired().HasPrecision(18, 2);
|
||||
builder.Property(o => o.Notes).HasMaxLength(1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||
|
||||
public sealed record CreateOrder(long CustomerId, decimal TotalPrice, string? Notes);
|
||||
public sealed record CreateOrder(decimal TotalPrice, string? Notes);
|
||||
|
||||
public sealed record CreateOrderItem(long AuthorBookId, long ProductPriceId, int Quantity);
|
||||
|
||||
public sealed record CreateShipping(long OrderId, long AddressId, long ShippingProviderId, string? TrackingNumber);
|
||||
public sealed record CreateShipping(long AddressId, long ShippingProviderId, string? TrackingNumber = null);
|
||||
|
||||
public sealed record CreateShippingProvider(ShippingProviderTypes Type, string Name, decimal Price, string TrackingUrl);
|
||||
|
||||
|
||||
@@ -49,6 +49,17 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
if (!await context.Prices.AnyAsync(pp => pp.Id == request.ProductPriceId, cancellationToken))
|
||||
return Result.Fail<long>("Product price not found.");
|
||||
|
||||
var existingItem = await context.OrderItems.FirstOrDefaultAsync(i => i.ProductPriceId == request.ProductPriceId && i.OrderId == orderId, cancellationToken);
|
||||
|
||||
if(existingItem is not null)
|
||||
{
|
||||
existingItem.Quantity += request.Quantity;
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
? Result.Ok(existingItem.Id)
|
||||
: Result.Fail<long>("Update existing order item.");
|
||||
}
|
||||
|
||||
var orderItem = context.OrderItems.Add(new Entities.OrderItem
|
||||
{
|
||||
OrderId = orderId,
|
||||
@@ -78,9 +89,6 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
|
||||
if (!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken))
|
||||
return Result.Fail("Order not found.");
|
||||
|
||||
var existingItems = context.OrderItems.Where(oi => oi.OrderId == orderId);
|
||||
context.OrderItems.RemoveRange(existingItems);
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
@@ -90,18 +98,23 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
if (!await context.Prices.AnyAsync(pp => pp.Id == item.ProductPriceId, cancellationToken))
|
||||
return Result.Fail($"Product price with ID {item.ProductPriceId} not found.");
|
||||
|
||||
context.OrderItems.Add(new Entities.OrderItem
|
||||
{
|
||||
OrderId = orderId,
|
||||
AuthorBookId = item.AuthorBookId,
|
||||
ProductPriceId = item.ProductPriceId,
|
||||
Quantity = item.Quantity
|
||||
});
|
||||
var existingItem = await context.OrderItems.FirstOrDefaultAsync(i => i.ProductPriceId == item.ProductPriceId && i.OrderId == orderId, cancellationToken);
|
||||
|
||||
if (existingItem is not null)
|
||||
existingItem.Quantity += item.Quantity;
|
||||
else
|
||||
context.OrderItems.Add(new Entities.OrderItem
|
||||
{
|
||||
OrderId = orderId,
|
||||
AuthorBookId = item.AuthorBookId,
|
||||
ProductPriceId = item.ProductPriceId,
|
||||
Quantity = item.Quantity
|
||||
});
|
||||
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail("Failed to add items to order.");
|
||||
return Result.Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -115,16 +128,13 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var orderItem = await context.OrderItems.FirstOrDefaultAsync(oi => oi.Id == orderItemId && oi.OrderId == orderId, cancellationToken);
|
||||
var rowsDeleted = await context.OrderItems
|
||||
.Where(oi => oi.Id == orderItemId && oi.OrderId == orderId)
|
||||
.ExecuteDeleteAsync(cancellationToken);
|
||||
|
||||
if (orderItem is null)
|
||||
return Result.Fail("Order item not found.");
|
||||
|
||||
context.OrderItems.Remove(orderItem);
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
return rowsDeleted > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail("Failed to remove item from order.");
|
||||
: Result.Fail("Order item not found or failed to remove.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -132,15 +142,14 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Result> ClearOrderItemasAsync(long orderId, CancellationToken cancellationToken = default)
|
||||
public async ValueTask<Result> ClearOrderItemsAsync(long orderId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var orderItems = context.OrderItems.Where(oi => oi.OrderId == orderId);
|
||||
|
||||
context.OrderItems.RemoveRange(orderItems);
|
||||
var deletedItems = await context.OrderItems.Where(oi => oi.OrderId == orderId)
|
||||
.ExecuteDeleteAsync(cancellationToken);
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
? Result.Ok()
|
||||
@@ -199,12 +208,15 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
{
|
||||
try
|
||||
{
|
||||
var fromDate = range.From.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc);
|
||||
var toDate = range.To.ToDateTime(TimeOnly.MaxValue, DateTimeKind.Utc);
|
||||
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var orders = await context.Orders
|
||||
.AsNoTracking()
|
||||
.Where(o => o.CreatedAt >= range.From.ToDateTime(TimeOnly.MinValue) && o.CreatedAt <= range.To.ToDateTime(TimeOnly.MaxValue))
|
||||
.Skip(index * range.MaxRecords)
|
||||
.Where(o => o.CreatedAt >= fromDate && o.CreatedAt <= toDate)
|
||||
.Skip(index).Take(range.MaxRecords)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return Result.Ok(orders.Select(o => o.ToModel()).ToArray());
|
||||
@@ -221,17 +233,15 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var order = await context.Orders.FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken);
|
||||
var rowsUpdated = await context.Orders
|
||||
.Where(o => o.Id == orderId)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(o => o.Status, newStatus)
|
||||
.SetProperty(o => o.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||
|
||||
if (order is null)
|
||||
return Result.Fail("Order not found.");
|
||||
|
||||
order.UpdatedAt = DateTime.UtcNow;
|
||||
order.Status = newStatus;
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
return rowsUpdated > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail("Failed to update order status.");
|
||||
: Result.Fail("Order not found or status update failed.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -239,7 +249,7 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Result> AddShippingToOrderAsync(long orderId, CreateShipping request, CancellationToken cancellationToken = default)
|
||||
public async ValueTask<Result<long>> AddShippingToOrderAsync(long orderId, CreateShipping request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -282,17 +292,16 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var shipping = await context.Shippings.FirstOrDefaultAsync(s => s.OrderId == orderId, cancellationToken);
|
||||
var rowsUpdated = await context.Shippings
|
||||
.Where(s => s.OrderId == orderId)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(s => s.Status, newStatus)
|
||||
.SetProperty(s => s.UpdatedAt, DateTime.UtcNow),
|
||||
cancellationToken);
|
||||
|
||||
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
|
||||
return rowsUpdated > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail("Failed to update shipping status.");
|
||||
: Result.Fail("Shipping not found for this order or status update failed.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -325,21 +334,14 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
try
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var rowsDeleted = await context.Shippings
|
||||
.Where(s => s.Id == shippingId && s.OrderId == orderId)
|
||||
.ExecuteDeleteAsync(cancellationToken);
|
||||
|
||||
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
|
||||
return rowsDeleted > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail("Failed to remove shipping from order.");
|
||||
: Result.Fail("Shipping record not found for this order.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -353,17 +355,15 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var shipping = await context.Shippings.FirstOrDefaultAsync(s => s.OrderId == orderId && s.Id == shippingId, cancellationToken);
|
||||
var rowsUpdated = await context.Shippings
|
||||
.Where(s => s.Id == shippingId && s.OrderId == orderId)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(s => s.TrackingNumber, trackingNumber)
|
||||
.SetProperty(s => s.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||
|
||||
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
|
||||
return rowsUpdated > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail("Failed to update shipping tracking number.");
|
||||
: Result.Fail("Shipping record not found for this order.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -371,7 +371,7 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Result> CreateShippingProviderAsync(CreateShippingProvider request, CancellationToken cancellationToken = default)
|
||||
public async ValueTask<Result<long>> CreateShippingProviderAsync(CreateShippingProvider request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -382,6 +382,7 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
|
||||
var shippingProvider = context.ShippingProviders.Add(new Entities.ShippingProvider
|
||||
{
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
Name = request.Name,
|
||||
Type = request.Type,
|
||||
Price = request.Price,
|
||||
@@ -389,7 +390,7 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
});
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
? Result.Ok()
|
||||
? Result.Ok(shippingProvider.Entity.Id)
|
||||
: Result.Fail("Failed to create shipping provider.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -440,24 +441,22 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var provider = await context.ShippingProviders.FirstOrDefaultAsync(sp => sp.Id == request.ProviderId, cancellationToken);
|
||||
var rowsUpdated = await context.ShippingProviders
|
||||
.Where(sp => sp.Id == request.ProviderId)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(sp => sp.Name, request.Name)
|
||||
.SetProperty(sp => sp.Price, request.Price)
|
||||
.SetProperty(sp => sp.TrackingUrl, request.TrackingUrl)
|
||||
.SetProperty(sp => sp.Enabled, request.Enabled)
|
||||
.SetProperty(sp => sp.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||
|
||||
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
|
||||
return rowsUpdated > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail("Failed to update shipping provider status.");
|
||||
: Result.Fail("Shipping provider not found.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,11 @@ public sealed class BookPageConfiguration : IEntityTypeConfiguration<BookPage>
|
||||
builder.Property(bp => bp.AuthorBookId).IsRequired();
|
||||
builder.Property(bp => bp.Content).IsRequired();
|
||||
builder.Property(bp => bp.Type).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.ContentType).IsRequired();
|
||||
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)
|
||||
.WithMany(b => b.Pages)
|
||||
|
||||
@@ -22,7 +22,7 @@ public class BookPage
|
||||
|
||||
public string[]? Notes { get; set; }
|
||||
|
||||
public PageReference[]? References { get; set; }
|
||||
public ICollection<PageReference>? References { get; set; }
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
|
||||
+3
-1
@@ -14,5 +14,7 @@ public class CreateBookPage
|
||||
|
||||
public string[]? Notes { get; set; }
|
||||
|
||||
public PageReference[]? References { get; set; }
|
||||
public ICollection<PageReference>? References { get; set; }
|
||||
}
|
||||
|
||||
public sealed class UpdateBookPage : CreateBookPage;
|
||||
@@ -1,3 +0,0 @@
|
||||
namespace LiteCharms.Features.MidrandBooks.Pages.Models;
|
||||
|
||||
public sealed class UpdateBookPage : CreateBookPage;
|
||||
@@ -13,19 +13,13 @@ public sealed class PageService(IDbContextFactory<MidrandBooksDbContext> context
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
if (!await context.Books.AnyAsync(b => b.Id == authorBookId, cancellationToken))
|
||||
return Result.Fail("Book not found");
|
||||
var rowsDeleted = await context.Pages
|
||||
.Where(p => p.AuthorBookId == authorBookId)
|
||||
.ExecuteDeleteAsync(cancellationToken);
|
||||
|
||||
var pages = await context.Pages.Where(p => p.AuthorBookId == authorBookId).ToListAsync(cancellationToken);
|
||||
|
||||
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();
|
||||
return rowsDeleted > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail("No pages found for the specified book");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -39,16 +33,13 @@ public sealed class PageService(IDbContextFactory<MidrandBooksDbContext> context
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var page = await context.Pages.FirstOrDefaultAsync(p => p.AuthorBookId == authorBookId && p.Number == pageNumber && p.Type == pageType, cancellationToken);
|
||||
var rowsDeleted = await context.Pages
|
||||
.Where(p => p.AuthorBookId == authorBookId && p.Number == pageNumber && p.Type == pageType)
|
||||
.ExecuteDeleteAsync(cancellationToken);
|
||||
|
||||
if (page is null)
|
||||
return Result.Fail("Page not found");
|
||||
|
||||
context.Pages.Remove(page);
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
return rowsDeleted > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail("Failed to delete page");
|
||||
: Result.Fail("Page not found");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -62,17 +53,15 @@ public sealed class PageService(IDbContextFactory<MidrandBooksDbContext> context
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var page = await context.Pages.FirstOrDefaultAsync(p => p.Id == bookPageId, cancellationToken);
|
||||
var rowsUpdated = await context.Pages
|
||||
.Where(p => p.Id == bookPageId)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(p => p.Enabled, enabled)
|
||||
.SetProperty(p => p.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||
|
||||
if (page is null)
|
||||
return Result.Fail("Page not found");
|
||||
|
||||
page.UpdatedAt = DateTime.UtcNow;
|
||||
page.Enabled = enabled;
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
return rowsUpdated > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail("Failed to update page status");
|
||||
: Result.Fail("Page not found");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -86,16 +75,13 @@ public sealed class PageService(IDbContextFactory<MidrandBooksDbContext> context
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var page = await context.Pages.FirstOrDefaultAsync(p => p.Id == bookPageId, cancellationToken);
|
||||
var rowsDeleted = await context.Pages
|
||||
.Where(p => p.Id == bookPageId)
|
||||
.ExecuteDeleteAsync(cancellationToken);
|
||||
|
||||
if (page is null)
|
||||
return Result.Fail("Page not found");
|
||||
|
||||
context.Pages.Remove(page);
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
return rowsDeleted > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail("Failed to delete page");
|
||||
: Result.Fail("Page not found");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -109,22 +95,20 @@ public sealed class PageService(IDbContextFactory<MidrandBooksDbContext> context
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var page = await context.Pages.FirstOrDefaultAsync(p => p.Id == bookPageId, cancellationToken);
|
||||
var rowsUpdated = await context.Pages
|
||||
.Where(p => p.Id == bookPageId)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(p => p.Type, request.Type)
|
||||
.SetProperty(p => p.ContentType, request.ContentType)
|
||||
.SetProperty(p => p.Number, request.Number)
|
||||
.SetProperty(p => p.Content, request.Content)
|
||||
.SetProperty(p => p.Notes, request.Notes)
|
||||
.SetProperty(p => p.References, request.References)
|
||||
.SetProperty(p => p.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||
|
||||
if (page is null)
|
||||
return 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
|
||||
return rowsUpdated > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail("Failed to update page");
|
||||
: Result.Fail("Page not found");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
+129
-66
@@ -1,7 +1,6 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using LiteCharms.Features.MidrandBooks.Postgres;
|
||||
using LiteCharms.Features.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
@@ -13,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
{
|
||||
[DbContext(typeof(MidrandBooksDbContext))]
|
||||
[Migration("20260527070840_Init")]
|
||||
[Migration("20260529070104_Init")]
|
||||
partial class Init
|
||||
{
|
||||
/// <inheritdoc />
|
||||
@@ -112,9 +111,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
b.Property<int>("PublisherType")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<SocialMedia[]>("SocialMedia")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.Property<string>("ThumbnailImageUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
@@ -291,9 +287,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<SocialMedia[]>("SocialMedia")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -338,7 +331,8 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<decimal>("Total")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("numeric(18,2)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -494,17 +488,14 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.PrimitiveCollection<string>("Notes")
|
||||
.HasColumnType("jsonb");
|
||||
b.PrimitiveCollection<string[]>("Notes")
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<int>("Number")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<PageReference[]>("References")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer");
|
||||
|
||||
@@ -570,8 +561,8 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.PrimitiveCollection<string>("Categories")
|
||||
.HasColumnType("jsonb");
|
||||
b.PrimitiveCollection<string[]>("Categories")
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -591,9 +582,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)");
|
||||
|
||||
b.Property<ProductMetadata>("Metadata")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
@@ -604,8 +592,8 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.PrimitiveCollection<string>("ThumbnailUrls")
|
||||
.HasColumnType("jsonb");
|
||||
b.PrimitiveCollection<string[]>("ThumbnailUrls")
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer");
|
||||
@@ -661,40 +649,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.HasOne("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", "Author")
|
||||
@@ -714,6 +668,38 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
b.Navigation("Product");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", b =>
|
||||
{
|
||||
b.OwnsMany("LiteCharms.Features.Models.SocialMedia", "SocialMedia", b1 =>
|
||||
{
|
||||
b1.Property<long>("AuthorId");
|
||||
|
||||
b1.Property<int>("__synthesizedOrdinal")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b1.Property<string>("ImageUrl");
|
||||
|
||||
b1.Property<string>("Name");
|
||||
|
||||
b1.Property<int>("Type");
|
||||
|
||||
b1.Property<string>("Url");
|
||||
|
||||
b1.HasKey("AuthorId", "__synthesizedOrdinal");
|
||||
|
||||
b1.ToTable("Authors");
|
||||
|
||||
b1
|
||||
.ToJson("SocialMedia")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("AuthorId");
|
||||
});
|
||||
|
||||
b.Navigation("SocialMedia");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Address", b =>
|
||||
{
|
||||
b.HasOne("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", "Customer")
|
||||
@@ -736,6 +722,38 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
b.Navigation("Customer");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", b =>
|
||||
{
|
||||
b.OwnsMany("LiteCharms.Features.Models.SocialMedia", "SocialMedia", b1 =>
|
||||
{
|
||||
b1.Property<long>("CustomerId");
|
||||
|
||||
b1.Property<int>("__synthesizedOrdinal")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b1.Property<string>("ImageUrl");
|
||||
|
||||
b1.Property<string>("Name");
|
||||
|
||||
b1.Property<int>("Type");
|
||||
|
||||
b1.Property<string>("Url");
|
||||
|
||||
b1.HasKey("CustomerId", "__synthesizedOrdinal");
|
||||
|
||||
b1.ToTable("Customers");
|
||||
|
||||
b1
|
||||
.ToJson("SocialMedia")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CustomerId");
|
||||
});
|
||||
|
||||
b.Navigation("SocialMedia");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.OrderItem", b =>
|
||||
{
|
||||
b.HasOne("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", "AuthorBook")
|
||||
@@ -798,7 +816,34 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
.OnDelete(DeleteBehavior.NoAction)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsMany("LiteCharms.Features.Models.PageReference", "References", b1 =>
|
||||
{
|
||||
b1.Property<long>("BookPageId");
|
||||
|
||||
b1.Property<int>("__synthesizedOrdinal")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b1.Property<string>("Description");
|
||||
|
||||
b1.Property<string>("Tag");
|
||||
|
||||
b1.Property<string>("Url");
|
||||
|
||||
b1.HasKey("BookPageId", "__synthesizedOrdinal");
|
||||
|
||||
b1.ToTable("BookPages");
|
||||
|
||||
b1
|
||||
.ToJson("References")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("BookPageId");
|
||||
});
|
||||
|
||||
b.Navigation("Book");
|
||||
|
||||
b.Navigation("References");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Payments.Entities.Refund", b =>
|
||||
@@ -812,6 +857,35 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
b.Navigation("Order");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
||||
{
|
||||
b.OwnsOne("LiteCharms.Features.Models.ProductMetadata", "Metadata", b1 =>
|
||||
{
|
||||
b1.Property<long>("ProductId");
|
||||
|
||||
b1.Property<string>("CopyrightInfo");
|
||||
|
||||
b1.Property<string>("ManufactureDate");
|
||||
|
||||
b1.Property<string>("Manufacturer");
|
||||
|
||||
b1.Property<string>("SerialNumber");
|
||||
|
||||
b1.HasKey("ProductId");
|
||||
|
||||
b1.ToTable("Products");
|
||||
|
||||
b1
|
||||
.ToJson("Metadata")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ProductId");
|
||||
});
|
||||
|
||||
b.Navigation("Metadata");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", b =>
|
||||
{
|
||||
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", "Product")
|
||||
@@ -823,15 +897,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.Navigation("Pages");
|
||||
@@ -865,8 +930,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
|
||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
||||
{
|
||||
b.Navigation("Price");
|
||||
|
||||
b.Navigation("Prices");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
+12
-46
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using LiteCharms.Features.Models;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
@@ -31,8 +30,8 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
Website = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
ImageUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: false),
|
||||
ThumbnailImageUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
|
||||
SocialMedia = table.Column<SocialMedia[]>(type: "jsonb", nullable: true),
|
||||
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true)
|
||||
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
|
||||
SocialMedia = table.Column<string>(type: "jsonb", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@@ -52,8 +51,8 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
Email = table.Column<string>(type: "text", nullable: false),
|
||||
Website = table.Column<string>(type: "text", nullable: false),
|
||||
Phone = table.Column<string>(type: "text", nullable: false),
|
||||
SocialMedia = table.Column<SocialMedia[]>(type: "jsonb", nullable: true),
|
||||
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true)
|
||||
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
|
||||
SocialMedia = table.Column<string>(type: "jsonb", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@@ -70,7 +69,7 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||
CustomerId = table.Column<long>(type: "bigint", nullable: false),
|
||||
Status = table.Column<int>(type: "integer", nullable: false),
|
||||
Total = table.Column<decimal>(type: "numeric(18,2)", nullable: false),
|
||||
Total = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
Notes = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
|
||||
InvoiceUrl = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
@@ -92,10 +91,10 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
Summary = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: false),
|
||||
Description = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
ImageUrl = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
ThumbnailUrls = table.Column<string>(type: "jsonb", nullable: true),
|
||||
Categories = table.Column<string>(type: "jsonb", nullable: true),
|
||||
Metadata = table.Column<ProductMetadata>(type: "jsonb", nullable: true),
|
||||
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false)
|
||||
ThumbnailUrls = table.Column<string[]>(type: "text[]", nullable: true),
|
||||
Categories = table.Column<string[]>(type: "text[]", nullable: true),
|
||||
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
|
||||
Metadata = table.Column<string>(type: "jsonb", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@@ -260,30 +259,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
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(
|
||||
name: "Shippings",
|
||||
columns: table => new
|
||||
@@ -334,9 +309,9 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
ContentType = table.Column<int>(type: "integer", nullable: false),
|
||||
Number = table.Column<int>(type: "integer", nullable: false, defaultValue: 0),
|
||||
Content = table.Column<byte[]>(type: "bytea", nullable: false),
|
||||
Notes = table.Column<string>(type: "jsonb", nullable: true),
|
||||
References = table.Column<PageReference[]>(type: "jsonb", nullable: true),
|
||||
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true)
|
||||
Notes = table.Column<string[]>(type: "text[]", nullable: true),
|
||||
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
|
||||
References = table.Column<string>(type: "jsonb", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@@ -428,12 +403,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
table: "Prices",
|
||||
column: "ProductId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ProductPrice_ProductId",
|
||||
table: "ProductPrice",
|
||||
column: "ProductId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Refunds_OrderId",
|
||||
table: "Refunds",
|
||||
@@ -468,9 +437,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
migrationBuilder.DropTable(
|
||||
name: "OrderItems");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ProductPrice");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Refunds");
|
||||
|
||||
+128
-65
@@ -1,7 +1,6 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using LiteCharms.Features.MidrandBooks.Postgres;
|
||||
using LiteCharms.Features.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
@@ -109,9 +108,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
b.Property<int>("PublisherType")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<SocialMedia[]>("SocialMedia")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.Property<string>("ThumbnailImageUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
@@ -288,9 +284,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<SocialMedia[]>("SocialMedia")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -335,7 +328,8 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<decimal>("Total")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("numeric(18,2)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -491,17 +485,14 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.PrimitiveCollection<string>("Notes")
|
||||
.HasColumnType("jsonb");
|
||||
b.PrimitiveCollection<string[]>("Notes")
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<int>("Number")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<PageReference[]>("References")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer");
|
||||
|
||||
@@ -567,8 +558,8 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.PrimitiveCollection<string>("Categories")
|
||||
.HasColumnType("jsonb");
|
||||
b.PrimitiveCollection<string[]>("Categories")
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -588,9 +579,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)");
|
||||
|
||||
b.Property<ProductMetadata>("Metadata")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
@@ -601,8 +589,8 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.PrimitiveCollection<string>("ThumbnailUrls")
|
||||
.HasColumnType("jsonb");
|
||||
b.PrimitiveCollection<string[]>("ThumbnailUrls")
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer");
|
||||
@@ -658,40 +646,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.HasOne("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", "Author")
|
||||
@@ -711,6 +665,38 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
b.Navigation("Product");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", b =>
|
||||
{
|
||||
b.OwnsMany("LiteCharms.Features.Models.SocialMedia", "SocialMedia", b1 =>
|
||||
{
|
||||
b1.Property<long>("AuthorId");
|
||||
|
||||
b1.Property<int>("__synthesizedOrdinal")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b1.Property<string>("ImageUrl");
|
||||
|
||||
b1.Property<string>("Name");
|
||||
|
||||
b1.Property<int>("Type");
|
||||
|
||||
b1.Property<string>("Url");
|
||||
|
||||
b1.HasKey("AuthorId", "__synthesizedOrdinal");
|
||||
|
||||
b1.ToTable("Authors");
|
||||
|
||||
b1
|
||||
.ToJson("SocialMedia")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("AuthorId");
|
||||
});
|
||||
|
||||
b.Navigation("SocialMedia");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Address", b =>
|
||||
{
|
||||
b.HasOne("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", "Customer")
|
||||
@@ -733,6 +719,38 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
b.Navigation("Customer");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", b =>
|
||||
{
|
||||
b.OwnsMany("LiteCharms.Features.Models.SocialMedia", "SocialMedia", b1 =>
|
||||
{
|
||||
b1.Property<long>("CustomerId");
|
||||
|
||||
b1.Property<int>("__synthesizedOrdinal")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b1.Property<string>("ImageUrl");
|
||||
|
||||
b1.Property<string>("Name");
|
||||
|
||||
b1.Property<int>("Type");
|
||||
|
||||
b1.Property<string>("Url");
|
||||
|
||||
b1.HasKey("CustomerId", "__synthesizedOrdinal");
|
||||
|
||||
b1.ToTable("Customers");
|
||||
|
||||
b1
|
||||
.ToJson("SocialMedia")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CustomerId");
|
||||
});
|
||||
|
||||
b.Navigation("SocialMedia");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.OrderItem", b =>
|
||||
{
|
||||
b.HasOne("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", "AuthorBook")
|
||||
@@ -795,7 +813,34 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
.OnDelete(DeleteBehavior.NoAction)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsMany("LiteCharms.Features.Models.PageReference", "References", b1 =>
|
||||
{
|
||||
b1.Property<long>("BookPageId");
|
||||
|
||||
b1.Property<int>("__synthesizedOrdinal")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b1.Property<string>("Description");
|
||||
|
||||
b1.Property<string>("Tag");
|
||||
|
||||
b1.Property<string>("Url");
|
||||
|
||||
b1.HasKey("BookPageId", "__synthesizedOrdinal");
|
||||
|
||||
b1.ToTable("BookPages");
|
||||
|
||||
b1
|
||||
.ToJson("References")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("BookPageId");
|
||||
});
|
||||
|
||||
b.Navigation("Book");
|
||||
|
||||
b.Navigation("References");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Payments.Entities.Refund", b =>
|
||||
@@ -809,6 +854,35 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
b.Navigation("Order");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
||||
{
|
||||
b.OwnsOne("LiteCharms.Features.Models.ProductMetadata", "Metadata", b1 =>
|
||||
{
|
||||
b1.Property<long>("ProductId");
|
||||
|
||||
b1.Property<string>("CopyrightInfo");
|
||||
|
||||
b1.Property<string>("ManufactureDate");
|
||||
|
||||
b1.Property<string>("Manufacturer");
|
||||
|
||||
b1.Property<string>("SerialNumber");
|
||||
|
||||
b1.HasKey("ProductId");
|
||||
|
||||
b1.ToTable("Products");
|
||||
|
||||
b1
|
||||
.ToJson("Metadata")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ProductId");
|
||||
});
|
||||
|
||||
b.Navigation("Metadata");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", b =>
|
||||
{
|
||||
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", "Product")
|
||||
@@ -820,15 +894,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.Navigation("Pages");
|
||||
@@ -862,8 +927,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
|
||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
||||
{
|
||||
b.Navigation("Price");
|
||||
|
||||
b.Navigation("Prices");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
|
||||
@@ -4,6 +4,8 @@ public sealed class ProductConfiguration : IEntityTypeConfiguration<Product>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Product> builder)
|
||||
{
|
||||
builder.Ignore(p => p.Price);
|
||||
|
||||
builder.ToTable("Products");
|
||||
|
||||
builder.HasKey(f => f.Id);
|
||||
@@ -14,9 +16,10 @@ public sealed class ProductConfiguration : IEntityTypeConfiguration<Product>
|
||||
builder.Property(f => f.Summary).IsRequired().HasMaxLength(512);
|
||||
builder.Property(f => f.Description).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.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>]
|
||||
public class ProductPrice : Models.ProductPrice
|
||||
{
|
||||
public virtual Product Product { get; set; } = new();
|
||||
public virtual Product? Product { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
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,3 +20,10 @@ public sealed record CreateProduct
|
||||
|
||||
public ProductMetadata? Metadata { get; set; }
|
||||
}
|
||||
|
||||
public sealed class CreateProductPrice
|
||||
{
|
||||
public decimal Amount { get; set; }
|
||||
|
||||
public decimal Discount { get; set; }
|
||||
}
|
||||
|
||||
@@ -14,17 +14,15 @@ public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> cont
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var productPrice = await context.Prices.FirstOrDefaultAsync(p => p.Id == productPriceId, cancellationToken);
|
||||
var rowsUpdated = await context.Prices
|
||||
.Where(p => p.Id == productPriceId)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(p => p.Enabled, isEnabled)
|
||||
.SetProperty(p => p.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||
|
||||
if (productPrice is null)
|
||||
return Result.Fail(new Error($"Product price with ID {productPriceId} not found"));
|
||||
|
||||
productPrice.UpdatedAt = DateTime.UtcNow;
|
||||
productPrice.Enabled = isEnabled;
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
return rowsUpdated > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail(new Error($"Failed to change status of product price with ID {productPriceId}"));
|
||||
: Result.Fail(new Error($"Product price with ID {productPriceId} not found"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -38,17 +36,15 @@ public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> cont
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var product = await context.Products.FirstOrDefaultAsync(p => p.Id == productId, cancellationToken);
|
||||
var rowsUpdated = await context.Products
|
||||
.Where(p => p.Id == productId)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(p => p.Enabled, isEnabled)
|
||||
.SetProperty(p => p.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||
|
||||
if (product is null)
|
||||
return Result.Fail(new Error($"Product with ID {productId} not found"));
|
||||
|
||||
product.UpdatedAt = DateTime.UtcNow;
|
||||
product.Enabled = isEnabled;
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
return rowsUpdated > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail(new Error($"Failed to change status of product with ID {productId}"));
|
||||
: Result.Fail(new Error($"Product with ID {productId} not found"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -64,17 +60,22 @@ public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> cont
|
||||
|
||||
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))
|
||||
query = query.Where(p => p.Name!.Contains(filter.Title));
|
||||
query = query.Where(p => EF.Functions.ILike(p.Name!, $"%{filter.Title}%"));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.Category))
|
||||
query = query.Where(p => p.Categories!.Any(c => c == filter.Category));
|
||||
query = query.Where(p => p.Categories.Contains(filter.Category));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.Manufacturer))
|
||||
query = query.Where(p => p.Metadata!.Manufacturer == filter.Manufacturer);
|
||||
query = query.Where(p => EF.Functions.ILike(p.Metadata!.Manufacturer!, $"%{filter.Manufacturer}%"));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.SerialNumber))
|
||||
query = query.Where(p => p.Metadata!.SerialNumber == filter.SerialNumber);
|
||||
query = query.Where(p => EF.Functions.ILike(p.Metadata!.SerialNumber!, $"%{filter.SerialNumber}%"));
|
||||
|
||||
if (filter.MinPrice > 0)
|
||||
query = query.Where(p => p.Prices!.Any(pr => pr.Amount >= filter.MinPrice && pr.Amount <= filter.MaxPrice));
|
||||
@@ -165,7 +166,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
|
||||
{
|
||||
@@ -175,16 +176,16 @@ public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> cont
|
||||
.AsNoTracking()
|
||||
.OrderByDescending(p => p.CreatedAt)
|
||||
.ThenBy(p => p.UpdatedAt)
|
||||
.FirstOrDefaultAsync(p => p.ProductId == productId, cancellationToken);
|
||||
.FirstOrDefaultAsync(p => p.ProductId == productId && p.Enabled, cancellationToken);
|
||||
|
||||
return product is not null
|
||||
? Result.Ok(new[] { product.ToModel() })
|
||||
: Result.Fail<ProductPrice[]>(new Error($"No price found for product with ID {productId}"));
|
||||
? Result.Ok(product.ToModel())
|
||||
: Result.Fail<ProductPrice>(new Error($"No price found for product with ID {productId}"));
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail<ProductPrice[]>(new Error(ex.Message).CausedBy(ex));
|
||||
return Result.Fail<ProductPrice>(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,8 +229,8 @@ public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> cont
|
||||
{
|
||||
try
|
||||
{
|
||||
var fromDate = range.From.ToDateTime(TimeOnly.MinValue);
|
||||
var toDate = range.To.ToDateTime(TimeOnly.MaxValue);
|
||||
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);
|
||||
|
||||
@@ -244,7 +245,7 @@ public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> cont
|
||||
.AsSplitQuery()
|
||||
.ToArrayAsync(cancellationToken);
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
return products?.Length > 0
|
||||
? Result.Ok(products.Select(p => p.ToModel()).ToArray())
|
||||
: Result.Fail<Product[]>(new Error("Failed to retrieve products."));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user