using AsbCloudApp.Comparators; using AsbCloudApp.Data; using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; using AsbCloudDb; using AsbCloudDb.Model; using AsbCloudInfrastructure.EfCache; using Mapster; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure.Repository { #nullable enable public class UserRoleRepository : IUserRoleRepository { private readonly IAsbCloudDbContext dbContext; private const string userRoleCacheTag = "UserRole"; private const string relationUserRoleUserRoleCacheTag = "RelationUserRoleUserRole"; private const string relationUserRolePermissionsCacheTag = "RelationUserRolePermissions"; private static readonly TimeSpan relationCacheObsolence = TimeSpan.FromMinutes(15); public UserRoleRepository(IAsbCloudDbContext dbContext) { this.dbContext = dbContext; } public async Task<int> InsertAsync(UserRoleDto dto, CancellationToken token) { var entity = dto.Adapt<UserRole>(); var updatedEntity = await dbContext.UserRoles.AddAsync(entity, token) .ConfigureAwait(false); dto.Id = updatedEntity.Entity.Id; await UpdatePermissionsAsync(dto, token); await UpdateIncludedRolesAsync(dto, token); await dbContext.SaveChangesAsync(token); DropCacheUserRole(); return updatedEntity?.Entity?.Id ?? 0; } public Task<int> InsertRangeAsync(IEnumerable<UserRoleDto> newItems, CancellationToken token) { throw new NotImplementedException(); } public async Task<IEnumerable<UserRoleDto>> GetAllAsync(CancellationToken token) { var dtos = await GetCacheUserRoleAsync(token) .ConfigureAwait(false); if (dtos is null) return Enumerable.Empty<UserRoleDto>(); return dtos; } public UserRoleDto? GetOrDefault(int id) { var dto = GetCacheUserRole().FirstOrDefault(x => x.Id == id); if (dto is null) return null; return dto; } public async Task<UserRoleDto?> GetOrDefaultAsync(int id, CancellationToken token) { var dto = (await GetCacheUserRoleAsync(token) .ConfigureAwait(false)).FirstOrDefault(r => r.Id == id); if (dto is null) return null; return dto; } public async Task<IEnumerable<UserRoleDto>> GetByNamesAsync(IEnumerable<string> names, CancellationToken token) { if (names?.Any() != true) return Enumerable.Empty<UserRoleDto>(); var dtos = (await GetCacheUserRoleAsync(token)) .Where(r => names.Contains(r.Caption)); if (dtos?.Count() != names.Count()) throw new ArgumentInvalidException("Invalid role names", nameof(names)); return dtos; } public async Task<int> UpdateAsync(UserRoleDto dto, CancellationToken token) { var entity = Convert(dto); await UpdatePermissionsAsync(dto, token); await UpdateIncludedRolesAsync(dto, token); var result = dbContext.UserRoles.Upsert(entity); await dbContext.SaveChangesAsync(token); DropCacheUserRole(); return result?.Entity?.Id ?? 0; } public IEnumerable<UserRoleDto> GetNestedById(int id, int recursionLevel = 7) { var dto = GetCacheUserRole().FirstOrDefault(r => r.Id == id); if (dto is null) return Enumerable.Empty<UserRoleDto>(); var role = Convert(dto); var roles = new SortedSet<UserRoleDto>(ComparerIId.GetInstance()) { dto }; 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 dto = (await GetCacheUserRoleAsync(token)).FirstOrDefault(r => r.Id == id); if (dto is not null) { var entity = Convert(dto); var removeEntity = dbContext.UserRoles.Remove(entity); await dbContext.SaveChangesAsync(token); DropCacheUserRole(); return removeEntity?.Entity?.Id ?? 0; } else return 0; } public async Task<int> DeleteAsync(IEnumerable<int> ids, CancellationToken token) { var dtos = (await GetCacheUserRoleAsync(token)).Where(r => ids.Contains(r.Id)); if (dtos is not null) { var entities = dtos.Select(Convert); var count = entities.Count(); dbContext.UserRoles.RemoveRange(entities); await dbContext.SaveChangesAsync(token); DropCacheUserRole(); return count; } 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 dtos = GetCacheUserRole() .Where(r => rolesIds.Contains(r.Id)); var roles = dtos.Select(Convert); foreach (var role in roles) 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 dto = GetCacheUserRole() .First(p => p.Id == relation.IdInclude); var includedRole = Convert(dto); if (HasPermission(includedRole, idPermission, --recursionLevel)) return true; } return false; } private async Task UpdateIncludedRolesAsync(UserRoleDto dto, CancellationToken token) { if (dto?.Roles is null) return; var relations = (await GetCacheRelationUserRoleUserRoleAsync(token).ConfigureAwait(false)) .Where(r => r.Id == dto.Id); dbContext.RelationUserRoleUserRoles.RemoveRange(relations); if (dto.Roles.Any()) { var newRelations = dto.Roles.Select(r => new RelationUserRoleUserRole { Id = dto.Id, IdInclude = r.Id }); await dbContext.RelationUserRoleUserRoles.AddRangeAsync(newRelations, token); await dbContext.SaveChangesAsync(token); DropCacheRelationUserRoleUserRole(); } } private async Task UpdatePermissionsAsync(UserRoleDto dto, CancellationToken token) { if (dto?.Permissions is null) return; var relations = (await GetCacheRelationUserRolePermissionsAsync(token).ConfigureAwait(false)) .Where(r => r.IdUserRole == dto.Id); 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); DropCacheRelationCompanyWell(); } } private Task<IEnumerable<UserRoleDto>> GetCacheUserRoleAsync(CancellationToken token) => dbContext.UserRoles .Include(r => r.RelationUserRolePermissions) .Include(r => r.RelationUserRoleUserRoles) .FromCacheAsync(userRoleCacheTag, relationCacheObsolence, Convert, token); private IEnumerable<UserRoleDto> GetCacheUserRole() => dbContext.UserRoles .Include(r => r.RelationUserRolePermissions) .Include(r => r.RelationUserRoleUserRoles) .FromCache(userRoleCacheTag, relationCacheObsolence, Convert); private void DropCacheUserRole() => dbContext.RelationUserUserRoles.DropCache(relationUserRoleUserRoleCacheTag); private Task<IEnumerable<RelationUserRoleUserRole>> GetCacheRelationUserRoleUserRoleAsync(CancellationToken token) => dbContext.RelationUserRoleUserRoles .Include(r => r.IncludeRole) .Include(r => r.Role) .FromCacheAsync(relationUserRoleUserRoleCacheTag, relationCacheObsolence, token); private IEnumerable<RelationUserRoleUserRole> GetCacheRelationUserRoleUserRole() => dbContext.RelationUserRoleUserRoles .Include(r => r.IncludeRole) .Include(r => r.Role) .FromCache(relationUserRoleUserRoleCacheTag, relationCacheObsolence); private void DropCacheRelationUserRoleUserRole() => dbContext.RelationUserUserRoles.DropCache(relationUserRoleUserRoleCacheTag); private Task<IEnumerable<RelationUserRolePermission>> GetCacheRelationUserRolePermissionsAsync(CancellationToken token) => dbContext.RelationUserRolePermissions .Include(r => r.UserRole) .Include(r => r.Permission) .FromCacheAsync(relationUserRolePermissionsCacheTag, relationCacheObsolence, token); private IEnumerable<RelationUserRolePermission> GetCacheRelationUserRolePermissions() => dbContext.RelationUserRolePermissions .Include(r => r.UserRole) .Include(r => r.Permission) .FromCache(relationUserRolePermissionsCacheTag, relationCacheObsolence); private void DropCacheRelationCompanyWell() => dbContext.RelationUserRolePermissions.DropCache(relationUserRolePermissionsCacheTag); 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 => { var dto = rolesCache.First(r => r.Id == rel.IdInclude); var includedRole = Convert(dto); return Convert(includedRole); }).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; } } #nullable disable }