"""SQLAlchemy ORM models for users and duties.""" from sqlalchemy import Boolean, ForeignKey, Integer, BigInteger, Text from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship class Base(DeclarativeBase): """Declarative base for all models.""" pass class Role(Base): """Role for access control: 'user' (miniapp access), 'admin' (admin actions).""" __tablename__ = "roles" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(Text, nullable=False, unique=True) users: Mapped[list["User"]] = relationship("User", back_populates="role") class User(Base): """Telegram user and display name; may have telegram_user_id=None for import-only users.""" __tablename__ = "users" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) telegram_user_id: Mapped[int | None] = mapped_column( BigInteger, unique=True, nullable=True ) full_name: Mapped[str] = mapped_column(Text, nullable=False) username: Mapped[str | None] = mapped_column(Text, nullable=True) first_name: Mapped[str | None] = mapped_column(Text, nullable=True) last_name: Mapped[str | None] = mapped_column(Text, nullable=True) phone: Mapped[str | None] = mapped_column(Text, nullable=True) name_manually_edited: Mapped[bool] = mapped_column( Boolean, nullable=False, server_default="0", default=False ) role_id: Mapped[int | None] = mapped_column( Integer, ForeignKey("roles.id"), nullable=True ) role: Mapped["Role | None"] = relationship("Role", back_populates="users") duties: Mapped[list["Duty"]] = relationship("Duty", back_populates="user") class CalendarSubscriptionToken(Base): """One active calendar subscription token per user; token_hash is unique.""" __tablename__ = "calendar_subscription_tokens" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column( Integer, ForeignKey("users.id"), nullable=False ) token_hash: Mapped[str] = mapped_column(Text, nullable=False, unique=True) created_at: Mapped[str] = mapped_column(Text, nullable=False) class Duty(Base): """Single duty/unavailable/vacation slot (UTC start_at/end_at, event_type).""" __tablename__ = "duties" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column( Integer, ForeignKey("users.id"), nullable=False ) # UTC, ISO 8601 with Z suffix (e.g. 2025-01-15T09:00:00Z) start_at: Mapped[str] = mapped_column(Text, nullable=False) end_at: Mapped[str] = mapped_column(Text, nullable=False) # duty | unavailable | vacation event_type: Mapped[str] = mapped_column(Text, nullable=False, server_default="duty") user: Mapped["User"] = relationship("User", back_populates="duties") class GroupDutyPin(Base): """Stores which message to update in each group for the pinned duty notice.""" __tablename__ = "group_duty_pins" chat_id: Mapped[int] = mapped_column(BigInteger, primary_key=True) message_id: Mapped[int] = mapped_column(Integer, nullable=False) class TrustedGroup(Base): """Groups authorized to receive duty information.""" __tablename__ = "trusted_groups" chat_id: Mapped[int] = mapped_column(BigInteger, primary_key=True) added_by_user_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True) added_at: Mapped[str] = mapped_column(Text, nullable=False)