"""Yijing (易经) APIs, ported from the legacy Flask server to the unified FastAPI service. All protected endpoints use the same Bearer JWT as the rest of the service, and the user_id is sourced from the authenticated auth_users row (old cookie sessions are handled on the H5 side by shrinking down to the token-less /api/auth/verify fallback before reaching these APIs). """ from datetime import datetime, timedelta from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from .. import db from .auth import get_current_user router = APIRouter(prefix="/api/v1/yijing", tags=["yijing"]) @router.get("/catalog") def yijing_catalog(): """Public catalog: trigrams + hexagrams, mirroring the legacy endpoint.""" trigrams = db.fetch_all( "SELECT id,name,symbol,nature,de,family,direction,xiantian,houtian,binary_code,class_xiang,summary " "FROM yijing_trigrams ORDER BY xiantian" ) hexagrams = db.fetch_all( "SELECT id,name,full_name,symbol,upper_trigram,lower_trigram,binary_code,judgment,gua_explanation,bagong,gong_wuxing " "FROM yijing_hexagrams ORDER BY id" ) return {"success": True, "trigrams": trigrams, "hexagrams": hexagrams} class ProgressUpdateRequest(BaseModel): stage: int item_type: str item_id: int status: str = "learning" @router.get("/progress") def yijing_progress(user=Depends(get_current_user)): rows = db.fetch_all( "SELECT stage,item_type,item_id,status,score,attempts,correct " "FROM yijing_progress WHERE user_id=%s", (user["id"],), ) by = {} for r in rows: by.setdefault(r["stage"], {})[ f'{r["item_type"]}:{r["item_id"]}' ] = { "status": r["status"], "score": r["score"], "attempts": r["attempts"], "correct": r["correct"], } return {"success": True, "progress": by} @router.post("/progress") def yijing_progress_update(req: ProgressUpdateRequest, user=Depends(get_current_user)): if not (1 <= req.stage <= 5) or not req.item_type or req.item_id <= 0: raise HTTPException(status_code=400, detail="参数错误") db.execute( "INSERT INTO yijing_progress (user_id,stage,item_type,item_id,status) " "VALUES (%s,%s,%s,%s,%s) ON DUPLICATE KEY UPDATE status=VALUES(status)", (user["id"], req.stage, req.item_type, req.item_id, req.status), ) return {"success": True} class QuizRequest(BaseModel): stage: int item_type: str item_id: int correct: bool = False answer: str = "" @router.post("/quiz") def yijing_quiz(req: QuizRequest, user=Depends(get_current_user)): if not (1 <= req.stage <= 5) or not req.item_type or req.item_id <= 0: raise HTTPException(status_code=400, detail="参数错误") correct = 1 if req.correct else 0 answer = req.answer[:500] db.execute( "INSERT INTO yijing_attempts (user_id,stage,item_type,item_id,correct,answer) " "VALUES (%s,%s,%s,%s,%s,%s)", (user["id"], req.stage, req.item_type, req.item_id, correct, answer), ) row = db.fetch_one( "SELECT score,correct FROM yijing_progress " "WHERE user_id=%s AND stage=%s AND item_type=%s AND item_id=%s", (user["id"], req.stage, req.item_type, req.item_id), ) if not row: db.execute( "INSERT INTO yijing_progress (user_id,stage,item_type,item_id,status,score,attempts,correct) " "VALUES (%s,%s,%s,%s,'learning',%s,1,%s)", (user["id"], req.stage, req.item_type, req.item_id, correct, correct), ) else: db.execute( "UPDATE yijing_progress SET attempts=attempts+1, correct=correct+%s, " "score=LEAST(correct+%s,2) WHERE user_id=%s AND stage=%s AND item_type=%s AND item_id=%s", (correct, correct, user["id"], req.stage, req.item_type, req.item_id), ) row = db.fetch_one( "SELECT score,correct FROM yijing_progress " "WHERE user_id=%s AND stage=%s AND item_type=%s AND item_id=%s", (user["id"], req.stage, req.item_type, req.item_id), ) mastered = False if row and row["score"] >= 2: db.execute( "UPDATE yijing_progress SET status='mastered' " "WHERE user_id=%s AND stage=%s AND item_type=%s AND item_id=%s", (user["id"], req.stage, req.item_type, req.item_id), ) mastered = True due = datetime.now() + (timedelta(days=1) if correct else timedelta(hours=1)) db.execute( "INSERT INTO yijing_reviews (user_id,item_type,item_id,due_at) VALUES (%s,%s,%s,%s) " "ON DUPLICATE KEY UPDATE due_at=VALUES(due_at)", (user["id"], req.item_type, req.item_id, due), ) return {"success": True, "mastered": mastered, "score": row["score"] if row else 0}