Files
Random-image-api/backend/app/db/authors_list.py
T
2026-02-22 18:58:55 +08:00

100 lines
2.9 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
import sqlalchemy as sa
from sqlalchemy import func, select
from sqlalchemy.exc import DBAPIError
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.models.images import Image
from app.db.sqlite_utils import sqlite_fts_phrase_query, sqlite_table_exists
@dataclass(frozen=True, slots=True)
class AuthorListItem:
user_id: int
user_name: str | None
count_images: int
async def list_authors(
session: AsyncSession,
*,
limit: int,
cursor: int | None = None,
q: str | None = None,
) -> tuple[list[AuthorListItem], int | None]:
limit_i = int(limit)
if limit_i < 1:
raise ValueError("limit must be >= 1")
cursor_i: int | None = None
if cursor is not None:
cursor_i = int(cursor)
if cursor_i <= 0:
raise ValueError("cursor must be > 0")
q_norm = (q or "").strip()
use_fts = False
fts_q = ""
if q_norm and len(q_norm) >= 3:
use_fts = await sqlite_table_exists(session, name="authors_fts")
if use_fts:
fts_q = sqlite_fts_phrase_query(q_norm)
def _build_stmt(*, use_fts_filter: bool) -> sa.Select:
clauses: list[object] = [Image.status == 1, Image.user_id.is_not(None)]
if cursor_i is not None:
clauses.append(Image.user_id > cursor_i)
if q_norm:
if use_fts_filter:
fts_user_ids = (
sa.text("SELECT rowid AS user_id FROM authors_fts WHERE authors_fts MATCH :q")
.bindparams(sa.bindparam("q", fts_q))
.columns(user_id=sa.Integer)
)
fts_user_ids_sq = fts_user_ids.subquery()
clauses.append(Image.user_id.in_(sa.select(fts_user_ids_sq.c.user_id)))
else:
clauses.append(Image.user_name.like(f"%{q_norm}%"))
return (
select(
Image.user_id,
func.max(Image.user_name).label("user_name"),
func.count(Image.id).label("count_images"),
)
.where(*clauses)
.group_by(Image.user_id)
.order_by(Image.user_id.asc())
.limit(limit_i + 1)
)
stmt = _build_stmt(use_fts_filter=use_fts)
try:
rows = (await session.execute(stmt)).all()
except DBAPIError:
if not use_fts:
raise
stmt = _build_stmt(use_fts_filter=False)
rows = (await session.execute(stmt)).all()
next_cursor: int | None = None
if len(rows) > limit_i:
rows = rows[:limit_i]
next_cursor = int(rows[-1][0] or 0)
items = [
AuthorListItem(
user_id=int(row[0]),
user_name=str(row[1]) if row[1] is not None else None,
count_images=int(row[2] or 0),
)
for row in rows
]
return items, next_cursor