199 lines
6.3 KiB
Python
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} |