feat: add MMGame backend and frontend source code

This commit is contained in:
gmh01
2026-08-01 21:30:23 +08:00
parent 26ce0dd600
commit c176c04aab
42 changed files with 3606 additions and 0 deletions
+144
View File
@@ -0,0 +1,144 @@
import uuid
from datetime import datetime, timedelta
from jose import jwt, JWTError
from passlib.context import CryptContext
from fastapi import APIRouter, Depends, HTTPException, Header
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from pydantic import BaseModel
from app.database import get_db
from app.models.user import User
from app.models.game import GameSession
from app.config import settings
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
router = APIRouter(prefix="/api/auth", tags=["auth"])
class RegisterRequest(BaseModel):
email: str
password: str
nickname: str
class LoginRequest(BaseModel):
email: str
password: str
class GuestRequest(BaseModel):
nickname: str = "游客"
class AuthResponse(BaseModel):
token: str
user_id: str
nickname: str
is_guest: bool
def create_token(user_id: str) -> str:
expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
return jwt.encode({"sub": user_id, "exp": expire, "v": 0}, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
async def get_current_user(authorization: str = Header(""), db: AsyncSession = Depends(get_db)) -> User:
if not authorization.startswith("Bearer "):
raise HTTPException(401, "未登录")
token = authorization[7:]
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
user_id = payload.get("sub")
except JWTError:
raise HTTPException(401, "无效的token")
result = await db.execute(select(User).where(User.id == user_id))
user = result.scalar_one_or_none()
if not user:
raise HTTPException(401, "用户不存在")
return user
@router.post("/register")
async def register(req: RegisterRequest, db: AsyncSession = Depends(get_db)):
result = await db.execute(select(User).where(User.email == req.email))
if result.scalar_one_or_none():
raise HTTPException(400, "邮箱已注册")
user = User(
id=str(uuid.uuid4()),
email=req.email,
nickname=req.nickname,
password_hash=pwd_context.hash(req.password),
)
db.add(user)
await db.commit()
return AuthResponse(token=create_token(user.id), user_id=user.id, nickname=user.nickname, is_guest=False)
@router.post("/login")
async def login(req: LoginRequest, db: AsyncSession = Depends(get_db)):
result = await db.execute(select(User).where(User.email == req.email))
user = result.scalar_one_or_none()
if not user or not user.password_hash or not pwd_context.verify(req.password, user.password_hash):
raise HTTPException(401, "邮箱或密码错误")
return AuthResponse(token=create_token(user.id), user_id=user.id, nickname=user.nickname, is_guest=False)
@router.post("/guest")
async def guest_login(req: GuestRequest, db: AsyncSession = Depends(get_db)):
user = User(
id=str(uuid.uuid4()),
nickname=req.nickname,
is_guest=True,
)
db.add(user)
await db.commit()
return AuthResponse(token=create_token(user.id), user_id=user.id, nickname=user.nickname, is_guest=True)
@router.post("/logout")
async def logout():
return {"status": "ok"}
@router.get("/profile")
async def get_profile(user: User = Depends(get_current_user)):
return {
"user_id": user.id,
"email": user.email,
"nickname": user.nickname,
"avatar_url": user.avatar_url,
"is_guest": user.is_guest,
"role": user.role,
"game_count": user.game_count,
"created_at": user.created_at.isoformat() if user.created_at else "",
}
@router.put("/profile")
async def update_profile(data: dict, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
if "nickname" in data:
user.nickname = data["nickname"]
if "avatar_url" in data:
user.avatar_url = data["avatar_url"]
await db.commit()
return {"status": "ok"}
@router.get("/history")
async def get_history(user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
result = await db.execute(
select(GameSession).order_by(GameSession.created_at.desc()).limit(50)
)
games = result.scalars().all()
return [
{
"id": g.id,
"script_id": g.script_id,
"status": g.status,
"phase": g.phase,
"started_at": g.started_at.isoformat() if g.started_at else "",
"completed_at": g.completed_at.isoformat() if g.completed_at else "",
"created_at": g.created_at.isoformat(),
}
for g in games
]