114 lines
3.7 KiB
C#
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;
|
|
}
|
|
}
|