diff --git a/LiteCharms.Features.MidrandBooks/AuthorBooks/BooksService.cs b/LiteCharms.Features.MidrandBooks/AuthorBooks/BooksService.cs index f3ba48b..2fdfe2b 100644 --- a/LiteCharms.Features.MidrandBooks/AuthorBooks/BooksService.cs +++ b/LiteCharms.Features.MidrandBooks/AuthorBooks/BooksService.cs @@ -30,7 +30,7 @@ public class BooksService(IDbContextFactory contextFactor } } - public async ValueTask> PublishBookAsync(long authorId, long productId, CancellationToken cancellationToken = default) + public async ValueTask> CreateBookAsync(long authorId, long productId, CancellationToken cancellationToken = default) { try { @@ -65,7 +65,11 @@ public class BooksService(IDbContextFactory contextFactor await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var book = await context.Books - .AsNoTracking().FirstOrDefaultAsync(b => b.Id == bookId, cancellationToken); + .AsNoTracking() + .Include(b => b.Author) + .Include(b => b.Product!.Price) + .Include(b => b.Pages) + .FirstOrDefaultAsync(b => b.Id == bookId, cancellationToken); return book is null ? Result.Fail(new Error($"Book with ID {bookId} not found")) @@ -88,6 +92,8 @@ public class BooksService(IDbContextFactory contextFactor var books = await context.Books .AsNoTracking() + .Include(b => b.Author) + .Include(b => b.Product!.Price) .OrderByDescending(b => b.CreatedAt) .Where(b => b.AuthorId == authorId) .ToListAsync(cancellationToken); @@ -101,4 +107,34 @@ public class BooksService(IDbContextFactory contextFactor return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } + + public async ValueTask> GetPublishedBooksAsync(int offset, int limit, CancellationToken cancellationToken = default) + { + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var books = await context.Books + .AsNoTracking() + .Include(b => b.Author) + .Include(b => b.Product!.Price) + .Include(b => b.Pages) + .Where(b => b.Enabled && b.Product!.Enabled && b.Author.Enabled) + .OrderByDescending(b => b.Ranking) + .ThenByDescending(b => b.Ranking) + .ThenByDescending(b => b.CreatedAt) + .ThenByDescending(b => b.UpdatedAt) + .Skip(offset).Take(limit) + .AsSplitQuery() + .ToArrayAsync(cancellationToken); + + return books?.Length > 0 + ? Result.Ok(books.Select(b => b.ToModel()).ToArray()) + : Result.Fail(new Error("No published books found.")); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } } diff --git a/LiteCharms.Features.MidrandBooks/AuthorBooks/Entities/AuthorBook.cs b/LiteCharms.Features.MidrandBooks/AuthorBooks/Entities/AuthorBook.cs index 9c44f39..c1282e8 100644 --- a/LiteCharms.Features.MidrandBooks/AuthorBooks/Entities/AuthorBook.cs +++ b/LiteCharms.Features.MidrandBooks/AuthorBooks/Entities/AuthorBook.cs @@ -8,7 +8,7 @@ public class AuthorBook : Models.AuthorBook { public virtual Author Author { get; set; } = new(); - public virtual Product Book { get; set; } = new(); + public new virtual Product? Product { get; set; } public virtual ICollection Pages { get; set; } = []; } diff --git a/LiteCharms.Features.MidrandBooks/AuthorBooks/Entities/AuthorBookConfiguration.cs b/LiteCharms.Features.MidrandBooks/AuthorBooks/Entities/AuthorBookConfiguration.cs index 1f18c56..3f852ac 100644 --- a/LiteCharms.Features.MidrandBooks/AuthorBooks/Entities/AuthorBookConfiguration.cs +++ b/LiteCharms.Features.MidrandBooks/AuthorBooks/Entities/AuthorBookConfiguration.cs @@ -20,7 +20,7 @@ public class AuthorBookConfiguration : IEntityTypeConfiguration .HasForeignKey(f => f.AuthorId) .OnDelete(DeleteBehavior.Restrict); - builder.HasOne(f => f.Book) + builder.HasOne(f => f.Product) .WithMany() .HasForeignKey(f => f.ProductId) .OnDelete(DeleteBehavior.Restrict); diff --git a/LiteCharms.Features.MidrandBooks/AuthorBooks/Models/AuthorBook.cs b/LiteCharms.Features.MidrandBooks/AuthorBooks/Models/AuthorBook.cs index 5d77fe9..6ac8dce 100644 --- a/LiteCharms.Features.MidrandBooks/AuthorBooks/Models/AuthorBook.cs +++ b/LiteCharms.Features.MidrandBooks/AuthorBooks/Models/AuthorBook.cs @@ -1,4 +1,6 @@ -namespace LiteCharms.Features.MidrandBooks.AuthorBooks.Models; +using LiteCharms.Features.MidrandBooks.Products.Models; + +namespace LiteCharms.Features.MidrandBooks.AuthorBooks.Models; public class AuthorBook { @@ -16,5 +18,7 @@ public class AuthorBook public int Ranking { get; set; } + public Product? Product { get; set; } + public bool Enabled { get; set; } } diff --git a/LiteCharms.Features.MidrandBooks/Authors/AuthorService.cs b/LiteCharms.Features.MidrandBooks/Authors/AuthorService.cs index b9cc707..3897349 100644 --- a/LiteCharms.Features.MidrandBooks/Authors/AuthorService.cs +++ b/LiteCharms.Features.MidrandBooks/Authors/AuthorService.cs @@ -1,4 +1,5 @@ -using LiteCharms.Features.MidrandBooks.Authors.Models; +using LiteCharms.Features.MidrandBooks.AuthorBooks.Models; +using LiteCharms.Features.MidrandBooks.Authors.Models; using LiteCharms.Features.MidrandBooks.Extensions; using LiteCharms.Features.MidrandBooks.Postgres; using LiteCharms.Features.MidrandBooks.Products.Models; @@ -8,7 +9,7 @@ namespace LiteCharms.Features.MidrandBooks.Authors; public class AuthorService(IDbContextFactory contextFactory) { - public async ValueTask> GetAuthorBooksAsync(long authorId, CancellationToken cancellationToken) + public async ValueTask> GetAuthorBooksAsync(long authorId, CancellationToken cancellationToken) { try { @@ -17,21 +18,24 @@ public class AuthorService(IDbContextFactory contextFacto 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")); + return Result.Fail(new Error($"Author with ID {authorId} not found")); - var books = await context.Books.AsNoTracking() + var books = await context.Books + .AsNoTracking() + .Include(b => b.Author) + .Include(b => b.Product!.Price) .OrderByDescending(b => b.CreatedAt) .Where(p => p.AuthorId == authorId) - .Select(p => p.Book.ToModel()) + .AsSplitQuery() .ToArrayAsync(cancellationToken); return books?.Length > 0 - ? Result.Ok(books) - : Result.Fail(new Error($"No books found for author with ID {authorId}")); + ? Result.Ok(books.Select(b => b.ToModel()).ToArray()) + : Result.Fail(new Error($"No books found for author with ID {authorId}")); } catch (Exception ex) { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); + return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } diff --git a/LiteCharms.Features.MidrandBooks/Extensions/Mappers.cs b/LiteCharms.Features.MidrandBooks/Extensions/Mappers.cs index 21607dc..4ec3897 100644 --- a/LiteCharms.Features.MidrandBooks/Extensions/Mappers.cs +++ b/LiteCharms.Features.MidrandBooks/Extensions/Mappers.cs @@ -30,7 +30,8 @@ public static class Mappers AuthorId = entity.AuthorId, Ranking = entity.Ranking, Rating = entity.Rating, - Enabled = entity.Enabled + Enabled = entity.Enabled, + Product = entity.Product?.ToModel(), }; public static ProductPrice ToModel(this Products.Entities.ProductPrice entity) => new() @@ -59,7 +60,8 @@ public static class Mappers ThumbnailUrls = entity.ThumbnailUrls, Metadata = entity.Metadata, Categories = entity.Categories, - Enabled = entity.Enabled + Enabled = entity.Enabled, + Price = entity.Prices?.FirstOrDefault(p => p.Enabled)?.ToModel() ?? null, }; } diff --git a/LiteCharms.Features.MidrandBooks/Products/Models/Product.cs b/LiteCharms.Features.MidrandBooks/Products/Models/Product.cs index 42a977f..1207c34 100644 --- a/LiteCharms.Features.MidrandBooks/Products/Models/Product.cs +++ b/LiteCharms.Features.MidrandBooks/Products/Models/Product.cs @@ -26,5 +26,7 @@ public class Product public ProductMetadata? Metadata { get; set; } + public ProductPrice? Price { get; set; } + public bool Enabled { get; set; } } diff --git a/LiteCharms.Features.MidrandBooks/Products/ProductService.cs b/LiteCharms.Features.MidrandBooks/Products/ProductService.cs index 5ee455f..4cc30ab 100644 --- a/LiteCharms.Features.MidrandBooks/Products/ProductService.cs +++ b/LiteCharms.Features.MidrandBooks/Products/ProductService.cs @@ -74,7 +74,7 @@ public class ProductService(IDbContextFactory contextFact if (!string.IsNullOrWhiteSpace(filter.SerialNumber)) query = query.Where(p => 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)); @@ -96,7 +96,7 @@ public class ProductService(IDbContextFactory contextFact { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - if(!await context.Products.AnyAsync(p => p.Id == productId, cancellationToken)) + if (!await context.Products.AnyAsync(p => p.Id == productId, cancellationToken)) return Result.Fail($"Product with ID {productId} does not exist."); var existingPrices = await context.Prices.Where(p => p.ProductId == productId).ToListAsync(cancellationToken); @@ -133,10 +133,10 @@ public class ProductService(IDbContextFactory contextFact { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - if(await context.Products.AnyAsync(p => p.Name == request.Name, cancellationToken)) + if (await context.Products.AnyAsync(p => p.Name == request.Name, cancellationToken)) return Result.Fail("A product with the same name already exists."); - if(request.Metadata is not null) + if (request.Metadata is not null) if (await context.Products.AnyAsync(p => p.Metadata!.SerialNumber == request.Metadata.SerialNumber, cancellationToken)) return Result.Fail("A product with the same metadata already exists."); @@ -223,7 +223,7 @@ public class ProductService(IDbContextFactory contextFact } } - public async ValueTask> GetProductsAsync(DateRange range, CancellationToken cancellationToken = default) + public async ValueTask> GetProductsAsync(int offset, DateRange range, CancellationToken cancellationToken = default) { try { @@ -234,11 +234,14 @@ public class ProductService(IDbContextFactory contextFact var products = await context.Products .AsNoTracking() + .Include(p => p.Prices) .OrderByDescending(p => p.CreatedAt) - .ThenBy(p => p.UpdatedAt) + .ThenByDescending(p => p.UpdatedAt) .Where(p => p.CreatedAt >= fromDate && p.CreatedAt <= toDate) + .Skip(offset) .Take(range.MaxRecords) - .ToArrayAsync(cancellationToken); + .AsSplitQuery() + .ToArrayAsync(cancellationToken); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok(products.Select(p => p.ToModel()).ToArray())