# app/modules/dev_tools/services/domain_health_service.py """ Domain Health Check Service Simulates the middleware resolution pipeline for every active access method (custom subdomain, default subdomain, custom domain, path-based) to verify they resolve to the expected store. """ import logging from sqlalchemy.orm import Session logger = logging.getLogger(__name__) def run_domain_health_check(db: Session) -> dict: """ Check all active store access methods by simulating middleware resolution: 1. StorePlatform custom subdomains (acme-rewards.rewardflow.lu) 2. Default subdomains per platform (acme.omsflow.lu) 3. StoreDomain custom domains (wizatech.shop) 4. Path-based store routes (/storefront/{subdomain}/, /store/{subdomain}/) Returns: dict with keys: total, passed, failed, details (list of entry dicts) """ from app.modules.tenancy.models import Platform, Store, StoreDomain from app.modules.tenancy.models.store_platform import StorePlatform from middleware.store_context import StoreContextManager details: list[dict] = [] # Pre-fetch all active stores, platforms, and memberships active_stores = ( db.query(Store).filter(Store.is_active.is_(True)).all() # noqa: SVC-005 ) store_by_id: dict[int, Store] = {s.id: s for s in active_stores} all_platforms = ( db.query(Platform).filter(Platform.is_active.is_(True)).all() ) platform_by_id: dict[int, Platform] = {p.id: p for p in all_platforms} all_store_platforms = ( db.query(StorePlatform) .filter(StorePlatform.is_active.is_(True)) .all() ) # Build store_id → list of (StorePlatform, Platform) for reuse store_memberships: dict[int, list[tuple]] = {} for sp in all_store_platforms: platform = platform_by_id.get(sp.platform_id) if platform: store_memberships.setdefault(sp.store_id, []).append((sp, platform)) # ── 1. Custom subdomains (StorePlatform.custom_subdomain) ── # e.g. acme-rewards.rewardflow.lu for sp in all_store_platforms: if not sp.custom_subdomain: continue expected_store = store_by_id.get(sp.store_id) platform = platform_by_id.get(sp.platform_id) if not expected_store or not platform: continue context = { "detection_method": "subdomain", "subdomain": sp.custom_subdomain, "_platform": platform, } resolved = StoreContextManager.get_store_from_context(db, context) passed = resolved is not None and resolved.id == expected_store.id details.append(_entry( domain=f"{sp.custom_subdomain}.{platform.domain or platform.code}", entry_type="custom subdomain", platform_code=platform.code, expected_store=expected_store, resolved_store=resolved, passed=passed, note="" if passed else "custom_subdomain lookup failed", )) # ── 2. Default subdomains per platform ── # e.g. acme.omsflow.lu — uses Store.subdomain on the platform domain. # With fail-closed routing, this only works if StorePlatform.custom_subdomain # matches Store.subdomain, so this check surfaces missing entries. for store in active_stores: if not store.subdomain: continue memberships = store_memberships.get(store.id, []) for sp, platform in memberships: if not platform.domain: continue # If custom_subdomain is already set to this value, it's covered # in check #1 above — but we still check default subdomain access # to confirm it resolves (it exercises the same code path). # Skip if custom_subdomain == subdomain to avoid duplicate entries. if sp.custom_subdomain and sp.custom_subdomain.lower() == store.subdomain.lower(): continue context = { "detection_method": "subdomain", "subdomain": store.subdomain, "_platform": platform, } resolved = StoreContextManager.get_store_from_context(db, context) passed = resolved is not None and resolved.id == store.id note = "" if not passed: note = ( f"Store '{store.subdomain}' not found or has no active " f"membership on platform {platform.code}" ) details.append(_entry( domain=f"{store.subdomain}.{platform.domain}", entry_type="default subdomain", platform_code=platform.code, expected_store=store, resolved_store=resolved, passed=passed, note=note, )) # ── 3. Custom domains (StoreDomain) ── # e.g. wizatech.shop store_domains = ( db.query(StoreDomain) .filter( StoreDomain.is_active.is_(True), StoreDomain.is_verified.is_(True), ) .all() ) for sd in store_domains: expected_store = sd.store if not expected_store: continue platform = sd.platform platform_code = platform.code if platform else None context = { "detection_method": "custom_domain", "domain": sd.domain, } resolved = StoreContextManager.get_store_from_context(db, context) passed = resolved is not None and resolved.id == expected_store.id details.append(_entry( domain=sd.domain, entry_type="custom domain", platform_code=platform_code, expected_store=expected_store, resolved_store=resolved, passed=passed, note="" if passed else "custom domain resolution failed", )) # ── 4. Path-based routes ── # e.g. /storefront/luxweb/, /store/luxweb/ # Path-based URLs use Store.subdomain as the path segment. for store in active_stores: if not store.subdomain: continue memberships = store_memberships.get(store.id, []) platform_codes = sorted(p.code for _, p in memberships) if memberships else [] platform_label = ", ".join(platform_codes) if platform_codes else None for prefix, label in [ ("/storefront/", "storefront"), ("/store/", "store"), ]: context = { "detection_method": "path", "subdomain": store.subdomain, "path_prefix": f"{prefix}{store.subdomain}", "full_prefix": prefix, } resolved = StoreContextManager.get_store_from_context(db, context) passed = resolved is not None and resolved.id == store.id details.append(_entry( domain=f"{prefix}{store.subdomain}/", entry_type=f"path ({label})", platform_code=platform_label, expected_store=store, resolved_store=resolved, passed=passed, note="" if passed else f"path-based {label} resolution failed", )) total = len(details) passed_count = sum(1 for d in details if d["status"] == "pass") return { "total": total, "passed": passed_count, "failed": total - passed_count, "details": details, } def _entry( domain: str, entry_type: str, platform_code: str | None, expected_store, resolved_store, passed: bool, note: str, ) -> dict: """Build a single health-check result entry.""" return { "domain": domain, "type": entry_type, "platform_code": platform_code, "expected_store": ( getattr(expected_store, "store_code", expected_store.subdomain) if expected_store else None ), "resolved_store": ( getattr(resolved_store, "store_code", resolved_store.subdomain) if resolved_store else None ), "status": "pass" if passed else "fail", "note": note, }