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, } }