new web ract

This commit is contained in:
jze9
2026-05-15 03:31:28 +05:00
parent de78624495
commit 2335497226
58 changed files with 7397 additions and 406 deletions

View File

@@ -1,12 +1,13 @@
import json
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func
from sqlalchemy import select, func, or_
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
from bd.models import Article, Category, Tag, ArticleStatus, article_tag
router = APIRouter()
@@ -22,6 +23,7 @@ def _article_card(a: Article) -> dict:
"slug": a.slug,
"excerpt": a.excerpt,
"cover_url": a.cover_url,
"source_url": a.source_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,
@@ -42,10 +44,13 @@ async def list_news(
limit: int = Query(20, ge=1, le=100),
category: str | None = Query(None),
tag: str | None = Query(None),
date_from: str | None = Query(None),
date_to: str | None = Query(None),
q: str | None = Query(None, max_length=200),
db: AsyncSession = Depends(get_db),
redis: aioredis.Redis = Depends(get_redis),
):
cache_key = f"news:list:{offset}:{limit}:{category or ''}:{tag or ''}"
cache_key = f"news:list:{offset}:{limit}:{category or ''}:{tag or ''}:{date_from or ''}:{date_to or ''}:{q or ''}"
cached = await redis.get(cache_key)
if cached:
return json.loads(cached)
@@ -68,6 +73,29 @@ async def list_news(
if t:
base = base.where(Article.tags.any(Tag.id == t.id))
if date_from:
try:
base = base.where(Article.published_at >= datetime.fromisoformat(date_from))
except ValueError:
pass
if date_to:
try:
base = base.where(Article.published_at <= datetime.fromisoformat(date_to))
except ValueError:
pass
if q:
pattern = f"%{q}%"
base = base.where(
or_(
Article.title.ilike(pattern),
Article.excerpt.ilike(pattern),
Article.content.ilike(pattern),
Article.tags.any(Tag.name.ilike(pattern)),
)
)
count_q = select(func.count()).select_from(base.subquery())
total: int = (await db.execute(count_q)).scalar_one()
@@ -82,7 +110,7 @@ async def list_news(
"has_more": (offset + limit) < total,
"items": [_article_card(a) for a in articles],
}
await redis.setex(cache_key, 30, json.dumps(result, default=str))
await redis.setex(cache_key, 30 if q else 120, json.dumps(result, default=str))
return result
@@ -96,7 +124,36 @@ async def list_categories(
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))
await redis.setex("news:categories", 300, json.dumps(result))
return result
@router.get("/tags")
async def list_tags(
limit: int = Query(30, ge=1, le=100),
db: AsyncSession = Depends(get_db),
redis: aioredis.Redis = Depends(get_redis),
):
cache_key = f"news:tags:{limit}"
cached = await redis.get(cache_key)
if cached:
return json.loads(cached)
q = (
select(Tag, func.count(article_tag.c.article_id).label("cnt"))
.join(article_tag, Tag.id == article_tag.c.tag_id)
.join(Article, article_tag.c.article_id == Article.id)
.where(Article.status == ArticleStatus.published)
.group_by(Tag.id)
.order_by(func.count(article_tag.c.article_id).desc())
.limit(limit)
)
rows = (await db.execute(q)).all()
result = [
{"id": str(r.Tag.id), "name": r.Tag.name, "slug": r.Tag.slug, "count": r.cnt}
for r in rows
]
await redis.setex(cache_key, 300, json.dumps(result))
return result