90 lines
3 KiB
Python
90 lines
3 KiB
Python
from typing import Optional
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException, status
|
||
from pydantic import BaseModel, Field
|
||
from sqlalchemy import select, text
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from config import settings
|
||
from deps import get_current_user
|
||
from models import Character, Skill, User, get_db
|
||
|
||
router = APIRouter(prefix="/character", tags=["character"])
|
||
|
||
|
||
class CharacterCreateIn(BaseModel):
|
||
name: str = Field(min_length=3, max_length=32, pattern=r"^[A-Za-z0-9_]+$")
|
||
|
||
|
||
class SkillOut(BaseModel):
|
||
skill_name: str
|
||
xp: int
|
||
level: int
|
||
|
||
|
||
class CharacterOut(BaseModel):
|
||
id: int
|
||
name: str
|
||
level: int
|
||
total_xp: int
|
||
gold: int
|
||
step_balance: int
|
||
step_lifetime: int
|
||
location_code: Optional[str] = None
|
||
location_name: Optional[str] = None
|
||
has_pending_event: bool = False
|
||
skills: list[SkillOut]
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
|
||
async def _with_location(db: AsyncSession, char: Character) -> CharacterOut:
|
||
out = CharacterOut.model_validate(char, from_attributes=True)
|
||
out.has_pending_event = char.pending_event is not None
|
||
if char.location_id is not None:
|
||
row = (await db.execute(text(
|
||
"SELECT code, name FROM locations WHERE id = :i"
|
||
), {"i": char.location_id})).first()
|
||
if row:
|
||
out.location_code, out.location_name = row[0], row[1]
|
||
return out
|
||
|
||
|
||
@router.post("", response_model=CharacterOut, status_code=status.HTTP_201_CREATED)
|
||
async def create_character(data: CharacterCreateIn, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
||
exists = (await db.execute(select(Character).where(Character.user_id == user.id))).scalar_one_or_none()
|
||
if exists:
|
||
raise HTTPException(409, "Zaten bir karakterin var")
|
||
|
||
name_taken = (await db.execute(select(Character).where(Character.name == data.name))).scalar_one_or_none()
|
||
if name_taken:
|
||
raise HTTPException(409, "Bu isim alınmış")
|
||
|
||
char = Character(user_id=user.id, name=data.name)
|
||
db.add(char)
|
||
await db.flush()
|
||
|
||
for skill_name in settings.SKILL_NAMES:
|
||
db.add(Skill(character_id=char.id, skill_name=skill_name, xp=0, level=1))
|
||
|
||
# Başlangıç lokasyonu: köy. Not: başlangıç tool'u YOK —
|
||
# ilk tool'lar köyde elle toplanan taş/sopa/sarmaşıktan craft edilir.
|
||
start = (await db.execute(text(
|
||
"SELECT id FROM locations WHERE type = 'village' ORDER BY id LIMIT 1"
|
||
))).first()
|
||
if start:
|
||
char.location_id = start[0]
|
||
|
||
await db.commit()
|
||
await db.refresh(char, ["skills"])
|
||
return await _with_location(db, char)
|
||
|
||
|
||
@router.get("/me", response_model=CharacterOut)
|
||
async def my_character(user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
||
char = (await db.execute(select(Character).where(Character.user_id == user.id))).scalar_one_or_none()
|
||
if not char:
|
||
raise HTTPException(404, "Karakter yok, önce oluştur")
|
||
await db.refresh(char, ["skills"])
|
||
return await _with_location(db, char)
|