from datetime import datetime from enum import Enum from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, func from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base class Protocol(str, Enum): WIREGUARD = "wireguard" AWG2 = "awg2" class UserRole(str, Enum): ADMIN = "admin" class AdminUser(Base): __tablename__ = "admin_users" id: Mapped[int] = mapped_column(Integer, primary_key=True) username: Mapped[str] = mapped_column(String(64), unique=True, index=True) email: Mapped[str] = mapped_column(String(255), unique=True) password_hash: Mapped[str] = mapped_column(String(255)) role: Mapped[str] = mapped_column(String(32), default=UserRole.ADMIN.value) is_active: Mapped[bool] = mapped_column(Boolean, default=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) class VpnServer(Base): __tablename__ = "vpn_servers" id: Mapped[int] = mapped_column(Integer, primary_key=True) name: Mapped[str] = mapped_column(String(120)) protocol: Mapped[str] = mapped_column(String(32), index=True) public_host: Mapped[str] = mapped_column(String(255)) public_port: Mapped[int] = mapped_column(Integer) interface_name: Mapped[str] = mapped_column(String(32)) subnet: Mapped[str] = mapped_column(String(64)) dns: Mapped[str] = mapped_column(String(255), default="1.1.1.1") server_private_key: Mapped[str] = mapped_column(Text) server_public_key: Mapped[str] = mapped_column(Text) # SSH access to the VPS ssh_host: Mapped[str | None] = mapped_column(String(255), nullable=True) ssh_port: Mapped[int] = mapped_column(Integer, default=22) ssh_username: Mapped[str | None] = mapped_column(String(128), nullable=True) ssh_password: Mapped[str | None] = mapped_column(Text, nullable=True) ssh_private_key: Mapped[str | None] = mapped_column(Text, nullable=True) ssh_server_info: Mapped[str | None] = mapped_column(Text, nullable=True) ssh_reachable: Mapped[bool] = mapped_column(Boolean, default=False) ssh_last_checked: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) # AmneziaWG 2.0 obfuscation params (ignored for plain WireGuard) jc: Mapped[int | None] = mapped_column(Integer, nullable=True) jmin: Mapped[int | None] = mapped_column(Integer, nullable=True) jmax: Mapped[int | None] = mapped_column(Integer, nullable=True) s1: Mapped[int | None] = mapped_column(Integer, nullable=True) s2: Mapped[int | None] = mapped_column(Integer, nullable=True) s3: Mapped[int | None] = mapped_column(Integer, nullable=True) s4: Mapped[int | None] = mapped_column(Integer, nullable=True) h1: Mapped[int | None] = mapped_column(Integer, nullable=True) h2: Mapped[int | None] = mapped_column(Integer, nullable=True) h3: Mapped[int | None] = mapped_column(Integer, nullable=True) h4: Mapped[int | None] = mapped_column(Integer, nullable=True) mtu: Mapped[int] = mapped_column(Integer, default=1420) vpn_installed: Mapped[bool] = mapped_column(Boolean, default=False) is_enabled: Mapped[bool] = mapped_column(Boolean, default=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) clients: Mapped[list["VpnClient"]] = relationship(back_populates="server", cascade="all, delete-orphan") class VpnClient(Base): __tablename__ = "vpn_clients" __table_args__ = (UniqueConstraint("server_id", "name", name="uq_client_name_per_server"),) id: Mapped[int] = mapped_column(Integer, primary_key=True) server_id: Mapped[int] = mapped_column(ForeignKey("vpn_servers.id", ondelete="CASCADE"), index=True) name: Mapped[str] = mapped_column(String(120)) private_key: Mapped[str] = mapped_column(Text) public_key: Mapped[str] = mapped_column(Text) preshared_key: Mapped[str] = mapped_column(Text) address: Mapped[str] = mapped_column(String(64)) allowed_ips: Mapped[str] = mapped_column(String(255), default="0.0.0.0/0, ::/0") is_enabled: Mapped[bool] = mapped_column(Boolean, default=True) notes: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now() ) server: Mapped[VpnServer] = relationship(back_populates="clients") class XuiPanel(Base): """Remote 3x-ui panel connection (API v3.5.0).""" __tablename__ = "xui_panels" id: Mapped[int] = mapped_column(Integer, primary_key=True) name: Mapped[str] = mapped_column(String(120)) base_url: Mapped[str] = mapped_column(String(512)) username: Mapped[str | None] = mapped_column(String(128), nullable=True) password: Mapped[str | None] = mapped_column(Text, nullable=True) api_token: Mapped[str | None] = mapped_column(Text, nullable=True) verify_ssl: Mapped[bool] = mapped_column(Boolean, default=True) is_reachable: Mapped[bool] = mapped_column(Boolean, default=False) last_error: Mapped[str | None] = mapped_column(Text, nullable=True) panel_info: Mapped[str | None] = mapped_column(Text, nullable=True) last_checked: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) is_enabled: Mapped[bool] = mapped_column(Boolean, default=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) site_clients: Mapped[list["XuiSiteClient"]] = relationship( back_populates="panel", cascade="all, delete-orphan" ) inbounds: Mapped[list["XuiPanelInbound"]] = relationship( back_populates="panel", cascade="all, delete-orphan" ) class XuiPanelInbound(Base): """Cached 3x-ui inbound; admin marks which are available for users.""" __tablename__ = "xui_panel_inbounds" __table_args__ = (UniqueConstraint("panel_id", "inbound_id", name="uq_xui_panel_inbound"),) id: Mapped[int] = mapped_column(Integer, primary_key=True) panel_id: Mapped[int] = mapped_column(ForeignKey("xui_panels.id", ondelete="CASCADE"), index=True) inbound_id: Mapped[int] = mapped_column(Integer) protocol: Mapped[str] = mapped_column(String(32), index=True) remark: Mapped[str | None] = mapped_column(String(255), nullable=True) port: Mapped[int | None] = mapped_column(Integer, nullable=True) enable: Mapped[bool] = mapped_column(Boolean, default=True) is_available_for_users: Mapped[bool] = mapped_column(Boolean, default=False) last_synced_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) panel: Mapped[XuiPanel] = relationship(back_populates="inbounds") class XuiSiteClient(Base): """Clients created via this website (not imported from existing 3x-ui users).""" __tablename__ = "xui_site_clients" __table_args__ = (UniqueConstraint("panel_id", "email", name="uq_xui_site_client_email"),) id: Mapped[int] = mapped_column(Integer, primary_key=True) panel_id: Mapped[int] = mapped_column(ForeignKey("xui_panels.id", ondelete="CASCADE"), index=True) email: Mapped[str] = mapped_column(String(255), index=True) protocol: Mapped[str] = mapped_column(String(32)) inbound_id: Mapped[int] = mapped_column(Integer) inbound_remark: Mapped[str | None] = mapped_column(String(255), nullable=True) uuid: Mapped[str | None] = mapped_column(String(128), nullable=True) flow: Mapped[str | None] = mapped_column(String(64), nullable=True) private_key: Mapped[str | None] = mapped_column(Text, nullable=True) public_key: Mapped[str | None] = mapped_column(Text, nullable=True) link: Mapped[str | None] = mapped_column(Text, nullable=True) address: Mapped[str | None] = mapped_column(String(64), nullable=True) endpoint: Mapped[str | None] = mapped_column(String(255), nullable=True) dns: Mapped[str | None] = mapped_column(String(255), nullable=True) mtu: Mapped[int | None] = mapped_column(Integer, nullable=True) server_public_key: Mapped[str | None] = mapped_column(Text, nullable=True) config_text: Mapped[str | None] = mapped_column(Text, nullable=True) expiry_days: Mapped[int] = mapped_column(Integer, default=0) start_after_first_use: Mapped[bool] = mapped_column(Boolean, default=False) expiry_time: Mapped[int] = mapped_column(Integer, default=0) total_gb: Mapped[int] = mapped_column(Integer, default=0) limit_ip: Mapped[int] = mapped_column(Integer, default=0) comment: Mapped[str | None] = mapped_column(Text, nullable=True) created_via: Mapped[str] = mapped_column(String(32), default="website") created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) panel: Mapped[XuiPanel] = relationship(back_populates="site_clients")