using Microsoft.EntityFrameworkCore; using Prefab.Catalog.Domain.Entities; using Prefab.Catalog.Domain.Services; namespace Prefab.Catalog.Data.Services; /// public sealed class UniqueChecker(ICatalogDbContextFactory dbFactory) : IUniqueChecker { private static DbContext AsDbContext(IModuleDbReadOnly db) { if (db is not DbContext context) { throw new InvalidOperationException("Catalog context must derive from DbContext."); } return context; } public async Task CategoryNameIsUnique(string name, CancellationToken cancellationToken = default) { ArgumentException.ThrowIfNullOrWhiteSpace(name); await using var db = await dbFactory.CreateReadOnlyAsync(cancellationToken); var normalized = name.Trim(); return !await db.Categories .Where(c => c.DeletedOn == null) .AnyAsync(c => c.Name == normalized, cancellationToken); } public async Task ProductModelNameIsUnique(string name, CancellationToken cancellationToken = default) { ArgumentException.ThrowIfNullOrWhiteSpace(name); await using var db = await dbFactory.CreateReadOnlyAsync(cancellationToken); var context = AsDbContext(db); var normalized = name.Trim().ToUpperInvariant(); return !await context.Set() .Where(p => p.DeletedOn == null && p.Kind == ProductKind.Model) .AnyAsync(p => p.Name != null && p.Name.ToUpper() == normalized, cancellationToken); } public async Task ProductSlugIsUnique(string? slug, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(slug)) { return true; } await using var db = await dbFactory.CreateReadOnlyAsync(cancellationToken); var context = AsDbContext(db); var normalized = slug.Trim().ToLowerInvariant(); return !await context.Set() .Where(p => p.DeletedOn == null && p.Kind == ProductKind.Model && p.Slug != null) .AnyAsync(p => p.Slug!.ToLower() == normalized, cancellationToken); } public async Task ProductSkuIsUnique(string sku, CancellationToken cancellationToken = default) { ArgumentException.ThrowIfNullOrWhiteSpace(sku); await using var db = await dbFactory.CreateReadOnlyAsync(cancellationToken); var context = AsDbContext(db); var normalized = sku.Trim().ToUpperInvariant(); return !await context.Set() .Where(p => p.DeletedOn == null && p.Sku != null) .AnyAsync(p => p.Sku!.ToUpper() == normalized, cancellationToken); } public async Task OptionCodeIsUniqueForProduct(Guid productId, string code, CancellationToken cancellationToken = default) { if (productId == Guid.Empty) { throw new ArgumentException("Product identifier is required.", nameof(productId)); } ArgumentException.ThrowIfNullOrWhiteSpace(code); await using var db = await dbFactory.CreateReadOnlyAsync(cancellationToken); var context = AsDbContext(db); var normalizedCode = code.Trim().ToUpperInvariant(); return !await context.Set() .Where(o => o.DeletedOn == null && o.ProductId == productId) .AnyAsync(o => o.Code.ToUpper() == normalizedCode, cancellationToken); } }