using System; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using timetracker.Shared; namespace timetracker.Data; public class TenantProvider : ITenantProvider { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IDbContextFactory _dbContextFactory; private readonly IConfiguration _configuration; public TenantProvider( IHttpContextAccessor httpContextAccessor, IDbContextFactory dbContextFactory, IConfiguration configuration) { _httpContextAccessor = httpContextAccessor; _dbContextFactory = dbContextFactory; _configuration = configuration; } public int? TenantId { get { var httpContext = _httpContextAccessor.HttpContext; if (httpContext == null) return null; if (httpContext.Items.TryGetValue("TenantId", out var cachedId)) { return (int?)cachedId; } // Resolve and cache in HttpContext.Items ResolveTenantAsync().GetAwaiter().GetResult(); if (httpContext.Items.TryGetValue("TenantId", out cachedId)) { return (int?)cachedId; } return null; } } public async Task GetCurrentTenantAsync() { var httpContext = _httpContextAccessor.HttpContext; if (httpContext == null) return null; if (httpContext.Items.TryGetValue("CurrentTenant", out var cachedTenant)) { return (Tenant?)cachedTenant; } await ResolveTenantAsync(); if (httpContext.Items.TryGetValue("CurrentTenant", out cachedTenant)) { return (Tenant?)cachedTenant; } return null; } private async Task ResolveTenantAsync() { var httpContext = _httpContextAccessor.HttpContext; if (httpContext == null) return; // Prevent double resolution in the same request if (httpContext.Items.ContainsKey("TenantResolved")) return; httpContext.Items["TenantResolved"] = true; var host = httpContext.Request.Host.Host; var subdomain = GetSubdomain(host); if (!string.IsNullOrEmpty(subdomain)) { await using var db = await _dbContextFactory.CreateDbContextAsync(); var tenant = await db.Tenants .FirstOrDefaultAsync(t => t.Subdomain.ToLower() == subdomain.ToLower()); if (tenant != null) { httpContext.Items["CurrentTenant"] = tenant; httpContext.Items["TenantId"] = tenant.Id; } } } private string? GetSubdomain(string host) { var baseDomain = _configuration["TenantSettings:BaseDomain"] ?? "localhost"; // Ignore port if included in host var hostName = host.Split(':')[0]; if (hostName.Equals(baseDomain, StringComparison.OrdinalIgnoreCase)) { return null; // Main domain } if (hostName.EndsWith("." + baseDomain, StringComparison.OrdinalIgnoreCase)) { return hostName.Substring(0, hostName.Length - baseDomain.Length - 1); } // Fallback for localhost testing if (baseDomain.Equals("localhost", StringComparison.OrdinalIgnoreCase)) { var parts = hostName.Split('.'); if (parts.Length > 1 && !hostName.Equals("localhost", StringComparison.OrdinalIgnoreCase)) { return parts[0]; } } return null; } }