初始提交: Qihu-AIFlow v2.0.1 完整项目
- 前端 React+TypeScript (源码+构建产物) - 后端 Python FastAPI - 数据库迁移 001-015 - Docker部署配置 - 微信ClawBot API频道绑定 - 项目文档 README.md
This commit is contained in:
commit
78ff261b5a
199 files changed
+69126
No files matched your search
@@ -0,0 +1,379 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_, desc, asc
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import (
|
||||
User, Asset, AssetCategory, NovelChapter, KnowledgeDocument,
|
||||
KnowledgeBase,
|
||||
gen_uuid
|
||||
)
|
||||
from ..auth import get_current_user
|
||||
from ..middleware.log_service import LogService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class CreateAssetRequest(BaseModel):
|
||||
name: str = Field(min_length=1, max_length=200)
|
||||
category: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
file_type: Optional[str] = None
|
||||
content: Optional[str] = None
|
||||
|
||||
|
||||
class UpdateAssetRequest(BaseModel):
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
file_type: Optional[str] = None
|
||||
|
||||
|
||||
class CreateChapterRequest(BaseModel):
|
||||
title: str = Field(min_length=1, max_length=500)
|
||||
content: Optional[str] = None
|
||||
sort_order: int = Field(default=0)
|
||||
|
||||
|
||||
VALID_ASSET_CATEGORIES = ("file", "novel", "image", "video", "audio", "custom")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 静态路由 - 必须在动态路由之前定义
|
||||
# ============================================================
|
||||
|
||||
@router.get("/storage-overview")
|
||||
def get_storage_overview(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""获取用户资产存储概览,包含资产和知识库文档的总存储量"""
|
||||
# Asset表中的文件大小
|
||||
from sqlalchemy import func as sa_func
|
||||
asset_size = db.query(sa_func.sum(Asset.size)).filter(
|
||||
and_(
|
||||
Asset.user_id == current_user.id,
|
||||
Asset.status != "deleted",
|
||||
)
|
||||
).scalar() or 0
|
||||
|
||||
# 知识库文档的文件大小
|
||||
kb_doc_size = db.query(sa_func.sum(KnowledgeDocument.file_size)).join(
|
||||
KnowledgeBase, KnowledgeDocument.knowledge_base_id == KnowledgeBase.id
|
||||
).filter(
|
||||
KnowledgeBase.user_id == current_user.id
|
||||
).scalar() or 0
|
||||
|
||||
total_storage = asset_size + kb_doc_size
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": {
|
||||
"asset_storage": asset_size,
|
||||
"knowledge_doc_storage": kb_doc_size,
|
||||
"total_storage": total_storage,
|
||||
},
|
||||
"message": "ok",
|
||||
}
|
||||
|
||||
|
||||
@router.get("/categories")
|
||||
def list_categories(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
categories = (
|
||||
db.query(AssetCategory)
|
||||
.filter(AssetCategory.user_id == current_user.id)
|
||||
.order_by(asc(AssetCategory.name))
|
||||
.all()
|
||||
)
|
||||
|
||||
result = []
|
||||
for c in categories:
|
||||
count = db.query(Asset).filter(
|
||||
and_(
|
||||
Asset.category == c.name,
|
||||
Asset.user_id == current_user.id,
|
||||
Asset.status != "deleted",
|
||||
)
|
||||
).count()
|
||||
|
||||
result.append({
|
||||
"id": c.id,
|
||||
"name": c.name,
|
||||
"type": c.type,
|
||||
"asset_count": count,
|
||||
"created_at": str(c.created_at) if c.created_at else None,
|
||||
})
|
||||
|
||||
return {"code": 0, "data": result, "message": "ok"}
|
||||
|
||||
|
||||
@router.get("/novels/{asset_id}/chapters")
|
||||
def get_novel_chapters(
|
||||
asset_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
asset = db.query(Asset).filter(
|
||||
and_(Asset.id == asset_id, Asset.user_id == current_user.id)
|
||||
).first()
|
||||
if not asset:
|
||||
raise HTTPException(status_code=404, detail="资产不存在")
|
||||
|
||||
chapters = (
|
||||
db.query(NovelChapter)
|
||||
.filter(NovelChapter.asset_id == asset_id)
|
||||
.order_by(asc(NovelChapter.sort_order), asc(NovelChapter.created_at))
|
||||
.all()
|
||||
)
|
||||
|
||||
result = []
|
||||
for ch in chapters:
|
||||
result.append({
|
||||
"id": ch.id,
|
||||
"asset_id": ch.asset_id,
|
||||
"title": ch.title,
|
||||
"sort_order": ch.sort_order,
|
||||
"word_count": ch.word_count or 0,
|
||||
"created_at": str(ch.created_at) if ch.created_at else None,
|
||||
"updated_at": str(ch.updated_at) if ch.updated_at else None,
|
||||
})
|
||||
|
||||
return {"code": 0, "data": result, "message": "ok"}
|
||||
|
||||
|
||||
@router.post("/novels/{asset_id}/chapters")
|
||||
def add_novel_chapter(
|
||||
asset_id: str,
|
||||
req: CreateChapterRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
asset = db.query(Asset).filter(
|
||||
and_(Asset.id == asset_id, Asset.user_id == current_user.id)
|
||||
).first()
|
||||
if not asset:
|
||||
raise HTTPException(status_code=404, detail="资产不存在")
|
||||
|
||||
chapter = NovelChapter(
|
||||
id=gen_uuid(),
|
||||
asset_id=asset_id,
|
||||
title=req.title,
|
||||
content=req.content,
|
||||
sort_order=req.sort_order,
|
||||
word_count=len(req.content or ""),
|
||||
created_at=datetime.now(),
|
||||
updated_at=datetime.now(),
|
||||
)
|
||||
db.add(chapter)
|
||||
|
||||
LogService.log_operation(
|
||||
user_id=current_user.id, username=current_user.username, action="add_chapter",
|
||||
target="novel_chapter", target_id=chapter.id,
|
||||
detail=f"为资产 {asset.name} 添加章节 {chapter.title}",
|
||||
)
|
||||
|
||||
db.commit()
|
||||
db.refresh(chapter)
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": {
|
||||
"id": chapter.id, "asset_id": chapter.asset_id, "title": chapter.title,
|
||||
"sort_order": chapter.sort_order, "word_count": chapter.word_count,
|
||||
"created_at": str(chapter.created_at),
|
||||
},
|
||||
"message": "章节添加成功",
|
||||
}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 动态路由 - {asset_id} 必须放在静态路由之后
|
||||
# ============================================================
|
||||
|
||||
@router.get("/")
|
||||
def list_assets(
|
||||
category: Optional[str] = Query(None),
|
||||
search: Optional[str] = Query(None),
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=20, ge=1, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
query = db.query(Asset).filter(
|
||||
and_(
|
||||
Asset.user_id == current_user.id,
|
||||
Asset.status != "deleted",
|
||||
)
|
||||
)
|
||||
|
||||
if category:
|
||||
query = query.filter(Asset.category == category)
|
||||
if search:
|
||||
query = query.filter(Asset.name.ilike(f"%{search}%"))
|
||||
|
||||
total = query.count()
|
||||
assets = (
|
||||
query.order_by(desc(Asset.updated_at))
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
|
||||
result = []
|
||||
for a in assets:
|
||||
result.append({
|
||||
"id": a.id,
|
||||
"name": a.name,
|
||||
"category": a.category,
|
||||
"file_type": a.file_type,
|
||||
"size": a.size or 0,
|
||||
"description": a.description,
|
||||
"url": a.url or "",
|
||||
"content": a.content or "",
|
||||
"status": a.status,
|
||||
"created_at": str(a.created_at) if a.created_at else None,
|
||||
"updated_at": str(a.updated_at) if a.updated_at else None,
|
||||
})
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": {"total": total, "page": page, "page_size": page_size, "items": result},
|
||||
"message": "ok",
|
||||
}
|
||||
|
||||
|
||||
@router.post("/")
|
||||
def create_asset(
|
||||
req: CreateAssetRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
cat = req.category if req.category in VALID_ASSET_CATEGORIES else "file"
|
||||
asset = Asset(
|
||||
id=gen_uuid(),
|
||||
user_id=current_user.id,
|
||||
name=req.name,
|
||||
category=cat,
|
||||
description=req.description or (req.content[:100] if req.content else None),
|
||||
file_type=req.file_type or "unknown",
|
||||
size=len(req.content.encode('utf-8')) if req.content else 0,
|
||||
content=req.content,
|
||||
status="active",
|
||||
created_at=datetime.now(),
|
||||
updated_at=datetime.now(),
|
||||
)
|
||||
db.add(asset)
|
||||
db.commit()
|
||||
db.refresh(asset)
|
||||
|
||||
LogService.log_operation(
|
||||
user_id=current_user.id, username=current_user.username, action="create",
|
||||
target="asset", target_id=asset.id,
|
||||
detail=f"创建资产 {asset.name}",
|
||||
)
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": {
|
||||
"id": asset.id, "name": asset.name, "category": asset.category,
|
||||
"description": asset.description, "status": asset.status,
|
||||
"created_at": str(asset.created_at),
|
||||
},
|
||||
"message": "创建成功",
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{asset_id}")
|
||||
def get_asset(
|
||||
asset_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
asset = db.query(Asset).filter(
|
||||
and_(Asset.id == asset_id, Asset.user_id == current_user.id)
|
||||
).first()
|
||||
if not asset:
|
||||
raise HTTPException(status_code=404, detail="资产不存在")
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": {
|
||||
"id": asset.id, "name": asset.name, "category": asset.category,
|
||||
"description": asset.description, "file_type": asset.file_type,
|
||||
"size": asset.size or 0,
|
||||
"url": asset.url or "",
|
||||
"content": asset.content or "",
|
||||
"status": asset.status,
|
||||
"created_at": str(asset.created_at), "updated_at": str(asset.updated_at),
|
||||
},
|
||||
"message": "ok",
|
||||
}
|
||||
|
||||
|
||||
@router.put("/{asset_id}")
|
||||
def update_asset(
|
||||
asset_id: str,
|
||||
req: UpdateAssetRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
asset = db.query(Asset).filter(
|
||||
and_(Asset.id == asset_id, Asset.user_id == current_user.id)
|
||||
).first()
|
||||
if not asset:
|
||||
raise HTTPException(status_code=404, detail="资产不存在")
|
||||
|
||||
if req.name is not None:
|
||||
asset.name = req.name
|
||||
if req.description is not None:
|
||||
asset.description = req.description
|
||||
if req.file_type is not None:
|
||||
asset.file_type = req.file_type
|
||||
asset.updated_at = datetime.now()
|
||||
|
||||
LogService.log_operation(
|
||||
user_id=current_user.id, username=current_user.username, action="update",
|
||||
target="asset", target_id=asset.id,
|
||||
detail=f"更新资产 {asset.name}",
|
||||
)
|
||||
|
||||
db.commit()
|
||||
db.refresh(asset)
|
||||
|
||||
return {"code": 0, "data": {
|
||||
"id": asset.id, "name": asset.name, "status": asset.status,
|
||||
"updated_at": str(asset.updated_at),
|
||||
}, "message": "更新成功"}
|
||||
|
||||
|
||||
@router.delete("/{asset_id}")
|
||||
def delete_asset(
|
||||
asset_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
asset = db.query(Asset).filter(
|
||||
and_(Asset.id == asset_id, Asset.user_id == current_user.id)
|
||||
).first()
|
||||
if not asset:
|
||||
raise HTTPException(status_code=404, detail="资产不存在")
|
||||
|
||||
# 软删除
|
||||
asset.status = "deleted"
|
||||
asset.updated_at = datetime.now()
|
||||
|
||||
LogService.log_operation(
|
||||
user_id=current_user.id, username=current_user.username, action="delete",
|
||||
target="asset", target_id=asset.id,
|
||||
detail=f"删除资产 {asset.name}",
|
||||
)
|
||||
|
||||
db.commit()
|
||||
|
||||
return {"code": 0, "message": "资产已删除"}
|
||||
Reference in new issue
Block a user