From 211f300973d159d58291669c52ae819ea2badd65 Mon Sep 17 00:00:00 2001 From: "ai.astrakhantsev" Date: Thu, 27 Oct 2022 11:22:39 +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 | 38 ++- AsbCloudApp/Services/IUserService.cs | 40 --- AsbCloudInfrastructure/DependencyInjection.cs | 2 +- .../Repository/UserRepository.cs | 239 ++++++++++++++---- .../Repository/UserRoleRepository.cs | 4 +- .../Services/AuthService.cs | 13 +- .../DrillingProgram/DrillingProgramService.cs | 11 +- .../Services/UserService.cs | 198 --------------- .../Services/WellFinalDocumentsService.cs | 13 +- .../DrillingProgramServiceTest.cs | 31 +-- .../ServicesTests/UserServiceTest.cs | 76 ------ .../WellFinalDocumentsServiceTest.cs | 10 +- .../Controllers/AdminUserController.cs | 4 +- AsbCloudWebApi/Controllers/AuthController.cs | 10 +- AsbCloudWebApi/Controllers/FileController.cs | 10 +- .../Middlewares/PermissionsMiddlware.cs | 5 +- 16 files changed, 270 insertions(+), 434 deletions(-) delete mode 100644 AsbCloudApp/Services/IUserService.cs delete mode 100644 AsbCloudInfrastructure/Services/UserService.cs delete mode 100644 AsbCloudWebApi.Tests/ServicesTests/UserServiceTest.cs diff --git a/AsbCloudApp/Repositories/IUserRepository.cs b/AsbCloudApp/Repositories/IUserRepository.cs index 02b713fe..4f6b0fac 100644 --- a/AsbCloudApp/Repositories/IUserRepository.cs +++ b/AsbCloudApp/Repositories/IUserRepository.cs @@ -1,41 +1,35 @@ using AsbCloudApp.Data; -using System; +using AsbCloudApp.Services; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; namespace AsbCloudApp.Repositories { /// /// Репозиторий пользователей /// - public interface IUserRepository + public interface IUserRepository : ICrudService { /// - /// Добавление пользователя - /// - /// - /// - /// - Task InsertAsync(UserExtendedDto dto, CancellationToken token); - - /// - /// Обновление ролей пользователя + /// Получить список всех прав пользователя (включая наследование групп) /// /// - /// - /// /// - Task UpdateRolesCacheForUserAsync(int idUser, IEnumerable newRoles, CancellationToken token); + IEnumerable GetNestedPermissions(int idUser); /// - /// Получить пользователя по логину + /// Получить список ролей пользователя (включая наследование) /// - /// - /// + /// + /// /// - Task GetUserByLoginAsync(string login, CancellationToken token); + IEnumerable GetRolesByIdUser(int idUser, int nestedLevel = 0); + + /// + /// определяет есть ли у пользователя указанное разрешение + /// + /// + /// + /// + public bool HasPermission(int idUser, string permissionName); } } diff --git a/AsbCloudApp/Services/IUserService.cs b/AsbCloudApp/Services/IUserService.cs deleted file mode 100644 index 5eb9ab70..00000000 --- a/AsbCloudApp/Services/IUserService.cs +++ /dev/null @@ -1,40 +0,0 @@ -using AsbCloudApp.Data; -using AsbCloudApp.Repositories; -using System.Collections.Generic; - -namespace AsbCloudApp.Services -{ - /// - /// Сервис пользователей - /// - public interface IUserService : ICrudService - { - /// - /// Сервис ролей - /// - IUserRoleRepository RoleService { get; } - - /// - /// Получить список всех прав пользователя (включая наследование групп) - /// - /// - /// - IEnumerable GetNestedPermissions(int idUser); - - /// - /// Получить список ролей пользователя (включая наследование) - /// - /// - /// - /// - IEnumerable GetRolesByIdUser(int idUser, int nestedLevel = 0); - - /// - /// определяет есть ли у пользователя указанное разрешение - /// - /// - /// - /// - public bool HasPermission(int idUser, string permissionName); - } -} diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index cc6c279a..a9e521ca 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -120,7 +120,6 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -163,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 index 83c058f9..1ba7f006 100644 --- a/AsbCloudInfrastructure/Repository/UserRepository.cs +++ b/AsbCloudInfrastructure/Repository/UserRepository.cs @@ -1,9 +1,13 @@ using AsbCloudApp.Data; +using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; +using AsbCloudApp.Services; +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; @@ -14,8 +18,11 @@ namespace AsbCloudInfrastructure.Repository #nullable enable public class UserRepository : IUserRepository { - private readonly IAsbCloudDbContext db; - private static readonly System.TimeSpan cacheObsolescence = System.TimeSpan.FromMinutes(15); + private readonly IAsbCloudDbContext dbContext; + private readonly IUserRoleRepository userRoleRepository; + private const string userCacheTag = "User"; + private const string relationUserUserRoleCacheTag = "RelationUserUserRole"; + private static readonly TimeSpan cacheObsolence = TimeSpan.FromMinutes(15); private static readonly TypeAdapterConfig userTypeAdapterConfig = TypeAdapterConfig .NewConfig() .Ignore(dst => dst.Company, @@ -24,75 +31,209 @@ namespace AsbCloudInfrastructure.Repository dst => dst.RelationUsersUserRoles) .Config; - public UserRepository(IAsbCloudDbContext db) { - this.db = db; + public UserRepository(IAsbCloudDbContext dbContext, IUserRoleRepository userRoleRepository) { + this.dbContext = dbContext; + this.userRoleRepository = userRoleRepository; } - public async Task InsertAsync(UserExtendedDto dto, CancellationToken token) + public async Task InsertAsync(UserExtendedDto dto, CancellationToken token = default) { dto.Id = default; - var updatedEntity = await db.Users.AddAsync(Convert(dto), token).ConfigureAwait(false); - return Convert(updatedEntity.Entity); + var entity = Convert(dto); + await AssertLoginIsBusyAsync(dto.Login, token); + var userRoles = await userRoleRepository.GetByNamesAsync(dto.RoleNames, token).ConfigureAwait(false); + var updatedEntity = await dbContext.Users.AddAsync(entity, token).ConfigureAwait(false); + if (userRoles?.Any() == true) + await UpdateRolesCacheForUserAsync(updatedEntity.Entity.Id, userRoles, token); + + await dbContext.SaveChangesAsync(token); + DropCacheUsers(); + return updatedEntity?.Entity?.Id ?? 0; } - public async Task UpdateRolesCacheForUserAsync(int idUser, IEnumerable newRoles, CancellationToken token) + public Task InsertRangeAsync(IEnumerable newItems, CancellationToken token = default) { - 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); - } + throw new NotImplementedException(); } - public async Task GetUserByLoginAsync(string login, CancellationToken token) + public async Task> GetAllAsync(CancellationToken token = default) { - 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 entities = await GetCacheUserAsync(token); + var dtos = entities.Select(Convert).ToList(); - //for (var i = 0; i < dtos.Count; i++) - // dtos[i].RoleNames = GetRolesNamesByIdUser(dtos[i].Id, token); + for (var i = 0; i < dtos.Count; i++) + dtos[i].RoleNames = GetRolesNamesByIdUser(dtos[i].Id); return dtos; } - //private async Task> GetRolesNamesByIdUser(int idUser, CancellationToken token) - //=> await GetRolesByIdUser(idUser, token) - // ?.Select(r => r.Caption) - // .Distinct(); + public UserExtendedDto? GetOrDefault(int id) + { + var entity = GetCacheUser().FirstOrDefault(u => u.Id == id); + if (entity is null) + return null; + var dto = Convert(entity); + dto.RoleNames = GetRolesNamesByIdUser(dto.Id); + return dto; + } - //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)); - //} + public async Task GetOrDefaultAsync(int id, CancellationToken token = default) + { + var entity = (await GetCacheUserAsync(token)).FirstOrDefault(u => u.Id == id); + if (entity is null) + return null; + var dto = Convert(entity); + dto.RoleNames = GetRolesNamesByIdUser(dto.Id); + return dto; + } - private User Convert(UserExtendedDto dto) + public async Task UpdateAsync(UserExtendedDto dto, CancellationToken token = default) + { + if (dto.Id <= 1) + throw new ArgumentInvalidException($"Invalid id {dto.Id}. You can't edit this user.", nameof(dto)); + + var oldUser = (await GetCacheUserAsync(token)).FirstOrDefault(u => u.Id == dto.Id); + if (oldUser is null) + return 0; + + if (oldUser.Login != dto.Login) + await AssertLoginIsBusyAsync(dto.Login, token); + + var userRoles = await userRoleRepository.GetByNamesAsync(dto.RoleNames, token).ConfigureAwait(false); + + if (userRoles is not null) + await UpdateRolesCacheForUserAsync(dto.Id, userRoles, token); + + var entity = Convert(dto); + + var result = dbContext.Users.Upsert(entity); + await dbContext.SaveChangesAsync(token); + DropCacheUsers(); + return result?.Entity?.Id ?? 0; + } + + public async Task DeleteAsync(int id, CancellationToken token = default) + { + var entity = (await GetCacheUserAsync(token)).FirstOrDefault(u => u.Id == id); + if (entity is null) + return 0; + + var result = dbContext.Users.Remove(entity); + await dbContext.SaveChangesAsync(token); + DropCacheUsers(); + return result?.Entity?.Id ?? 0; + } + + public async Task DeleteAsync(IEnumerable ids, CancellationToken token = default) + { + var entities = (await GetCacheUserAsync(token)).Where(r => ids.Contains(r.Id)); + if (entities is null) + return 0; + + var count = entities.Count(); + dbContext.Users.RemoveRange(entities); + await dbContext.SaveChangesAsync(token); + DropCacheUsers(); + return count; + } + + public IEnumerable? GetRolesByIdUser(int idUser, int nestedLevel = 0) + { + var roles = GetCachRelationUserUserRoleCacheTag().Where(r => r.IdUser == idUser); + if (roles is null) + return null; + + return roles.SelectMany(r => userRoleRepository.GetNestedById(r.IdUserRole, nestedLevel)); + } + + public IEnumerable? GetNestedPermissions(int idUser) + { + var roles = GetRolesByIdUser(idUser, 7); + if (roles is null) + return null; + var permissions = roles + .Where(r => r.Permissions is not null) + .SelectMany(r => r.Permissions); + + return permissions; + } + + public bool HasPermission(int idUser, string permissionName) + { + if (idUser == 1) + return true; + + var relationsToRoles = GetCachRelationUserUserRoleCacheTag().Where(r => r.IdUser == idUser); + if (relationsToRoles is null) + return false; + + return userRoleRepository.HasPermission(relationsToRoles.Select(r => r.IdUserRole), + permissionName); + } + + private IEnumerable? GetRolesNamesByIdUser(int idUser) + => GetRolesByIdUser(idUser) + ?.Select(r => r.Caption) + .Distinct(); + + private async Task AssertLoginIsBusyAsync(string login, CancellationToken token = default) + { + var existingUser = (await GetCacheUserAsync(token)) + .FirstOrDefault(u => u.Login.ToLower() == login.ToLower()); + if (existingUser is not null) + throw new ArgumentInvalidException($"Login {login} is busy by {existingUser.MakeDisplayName()}, id{existingUser.Id}", nameof(login)); + } + + private Task> GetCacheUserAsync(CancellationToken token) + => dbContext.Users + .Include(r => r.Company) + .Include(r => r.RelationUsersUserRoles) + .FromCacheAsync(userCacheTag, cacheObsolence, token); + private IEnumerable GetCacheUser() + => dbContext.Users + .Include(r => r.Company) + .Include(r => r.RelationUsersUserRoles) + .FromCache(userCacheTag, cacheObsolence); + private void DropCacheUsers() + => dbContext.Users.DropCache(userCacheTag); + + private Task> GetCacheRelationUserUserRoleAsync(CancellationToken token) + => dbContext.RelationUserUserRoles + .Include(r => r.UserRole) + .Include(r => r.User) + .FromCacheAsync(relationUserUserRoleCacheTag, cacheObsolence, token); + private IEnumerable GetCachRelationUserUserRoleCacheTag() + => dbContext.RelationUserUserRoles + .Include(r => r.UserRole) + .Include(r => r.User) + .FromCache(relationUserUserRoleCacheTag, cacheObsolence); + private void DropCacheRelationUserUserRoleCacheTag() + => dbContext.RelationUserUserRoles.DropCache(relationUserUserRoleCacheTag); + + private async Task UpdateRolesCacheForUserAsync(int idUser, IEnumerable newRoles, CancellationToken token) + { + var relations = (await GetCacheRelationUserUserRoleAsync(token)).Where(r => r.IdUser == idUser); + dbContext.RelationUserUserRoles.RemoveRange(relations); + + if (newRoles?.Any() == true) + await dbContext.RelationUserUserRoles.AddRangeAsync(newRoles.Select(role => new RelationUserUserRole + { + IdUser = idUser, + IdUserRole = role.Id + }), token).ConfigureAwait(false); + + await dbContext.SaveChangesAsync(token); + DropCacheRelationUserUserRoleCacheTag(); + } + + protected virtual 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; + entity.PasswordHash = dbContext.Users.FirstOrDefault(u => u.Id == dto.Id)?.PasswordHash; return entity; } - private UserExtendedDto Convert(User entity) + protected virtual UserExtendedDto Convert(User entity) { var dto = entity.Adapt(); return dto; diff --git a/AsbCloudInfrastructure/Repository/UserRoleRepository.cs b/AsbCloudInfrastructure/Repository/UserRoleRepository.cs index 89d6150e..596ecfca 100644 --- a/AsbCloudInfrastructure/Repository/UserRoleRepository.cs +++ b/AsbCloudInfrastructure/Repository/UserRoleRepository.cs @@ -38,7 +38,8 @@ namespace AsbCloudInfrastructure.Repository await UpdatePermissionsAsync(dto, token); await UpdateIncludedRolesAsync(dto, token); - dbContext.UserRoles.DropCache(); + await dbContext.SaveChangesAsync(token); + DropCacheUserRole(); return updatedEntity?.Entity?.Id ?? 0; } @@ -98,6 +99,7 @@ namespace AsbCloudInfrastructure.Repository await UpdateIncludedRolesAsync(dto, token); var result = dbContext.UserRoles.Upsert(entity); + await dbContext.SaveChangesAsync(token); DropCacheUserRole(); return result?.Entity?.Id ?? 0; } diff --git a/AsbCloudInfrastructure/Services/AuthService.cs b/AsbCloudInfrastructure/Services/AuthService.cs index bf81ed4b..385a4a1a 100644 --- a/AsbCloudInfrastructure/Services/AuthService.cs +++ b/AsbCloudInfrastructure/Services/AuthService.cs @@ -1,4 +1,5 @@ using AsbCloudApp.Data; +using AsbCloudApp.Repositories; using AsbCloudApp.Services; using AsbCloudDb.Model; using Mapster; @@ -21,7 +22,7 @@ namespace AsbCloudInfrastructure.Services public class AuthService : IAuthService { private readonly IAsbCloudDbContext db; - private readonly IUserService userService; + private readonly IUserRepository userRepository; public const string issuer = "a"; public const string audience = "a"; @@ -36,10 +37,10 @@ namespace AsbCloudInfrastructure.Services private readonly HashAlgorithm hashAlgorithm; private readonly Random rnd; - public AuthService(IAsbCloudDbContext db, IUserService userService) + public AuthService(IAsbCloudDbContext db, IUserRepository userRepository) { this.db = db; - this.userService = userService; + this.userRepository = userRepository; hashAlgorithm = SHA384.Create(); rnd = new Random((int)(DateTime.Now.Ticks % 2147480161)); } @@ -165,12 +166,12 @@ namespace AsbCloudInfrastructure.Services if (identity is null || user.IdState == 0) return null; - var userDto = await userService.GetOrDefaultAsync(user.Id, token); + var userDto = await userRepository.GetOrDefaultAsync(user.Id, token); if (userDto is null) return null; var dto = userDto.Adapt(); - dto.Permissions = userService.GetNestedPermissions(userDto.Id); + dto.Permissions = userRepository.GetNestedPermissions(userDto.Id); dto.Token = MakeToken(identity.Claims); return dto; } @@ -209,7 +210,7 @@ namespace AsbCloudInfrastructure.Services new (ClaimsIdentity.DefaultNameClaimType, user.Login), new (claimNameIdCompany, user.IdCompany?.ToString()!), }; - var roles = userService.GetRolesByIdUser(user.Id); + var roles = userRepository.GetRolesByIdUser(user.Id); if (roles is not null) foreach (var role in roles) claims.Add(new Claim(ClaimsIdentity.DefaultRoleClaimType, role.Caption)); diff --git a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs index e926a886..b1a6d0c6 100644 --- a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs +++ b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs @@ -1,5 +1,6 @@ using AsbCloudApp.Data; using AsbCloudApp.Exceptions; +using AsbCloudApp.Repositories; using AsbCloudApp.Services; using AsbCloudDb.Model; using AsbCloudInfrastructure.Repository; @@ -21,7 +22,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram private readonly IAsbCloudDbContext context; private readonly FileService fileService; - private readonly IUserService userService; + private readonly IUserRepository userRepository; private readonly IWellService wellService; private readonly IConfiguration configuration; private readonly IBackgroundWorkerService backgroundWorker; @@ -51,7 +52,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram public DrillingProgramService( IAsbCloudDbContext context, FileService fileService, - IUserService userService, + IUserRepository userRepository, IWellService wellService, IConfiguration configuration, IBackgroundWorkerService backgroundWorker, @@ -59,7 +60,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram { this.context = context; this.fileService = fileService; - this.userService = userService; + this.userRepository = userRepository; this.wellService = wellService; this.configuration = configuration; this.backgroundWorker = backgroundWorker; @@ -127,7 +128,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram Parts = parts, Program = files.FirstOrDefault(f => f.IdCategory == idFileCategoryDrillingProgram) .Adapt(), - PermissionToEdit = userService.HasPermission(idUser, "DrillingProgram.edit"), + PermissionToEdit = userRepository.HasPermission(idUser, "DrillingProgram.edit"), }; if (parts.Any()) @@ -226,7 +227,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram public async Task AddUserAsync(int idWell, int idFileCategory, int idUser, int idUserRole, CancellationToken token = default) { - var user = await userService.GetOrDefaultAsync(idUser, token); + var user = await userRepository.GetOrDefaultAsync(idUser, token); if (user is null) throw new ArgumentInvalidException($"User id == {idUser} does not exist", nameof(idUser)); diff --git a/AsbCloudInfrastructure/Services/UserService.cs b/AsbCloudInfrastructure/Services/UserService.cs deleted file mode 100644 index 09e284ae..00000000 --- a/AsbCloudInfrastructure/Services/UserService.cs +++ /dev/null @@ -1,198 +0,0 @@ -using AsbCloudApp.Data; -using AsbCloudApp.Exceptions; -using AsbCloudApp.Repositories; -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 UserService : IUserService - { - private readonly CacheTable cacheUsers; - private readonly CacheTable cacheRelationUserToRoles; - public ISet Includes { get; } = new SortedSet(); - public IUserRoleRepository RoleService { get; } - - private static readonly TypeAdapterConfig userTypeAdapterConfig = TypeAdapterConfig - .NewConfig() - .Ignore(dst => dst.Company, - dst => dst.FileMarks, - dst => dst.Files, - dst => dst.RelationUsersUserRoles) - .Config; - - public UserService(IAsbCloudDbContext context, CacheDb cacheDb, IUserRoleRepository roleService) - { - var db = (AsbCloudDbContext)context; - cacheUsers = cacheDb.GetCachedTable( - db, - new[] { - nameof(User.RelationUsersUserRoles), - nameof(User.Company), - }); - cacheRelationUserToRoles = cacheDb.GetCachedTable( - db, - new[] { - nameof(RelationUserUserRole.User), - nameof(RelationUserUserRole.UserRole), - }); - RoleService = roleService; - } - - public async Task InsertAsync(UserExtendedDto dto, CancellationToken token = default) - { - dto.Id = default; - var entity = Convert(dto); - await AssertLoginIsBusyAsync(dto.Login, token); - var userRoles = await RoleService.GetByNamesAsync(dto.RoleNames, token).ConfigureAwait(false); - var updatedEntity = await cacheUsers.InsertAsync(entity, token).ConfigureAwait(false); - if (userRoles?.Any() == true) - await UpdateRolesCacheForUserAsync(updatedEntity.Id, userRoles, token); - return updatedEntity?.Id ?? 0; - } - - private async Task AssertLoginIsBusyAsync(string login, CancellationToken token = default) - { - var existingUser = await cacheUsers.FirstOrDefaultAsync(u => u.Login.ToLower() == login.ToLower(), token); - if (existingUser is not null) - throw new ArgumentInvalidException($"Login {login} is busy by {existingUser.MakeDisplayName()}, id{existingUser.Id}", nameof(login)); - } - - public Task InsertRangeAsync(IEnumerable newItems, CancellationToken token = default) - { - throw new NotImplementedException(); - } - - public async Task> GetAllAsync(CancellationToken token = default) - { - var entities = (await cacheUsers.WhereAsync(token).ConfigureAwait(false)) - .ToList(); - if (entities.Count == 0) - return null; - var dtos = entities.Select(Convert).ToList(); - for (var i = 0; i < dtos.Count; i++) - dtos[i].RoleNames = GetRolesNamesByIdUser(dtos[i].Id); - return dtos; - } - - public UserExtendedDto GetOrDefault(int id) - { - var entity = cacheUsers.FirstOrDefault(u => u.Id == id); - var dto = Convert(entity); - dto.RoleNames = GetRolesNamesByIdUser(dto.Id); - return dto; - } - - public async Task GetOrDefaultAsync(int id, CancellationToken token = default) - { - var entity = await cacheUsers.FirstOrDefaultAsync(u => u.Id == id, token).ConfigureAwait(false); - var dto = Convert(entity); - dto.RoleNames = GetRolesNamesByIdUser(dto.Id); - return dto; - } - - public async Task UpdateAsync(UserExtendedDto dto, CancellationToken token = default) - { - if (dto.Id <= 1) - throw new ArgumentInvalidException($"Invalid id {dto.Id}. You can't edit this user.", nameof(dto)); - - var oldUser = await cacheUsers.FirstOrDefaultAsync(u => u.Id == dto.Id, token); - if (oldUser.Login != dto.Login) - await AssertLoginIsBusyAsync(dto.Login, token); - - var userRoles = await RoleService.GetByNamesAsync(dto.RoleNames, token).ConfigureAwait(false); - await UpdateRolesCacheForUserAsync(dto.Id, userRoles, token); - - var entity = Convert(dto); - - var result = await cacheUsers.UpsertAsync(entity, token) - .ConfigureAwait(false); - return result; - } - - public Task DeleteAsync(int id, CancellationToken token = default) - { - if (id <= 1) - return Task.FromResult(0); - return cacheUsers.RemoveAsync(r => r.Id == id, token); - } - - public Task DeleteAsync(IEnumerable ids, CancellationToken token = default) - { - var filteredIds = ids.Where(i => i > 1).ToList(); - return cacheUsers.RemoveAsync(r => filteredIds.Contains(r.Id), token); - } - - private IEnumerable GetRolesNamesByIdUser(int idUser) - => GetRolesByIdUser(idUser) - ?.Select(r => r.Caption) - .Distinct(); - - public IEnumerable GetRolesByIdUser(int idUser, int nestedLevel = 0) - { - var roles = cacheRelationUserToRoles.Where(r => r.IdUser == idUser); - if (roles?.Any() != true) - return null; - return roles.SelectMany(r => RoleService.GetNestedById(r.IdUserRole, nestedLevel)); - } - - public IEnumerable GetNestedPermissions(int idUser) - { - var roles = GetRolesByIdUser(idUser, 7); - if (roles?.Any() != true) - return null; - var permissions = roles - .Where(r => r.Permissions is not null) - .SelectMany(r => r.Permissions); - - return permissions; - } - - private async Task UpdateRolesCacheForUserAsync(int idUser, IEnumerable newRoles, CancellationToken token) - { - await cacheRelationUserToRoles.RemoveAsync(r => r.IdUser == idUser, token) - .ConfigureAwait(false); - - if (newRoles?.Any() == true) - await cacheRelationUserToRoles.InsertAsync(newRoles.Select(role => new RelationUserUserRole - { - IdUser = idUser, - IdUserRole = role.Id - }), token).ConfigureAwait(false); - } - - public bool HasPermission(int idUser, string permissionName) - { - if (idUser == 1) - return true; - - var relationsToRoles = cacheRelationUserToRoles.Where(r => r.IdUser == idUser); - if (relationsToRoles is null) - return false; - - return RoleService.HasPermission(relationsToRoles.Select(r => r.IdUserRole), - permissionName); - } - - protected virtual User Convert(UserExtendedDto dto) - { - var entity = dto.Adapt(userTypeAdapterConfig); - if (string.IsNullOrEmpty(entity.PasswordHash)) - entity.PasswordHash = cacheUsers.FirstOrDefault(u => u.Id == dto.Id)?.PasswordHash; - return entity; - } - - protected virtual UserExtendedDto Convert(User entity) - { - var dto = entity.Adapt(); - return dto; - } - } -} diff --git a/AsbCloudInfrastructure/Services/WellFinalDocumentsService.cs b/AsbCloudInfrastructure/Services/WellFinalDocumentsService.cs index 4d44f38c..c82b9f7a 100644 --- a/AsbCloudInfrastructure/Services/WellFinalDocumentsService.cs +++ b/AsbCloudInfrastructure/Services/WellFinalDocumentsService.cs @@ -1,5 +1,6 @@ using AsbCloudApp.Data; using AsbCloudApp.Exceptions; +using AsbCloudApp.Repositories; using AsbCloudApp.Requests; using AsbCloudApp.Services; using AsbCloudDb.Model; @@ -24,7 +25,7 @@ namespace AsbCloudInfrastructure.Services { private readonly IAsbCloudDbContext context; private readonly FileService fileService; - private readonly IUserService userService; + private readonly IUserRepository userRepository; private readonly IWellService wellService; private readonly IConfiguration configuration; private readonly IEmailService emailService; @@ -34,7 +35,7 @@ namespace AsbCloudInfrastructure.Services public WellFinalDocumentsService(IAsbCloudDbContext context, FileService fileService, - IUserService userService, + IUserRepository userRepository, IWellService wellService, IConfiguration configuration, IEmailService emailService, @@ -42,7 +43,7 @@ namespace AsbCloudInfrastructure.Services { this.context = context; this.fileService = fileService; - this.userService = userService; + this.userRepository = userRepository; this.wellService = wellService; this.configuration = configuration; this.emailService = emailService; @@ -120,7 +121,7 @@ namespace AsbCloudInfrastructure.Services var result = new WellCaseDto { IdWell = idWell, - PermissionToSetPubliher = userService.HasPermission(idUser, "WellFinalDocument.editPublisher"), + PermissionToSetPubliher = userRepository.HasPermission(idUser, "WellFinalDocument.editPublisher"), WellFinalDocuments = docs, }; return result; @@ -133,7 +134,7 @@ namespace AsbCloudInfrastructure.Services .ToListAsync(token) .ConfigureAwait(false); - var allUsers = await userService.GetAllAsync(token) + var allUsers = await userRepository.GetAllAsync(token) .ConfigureAwait(false); return allUsers.Where(x => { @@ -181,7 +182,7 @@ namespace AsbCloudInfrastructure.Services { foreach (var item in dtos) { - var user = await userService.GetOrDefaultAsync(item.IdUser, token); + var user = await userRepository.GetOrDefaultAsync(item.IdUser, token); if (user?.Email is not null) { var category = await fileCategoryService.GetOrDefaultAsync(item.IdCategory, token); diff --git a/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs index a1e275b9..2c829eb4 100644 --- a/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs +++ b/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs @@ -1,4 +1,5 @@ using AsbCloudApp.Data; +using AsbCloudApp.Repositories; using AsbCloudApp.Services; using AsbCloudDb.Model; using AsbCloudInfrastructure.Repository; @@ -79,7 +80,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests }; private readonly Mock fileServiceMock; - private readonly Mock userServiceMock; + private readonly Mock userRepositoryMock; private readonly Mock wellServiceMock; private readonly Mock configurationMock; private readonly Mock backgroundWorkerMock; @@ -98,7 +99,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests db.SaveChanges(); fileServiceMock = new Mock(); - userServiceMock = new Mock(); + userRepositoryMock = new Mock(); wellServiceMock = new Mock(); configurationMock = new Mock(); backgroundWorkerMock = new Mock(); @@ -110,7 +111,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests var service = new DrillingProgramService( db, fileServiceMock.Object, - userServiceMock.Object, + userRepositoryMock.Object, wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, @@ -127,7 +128,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests var service = new DrillingProgramService( db, fileServiceMock.Object, - userServiceMock.Object, + userRepositoryMock.Object, wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, @@ -146,7 +147,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests var service = new DrillingProgramService( db, fileServiceMock.Object, - userServiceMock.Object, + userRepositoryMock.Object, wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, @@ -163,13 +164,13 @@ namespace AsbCloudWebApi.Tests.ServicesTests db.DrillingProgramParts.Add(new DrillingProgramPart { IdFileCategory = 1001, IdWell = idWell }); db.SaveChanges(); - userServiceMock.Setup((s) => s.GetOrDefaultAsync(It.IsAny(), It.IsAny())) + userRepositoryMock.Setup((s) => s.GetOrDefaultAsync(It.IsAny(), It.IsAny())) .Returns(Task.FromResult(publisher1.Adapt())); var service = new DrillingProgramService( db, fileServiceMock.Object, - userServiceMock.Object, + userRepositoryMock.Object, wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, @@ -198,13 +199,13 @@ namespace AsbCloudWebApi.Tests.ServicesTests IdUserRole = idUserRole }); db.SaveChanges(); - userServiceMock.Setup((s) => s.GetOrDefaultAsync(It.IsAny(), It.IsAny())) + userRepositoryMock.Setup((s) => s.GetOrDefaultAsync(It.IsAny(), It.IsAny())) .Returns(Task.FromResult(publisher1.Adapt())); var service = new DrillingProgramService( db, fileServiceMock.Object, - userServiceMock.Object, + userRepositoryMock.Object, wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, @@ -230,7 +231,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests var service = new DrillingProgramService( db, fileServiceMock.Object, - userServiceMock.Object, + userRepositoryMock.Object, wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, @@ -261,7 +262,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests var service = new DrillingProgramService( db, fileServiceMock.Object, - userServiceMock.Object, + userRepositoryMock.Object, wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, @@ -299,7 +300,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests var service = new DrillingProgramService( db, fileServiceMock.Object, - userServiceMock.Object, + userRepositoryMock.Object, wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, @@ -326,7 +327,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests var service = new DrillingProgramService( db, fileServiceMock.Object, - userServiceMock.Object, + userRepositoryMock.Object, wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, @@ -353,7 +354,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests var service = new DrillingProgramService( db, fileServiceMock.Object, - userServiceMock.Object, + userRepositoryMock.Object, wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, @@ -383,7 +384,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests var service = new DrillingProgramService( db, fileServiceMock.Object, - userServiceMock.Object, + userRepositoryMock.Object, wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, diff --git a/AsbCloudWebApi.Tests/ServicesTests/UserServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/UserServiceTest.cs deleted file mode 100644 index bd47b853..00000000 --- a/AsbCloudWebApi.Tests/ServicesTests/UserServiceTest.cs +++ /dev/null @@ -1,76 +0,0 @@ -using AsbCloudApp.Data; -using AsbCloudApp.Repositories; -using AsbCloudApp.Services; -using AsbCloudDb.Model; -using AsbCloudInfrastructure.Services; -using AsbCloudInfrastructure.Services.Cache; -using Moq; -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace AsbCloudWebApi.Tests.ServicesTests -{ - public class UserServiceTest - { - private readonly AsbCloudDbContext context; - private readonly CacheDb cacheDb; - private readonly Mock roleRepository; - - public UserServiceTest() - { - context = TestHelpter.MakeTestContext(); - cacheDb = new CacheDb(); - roleRepository = new Mock(); - - context.Users.RemoveRange(context.Users.Where(u => u.Id > 1)); - context.SaveChanges(); - } - - ~UserServiceTest() - { - context.Users.RemoveRange(context.Users.Where(u => u.Id > 1)); - context.SaveChanges(); - } - - [Fact] - public async Task InsertAsync_returns_id() - { - var service = new UserService(context, cacheDb, roleRepository.Object); - var dto = new UserExtendedDto - { - Id = 0, - IdCompany = 1, - Email = "test@test.test", - Login = "test", - Name = "test", - Company = new CompanyDto { Caption = "test", Id = 1 }, - Patronymic = "test", - Phone = "test", - Position = "test", - Surname = "test", - - }; - var id = await service.InsertAsync(dto, CancellationToken.None); - - Assert.NotEqual(0, id); - } - - [Fact] - public async Task InsertAsync_busy_login_throws_exceprion() - { - var service = new UserService(context, cacheDb, roleRepository.Object); - var dto = new UserExtendedDto - { - Id = 5, - Email = "test@test.test", - Login = "test clone" - }; - - await service.InsertAsync(dto, CancellationToken.None); - await Assert.ThrowsAsync(() => service.InsertAsync(dto, CancellationToken.None)); - } - } -} diff --git a/AsbCloudWebApi.Tests/ServicesTests/WellFinalDocumentsServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/WellFinalDocumentsServiceTest.cs index 1718b92d..eb0d0a89 100644 --- a/AsbCloudWebApi.Tests/ServicesTests/WellFinalDocumentsServiceTest.cs +++ b/AsbCloudWebApi.Tests/ServicesTests/WellFinalDocumentsServiceTest.cs @@ -10,6 +10,8 @@ using Xunit; using System.IO; using System.Collections.Generic; using System.Linq; +using AsbCloudApp.Repositories; +using AsbCloudInfrastructure.Repository; namespace AsbCloudWebApi.Tests.ServicesTests { @@ -18,7 +20,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests private readonly AsbCloudDbContext context; private WellFinalDocumentsService service; private readonly Mock fileServiceMock; - private readonly Mock userServiceMock; + private readonly Mock userRepositoryMock; private readonly Mock wellServiceMock; private readonly Mock configurationMock; private readonly Mock emailServiceMock; @@ -45,8 +47,8 @@ namespace AsbCloudWebApi.Tests.ServicesTests context.SaveChanges(); fileServiceMock = new Mock(); - userServiceMock = new Mock(); - userServiceMock.Setup(x => x.GetAllAsync(CancellationToken.None)).Returns(Task.Run(() => users.Select(x => (UserExtendedDto)x))); + userRepositoryMock = new Mock(); + userRepositoryMock.Setup(x => x.GetAllAsync(CancellationToken.None)).Returns(Task.Run(() => users.Select(x => (UserExtendedDto)x))); wellServiceMock = new Mock(); configurationMock = new Mock(); @@ -56,7 +58,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests service = new WellFinalDocumentsService( context: context, fileService: fileServiceMock.Object, - userService: userServiceMock.Object, + userRepository: userRepositoryMock.Object, wellService: wellServiceMock.Object, configuration: configurationMock.Object, emailService: emailServiceMock.Object, diff --git a/AsbCloudWebApi/Controllers/AdminUserController.cs b/AsbCloudWebApi/Controllers/AdminUserController.cs index e515d370..0ce262d0 100644 --- a/AsbCloudWebApi/Controllers/AdminUserController.cs +++ b/AsbCloudWebApi/Controllers/AdminUserController.cs @@ -1,5 +1,7 @@ using AsbCloudApp.Data; +using AsbCloudApp.Repositories; using AsbCloudApp.Services; +using AsbCloudInfrastructure.Repository; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -13,7 +15,7 @@ namespace AsbCloudWebApi.Controllers [Authorize] public class AdminUserController : CrudController> { - public AdminUserController(IUserService service) + public AdminUserController(IUserRepository service) : base(service) { } diff --git a/AsbCloudWebApi/Controllers/AuthController.cs b/AsbCloudWebApi/Controllers/AuthController.cs index aeec1f81..b2702f81 100644 --- a/AsbCloudWebApi/Controllers/AuthController.cs +++ b/AsbCloudWebApi/Controllers/AuthController.cs @@ -1,5 +1,7 @@ using AsbCloudApp.Data; +using AsbCloudApp.Repositories; using AsbCloudApp.Services; +using AsbCloudInfrastructure.Repository; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; @@ -16,12 +18,12 @@ namespace AsbCloudWebApi.Controllers public class AuthController : ControllerBase { private readonly IAuthService authService; - private readonly IUserService userService; + private readonly IUserRepository userRepository; - public AuthController(IAuthService authService, IUserService userService) + public AuthController(IAuthService authService, IUserRepository userRepository) { this.authService = authService; - this.userService = userService; + this.userRepository = userRepository; } /// @@ -97,7 +99,7 @@ namespace AsbCloudWebApi.Controllers if (editorUserId is null) return Forbid(); - if (!((editorUserId == idUser) || userService.HasPermission((int)editorUserId, "Auth.edit"))) + if (!((editorUserId == idUser) || userRepository.HasPermission((int)editorUserId, "Auth.edit"))) return Forbid(); var code = authService.ChangePassword(idUser, newPassword); diff --git a/AsbCloudWebApi/Controllers/FileController.cs b/AsbCloudWebApi/Controllers/FileController.cs index d4dfad33..1340c55a 100644 --- a/AsbCloudWebApi/Controllers/FileController.cs +++ b/AsbCloudWebApi/Controllers/FileController.cs @@ -8,6 +8,8 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using AsbCloudDb.Model; +using AsbCloudInfrastructure.Repository; +using AsbCloudApp.Repositories; namespace AsbCloudWebApi.Controllers { @@ -42,7 +44,7 @@ namespace AsbCloudWebApi.Controllers [Permission] [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] public async Task SaveFilesAsync(int idWell, int idCategory, - [FromForm] IFormFileCollection files, [FromServices] IUserService userService, CancellationToken token = default) + [FromForm] IFormFileCollection files, [FromServices] IUserRepository userRepository, CancellationToken token = default) { int? idCompany = User.GetCompanyId(); int? idUser = User.GetUserId(); @@ -54,7 +56,7 @@ namespace AsbCloudWebApi.Controllers idWell, token).ConfigureAwait(false)) return Forbid(); - if (!userService.HasPermission((int)idUser, $"File.edit{idCategory}")) + if (!userRepository.HasPermission((int)idUser, $"File.edit{idCategory}")) return Forbid(); foreach (var file in files) @@ -140,7 +142,7 @@ namespace AsbCloudWebApi.Controllers [Permission] [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] public async Task DeleteAsync(int idWell, int idFile, - [FromServices] IUserService userService, + [FromServices] IUserRepository userRepository, CancellationToken token = default) { int? idUser = User.GetUserId(); @@ -156,7 +158,7 @@ namespace AsbCloudWebApi.Controllers if (fileInfo is null) return NotFound(idFile); - if (!userService.HasPermission((int)idUser, $"File.edit{fileInfo?.IdCategory}")) + if (!userRepository.HasPermission((int)idUser, $"File.edit{fileInfo?.IdCategory}")) return Forbid(); var result = await fileService.MarkAsDeletedAsync(idFile, token); diff --git a/AsbCloudWebApi/Middlewares/PermissionsMiddlware.cs b/AsbCloudWebApi/Middlewares/PermissionsMiddlware.cs index 5373cf64..4f05c7d2 100644 --- a/AsbCloudWebApi/Middlewares/PermissionsMiddlware.cs +++ b/AsbCloudWebApi/Middlewares/PermissionsMiddlware.cs @@ -1,4 +1,5 @@ -using AsbCloudApp.Services; +using AsbCloudApp.Repositories; +using AsbCloudApp.Services; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -69,7 +70,7 @@ namespace AsbCloudWebApi.Middlewares PermissionAttribute.Registered.Add(permissionName); } - var userService = context.RequestServices.GetRequiredService(); + var userService = context.RequestServices.GetRequiredService(); isAuthorized = userService.HasPermission((int)idUser, permissionName); }