Initial commit: WordLoop 单词学习应用
Vue 前端 + FastAPI 后端,含部署脚本与词典数据。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from auth import create_access_token, get_current_user, hash_password, verify_password
|
||||
from database import get_db
|
||||
from models import User, UserSettings
|
||||
from schemas import TokenResponse, UserLogin, UserOut, UserRegister
|
||||
|
||||
router = APIRouter(prefix="/api/auth", tags=["auth"])
|
||||
|
||||
|
||||
def utc_now_iso() -> str:
|
||||
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
|
||||
@router.post("/register", response_model=UserOut)
|
||||
def register(data: UserRegister, db: Session = Depends(get_db)):
|
||||
if db.query(User).filter(User.username == data.username).first():
|
||||
raise HTTPException(status_code=400, detail="用户名已存在")
|
||||
|
||||
user = User(
|
||||
username=data.username,
|
||||
password_hash=hash_password(data.password),
|
||||
created_at=utc_now_iso(),
|
||||
)
|
||||
db.add(user)
|
||||
db.flush()
|
||||
|
||||
settings = UserSettings(
|
||||
user_id=user.id,
|
||||
daily_target=20,
|
||||
master_required_count=3,
|
||||
weak_wrong_threshold=3,
|
||||
)
|
||||
db.add(settings)
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
return user
|
||||
|
||||
|
||||
@router.post("/login", response_model=TokenResponse)
|
||||
def login(data: UserLogin, db: Session = Depends(get_db)):
|
||||
user = db.query(User).filter(User.username == data.username).first()
|
||||
if not user or not verify_password(data.password, user.password_hash):
|
||||
raise HTTPException(status_code=401, detail="用户名或密码错误")
|
||||
|
||||
token = create_access_token(user.id, user.username)
|
||||
return TokenResponse(access_token=token)
|
||||
|
||||
|
||||
@router.get("/me", response_model=UserOut)
|
||||
def me(current_user: User = Depends(get_current_user)):
|
||||
return current_user
|
||||
@@ -0,0 +1,49 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from auth import get_current_user
|
||||
from database import get_db
|
||||
from models import User
|
||||
from schemas import (
|
||||
DailyQuizResponse,
|
||||
QuizAnswerRequest,
|
||||
QuizAnswerResponse,
|
||||
QuizStatsResponse,
|
||||
)
|
||||
from services.quiz_service import quiz_service
|
||||
|
||||
router = APIRouter(prefix="/api/quiz", tags=["quiz"])
|
||||
|
||||
|
||||
@router.get("/daily", response_model=DailyQuizResponse)
|
||||
def daily_quiz(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
result = quiz_service.get_daily_quiz(db, current_user)
|
||||
return DailyQuizResponse(**result)
|
||||
|
||||
|
||||
@router.post("/answer", response_model=QuizAnswerResponse)
|
||||
def submit_answer(
|
||||
data: QuizAnswerRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
result = quiz_service.submit_answer(
|
||||
db,
|
||||
current_user,
|
||||
data.word_id,
|
||||
data.question_type,
|
||||
data.user_answer,
|
||||
data.correct_answer,
|
||||
)
|
||||
return QuizAnswerResponse(**result)
|
||||
|
||||
|
||||
@router.get("/stats", response_model=QuizStatsResponse)
|
||||
def quiz_stats(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
return quiz_service.get_stats(db, current_user)
|
||||
@@ -0,0 +1,45 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from auth import get_current_user
|
||||
from database import get_db
|
||||
from models import User, UserSettings
|
||||
from schemas import SettingsOut, SettingsUpdate
|
||||
from services.quiz_service import quiz_service
|
||||
|
||||
router = APIRouter(prefix="/api/settings", tags=["settings"])
|
||||
|
||||
|
||||
@router.get("", response_model=SettingsOut)
|
||||
def get_settings(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
s = quiz_service.get_settings(db, current_user)
|
||||
return SettingsOut(
|
||||
daily_target=s.daily_target,
|
||||
master_required_count=s.master_required_count,
|
||||
weak_wrong_threshold=s.weak_wrong_threshold,
|
||||
)
|
||||
|
||||
|
||||
@router.patch("", response_model=SettingsOut)
|
||||
def update_settings(
|
||||
data: SettingsUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
s = quiz_service.get_settings(db, current_user)
|
||||
if data.daily_target is not None:
|
||||
s.daily_target = data.daily_target
|
||||
if data.master_required_count is not None:
|
||||
s.master_required_count = data.master_required_count
|
||||
if data.weak_wrong_threshold is not None:
|
||||
s.weak_wrong_threshold = data.weak_wrong_threshold
|
||||
db.commit()
|
||||
db.refresh(s)
|
||||
return SettingsOut(
|
||||
daily_target=s.daily_target,
|
||||
master_required_count=s.master_required_count,
|
||||
weak_wrong_threshold=s.weak_wrong_threshold,
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from auth import get_current_user
|
||||
from database import get_db
|
||||
from models import User
|
||||
from schemas import TranslateRequest, TranslateResponse
|
||||
from services.translation_service import translation_service
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["translate"])
|
||||
|
||||
|
||||
@router.post("/translate", response_model=TranslateResponse)
|
||||
def translate(
|
||||
data: TranslateRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
result = translation_service.translate(db, data.text)
|
||||
return TranslateResponse(**result)
|
||||
@@ -0,0 +1,60 @@
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from auth import get_current_user
|
||||
from database import get_db
|
||||
from models import User
|
||||
from schemas import WordCreate, WordOut, WordUpdate
|
||||
from services.word_service import word_service
|
||||
|
||||
router = APIRouter(prefix="/api/words", tags=["words"])
|
||||
|
||||
|
||||
@router.post("", response_model=WordOut)
|
||||
def create_word(
|
||||
data: WordCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
word = word_service.create_word(db, current_user, data.model_dump())
|
||||
return word
|
||||
|
||||
|
||||
@router.get("", response_model=list[WordOut])
|
||||
def list_words(
|
||||
status: Optional[str] = None,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
return word_service.list_words(db, current_user, status)
|
||||
|
||||
|
||||
@router.get("/{word_id}", response_model=WordOut)
|
||||
def get_word(
|
||||
word_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
return word_service.get_word(db, current_user, word_id)
|
||||
|
||||
|
||||
@router.delete("/{word_id}")
|
||||
def delete_word(
|
||||
word_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
word_service.delete_word(db, current_user, word_id)
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.patch("/{word_id}", response_model=WordOut)
|
||||
def update_word(
|
||||
word_id: int,
|
||||
data: WordUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
return word_service.update_word(db, current_user, word_id, data.model_dump(exclude_unset=True))
|
||||
Reference in New Issue
Block a user