Implemented category feature

This commit is contained in:
Khwezi Mngoma
2026-05-30 14:22:00 +02:00
parent 2db3b3d293
commit e40c958066
11 changed files with 1284 additions and 7 deletions
@@ -0,0 +1,132 @@
using LiteCharms.Features.MidrandBooks.Abstractions;
using LiteCharms.Features.MidrandBooks.Categories.Models;
using LiteCharms.Features.MidrandBooks.Extensions;
using LiteCharms.Features.MidrandBooks.Postgres;
namespace LiteCharms.Features.MidrandBooks.Categories;
public sealed class CategoryService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
{
public async ValueTask<Result> UpdateCategoryStatusAsync(long categoryId, bool enabled, bool isMain, CancellationToken cancellationToken = default)
{
try
{
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var rowsUpdated = await context.Categories
.Where(c => c.Id == categoryId && c.Enabled)
.ExecuteUpdateAsync(setters => setters
.SetProperty(c => c.Enabled, enabled)
.SetProperty(c => c.IsMain, isMain), cancellationToken);
return rowsUpdated > 0
? Result.Ok()
: Result.Fail(new Error($"Failed to update category"));
}
catch (Exception ex)
{
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
public async ValueTask<Result<Category>> GetCategoryAsync(long categoryId, CancellationToken cancellationToken = default)
{
try
{
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var category = await context.Categories.AsNoTracking().FirstOrDefaultAsync(c => c.Id == categoryId, cancellationToken);
return category is not null
? Result.Ok(category.ToModel())
: Result.Fail<Category>("Failed to create new category");
}
catch (Exception ex)
{
return Result.Fail<Category>(new Error(ex.Message).CausedBy(ex));
}
}
public async ValueTask<Result<Category[]>> GetCategoriesAsync(bool? isMain = null, CancellationToken cancellationToken = default)
{
try
{
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var query = context.Categories.AsNoTracking()
.OrderByDescending(o => o.IsMain)
.ThenByDescending(o => o.Id)
.ThenBy(o => o.IsMain)
.AsQueryable();
query = isMain is null
? query.Where(c => c.Enabled).AsQueryable()
: query.Where(c => c.Enabled && c.IsMain == isMain.Value);
var categories = await query.ToListAsync(cancellationToken);
return categories?.Count > 0
? Result.Ok(categories.Select(c => c.ToModel()).ToArray())
: Result.Fail<Category[]>("No categories found");
}
catch (Exception ex)
{
return Result.Fail<Category[]>(new Error(ex.Message).CausedBy(ex));
}
}
public async ValueTask<Result> CreateCategoriesAsync(CancellationToken cancellationToken = default, params string[] categories)
{
try
{
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
foreach (var category in categories)
{
if (await context.Categories.AnyAsync(c => EF.Functions.ILike(c.Name!, category!), cancellationToken))
continue;
context.Categories.Add(new Entities.Category
{
Name = category.Humanize(LetterCasing.Title),
IsMain = false,
Enabled = true,
});
}
return await context.SaveChangesAsync(cancellationToken) > 0
? Result.Ok()
: Result.Fail("Failed to add any category in the list");
}
catch (Exception ex)
{
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
public async ValueTask<Result<long>> CreateCategoryAsync(string category, bool isMain, CancellationToken cancellationToken = default)
{
try
{
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
if (await context.Categories.AnyAsync(c => EF.Functions.ILike(c.Name!, category!), cancellationToken))
return Result.Fail($"Category '{category}' already exists");
var newCategory = context.Categories.Add(new Entities.Category
{
Name = StringHumanizeExtensions.Humanize(category, LetterCasing.Title),
IsMain = isMain,
Enabled = true,
});
return await context.SaveChangesAsync(cancellationToken) > 0
? Result.Ok(newCategory.Entity.Id)
: Result.Fail("Failed to create new category");
}
catch (Exception ex)
{
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
}
@@ -0,0 +1,4 @@
namespace LiteCharms.Features.MidrandBooks.Categories.Entities;
[EntityTypeConfiguration<CategoryConfiguration, Category>]
public sealed class Category : Models.Category;
@@ -0,0 +1,14 @@
namespace LiteCharms.Features.MidrandBooks.Categories.Entities;
public sealed class CategoryConfiguration : IEntityTypeConfiguration<Category>
{
public void Configure(EntityTypeBuilder<Category> builder)
{
builder.ToTable("Categories");
builder.HasKey(c => c.Id);
builder.Property(c => c.Name).IsRequired().HasMaxLength(15);
builder.Property(c => c.IsMain).HasDefaultValue(false);
builder.Property(c => c.Enabled).HasDefaultValue(true);
}
}
@@ -0,0 +1,12 @@
namespace LiteCharms.Features.MidrandBooks.Categories.Models;
public class Category
{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsMain { get; set; }
public bool Enabled { get; set; }
}