using CPRNIMS.Infrastructure.Database; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; namespace CPRNIMS.Domain.Services.Account { public interface IRoleAuthorizationCache { Task> GetAllowedRoleIdsAsync(string controller); Task UserHasAccessAsync(string userId, string controller); Task> GetUserRoleIdsAsync(string userId); void InvalidateCache(string controller); void InvalidateUserCache(string userId); void InvalidateAllCache(); } public class RoleAuthorizationCache : IRoleAuthorizationCache { private readonly IMemoryCache _cache; private readonly NonInventoryDbContext _dbContext; private readonly ILogger _logger; private const string CONTROLLER_ROLES_PREFIX = "controller_roles_"; private const string USER_ROLES_PREFIX = "user_roles_"; private const int CACHE_DURATION_MINUTES = 30; public RoleAuthorizationCache( IMemoryCache cache, NonInventoryDbContext dbContext, ILogger logger) { _cache = cache ?? throw new ArgumentNullException(nameof(cache)); _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } /// /// Get all role IDs that have access to a specific controller (CACHED) /// public async Task> GetAllowedRoleIdsAsync(string controller) { if (string.IsNullOrWhiteSpace(controller)) throw new ArgumentException("Controller name cannot be null or empty", nameof(controller)); var cacheKey = $"{CONTROLLER_ROLES_PREFIX}{controller}"; return await _cache.GetOrCreateAsync(cacheKey, async entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CACHE_DURATION_MINUTES); entry.SetPriority(CacheItemPriority.High); _logger.LogInformation("Cache MISS: Loading role permissions for controller: {Controller}", controller); var roleIds = await _dbContext.AuthorizeRoles .Where(ar => ar.IsActive && ar.Controller == controller) .Select(ar => ar.RoleId) .Distinct() .ToListAsync(); _logger.LogInformation("Cached {Count} roles for controller: {Controller}", roleIds.Count, controller); return roleIds ?? new List(); }); } /// /// Get all role IDs for a specific user (CACHED) /// public async Task> GetUserRoleIdsAsync(string userId) { if (string.IsNullOrWhiteSpace(userId)) throw new ArgumentException("User ID cannot be null or empty", nameof(userId)); var cacheKey = $"{USER_ROLES_PREFIX}{userId}"; return await _cache.GetOrCreateAsync(cacheKey, async entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CACHE_DURATION_MINUTES); entry.SetPriority(CacheItemPriority.High); _logger.LogInformation("Cache MISS: Loading roles for user: {UserId}", userId); var userRoleIds = await _dbContext.UserRoles .Where(ur => ur.UserId == userId) .Select(ur => ur.RoleId) .ToListAsync(); _logger.LogInformation("Cached {Count} roles for user: {UserId}", userRoleIds.Count, userId); return userRoleIds ?? new List(); }); } /// /// Check if a specific user has access to a controller (FULLY CACHED) /// public async Task UserHasAccessAsync(string userId, string controller) { if (string.IsNullOrWhiteSpace(userId)) throw new ArgumentException("User ID cannot be null or empty", nameof(userId)); if (string.IsNullOrWhiteSpace(controller)) throw new ArgumentException("Controller name cannot be null or empty", nameof(controller)); try { // Get allowed role IDs from cache (or database if cache miss) var allowedRoleIds = await GetAllowedRoleIdsAsync(controller); if (!allowedRoleIds.Any()) { _logger.LogWarning("No roles configured for controller: {Controller}", controller); return false; } // Get user's role IDs from cache (or database if cache miss) var userRoleIds = await GetUserRoleIdsAsync(userId); if (!userRoleIds.Any()) { _logger.LogWarning("User {UserId} has no roles assigned", userId); return false; } // Check if user has any of the required roles (IN-MEMORY operation, no database!) var hasAccess = userRoleIds.Any(userRole => allowedRoleIds.Contains(userRole)); _logger.LogDebug("User {UserId} access to {Controller}: {HasAccess}", userId, controller, hasAccess); return hasAccess; } catch (Exception ex) { _logger.LogError(ex, "Error checking access for user {UserId} on controller {Controller}", userId, controller); throw; } } /// /// Invalidate cache for a specific controller /// public void InvalidateCache(string controller) { if (string.IsNullOrWhiteSpace(controller)) return; var cacheKey = $"{CONTROLLER_ROLES_PREFIX}{controller}"; _cache.Remove(cacheKey); _logger.LogInformation("Cache invalidated for controller: {Controller}", controller); } /// /// Invalidate cache for a specific user (call this when user roles change) /// public void InvalidateUserCache(string userId) { if (string.IsNullOrWhiteSpace(userId)) return; var cacheKey = $"{USER_ROLES_PREFIX}{userId}"; _cache.Remove(cacheKey); _logger.LogInformation("Cache invalidated for user: {UserId}", userId); } /// /// Invalidate all controller role caches /// public void InvalidateAllCache() { _logger.LogWarning("All cache invalidation requested - consider implementing a cache key tracking system"); } } }