240 lines
8.6 KiB
C#
240 lines
8.6 KiB
C#
using AMREZ.EOP.Abstractions.Applications.Tenancy;
|
|
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
|
|
using AMREZ.EOP.Abstractions.Storage;
|
|
using AMREZ.EOP.Domain.Entities.Tenancy;
|
|
using AMREZ.EOP.Infrastructures.Data;
|
|
using AMREZ.EOP.Infrastructures.Options;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace AMREZ.EOP.Infrastructures.Repositories;
|
|
|
|
public sealed class TenantRepository : ITenantRepository
|
|
{
|
|
private readonly IDbScope _scope;
|
|
private readonly ITenantResolver _resolver;
|
|
private readonly IHttpContextAccessor _http;
|
|
|
|
public TenantRepository(IDbScope scope, ITenantResolver resolver, IHttpContextAccessor http)
|
|
{
|
|
_scope = scope;
|
|
_resolver = resolver;
|
|
_http = http;
|
|
}
|
|
|
|
private AppDbContext Db()
|
|
{
|
|
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
|
|
var platform = _resolver.Resolve(http, "@platform") ??
|
|
throw new InvalidOperationException("No platform tenant");
|
|
_scope.EnsureForTenant(platform);
|
|
return _scope.Get<AppDbContext>();
|
|
}
|
|
|
|
private static string Norm(string s) => (s ?? string.Empty).Trim().ToLowerInvariant();
|
|
|
|
public Task<bool> TenantExistsAsync(string tenantKey, CancellationToken ct = default)
|
|
{
|
|
var key = Norm(tenantKey);
|
|
return Db().Set<TenantConfig>().AsNoTracking().AnyAsync(x => x.TenantKey == key, ct);
|
|
}
|
|
|
|
public Task<TenantConfig?> GetAsync(string tenantKey, CancellationToken ct = default)
|
|
{
|
|
var key = Norm(tenantKey);
|
|
return Db().Set<TenantConfig>().AsNoTracking().FirstOrDefaultAsync(x => x.TenantKey == key, ct);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<TenantConfig>> ListTenantsAsync(CancellationToken ct = default)
|
|
=> await Db().Set<TenantConfig>().AsNoTracking().OrderBy(x => x.TenantKey).ToListAsync(ct);
|
|
|
|
public async Task<bool> CreateAsync(TenantConfig row, CancellationToken ct = default)
|
|
{
|
|
row.TenantKey = Norm(row.TenantKey);
|
|
row.Schema = string.IsNullOrWhiteSpace(row.Schema) ? null : row.Schema!.Trim();
|
|
row.ConnectionString = string.IsNullOrWhiteSpace(row.ConnectionString) ? null : row.ConnectionString!.Trim();
|
|
row.UpdatedAtUtc = DateTimeOffset.UtcNow;
|
|
|
|
await Db().Set<TenantConfig>().AddAsync(row, ct);
|
|
return await Db().SaveChangesAsync(ct) > 0;
|
|
}
|
|
|
|
public async Task<bool> UpdateAsync(TenantConfig row, DateTimeOffset? ifUnmodifiedSince,
|
|
CancellationToken ct = default)
|
|
{
|
|
var key = Norm(row.TenantKey);
|
|
var db = Db();
|
|
var cur = await db.Set<TenantConfig>().FirstOrDefaultAsync(x => x.TenantKey == key, ct);
|
|
if (cur is null) return false;
|
|
|
|
if (ifUnmodifiedSince.HasValue && cur.UpdatedAtUtc > ifUnmodifiedSince.Value)
|
|
return false;
|
|
|
|
cur.Schema = row.Schema is null
|
|
? cur.Schema
|
|
: (string.IsNullOrWhiteSpace(row.Schema) ? null : row.Schema.Trim());
|
|
cur.ConnectionString = row.ConnectionString is null
|
|
? cur.ConnectionString
|
|
: (string.IsNullOrWhiteSpace(row.ConnectionString) ? null : row.ConnectionString.Trim());
|
|
cur.Mode = row.Mode;
|
|
cur.IsActive = row.IsActive;
|
|
cur.UpdatedAtUtc = DateTimeOffset.UtcNow;
|
|
|
|
return await db.SaveChangesAsync(ct) > 0;
|
|
}
|
|
|
|
public async Task<bool> DeleteAsync(string tenantKey, CancellationToken ct = default)
|
|
{
|
|
var key = Norm(tenantKey);
|
|
var db = Db();
|
|
|
|
var t = await db.Set<TenantConfig>().FirstOrDefaultAsync(x => x.TenantKey == key, ct);
|
|
if (t is null) return false;
|
|
|
|
if (t.IsActive)
|
|
{
|
|
t.IsActive = false;
|
|
t.UpdatedAtUtc = DateTimeOffset.UtcNow;
|
|
}
|
|
|
|
var domains = await db.Set<TenantDomain>()
|
|
.Where(d => d.TenantKey == key && d.IsActive)
|
|
.ToListAsync(ct);
|
|
|
|
foreach (var d in domains)
|
|
{
|
|
d.IsActive = false;
|
|
d.UpdatedAtUtc = DateTimeOffset.UtcNow;
|
|
}
|
|
|
|
return await db.SaveChangesAsync(ct) > 0;
|
|
}
|
|
|
|
public async Task<bool> MapDomainAsync(string domain, string tenantKey, CancellationToken ct = default)
|
|
{
|
|
var d = Norm(domain);
|
|
var key = Norm(tenantKey);
|
|
|
|
if (!await Db().Set<TenantConfig>().AnyAsync(x => x.TenantKey == key && x.IsActive, ct))
|
|
return false;
|
|
|
|
var db = Db();
|
|
var ex = await db.Set<TenantDomain>().FirstOrDefaultAsync(x => x.Domain == d, ct);
|
|
if (ex is null)
|
|
await db.Set<TenantDomain>()
|
|
.AddAsync(
|
|
new TenantDomain
|
|
{
|
|
Domain = d, TenantKey = key, IsPlatformBaseDomain = false, IsActive = true,
|
|
UpdatedAtUtc = DateTimeOffset.UtcNow
|
|
}, ct);
|
|
else
|
|
{
|
|
ex.TenantKey = key;
|
|
ex.IsPlatformBaseDomain = false;
|
|
ex.IsActive = true;
|
|
ex.UpdatedAtUtc = DateTimeOffset.UtcNow;
|
|
}
|
|
|
|
return await db.SaveChangesAsync(ct) > 0;
|
|
}
|
|
|
|
public async Task<bool> UnmapDomainAsync(string domain, CancellationToken ct = default)
|
|
{
|
|
var d = Norm(domain);
|
|
var db = Db();
|
|
var ex = await db.Set<TenantDomain>().FirstOrDefaultAsync(x => x.Domain == d, ct);
|
|
if (ex is null) return false;
|
|
|
|
ex.IsActive = false;
|
|
ex.TenantKey = null;
|
|
ex.IsPlatformBaseDomain = false;
|
|
ex.UpdatedAtUtc = DateTimeOffset.UtcNow;
|
|
|
|
return await db.SaveChangesAsync(ct) > 0;
|
|
}
|
|
|
|
public async Task<IReadOnlyList<TenantDomain>> ListDomainsAsync(string? tenantKey, CancellationToken ct = default)
|
|
{
|
|
var db = Db();
|
|
var q = db.Set<TenantDomain>().AsNoTracking();
|
|
if (!string.IsNullOrWhiteSpace(tenantKey))
|
|
{
|
|
var key = Norm(tenantKey!);
|
|
q = q.Where(x => x.TenantKey == key || x.IsPlatformBaseDomain);
|
|
}
|
|
|
|
return await q.OrderBy(x => x.Domain).ToListAsync(ct);
|
|
}
|
|
|
|
public async Task<bool> AddBaseDomainAsync(string baseDomain, CancellationToken ct = default)
|
|
{
|
|
var d = Norm(baseDomain);
|
|
var db = Db();
|
|
var ex = await db.Set<TenantDomain>().FirstOrDefaultAsync(x => x.Domain == d, ct);
|
|
if (ex is null)
|
|
await db.Set<TenantDomain>()
|
|
.AddAsync(
|
|
new TenantDomain
|
|
{
|
|
Domain = d, TenantKey = null, IsPlatformBaseDomain = true, IsActive = true,
|
|
UpdatedAtUtc = DateTimeOffset.UtcNow
|
|
}, ct);
|
|
else
|
|
{
|
|
ex.TenantKey = null;
|
|
ex.IsPlatformBaseDomain = true;
|
|
ex.IsActive = true;
|
|
ex.UpdatedAtUtc = DateTimeOffset.UtcNow;
|
|
}
|
|
|
|
return await db.SaveChangesAsync(ct) > 0;
|
|
}
|
|
|
|
public async Task<bool> RemoveBaseDomainAsync(string baseDomain, CancellationToken ct = default)
|
|
{
|
|
var d = Norm(baseDomain);
|
|
var db = Db();
|
|
var ex = await db.Set<TenantDomain>()
|
|
.FirstOrDefaultAsync(x => x.Domain == d && x.IsPlatformBaseDomain, ct);
|
|
if (ex is null) return false;
|
|
|
|
ex.IsActive = false;
|
|
ex.UpdatedAtUtc = DateTimeOffset.UtcNow;
|
|
|
|
return await db.SaveChangesAsync(ct) > 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// คืนค่า TenantKey จาก TenantId (Guid) ถ้าไม่เจอหรือไม่ active คืน null
|
|
/// </summary>
|
|
public async Task<string?> GetTenantKeyByTenantIdAsync(Guid tenantId, CancellationToken ct = default)
|
|
{
|
|
var key = await Db().Set<TenantConfig>()
|
|
.AsNoTracking()
|
|
.Where(x => x.TenantId == tenantId && x.IsActive)
|
|
.Select(x => x.TenantKey)
|
|
.FirstOrDefaultAsync(ct);
|
|
|
|
return string.IsNullOrWhiteSpace(key) ? null : key;
|
|
}
|
|
|
|
/// <summary>
|
|
/// คืนค่าแม็พ TenantId -> TenantKey เฉพาะที่เจอและ active
|
|
/// </summary>
|
|
public async Task<Dictionary<Guid, string>> MapTenantKeysAsync(IEnumerable<Guid> tenantIds, CancellationToken ct = default)
|
|
{
|
|
var ids = (tenantIds ?? Array.Empty<Guid>()).Distinct().ToArray();
|
|
if (ids.Length == 0) return new Dictionary<Guid, string>();
|
|
|
|
var rows = await Db().Set<TenantConfig>()
|
|
.AsNoTracking()
|
|
.Where(x => ids.Contains(x.TenantId) && x.IsActive)
|
|
.Select(x => new { x.TenantId, x.TenantKey })
|
|
.ToListAsync(ct);
|
|
|
|
return rows.ToDictionary(x => x.TenantId, x => x.TenantKey);
|
|
}
|
|
|
|
} |