import os import uuid import hashlib from pathlib import Path from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Query from sqlalchemy.orm import Session from typing import List, Optional from ..database import get_db from ..models import MediaAsset from ..schemas import MediaAssetCreate, MediaAssetUpdate, MediaAssetResponse from .auth import get_current_user router = APIRouter(prefix="/api/assets", tags=["assets"]) PROJECT_ROOT = Path(__file__).resolve().parents[4] if os.getenv('PROJECT_ROOT'): PROJECT_ROOT = Path(os.getenv('PROJECT_ROOT')) UPLOAD_DIR = PROJECT_ROOT / "content" / "images" ALLOWED_IMAGE_TYPES = {"image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml"} @router.get("", response_model=List[MediaAssetResponse]) def list_assets( file_type: Optional[str] = None, tag: Optional[str] = None, topic_id: Optional[str] = None, search: Optional[str] = None, limit: int = Query(50, le=200), offset: int = Query(0, ge=0), db: Session = Depends(get_db), current_user=Depends(get_current_user) ): query = db.query(MediaAsset) if file_type: query = query.filter(MediaAsset.file_type == file_type) if tag: query = query.filter(MediaAsset.tags.contains([tag])) if topic_id: query = query.filter(MediaAsset.topic_ids.contains([topic_id])) if search: query = query.filter( (MediaAsset.filename.contains(search)) | (MediaAsset.alt_text.contains(search)) ) return query.order_by(MediaAsset.created_at.desc()).offset(offset).limit(limit).all() @router.get("/tags") def list_tags( db: Session = Depends(get_db), current_user=Depends(get_current_user) ): assets = db.query(MediaAsset.tags).all() all_tags = set() for a in assets: if a[0]: all_tags.update(a[0]) return sorted(all_tags) @router.get("/counts") def get_counts( db: Session = Depends(get_db), current_user=Depends(get_current_user) ): total = db.query(MediaAsset).count() by_type = {} rows = db.query(MediaAsset.file_type, db.func.count(MediaAsset.id)).group_by(MediaAsset.file_type).all() for ftype, cnt in rows: by_type[ftype] = cnt return {"total": total, "by_type": by_type} @router.post("/upload", response_model=MediaAssetResponse) async def upload_asset( file: UploadFile = File(...), tags: Optional[str] = Query(None, description="逗号分隔的标签"), alt_text: Optional[str] = Query(None), topic_ids: Optional[str] = Query(None, description="逗号分隔的选题ID"), db: Session = Depends(get_db), current_user=Depends(get_current_user) ): if file.content_type not in ALLOWED_IMAGE_TYPES: raise HTTPException(status_code=400, detail=f"不支持的文件类型: {file.content_type}") UPLOAD_DIR.mkdir(parents=True, exist_ok=True) suffix = Path(file.filename).suffix or "" unique_name = f"{uuid.uuid4().hex[:12]}{suffix}" file_path = UPLOAD_DIR / unique_name content = await file.read() file_size = len(content) with open(file_path, "wb") as f: f.write(content) file_type = file.content_type.split("/")[0] if file_type not in ("image", "video", "application"): if suffix in (".pdf", ".doc", ".docx", ".ppt", ".pptx"): file_type = "document" elif suffix in (".mp4", ".mov", ".avi"): file_type = "video" else: file_type = "image" parsed_tags = [t.strip() for t in tags.split(",")] if tags else [] parsed_topic_ids = [t.strip() for t in topic_ids.split(",")] if topic_ids else [] asset = MediaAsset( filename=file.filename, file_path=str(file_path), file_type=file_type, mime_type=file.content_type, size=file_size, alt_text=alt_text, tags=parsed_tags, topic_ids=parsed_topic_ids, uploaded_by=current_user.username ) db.add(asset) db.commit() db.refresh(asset) return asset @router.put("/{asset_id}", response_model=MediaAssetResponse) def update_asset( asset_id: int, data: MediaAssetUpdate, db: Session = Depends(get_db), current_user=Depends(get_current_user) ): asset = db.query(MediaAsset).filter(MediaAsset.id == asset_id).first() if not asset: raise HTTPException(status_code=404, detail="素材不存在") for k, v in data.model_dump(exclude_unset=True).items(): setattr(asset, k, v) db.commit() db.refresh(asset) return asset @router.delete("/{asset_id}") def delete_asset( asset_id: int, db: Session = Depends(get_db), current_user=Depends(get_current_user) ): asset = db.query(MediaAsset).filter(MediaAsset.id == asset_id).first() if not asset: raise HTTPException(status_code=404, detail="素材不存在") if os.path.exists(asset.file_path): try: os.remove(asset.file_path) except OSError: pass db.delete(asset) db.commit() return {"ok": True} @router.post("/{asset_id}/use") def increment_usage( asset_id: int, db: Session = Depends(get_db), current_user=Depends(get_current_user) ): asset = db.query(MediaAsset).filter(MediaAsset.id == asset_id).first() if not asset: raise HTTPException(status_code=404, detail="素材不存在") asset.usage_count = (asset.usage_count or 0) + 1 db.commit() return {"ok": True, "usage_count": asset.usage_count}