180 lines
5.2 KiB
Python
180 lines
5.2 KiB
Python
"""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 app.database import get_db
|
|
from app.dependencies import CurrentUser
|
|
from app.models.asset import Asset, AssetType
|
|
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,
|
|
current_user: CurrentUser,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""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(
|
|
current_user: CurrentUser,
|
|
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),
|
|
):
|
|
"""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,
|
|
current_user: CurrentUser,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""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,
|
|
current_user: CurrentUser,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""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,
|
|
current_user: CurrentUser,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""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(...),
|
|
current_user: CurrentUser = None,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""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}
|