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}
|
||||
Reference in New Issue
Block a user