Wrote tests for most services, applied EF core optimisations
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
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
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace LiteCharms.Features.MidrandBooks.Tests;
|
||||
namespace LiteCharms.Features.MidrandBooks.Tests.Common;
|
||||
|
||||
public class IntegrationFactAttribute : FactAttribute
|
||||
{
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
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()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdateShippingStatusAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetShippingByOrderIdAsync_ShouldReturn_ResultWithShipping()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task RemoveShippingFromOrderAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdateShippingTrackingNumberAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task CreateShippingProviderAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetShippingProvidersAsync_ShouldReturn_ResultWithShippingProviderList()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task GetShippingProviderAsync_ShouldReturn_ResultWithShippingProvider()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[IntegrationFact]
|
||||
public async Task UpdateShippingProviderAsync_ShouldReturn_ResultWithSuccess()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -1,10 +1,11 @@
|
||||
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 ProductServiceFeatureTest(Fixture fixture, ITestOutputHelper output) : IClassFixture<Fixture>
|
||||
public class ProductServiceFeatureTests(Fixture fixture, ITestOutputHelper output) : IClassFixture<Fixture>
|
||||
{
|
||||
private readonly ProductService productService = fixture.Services.GetRequiredService<ProductService>();
|
||||
|
||||
@@ -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,7 +112,7 @@ 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))
|
||||
return Result.Fail<long>(new Error($"An author with the name {request.Name} {request.LastName} already exists"));
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -79,9 +90,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)
|
||||
{
|
||||
if (!await context.Books.AnyAsync(ab => ab.Id == item.AuthorBookId, cancellationToken))
|
||||
@@ -90,6 +98,11 @@ 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.");
|
||||
|
||||
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,
|
||||
@@ -97,11 +110,11 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
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)
|
||||
{
|
||||
@@ -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)
|
||||
{
|
||||
@@ -326,20 +335,13 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
if(!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken))
|
||||
return Result.Fail("Order not found.");
|
||||
var rowsDeleted = await context.Shippings
|
||||
.Where(s => s.Id == shippingId && s.OrderId == orderId)
|
||||
.ExecuteDeleteAsync(cancellationToken);
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -440,20 +440,18 @@ 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)
|
||||
{
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -14,5 +14,5 @@ public class CreateBookPage
|
||||
|
||||
public string[]? Notes { get; set; }
|
||||
|
||||
public PageReference[]? References { get; set; }
|
||||
public ICollection<PageReference>? References { get; set; }
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
+7
-7
@@ -57,7 +57,7 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
|
||||
b.HasIndex("ProductId");
|
||||
|
||||
b.ToTable("Books");
|
||||
b.ToTable("Books", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", b =>
|
||||
@@ -454,7 +454,7 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ShippingProviders");
|
||||
b.ToTable("ShippingProviders", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Pages.Entities.BookPage", b =>
|
||||
@@ -677,7 +677,7 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
b.HasIndex("ProductId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ProductPrice");
|
||||
b.ToTable("ProductPrice", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
||||
@@ -718,7 +718,7 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
|
||||
b1.HasKey("AuthorId", "__synthesizedOrdinal");
|
||||
|
||||
b1.ToTable("Authors");
|
||||
b1.ToTable("Authors", (string)null);
|
||||
|
||||
b1
|
||||
.ToJson("SocialMedia")
|
||||
@@ -772,7 +772,7 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
|
||||
b1.HasKey("CustomerId", "__synthesizedOrdinal");
|
||||
|
||||
b1.ToTable("Customers");
|
||||
b1.ToTable("Customers", (string)null);
|
||||
|
||||
b1
|
||||
.ToJson("SocialMedia")
|
||||
@@ -862,7 +862,7 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
|
||||
b1.HasKey("BookPageId", "__synthesizedOrdinal");
|
||||
|
||||
b1.ToTable("BookPages");
|
||||
b1.ToTable("BookPages", (string)null);
|
||||
|
||||
b1
|
||||
.ToJson("References")
|
||||
@@ -904,7 +904,7 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||
|
||||
b1.HasKey("ProductId");
|
||||
|
||||
b1.ToTable("Products");
|
||||
b1.ToTable("Products", (string)null);
|
||||
|
||||
b1
|
||||
.ToJson("Metadata")
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user