using AsbCloudApp.Comparators; using AsbCloudApp.Data; using AsbCloudApp.Data.User; using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; using AsbCloudDb; using AsbCloudDb.Model; using Mapster; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure.Repository; public class UserRoleRepository : IUserRoleRepository { private readonly IAsbCloudDbContext dbContext; private readonly IMemoryCache memoryCache; public UserRoleRepository(IAsbCloudDbContext dbContext, IMemoryCache memoryCache) { this.dbContext = dbContext; this.memoryCache = memoryCache; } public async Task<int> InsertAsync(UserRoleDto dto, CancellationToken token) { var entity = dto.Adapt<UserRole>(); var updatedEntity = dbContext.UserRoles.Add(entity); await dbContext.SaveChangesAsync(token); if (updatedEntity.IsKeySet) { dto.Id = updatedEntity.Entity.Id; await UpdatePermissionsAsync(dto, token); await UpdateIncludedRolesAsync(dto, token); DropCacheUserRole(); return updatedEntity.Entity.Id; } return 0; } public Task<int> InsertRangeAsync(IEnumerable<UserRoleDto> newItems, CancellationToken token) { throw new NotImplementedException(); } public async Task<IEnumerable<UserRoleDto>> GetAllAsync(CancellationToken token) { var entities = await GetCacheUserRoleAsync(token) .ConfigureAwait(false); return entities.Select(Convert); } public UserRoleDto? GetOrDefault(int id) { var entity = GetCacheUserRole().FirstOrDefault(x => x.Id == id); if (entity is null) return null; return Convert(entity); } public async Task<UserRoleDto?> GetOrDefaultAsync(int id, CancellationToken token) { var entity = (await GetCacheUserRoleAsync(token) .ConfigureAwait(false)).FirstOrDefault(r => r.Id == id); if (entity is null) return null; return Convert(entity); } public async Task<IEnumerable<UserRoleDto>> GetByNamesAsync(IEnumerable<string> names, CancellationToken token) { if (names?.Any() != true) return Enumerable.Empty<UserRoleDto>(); var entities = (await GetCacheUserRoleAsync(token)) .Where(r => names.Contains(r.Caption)); if (entities?.Count() != names.Count()) throw new ArgumentInvalidException(nameof(names), "Invalid role names"); return entities.Select(Convert); } public async Task<int> UpdateAsync(UserRoleDto dto, CancellationToken token) { await UpdatePermissionsAsync(dto, token); await UpdateIncludedRolesAsync(dto, token); var entity = Convert(dto); var result = dbContext.UserRoles.Upsert(entity); await dbContext.SaveChangesAsync(token); DropCacheUserRole(); return result.Entity.Id; } public IEnumerable<UserRoleDto> GetNestedById(int id, int recursionLevel = 7) { var role = GetCacheUserRole() .FirstOrDefault(r => r.Id == id); if (role is null) return Enumerable.Empty<UserRoleDto>(); var roles = new SortedSet<UserRoleDto>(ComparerIId.GetInstance()) { Convert(role) }; if (recursionLevel <= 0 || role.RelationUserRoleUserRoles?.Any() != true) return roles; foreach (var relation in role.RelationUserRoleUserRoles) { var nestedRoles = GetNestedById(relation.IdInclude, --recursionLevel); if (nestedRoles?.Any() != true) continue; foreach (var nestedRole in nestedRoles) roles.Add(nestedRole); } return roles; } public async Task<int> DeleteAsync(int id, CancellationToken token) { var entity = (await GetCacheUserRoleAsync(token)).FirstOrDefault(r => r.Id == id); if (entity is not null) { var removeEntity = dbContext.UserRoles.Remove(entity); await dbContext.SaveChangesAsync(token); DropCacheUserRole(); return removeEntity.Entity.Id; } else return 0; } public bool HasPermission(IEnumerable<int> rolesIds, string permissionName) { var permissionInfo = GetCacheRelationUserRolePermissions() .FirstOrDefault(p => p. Permission?.Name.ToLower() == permissionName.ToLower()) ?.Permission; if (permissionInfo is null) return false; if (rolesIds.Contains(1)) return true; var idPermissionInfo = permissionInfo.Id; var entities = GetCacheUserRole() .Where(r => rolesIds.Contains(r.Id)); foreach (var role in entities) if (HasPermission(role, idPermissionInfo)) return true; return false; } private bool HasPermission(UserRole userRole, int idPermission, int recursionLevel = 7) { if (userRole.RelationUserRolePermissions.Any(p => p.IdPermission == idPermission)) return true; if (recursionLevel <= 0 || userRole.RelationUserRoleUserRoles?.Any() != true) return false; foreach (var relation in userRole.RelationUserRoleUserRoles) { var entity = GetCacheUserRole() .First(p => p.Id == relation.IdInclude); if (HasPermission(entity, idPermission, --recursionLevel)) return true; } return false; } private IEnumerable<UserRoleDto> GetNestedByIds(IEnumerable<int> ids, int recursionLevel = 7) { var roles = new List<UserRoleDto>(); foreach (var id in ids) roles.AddRange(GetNestedById(id, recursionLevel)); return roles; } private async Task UpdateIncludedRolesAsync(UserRoleDto dto, CancellationToken token) { if (!dto.Roles.Any()) return; var idsIncludeRole = GetNestedByIds(dto.Roles.Select(x => x.Id)).Select(x => x.Id); if (idsIncludeRole.Any(x => x == dto.Id)) throw new ArgumentInvalidException(nameof(dto), "Invalid include role (self reference)"); var removeRelationsQuery = dbContext.RelationUserRoleUserRoles .Where(r => r.Id == dto.Id); dbContext.RelationUserRoleUserRoles.RemoveRange(removeRelationsQuery); var newRelations = dto.Roles.Select(r => new RelationUserRoleUserRole { Id = dto.Id, IdInclude = r.Id, }); dbContext.RelationUserRoleUserRoles.AddRange(newRelations); await dbContext.SaveChangesAsync(token); DropCacheRelationUserRoleUserRole(); } private async Task UpdatePermissionsAsync(UserRoleDto dto, CancellationToken token) { if (dto?.Permissions is null) return; var relations = await dbContext.RelationUserRolePermissions .Where(r => r.IdUserRole == dto.Id) .ToListAsync(token) .ConfigureAwait(false); dbContext.RelationUserRolePermissions.RemoveRange(relations); if (dto.Permissions.Any()) { var newRelations = dto.Permissions.Select(p => new RelationUserRolePermission { IdPermission = p.Id, IdUserRole = dto.Id, }); await dbContext.RelationUserRolePermissions.AddRangeAsync(newRelations, token); await dbContext.SaveChangesAsync(token); } DropCacheRelationUserRolePermissions(); } private Task<IEnumerable<UserRole>> GetCacheUserRoleAsync(CancellationToken token) => memoryCache.GetOrCreateBasicAsync(dbContext.Set<UserRole>() .Include(r => r.RelationUserRolePermissions) .Include(r => r.RelationUserRoleUserRoles) .Include(r => r.RelationUsersUserRoles), token); private IEnumerable<UserRole> GetCacheUserRole() => memoryCache.GetOrCreateBasic(dbContext.Set<UserRole>() .Include(r => r.RelationUserRolePermissions) .Include(r => r.RelationUserRoleUserRoles) .Include(r => r.RelationUsersUserRoles)); private void DropCacheUserRole() => memoryCache.DropBasic<UserRole>(); private void DropCacheRelationUserRoleUserRole() => memoryCache.DropBasic<RelationUserUserRole>(); private IEnumerable<RelationUserRolePermission> GetCacheRelationUserRolePermissions() => memoryCache.GetOrCreateBasic(dbContext.Set<RelationUserRolePermission>() .Include(r => r.UserRole) .Include(r => r.Permission)); private void DropCacheRelationUserRolePermissions() => memoryCache.DropBasic<RelationUserRolePermission>(); private UserRoleDto Convert(UserRole entity) { var dto = entity.Adapt<UserRoleDto>(); if (entity.RelationUserRolePermissions?.Any() == true) { dto.Permissions = GetCacheRelationUserRolePermissions() .Where(r => entity.Id == r.IdUserRole) .Select(r => Convert(r.Permission)); } if (entity.RelationUserRoleUserRoles?.Any() == true) { var rolesCache = GetCacheUserRole(); dto.Roles = entity.RelationUserRoleUserRoles .Select(rel => Convert(rolesCache .First(r => r.Id == rel.IdInclude))) .ToArray(); } return dto; } private static PermissionDto Convert(Permission entity) { var dto = entity.Adapt<PermissionDto>(); return dto; } private static UserRole Convert(UserRoleDto dto) { var entity = dto.Adapt<UserRole>(); return entity; } }