- Introduced a new `trusted_groups` table to store groups authorized to receive duty information. - Implemented functions to add, remove, and check trusted groups in the database. - Enhanced command handlers to manage trusted groups, including `/trust_group` and `/untrust_group` commands for admin users. - Updated internationalization messages to support new commands and group status notifications. - Added unit tests for trusted groups repository functions to ensure correct behavior and data integrity.
97 lines
3.5 KiB
Python
97 lines
3.5 KiB
Python
"""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)
|