From 5ea6919738848f61277b6e2acf306a75473e5a21 Mon Sep 17 00:00:00 2001 From: "ai.astrakhantsev" Date: Wed, 26 Oct 2022 15:36:49 +0500 Subject: [PATCH] =?UTF-8?q?#7205798=20=D0=9F=D0=B5=D1=80=D0=B5=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=20=D1=81=D0=B5=D1=80=D0=B2=D0=B8=D1=81=D0=B0=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB?= =?UTF-8?q?=D0=B5=D0=B9=20=D0=B8=20=D1=80=D0=BE=D0=BB=D0=B5=D0=B9=20=D0=B2?= =?UTF-8?q?=20=D1=80=D0=B5=D0=BF=D0=BE=D0=B7=D0=B8=D1=82=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AsbCloudApp/Repositories/IUserRepository.cs | 41 +++ .../IUserRoleRepository.cs} | 18 +- AsbCloudApp/Services/IUserService.cs | 3 +- .../Services/IWellFinalDocumentsService.cs | 3 +- AsbCloudDb/Model/IAsbCloudDbContext.cs | 1 + AsbCloudInfrastructure/DependencyInjection.cs | 4 +- .../Repository/UserRepository.cs | 102 ++++++ .../Repository/UserRoleRepository.cs | 311 ++++++++++++++++++ .../Services/UserRoleService.cs | 234 ------------- .../Services/UserService.cs | 5 +- .../ServicesTests/UserRoleServiceTest.cs | 187 ----------- .../ServicesTests/UserServiceTest.cs | 9 +- .../Controllers/AdminUserRoleController.cs | 5 +- 13 files changed, 481 insertions(+), 442 deletions(-) create mode 100644 AsbCloudApp/Repositories/IUserRepository.cs rename AsbCloudApp/{Services/IUserRoleService.cs => Repositories/IUserRoleRepository.cs} (70%) create mode 100644 AsbCloudInfrastructure/Repository/UserRepository.cs create mode 100644 AsbCloudInfrastructure/Repository/UserRoleRepository.cs delete mode 100644 AsbCloudInfrastructure/Services/UserRoleService.cs delete mode 100644 AsbCloudWebApi.Tests/ServicesTests/UserRoleServiceTest.cs diff --git a/AsbCloudApp/Repositories/IUserRepository.cs b/AsbCloudApp/Repositories/IUserRepository.cs new file mode 100644 index 00000000..02b713fe --- /dev/null +++ b/AsbCloudApp/Repositories/IUserRepository.cs @@ -0,0 +1,41 @@ +using AsbCloudApp.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudApp.Repositories +{ + /// + /// Репозиторий пользователей + /// + public interface IUserRepository + { + /// + /// Добавление пользователя + /// + /// + /// + /// + Task InsertAsync(UserExtendedDto dto, CancellationToken token); + + /// + /// Обновление ролей пользователя + /// + /// + /// + /// + /// + Task UpdateRolesCacheForUserAsync(int idUser, IEnumerable newRoles, CancellationToken token); + + /// + /// Получить пользователя по логину + /// + /// + /// + /// + Task GetUserByLoginAsync(string login, CancellationToken token); + } +} diff --git a/AsbCloudApp/Services/IUserRoleService.cs b/AsbCloudApp/Repositories/IUserRoleRepository.cs similarity index 70% rename from AsbCloudApp/Services/IUserRoleService.cs rename to AsbCloudApp/Repositories/IUserRoleRepository.cs index b9341c9d..177e4379 100644 --- a/AsbCloudApp/Services/IUserRoleService.cs +++ b/AsbCloudApp/Repositories/IUserRoleRepository.cs @@ -1,23 +1,24 @@ using AsbCloudApp.Data; +using AsbCloudApp.Services; using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; +using System.Threading; -namespace AsbCloudApp.Services +namespace AsbCloudApp.Repositories { +#nullable enable /// - /// Репозиторий ролей пользователя + /// Разрешения на доступ к данным /// - public interface IUserRoleService : ICrudService + public interface IUserRoleRepository : ICrudService { - // todo: переименовать /// /// получить dto по названиям /// /// /// /// - Task> GetByNamesAsync(IEnumerable names, CancellationToken token = default); + Task?> GetByNamesAsync(IEnumerable names, CancellationToken token = default); /// /// получить все вложенные разрешения @@ -25,7 +26,7 @@ namespace AsbCloudApp.Services /// /// /// - IEnumerable GetNestedById(int id, int counter = 10); + IEnumerable? GetNestedById(int id, int counter = 10); /// /// определяет содержится ли разрешение в одной из указанных ролей @@ -35,4 +36,5 @@ namespace AsbCloudApp.Services /// bool HasPermission(IEnumerable rolesIds, string permissionName); } -} \ No newline at end of file +#nullable disable +} diff --git a/AsbCloudApp/Services/IUserService.cs b/AsbCloudApp/Services/IUserService.cs index 34f12212..5eb9ab70 100644 --- a/AsbCloudApp/Services/IUserService.cs +++ b/AsbCloudApp/Services/IUserService.cs @@ -1,4 +1,5 @@ using AsbCloudApp.Data; +using AsbCloudApp.Repositories; using System.Collections.Generic; namespace AsbCloudApp.Services @@ -11,7 +12,7 @@ namespace AsbCloudApp.Services /// /// Сервис ролей /// - IUserRoleService RoleService { get; } + IUserRoleRepository RoleService { get; } /// /// Получить список всех прав пользователя (включая наследование групп) diff --git a/AsbCloudApp/Services/IWellFinalDocumentsService.cs b/AsbCloudApp/Services/IWellFinalDocumentsService.cs index 7b910c74..4da098ce 100644 --- a/AsbCloudApp/Services/IWellFinalDocumentsService.cs +++ b/AsbCloudApp/Services/IWellFinalDocumentsService.cs @@ -50,7 +50,8 @@ namespace AsbCloudApp.Services /// /// Сохранение файла /// - /// + /// + /// /// /// /// diff --git a/AsbCloudDb/Model/IAsbCloudDbContext.cs b/AsbCloudDb/Model/IAsbCloudDbContext.cs index 42642377..5bab3c21 100644 --- a/AsbCloudDb/Model/IAsbCloudDbContext.cs +++ b/AsbCloudDb/Model/IAsbCloudDbContext.cs @@ -28,6 +28,7 @@ namespace AsbCloudDb.Model DbSet RelationDrillingProgramPartUsers { get; } DbSet RelationUserRolePermissions { get; } DbSet RelationUserUserRoles { get; } + DbSet RelationUserRoleUserRoles { get; } DbSet ReportProperties { get; } DbSet Subsystems { get; } DbSet SubsystemOperationTimes { get; } diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 48d759fc..cc6c279a 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -121,15 +121,12 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); - //services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddTransient, CrudWellRelatedServiceBase>(); @@ -165,6 +162,7 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); // Subsystem service services.AddTransient, CrudCacheServiceBase>(); services.AddTransient(); diff --git a/AsbCloudInfrastructure/Repository/UserRepository.cs b/AsbCloudInfrastructure/Repository/UserRepository.cs new file mode 100644 index 00000000..83c058f9 --- /dev/null +++ b/AsbCloudInfrastructure/Repository/UserRepository.cs @@ -0,0 +1,102 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Repositories; +using AsbCloudDb.Model; +using AsbCloudInfrastructure.EfCache; +using Mapster; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Repository +{ +#nullable enable + public class UserRepository : IUserRepository + { + private readonly IAsbCloudDbContext db; + private static readonly System.TimeSpan cacheObsolescence = System.TimeSpan.FromMinutes(15); + private static readonly TypeAdapterConfig userTypeAdapterConfig = TypeAdapterConfig + .NewConfig() + .Ignore(dst => dst.Company, + dst => dst.FileMarks, + dst => dst.Files, + dst => dst.RelationUsersUserRoles) + .Config; + + public UserRepository(IAsbCloudDbContext db) { + this.db = db; + } + + public async Task InsertAsync(UserExtendedDto dto, CancellationToken token) + { + dto.Id = default; + var updatedEntity = await db.Users.AddAsync(Convert(dto), token).ConfigureAwait(false); + return Convert(updatedEntity.Entity); + } + + public async Task UpdateRolesCacheForUserAsync(int idUser, IEnumerable newRoles, CancellationToken token) + { + var entity = await db.RelationUserUserRoles.FirstOrDefaultAsync(x => x.IdUser == idUser, token).ConfigureAwait(false); + if (entity is not null) + { + db.RelationUserUserRoles.Remove(entity); + if (newRoles?.Any() == true) + await db.RelationUserUserRoles.AddRangeAsync(newRoles.Select(role => new RelationUserUserRole + { + IdUser = idUser, + IdUserRole = role.Id + }), token).ConfigureAwait(false); + } + } + + public async Task GetUserByLoginAsync(string login, CancellationToken token) + { + var entities = await db.Users.FromCacheAsync("Users", cacheObsolescence, token).ConfigureAwait(false); + var entity = entities.FirstOrDefault(x => x.Login.ToLower() == login.ToLower()); + if (entity is not null) + return Convert(entity); + return null; + } + + public async Task?> GetAllAsync(CancellationToken token = default) + { + var entities = await db.Users.FromCacheAsync("Users", cacheObsolescence, token).ConfigureAwait(false); + if (entities is null) + return null; + var dtos = entities.Select(Convert).ToList(); + //for (var i = 0; i < dtos.Count; i++) + // dtos[i].RoleNames = GetRolesNamesByIdUser(dtos[i].Id, token); + return dtos; + } + + //private async Task> GetRolesNamesByIdUser(int idUser, CancellationToken token) + //=> await GetRolesByIdUser(idUser, token) + // ?.Select(r => r.Caption) + // .Distinct(); + + //public async Task> GetRolesByIdUser(int idUser, CancellationToken token, int nestedLevel = 0) + //{ + // var allRoles = await db.RelationUserUserRoles.FromCacheAsync("RelationUserUserRoles", cacheObsolescence, token).ConfigureAwait(false); + // var roles = allRoles.Where(r => r.IdUser == idUser); + // if (roles?.Any() != true) + // return null; + // return roles.SelectMany(r => RoleService.GetNestedById(r.IdUserRole, nestedLevel)); + //} + + private User Convert(UserExtendedDto dto) + { + var entity = dto.Adapt(userTypeAdapterConfig); + if (string.IsNullOrEmpty(entity.PasswordHash)) + entity.PasswordHash = db.Users.FirstOrDefault(u => u.Id == dto.Id)?.PasswordHash; + return entity; + } + + private UserExtendedDto Convert(User entity) + { + var dto = entity.Adapt(); + return dto; + } + } +#nullable disable +} diff --git a/AsbCloudInfrastructure/Repository/UserRoleRepository.cs b/AsbCloudInfrastructure/Repository/UserRoleRepository.cs new file mode 100644 index 00000000..89d6150e --- /dev/null +++ b/AsbCloudInfrastructure/Repository/UserRoleRepository.cs @@ -0,0 +1,311 @@ +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 InsertAsync(UserRoleDto dto, CancellationToken token) + { + var entity = dto.Adapt(); + var updatedEntity = await dbContext.UserRoles.AddAsync(entity, token) + .ConfigureAwait(false); + dto.Id = updatedEntity.Entity.Id; + await UpdatePermissionsAsync(dto, token); + await UpdateIncludedRolesAsync(dto, token); + + dbContext.UserRoles.DropCache(); + return updatedEntity?.Entity?.Id ?? 0; + } + + public Task InsertRangeAsync(IEnumerable newItems, CancellationToken token) + { + throw new NotImplementedException(); + } + + public async Task> GetAllAsync(CancellationToken token) + { + var entities = await GetCacheUserRoleAsync(token) + .ConfigureAwait(false); + + if (entities is not null) + { + var dtos = entities.Select(Convert); + return dtos; + } + else + return new List(); + } + + public UserRoleDto? GetOrDefault(int id) + { + var entity = GetCacheUserRole().FirstOrDefault(x => x.Id == id); + if (entity is null) + return null; + var dto = Convert(entity); + return dto; + } + + 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; + var dto = Convert(entity); + return dto; + } + + public async Task?> GetByNamesAsync(IEnumerable names, CancellationToken token) + { + if (names?.Any() != true) + return null; + var entities = (await GetCacheUserRoleAsync(token)).Where(r => names.Contains(r.Caption)); + if (entities?.Count() != names.Count()) + throw new ArgumentInvalidException("Invalid role names", nameof(names)); + var dtos = entities.Select(Convert); + return dtos; + } + + public async Task UpdateAsync(UserRoleDto dto, CancellationToken token) + { + var entity = Convert(dto); + await UpdatePermissionsAsync(dto, token); + await UpdateIncludedRolesAsync(dto, token); + + var result = dbContext.UserRoles.Upsert(entity); + DropCacheUserRole(); + return result?.Entity?.Id ?? 0; + } + + public IEnumerable? GetNestedById(int id, int recursionLevel = 7) + { + var role = GetCacheUserRole().FirstOrDefault(r => r.Id == id); + if (role is null) + return null; + var dto = Convert(role); + var roles = new SortedSet(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 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 ?? 0; + } + else return 0; + } + + public async Task DeleteAsync(IEnumerable ids, CancellationToken token) + { + var entities = (await GetCacheUserRoleAsync(token)).Where(r => ids.Contains(r.Id)); + + if (entities is not null) + { + var count = entities.Count(); + dbContext.UserRoles.RemoveRange(entities); + await dbContext.SaveChangesAsync(token); + DropCacheUserRole(); + return count; + } + 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 roles = GetCacheUserRole() + .Where(r => rolesIds.Contains(r.Id)); + 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 includedRole = GetCacheUserRole() + .First(p => p.Id == relation.IdInclude); + 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> GetCacheUserRoleAsync(CancellationToken token) + => dbContext.UserRoles + .Include(r => r.RelationUserRolePermissions) + .Include(r => r.RelationUserRoleUserRoles) + .FromCacheAsync(userRoleCacheTag, relationCacheObsolence, token); + private IEnumerable GetCacheUserRole() + => dbContext.UserRoles + .Include(r => r.RelationUserRolePermissions) + .Include(r => r.RelationUserRoleUserRoles) + .FromCache(userRoleCacheTag, relationCacheObsolence); + private void DropCacheUserRole() + => dbContext.RelationUserUserRoles.DropCache(relationUserRoleUserRoleCacheTag); + + private Task> GetCacheRelationUserRoleUserRoleAsync(CancellationToken token) + => dbContext.RelationUserRoleUserRoles + .Include(r => r.IncludeRole) + .Include(r => r.Role) + .FromCacheAsync(relationUserRoleUserRoleCacheTag, relationCacheObsolence, token); + private IEnumerable GetCacheRelationUserRoleUserRole() + => dbContext.RelationUserRoleUserRoles + .Include(r => r.IncludeRole) + .Include(r => r.Role) + .FromCache(relationUserRoleUserRoleCacheTag, relationCacheObsolence); + private void DropCacheRelationUserRoleUserRole() + => dbContext.RelationUserUserRoles.DropCache(relationUserRoleUserRoleCacheTag); + + private Task> GetCacheRelationUserRolePermissionsAsync(CancellationToken token) + => dbContext.RelationUserRolePermissions + .Include(r => r.UserRole) + .Include(r => r.Permission) + .FromCacheAsync(relationUserRolePermissionsCacheTag, relationCacheObsolence, token); + private IEnumerable 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(); + if (entity.RelationUserRolePermissions?.Any() == true) + { + dto.Permissions = GetCacheRelationUserRolePermissions() + .Where(r => entity.Id == r.IdUserRole) + .Select(r => Convert(r.Permission)); + } + + if (entity.RelationUserRoleUserRoles?.Any() == true) + { + dto.Roles = entity.RelationUserRoleUserRoles.Select(rel => + { + var includedRole = GetCacheUserRole().First(r => r.Id == rel.IdInclude); + return Convert(includedRole); + }).ToList(); + } + 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; + } + } +#nullable disable +} diff --git a/AsbCloudInfrastructure/Services/UserRoleService.cs b/AsbCloudInfrastructure/Services/UserRoleService.cs deleted file mode 100644 index 37f05d8c..00000000 --- a/AsbCloudInfrastructure/Services/UserRoleService.cs +++ /dev/null @@ -1,234 +0,0 @@ -using AsbCloudApp.Comparators; -using AsbCloudApp.Data; -using AsbCloudApp.Exceptions; -using AsbCloudApp.Services; -using AsbCloudDb.Model; -using AsbCloudInfrastructure.Services.Cache; -using Mapster; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace AsbCloudInfrastructure.Services -{ - public class UserRoleService : IUserRoleService - { - private readonly CacheTable cacheUserRoles; - private readonly CacheTable cacheRelationUserRolePermissions; - private readonly CacheTable cacheRelationUserRoleUserRole; - - public ISet Includes { get; } = new SortedSet(); - - public UserRoleService(IAsbCloudDbContext context, CacheDb cacheDb) - { - cacheUserRoles = cacheDb.GetCachedTable((AsbCloudDbContext)context, nameof(UserRole.RelationUserRolePermissions), nameof(UserRole.RelationUserRoleUserRoles)); - cacheRelationUserRolePermissions = cacheDb.GetCachedTable((AsbCloudDbContext)context, nameof(RelationUserRolePermission.Permission)); - cacheRelationUserRoleUserRole = cacheDb.GetCachedTable((AsbCloudDbContext)context, nameof(RelationUserRoleUserRole.IncludeRole), nameof(RelationUserRoleUserRole.Role)); - } - - public async Task InsertAsync(UserRoleDto dto, CancellationToken token = default) - { - var entity = dto.Adapt(); - var updatedEntity = await cacheUserRoles.InsertAsync(entity, token) - .ConfigureAwait(false); - dto.Id = updatedEntity.Id; - await UpdatePermissionsAsync(dto, token); - await UpdateIncludedRolesAsync(dto, token); - - await cacheUserRoles.RefreshAsync(true, token) - .ConfigureAwait(false); - return updatedEntity?.Id ?? 0; - } - - public Task InsertRangeAsync(IEnumerable dtos, CancellationToken token = default) - { - throw new NotImplementedException(); - //var entities = dtos.Adapt(); - //return await cacheUserRoles.InsertAsync(entities, token).ConfigureAwait(false); - } - - public async Task> GetAllAsync(CancellationToken token = default) - { - var entities = await cacheUserRoles.WhereAsync(token) - .ConfigureAwait(false); - var dtos = entities?.Select(Convert); - return dtos; - } - public UserRoleDto GetOrDefault(int id) - { - var entity = cacheUserRoles.FirstOrDefault(r => r.Id == id); - if (entity is null) - return null; - var dto = Convert(entity); - return dto; - } - - public async Task GetOrDefaultAsync(int id, CancellationToken token = default) - { - var entity = await cacheUserRoles.FirstOrDefaultAsync(r => r.Id == id, token) - .ConfigureAwait(false); - if (entity is null) - return null; - var dto = Convert(entity); - return dto; - } - - public async Task> GetByNamesAsync(IEnumerable names, CancellationToken token = default) - { - if (names?.Any() != true) - return null; - var entities = await cacheUserRoles.WhereAsync(r => names.Contains(r.Caption), token) - .ConfigureAwait(false); - if (entities?.Count() != names.Count()) - throw new ArgumentInvalidException("Invalid role names", nameof(names)); - var dtos = entities.Select(Convert); - return dtos; - } - - public async Task UpdateAsync(UserRoleDto dto, CancellationToken token = default) - { - var entity = Convert(dto); - await UpdatePermissionsAsync(dto, token); - await UpdateIncludedRolesAsync(dto, token); - - var result = await cacheUserRoles.UpsertAsync(entity, token) - .ConfigureAwait(false); - return result; - } - - public IEnumerable GetNestedById(int id, int recursionLevel = 7) - { - var role = cacheUserRoles.FirstOrDefault(r => r.Id == id); - if (role is null) - return null; - var dto = Convert(role); - var roles = new SortedSet(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; - } - - private async Task UpdatePermissionsAsync(UserRoleDto dto, CancellationToken token) - { - if (dto?.Permissions is null) - return; - - await cacheRelationUserRolePermissions.RemoveAsync(r => r.IdUserRole == dto.Id, token) - .ConfigureAwait(false); - - if (dto.Permissions.Any()) - { - var newRelationRoleToPermission = dto.Permissions.Select(p => new RelationUserRolePermission - { - IdPermission = p.Id, - IdUserRole = dto.Id, - }); - - await cacheRelationUserRolePermissions.InsertAsync(newRelationRoleToPermission, token) - .ConfigureAwait(false); - } - } - - private async Task UpdateIncludedRolesAsync(UserRoleDto dto, CancellationToken token) - { - if (dto?.Roles is null) - return; - - await cacheRelationUserRoleUserRole.RemoveAsync(rel => rel.Id == dto.Id, token); - - if (dto.Roles.Any()) - { - var newRelations = dto.Roles.Select(r => new RelationUserRoleUserRole { Id = dto.Id, IdInclude = r.Id }); - await cacheRelationUserRoleUserRole.UpsertAsync(newRelations, token); - } - } - - public Task DeleteAsync(int id, CancellationToken token = default) - => cacheUserRoles.RemoveAsync(r => r.Id == id, token); - - public Task DeleteAsync(IEnumerable ids, CancellationToken token = default) - => cacheUserRoles.RemoveAsync(r => ids.Contains(r.Id), token); - - public bool HasPermission(IEnumerable rolesIds, string permissionName) - { - var permissionInfo = cacheRelationUserRolePermissions - .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 roles = cacheUserRoles.Where(r => rolesIds.Contains(r.Id)); - 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 includedRole = cacheUserRoles.First(p => p.Id == relation.IdInclude); - if (HasPermission(includedRole, idPermission, --recursionLevel)) - return true; - } - return false; - } - - private static UserRole Convert(UserRoleDto dto) - { - var entity = dto.Adapt(); - return entity; - } - - private UserRoleDto Convert(UserRole entity) - { - var dto = entity.Adapt(); - if (entity.RelationUserRolePermissions?.Any() == true) - { - dto.Permissions = cacheRelationUserRolePermissions - .Where(r => entity.Id == r.IdUserRole) - .Select(r => Convert(r.Permission)); - } - - if (entity.RelationUserRoleUserRoles?.Any() == true) - { - dto.Roles = entity.RelationUserRoleUserRoles.Select(rel => - { - var includedRole = cacheUserRoles.First(r => r.Id == rel.IdInclude); - return Convert(includedRole); - }).ToList(); - } - return dto; - } - - private static PermissionDto Convert(Permission entity) - { - var dto = entity.Adapt(); - return dto; - } - } -} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/UserService.cs b/AsbCloudInfrastructure/Services/UserService.cs index c282204e..09e284ae 100644 --- a/AsbCloudInfrastructure/Services/UserService.cs +++ b/AsbCloudInfrastructure/Services/UserService.cs @@ -1,5 +1,6 @@ using AsbCloudApp.Data; using AsbCloudApp.Exceptions; +using AsbCloudApp.Repositories; using AsbCloudApp.Services; using AsbCloudDb.Model; using AsbCloudInfrastructure.Services.Cache; @@ -17,7 +18,7 @@ namespace AsbCloudInfrastructure.Services private readonly CacheTable cacheUsers; private readonly CacheTable cacheRelationUserToRoles; public ISet Includes { get; } = new SortedSet(); - public IUserRoleService RoleService { get; } + public IUserRoleRepository RoleService { get; } private static readonly TypeAdapterConfig userTypeAdapterConfig = TypeAdapterConfig .NewConfig() @@ -27,7 +28,7 @@ namespace AsbCloudInfrastructure.Services dst => dst.RelationUsersUserRoles) .Config; - public UserService(IAsbCloudDbContext context, CacheDb cacheDb, IUserRoleService roleService) + public UserService(IAsbCloudDbContext context, CacheDb cacheDb, IUserRoleRepository roleService) { var db = (AsbCloudDbContext)context; cacheUsers = cacheDb.GetCachedTable( diff --git a/AsbCloudWebApi.Tests/ServicesTests/UserRoleServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/UserRoleServiceTest.cs deleted file mode 100644 index 8ce1c1b3..00000000 --- a/AsbCloudWebApi.Tests/ServicesTests/UserRoleServiceTest.cs +++ /dev/null @@ -1,187 +0,0 @@ -using AsbCloudApp.Data; -using AsbCloudDb.Model; -using AsbCloudInfrastructure.Services; -using AsbCloudInfrastructure.Services.Cache; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace AsbCloudWebApi.Tests.ServicesTests -{ - public class UserRoleServiceTest - { - private readonly AsbCloudDbContext context; - private readonly CacheDb cacheDb; - - private readonly List roles = new() { - new UserRole { Id = 1_000_001, Caption = "role 1 level 0" }, - new UserRole { Id = 1_000_002, Caption = "role 2 level 1" }, - new UserRole { Id = 1_000_003, Caption = "role 3 level 1" }, - new UserRole { Id = 1_000_004, Caption = "role 4 level 2" }, - }; - - private readonly List relationRoleRole = new() - { - new RelationUserRoleUserRole { Id = 1_000_002, IdInclude = 1_000_001 }, - new RelationUserRoleUserRole { Id = 1_000_003, IdInclude = 1_000_001 }, - new RelationUserRoleUserRole { Id = 1_000_004, IdInclude = 1_000_002 }, - new RelationUserRoleUserRole { Id = 1_000_004, IdInclude = 1_000_003 }, - }; - - private readonly List permissions = new() - { - new Permission { Id = 2_000_001, Name = "permission 1" }, - new Permission { Id = 2_000_002, Name = "permission 2" }, - new Permission { Id = 2_000_003, Name = "permission 3" }, - new Permission { Id = 2_000_004, Name = "permission 4" }, - }; - - private readonly List relationRolePermission = new() - { - new RelationUserRolePermission { IdUserRole = 1_000_001, IdPermission = 2_000_001 }, - new RelationUserRolePermission { IdUserRole = 1_000_002, IdPermission = 2_000_002 }, - new RelationUserRolePermission { IdUserRole = 1_000_003, IdPermission = 2_000_003 }, - new RelationUserRolePermission { IdUserRole = 1_000_004, IdPermission = 2_000_004 }, - }; - - public UserRoleServiceTest() - { - cacheDb = new CacheDb(); - context = TestHelpter.MakeTestContext(); - context.UserRoles.RemoveRange(roles); - context.Permissions.RemoveRange(permissions); - context.SaveChanges(); - context.UserRoles.AddRange(roles); - context.Permissions.AddRange(permissions); - context.SaveChanges(); - context.RelationUserRoleUserRoles.AddRange(relationRoleRole); - context.RelationUserRolePermissions.AddRange(relationRolePermission); - context.SaveChanges(); - } - - ~UserRoleServiceTest() - { - context.UserRoles.RemoveRange(roles); - context.Permissions.RemoveRange(permissions); - context.RelationUserRoleUserRoles.RemoveRange(relationRoleRole); - context.RelationUserRolePermissions.RemoveRange(relationRolePermission); - context.SaveChanges(); - context.Dispose(); - } - - [Fact] - public void GetNestedById_return_4_items() - { - var service = new UserRoleService(context, cacheDb); - var nestedRoles = service.GetNestedById(1_000_004); - Assert.Equal(roles.Count, nestedRoles.Count()); - } - - [Fact] - public void HasPermission_return_true() - { - var service = new UserRoleService(context, cacheDb); - var result = service.HasPermission(new int[] { 1_000_004 }, "permission 1"); - Assert.True(result); - } - - [Fact] - public void HasPermission_return_false() - { - var service = new UserRoleService(context, cacheDb); - var result = service.HasPermission(new int[] { 1_000_003 }, "permission 2"); - Assert.False(result); - } - - [Fact] - public async Task InsertAsync_returns_id() - { - var service = new UserRoleService(context, cacheDb); - var newRole = new UserRoleDto - { - Caption = "new role", - IdType = 0, - }; - var id = await service.InsertAsync(newRole, CancellationToken.None); - Assert.NotEqual(0, id); - } - - [Fact] - public async Task InsertAsync_updates_relation_to_permission() - { - var service = new UserRoleService(context, cacheDb); - var newRole = new UserRoleDto - { - Caption = "new role", - IdType = 0, - Permissions = new[] { new PermissionDto { Id = 2_000_001 } }, - }; - var id = await service.InsertAsync(newRole, CancellationToken.None); - var entity = await service.GetOrDefaultAsync(id); - Assert.Equal(newRole.Permissions.Count(), entity.Permissions.Count()); - } - - [Fact] - public async Task InsertAsync_updates_relation_to_role() - { - var service = new UserRoleService(context, cacheDb); - var newRole = new UserRoleDto - { - Caption = "new role", - IdType = 0, - Roles = new[] { new UserRoleDto { Id = 1_000_001 } } - }; - var id = await service.InsertAsync(newRole, CancellationToken.None); - var entity = await service.GetOrDefaultAsync(id); - Assert.Equal(newRole.Roles.Count, entity.Roles.Count); - } - - [Fact] - public async Task UpdateAsync_returns_id() - { - var service = new UserRoleService(context, cacheDb); - const int updateId = 1_000_002; - var modRole = new UserRoleDto - { - Id = updateId, - Caption = "role 2 level 1" - }; - var id = await service.UpdateAsync(modRole, CancellationToken.None); - Assert.Equal(updateId, id); - } - - [Fact] - public async Task UpdateAsync_updates_relation_to_permission() - { - var service = new UserRoleService(context, cacheDb); - const int updateId = 1_000_002; - var modRole = new UserRoleDto - { - Id = updateId, - Caption = "role 2 level 1", - Permissions = new[] { new PermissionDto { Id = 2_000_001 } }, - }; - var id = await service.UpdateAsync(modRole, CancellationToken.None); - var entity = await service.GetOrDefaultAsync(id); - Assert.Equal(modRole.Permissions.Count(), entity.Permissions.Count()); - } - - [Fact] - public async Task UpdateAsync_updates_relation_to_role() - { - var service = new UserRoleService(context, cacheDb); - const int updateId = 1_000_002; - var modRole = new UserRoleDto - { - Id = updateId, - Caption = "role 2 level 1", - Roles = new[] { new UserRoleDto { Id = 1_000_001 } } - }; - var id = await service.UpdateAsync(modRole, CancellationToken.None); - var entity = await service.GetOrDefaultAsync(id); - Assert.Equal(modRole.Roles.Count(), entity.Roles.Count()); - } - } -} diff --git a/AsbCloudWebApi.Tests/ServicesTests/UserServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/UserServiceTest.cs index 1e3d1b3f..bd47b853 100644 --- a/AsbCloudWebApi.Tests/ServicesTests/UserServiceTest.cs +++ b/AsbCloudWebApi.Tests/ServicesTests/UserServiceTest.cs @@ -1,4 +1,5 @@ using AsbCloudApp.Data; +using AsbCloudApp.Repositories; using AsbCloudApp.Services; using AsbCloudDb.Model; using AsbCloudInfrastructure.Services; @@ -16,13 +17,13 @@ namespace AsbCloudWebApi.Tests.ServicesTests { private readonly AsbCloudDbContext context; private readonly CacheDb cacheDb; - private readonly Mock roleService; + private readonly Mock roleRepository; public UserServiceTest() { context = TestHelpter.MakeTestContext(); cacheDb = new CacheDb(); - roleService = new Mock(); + roleRepository = new Mock(); context.Users.RemoveRange(context.Users.Where(u => u.Id > 1)); context.SaveChanges(); @@ -37,7 +38,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests [Fact] public async Task InsertAsync_returns_id() { - var service = new UserService(context, cacheDb, roleService.Object); + var service = new UserService(context, cacheDb, roleRepository.Object); var dto = new UserExtendedDto { Id = 0, @@ -60,7 +61,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests [Fact] public async Task InsertAsync_busy_login_throws_exceprion() { - var service = new UserService(context, cacheDb, roleService.Object); + var service = new UserService(context, cacheDb, roleRepository.Object); var dto = new UserExtendedDto { Id = 5, diff --git a/AsbCloudWebApi/Controllers/AdminUserRoleController.cs b/AsbCloudWebApi/Controllers/AdminUserRoleController.cs index 5625f0ae..d3ca48ba 100644 --- a/AsbCloudWebApi/Controllers/AdminUserRoleController.cs +++ b/AsbCloudWebApi/Controllers/AdminUserRoleController.cs @@ -1,4 +1,5 @@ using AsbCloudApp.Data; +using AsbCloudApp.Repositories; using AsbCloudApp.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -12,9 +13,9 @@ namespace AsbCloudWebApi.Controllers [Route("api/admin/role")] [ApiController] [Authorize] - public class AdminUserRoleController : CrudController + public class AdminUserRoleController : CrudController { - public AdminUserRoleController(IUserRoleService service) + public AdminUserRoleController(IUserRoleRepository service) : base(service) { UpdateForbidAsync = async (dto, token) =>