330 lines
13 KiB
C#
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;
|
|
}
|
|
}
|
|
} |