""" 租戶網域 Model 支援單一租戶使用多個網域 (多品牌/國際化) """ from datetime import datetime from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, UniqueConstraint, Index from sqlalchemy.orm import relationship import enum from app.db.base import Base class DomainStatus(str, enum.Enum): """網域狀態""" PENDING = "pending" # 待驗證 ACTIVE = "active" # 啟用中 DISABLED = "disabled" # 已停用 class TenantDomain(Base): """租戶網域表 (一個租戶可以有多個網域)""" __tablename__ = "tenant_domains" __table_args__ = ( # 每個租戶只能有一個主要網域 Index("idx_tenant_primary_domain", "tenant_id", unique=True, postgresql_where=Column("is_primary") == True), # 一般索引 Index("idx_tenant_domains_tenant", "tenant_id"), Index("idx_tenant_domains_status", "status"), ) id = Column(Integer, primary_key=True, index=True) tenant_id = Column(Integer, ForeignKey("organizes.id", ondelete="CASCADE"), nullable=False, index=True) domain = Column(String(100), unique=True, nullable=False, index=True, comment="網域名稱 (abc.com.tw)") # 網域屬性 is_primary = Column(Boolean, default=False, nullable=False, comment="是否為主要網域") status = Column(String(20), default=DomainStatus.PENDING, nullable=False, comment="狀態") verified = Column(Boolean, default=False, nullable=False, comment="DNS 驗證狀態") # DNS 驗證 verification_token = Column(String(100), nullable=True, comment="驗證 Token") verified_at = Column(DateTime, nullable=True, comment="驗證時間") # 服務啟用狀態 enable_email = Column(Boolean, default=True, nullable=False, comment="啟用郵件服務") enable_webmail = Column(Boolean, default=True, nullable=False, comment="啟用 WebMail") enable_drive = Column(Boolean, default=True, nullable=False, comment="啟用雲端硬碟") # 時間記錄 created_at = Column(DateTime, default=datetime.utcnow, nullable=False) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False) # 關聯 tenant = relationship("Tenant", back_populates="domains") email_aliases = relationship( "UserEmailAlias", back_populates="domain", cascade="all, delete-orphan", lazy="dynamic" ) def __repr__(self): return f"" @property def is_verified(self) -> bool: """是否已驗證""" return self.verified and self.status == DomainStatus.ACTIVE def generate_dns_records(self) -> list: """生成 DNS 驗證記錄指引""" records = [] # TXT 記錄 - 網域所有權驗證 records.append({ "type": "TXT", "name": "@", "value": f"porsche-cloud-verify={self.verification_token}", "purpose": "網域所有權驗證" }) if self.enable_email: # MX 記錄 - 郵件伺服器 records.append({ "type": "MX", "name": "@", "value": "mail.porschecloud.tw", "priority": 10, "purpose": "郵件伺服器" }) # SPF 記錄 - 防止郵件偽造 records.append({ "type": "TXT", "name": "@", "value": "v=spf1 include:porschecloud.tw ~all", "purpose": "郵件 SPF 記錄" }) if self.enable_webmail: # CNAME - WebMail records.append({ "type": "CNAME", "name": "mail", "value": f"tenant-{self.tenant.tenant_code}.porschecloud.tw", "purpose": "WebMail 訪問" }) if self.enable_drive: # CNAME - 雲端硬碟 records.append({ "type": "CNAME", "name": "drive", "value": f"tenant-{self.tenant.tenant_code}.porschecloud.tw", "purpose": "雲端硬碟訪問" }) return records