Files
prefab-page-detail/Prefab.Catalog/Domain/Entities/Category.cs
2025-10-27 17:39:18 -04:00

114 lines
3.7 KiB
C#

using System.Text.RegularExpressions;
using Prefab.Base.Catalog.Categories;
using Prefab.Catalog.Domain.Events;
using Prefab.Catalog.Domain.Exceptions;
using Prefab.Catalog.Domain.Services;
using Prefab.Domain.Common;
namespace Prefab.Catalog.Domain.Entities;
public class Category : EntityWithAuditAndStatus<Guid>
{
private static readonly Regex SlugRegex = new("^[a-z0-9]+(?:-[a-z0-9]+)*$", RegexOptions.Compiled);
private Category()
{
}
public Guid? ParentId { get; private set; }
public string Name { get; private set; } = string.Empty;
public string? Description { get; private set; }
public string? Slug { get; private set; }
public int DisplayOrder { get; private set; }
public bool IsFeatured { get; private set; }
public string? HeroImageUrl { get; private set; }
public string? Icon { get; private set; }
public static async Task<Category> Create(string name, string? description, IUniqueChecker check, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrWhiteSpace(name);
ArgumentNullException.ThrowIfNull(check);
var trimmedName = name.Trim();
var trimmedDescription = string.IsNullOrWhiteSpace(description) ? null : description.Trim();
if (!await check.CategoryNameIsUnique(trimmedName, cancellationToken))
{
throw new DuplicateNameException(trimmedName, nameof(Category));
}
var category = new Category
{
Id = Guid.NewGuid(),
Name = trimmedName,
Description = trimmedDescription
};
category.AddEvent(new CategoryCreated(category.Id, category.Name));
return category;
}
public async Task Rename(string newName, IUniqueChecker check, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrWhiteSpace(newName);
ArgumentNullException.ThrowIfNull(check);
var trimmed = newName.Trim();
if (string.Equals(trimmed, Name, StringComparison.Ordinal))
{
return;
}
if (!await check.CategoryNameIsUnique(trimmed, cancellationToken))
{
throw new DuplicateNameException(trimmed, nameof(Category));
}
var oldName = Name;
Name = trimmed;
AddEvent(new CategoryRenamed(Id, oldName, Name));
}
public void ConfigureMetadata(string slug, int displayOrder, bool isFeatured, string? heroImageUrl, string? icon)
{
ArgumentException.ThrowIfNullOrWhiteSpace(slug);
var trimmedSlug = slug.Trim();
if (trimmedSlug.Length > CategoryRules.SlugMaxLength)
{
throw new DomainValidationException($"Category slug cannot exceed {CategoryRules.SlugMaxLength} characters.");
}
if (!SlugRegex.IsMatch(trimmedSlug))
{
throw new DomainValidationException("Category slug must use lowercase letters, numbers, and hyphens.");
}
var trimmedHeroImageUrl = string.IsNullOrWhiteSpace(heroImageUrl) ? null : heroImageUrl.Trim();
if (trimmedHeroImageUrl is not null && trimmedHeroImageUrl.Length > CategoryRules.HeroImageUrlMaxLength)
{
throw new DomainValidationException($"Hero image URL cannot exceed {CategoryRules.HeroImageUrlMaxLength} characters.");
}
var trimmedIcon = string.IsNullOrWhiteSpace(icon) ? null : icon.Trim();
if (trimmedIcon is not null && trimmedIcon.Length > CategoryRules.IconMaxLength)
{
throw new DomainValidationException($"Icon cannot exceed {CategoryRules.IconMaxLength} characters.");
}
Slug = trimmedSlug;
DisplayOrder = displayOrder;
IsFeatured = isFeatured;
HeroImageUrl = trimmedHeroImageUrl;
Icon = trimmedIcon;
}
}