from __future__ import annotations from datetime import UTC, datetime from typing import Any from sqlalchemy import DateTime, ForeignKey, Index, String, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from app.models.base import BaseModel, ID_TYPE, TimestampMixin class ShareLink(BaseModel, TimestampMixin): __tablename__ = "share_links" __table_args__ = ( UniqueConstraint("token", name="uk_share_links_token"), Index("idx_share_links_expires_at", "expires_at"), Index("idx_share_links_revoked_at", "revoked_at"), Index("idx_share_links_created_by", "created_by"), Index("idx_share_links_updated_at", "updated_at"), ) id: Mapped[int] = mapped_column(ID_TYPE, primary_key=True) token: Mapped[str] = mapped_column(String(64), nullable=False) label: Mapped[str | None] = mapped_column(String(128), nullable=True) expires_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) revoked_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) created_by: Mapped[int | None] = mapped_column( ID_TYPE, ForeignKey("accounts.id", ondelete="SET NULL"), nullable=True, ) creator: Mapped[Any] = relationship( "Account", foreign_keys=[created_by], back_populates="created_share_links", ) @property def is_revoked(self) -> bool: return self.revoked_at is not None def is_expired(self, *, now: datetime | None = None) -> bool: current = now or datetime.now(UTC).replace(tzinfo=None) return self.expires_at <= current def is_active(self, *, now: datetime | None = None) -> bool: return not self.is_revoked and not self.is_expired(now=now)