feat: GEO/SEO structured data + search ranking tracker
This commit is contained in:
@@ -78,6 +78,8 @@ def update_user(
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
if admin_user.role != "admin" and user.org_id != admin_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
|
||||
changes = {}
|
||||
if user_update.username is not None:
|
||||
@@ -122,6 +124,8 @@ def delete_user(
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
if admin_user.role != "admin" and user.org_id != admin_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
|
||||
if user.role == "admin":
|
||||
raise HTTPException(status_code=400, detail="不能删除管理员用户")
|
||||
|
||||
@@ -83,6 +83,8 @@ def get_article_detail(article_id: str, current_user: User = Depends(get_current
|
||||
"topic_title": topic.title if topic else None,
|
||||
"platform": article.platform,
|
||||
"file_path": article.file_path,
|
||||
"title": article.title,
|
||||
"content": article.content,
|
||||
"status": article.status,
|
||||
"compliance_score": article.compliance_score,
|
||||
"word_count": article.word_count,
|
||||
|
||||
@@ -10,7 +10,7 @@ from typing import List, Optional
|
||||
from ..database import get_db
|
||||
from ..models import MediaAsset
|
||||
from ..schemas import MediaAssetCreate, MediaAssetUpdate, MediaAssetResponse
|
||||
from .auth import get_current_user
|
||||
from .auth import get_current_user, org_filter
|
||||
|
||||
router = APIRouter(prefix="/api/assets", tags=["assets"])
|
||||
|
||||
@@ -33,6 +33,9 @@ def list_assets(
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
query = db.query(MediaAsset)
|
||||
of = org_filter(current_user, MediaAsset)
|
||||
if of is not True:
|
||||
query = query.filter(of)
|
||||
|
||||
if file_type:
|
||||
query = query.filter(MediaAsset.file_type == file_type)
|
||||
@@ -54,7 +57,11 @@ def list_tags(
|
||||
db: Session = Depends(get_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
assets = db.query(MediaAsset.tags).all()
|
||||
of = org_filter(current_user, MediaAsset)
|
||||
base = db.query(MediaAsset)
|
||||
if of is not True:
|
||||
base = base.filter(of)
|
||||
assets = base.with_entities(MediaAsset.tags).all()
|
||||
all_tags = set()
|
||||
for a in assets:
|
||||
if a[0]:
|
||||
@@ -67,9 +74,13 @@ def get_counts(
|
||||
db: Session = Depends(get_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
total = db.query(MediaAsset).count()
|
||||
of = org_filter(current_user, MediaAsset)
|
||||
base = db.query(MediaAsset)
|
||||
if of is not True:
|
||||
base = base.filter(of)
|
||||
total = base.count()
|
||||
by_type = {}
|
||||
rows = db.query(MediaAsset.file_type, func.count(MediaAsset.id)).group_by(MediaAsset.file_type).all()
|
||||
rows = base.with_entities(MediaAsset.file_type, func.count(MediaAsset.id)).group_by(MediaAsset.file_type).all()
|
||||
for ftype, cnt in rows:
|
||||
by_type[ftype] = cnt
|
||||
return {"total": total, "by_type": by_type}
|
||||
@@ -120,7 +131,8 @@ async def upload_asset(
|
||||
alt_text=alt_text,
|
||||
tags=parsed_tags,
|
||||
topic_ids=parsed_topic_ids,
|
||||
uploaded_by=current_user.username
|
||||
uploaded_by=current_user.username,
|
||||
org_id=current_user.org_id or "default",
|
||||
)
|
||||
db.add(asset)
|
||||
db.commit()
|
||||
@@ -138,6 +150,8 @@ def update_asset(
|
||||
asset = db.query(MediaAsset).filter(MediaAsset.id == asset_id).first()
|
||||
if not asset:
|
||||
raise HTTPException(status_code=404, detail="素材不存在")
|
||||
if current_user.role != "admin" and asset.org_id != current_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="素材不存在")
|
||||
|
||||
for k, v in data.model_dump(exclude_unset=True).items():
|
||||
setattr(asset, k, v)
|
||||
@@ -155,6 +169,8 @@ def delete_asset(
|
||||
asset = db.query(MediaAsset).filter(MediaAsset.id == asset_id).first()
|
||||
if not asset:
|
||||
raise HTTPException(status_code=404, detail="素材不存在")
|
||||
if current_user.role != "admin" and asset.org_id != current_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="素材不存在")
|
||||
|
||||
if os.path.exists(asset.file_path):
|
||||
try:
|
||||
@@ -176,6 +192,8 @@ def increment_usage(
|
||||
asset = db.query(MediaAsset).filter(MediaAsset.id == asset_id).first()
|
||||
if not asset:
|
||||
raise HTTPException(status_code=404, detail="素材不存在")
|
||||
if current_user.role != "admin" and asset.org_id != current_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="素材不存在")
|
||||
asset.usage_count = (asset.usage_count or 0) + 1
|
||||
db.commit()
|
||||
return {"ok": True, "usage_count": asset.usage_count}
|
||||
@@ -15,12 +15,15 @@ def list_topics(
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
status: Optional[str] = None,
|
||||
field: Optional[str] = None
|
||||
field: Optional[str] = None,
|
||||
org_id: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""获取选题列表,支持分页和筛选"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
query = db.query(Topic)
|
||||
if org_id:
|
||||
query = query.filter(Topic.org_id == org_id)
|
||||
if status:
|
||||
query = query.filter(Topic.status == status)
|
||||
if field:
|
||||
@@ -51,11 +54,14 @@ def list_topics(
|
||||
db.close()
|
||||
|
||||
|
||||
def get_topic(topic_id: str) -> Dict[str, Any]:
|
||||
def get_topic(topic_id: str, org_id: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""获取单个选题详情"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
t = db.query(Topic).filter(Topic.id == topic_id).first()
|
||||
query = db.query(Topic).filter(Topic.id == topic_id)
|
||||
if org_id:
|
||||
query = query.filter(Topic.org_id == org_id)
|
||||
t = query.first()
|
||||
if not t:
|
||||
return {"error": f"选题 {topic_id} 不存在"}
|
||||
return {
|
||||
@@ -191,19 +197,21 @@ def list_system_configs() -> List[Dict[str, Any]]:
|
||||
ACTION_REGISTRY = {
|
||||
"list_topics": {
|
||||
"func": list_topics,
|
||||
"description": "获取选题列表,支持分页(page, page_size)和筛选(status, field)",
|
||||
"description": "获取选题列表,支持分页(page, page_size)和筛选(status, field, org_id)",
|
||||
"params_schema": {
|
||||
"page": {"type": "integer", "default": 1, "desc": "页码"},
|
||||
"page_size": {"type": "integer", "default": 20, "desc": "每页条数"},
|
||||
"status": {"type": "string", "enum": ["pending", "review", "draft", "ready", "published"], "desc": "按状态筛选"},
|
||||
"field": {"type": "string", "desc": "按领域筛选"}
|
||||
"field": {"type": "string", "desc": "按领域筛选"},
|
||||
"org_id": {"type": "string", "desc": "按组织筛选"}
|
||||
}
|
||||
},
|
||||
"get_topic": {
|
||||
"func": get_topic,
|
||||
"description": "获取单个选题详情",
|
||||
"params_schema": {
|
||||
"topic_id": {"type": "string", "desc": "选题ID,如 A01 或 LIVING-001-26"}
|
||||
"topic_id": {"type": "string", "desc": "选题ID,如 A01 或 LIVING-001-26"},
|
||||
"org_id": {"type": "string", "desc": "按组织筛选(可选)"}
|
||||
}
|
||||
},
|
||||
"get_recent_task_logs": {
|
||||
|
||||
@@ -120,6 +120,11 @@ def update_entry(
|
||||
if not entry:
|
||||
raise HTTPException(status_code=404, detail="日历条目不存在")
|
||||
|
||||
if entry.topic_id:
|
||||
topic = db.query(Topic).filter(Topic.id == entry.topic_id).first()
|
||||
if topic and current_user.role != "admin" and topic.org_id != current_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="日历条目不存在")
|
||||
|
||||
for k, v in data.model_dump(exclude_unset=True).items():
|
||||
setattr(entry, k, v)
|
||||
db.commit()
|
||||
@@ -147,6 +152,10 @@ def delete_entry(
|
||||
entry = db.query(ContentCalendar).filter(ContentCalendar.id == entry_id).first()
|
||||
if not entry:
|
||||
raise HTTPException(status_code=404, detail="日历条目不存在")
|
||||
if entry.topic_id:
|
||||
topic = db.query(Topic).filter(Topic.id == entry.topic_id).first()
|
||||
if topic and current_user.role != "admin" and topic.org_id != current_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="日历条目不存在")
|
||||
db.delete(entry)
|
||||
db.commit()
|
||||
return {"ok": True}
|
||||
|
||||
@@ -186,6 +186,13 @@ def create_metric(
|
||||
return metric
|
||||
|
||||
|
||||
def _check_metric_org(metric, current_user, db):
|
||||
"""Check if metric's topic belongs to user's org"""
|
||||
topic = db.query(Topic).filter(Topic.id == metric.topic_id).first()
|
||||
if topic and current_user.role != "admin" and topic.org_id != current_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="数据记录不存在")
|
||||
|
||||
|
||||
@router.put("/entries/{metric_id}", response_model=ContentMetricsResponse)
|
||||
def update_metric(
|
||||
metric_id: int,
|
||||
@@ -196,6 +203,7 @@ def update_metric(
|
||||
metric = db.query(ContentMetrics).filter(ContentMetrics.id == metric_id).first()
|
||||
if not metric:
|
||||
raise HTTPException(status_code=404, detail="数据记录不存在")
|
||||
_check_metric_org(metric, current_user, db)
|
||||
|
||||
for k, v in data.model_dump(exclude_unset=True).items():
|
||||
setattr(metric, k, v)
|
||||
@@ -214,6 +222,7 @@ def delete_metric(
|
||||
metric = db.query(ContentMetrics).filter(ContentMetrics.id == metric_id).first()
|
||||
if not metric:
|
||||
raise HTTPException(status_code=404, detail="数据记录不存在")
|
||||
_check_metric_org(metric, current_user, db)
|
||||
db.delete(metric)
|
||||
db.commit()
|
||||
return {"ok": True}
|
||||
@@ -357,6 +366,8 @@ def fetch_zhihu_metrics(
|
||||
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="选题不存在")
|
||||
|
||||
post_id = _extract_zhihu_post_id(data.zhihu_url)
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ def _aggregate_status_counts(q):
|
||||
return counts
|
||||
|
||||
@router.get("/status")
|
||||
def get_status(db: Session = Depends(get_db)):
|
||||
def get_status(db: Session = Depends(get_db), current_user=Depends(get_current_user)):
|
||||
total = db.query(Topic).count()
|
||||
counts = _aggregate_status_counts(db.query(Topic))
|
||||
today = date.today()
|
||||
@@ -243,15 +243,16 @@ def get_pipeline_status(db: Session = Depends(get_db), current_user=Depends(get_
|
||||
return {"topics_count": total, "status_distribution": counts, "pipeline_modules": pipeline_status}
|
||||
|
||||
@router.post("/sync/run")
|
||||
def run_sync():
|
||||
def run_sync(current_user=Depends(get_current_user)):
|
||||
try:
|
||||
sync_all_topics()
|
||||
return {"message": "Sync completed (DB → JSON backup)"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/optimize-sources/run")
|
||||
def trigger_optimize_sources():
|
||||
def trigger_optimize_sources(current_user=Depends(get_current_user)):
|
||||
try:
|
||||
from ..core.scheduler import scheduler
|
||||
def _bg():
|
||||
@@ -265,8 +266,9 @@ def trigger_optimize_sources():
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/metrics-sync/run")
|
||||
def trigger_metrics_sync():
|
||||
def trigger_metrics_sync(current_user=Depends(get_current_user)):
|
||||
try:
|
||||
from ..core.scheduler import scheduler
|
||||
def _bg():
|
||||
@@ -330,7 +332,7 @@ def list_automation_topics(db: Session = Depends(get_db), current_user=Depends(g
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post("/refresh")
|
||||
def refresh_all():
|
||||
def refresh_all(current_user=Depends(get_current_user)):
|
||||
try:
|
||||
sync_all_topics()
|
||||
return {"message": "Refresh completed"}
|
||||
|
||||
@@ -14,6 +14,16 @@ _creator_semaphore = threading.Semaphore(3)
|
||||
router = APIRouter(prefix="/api/tasks", tags=["tasks"])
|
||||
|
||||
|
||||
def _check_task_org(task, current_user, db):
|
||||
"""Check if task's topic belongs to user's org"""
|
||||
if current_user.role == "admin" or not task.topic_id:
|
||||
return True
|
||||
topic = db.query(Topic).filter(Topic.id == task.topic_id).first()
|
||||
if topic and topic.org_id != current_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
return True
|
||||
|
||||
|
||||
@router.get("", response_model=List[ContentTaskResponse])
|
||||
def list_tasks(
|
||||
status: Optional[str] = None,
|
||||
@@ -94,6 +104,7 @@ def get_task(
|
||||
task = db.query(ContentTask).filter(ContentTask.task_id == task_id).first()
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
_check_task_org(task, current_user, db)
|
||||
return task
|
||||
|
||||
|
||||
@@ -106,6 +117,7 @@ def start_task(
|
||||
task = db.query(ContentTask).filter(ContentTask.task_id == task_id).first()
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
_check_task_org(task, current_user, db)
|
||||
|
||||
from datetime import datetime, timezone
|
||||
task.status = "running"
|
||||
@@ -128,6 +140,7 @@ def update_progress(
|
||||
task = db.query(ContentTask).filter(ContentTask.task_id == task_id).first()
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
_check_task_org(task, current_user, db)
|
||||
|
||||
task.progress = progress
|
||||
if message:
|
||||
@@ -148,6 +161,7 @@ def complete_task(
|
||||
task = db.query(ContentTask).filter(ContentTask.task_id == task_id).first()
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
_check_task_org(task, current_user, db)
|
||||
|
||||
from datetime import datetime, timezone
|
||||
finished = datetime.now(timezone.utc)
|
||||
@@ -175,6 +189,7 @@ def fail_task(
|
||||
task = db.query(ContentTask).filter(ContentTask.task_id == task_id).first()
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
_check_task_org(task, current_user, db)
|
||||
|
||||
from datetime import datetime, timezone
|
||||
finished = datetime.now(timezone.utc)
|
||||
@@ -197,6 +212,7 @@ def cancel_task(
|
||||
task = db.query(ContentTask).filter(ContentTask.task_id == task_id).first()
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
_check_task_org(task, current_user, db)
|
||||
|
||||
task.status = "cancelled"
|
||||
db.commit()
|
||||
@@ -406,6 +422,13 @@ def run_creator_task(
|
||||
):
|
||||
from datetime import datetime, timezone
|
||||
|
||||
if topic_id:
|
||||
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="选题不存在")
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
task_id = f"task_{uuid.uuid4().hex[:16]}"
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@ from ..database import get_db
|
||||
from ..models import Topic, TopicField, TopicConfigField, Article, ContentMetrics
|
||||
from ..schemas import (
|
||||
TopicCreate, TopicUpdate, TopicResponse, TopicScoreRequest,
|
||||
TopicAnalyzeRequest, TopicAnalyzeResponse,
|
||||
)
|
||||
from ..core.nvidia_client import call_llm
|
||||
from .auth import get_current_user, org_filter
|
||||
|
||||
router = APIRouter(prefix="/api/topics", tags=["topics"], dependencies=[Depends(get_current_user)])
|
||||
@@ -93,6 +95,65 @@ def topic_stats(
|
||||
}
|
||||
|
||||
|
||||
@router.post("/analyze", response_model=TopicAnalyzeResponse)
|
||||
def analyze_topic(
|
||||
data: TopicAnalyzeRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
"""AI 分析用户输入的选题思路,返回结构化的选题信息"""
|
||||
import json, re
|
||||
from sqlalchemy import text as sa_text
|
||||
|
||||
refs = "\n".join(f"- {link}" for link in data.reference_links) if data.reference_links else "无"
|
||||
prompt_text = f"""你是一个专业的内容策略师。用户提供了一个选题思路,请分析并提炼为结构化的选题信息。
|
||||
|
||||
用户输入的原始内容:
|
||||
{data.raw_input}
|
||||
|
||||
参考链接(如有):
|
||||
{refs}
|
||||
|
||||
请输出 JSON(只输出 JSON,不要其他文字):
|
||||
{{
|
||||
"title": "优化后的选题标题(20字内,含核心关键词,有吸引力)",
|
||||
"format": "内容形式(趋势洞察/实操指南/对比分析/案例解读/观点讨论)",
|
||||
"core_concept": "核心观点(一句话说清独特价值,20字内)",
|
||||
"audience_pain": "受众痛点(目标读者的真实困惑或需求,20字内)",
|
||||
"unique_angle": "差异化切入点(与常见文章不同的视角,20字内)",
|
||||
"tags": ["标签1", "标签2", "标签3", "标签4", "标签5"]
|
||||
}}"""
|
||||
|
||||
try:
|
||||
resp = call_llm(prompt_text, temperature=0.6, max_tokens=1500)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=503, detail=f"AI 分析失败: {str(e)}")
|
||||
|
||||
# Parse JSON from response
|
||||
resp = resp.strip()
|
||||
# Remove markdown code fences if present
|
||||
resp = re.sub(r'^```(?:json)?\s*', '', resp)
|
||||
resp = re.sub(r'\s*```$', '', resp)
|
||||
parsed = json.loads(resp)
|
||||
|
||||
field_name = None
|
||||
if data.field_id:
|
||||
field = db.query(TopicField).filter(TopicField.id == data.field_id).first()
|
||||
if field:
|
||||
field_name = field.name
|
||||
|
||||
return TopicAnalyzeResponse(
|
||||
title=parsed.get("title", ""),
|
||||
format=parsed.get("format", ""),
|
||||
core_concept=parsed.get("core_concept", ""),
|
||||
audience_pain=parsed.get("audience_pain", ""),
|
||||
unique_angle=parsed.get("unique_angle", ""),
|
||||
field_name=field_name,
|
||||
tags=parsed.get("tags", []),
|
||||
priority="中",
|
||||
)
|
||||
|
||||
|
||||
@router.post("", response_model=TopicResponse)
|
||||
def create_topic(
|
||||
data: TopicCreate,
|
||||
@@ -111,7 +172,7 @@ def create_topic(
|
||||
else:
|
||||
topic_id = f"T{datetime.now().strftime('%m%d%H%M')}"
|
||||
|
||||
field_name = None
|
||||
field_name = "未分类"
|
||||
if data.field_id:
|
||||
field = db.query(TopicField).filter(TopicField.id == data.field_id).first()
|
||||
if field:
|
||||
@@ -164,7 +225,7 @@ def update_topic(
|
||||
topic.field_id = data.field_id
|
||||
if data.field_id:
|
||||
field = db.query(TopicField).filter(TopicField.id == data.field_id).first()
|
||||
topic.field_name = field.name if field else None
|
||||
topic.field_name = field.name if field else '未分类'
|
||||
|
||||
for k, v in data.model_dump(exclude_unset=True, exclude={"field_id"}).items():
|
||||
if k == "tags" or k == "custom_data" or k == "scoring_data":
|
||||
|
||||
Reference in New Issue
Block a user