Add Master Data
This commit is contained in:
@@ -65,47 +65,16 @@ public class UserRepository : IUserRepository
|
||||
|
||||
public async Task<User?> FindActiveByEmailAsync(string email, CancellationToken ct = default)
|
||||
{
|
||||
var r = _redis?.GetDatabase();
|
||||
var norm = email.Trim().ToLowerInvariant();
|
||||
|
||||
if (r is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cached = await r.StringGetAsync($"uidx:{norm}");
|
||||
if (cached.HasValue)
|
||||
{
|
||||
var hit = JsonSerializer.Deserialize<User>(cached!);
|
||||
if (hit?.IsActive == true) return hit;
|
||||
}
|
||||
}
|
||||
catch { /* ignore cache errors */ }
|
||||
}
|
||||
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
|
||||
var user = await (
|
||||
from u in db.Users.AsNoTracking()
|
||||
join i in db.UserIdentities.AsNoTracking() on u.Id equals i.UserId
|
||||
where u.IsActive
|
||||
&& i.Type == IdentityType.Email
|
||||
&& i.Identifier == norm
|
||||
select u
|
||||
).FirstOrDefaultAsync(ct);
|
||||
|
||||
if (user is not null && r is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var payload = JsonSerializer.Serialize(user);
|
||||
var ttl = TimeSpan.FromMinutes(5);
|
||||
await r.StringSetAsync($"uidx:{norm}", payload, ttl);
|
||||
await r.StringSetAsync($"user:{user.Id:N}", payload, ttl);
|
||||
}
|
||||
catch { /* ignore cache errors */ }
|
||||
}
|
||||
|
||||
return user;
|
||||
return await db.Users
|
||||
.AsNoTracking()
|
||||
.Include(u => u.UserRoles)
|
||||
.ThenInclude(ur => ur.Role)
|
||||
.Where(u => u.IsActive &&
|
||||
u.Identities.Any(i => i.Type == IdentityType.Email && i.Identifier == norm))
|
||||
.FirstOrDefaultAsync(ct);
|
||||
}
|
||||
|
||||
public async Task<bool> EmailExistsAsync(string email, CancellationToken ct = default)
|
||||
@@ -142,11 +111,61 @@ public class UserRepository : IUserRepository
|
||||
if (!string.IsNullOrWhiteSpace(email))
|
||||
await r.StringSetAsync(IdentityEmailKey(tid.ToString(), email!), payload, ttl);
|
||||
}
|
||||
catch { }
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AddIdentityAsync(Guid userId, IdentityType type, string identifier, bool isPrimary, CancellationToken ct = default)
|
||||
public async Task<string[]> GetRoleCodesByUserIdAsync(Guid userId, Guid tenantId, CancellationToken ct = default)
|
||||
{
|
||||
var r = _redis?.GetDatabase();
|
||||
var cacheKey = $"urole:{tenantId:N}:{userId:N}";
|
||||
|
||||
// ---- cache hit ----
|
||||
if (r is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cached = await r.StringGetAsync(cacheKey);
|
||||
if (cached.HasValue)
|
||||
{
|
||||
var arr = JsonSerializer.Deserialize<string[]>(cached!) ?? Array.Empty<string>();
|
||||
if (arr.Length > 0) return arr;
|
||||
}
|
||||
}
|
||||
catch { /* ignore cache errors */ }
|
||||
}
|
||||
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
|
||||
// NOTE:
|
||||
// - ไม่อ้างอิง r.TenantId เพื่อให้คอมไพล์ได้แม้ Role ยังไม่มีฟิลด์ TenantId
|
||||
// - ถ้าคุณเพิ่ม Role.TenantId แล้ว และต้องการกรองตาม tenant ให้เติม .Where(role => role.TenantId == tenantId)
|
||||
var roles = await db.UserRoles
|
||||
.AsNoTracking()
|
||||
.Where(ur => ur.UserId == userId)
|
||||
.Select(ur => ur.Role)
|
||||
.Where(role => role != null)
|
||||
.Select(role => role!.Code) // ใช้ Code เป็นค่าของ claim "role"
|
||||
.Distinct()
|
||||
.ToArrayAsync(ct);
|
||||
|
||||
// ---- cache miss → set ----
|
||||
if (r is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await r.StringSetAsync(cacheKey, JsonSerializer.Serialize(roles), TimeSpan.FromMinutes(5));
|
||||
}
|
||||
catch { /* ignore cache errors */ }
|
||||
}
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
public async Task AddIdentityAsync(Guid userId, IdentityType type, string identifier, bool isPrimary,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
@@ -162,18 +181,19 @@ public class UserRepository : IUserRepository
|
||||
|
||||
var entity = new UserIdentity
|
||||
{
|
||||
TenantId = tid,
|
||||
UserId = userId,
|
||||
Type = type,
|
||||
TenantId = tid,
|
||||
UserId = userId,
|
||||
Type = type,
|
||||
Identifier = norm,
|
||||
IsPrimary = isPrimary
|
||||
IsPrimary = isPrimary
|
||||
};
|
||||
|
||||
await db.UserIdentities.AddAsync(entity, ct);
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
public async Task VerifyIdentityAsync(Guid userId, IdentityType type, string identifier, DateTimeOffset verifiedAt, CancellationToken ct = default)
|
||||
public async Task VerifyIdentityAsync(Guid userId, IdentityType type, string identifier, DateTimeOffset verifiedAt,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
@@ -191,7 +211,8 @@ public class UserRepository : IUserRepository
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
public async Task<UserIdentity?> GetPrimaryIdentityAsync(Guid userId, IdentityType type, CancellationToken ct = default)
|
||||
public async Task<UserIdentity?> GetPrimaryIdentityAsync(Guid userId, IdentityType type,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
@@ -234,7 +255,8 @@ public class UserRepository : IUserRepository
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
public async Task<UserMfaFactor> AddTotpFactorAsync(Guid userId, string label, string secret, CancellationToken ct = default)
|
||||
public async Task<UserMfaFactor> AddTotpFactorAsync(Guid userId, string label, string secret,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
@@ -294,7 +316,8 @@ public class UserRepository : IUserRepository
|
||||
return session;
|
||||
}
|
||||
|
||||
public async Task<UserSession?> FindSessionByRefreshHashAsync(Guid tenantId, string refreshTokenHash, CancellationToken ct = default)
|
||||
public async Task<UserSession?> FindSessionByRefreshHashAsync(Guid tenantId, string refreshTokenHash,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
return await db.UserSessions
|
||||
@@ -302,7 +325,8 @@ public class UserRepository : IUserRepository
|
||||
.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.RefreshTokenHash == refreshTokenHash, ct);
|
||||
}
|
||||
|
||||
public async Task<bool> RotateSessionRefreshAsync(Guid tenantId, Guid sessionId, string newRefreshTokenHash, DateTimeOffset newIssuedAt, DateTimeOffset? newExpiresAt, CancellationToken ct = default)
|
||||
public async Task<bool> RotateSessionRefreshAsync(Guid tenantId, Guid sessionId, string newRefreshTokenHash,
|
||||
DateTimeOffset newIssuedAt, DateTimeOffset? newExpiresAt, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var s = await db.UserSessions.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Id == sessionId, ct);
|
||||
@@ -319,7 +343,8 @@ public class UserRepository : IUserRepository
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
|
||||
var s = await db.UserSessions.FirstOrDefaultAsync(x => x.TenantId == tid && x.Id == sessionId && x.UserId == userId, ct);
|
||||
var s = await db.UserSessions.FirstOrDefaultAsync(
|
||||
x => x.TenantId == tid && x.Id == sessionId && x.UserId == userId, ct);
|
||||
if (s is null) return 0;
|
||||
|
||||
s.RevokedAt = DateTimeOffset.UtcNow;
|
||||
|
||||
Reference in New Issue
Block a user