101 lines
3.7 KiB
C#
101 lines
3.7 KiB
C#
using Microsoft.Extensions.DependencyInjection;
|
|
using Npgsql;
|
|
using Tiku.Application.Tenancy;
|
|
using Tiku.Domain.Tenancy;
|
|
using Tiku.Infrastructure.Caching;
|
|
using ZiggyCreatures.Caching.Fusion;
|
|
|
|
namespace Tiku.Infrastructure.Tenancy;
|
|
|
|
internal sealed record TenantDirectoryCacheEntry(bool Found, TenantDirectoryEntry? Entry);
|
|
|
|
public sealed class TenantDirectory(
|
|
NpgsqlDataSource dataSource,
|
|
[FromKeyedServices(BusinessCachingServiceCollectionExtensions.CacheName)]
|
|
IFusionCache cache) : ITenantDirectory
|
|
{
|
|
public Task<TenantDirectoryEntry?> FindByHostAsync(
|
|
string host,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
const string sql = """
|
|
select t.id, t.slug, t.name, t.status, t.mode, d.host
|
|
from tenant_domains d
|
|
join tenants t on t.id = d.tenant_id
|
|
where d.host = @lookup
|
|
and d.status = 'active'
|
|
and t.status = 'active'
|
|
limit 1
|
|
""";
|
|
return FindAsync(sql, "host", Normalize(host), cancellationToken);
|
|
}
|
|
|
|
public Task<TenantDirectoryEntry?> FindByCodeAsync(
|
|
string tenantCode,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
const string sql = """
|
|
select t.id, t.slug, t.name, t.status, t.mode, null::text as host
|
|
from tenants t
|
|
where t.slug = @lookup
|
|
and t.status = 'active'
|
|
limit 1
|
|
""";
|
|
return FindAsync(sql, "code", Normalize(tenantCode), cancellationToken);
|
|
}
|
|
|
|
private async Task<TenantDirectoryEntry?> FindAsync(
|
|
string sql,
|
|
string kind,
|
|
string lookup,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var cacheKey = CacheKey(kind, lookup);
|
|
var cached = await cache.GetOrSetAsync<TenantDirectoryCacheEntry>(
|
|
cacheKey,
|
|
async (context, token) =>
|
|
{
|
|
await using var command = dataSource.CreateCommand(sql);
|
|
command.Parameters.AddWithValue("lookup", lookup);
|
|
await using var reader = await command.ExecuteReaderAsync(token);
|
|
if (!await reader.ReadAsync(token))
|
|
{
|
|
context.Options.Duration = TimeSpan.FromSeconds(20);
|
|
context.Options.MemoryCacheDuration = TimeSpan.FromSeconds(20);
|
|
return new TenantDirectoryCacheEntry(false, null);
|
|
}
|
|
|
|
return new TenantDirectoryCacheEntry(true, new TenantDirectoryEntry(
|
|
reader.GetGuid(0),
|
|
reader.GetString(1),
|
|
reader.GetString(2),
|
|
ParseEnum<TenantStatus>(reader.GetString(3)),
|
|
ParseEnum<TenantMode>(reader.GetString(4)),
|
|
reader.IsDBNull(5) ? null : reader.GetString(5)));
|
|
},
|
|
options =>
|
|
{
|
|
options.Duration = TimeSpan.FromSeconds(300);
|
|
options.MemoryCacheDuration = TimeSpan.FromSeconds(30);
|
|
},
|
|
token: cancellationToken);
|
|
return cached.Entry;
|
|
}
|
|
|
|
private static TEnum ParseEnum<TEnum>(string value)
|
|
where TEnum : struct, Enum
|
|
{
|
|
return Enum.Parse<TEnum>(value.Replace("_", string.Empty, StringComparison.Ordinal), true);
|
|
}
|
|
|
|
private static string Normalize(string value)
|
|
{
|
|
return value.Trim().ToLowerInvariant();
|
|
}
|
|
|
|
internal static string CacheKey(string kind, string lookup)
|
|
{
|
|
return $"tenant-directory:v1:{kind}:{Normalize(lookup)}";
|
|
}
|
|
}
|