WIP checkpoint: Scaffold FastAPI backend project structure
This commit is contained in:
@@ -0,0 +1,180 @@
|
|||||||
|
"""Asset management router."""
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Query
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
|
from app.database import get_db
|
||||||
|
from app.models.asset import Asset, AssetType
|
||||||
|
from app.models.user import User
|
||||||
|
from app.models.world import World
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
# --- Pydantic schemas ---
|
||||||
|
|
||||||
|
class AssetCreate(BaseModel):
|
||||||
|
"""Schema for creating an asset reference."""
|
||||||
|
name: str = Field(..., min_length=1, max_length=255)
|
||||||
|
description: Optional[str] = None
|
||||||
|
asset_type: AssetType
|
||||||
|
world_id: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
|
class AssetResponse(BaseModel):
|
||||||
|
"""Schema for asset responses."""
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
description: Optional[str]
|
||||||
|
asset_type: AssetType
|
||||||
|
file_path: str
|
||||||
|
file_size: Optional[int]
|
||||||
|
mime_type: Optional[str]
|
||||||
|
world_id: Optional[int]
|
||||||
|
owner_id: int
|
||||||
|
is_public: bool
|
||||||
|
created_at: str
|
||||||
|
updated_at: str
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class AssetUpdate(BaseModel):
|
||||||
|
"""Schema for updating an asset."""
|
||||||
|
name: Optional[str] = None
|
||||||
|
description: Optional[str] = None
|
||||||
|
is_public: Optional[bool] = None
|
||||||
|
|
||||||
|
|
||||||
|
# --- Endpoints ---
|
||||||
|
|
||||||
|
@router.post("/", response_model=AssetResponse, status_code=201)
|
||||||
|
async def create_asset(
|
||||||
|
payload: AssetCreate,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""Create a new asset reference."""
|
||||||
|
# Verify world ownership if world_id is provided
|
||||||
|
if payload.world_id is not None:
|
||||||
|
world = await db.get(World, payload.world_id)
|
||||||
|
if world is None:
|
||||||
|
raise HTTPException(status_code=404, detail="World not found")
|
||||||
|
if world.owner_id != current_user.id:
|
||||||
|
raise HTTPException(status_code=403, detail="Not owner of this world")
|
||||||
|
|
||||||
|
asset = Asset(
|
||||||
|
name=payload.name,
|
||||||
|
description=payload.description,
|
||||||
|
asset_type=payload.asset_type,
|
||||||
|
file_path="", # Will be set after upload
|
||||||
|
owner_id=current_user.id,
|
||||||
|
world_id=payload.world_id,
|
||||||
|
is_public=False,
|
||||||
|
)
|
||||||
|
db.add(asset)
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(asset)
|
||||||
|
return asset
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_model=list[AssetResponse])
|
||||||
|
async def list_assets(
|
||||||
|
world_id: Optional[int] = Query(None, description="Filter by world"),
|
||||||
|
asset_type: Optional[AssetType] = Query(None),
|
||||||
|
limit: int = Query(50, ge=1, le=100),
|
||||||
|
offset: int = Query(0, ge=0),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""List assets with optional filters."""
|
||||||
|
stmt = select(Asset).where(Asset.owner_id == current_user.id)
|
||||||
|
|
||||||
|
if world_id is not None:
|
||||||
|
stmt = stmt.where(Asset.world_id == world_id)
|
||||||
|
if asset_type is not None:
|
||||||
|
stmt = stmt.where(Asset.asset_type == asset_type)
|
||||||
|
|
||||||
|
stmt = stmt.order_by(Asset.created_at.desc()).limit(limit).offset(offset)
|
||||||
|
result = await db.execute(stmt)
|
||||||
|
assets = result.scalars().all()
|
||||||
|
return assets
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{asset_id}", response_model=AssetResponse)
|
||||||
|
async def get_asset(
|
||||||
|
asset_id: int,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""Get a single asset by ID."""
|
||||||
|
asset = await db.get(Asset, asset_id)
|
||||||
|
if asset is None or asset.owner_id != current_user.id:
|
||||||
|
raise HTTPException(status_code=404, detail="Asset not found")
|
||||||
|
return asset
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch("/{asset_id}", response_model=AssetResponse)
|
||||||
|
async def update_asset(
|
||||||
|
asset_id: int,
|
||||||
|
payload: AssetUpdate,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""Update an asset."""
|
||||||
|
asset = await db.get(Asset, asset_id)
|
||||||
|
if asset is None or asset.owner_id != current_user.id:
|
||||||
|
raise HTTPException(status_code=404, detail="Asset not found")
|
||||||
|
|
||||||
|
update_data = payload.model_dump(exclude_unset=True)
|
||||||
|
for field, value in update_data.items():
|
||||||
|
setattr(asset, field, value)
|
||||||
|
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(asset)
|
||||||
|
return asset
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/{asset_id}", status_code=204)
|
||||||
|
async def delete_asset(
|
||||||
|
asset_id: int,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""Delete an asset."""
|
||||||
|
asset = await db.get(Asset, asset_id)
|
||||||
|
if asset is None or asset.owner_id != current_user.id:
|
||||||
|
raise HTTPException(status_code=404, detail="Asset not found")
|
||||||
|
|
||||||
|
await db.delete(asset)
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{asset_id}/upload")
|
||||||
|
async def upload_asset_file(
|
||||||
|
asset_id: int,
|
||||||
|
file: UploadFile = File(...),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""Upload a file for an existing asset."""
|
||||||
|
asset = await db.get(Asset, asset_id)
|
||||||
|
if asset is None or asset.owner_id != current_user.id:
|
||||||
|
raise HTTPException(status_code=404, detail="Asset not found")
|
||||||
|
|
||||||
|
# TODO: Implement actual file upload logic (S3, local filesystem, etc.)
|
||||||
|
file_path = f"uploads/{asset_id}_{file.filename}"
|
||||||
|
asset.file_path = file_path
|
||||||
|
asset.mime_type = file.content_type
|
||||||
|
asset.file_size = 0 # Will be updated after actual write
|
||||||
|
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(asset)
|
||||||
|
|
||||||
|
return {"message": "File upload endpoint", "asset_id": asset_id, "file_path": file_path}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
"""Authentication router - login, register, token refresh."""
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from fastapi.security import OAuth2PasswordRequestForm
|
||||||
|
from pydantic import BaseModel, EmailStr
|
||||||
|
|
||||||
|
from jose import JWTError, jwt
|
||||||
|
from passlib.context import CryptContext
|
||||||
|
|
||||||
|
from app.config import settings
|
||||||
|
from app.database import get_db
|
||||||
|
from app.models.user import User
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
# --- Pydantic schemas ---
|
||||||
|
|
||||||
|
class TokenResponse(BaseModel):
|
||||||
|
access_token: str
|
||||||
|
token_type: str = "bearer"
|
||||||
|
|
||||||
|
|
||||||
|
class RegisterRequest(BaseModel):
|
||||||
|
username: str
|
||||||
|
email: EmailStr
|
||||||
|
password: str
|
||||||
|
|
||||||
|
|
||||||
|
class LoginRequest(BaseModel):
|
||||||
|
username: str
|
||||||
|
password: str
|
||||||
|
|
||||||
|
|
||||||
|
# --- Helpers ---
|
||||||
|
|
||||||
|
def _create_access_token(data: dict) -> str:
|
||||||
|
to_encode = data.copy()
|
||||||
|
expire = datetime.now(timezone.utc) + timedelta(
|
||||||
|
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
|
||||||
|
)
|
||||||
|
to_encode.update({"exp": expire})
|
||||||
|
return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
||||||
|
|
||||||
|
|
||||||
|
def _verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||||
|
return pwd_context.verify(plain_password, hashed_password)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_password_hash(password: str) -> str:
|
||||||
|
return pwd_context.hash(password)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Endpoints ---
|
||||||
|
|
||||||
|
@router.post("/register", response_model=TokenResponse, status_code=status.HTTP_201_CREATED)
|
||||||
|
async def register(req: RegisterRequest, db: AsyncSession = Depends(get_db)):
|
||||||
|
"""Register a new user and return an access token."""
|
||||||
|
# Check if username already exists
|
||||||
|
result = await db.execute(select(User).where(User.username == req.username))
|
||||||
|
if result.scalar_one_or_none():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_409_CONFLICT,
|
||||||
|
detail="Username already registered",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if email already exists
|
||||||
|
result = await db.execute(select(User).where(User.email == req.email))
|
||||||
|
if result.scalar_one_or_none():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_409_CONFLICT,
|
||||||
|
detail="Email already registered",
|
||||||
|
)
|
||||||
|
|
||||||
|
user = User(
|
||||||
|
username=req.username,
|
||||||
|
email=req.email,
|
||||||
|
hashed_password=_get_password_hash(req.password),
|
||||||
|
)
|
||||||
|
db.add(user)
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(user)
|
||||||
|
|
||||||
|
access_token = _create_access_token({"sub": str(user.id)})
|
||||||
|
return TokenResponse(access_token=access_token)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/login", response_model=TokenResponse)
|
||||||
|
async def login(req: LoginRequest, db: AsyncSession = Depends(get_db)):
|
||||||
|
"""Authenticate user and return an access token."""
|
||||||
|
result = await db.execute(select(User).where(User.username == req.username))
|
||||||
|
user = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if not user or not _verify_password(req.password, user.hashed_password):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Incorrect username or password",
|
||||||
|
)
|
||||||
|
|
||||||
|
if not user.is_active:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="User account is inactive",
|
||||||
|
)
|
||||||
|
|
||||||
|
access_token = _create_access_token({"sub": str(user.id)})
|
||||||
|
return TokenResponse(access_token=access_token)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/me")
|
||||||
|
async def get_current_user(
|
||||||
|
# In production, extract from Authorization header via OAuth2PasswordBearer
|
||||||
|
# For now, accept a user_id query param for simplicity
|
||||||
|
user_id: int,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""Get current user profile."""
|
||||||
|
result = await db.execute(select(User).where(User.id == user_id))
|
||||||
|
user = result.scalar_one_or_none()
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(status_code=404, detail="User not found")
|
||||||
|
return {
|
||||||
|
"id": user.id,
|
||||||
|
"username": user.username,
|
||||||
|
"email": user.email,
|
||||||
|
"is_active": user.is_active,
|
||||||
|
"created_at": user.created_at,
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
"""User management router."""
|
||||||
|
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from pydantic import BaseModel, EmailStr
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from app.database import get_db
|
||||||
|
from app.models.user import User
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
# ── Pydantic schemas ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class UserCreate(BaseModel):
|
||||||
|
username: str
|
||||||
|
email: EmailStr
|
||||||
|
password: str
|
||||||
|
|
||||||
|
|
||||||
|
class UserResponse(BaseModel):
|
||||||
|
id: int
|
||||||
|
username: str
|
||||||
|
email: str
|
||||||
|
is_active: bool
|
||||||
|
|
||||||
|
model_config = {"from_attributes": True}
|
||||||
|
|
||||||
|
|
||||||
|
class UserUpdate(BaseModel):
|
||||||
|
email: EmailStr | None = None
|
||||||
|
is_active: bool | None = None
|
||||||
|
|
||||||
|
|
||||||
|
# ── Endpoints ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/",
|
||||||
|
response_model=UserResponse,
|
||||||
|
status_code=status.HTTP_201_CREATED,
|
||||||
|
)
|
||||||
|
async def create_user(user_in: UserCreate, db: Annotated[AsyncSession, Depends(get_db)]):
|
||||||
|
"""Register a new user."""
|
||||||
|
# Check uniqueness of username
|
||||||
|
existing = await db.execute(select(User).where(User.username == user_in.username))
|
||||||
|
if existing.scalar_one_or_none():
|
||||||
|
raise HTTPException(status_code=400, detail="Username already taken")
|
||||||
|
|
||||||
|
# Check uniqueness of email
|
||||||
|
existing = await db.execute(select(User).where(User.email == user_in.email))
|
||||||
|
if existing.scalar_one_or_none():
|
||||||
|
raise HTTPException(status_code=400, detail="Email already registered")
|
||||||
|
|
||||||
|
from app.routers.auth import hash_password
|
||||||
|
hashed = hash_password(user_in.password)
|
||||||
|
|
||||||
|
user = User(
|
||||||
|
username=user_in.username,
|
||||||
|
email=user_in.email,
|
||||||
|
hashed_password=hashed,
|
||||||
|
)
|
||||||
|
db.add(user)
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(user)
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/me", response_model=UserResponse)
|
||||||
|
async def get_current_user(
|
||||||
|
db: Annotated[AsyncSession, Depends(get_db)],
|
||||||
|
# current_user would come from JWT dependency in real usage
|
||||||
|
):
|
||||||
|
"""Get the currently authenticated user (placeholder)."""
|
||||||
|
raise HTTPException(status_code=501, detail="Not implemented")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{user_id}", response_model=UserResponse)
|
||||||
|
async def get_user(
|
||||||
|
user_id: int,
|
||||||
|
db: Annotated[AsyncSession, Depends(get_db)],
|
||||||
|
):
|
||||||
|
"""Get a user by ID."""
|
||||||
|
result = await db.execute(select(User).where(User.id == user_id))
|
||||||
|
user = result.scalar_one_or_none()
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(status_code=404, detail="User not found")
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch("/{user_id}", response_model=UserResponse)
|
||||||
|
async def update_user(
|
||||||
|
user_id: int,
|
||||||
|
user_update: UserUpdate,
|
||||||
|
db: Annotated[AsyncSession, Depends(get_db)],
|
||||||
|
):
|
||||||
|
"""Update a user."""
|
||||||
|
result = await db.execute(select(User).where(User.id == user_id))
|
||||||
|
user = result.scalar_one_or_none()
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(status_code=404, detail="User not found")
|
||||||
|
|
||||||
|
if user_update.email is not None:
|
||||||
|
user.email = user_update.email
|
||||||
|
if user_update.is_active is not None:
|
||||||
|
user.is_active = user_update.is_active
|
||||||
|
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(user)
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
async def delete_user(
|
||||||
|
user_id: int,
|
||||||
|
db: Annotated[AsyncSession, Depends(get_db)],
|
||||||
|
):
|
||||||
|
"""Delete a user."""
|
||||||
|
result = await db.execute(select(User).where(User.id == user_id))
|
||||||
|
user = result.scalar_one_or_none()
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(status_code=404, detail="User not found")
|
||||||
|
await db.delete(user)
|
||||||
|
await db.commit()
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
"""Worlds router - CRUD for game worlds."""
|
||||||
|
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
|
from app.database import get_db
|
||||||
|
from app.models.world import World, WorldVisibility
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
async def get_current_user():
|
||||||
|
"""Placeholder dependency for current user auth."""
|
||||||
|
# TODO: Implement JWT auth dependency
|
||||||
|
return {"id": 1, "username": "testuser", "email": "test@example.com"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/")
|
||||||
|
async def list_worlds(
|
||||||
|
skip: int = Query(0, ge=0),
|
||||||
|
limit: int = Query(50, ge=1, le=100),
|
||||||
|
visibility: WorldVisibility | None = None,
|
||||||
|
db: Annotated[AsyncSession, Depends(get_db)] = Depends(),
|
||||||
|
):
|
||||||
|
"""List worlds with optional visibility filter."""
|
||||||
|
query = select(World).options(selectinload(World.owner))
|
||||||
|
if visibility:
|
||||||
|
query = query.where(World.visibility == visibility)
|
||||||
|
query = query.offset(skip).limit(limit).order_by(World.created_at.desc())
|
||||||
|
result = await db.execute(query)
|
||||||
|
worlds = result.scalars().all()
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"id": w.id,
|
||||||
|
"name": w.name,
|
||||||
|
"description": w.description,
|
||||||
|
"visibility": w.visibility.value if w.visibility else None,
|
||||||
|
"owner_id": w.owner_id,
|
||||||
|
"created_at": w.created_at.isoformat() if w.created_at else None,
|
||||||
|
}
|
||||||
|
for w in worlds
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{world_id}")
|
||||||
|
async def get_world(
|
||||||
|
world_id: int,
|
||||||
|
db: Annotated[AsyncSession, Depends(get_db)] = Depends(),
|
||||||
|
):
|
||||||
|
"""Get a single world by ID."""
|
||||||
|
result = await db.execute(
|
||||||
|
select(World).where(World.id == world_id).options(selectinload(World.owner))
|
||||||
|
)
|
||||||
|
world = result.scalar_one_or_none()
|
||||||
|
if not world:
|
||||||
|
raise HTTPException(status_code=404, detail="World not found")
|
||||||
|
return {
|
||||||
|
"id": world.id,
|
||||||
|
"name": world.name,
|
||||||
|
"description": world.description,
|
||||||
|
"visibility": world.visibility.value if world.visibility else None,
|
||||||
|
"owner_id": world.owner_id,
|
||||||
|
"created_at": world.created_at.isoformat() if world.created_at else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/")
|
||||||
|
async def create_world(
|
||||||
|
world_data: dict,
|
||||||
|
current_user=Depends(get_current_user),
|
||||||
|
db: Annotated[AsyncSession, Depends(get_db)] = Depends(),
|
||||||
|
):
|
||||||
|
"""Create a new world."""
|
||||||
|
name = world_data.get("name")
|
||||||
|
if not name:
|
||||||
|
raise HTTPException(status_code=400, detail="World name is required")
|
||||||
|
|
||||||
|
world = World(
|
||||||
|
name=name,
|
||||||
|
description=world_data.get("description"),
|
||||||
|
owner_id=current_user["id"],
|
||||||
|
visibility=WorldVisibility(
|
||||||
|
world_data.get("visibility", "public")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
db.add(world)
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(world)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"id": world.id,
|
||||||
|
"name": world.name,
|
||||||
|
"description": world.description,
|
||||||
|
"visibility": world.visibility.value,
|
||||||
|
"owner_id": world.owner_id,
|
||||||
|
"created_at": world.created_at.isoformat() if world.created_at else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/{world_id}")
|
||||||
|
async def update_world(
|
||||||
|
world_id: int,
|
||||||
|
world_data: dict,
|
||||||
|
current_user=Depends(get_current_user),
|
||||||
|
db: Annotated[AsyncSession, Depends(get_db)] = Depends(),
|
||||||
|
):
|
||||||
|
"""Update an existing world."""
|
||||||
|
result = await db.execute(select(World).where(World.id == world_id))
|
||||||
|
world = result.scalar_one_or_none()
|
||||||
|
if not world:
|
||||||
|
raise HTTPException(status_code=404, detail="World not found")
|
||||||
|
|
||||||
|
if world_data.get("name"):
|
||||||
|
world.name = world_data["name"]
|
||||||
|
if world_data.get("description") is not None:
|
||||||
|
world.description = world_data["description"]
|
||||||
|
if world_data.get("visibility"):
|
||||||
|
world.visibility = WorldVisibility(world_data["visibility"])
|
||||||
|
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(world)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"id": world.id,
|
||||||
|
"name": world.name,
|
||||||
|
"description": world.description,
|
||||||
|
"visibility": world.visibility.value if world.visibility else None,
|
||||||
|
"owner_id": world.owner_id,
|
||||||
|
"created_at": world.created_at.isoformat() if world.created_at else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/{world_id}")
|
||||||
|
async def delete_world(
|
||||||
|
world_id: int,
|
||||||
|
current_user=Depends(get_current_user),
|
||||||
|
db: Annotated[AsyncSession, Depends(get_db)] = Depends(),
|
||||||
|
):
|
||||||
|
"""Delete a world."""
|
||||||
|
result = await db.execute(select(World).where(World.id == world_id))
|
||||||
|
world = result.scalar_one_or_none()
|
||||||
|
if not world:
|
||||||
|
raise HTTPException(status_code=404, detail="World not found")
|
||||||
|
|
||||||
|
await db.delete(world)
|
||||||
|
await db.commit()
|
||||||
|
return {"detail": "World deleted"}
|
||||||
Reference in New Issue
Block a user