from __future__ import annotations from datetime import datetime, timezone from sqlalchemy import DateTime, Enum, ForeignKey, Integer, String from sqlalchemy.orm import Mapped, mapped_column from .base import Base from .users import UserRole class UserInvite(Base): """An emailed invitation to make a local account (milestone 496). The link carries a one-time key; only its sha256 is stored, as for API tokens. The admin chose the role and how many days the link works; the invitee chooses their own username, email and password.""" __tablename__ = "user_invites" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) key_hash: Mapped[str] = mapped_column(String(64), unique=True, nullable=False) # Where the invite was sent; the invitee may sign up with another address. email: Mapped[str] = mapped_column(String(255), nullable=False) role: Mapped[UserRole] = mapped_column(Enum(UserRole), nullable=False, default=UserRole.viewer) days: Mapped[int] = mapped_column(Integer, nullable=False, default=7) invited_by: Mapped[str | None] = mapped_column( String(36), ForeignKey("users.id", ondelete="SET NULL"), nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc)) # When the current key was sent; a resend replaces the key and restarts the clock. sent_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc)) expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) used_by: Mapped[str | None] = mapped_column( String(36), ForeignKey("users.id", ondelete="SET NULL"), nullable=True) revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)