MyBookmark/Modules.User.Infrastructure.Database/Repositories/UserRepository.cs

330 lines
13 KiB
C#

using Microsoft.EntityFrameworkCore;
using Modules.User.Application.Models.Access;
using Modules.User.Application.Repositories;
using Modules.User.Database.Database;
using Modules.User.Database.Database.Entities;
using Modules.User.Domain.Factories;
using Modules.User.Domain.Factories.User;
using Modules.User.Domain.ValueObjects;
using ClientInfo = Modules.User.Domain.Entities.User.ClientInfo;
namespace Modules.User.Database.Repositories;
public class UserRepository(UserDbContext context) : IUserRepository
{
private IQueryable<Database.Entities.User> GetUserQuery(bool asNoTracking = true)
{
var query = context.Users
.Include(q => q.Account)
.ThenInclude(q => q.Sessions)
.Include(q => q.Account)
.ThenInclude(q => q.Bans)
.Include(q => q.Account)
.ThenInclude(q => q.Roles)
.ThenInclude(q => q.Role)
// .ThenInclude(q => q.Permissions)
.Include(q => q.Account)
.ThenInclude(q => q.Permissions)
// .ThenInclude(q => q.Permission)
.AsSplitQuery();
return asNoTracking ? query.AsNoTracking() : query;
}
public async Task<Domain.Entities.User.User?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
{
var dbUser = await GetUserQuery()
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
return dbUser == null ? null : MapToDomain(dbUser);
}
public async Task<bool> ExistsByEmailAsync(string email, CancellationToken cancellationToken)
{
return await context.Accounts
.AnyAsync(q => q.Email == email.Trim().ToLower(), cancellationToken);
}
public async Task<HashedAccount?> TryGetHashedAccountAsync(string email, CancellationToken cancellationToken)
{
var normalized = email.Trim().ToLowerInvariant();
var account = await context.Accounts
.Where(a => a.Email == normalized)
.Select(q => new HashedAccount(q.Id, q.HashedPassword))
.SingleOrDefaultAsync(cancellationToken);
return account;
}
public async Task<Domain.Entities.User.User?> GetByAccountIdAsync(Guid accountId, CancellationToken cancellationToken = default)
{
var dbUser = await GetUserQuery()
.FirstOrDefaultAsync(x => x.Account.Id == accountId, cancellationToken);
return dbUser == null ? null : MapToDomain(dbUser);
}
public Task AddAsync(Domain.Entities.User.User user, CancellationToken cancellationToken = default)
{
var dbUser = MapToEfNew(user);
context.Users.Add(dbUser);
return Task.CompletedTask;
}
public async Task SaveAsync(Domain.Entities.User.User user, CancellationToken cancellationToken = default)
{
var dbUser = await GetUserQuery(asNoTracking: false)
.FirstAsync(x => x.Id == user.Id, cancellationToken);
await context.Entry(dbUser.Account)
.Collection(a => a.Sessions)
.Query()
.Include(q => q.ClientInfo)
.LoadAsync(cancellationToken);
// await context.Entry(dbUser.Account).Collection(a => a.Roles).LoadAsync(cancellationToken);
// await context.Entry(dbUser.Account).Collection(a => a.Permissions).LoadAsync(cancellationToken);
// await context.Entry(dbUser.Account).Collection(a => a.Bans).LoadAsync(cancellationToken);
// ApplyDomainToEf(user, dbUser);
await ApplyDomainToEf(user, dbUser, cancellationToken);
}
// ---------- Mapping ----------
private static Domain.Entities.User.User MapToDomain(Database.Entities.User ef)
{
var email = Email.Create(ef.Account.Email);
var sessions = ef.Account.Sessions
.OrderBy(q => q.Id)
.Select(q => SessionFactory.Load(q.Id, q.RefreshToken,
ClientInfo.Create(q.ClientInfo.UserAgent, q.ClientInfo.Country, q.ClientInfo.Region),
q.ExpiredDate, q.LastUpdate));
var roles = ef.Account.Roles
.Where(q => !q.RevokedAtUtc.HasValue)
.OrderByDescending(q => q.GrantedAtUtc)
.Select(q => RoleGrantFactory.Load(q.RoleId, q.GrantedAtUtc, q.IssuerId, q.GrantReason,
q.RevokedAtUtc, q.RevokerId, q.RevokeReason));
var permissions = ef.Account.Permissions
.Where(q => !q.RevokedAtUtc.HasValue)
.OrderByDescending(q => q.GrantedAtUtc)
.Select(q => PermissionGrantFactory.Load(q.PermissionId, q.GrantedAtUtc, q.IssuerId, q.GrantReason,
q.RevokedAtUtc, q.RevokerId, q.RevokeReason));
var bans = ef.Account.Bans
.OrderByDescending(q => q.ReleaseDate).ThenByDescending(q => q.BanDate)
.Select(q => BanFactory.Load(q.Id, q.Reason, q.BanDate, q.ReleaseDate, q.IssuerId,
q.UnbanIssuerId, q.UnbanDate, q.UnbanReason, q.Deleted));
var account = AccountFactory.Load(ef.Account.Id, email, ef.Account.HashedPassword,
sessions, roles, permissions, bans, ef.Account.Deleted);
var user = UserFactory.Load(ef.Id, ef.NickName, ef.FirstName, ef.Patronymic, ef.LastName,
ef.AvatarId, ef.RegionalSettings?.LanguageId, ef.BirthDate, account, ef.Deleted);
return user;
}
private static Database.Entities.User MapToEfNew(Domain.Entities.User.User domain)
{
return new Database.Entities.User
{
Id = domain.Id,
NickName = domain.NickName,
FirstName = domain.FirstName,
Patronymic = domain.Patronymic,
LastName = domain.LastName,
AvatarId = domain.Avatar?.Id,
RegionalSettings = domain.Language is null ? null : new RegionalSettings { LanguageId = domain.Language.Id },
BirthDate = domain.BirthDate,
Account = new Account
{
Id = domain.Account.Id,
UserId = domain.Id,
Email = domain.Account.Email.Value,
HashedPassword = domain.Account.HashedPassword,
Sessions = [..domain.Account.Sessions.Select(s => new Session
{
Id = s.Id,
AccountId = domain.Account.Id,
RefreshToken = s.RefreshToken,
ClientInfo = new Database.Entities.ClientInfo
{
UserAgent = s.ClientInfo.UserAgent,
Country = s.ClientInfo.Country,
Region = s.ClientInfo.Region
},
ExpiredDate = s.ExpiredDateUtc,
LastUpdate = s.LastUpdateUtc
})],
Roles = [..domain.Account.RoleGrants.Select(r => new AccountRole
{
// Id =
// AccountId = domain.Account.Id,
RoleId = r.RoleId,
IssuerId = r.IssuerId,
GrantedAtUtc = r.GrantedAtUtc,
RevokedAtUtc = null,
})],
Permissions = [..domain.Account.PermissionGrants.Select(p => new AccountPermission
{
// Id =
// AccountId = domain.Account.Id,
PermissionId = p.PermissionId,
IssuerId = p.IssuerId,
GrantedAtUtc = p.GrantedAtUtc,
RevokedAtUtc = null,
})],
Bans = [..domain.Account.Bans.Select(b => new AccountBan
{
Id = b.Id,
AccountId = domain.Account.Id,
Reason = b.Reason,
BanDate = b.BanDate,
ReleaseDate = b.ReleaseDate,
IssuerId = b.BanIssuerId,
Deleted = b.Deleted,
})],
Deleted = domain.Account.Deleted,
},
Deleted = domain.Deleted,
};
}
private async Task ApplyDomainToEf(Domain.Entities.User.User domain, Database.Entities.User ef, CancellationToken cancellationToken)
{
ef.NickName = domain.NickName;
ef.FirstName = domain.FirstName;
ef.Patronymic = domain.Patronymic;
ef.LastName = domain.LastName;
ef.AvatarId = domain.Avatar?.Id;
ef.RegionalSettings ??= new RegionalSettings();
ef.RegionalSettings.LanguageId = domain.Language?.Id;
ef.BirthDate = domain.BirthDate;
ef.Deleted = domain.Deleted;
ef.Account.Email = domain.Account.Email.Value;
ef.Account.HashedPassword = domain.Account.HashedPassword;
ef.Account.Deleted = domain.Account.Deleted;
var efSessions = ef.Account.Sessions.ToDictionary(s => s.Id, s => s);
var domainSessions = domain.Account.Sessions.ToDictionary(s => s.Id, s => s);
foreach (var es in ef.Account.Sessions.ToList())
{
if (!domainSessions.ContainsKey(es.Id))
ef.Account.Sessions.Remove(es);
}
// var keepIds = domain.Account.Sessions.Select(s => s.Id).ToArray();
// await context.Sessions
// .Where(s => s.AccountId == ef.Account.Id && !keepIds.Contains(s.Id))
// .ExecuteDeleteAsync(cancellationToken);
foreach (var ds in domain.Account.Sessions)
{
if (!efSessions.TryGetValue(ds.Id, out var es))
{
es = new Session
{
Id = ds.Id,
AccountId = ef.Account.Id,
ClientInfo = new Database.Entities.ClientInfo
{
UserAgent = ds.ClientInfo.UserAgent,
Country = ds.ClientInfo.Country,
Region = ds.ClientInfo.Region,
}
};
// ef.Account.Sessions.Add(es);
context.Sessions.Add(es);
}
es.RefreshToken = ds.RefreshToken;
es.ExpiredDate = ds.ExpiredDateUtc;
es.LastUpdate = ds.LastUpdateUtc;
es.ClientInfo ??= new Database.Entities.ClientInfo();
es.ClientInfo.UserAgent = ds.ClientInfo.UserAgent;
es.ClientInfo.Country = ds.ClientInfo.Country;
es.ClientInfo.Region = ds.ClientInfo.Region;
}
var now = DateTime.UtcNow;
var efRoles = ef.Account.Roles
.Where(q => !q.RevokedAtUtc.HasValue)
.ToDictionary(x => x.RoleId);
var domainRoles = domain.Account.RoleGrants
.Select(r => r.RoleId)
.ToList();
foreach (var dg in domain.Account.RoleGrants)
{
if (!efRoles.TryGetValue(dg.RoleId, out var er))
{
er = new AccountRole
{
AccountId = ef.Account.Id,
RoleId = dg.RoleId,
GrantedAtUtc = dg.GrantedAtUtc,
IssuerId = dg.IssuerId,
RevokedAtUtc = null,
};
ef.Account.Roles.Add(er);
}
}
foreach (var er in efRoles.Values)
{
if (!domainRoles.Contains(er.RoleId))
{
er.RevokedAtUtc = now;
}
}
var efPermissions = ef.Account.Permissions
.Where(q => !q.RevokedAtUtc.HasValue)
.ToDictionary(p => p.PermissionId);
var domainPermissions = domain.Account.PermissionGrants
.Select(r => r.PermissionId)
.ToList();
foreach (var dg in domain.Account.PermissionGrants)
{
if (!efPermissions.TryGetValue(dg.PermissionId, out var ep))
{
ep = new AccountPermission
{
AccountId = ef.Account.Id,
PermissionId = dg.PermissionId,
GrantedAtUtc = dg.GrantedAtUtc,
IssuerId = dg.IssuerId,
RevokedAtUtc = null,
};
ef.Account.Permissions.Add(ep);
}
}
foreach (var er in efPermissions.Values)
{
if (!domainPermissions.Contains(er.PermissionId))
{
er.RevokedAtUtc = now;
}
}
var efBans = ef.Account.Bans.ToDictionary(b => b.Id);
foreach (var db in domain.Account.Bans)
{
if (!efBans.TryGetValue(db.Id, out var eb))
{
eb = new AccountBan
{
Id = db.Id,
AccountId = ef.Account.Id,
IssuerId = db.BanIssuerId,
};
ef.Account.Bans.Add(eb);
}
eb.Reason = db.Reason;
eb.BanDate = db.BanDate;
eb.ReleaseDate = db.ReleaseDate;
eb.Deleted = db.Deleted;
}
}
}