from datetime import datetime, timedelta from sqlalchemy import DateTime, Integer, String, and_, or_ from sqlalchemy.orm import Mapped, Session, mapped_column from app.database import Base class HTMLFile(Base): __tablename__ = "html_files" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) unique_id: Mapped[str] = mapped_column( String(32), unique=True, index=True, nullable=False, ) filename: Mapped[str] = mapped_column(String(255), nullable=False) title: Mapped[str | None] = mapped_column(String(120), nullable=True) source: Mapped[str | None] = mapped_column(String(80), nullable=True) request_id: Mapped[str | None] = mapped_column(String(120), nullable=True) size_bytes: Mapped[int | None] = mapped_column(Integer, nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime, default=datetime.utcnow, nullable=False, ) expires_at: Mapped[datetime | None] = mapped_column( DateTime, nullable=True, index=True, ) updated_at: Mapped[datetime] = mapped_column( DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False, ) @classmethod def list_expired_records( cls, db: Session, default_retention_days: int, ) -> list["HTMLFile"]: now = datetime.utcnow() fallback_cutoff = now - timedelta(days=default_retention_days) return ( db.query(cls) .filter( or_( cls.expires_at < now, and_( cls.expires_at.is_(None), cls.created_at < fallback_cutoff, ), ) ) .all() ) def resolved_expires_at(self, default_retention_days: int) -> datetime: if self.expires_at is not None: return self.expires_at return self.created_at + timedelta(days=default_retention_days)