Included navigation fields in get queries
This commit is contained in:
@@ -30,7 +30,7 @@ public class BooksService(IDbContextFactory<MidrandBooksDbContext> contextFactor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<Result<long>> PublishBookAsync(long authorId, long productId, CancellationToken cancellationToken = default)
|
public async ValueTask<Result<long>> CreateBookAsync(long authorId, long productId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -65,7 +65,11 @@ public class BooksService(IDbContextFactory<MidrandBooksDbContext> contextFactor
|
|||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var book = await context.Books
|
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
|
return book is null
|
||||||
? Result.Fail<AuthorBook>(new Error($"Book with ID {bookId} not found"))
|
? Result.Fail<AuthorBook>(new Error($"Book with ID {bookId} not found"))
|
||||||
@@ -88,6 +92,8 @@ public class BooksService(IDbContextFactory<MidrandBooksDbContext> contextFactor
|
|||||||
|
|
||||||
var books = await context.Books
|
var books = await context.Books
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
|
.Include(b => b.Author)
|
||||||
|
.Include(b => b.Product!.Price)
|
||||||
.OrderByDescending(b => b.CreatedAt)
|
.OrderByDescending(b => b.CreatedAt)
|
||||||
.Where(b => b.AuthorId == authorId)
|
.Where(b => b.AuthorId == authorId)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
@@ -101,4 +107,34 @@ public class BooksService(IDbContextFactory<MidrandBooksDbContext> contextFactor
|
|||||||
return Result.Fail<AuthorBook[]>(new Error(ex.Message).CausedBy(ex));
|
return Result.Fail<AuthorBook[]>(new Error(ex.Message).CausedBy(ex));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<AuthorBook[]>> 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<AuthorBook[]>(new Error("No published books found."));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<AuthorBook[]>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public class AuthorBook : Models.AuthorBook
|
|||||||
{
|
{
|
||||||
public virtual Author Author { get; set; } = new();
|
public virtual Author Author { get; set; } = new();
|
||||||
|
|
||||||
public virtual Product Book { get; set; } = new();
|
public new virtual Product? Product { get; set; }
|
||||||
|
|
||||||
public virtual ICollection<BookPage> Pages { get; set; } = [];
|
public virtual ICollection<BookPage> Pages { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public class AuthorBookConfiguration : IEntityTypeConfiguration<AuthorBook>
|
|||||||
.HasForeignKey(f => f.AuthorId)
|
.HasForeignKey(f => f.AuthorId)
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
builder.HasOne(f => f.Book)
|
builder.HasOne(f => f.Product)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey(f => f.ProductId)
|
.HasForeignKey(f => f.ProductId)
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|||||||
@@ -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
|
public class AuthorBook
|
||||||
{
|
{
|
||||||
@@ -16,5 +18,7 @@ public class AuthorBook
|
|||||||
|
|
||||||
public int Ranking { get; set; }
|
public int Ranking { get; set; }
|
||||||
|
|
||||||
|
public Product? Product { get; set; }
|
||||||
|
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.Extensions;
|
||||||
using LiteCharms.Features.MidrandBooks.Postgres;
|
using LiteCharms.Features.MidrandBooks.Postgres;
|
||||||
using LiteCharms.Features.MidrandBooks.Products.Models;
|
using LiteCharms.Features.MidrandBooks.Products.Models;
|
||||||
@@ -8,7 +9,7 @@ namespace LiteCharms.Features.MidrandBooks.Authors;
|
|||||||
|
|
||||||
public class AuthorService(IDbContextFactory<MidrandBooksDbContext> contextFactory)
|
public class AuthorService(IDbContextFactory<MidrandBooksDbContext> contextFactory)
|
||||||
{
|
{
|
||||||
public async ValueTask<Result<Product[]>> GetAuthorBooksAsync(long authorId, CancellationToken cancellationToken)
|
public async ValueTask<Result<AuthorBook[]>> GetAuthorBooksAsync(long authorId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -17,21 +18,24 @@ public class AuthorService(IDbContextFactory<MidrandBooksDbContext> contextFacto
|
|||||||
var author = await context.Authors.FirstOrDefaultAsync(a => a.Id == authorId, cancellationToken);
|
var author = await context.Authors.FirstOrDefaultAsync(a => a.Id == authorId, cancellationToken);
|
||||||
|
|
||||||
if (author is null)
|
if (author is null)
|
||||||
return Result.Fail<Product[]>(new Error($"Author with ID {authorId} not found"));
|
return Result.Fail<AuthorBook[]>(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)
|
.OrderByDescending(b => b.CreatedAt)
|
||||||
.Where(p => p.AuthorId == authorId)
|
.Where(p => p.AuthorId == authorId)
|
||||||
.Select(p => p.Book.ToModel())
|
.AsSplitQuery()
|
||||||
.ToArrayAsync(cancellationToken);
|
.ToArrayAsync(cancellationToken);
|
||||||
|
|
||||||
return books?.Length > 0
|
return books?.Length > 0
|
||||||
? Result.Ok(books)
|
? Result.Ok(books.Select(b => b.ToModel()).ToArray())
|
||||||
: Result.Fail<Product[]>(new Error($"No books found for author with ID {authorId}"));
|
: Result.Fail<AuthorBook[]>(new Error($"No books found for author with ID {authorId}"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return Result.Fail<Product[]>(new Error(ex.Message).CausedBy(ex));
|
return Result.Fail<AuthorBook[]>(new Error(ex.Message).CausedBy(ex));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ public static class Mappers
|
|||||||
AuthorId = entity.AuthorId,
|
AuthorId = entity.AuthorId,
|
||||||
Ranking = entity.Ranking,
|
Ranking = entity.Ranking,
|
||||||
Rating = entity.Rating,
|
Rating = entity.Rating,
|
||||||
Enabled = entity.Enabled
|
Enabled = entity.Enabled,
|
||||||
|
Product = entity.Product?.ToModel(),
|
||||||
};
|
};
|
||||||
|
|
||||||
public static ProductPrice ToModel(this Products.Entities.ProductPrice entity) => new()
|
public static ProductPrice ToModel(this Products.Entities.ProductPrice entity) => new()
|
||||||
@@ -59,7 +60,8 @@ public static class Mappers
|
|||||||
ThumbnailUrls = entity.ThumbnailUrls,
|
ThumbnailUrls = entity.ThumbnailUrls,
|
||||||
Metadata = entity.Metadata,
|
Metadata = entity.Metadata,
|
||||||
Categories = entity.Categories,
|
Categories = entity.Categories,
|
||||||
Enabled = entity.Enabled
|
Enabled = entity.Enabled,
|
||||||
|
Price = entity.Prices?.FirstOrDefault(p => p.Enabled)?.ToModel() ?? null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,5 +26,7 @@ public class Product
|
|||||||
|
|
||||||
public ProductMetadata? Metadata { get; set; }
|
public ProductMetadata? Metadata { get; set; }
|
||||||
|
|
||||||
|
public ProductPrice? Price { get; set; }
|
||||||
|
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ public class ProductService(IDbContextFactory<MidrandBooksDbContext> contextFact
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
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<long>($"Product with ID {productId} does not exist.");
|
return Result.Fail<long>($"Product with ID {productId} does not exist.");
|
||||||
|
|
||||||
var existingPrices = await context.Prices.Where(p => p.ProductId == productId).ToListAsync(cancellationToken);
|
var existingPrices = await context.Prices.Where(p => p.ProductId == productId).ToListAsync(cancellationToken);
|
||||||
@@ -133,10 +133,10 @@ public class ProductService(IDbContextFactory<MidrandBooksDbContext> contextFact
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
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<long>("A product with the same name already exists.");
|
return Result.Fail<long>("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))
|
if (await context.Products.AnyAsync(p => p.Metadata!.SerialNumber == request.Metadata.SerialNumber, cancellationToken))
|
||||||
return Result.Fail<long>("A product with the same metadata already exists.");
|
return Result.Fail<long>("A product with the same metadata already exists.");
|
||||||
|
|
||||||
@@ -223,7 +223,7 @@ public class ProductService(IDbContextFactory<MidrandBooksDbContext> contextFact
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<Result<Product[]>> GetProductsAsync(DateRange range, CancellationToken cancellationToken = default)
|
public async ValueTask<Result<Product[]>> GetProductsAsync(int offset, DateRange range, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -234,11 +234,14 @@ public class ProductService(IDbContextFactory<MidrandBooksDbContext> contextFact
|
|||||||
|
|
||||||
var products = await context.Products
|
var products = await context.Products
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
|
.Include(p => p.Prices)
|
||||||
.OrderByDescending(p => p.CreatedAt)
|
.OrderByDescending(p => p.CreatedAt)
|
||||||
.ThenBy(p => p.UpdatedAt)
|
.ThenByDescending(p => p.UpdatedAt)
|
||||||
.Where(p => p.CreatedAt >= fromDate && p.CreatedAt <= toDate)
|
.Where(p => p.CreatedAt >= fromDate && p.CreatedAt <= toDate)
|
||||||
|
.Skip(offset)
|
||||||
.Take(range.MaxRecords)
|
.Take(range.MaxRecords)
|
||||||
.ToArrayAsync(cancellationToken);
|
.AsSplitQuery()
|
||||||
|
.ToArrayAsync(cancellationToken);
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
? Result.Ok(products.Select(p => p.ToModel()).ToArray())
|
? Result.Ok(products.Select(p => p.ToModel()).ToArray())
|
||||||
|
|||||||
Reference in New Issue
Block a user