328 lines
12 KiB
Python
328 lines
12 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import text
|
|
from typing import Optional, List
|
|
from datetime import datetime
|
|
import json
|
|
import traceback
|
|
|
|
from ...database import SessionLocal, get_db
|
|
from ...auth import get_current_user
|
|
from ...models import User
|
|
|
|
router = APIRouter()
|
|
|
|
def _execute_raw(db: Session, sql: str, params: dict = None):
|
|
"""执行原始SQL的辅助函数"""
|
|
try:
|
|
result = db.execute(text(sql), params or {})
|
|
db.commit()
|
|
return result
|
|
except Exception as e:
|
|
db.rollback()
|
|
raise e
|
|
|
|
@router.get("/achievements")
|
|
async def list_achievements(
|
|
category: Optional[str] = Query(None),
|
|
is_active: Optional[bool] = Query(True),
|
|
db: Session = Depends(get_db),
|
|
user = Depends(get_current_user)
|
|
):
|
|
"""列出所有成就(带用户进度)"""
|
|
try:
|
|
achievements = []
|
|
|
|
# Build query
|
|
sql = "SELECT * FROM achievements WHERE 1=1"
|
|
params = {}
|
|
if category:
|
|
sql += " AND category = :category"
|
|
params["category"] = category
|
|
if is_active is not None:
|
|
sql += " AND is_active = :is_active"
|
|
params["is_active"] = 1 if is_active else 0
|
|
sql += " ORDER BY display_order ASC"
|
|
|
|
result = _execute_raw(db, sql, params)
|
|
rows = result.fetchall()
|
|
|
|
user_id = user.id if hasattr(user, 'id') else None
|
|
|
|
for row in rows:
|
|
ach = {
|
|
"id": row[0],
|
|
"name": row[1],
|
|
"description": row[2],
|
|
"icon": row[3],
|
|
"category": row[4],
|
|
"criteria_type": row[5],
|
|
"criteria_key": row[6],
|
|
"criteria_value": row[7],
|
|
"reward_type": row[8],
|
|
"reward_value": float(row[9]) if row[9] else 0,
|
|
"badge_icon": row[10],
|
|
"is_active": bool(row[11]) if row[11] is not None else True,
|
|
"display_order": row[12],
|
|
"user_progress": 0,
|
|
"is_completed": False,
|
|
"reward_claimed": False
|
|
}
|
|
|
|
# Get user progress if user is logged in
|
|
if user_id:
|
|
try:
|
|
progress_result = _execute_raw(db,
|
|
"SELECT progress, is_completed, reward_claimed FROM user_achievements WHERE user_id = :uid AND achievement_id = :aid",
|
|
{"uid": user_id, "aid": ach["id"]})
|
|
prog_row = progress_result.fetchone()
|
|
if prog_row:
|
|
ach["user_progress"] = prog_row[0] or 0
|
|
ach["is_completed"] = bool(prog_row[1]) if prog_row[1] is not None else False
|
|
ach["reward_claimed"] = bool(prog_row[2]) if prog_row[2] is not None else False
|
|
except:
|
|
pass
|
|
|
|
achievements.append(ach)
|
|
|
|
return {"code": 0, "data": {"items": achievements, "total": len(achievements)}}
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
return {"code": 0, "data": {"items": [], "total": 0}}
|
|
|
|
@router.get("/achievements/{ach_id}")
|
|
async def get_achievement_detail(
|
|
ach_id: str,
|
|
db: Session = Depends(get_db),
|
|
user = Depends(get_current_user)
|
|
):
|
|
"""获取成就详情"""
|
|
try:
|
|
result = _execute_raw(db, "SELECT * FROM achievements WHERE id = :id", {"id": ach_id})
|
|
row = result.fetchone()
|
|
if not row:
|
|
raise HTTPException(status_code=404, detail="成就不存在")
|
|
|
|
achievement = {
|
|
"id": row[0],
|
|
"name": row[1],
|
|
"description": row[2],
|
|
"icon": row[3],
|
|
"category": row[4],
|
|
"criteria_type": row[5],
|
|
"criteria_key": row[6],
|
|
"criteria_value": row[7],
|
|
"reward_type": row[8],
|
|
"reward_value": float(row[9]) if row[9] else 0,
|
|
"badge_icon": row[10],
|
|
"is_active": bool(row[11]) if row[11] is not None else True,
|
|
"display_order": row[12]
|
|
}
|
|
|
|
return {"code": 0, "data": achievement}
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@router.post("/achievements")
|
|
async def create_achievement(
|
|
name: str = Query(...),
|
|
category: str = Query(...),
|
|
criteria_type: str = Query(...),
|
|
criteria_value: int = Query(...),
|
|
description: Optional[str] = Query(""),
|
|
icon: Optional[str] = Query(""),
|
|
criteria_key: Optional[str] = Query(None),
|
|
reward_type: str = Query("points"),
|
|
reward_value: float = Query(0),
|
|
db: Session = Depends(get_db),
|
|
user = Depends(get_current_user)
|
|
):
|
|
"""创建新成就(管理员)"""
|
|
try:
|
|
import uuid
|
|
ach_id = str(uuid.uuid4())
|
|
|
|
sql = """INSERT INTO achievements
|
|
(id, name, description, icon, category, criteria_type, criteria_key,
|
|
criteria_value, reward_type, reward_value, is_active, display_order)
|
|
VALUES (:id, :name, :desc, :icon, :cat, :ctype, :ckey,
|
|
:cvalue, :rtype, :rvalue, 1, 0)"""
|
|
|
|
_execute_raw(db, sql, {
|
|
"id": ach_id,
|
|
"name": name,
|
|
"desc": description,
|
|
"icon": icon,
|
|
"cat": category,
|
|
"ctype": criteria_type,
|
|
"ckey": criteria_key,
|
|
"cvalue": criteria_value,
|
|
"rtype": reward_type,
|
|
"rvalue": reward_value
|
|
})
|
|
|
|
return {"code": 0, "message": "成就创建成功", "data": {"id": ach_id}}
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@router.put("/achievements/{ach_id}")
|
|
async def update_achievement(
|
|
ach_id: str,
|
|
name: Optional[str] = Query(None),
|
|
description: Optional[str] = Query(None),
|
|
is_active: Optional[bool] = Query(None),
|
|
reward_value: Optional[float] = Query(None),
|
|
db: Session = Depends(get_db),
|
|
user = Depends(get_current_user)
|
|
):
|
|
"""更新成就(管理员)"""
|
|
try:
|
|
updates = []
|
|
params = {"id": ach_id}
|
|
|
|
if name is not None:
|
|
updates.append("name = :name")
|
|
params["name"] = name
|
|
if description is not None:
|
|
updates.append("description = :desc")
|
|
params["desc"] = description
|
|
if is_active is not None:
|
|
updates.append("is_active = :active")
|
|
params["active"] = 1 if is_active else 0
|
|
if reward_value is not None:
|
|
updates.append("reward_value = :rvalue")
|
|
params["rvalue"] = reward_value
|
|
|
|
if updates:
|
|
sql = f"UPDATE achievements SET {', '.join(updates)} WHERE id = :id"
|
|
_execute_raw(db, sql, params)
|
|
|
|
return {"code": 0, "message": "更新成功"}
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@router.delete("/achievements/{ach_id}")
|
|
async def delete_achievement(
|
|
ach_id: str,
|
|
db: Session = Depends(get_db),
|
|
user = Depends(get_current_user)
|
|
):
|
|
"""删除成就(管理员)"""
|
|
try:
|
|
_execute_raw(db, "DELETE FROM achievements WHERE id = :id", {"id": ach_id})
|
|
return {"code": 0, "message": "删除成功"}
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@router.get("/user/achievements")
|
|
async def get_my_achievements(
|
|
db: Session = Depends(get_db),
|
|
user = Depends(get_current_user)
|
|
):
|
|
"""获取当前用户的成就进度"""
|
|
try:
|
|
user_id = user.id if hasattr(user, 'id') else None
|
|
if not user_id:
|
|
return {"code": 0, "data": {"items": [], "total": 0}}
|
|
|
|
# Get all achievements with user progress
|
|
result = _execute_raw(db, "SELECT a.*, ua.progress, ua.is_completed, ua.reward_claimed, ua.completed_at FROM achievements a LEFT JOIN user_achievements ua ON a.id = ua.achievement_id AND ua.user_id = :uid WHERE a.is_active = 1 ORDER BY a.display_order ASC", {"uid": user_id})
|
|
|
|
rows = result.fetchall()
|
|
achievements = []
|
|
for row in rows:
|
|
achievements.append({
|
|
"id": row[0],
|
|
"name": row[1],
|
|
"description": row[2],
|
|
"icon": row[3],
|
|
"category": row[4],
|
|
"criteria_type": row[5],
|
|
"criteria_value": row[7],
|
|
"reward_type": row[8],
|
|
"reward_value": float(row[9]) if row[9] else 0,
|
|
"progress": row[13] or 0,
|
|
"is_completed": bool(row[14]) if row[14] is not None else False,
|
|
"reward_claimed": bool(row[15]) if row[15] is not None else False,
|
|
"completed_at": str(row[16]) if row[16] else None
|
|
})
|
|
|
|
return {"code": 0, "data": {"items": achievements, "total": len(achievements)}}
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
return {"code": 0, "data": {"items": [], "total": 0}}
|
|
|
|
@router.post("/user/achievements/{ach_id}/claim")
|
|
async def claim_achievement_reward(
|
|
ach_id: str,
|
|
db: Session = Depends(get_db),
|
|
user = Depends(get_current_user)
|
|
):
|
|
"""领取成就奖励"""
|
|
try:
|
|
user_id = user.id if hasattr(user, 'id') else None
|
|
if not user_id:
|
|
raise HTTPException(status_code=401, detail="未登录")
|
|
|
|
# Check if achievement is completed
|
|
result = _execute_raw(db,
|
|
"SELECT is_completed, reward_claimed FROM user_achievements WHERE user_id = :uid AND achievement_id = :aid",
|
|
{"uid": user_id, "aid": ach_id})
|
|
row = result.fetchone()
|
|
|
|
if not row or not row[0]:
|
|
raise HTTPException(status_code=400, detail="成就未完成")
|
|
|
|
if row[1]:
|
|
raise HTTPException(status_code=400, detail="奖励已领取")
|
|
|
|
# Mark reward as claimed
|
|
_execute_raw(db,
|
|
"UPDATE user_achievements SET reward_claimed = 1, claimed_at = NOW() WHERE user_id = :uid AND achievement_id = :aid",
|
|
{"uid": user_id, "aid": ach_id})
|
|
|
|
return {"code": 0, "message": "奖励领取成功"}
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@router.post("/check-progress")
|
|
async def check_user_progress(
|
|
db: Session = Depends(get_db),
|
|
user = Depends(get_current_user)
|
|
):
|
|
"""检查并更新用户成就进度"""
|
|
try:
|
|
user_id = user.id if hasattr(user, 'id') else None
|
|
if not user_id:
|
|
raise HTTPException(status_code=401, detail="未登录")
|
|
|
|
# Get user stats (this would need to be implemented based on actual data)
|
|
# For now, just return success
|
|
return {"code": 0, "message": "进度检查完成", "data": {"updated": 0}}
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@router.get("/leaderboard")
|
|
async def get_achievement_leaderboard(
|
|
period: str = Query("all"),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""获取成就排行榜"""
|
|
try:
|
|
# This would aggregate user achievements and rank them
|
|
# For now, return empty
|
|
return {"code": 0, "data": {"items": []}}
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
return {"code": 0, "data": {"items": []}}
|