- Introduced a new database model for calendar subscription tokens, allowing users to generate unique tokens for accessing their personal calendar. - Implemented API endpoint to return ICS files containing only the subscribing user's duties, enhancing user experience with personalized calendar access. - Added utility functions for generating ICS files from user duties, ensuring proper formatting and timezone handling. - Updated command handlers to support the new calendar link feature, providing users with easy access to their personal calendar subscriptions. - Included unit tests for the new functionality, ensuring reliability and correctness of token generation and ICS file creation.
65 lines
2.3 KiB
Python
65 lines
2.3 KiB
Python
"""SQLAlchemy ORM models for users and duties."""
|
|
|
|
from sqlalchemy import ForeignKey, Integer, BigInteger, Text
|
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
|
|
|
|
|
|
class Base(DeclarativeBase):
|
|
"""Declarative base for all models."""
|
|
|
|
pass
|
|
|
|
|
|
class User(Base):
|
|
__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)
|
|
|
|
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):
|
|
__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)
|