from datetime import datetime, timezone from typing import Optional from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, String, Text, text from sqlalchemy.orm import Mapped, mapped_column from app.database import Base class Website(Base): __tablename__ = "websites" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) name: Mapped[str] = mapped_column(String(255), nullable=False) site_type: Mapped[str] = mapped_column(String(32), default="sub2api") base_url: Mapped[str] = mapped_column(String(512), nullable=False) api_prefix: Mapped[str] = mapped_column(String(128), default="/api/v1/admin") auth_type: Mapped[str] = mapped_column(String(32), default="api_key") auth_config_json: Mapped[str] = mapped_column(Text, default="{}") groups_endpoint: Mapped[str] = mapped_column(String(256), default="/groups") group_update_endpoint: Mapped[str] = mapped_column(String(256), default="/groups/{id}") enabled: Mapped[bool] = mapped_column(Boolean, default=True) auto_sync_enabled: Mapped[bool] = mapped_column(Boolean, default=True) timeout_seconds: Mapped[int] = mapped_column(Integer, default=30) last_status: Mapped[str] = mapped_column(String(32), default="unknown") last_checked_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True) last_error: Mapped[Optional[str]] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc)) updated_at: Mapped[datetime] = mapped_column( DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc) ) class WebsiteGroupBinding(Base): __tablename__ = "website_group_bindings" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) website_id: Mapped[int] = mapped_column(Integer, ForeignKey("websites.id", ondelete="CASCADE"), index=True) target_group_id: Mapped[str] = mapped_column(String(255), nullable=False) target_group_name: Mapped[str] = mapped_column(String(255), default="") source_groups_json: Mapped[str] = mapped_column(Text, default="[]") percent: Mapped[str] = mapped_column(String(32), default="0") algorithm: Mapped[str] = mapped_column(String(64), default="max_plus_percent") enabled: Mapped[bool] = mapped_column(Boolean, default=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc)) updated_at: Mapped[datetime] = mapped_column( DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc) ) class WebsiteSyncLog(Base): __tablename__ = "website_sync_logs" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) website_id: Mapped[int] = mapped_column(Integer, ForeignKey("websites.id", ondelete="CASCADE"), index=True) binding_id: Mapped[Optional[int]] = mapped_column( Integer, ForeignKey("website_group_bindings.id", ondelete="SET NULL"), nullable=True, index=True ) target_group_id: Mapped[str] = mapped_column(String(255), default="") target_group_name: Mapped[str] = mapped_column(String(255), default="") algorithm: Mapped[str] = mapped_column(String(64), default="") percent: Mapped[str] = mapped_column(String(32), default="0") source_rates_json: Mapped[str] = mapped_column(Text, default="[]") old_rate: Mapped[Optional[str]] = mapped_column(String(64), nullable=True) new_rate: Mapped[Optional[str]] = mapped_column(String(64), nullable=True) status: Mapped[str] = mapped_column(String(16), nullable=False) message: Mapped[str] = mapped_column(Text, default="") created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc), index=True) __table_args__ = ( Index("ix_sync_website_created", "website_id", text("created_at DESC")), Index("ix_sync_binding_created", "binding_id", text("created_at DESC")), )