import uuid from datetime import datetime, timezone from sqlalchemy import DateTime, JSON, func from sqlalchemy.ext.asyncio import AsyncSession from sqlmodel import Field, SQLModel, select def _now() -> datetime: return datetime.now(timezone.utc) class SavedReports(SQLModel, table=True): __tablename__ = "saved_reports" id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) user_id: uuid.UUID = Field(index=True, foreign_key="users.id") name: str description: str | None = Field(default=None) report_type: str filters: dict = Field(default_factory=dict, sa_type=JSON) last_run_at: datetime | None = Field(default=None, sa_type=DateTime(timezone=True)) created_at: datetime = Field(default_factory=_now, sa_type=DateTime(timezone=True)) updated_at: datetime = Field(default_factory=_now, sa_type=DateTime(timezone=True)) is_deleted: bool = Field(default=False) @staticmethod def _as_uuid(record_id) -> uuid.UUID | None: if record_id in (None, ""): return None try: return uuid.UUID(str(record_id)) except ValueError: return None @classmethod async def get_by_id(cls, session: AsyncSession, record_id, *, user_id=None): uid = cls._as_uuid(record_id) if uid is None: return None statement = select(cls).where(cls.id == uid, cls.is_deleted == False) # noqa: E712 if user_id is not None: statement = statement.where(cls.user_id == user_id) result = await session.execute(statement) return result.scalars().first() @classmethod async def fetch_saved_reports(cls, session: AsyncSession, *, user_id): statement = select(cls).where( cls.user_id == user_id, cls.is_deleted == False # noqa: E712 ) count_statement = select(func.count()).select_from(statement.subquery()) total = (await session.execute(count_statement)).scalar_one() statement = statement.order_by(cls.created_at.desc()) result = await session.execute(statement) return list(result.scalars().all()), total @classmethod async def insert_saved_report(cls, session: AsyncSession, fields: dict): row = cls(**fields) session.add(row) await session.commit() return await cls.get_by_id(session, row.id) @classmethod async def update_saved_report(cls, session: AsyncSession, record_id, fields: dict, *, user_id): row = await cls.get_by_id(session, record_id, user_id=user_id) if not row: return None for key, value in fields.items(): setattr(row, key, value) row.updated_at = _now() session.add(row) await session.commit() await session.refresh(row) return row @classmethod async def soft_delete_saved_report(cls, session: AsyncSession, record_id, *, user_id): row = await cls.get_by_id(session, record_id, user_id=user_id) if not row: return None row.is_deleted = True row.updated_at = _now() session.add(row) await session.commit() return row @classmethod async def touch_last_run(cls, session: AsyncSession, record_id, *, user_id): row = await cls.get_by_id(session, record_id, user_id=user_id) if not row: return None row.last_run_at = _now() session.add(row) await session.commit() return row class ReportRuns(SQLModel, table=True): __tablename__ = "report_runs" id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) saved_report_id: uuid.UUID = Field(index=True, foreign_key="saved_reports.id") run_by: uuid.UUID = Field(foreign_key="users.id") run_at: datetime = Field(default_factory=_now, sa_type=DateTime(timezone=True)) params: dict = Field(default_factory=dict, sa_type=JSON) row_count: int = Field(default=0) status: str = Field(default="completed") @classmethod async def insert_run(cls, session: AsyncSession, fields: dict): row = cls(**fields) session.add(row) await session.commit() return row @classmethod async def fetch_runs(cls, session: AsyncSession, *, saved_report_id, top: int = 20): statement = ( select(cls) .where(cls.saved_report_id == saved_report_id) .order_by(cls.run_at.desc()) .limit(top) ) result = await session.execute(statement) return list(result.scalars().all()) import users.models as _users_models # noqa: E402, F401