import uuid import enum import secrets import string from datetime import datetime, timezone, date from sqlalchemy import String, Boolean, DateTime, Date, Enum as SAEnum, ForeignKey, Text, Integer from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base class LicenseStatus(str, enum.Enum): active = "active" expired = "expired" revoked = "revoked" trial = "trial" def generate_license_key() -> str: alphabet = string.ascii_uppercase + string.digits segments = ["TTUB"] + [ "".join(secrets.choice(alphabet) for _ in range(5)) for _ in range(3) ] return "-".join(segments) class License(Base): __tablename__ = "licenses" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) school_id: Mapped[str] = mapped_column(String(36), ForeignKey("schools.id", ondelete="CASCADE"), unique=True, nullable=False) key: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, default=generate_license_key, index=True) status: Mapped[LicenseStatus] = mapped_column(SAEnum(LicenseStatus), default=LicenseStatus.trial, nullable=False) tier: Mapped[str] = mapped_column(String(20), default="standard", nullable=False) issued_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) expires_at: Mapped[date | None] = mapped_column(Date, nullable=True) last_validated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) last_seen_ip: Mapped[str | None] = mapped_column(String(45), nullable=True) hardware_fingerprint: Mapped[str | None] = mapped_column(String(255), nullable=True) max_students: Mapped[int] = mapped_column(Integer, default=500, nullable=False) notes: Mapped[str | None] = mapped_column(Text, nullable=True) school: Mapped["School"] = relationship("School", back_populates="license")