Files
orion/app/modules/tenancy/models/merchant_domain.py
Samir Boulahtit 1b8a40f1ff
All checks were successful
CI / dependency-scanning (push) Successful in 27s
CI / docs (push) Successful in 35s
CI / ruff (push) Successful in 8s
CI / pytest (push) Successful in 34m22s
CI / validate (push) Successful in 19s
CI / deploy (push) Successful in 2m25s
feat(validators): add noqa suppression support to security and performance validators
- Add centralized _is_noqa_suppressed() to BaseValidator with normalization
  (accepts both SEC001 and SEC-001 formats for ruff compatibility)
- Wire noqa support into all 21 security and 18 performance check functions
- Add ruff external config for SEC/PERF/MOD/EXC codes in pyproject.toml
- Convert all 280 Python noqa comments to dashless format (ruff-compatible)
- Add site/ to IGNORE_PATTERNS (excludes mkdocs build output)
- Suppress 152 false positive findings (test passwords, seed data, validator
  self-references, Apple Wallet SHA1, etc.)
- Security: 79 errors → 0, 60 warnings → 0
- Performance: 80 warnings → 77 (3 test script suppressions)
- Add proposal doc with noqa inventory and remaining findings recommendations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 22:56:56 +01:00

118 lines
3.7 KiB
Python

# app/modules/tenancy/models/merchant_domain.py
"""
Merchant Domain Model - Maps custom domains to merchants for merchant-level domain routing.
When a merchant subscribes to a platform (e.g., loyalty), they can register a domain
(e.g., myloyaltyprogram.lu) that serves as the default for all their stores.
Individual stores can optionally override this with their own custom StoreDomain.
Domain Resolution Priority:
1. Store-specific custom domain (StoreDomain) -> highest priority
2. Merchant domain (MerchantDomain) -> inherited default
3. Store subdomain ({store.subdomain}.loyalty.lu) -> fallback
"""
from sqlalchemy import (
Boolean,
Column,
DateTime,
ForeignKey,
Index,
Integer,
String,
UniqueConstraint,
)
from sqlalchemy.orm import relationship
from app.core.database import Base
from models.database.base import TimestampMixin
class MerchantDomain(Base, TimestampMixin):
"""
Maps custom domains to merchants for merchant-level domain routing.
Examples:
- myloyaltyprogram.lu -> Merchant "WizaCorp" (all stores inherit)
- Store ORION overrides with StoreDomain -> mysuperloyaltyprogram.lu
- Store WIZAGADGETS -> inherits myloyaltyprogram.lu
"""
__tablename__ = "merchant_domains"
id = Column(Integer, primary_key=True, index=True)
merchant_id = Column(
Integer, ForeignKey("merchants.id", ondelete="CASCADE"), nullable=False
)
# Domain configuration
domain = Column(String(255), nullable=False, unique=True, index=True)
is_primary = Column(Boolean, default=True, nullable=False)
is_active = Column(Boolean, default=True, nullable=False)
# SSL/TLS status (for monitoring)
ssl_status = Column(
String(50), default="pending"
) # pending, active, expired, error
ssl_verified_at = Column(DateTime(timezone=True), nullable=True)
# DNS verification (to confirm domain ownership)
verification_token = Column(String(100), unique=True, nullable=True)
is_verified = Column(Boolean, default=False, nullable=False)
verified_at = Column(DateTime(timezone=True), nullable=True)
# Platform association (for platform context resolution from custom domains)
platform_id = Column(
Integer,
ForeignKey("platforms.id", ondelete="SET NULL"),
nullable=True,
index=True,
comment="Platform this domain is associated with (for platform context resolution)",
)
# Relationships
merchant = relationship("Merchant", back_populates="merchant_domains")
platform = relationship("Platform")
# Constraints
__table_args__ = (
UniqueConstraint("merchant_id", "platform_id", name="uq_merchant_domain_platform"),
Index("idx_merchant_domain_active", "domain", "is_active"),
Index("idx_merchant_domain_primary", "merchant_id", "is_primary"),
Index("idx_merchant_domain_platform", "platform_id"),
)
def __repr__(self):
return f"<MerchantDomain(domain='{self.domain}', merchant_id={self.merchant_id})>"
@property
def full_url(self):
"""Return full URL with https"""
return f"https://{self.domain}"
@classmethod
def normalize_domain(cls, domain: str) -> str:
"""
Normalize domain for consistent storage.
Reuses the same logic as StoreDomain.normalize_domain().
Examples:
- https://example.com -> example.com
- www.example.com -> example.com
- EXAMPLE.COM -> example.com
"""
# Remove protocol
domain = domain.replace("https://", "").replace("http://", "") # noqa: SEC034
# Remove trailing slash
domain = domain.rstrip("/")
# Convert to lowercase
domain = domain.lower()
return domain
__all__ = ["MerchantDomain"]