from datetime import datetime from sqlalchemy import select, func, desc as sa_desc, asc as sa_asc from sqlalchemy.ext.asyncio import AsyncSession from backend.models.author import Author from backend.models.post import Post from backend.models.comment import Comment async def list_authors( db: AsyncSession, subreddit_id: int | None = None, sort_by: str = "total_comments", sort_order: str = "desc", since: datetime | None = None, until: datetime | None = None, page: int = 1, per_page: int = 25, ) -> tuple[list[dict], int]: # Build count subqueries post_q = select(func.count(Post.id)).where(Post.author_id == Author.id) comment_q = select(func.count(Comment.id)).where(Comment.author_id == Author.id) if subreddit_id: post_q = post_q.where(Post.subreddit_id == subreddit_id) comment_q = comment_q.join(Post).where(Post.subreddit_id == subreddit_id) if since: post_q = post_q.where(Post.created_utc >= since) comment_q = comment_q.where(Comment.created_utc >= since) if until: post_q = post_q.where(Post.created_utc <= until) comment_q = comment_q.where(Comment.created_utc <= until) post_sub = post_q.correlate(Author).scalar_subquery().label("total_posts") comment_sub = comment_q.correlate(Author).scalar_subquery().label("total_comments") # Build main query as a subquery so we can filter on computed columns inner = select( Author.id, Author.username, Author.first_seen_at, Author.last_seen_at, post_sub, comment_sub, ).subquery() # Count authors with any activity count_stmt = select(func.count()).select_from(inner).where( (inner.c.total_posts > 0) | (inner.c.total_comments > 0) ) total = (await db.execute(count_stmt)).scalar() or 0 # Sort and paginate sort_col = inner.c.total_posts if sort_by == "total_posts" else inner.c.total_comments order_fn = sa_desc if sort_order == "desc" else sa_asc query = ( select(inner) .where((inner.c.total_posts > 0) | (inner.c.total_comments > 0)) .order_by(order_fn(sort_col)) .offset((page - 1) * per_page) .limit(per_page) ) result = await db.execute(query) authors = [] for row in result.all(): authors.append({ "id": row.id, "username": row.username, "first_seen_at": row.first_seen_at, "last_seen_at": row.last_seen_at, "total_posts": row.total_posts or 0, "total_comments": row.total_comments or 0, }) return authors, total async def get_author(db: AsyncSession, author_id: int) -> dict | None: post_count = ( select(func.count(Post.id)) .where(Post.author_id == author_id) .scalar_subquery() ) comment_count = ( select(func.count(Comment.id)) .where(Comment.author_id == author_id) .scalar_subquery() ) result = await db.execute( select( Author, post_count.label("total_posts"), comment_count.label("total_comments"), ).where(Author.id == author_id) ) row = result.first() if not row: return None author = row[0] data = {c.name: getattr(author, c.name) for c in author.__table__.columns} data["total_posts"] = row[1] or 0 data["total_comments"] = row[2] or 0 return data