121 lines
4.0 KiB
Python
121 lines
4.0 KiB
Python
from datetime import datetime, timedelta, timezone
|
|
from typing import List, Optional
|
|
|
|
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.deps import get_current_user
|
|
from app.db.session import get_db
|
|
from app.models import Asset, User
|
|
from app.schemas import AssetResponse, AssetTrashRequest
|
|
from app.services.storage import asset_storage
|
|
|
|
router = APIRouter(prefix="/api/assets", tags=["assets"])
|
|
|
|
ALLOWED_TYPES = {
|
|
"image": ["image/jpeg", "image/png", "image/webp"],
|
|
"video": ["video/mp4", "video/webm", "video/quicktime"],
|
|
"audio": ["audio/mpeg", "audio/mp4", "audio/wav", "audio/ogg", "audio/x-wav"],
|
|
"pose_sheet": ["image/jpeg", "image/png", "image/webp"],
|
|
}
|
|
MAX_SIZE_BYTES = 500 * 1024 * 1024
|
|
|
|
|
|
@router.post("/upload", response_model=AssetResponse, status_code=201)
|
|
async def upload_asset(
|
|
file: UploadFile = File(...),
|
|
asset_type: str = Form(...),
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
if asset_type not in ALLOWED_TYPES:
|
|
raise HTTPException(400, f"asset_type must be one of {list(ALLOWED_TYPES.keys())}")
|
|
|
|
mime = file.content_type or ""
|
|
if mime not in ALLOWED_TYPES[asset_type]:
|
|
raise HTTPException(400, f"Unsupported mime type {mime} for {asset_type}")
|
|
|
|
subfolder = f"{current_user.id}/{asset_type}"
|
|
storage_path, size_bytes = await asset_storage.save_upload(file, subfolder)
|
|
if size_bytes > MAX_SIZE_BYTES:
|
|
raise HTTPException(413, "File too large (max 500MB)")
|
|
|
|
thumbnail_path = None
|
|
width = height = None
|
|
duration_seconds = None
|
|
if asset_type in ("image", "pose_sheet"):
|
|
thumbnail_path = asset_storage.generate_thumbnail(storage_path, f"{current_user.id}/thumbs")
|
|
try:
|
|
from PIL import Image
|
|
|
|
abs_path = asset_storage.absolute_path(storage_path)
|
|
with Image.open(abs_path) as image:
|
|
width, height = image.size
|
|
except Exception:
|
|
pass
|
|
elif asset_type == "video":
|
|
thumbnail_path = asset_storage.generate_video_thumbnail(storage_path, f"{current_user.id}/thumbs")
|
|
duration_seconds = asset_storage.detect_duration_seconds(storage_path)
|
|
else:
|
|
duration_seconds = asset_storage.detect_duration_seconds(storage_path)
|
|
|
|
asset = Asset(
|
|
owner_id=current_user.id,
|
|
asset_type=asset_type,
|
|
mime_type=mime,
|
|
original_filename=file.filename or "upload",
|
|
storage_path=storage_path,
|
|
thumbnail_path=thumbnail_path,
|
|
size_bytes=size_bytes,
|
|
width=width,
|
|
height=height,
|
|
duration_seconds=duration_seconds,
|
|
)
|
|
db.add(asset)
|
|
db.commit()
|
|
db.refresh(asset)
|
|
return asset
|
|
|
|
|
|
@router.get("/", response_model=List[AssetResponse])
|
|
def list_assets(
|
|
asset_type: Optional[str] = None,
|
|
include_trashed: bool = False,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
query = db.query(Asset).filter(Asset.owner_id == current_user.id)
|
|
if not include_trashed:
|
|
query = query.filter(Asset.is_trashed.is_(False))
|
|
if asset_type:
|
|
query = query.filter(Asset.asset_type == asset_type)
|
|
return query.order_by(Asset.created_at.desc()).all()
|
|
|
|
|
|
@router.post("/trash", status_code=200)
|
|
def move_assets_to_trash(
|
|
payload: AssetTrashRequest,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
if not payload.asset_ids:
|
|
raise HTTPException(400, "No asset ids provided")
|
|
|
|
assets = (
|
|
db.query(Asset)
|
|
.filter(Asset.owner_id == current_user.id, Asset.id.in_(payload.asset_ids))
|
|
.all()
|
|
)
|
|
if not assets:
|
|
raise HTTPException(404, "No matching assets found")
|
|
|
|
delete_after_at = datetime.now(timezone.utc) + timedelta(days=30)
|
|
for asset in assets:
|
|
asset.is_trashed = True
|
|
asset.delete_after_at = delete_after_at
|
|
db.commit()
|
|
return {
|
|
"moved_to_trash": len(assets),
|
|
"delete_after_at": delete_after_at.isoformat(),
|
|
}
|