from datetime import datetime, time from sqlalchemy import DateTime, Integer, String, Time, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column from app.core.database import Base from app.core.utils.time import utc_now from app.modules.feishu_users.constants import ( DEFAULT_FEISHU_USER_TIMEZONE, FeishuUserRole, FeishuUserStatus, ) class FeishuUser(Base): __tablename__ = "feishu_users" __table_args__ = ( UniqueConstraint( "tenant_key", "open_id", name="uq_feishu_user_tenant_open_id", ), ) id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(64), unique=True, index=True) tenant_key: Mapped[str] = mapped_column(String(128), index=True) open_id: Mapped[str] = mapped_column(String(128), index=True) union_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True) user_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True) role: Mapped[str] = mapped_column( String(32), default=FeishuUserRole.USER, index=True, ) status: Mapped[str] = mapped_column( String(32), default=FeishuUserStatus.ACTIVE, index=True, ) timezone: Mapped[str] = mapped_column( String(64), default=DEFAULT_FEISHU_USER_TIMEZONE, ) quiet_hours_start: Mapped[time | None] = mapped_column(Time, nullable=True) quiet_hours_end: Mapped[time | None] = mapped_column(Time, nullable=True) last_active_at: Mapped[datetime] = mapped_column(DateTime, default=utc_now, index=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=utc_now, index=True) updated_at: Mapped[datetime] = mapped_column( DateTime, default=utc_now, onupdate=utc_now, ) class FeishuAdminBootstrapTombstone(Base): """Irreversible marker preventing a deleted initial admin from re-bootstrap.""" __tablename__ = "feishu_admin_bootstrap_tombstones" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) identity_hash: Mapped[str] = mapped_column(String(64), unique=True, index=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=utc_now, index=True)