feat: GEO/SEO structured data + search ranking tracker
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user