feat: Phase 4 多租户隔离 + 四阶段升级测试 + CSS 统一化
Phase 4: org_id 注入 JWT/API 过滤/组织管理 CRUD/前端组织列 测试: tests/test_phase_upgrades.py 97项全覆盖 CSS: theme-modern.css 共享 mobile-card-list/status-dot/search-bar 等模式 修复: initial_data.py LLM配置 NOT NULL 约束, TopicResponse 含 org_id
This commit is contained in:
@@ -11,7 +11,7 @@ from ..schemas import (
|
||||
ContentMetricsCreate, ContentMetricsUpdate, ContentMetricsResponse,
|
||||
MetricsDashboard
|
||||
)
|
||||
from .auth import get_current_user
|
||||
from .auth import get_current_user, org_filter
|
||||
|
||||
router = APIRouter(prefix="/api/metrics", tags=["metrics"])
|
||||
|
||||
@@ -24,18 +24,24 @@ def get_dashboard(
|
||||
):
|
||||
since = datetime.now() - timedelta(days=days)
|
||||
|
||||
total_topics = db.query(Topic).count()
|
||||
topic_base = db.query(Topic)
|
||||
of = org_filter(current_user, Topic)
|
||||
if of is not True:
|
||||
topic_base = topic_base.filter(of)
|
||||
|
||||
raw_status = db.query(Topic.status, func.count()).group_by(Topic.status).all()
|
||||
total_topics = topic_base.count()
|
||||
|
||||
raw_status = topic_base.with_entities(Topic.status, func.count()).group_by(Topic.status).all()
|
||||
topics_by_status = {}
|
||||
for s, cnt in raw_status:
|
||||
topics_by_status[s] = cnt
|
||||
|
||||
total_published = db.query(ContentMetrics).filter(
|
||||
ContentMetrics.views > 0
|
||||
).count()
|
||||
metrics_q = db.query(ContentMetrics).join(Topic, ContentMetrics.topic_id == Topic.id)
|
||||
if of is not True:
|
||||
metrics_q = metrics_q.filter(of)
|
||||
total_published = metrics_q.filter(ContentMetrics.views > 0).count()
|
||||
|
||||
all_metrics = db.query(ContentMetrics).filter(
|
||||
all_metrics = metrics_q.filter(
|
||||
ContentMetrics.created_at >= since
|
||||
).all()
|
||||
|
||||
@@ -45,11 +51,14 @@ def get_dashboard(
|
||||
engagement_rates = [m.engagement_rate for m in all_metrics if m.views > 0]
|
||||
avg_engagement = sum(engagement_rates) / len(engagement_rates) if engagement_rates else 0
|
||||
|
||||
top_topics_data = db.query(
|
||||
top_q = db.query(
|
||||
ContentMetrics.topic_id,
|
||||
func.sum(ContentMetrics.views).label("total_views"),
|
||||
func.sum(ContentMetrics.likes).label("total_likes")
|
||||
).join(Topic).filter(
|
||||
).join(Topic, ContentMetrics.topic_id == Topic.id)
|
||||
if of is not True:
|
||||
top_q = top_q.filter(of)
|
||||
top_topics_data = top_q.filter(
|
||||
ContentMetrics.created_at >= since
|
||||
).group_by(ContentMetrics.topic_id).order_by(desc("total_views")).limit(10).all()
|
||||
|
||||
@@ -63,7 +72,10 @@ def get_dashboard(
|
||||
"total_likes": row.total_likes or 0,
|
||||
})
|
||||
|
||||
recent_metrics = db.query(ContentMetrics).order_by(
|
||||
recent_q = db.query(ContentMetrics).join(Topic, ContentMetrics.topic_id == Topic.id)
|
||||
if of is not True:
|
||||
recent_q = recent_q.filter(of)
|
||||
recent_metrics = recent_q.order_by(
|
||||
ContentMetrics.created_at.desc()
|
||||
).limit(10).all()
|
||||
|
||||
@@ -93,13 +105,17 @@ def get_trend(
|
||||
else:
|
||||
date_format = func.date_trunc(group_by, ContentMetrics.created_at)
|
||||
|
||||
rows = db.query(
|
||||
q = db.query(
|
||||
date_format.label("period"),
|
||||
func.sum(ContentMetrics.views).label("views"),
|
||||
func.sum(ContentMetrics.likes).label("likes"),
|
||||
func.sum(ContentMetrics.comments).label("comments"),
|
||||
func.count(ContentMetrics.id).label("count")
|
||||
).filter(
|
||||
).join(Topic, ContentMetrics.topic_id == Topic.id)
|
||||
of_m = org_filter(current_user, Topic)
|
||||
if of_m is not True:
|
||||
q = q.filter(of_m)
|
||||
rows = q.filter(
|
||||
ContentMetrics.created_at >= since
|
||||
).group_by(date_format).order_by(date_format).all()
|
||||
|
||||
@@ -123,7 +139,10 @@ def list_metrics(
|
||||
db: Session = Depends(get_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
query = db.query(ContentMetrics)
|
||||
query = db.query(ContentMetrics).join(Topic, ContentMetrics.topic_id == Topic.id)
|
||||
of_m = org_filter(current_user, Topic)
|
||||
if of_m is not True:
|
||||
query = query.filter(of_m)
|
||||
if topic_id:
|
||||
query = query.filter(ContentMetrics.topic_id == topic_id)
|
||||
if platform:
|
||||
@@ -140,6 +159,8 @@ def create_metric(
|
||||
topic = db.query(Topic).filter(Topic.id == data.topic_id).first()
|
||||
if not topic:
|
||||
raise HTTPException(status_code=404, detail="选题不存在")
|
||||
if current_user.role != "admin" and topic.org_id != current_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="选题不存在")
|
||||
|
||||
existing = db.query(ContentMetrics).filter(
|
||||
ContentMetrics.topic_id == data.topic_id,
|
||||
@@ -204,6 +225,8 @@ def get_topic_metrics(
|
||||
topic = db.query(Topic).filter(Topic.id == topic_id).first()
|
||||
if not topic:
|
||||
raise HTTPException(status_code=404, detail="选题不存在")
|
||||
if current_user.role != "admin" and topic.org_id != current_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="选题不存在")
|
||||
return db.query(ContentMetrics).filter(
|
||||
ContentMetrics.topic_id == topic_id
|
||||
).order_by(ContentMetrics.created_at.desc()).all()
|
||||
@@ -214,13 +237,17 @@ def get_metrics_by_platform(
|
||||
db: Session = Depends(get_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
rows = db.query(
|
||||
q = db.query(
|
||||
ContentMetrics.platform,
|
||||
func.sum(ContentMetrics.views).label("total_views"),
|
||||
func.sum(ContentMetrics.likes).label("total_likes"),
|
||||
func.sum(ContentMetrics.comments).label("total_comments"),
|
||||
func.count(ContentMetrics.id).label("count")
|
||||
).group_by(ContentMetrics.platform).all()
|
||||
).join(Topic, ContentMetrics.topic_id == Topic.id)
|
||||
of_m = org_filter(current_user, Topic)
|
||||
if of_m is not True:
|
||||
q = q.filter(of_m)
|
||||
rows = q.group_by(ContentMetrics.platform).all()
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -243,11 +270,15 @@ def recommend_topics_from_metrics(
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
try:
|
||||
high_performing = db.query(
|
||||
hp_q = db.query(
|
||||
ContentMetrics.topic_id,
|
||||
func.avg(ContentMetrics.engagement_rate).label("avg_engagement"),
|
||||
func.max(ContentMetrics.views).label("max_views")
|
||||
).group_by(ContentMetrics.topic_id).order_by(desc("avg_engagement")).limit(20).all()
|
||||
).join(Topic, ContentMetrics.topic_id == Topic.id)
|
||||
of_m = org_filter(current_user, Topic)
|
||||
if of_m is not True:
|
||||
hp_q = hp_q.filter(of_m)
|
||||
high_performing = hp_q.group_by(ContentMetrics.topic_id).order_by(desc("avg_engagement")).limit(20).all()
|
||||
|
||||
recommendations = []
|
||||
for row in high_performing:
|
||||
|
||||
Reference in New Issue
Block a user