using Microsoft.EntityFrameworkCore; using Tiku.Application.Security; using Tiku.Domain.Commerce; using Tiku.Domain.Platform; using Tiku.Domain.Tenancy; using Tiku.Infrastructure.Persistence; namespace Tiku.Infrastructure.Security; internal sealed class CapabilityAccessEvaluator(TikuDbContext dbContext) : ICapabilityAccessEvaluator { public async Task IsAllowedAsync( Guid tenantId, string moduleCode, CapabilityOperation operation, CancellationToken cancellationToken = default) { var normalized = moduleCode.Trim().ToLowerInvariant(); if (!ProductModuleCatalog.Contains(normalized)) { return false; } var moduleExists = await dbContext.ProductModules.AsNoTracking() .AnyAsync(item => item.Code == normalized && item.Status == ProductModuleStatus.Active, cancellationToken); if (!moduleExists) { return false; } var tenantActive = await dbContext.Tenants.AsNoTracking() .AnyAsync(item => item.Id == tenantId && item.Status == TenantStatus.Active, cancellationToken); if (!tenantActive) { return false; } var now = DateTimeOffset.UtcNow; var overrideMode = await dbContext.TenantModuleOverrides.AsNoTracking() .Where(item => item.TenantId == tenantId && item.ModuleCode == normalized && (item.ExpiresAt == null || item.ExpiresAt > now)) .Select(item => (TenantModuleOverrideMode?)item.Mode) .SingleOrDefaultAsync(cancellationToken); if (overrideMode == TenantModuleOverrideMode.Disabled) { return false; } var subscription = await dbContext.TenantSubscriptions.AsNoTracking() .Where(item => item.TenantId == tenantId) .OrderByDescending(item => item.UpdatedAt) .Select(item => new { item.PlanCode, item.Status, item.StartsAt, item.ExpiresAt }) .FirstOrDefaultAsync(cancellationToken); if (subscription is null || subscription.StartsAt > now || subscription.ExpiresAt <= now) { return false; } var entitled = overrideMode == TenantModuleOverrideMode.Enabled || await dbContext.PlanModuleEntitlements.AsNoTracking().AnyAsync( item => item.PlanCode == subscription.PlanCode && item.ModuleCode == normalized && item.Enabled, cancellationToken); if (!entitled) { return false; } return operation == CapabilityOperation.Read || subscription.Status is TenantSubscriptionStatus.Trial or TenantSubscriptionStatus.Active; } public async Task> GetEnabledModulesAsync( Guid tenantId, CapabilityOperation operation = CapabilityOperation.Read, CancellationToken cancellationToken = default) { var modules = await dbContext.ProductModules.AsNoTracking() .Where(item => item.Status == ProductModuleStatus.Active) .Select(item => item.Code) .ToArrayAsync(cancellationToken); var enabled = new HashSet(StringComparer.Ordinal); foreach (var module in modules) { if (await IsAllowedAsync(tenantId, module, operation, cancellationToken)) { enabled.Add(module); } } return enabled; } }