68 lines
2.4 KiB
Python
68 lines
2.4 KiB
Python
"""
|
|
app/tenancy/resolver.py
|
|
-----------------------
|
|
Resolve an incoming Host header to a tenant by querying the control plane.
|
|
|
|
The `control` package is imported lazily inside the function so that the data
|
|
plane carries no import-time dependency on the control plane when
|
|
multi-tenancy is disabled.
|
|
|
|
Resolution rules:
|
|
* exact match on tenant_domains.domain (host, lowercased, port stripped)
|
|
* tenant must be status='active'
|
|
* custom domains must be verified; subdomains we issue are trusted
|
|
|
|
MT-5: plan fields are loaded in the same session and stored on TenantContext
|
|
so quota.py / gates.py can read them without a second control-DB query.
|
|
|
|
Returns a detached TenantContext or None.
|
|
"""
|
|
|
|
from app.tenancy.context import TenantContext
|
|
|
|
|
|
def resolve_tenant(host):
|
|
if not host:
|
|
return None
|
|
|
|
# Lazy import — keeps control plane optional when MT is disabled.
|
|
from control.base import control_session
|
|
from control.models import TenantDomain
|
|
|
|
with control_session() as s:
|
|
domain = (
|
|
s.query(TenantDomain)
|
|
.filter(TenantDomain.domain == host)
|
|
.first()
|
|
)
|
|
if domain is None:
|
|
return None
|
|
if domain.kind == 'custom' and not domain.verified:
|
|
return None
|
|
|
|
tenant = domain.tenant
|
|
if tenant is None or tenant.status != 'active':
|
|
return None
|
|
|
|
plan = tenant.plan # relationship already loaded via joined session
|
|
|
|
# Materialise everything needed while the session is still open
|
|
# (db_uri decrypts the stored credential).
|
|
return TenantContext(
|
|
id=tenant.id,
|
|
slug=tenant.slug,
|
|
name=tenant.name,
|
|
plan_id=tenant.plan_id,
|
|
db_uri=tenant.db_uri,
|
|
# MT-5: plan fields
|
|
plan_code=plan.code if plan else None,
|
|
max_users=plan.max_users if plan else None,
|
|
max_facilities=plan.max_facilities if plan else None,
|
|
max_inspections_month=plan.max_inspections_month if plan else None,
|
|
max_issues_month=plan.max_issues_month if plan else None,
|
|
allow_mobile_api=plan.allow_mobile_api if plan else True,
|
|
allow_scheduled_reports=plan.allow_scheduled_reports if plan else True,
|
|
allow_branding=plan.allow_branding if plan else True,
|
|
allow_custom_domain=plan.allow_custom_domain if plan else True,
|
|
)
|