test ferst
This commit is contained in:
132
api/route/public.py
Normal file
132
api/route/public.py
Normal file
@@ -0,0 +1,132 @@
|
||||
import json
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.orm import selectinload
|
||||
import redis.asyncio as aioredis
|
||||
|
||||
from bd.database import get_db, get_redis
|
||||
from bd.models import Article, Category, Tag, ArticleStatus
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def _cat_dict(c: Category) -> dict:
|
||||
return {"id": str(c.id), "name": c.name, "slug": c.slug}
|
||||
|
||||
|
||||
def _article_card(a: Article) -> dict:
|
||||
return {
|
||||
"id": str(a.id),
|
||||
"title": a.title,
|
||||
"slug": a.slug,
|
||||
"excerpt": a.excerpt,
|
||||
"cover_url": a.cover_url,
|
||||
"published_at": a.published_at.isoformat() if a.published_at else None,
|
||||
"view_count": a.view_count,
|
||||
"category": _cat_dict(a.category) if a.category else None,
|
||||
"tags": [{"id": str(t.id), "name": t.name, "slug": t.slug} for t in a.tags],
|
||||
}
|
||||
|
||||
|
||||
def _article_full(a: Article) -> dict:
|
||||
d = _article_card(a)
|
||||
d["content"] = a.content
|
||||
d["font_family"] = a.font_family
|
||||
return d
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_news(
|
||||
offset: int = Query(0, ge=0),
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
category: str | None = Query(None),
|
||||
tag: str | None = Query(None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
redis: aioredis.Redis = Depends(get_redis),
|
||||
):
|
||||
cache_key = f"news:list:{offset}:{limit}:{category or ''}:{tag or ''}"
|
||||
cached = await redis.get(cache_key)
|
||||
if cached:
|
||||
return json.loads(cached)
|
||||
|
||||
base = (
|
||||
select(Article)
|
||||
.options(selectinload(Article.category), selectinload(Article.tags))
|
||||
.where(Article.status == ArticleStatus.published)
|
||||
)
|
||||
|
||||
if category:
|
||||
cat = (await db.execute(select(Category).where(Category.slug == category))).scalar_one_or_none()
|
||||
if cat:
|
||||
base = base.where(Article.category_id == cat.id)
|
||||
else:
|
||||
return {"total": 0, "offset": offset, "limit": limit, "has_more": False, "items": []}
|
||||
|
||||
if tag:
|
||||
t = (await db.execute(select(Tag).where(Tag.slug == tag))).scalar_one_or_none()
|
||||
if t:
|
||||
base = base.where(Article.tags.any(Tag.id == t.id))
|
||||
|
||||
count_q = select(func.count()).select_from(base.subquery())
|
||||
total: int = (await db.execute(count_q)).scalar_one()
|
||||
|
||||
articles = (
|
||||
await db.execute(base.order_by(Article.published_at.desc()).offset(offset).limit(limit))
|
||||
).scalars().all()
|
||||
|
||||
result = {
|
||||
"total": total,
|
||||
"offset": offset,
|
||||
"limit": limit,
|
||||
"has_more": (offset + limit) < total,
|
||||
"items": [_article_card(a) for a in articles],
|
||||
}
|
||||
await redis.setex(cache_key, 30, json.dumps(result, default=str))
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/categories")
|
||||
async def list_categories(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
redis: aioredis.Redis = Depends(get_redis),
|
||||
):
|
||||
cached = await redis.get("news:categories")
|
||||
if cached:
|
||||
return json.loads(cached)
|
||||
cats = (await db.execute(select(Category).order_by(Category.name))).scalars().all()
|
||||
result = [_cat_dict(c) for c in cats]
|
||||
await redis.setex("news:categories", 60, json.dumps(result))
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/{slug}")
|
||||
async def get_article(
|
||||
slug: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
redis: aioredis.Redis = Depends(get_redis),
|
||||
):
|
||||
cache_key = f"news:article:{slug}"
|
||||
cached = await redis.get(cache_key)
|
||||
|
||||
q = (
|
||||
select(Article)
|
||||
.options(selectinload(Article.category), selectinload(Article.tags))
|
||||
.where(Article.slug == slug, Article.status == ArticleStatus.published)
|
||||
)
|
||||
article = (await db.execute(q)).scalar_one_or_none()
|
||||
if not article:
|
||||
raise HTTPException(status_code=404, detail="Article not found")
|
||||
|
||||
article.view_count += 1
|
||||
await db.commit()
|
||||
|
||||
if cached:
|
||||
data = json.loads(cached)
|
||||
data["view_count"] = article.view_count
|
||||
return data
|
||||
|
||||
await db.refresh(article)
|
||||
result = _article_full(article)
|
||||
await redis.setex(cache_key, 60, json.dumps(result, default=str))
|
||||
return result
|
||||
Reference in New Issue
Block a user