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 UserService : IUserService { private readonly CacheTable cacheUsers; private readonly CacheTable cacheRelationUserToRoles; public ISet Includes { get; } = new SortedSet(); public IUserRoleService 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, IUserRoleService 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; } } }