2022-10-26 15:36:49 +05:00
|
|
|
|
using AsbCloudApp.Data;
|
2022-10-27 11:22:39 +05:00
|
|
|
|
using AsbCloudApp.Exceptions;
|
2022-10-26 15:36:49 +05:00
|
|
|
|
using AsbCloudApp.Repositories;
|
|
|
|
|
using AsbCloudDb.Model;
|
|
|
|
|
using Mapster;
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
2022-10-27 11:22:39 +05:00
|
|
|
|
using System;
|
2022-10-26 15:36:49 +05:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
2023-02-05 21:53:51 +05:00
|
|
|
|
using Microsoft.Extensions.Caching.Memory;
|
2022-10-26 15:36:49 +05:00
|
|
|
|
|
|
|
|
|
namespace AsbCloudInfrastructure.Repository
|
|
|
|
|
{
|
|
|
|
|
#nullable enable
|
|
|
|
|
public class UserRepository : IUserRepository
|
|
|
|
|
{
|
2022-10-27 11:22:39 +05:00
|
|
|
|
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);
|
2022-10-26 15:36:49 +05:00
|
|
|
|
private static readonly TypeAdapterConfig userTypeAdapterConfig = TypeAdapterConfig<UserExtendedDto, User>
|
|
|
|
|
.NewConfig()
|
|
|
|
|
.Ignore(dst => dst.Company,
|
|
|
|
|
dst => dst.FileMarks,
|
|
|
|
|
dst => dst.Files,
|
|
|
|
|
dst => dst.RelationUsersUserRoles)
|
|
|
|
|
.Config;
|
2023-02-05 21:53:51 +05:00
|
|
|
|
private readonly IMemoryCache memoryCache;
|
|
|
|
|
public UserRepository(IAsbCloudDbContext dbContext, IUserRoleRepository userRoleRepository, IMemoryCache memoryCache)
|
|
|
|
|
{
|
2022-10-27 11:22:39 +05:00
|
|
|
|
this.dbContext = dbContext;
|
|
|
|
|
this.userRoleRepository = userRoleRepository;
|
2023-02-05 21:53:51 +05:00
|
|
|
|
this.memoryCache = memoryCache;
|
2022-10-26 15:36:49 +05:00
|
|
|
|
}
|
|
|
|
|
|
2022-12-14 10:38:44 +05:00
|
|
|
|
public async Task<int> InsertAsync(UserExtendedDto dto, CancellationToken token)
|
2022-10-26 15:36:49 +05:00
|
|
|
|
{
|
|
|
|
|
dto.Id = default;
|
2022-10-27 11:22:39 +05:00
|
|
|
|
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);
|
2022-12-14 10:38:44 +05:00
|
|
|
|
await dbContext.SaveChangesAsync(token);
|
|
|
|
|
|
2022-10-27 11:22:39 +05:00
|
|
|
|
if (userRoles?.Any() == true)
|
|
|
|
|
await UpdateRolesCacheForUserAsync(updatedEntity.Entity.Id, userRoles, token);
|
|
|
|
|
|
|
|
|
|
DropCacheUsers();
|
2022-11-08 12:04:09 +05:00
|
|
|
|
return updatedEntity.Entity.Id;
|
2022-10-27 11:22:39 +05:00
|
|
|
|
}
|
|
|
|
|
|
2022-12-14 10:38:44 +05:00
|
|
|
|
public Task<int> InsertRangeAsync(IEnumerable<UserExtendedDto> newItems, CancellationToken token)
|
2022-10-27 11:22:39 +05:00
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-14 10:38:44 +05:00
|
|
|
|
public async Task<IEnumerable<UserExtendedDto>> GetAllAsync(CancellationToken token)
|
2022-10-27 11:22:39 +05:00
|
|
|
|
{
|
2023-02-05 21:53:51 +05:00
|
|
|
|
var dtos = (await GetCacheUserAsync(token)).ToList();
|
|
|
|
|
var listDtos = dtos.ToList();
|
2022-10-27 15:44:04 +05:00
|
|
|
|
if (dtos is null)
|
|
|
|
|
return Enumerable.Empty<UserExtendedDto>();
|
2022-10-27 11:22:39 +05:00
|
|
|
|
|
2023-02-05 21:53:51 +05:00
|
|
|
|
for (var i = 0; i < listDtos.Count; i++)
|
|
|
|
|
listDtos[i].RoleNames = GetRolesNamesByIdUser(listDtos[i].Id);
|
2022-10-27 11:22:39 +05:00
|
|
|
|
return dtos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public UserExtendedDto? GetOrDefault(int id)
|
|
|
|
|
{
|
2022-10-27 15:44:04 +05:00
|
|
|
|
var dto = GetCacheUser().FirstOrDefault(u => u.Id == id);
|
|
|
|
|
if (dto is null)
|
2022-10-27 11:22:39 +05:00
|
|
|
|
return null;
|
2022-10-27 15:44:04 +05:00
|
|
|
|
var entity = Convert(dto);
|
2022-10-27 11:22:39 +05:00
|
|
|
|
dto.RoleNames = GetRolesNamesByIdUser(dto.Id);
|
|
|
|
|
return dto;
|
2022-10-26 15:36:49 +05:00
|
|
|
|
}
|
|
|
|
|
|
2022-12-14 10:38:44 +05:00
|
|
|
|
public async Task<UserExtendedDto?> GetOrDefaultAsync(int id, CancellationToken token)
|
2022-10-26 15:36:49 +05:00
|
|
|
|
{
|
2022-10-27 15:44:04 +05:00
|
|
|
|
var dto = (await GetCacheUserAsync(token)).FirstOrDefault(u => u.Id == id);
|
|
|
|
|
if (dto is null)
|
2022-10-27 11:22:39 +05:00
|
|
|
|
return null;
|
2022-10-27 15:44:04 +05:00
|
|
|
|
|
2022-10-27 11:22:39 +05:00
|
|
|
|
dto.RoleNames = GetRolesNamesByIdUser(dto.Id);
|
|
|
|
|
return dto;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-14 10:38:44 +05:00
|
|
|
|
public async Task<int> UpdateAsync(UserExtendedDto dto, CancellationToken token)
|
2022-10-27 11:22:39 +05:00
|
|
|
|
{
|
|
|
|
|
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);
|
2022-11-08 12:19:50 +05:00
|
|
|
|
await UpdateRolesCacheForUserAsync(dto.Id, userRoles, token);
|
2023-02-02 15:21:48 +05:00
|
|
|
|
var userInDb = dbContext.Users.FirstOrDefault(u => u.Id == dto.Id);
|
|
|
|
|
if (userInDb is not null)
|
|
|
|
|
{
|
|
|
|
|
userInDb = Convert(dto);
|
|
|
|
|
await dbContext.SaveChangesAsync(token);
|
|
|
|
|
}
|
2022-10-27 11:22:39 +05:00
|
|
|
|
DropCacheUsers();
|
2023-02-02 15:21:48 +05:00
|
|
|
|
return userInDb!.Id;
|
2022-10-27 11:22:39 +05:00
|
|
|
|
}
|
|
|
|
|
|
2022-12-14 10:38:44 +05:00
|
|
|
|
public async Task<int> DeleteAsync(int id, CancellationToken token)
|
2022-10-26 15:36:49 +05:00
|
|
|
|
{
|
2022-10-27 15:44:04 +05:00
|
|
|
|
var dto = (await GetCacheUserAsync(token)).FirstOrDefault(u => u.Id == id);
|
|
|
|
|
if (dto is null)
|
2022-10-27 11:22:39 +05:00
|
|
|
|
return 0;
|
|
|
|
|
|
2022-10-27 15:44:04 +05:00
|
|
|
|
var entity = Convert(dto);
|
2022-10-27 11:22:39 +05:00
|
|
|
|
var result = dbContext.Users.Remove(entity);
|
|
|
|
|
await dbContext.SaveChangesAsync(token);
|
|
|
|
|
DropCacheUsers();
|
2022-11-08 12:04:09 +05:00
|
|
|
|
return result.Entity.Id;
|
2022-10-27 11:22:39 +05:00
|
|
|
|
}
|
|
|
|
|
|
2022-10-27 14:18:59 +05:00
|
|
|
|
public IEnumerable<UserRoleDto> GetRolesByIdUser(int idUser, int nestedLevel = 0)
|
2022-10-27 11:22:39 +05:00
|
|
|
|
{
|
|
|
|
|
var roles = GetCachRelationUserUserRoleCacheTag().Where(r => r.IdUser == idUser);
|
|
|
|
|
return roles.SelectMany(r => userRoleRepository.GetNestedById(r.IdUserRole, nestedLevel));
|
2022-10-26 15:36:49 +05:00
|
|
|
|
}
|
|
|
|
|
|
2022-10-27 14:18:59 +05:00
|
|
|
|
public IEnumerable<PermissionDto> GetNestedPermissions(int idUser)
|
2022-10-27 11:22:39 +05:00
|
|
|
|
{
|
|
|
|
|
var roles = GetRolesByIdUser(idUser, 7);
|
|
|
|
|
if (roles is null)
|
2022-11-07 13:53:10 +05:00
|
|
|
|
return Enumerable.Empty<PermissionDto>();
|
2022-10-27 11:22:39 +05:00
|
|
|
|
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;
|
2022-10-26 15:36:49 +05:00
|
|
|
|
|
2022-10-27 14:18:59 +05:00
|
|
|
|
var relationsToRoles = GetCachRelationUserUserRoleCacheTag()
|
|
|
|
|
.Where(r => r.IdUser == idUser);
|
2022-10-27 11:22:39 +05:00
|
|
|
|
if (relationsToRoles is null)
|
|
|
|
|
return false;
|
|
|
|
|
|
2022-10-27 14:18:59 +05:00
|
|
|
|
return userRoleRepository.HasPermission(relationsToRoles
|
|
|
|
|
.Select(r => r.IdUserRole), permissionName);
|
2022-10-27 11:22:39 +05:00
|
|
|
|
}
|
|
|
|
|
|
2022-11-08 12:22:31 +05:00
|
|
|
|
private IEnumerable<string> GetRolesNamesByIdUser(int idUser)
|
2022-11-07 13:53:10 +05:00
|
|
|
|
=> GetRolesByIdUser(idUser, 7)
|
2022-11-08 12:19:50 +05:00
|
|
|
|
.Select(r => r.Caption)
|
2022-10-27 11:22:39 +05:00
|
|
|
|
.Distinct();
|
|
|
|
|
|
2022-12-14 10:38:44 +05:00
|
|
|
|
private async Task AssertLoginIsBusyAsync(string login, CancellationToken token)
|
2022-10-27 11:22:39 +05:00
|
|
|
|
{
|
2022-10-27 15:44:04 +05:00
|
|
|
|
var existingUserDto = (await GetCacheUserAsync(token))
|
2022-10-27 11:22:39 +05:00
|
|
|
|
.FirstOrDefault(u => u.Login.ToLower() == login.ToLower());
|
2022-10-27 15:49:22 +05:00
|
|
|
|
|
|
|
|
|
if (existingUserDto is not null)
|
|
|
|
|
throw new ArgumentInvalidException($"Login {login} is busy by {existingUserDto.MakeDisplayName()}, id{existingUserDto.Id}", nameof(login));
|
2022-10-27 11:22:39 +05:00
|
|
|
|
}
|
|
|
|
|
|
2023-02-05 21:53:51 +05:00
|
|
|
|
private async Task<IEnumerable<UserExtendedDto>> GetCacheUserAsync(CancellationToken token)
|
|
|
|
|
{
|
|
|
|
|
var query = dbContext.Users
|
2022-10-27 11:22:39 +05:00
|
|
|
|
.Include(r => r.Company)
|
2023-02-05 21:53:51 +05:00
|
|
|
|
.Include(r => r.RelationUsersUserRoles);
|
|
|
|
|
return await FromCacheAsync(query, userCacheTag, cacheObsolence, Convert, token);
|
|
|
|
|
}
|
2022-10-27 15:44:04 +05:00
|
|
|
|
private IEnumerable<UserExtendedDto> GetCacheUser()
|
2023-02-05 21:53:51 +05:00
|
|
|
|
{
|
|
|
|
|
var query = dbContext.Users
|
2022-10-27 11:22:39 +05:00
|
|
|
|
.Include(r => r.Company)
|
2023-02-05 21:53:51 +05:00
|
|
|
|
.Include(r => r.RelationUsersUserRoles);
|
|
|
|
|
return FromCache(query, userCacheTag, cacheObsolence, Convert);
|
|
|
|
|
}
|
2022-10-27 11:22:39 +05:00
|
|
|
|
private void DropCacheUsers()
|
2023-02-05 21:53:51 +05:00
|
|
|
|
{
|
|
|
|
|
memoryCache.Remove(userCacheTag);
|
|
|
|
|
}
|
2022-10-27 11:22:39 +05:00
|
|
|
|
|
2023-02-05 21:53:51 +05:00
|
|
|
|
private async Task<IEnumerable<RelationUserUserRole>> GetCacheRelationUserUserRoleAsync(CancellationToken token)
|
|
|
|
|
{
|
|
|
|
|
var query = dbContext.RelationUserUserRoles
|
2022-10-27 11:22:39 +05:00
|
|
|
|
.Include(r => r.UserRole)
|
2023-02-05 21:53:51 +05:00
|
|
|
|
.Include(r => r.User);
|
|
|
|
|
return await FromCacheAsync(query, relationUserUserRoleCacheTag, cacheObsolence, token);
|
|
|
|
|
}
|
2022-10-27 11:22:39 +05:00
|
|
|
|
private IEnumerable<RelationUserUserRole> GetCachRelationUserUserRoleCacheTag()
|
2023-02-05 21:53:51 +05:00
|
|
|
|
{
|
|
|
|
|
var query = dbContext.RelationUserUserRoles
|
|
|
|
|
.Include(r => r.UserRole)
|
|
|
|
|
.Include(r => r.User);
|
|
|
|
|
return FromCache(query, relationUserUserRoleCacheTag, cacheObsolence);
|
|
|
|
|
}
|
2022-10-27 11:22:39 +05:00
|
|
|
|
private void DropCacheRelationUserUserRoleCacheTag()
|
2023-02-05 21:53:51 +05:00
|
|
|
|
{
|
|
|
|
|
memoryCache.Remove(relationUserUserRoleCacheTag);
|
|
|
|
|
}
|
2022-10-27 11:22:39 +05:00
|
|
|
|
|
|
|
|
|
private async Task UpdateRolesCacheForUserAsync(int idUser, IEnumerable<UserRoleDto> 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();
|
|
|
|
|
}
|
2022-10-26 15:36:49 +05:00
|
|
|
|
|
2023-02-05 21:53:51 +05:00
|
|
|
|
public async Task<IEnumerable<TModel>> FromCacheAsync<TEntity, TModel>(IQueryable<TEntity> query, string tag, TimeSpan obsolescence, Func<TEntity, TModel> convert, CancellationToken token)
|
|
|
|
|
where TEntity : class
|
|
|
|
|
{
|
|
|
|
|
async Task<TEntity[]> factory(CancellationToken token)
|
|
|
|
|
=> await query.AsNoTracking().ToArrayAsync(token);
|
|
|
|
|
var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token);
|
|
|
|
|
return cache.Select(convert);
|
|
|
|
|
}
|
|
|
|
|
public async Task<IEnumerable<TEntity>> FromCacheAsync<TEntity>(IQueryable<TEntity> query, string tag, TimeSpan obsolescence, CancellationToken token)
|
|
|
|
|
where TEntity : class
|
|
|
|
|
{
|
|
|
|
|
async Task<TEntity[]> factory(CancellationToken token)
|
|
|
|
|
=> await query.AsNoTracking().ToArrayAsync(token);
|
|
|
|
|
var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token);
|
|
|
|
|
return cache;
|
|
|
|
|
}
|
|
|
|
|
public IEnumerable<TModel> FromCache<TEntity, TModel>(IQueryable<TEntity> query, string tag, TimeSpan obsolescence, Func<TEntity, TModel> convert)
|
|
|
|
|
where TEntity : class
|
|
|
|
|
{
|
|
|
|
|
TEntity[] factory()
|
|
|
|
|
=> query.AsNoTracking().ToArray();
|
|
|
|
|
var cache = GetOrAddCache(tag, factory, obsolescence);
|
|
|
|
|
return cache.Select(convert);
|
|
|
|
|
}
|
|
|
|
|
public IEnumerable<TEntity> FromCache<TEntity>(IQueryable<TEntity> query, string tag, TimeSpan obsolescence)
|
|
|
|
|
where TEntity : class
|
|
|
|
|
{
|
|
|
|
|
TEntity[] factory()
|
|
|
|
|
=> query.AsNoTracking().ToArray();
|
|
|
|
|
var cache = GetOrAddCache(tag, factory, obsolescence);
|
|
|
|
|
return cache;
|
|
|
|
|
}
|
|
|
|
|
private async Task<TEntity[]> GetOrAddCacheAsync<TEntity>(string tag, Func<CancellationToken, Task<TEntity[]>> valueFactoryAsync, TimeSpan obsolete, CancellationToken token)
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
memoryCache.TryGetValue(tag, out TEntity[]? cached);
|
|
|
|
|
if (cached == null)
|
|
|
|
|
{
|
|
|
|
|
var values = await valueFactoryAsync(token);
|
|
|
|
|
|
|
|
|
|
if (values != null)
|
|
|
|
|
{
|
|
|
|
|
memoryCache.Set(tag, values, new MemoryCacheEntryOptions().SetAbsoluteExpiration(obsolete));
|
|
|
|
|
}
|
|
|
|
|
return values!;
|
|
|
|
|
}
|
|
|
|
|
return cached;
|
|
|
|
|
}
|
|
|
|
|
private TEntity[] GetOrAddCache<TEntity>(string tag, Func<TEntity[]> valueFactory, TimeSpan obsolete)
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
memoryCache.TryGetValue(tag, out TEntity[]? cached);
|
|
|
|
|
if (cached == null)
|
|
|
|
|
{
|
|
|
|
|
var values = valueFactory();
|
|
|
|
|
|
|
|
|
|
if (values != null)
|
|
|
|
|
{
|
|
|
|
|
memoryCache.Set(tag, values, new MemoryCacheEntryOptions().SetAbsoluteExpiration(obsolete));
|
|
|
|
|
}
|
|
|
|
|
return values!;
|
|
|
|
|
}
|
|
|
|
|
return cached;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-27 11:22:39 +05:00
|
|
|
|
protected virtual User Convert(UserExtendedDto dto)
|
2022-10-26 15:36:49 +05:00
|
|
|
|
{
|
2023-02-02 15:21:48 +05:00
|
|
|
|
var entity = dto.Adapt<User>(userTypeAdapterConfig);
|
2022-10-26 15:36:49 +05:00
|
|
|
|
if (string.IsNullOrEmpty(entity.PasswordHash))
|
2022-10-27 11:22:39 +05:00
|
|
|
|
entity.PasswordHash = dbContext.Users.FirstOrDefault(u => u.Id == dto.Id)?.PasswordHash;
|
2022-10-26 15:36:49 +05:00
|
|
|
|
return entity;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-27 11:22:39 +05:00
|
|
|
|
protected virtual UserExtendedDto Convert(User entity)
|
2022-10-26 15:36:49 +05:00
|
|
|
|
{
|
|
|
|
|
var dto = entity.Adapt<UserExtendedDto>();
|
|
|
|
|
return dto;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#nullable disable
|
|
|
|
|
}
|