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 InsertAsync(UserRoleDto dto, CancellationToken token) { var entity = dto.Adapt(); 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 InsertRangeAsync(IEnumerable newItems, CancellationToken token) { throw new NotImplementedException(); } public async Task> 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 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> GetByNamesAsync(IEnumerable names, CancellationToken token) { if (names?.Any() != true) return Enumerable.Empty(); 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 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 GetNestedById(int id, int recursionLevel = 7) { var role = GetCacheUserRole() .FirstOrDefault(r => r.Id == id); if (role is null) return Enumerable.Empty(); var roles = new SortedSet(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 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 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 GetNestedByIds(IEnumerable ids, int recursionLevel = 7) { var roles = new List(); 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> GetCacheUserRoleAsync(CancellationToken token) => memoryCache.GetOrCreateBasicAsync(dbContext.Set() .Include(r => r.RelationUserRolePermissions) .Include(r => r.RelationUserRoleUserRoles) .Include(r => r.RelationUsersUserRoles), token); private IEnumerable GetCacheUserRole() => memoryCache.GetOrCreateBasic(dbContext.Set() .Include(r => r.RelationUserRolePermissions) .Include(r => r.RelationUserRoleUserRoles) .Include(r => r.RelationUsersUserRoles)); private void DropCacheUserRole() => memoryCache.DropBasic(); private void DropCacheRelationUserRoleUserRole() => memoryCache.DropBasic(); private IEnumerable GetCacheRelationUserRolePermissions() => memoryCache.GetOrCreateBasic(dbContext.Set() .Include(r => r.UserRole) .Include(r => r.Permission)); private void DropCacheRelationUserRolePermissions() => memoryCache.DropBasic(); private UserRoleDto Convert(UserRole entity) { var dto = entity.Adapt(); 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(); return dto; } private static UserRole Convert(UserRoleDto dto) { var entity = dto.Adapt(); return entity; } }