Files
InsightRadar/backend/app/api/endpoints/preferences.py
T
stardrophere 3d7d53f96f update
2026-03-12 13:00:10 +08:00

163 lines
4.9 KiB
Python

from typing import List
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from app.api.dependencies import get_current_user, get_db
from app.models.models import AppUser, UserTopicPreference
from app.schemas.preference_schema import (
MatchedEventResponse,
UserPreferenceRecommendationResponse,
UserTopicPreferenceCreate,
UserTopicPreferenceResponse,
)
from app.services.matching_service import recommend_events_for_user
router = APIRouter()
def _ensure_self_access(path_user_id: int, current_user: AppUser) -> None:
"""校验路径 user_id 是否为当前登录用户本人。"""
if path_user_id != current_user.id:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You can only operate your own resources",
)
@router.get(
"/users/{user_id}/preferences",
response_model=List[UserTopicPreferenceResponse],
)
def list_user_preferences(
user_id: int,
db: Session = Depends(get_db),
current_user: AppUser = Depends(get_current_user),
):
"""获取用户已设置的兴趣关键词。"""
_ensure_self_access(user_id, current_user)
preferences = (
db.query(UserTopicPreference)
.filter(UserTopicPreference.user_id == user_id)
.order_by(UserTopicPreference.created_at.desc())
.all()
)
return preferences
@router.post(
"/users/{user_id}/preferences",
response_model=UserTopicPreferenceResponse,
status_code=status.HTTP_201_CREATED,
)
def create_user_preference(
user_id: int,
payload: UserTopicPreferenceCreate,
db: Session = Depends(get_db),
current_user: AppUser = Depends(get_current_user),
):
"""新增一个用户兴趣关键词。"""
_ensure_self_access(user_id, current_user)
keyword = payload.interested_keyword.strip()
if not keyword:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Keyword cannot be empty")
db_obj = UserTopicPreference(
user_id=user_id,
interested_keyword=keyword,
)
db.add(db_obj)
try:
db.commit()
except IntegrityError:
db.rollback()
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Preference keyword already exists for this user",
)
db.refresh(db_obj)
return db_obj
@router.delete(
"/users/{user_id}/preferences/{preference_id}",
status_code=status.HTTP_204_NO_CONTENT,
)
def delete_user_preference(
user_id: int,
preference_id: int,
db: Session = Depends(get_db),
current_user: AppUser = Depends(get_current_user),
):
"""删除一个用户兴趣关键词。"""
_ensure_self_access(user_id, current_user)
preference = (
db.query(UserTopicPreference)
.filter(
UserTopicPreference.id == preference_id,
UserTopicPreference.user_id == user_id,
)
.first()
)
if not preference:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Preference not found")
db.delete(preference)
db.commit()
return None
@router.get(
"/users/{user_id}/recommended-events",
response_model=UserPreferenceRecommendationResponse,
)
def recommend_events(
user_id: int,
min_hot: int = Query(3, ge=1, description="最小热度阈值"),
hours: int = Query(72, ge=1, le=24 * 30, description="仅匹配最近多少小时的事件"),
limit: int = Query(20, ge=1, le=50, description="最多返回多少条推荐"),
semantic_threshold: float = Query(0.78, ge=0.0, le=1.0, description="语义匹配相似度阈值"),
sort_by: str = Query("match_score", description="排序方式: match_score | created_at"),
db: Session = Depends(get_db),
current_user: AppUser = Depends(get_current_user),
):
"""基于用户兴趣词推荐事件(精确匹配 + 语义匹配)。"""
_ensure_self_access(user_id, current_user)
matched = recommend_events_for_user(
db,
user_id=user_id,
min_hot=min_hot,
hours=hours,
limit=limit,
semantic_threshold=semantic_threshold,
)
if sort_by == "created_at":
matched.sort(key=lambda x: x.event.created_at, reverse=True)
result_data: list[MatchedEventResponse] = []
for item in matched:
result_data.append(
MatchedEventResponse(
event_id=item.event.id,
unified_title=item.event.unified_title,
summary=item.event.ai_comprehensive_summary,
hot_score=item.event.hot_score,
created_at=item.event.created_at,
tags=item.tags,
match_score=item.match_score,
exact_hits=item.exact_hits,
semantic_hits=item.semantic_hits,
)
)
return UserPreferenceRecommendationResponse(
user_id=user_id,
total=len(result_data),
data=result_data,
)