Files
2026-06-09 17:18:09 +08:00

199 lines
6.3 KiB
Python

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 sqlalchemy import func
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, org_filter
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)
of = org_filter(current_user, MediaAsset)
if of is not True:
query = query.filter(of)
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)
):
of = org_filter(current_user, MediaAsset)
base = db.query(MediaAsset)
if of is not True:
base = base.filter(of)
assets = base.with_entities(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)
):
of = org_filter(current_user, MediaAsset)
base = db.query(MediaAsset)
if of is not True:
base = base.filter(of)
total = base.count()
by_type = {}
rows = base.with_entities(MediaAsset.file_type, 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,
org_id=current_user.org_id or "default",
)
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="素材不存在")
if current_user.role != "admin" and asset.org_id != current_user.org_id:
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 current_user.role != "admin" and asset.org_id != current_user.org_id:
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="素材不存在")
if current_user.role != "admin" and asset.org_id != current_user.org_id:
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}