from __future__ import annotations from datetime import datetime from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship class Base(DeclarativeBase): pass class TimestampMixin: created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False) updated_at: Mapped[datetime] = mapped_column( DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False, ) class AppSettingORM(Base, TimestampMixin): __tablename__ = "app_settings" id: Mapped[int] = mapped_column(Integer, primary_key=True) key: Mapped[str] = mapped_column(String(100), unique=True, nullable=False) value: Mapped[str | None] = mapped_column(Text, nullable=True) class SourceORM(Base, TimestampMixin): __tablename__ = "sources" id: Mapped[int] = mapped_column(Integer, primary_key=True) key: Mapped[str] = mapped_column(String(100), unique=True, nullable=False) enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) kind: Mapped[str] = mapped_column(String(50), nullable=False) url: Mapped[str] = mapped_column(Text, nullable=False) display_name: Mapped[str | None] = mapped_column(String(255), nullable=True) headers_json: Mapped[str] = mapped_column(Text, default="{}", nullable=False) include_regex: Mapped[str | None] = mapped_column(Text, nullable=True) exclude_regex: Mapped[str | None] = mapped_column(Text, nullable=True) prefix: Mapped[str] = mapped_column(String(255), default="", nullable=False) suffix: Mapped[str] = mapped_column(String(255), default="", nullable=False) cache_ttl_seconds: Mapped[int | None] = mapped_column(Integer, nullable=True) class ProfileORM(Base, TimestampMixin): __tablename__ = "profiles" id: Mapped[int] = mapped_column(Integer, primary_key=True) key: Mapped[str] = mapped_column(String(100), unique=True, nullable=False) title: Mapped[str] = mapped_column(String(255), nullable=False) provider_interval: Mapped[int] = mapped_column(Integer, default=21600, nullable=False) rule_interval: Mapped[int] = mapped_column(Integer, default=86400, nullable=False) test_url: Mapped[str] = mapped_column(Text, nullable=False) test_interval: Mapped[int] = mapped_column(Integer, default=300, nullable=False) main_policy: Mapped[str] = mapped_column(String(255), nullable=False) source_policy: Mapped[str] = mapped_column(String(255), nullable=False) mixed_auto_policy: Mapped[str] = mapped_column(String(255), nullable=False) manual_policy: Mapped[str] = mapped_column(String(255), nullable=False) direct_policy: Mapped[str] = mapped_column(String(255), nullable=False) mode: Mapped[str] = mapped_column(String(50), default="rule", nullable=False) allow_lan: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) ipv6: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) mixed_port: Mapped[int | None] = mapped_column(Integer, nullable=True) socks_port: Mapped[int | None] = mapped_column(Integer, nullable=True) log_level: Mapped[str | None] = mapped_column(String(50), nullable=True) source_links: Mapped[list["ProfileSourceORM"]] = relationship( back_populates="profile", cascade="all, delete-orphan", ) rule_links: Mapped[list["ProfileRuleModuleORM"]] = relationship( back_populates="profile", cascade="all, delete-orphan", ) groups: Mapped[list["PolicyGroupORM"]] = relationship( back_populates="profile", cascade="all, delete-orphan", ) class ProfileSourceORM(Base): __tablename__ = "profile_sources" __table_args__ = (UniqueConstraint("profile_id", "source_id", name="uq_profile_source"),) id: Mapped[int] = mapped_column(Integer, primary_key=True) profile_id: Mapped[int] = mapped_column(ForeignKey("profiles.id"), nullable=False) source_id: Mapped[int] = mapped_column(ForeignKey("sources.id"), nullable=False) order_index: Mapped[int] = mapped_column(Integer, default=0, nullable=False) enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) profile: Mapped[ProfileORM] = relationship(back_populates="source_links") source: Mapped[SourceORM] = relationship() class RuleModuleORM(Base, TimestampMixin): __tablename__ = "rule_modules" id: Mapped[int] = mapped_column(Integer, primary_key=True) key: Mapped[str] = mapped_column(String(100), unique=True, nullable=False) file_path: Mapped[str | None] = mapped_column(Text, nullable=True) behavior: Mapped[str] = mapped_column(String(50), nullable=False) format: Mapped[str] = mapped_column(String(50), nullable=False) policy: Mapped[str] = mapped_column(String(255), nullable=False) no_resolve: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) payload_json: Mapped[str] = mapped_column(Text, default="[]", nullable=False) profile_links: Mapped[list["ProfileRuleModuleORM"]] = relationship( back_populates="rule_module", cascade="all, delete-orphan", ) class ProfileRuleModuleORM(Base): __tablename__ = "profile_rule_modules" __table_args__ = (UniqueConstraint("profile_id", "rule_module_id", name="uq_profile_rule_module"),) id: Mapped[int] = mapped_column(Integer, primary_key=True) profile_id: Mapped[int] = mapped_column(ForeignKey("profiles.id"), nullable=False) rule_module_id: Mapped[int] = mapped_column(ForeignKey("rule_modules.id"), nullable=False) enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) order_index: Mapped[int] = mapped_column(Integer, default=0, nullable=False) policy_override: Mapped[str | None] = mapped_column(String(255), nullable=True) payload_override_json: Mapped[str | None] = mapped_column(Text, nullable=True) no_resolve_override: Mapped[bool | None] = mapped_column(Boolean, nullable=True) profile: Mapped[ProfileORM] = relationship(back_populates="rule_links") rule_module: Mapped[RuleModuleORM] = relationship(back_populates="profile_links") class PolicyGroupORM(Base, TimestampMixin): __tablename__ = "policy_groups" id: Mapped[int] = mapped_column(Integer, primary_key=True) profile_id: Mapped[int] = mapped_column(ForeignKey("profiles.id"), nullable=False) group_kind: Mapped[str] = mapped_column(String(50), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) type: Mapped[str] = mapped_column(String(50), nullable=False) order_index: Mapped[int] = mapped_column(Integer, default=0, nullable=False) proxies_json: Mapped[str | None] = mapped_column(Text, nullable=True) filter_regex: Mapped[str | None] = mapped_column(Text, nullable=True) tolerance: Mapped[int | None] = mapped_column(Integer, nullable=True) url: Mapped[str | None] = mapped_column(Text, nullable=True) interval: Mapped[int | None] = mapped_column(Integer, nullable=True) enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) profile: Mapped[ProfileORM] = relationship(back_populates="groups")