Files
prefab-page-detail/Prefab.Catalog/Data/Services/UniqueChecker.cs
2025-10-27 17:39:18 -04:00

96 lines
3.4 KiB
C#

using Microsoft.EntityFrameworkCore;
using Prefab.Catalog.Domain.Entities;
using Prefab.Catalog.Domain.Services;
namespace Prefab.Catalog.Data.Services;
/// <inheritdoc cref="IUniqueChecker"/>
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<bool> 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<bool> 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<Product>()
.Where(p => p.DeletedOn == null && p.Kind == ProductKind.Model)
.AnyAsync(p => p.Name != null && p.Name.ToUpper() == normalized, cancellationToken);
}
public async Task<bool> 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<Product>()
.Where(p => p.DeletedOn == null && p.Kind == ProductKind.Model && p.Slug != null)
.AnyAsync(p => p.Slug!.ToLower() == normalized, cancellationToken);
}
public async Task<bool> 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<Product>()
.Where(p => p.DeletedOn == null && p.Sku != null)
.AnyAsync(p => p.Sku!.ToUpper() == normalized, cancellationToken);
}
public async Task<bool> 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<OptionDefinition>()
.Where(o => o.DeletedOn == null && o.ProductId == productId)
.AnyAsync(o => o.Code.ToUpper() == normalizedCode, cancellationToken);
}
}