78 lines
2.5 KiB
Python
78 lines
2.5 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import desc, func
|
|
from typing import Optional
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
from ..database import get_db
|
|
from ..models import SearchRanking, Article, Topic
|
|
from .auth import get_current_user, org_filter
|
|
|
|
router = APIRouter(prefix="/api/seo", tags=["seo"])
|
|
|
|
ArticleModel = Article
|
|
|
|
|
|
def _apply_org_filter(query, current_user, db):
|
|
of = org_filter(current_user, ArticleModel)
|
|
if of is not True:
|
|
query = query.join(ArticleModel, SearchRanking.article_id == ArticleModel.id, isouter=True)
|
|
query = query.filter(of)
|
|
return query
|
|
|
|
|
|
@router.get("/rankings")
|
|
def get_rankings(
|
|
article_id: Optional[str] = None,
|
|
keyword: Optional[str] = None,
|
|
days: int = Query(30, ge=1, le=365),
|
|
limit: int = Query(50, ge=1, le=200),
|
|
db: Session = Depends(get_db),
|
|
current_user: dict = Depends(get_current_user),
|
|
):
|
|
"""获取搜索排名数据"""
|
|
query = db.query(SearchRanking)
|
|
query = _apply_org_filter(query, current_user, db)
|
|
|
|
if article_id:
|
|
query = query.filter(SearchRanking.article_id == article_id)
|
|
if keyword:
|
|
query = query.filter(SearchRanking.keyword.ilike(f"%{keyword}%"))
|
|
|
|
cutoff = datetime.now(timezone.utc) - timedelta(days=days)
|
|
query = query.filter(SearchRanking.checked_at >= cutoff)
|
|
|
|
rankings = query.order_by(desc(SearchRanking.checked_at)).limit(limit).all()
|
|
return {"ok": True, "data": [r.to_dict() for r in rankings]}
|
|
|
|
|
|
@router.get("/rankings/overview")
|
|
def get_rankings_overview(
|
|
days: int = Query(30, ge=1, le=365),
|
|
db: Session = Depends(get_db),
|
|
current_user: dict = Depends(get_current_user),
|
|
):
|
|
"""搜索排名概览统计"""
|
|
cutoff = datetime.now(timezone.utc) - timedelta(days=days)
|
|
|
|
base = db.query(SearchRanking)
|
|
base = _apply_org_filter(base, current_user, db)
|
|
base = base.filter(SearchRanking.checked_at >= cutoff)
|
|
|
|
total_checks = base.count()
|
|
on_page = base.filter(SearchRanking.position.isnot(None)).count()
|
|
ai_cited = base.filter(SearchRanking.ai_cited == True).count()
|
|
|
|
best = base.filter(SearchRanking.position.isnot(None)).order_by(SearchRanking.position).first()
|
|
|
|
return {
|
|
"ok": True,
|
|
"data": {
|
|
"total_checks": total_checks,
|
|
"on_page": on_page,
|
|
"ai_cited": ai_cited,
|
|
"best_position": best.position if best else None,
|
|
"best_keyword": best.keyword if best else None,
|
|
}
|
|
}
|