namespace PLib.Domain.Videos; /// What a label is for; the relation to videos is identical either way. public enum LabelKind { /// A free-form word used to narrow the grid down. Tag, /// A named group the user curates and browses as a whole. Collection, } /// /// A named grouping of videos — a tag or a collection. /// /// /// Tags and collections are the same relation: a name, many videos, a video in many of them. /// They differ only in intent, and that intent is . One entity means one /// join table, one repository and one set of rules about naming; splitting them later is a /// rename and a migration, whereas keeping two near-identical aggregates in sync from the /// start is a permanent tax. /// public sealed class LibraryLabel { private readonly List _videos = []; /// Required by EF Core materialization; do not use from application code. private LibraryLabel() { Name = null!; NormalizedName = null!; } public LibraryLabel(string name, LabelKind kind) { ArgumentException.ThrowIfNullOrWhiteSpace(name); Id = Guid.CreateVersion7(); Kind = kind; CreatedAt = DateTimeOffset.UtcNow; Name = name.Trim(); NormalizedName = Normalize(name); } public Guid Id { get; private set; } public string Name { get; private set; } /// /// Upper-cased, trimmed name. Uniqueness is enforced on this rather than on /// , so "Комедия" and "комедия" cannot both exist. /// public string NormalizedName { get; private set; } public LabelKind Kind { get; private set; } public DateTimeOffset CreatedAt { get; private set; } public IReadOnlyCollection Videos => _videos; public static string Normalize(string name) => name.Trim().ToUpperInvariant(); public void Rename(string name) { ArgumentException.ThrowIfNullOrWhiteSpace(name); Name = name.Trim(); NormalizedName = Normalize(name); } }