This commit is contained in:
1 parent
71fc502a92
commit
669c16b0a0
11 files changed
+4881
-4282
No files matched your search
@@ -1,412 +1,544 @@
|
||||
"""
|
||||
应用市场审核 API(管理员)
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from ...database import get_db, SessionLocal
|
||||
from ...models import MarketApp, Coupon, UserCoupon, User
|
||||
from ...auth import get_current_admin
|
||||
from ...middleware.log_service import LogService
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 应用审核管理
|
||||
# ============================================================
|
||||
|
||||
@router.get("/apps/pending")
|
||||
async def get_pending_apps(
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取待审核应用列表"""
|
||||
apps = db.query(MarketApp).filter(
|
||||
MarketApp.status == "pending"
|
||||
).order_by(MarketApp.created_at.desc()).limit(1000).all()
|
||||
|
||||
return [
|
||||
{
|
||||
"id": a.id,
|
||||
"name": a.name,
|
||||
"type": a.type,
|
||||
"source": a.source,
|
||||
"publisher_id": a.publisher_id,
|
||||
"publisher_name": a.publisher_name,
|
||||
"description": a.description,
|
||||
"icon": a.icon,
|
||||
"tutorial": a.tutorial,
|
||||
"price": float(a.price) if a.price else 0,
|
||||
"is_member_free": a.is_member_free,
|
||||
"join_promotion": a.join_promotion,
|
||||
"status": a.status,
|
||||
"rating_avg": float(a.rating_avg) if a.rating_avg else 0,
|
||||
"review_count": a.review_count,
|
||||
"install_count": a.install_count,
|
||||
"skill_content": a.skill_content,
|
||||
"mcp_transport": a.mcp_transport,
|
||||
"agent_endpoint": a.agent_endpoint,
|
||||
"created_at": a.created_at.isoformat() if a.created_at else None,
|
||||
"updated_at": a.updated_at.isoformat() if a.updated_at else None,
|
||||
}
|
||||
for a in apps
|
||||
]
|
||||
|
||||
|
||||
@router.put("/apps/{id}/approve")
|
||||
async def approve_app(
|
||||
id: str,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""审核通过应用"""
|
||||
app = db.query(MarketApp).filter(MarketApp.id == id).first()
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="应用不存在")
|
||||
|
||||
app.status = "published"
|
||||
app.updated_at = datetime.now()
|
||||
db.commit()
|
||||
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="approve_app",
|
||||
target_type="market_app",
|
||||
target_id=id,
|
||||
detail=f"审核通过应用 {app.name}",
|
||||
)
|
||||
|
||||
return {"message": "应用审核通过"}
|
||||
|
||||
|
||||
@router.put("/apps/{id}/reject")
|
||||
async def reject_app(
|
||||
id: str,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""审核拒绝应用"""
|
||||
app = db.query(MarketApp).filter(MarketApp.id == id).first()
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="应用不存在")
|
||||
|
||||
app.status = "removed"
|
||||
app.updated_at = datetime.now()
|
||||
db.commit()
|
||||
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="reject_app",
|
||||
target_type="market_app",
|
||||
target_id=id,
|
||||
detail=f"审核拒绝应用 {app.name}",
|
||||
)
|
||||
|
||||
return {"message": "应用审核已拒绝"}
|
||||
|
||||
|
||||
@router.get("/apps")
|
||||
async def get_all_apps(
|
||||
status: str = Query(None, description="状态筛选: pending/published/removed"),
|
||||
app_type: str = Query(None, description="类型筛选: skill/agent/mcp"),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取所有应用列表(管理员)"""
|
||||
query = db.query(MarketApp)
|
||||
|
||||
if status:
|
||||
query = query.filter(MarketApp.status == status)
|
||||
if app_type:
|
||||
query = query.filter(MarketApp.type == app_type)
|
||||
|
||||
total = query.count()
|
||||
apps = query.order_by(MarketApp.created_at.desc()).offset(
|
||||
(page - 1) * page_size
|
||||
).limit(page_size).all()
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"items": [
|
||||
{
|
||||
"id": a.id,
|
||||
"name": a.name,
|
||||
"type": a.type,
|
||||
"source": a.source,
|
||||
"publisher_id": a.publisher_id,
|
||||
"publisher_name": a.publisher_name,
|
||||
"description": a.description,
|
||||
"icon": a.icon,
|
||||
"tutorial": a.tutorial,
|
||||
"price": float(a.price) if a.price else 0,
|
||||
"is_member_free": a.is_member_free,
|
||||
"join_promotion": a.join_promotion,
|
||||
"status": a.status,
|
||||
"rating_avg": float(a.rating_avg) if a.rating_avg else 0,
|
||||
"review_count": a.review_count,
|
||||
"install_count": a.install_count,
|
||||
"created_at": a.created_at.isoformat() if a.created_at else None,
|
||||
"updated_at": a.updated_at.isoformat() if a.updated_at else None,
|
||||
}
|
||||
for a in apps
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@router.put("/apps/{id}")
|
||||
async def admin_edit_app(
|
||||
id: str,
|
||||
name: str = None,
|
||||
description: str = None,
|
||||
icon: str = None,
|
||||
price: float = None,
|
||||
is_member_free: bool = None,
|
||||
join_promotion: bool = None,
|
||||
tutorial: str = None,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""管理员编辑应用"""
|
||||
app = db.query(MarketApp).filter(MarketApp.id == id).first()
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="应用不存在")
|
||||
|
||||
if name is not None:
|
||||
app.name = name
|
||||
if description is not None:
|
||||
app.description = description
|
||||
if icon is not None:
|
||||
app.icon = icon
|
||||
if price is not None:
|
||||
app.price = price
|
||||
if is_member_free is not None:
|
||||
app.is_member_free = is_member_free
|
||||
if join_promotion is not None:
|
||||
app.join_promotion = join_promotion
|
||||
if tutorial is not None:
|
||||
app.tutorial = tutorial
|
||||
app.updated_at = datetime.now()
|
||||
db.commit()
|
||||
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="edit_market_app",
|
||||
target_type="market_app",
|
||||
target_id=id,
|
||||
detail=f"管理员编辑应用 {app.name}",
|
||||
)
|
||||
|
||||
return {"message": "应用已更新"}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 优惠券管理
|
||||
# ============================================================
|
||||
|
||||
@router.get("/coupons")
|
||||
async def get_coupons(
|
||||
status: str = Query(None, description="状态筛选: active/expired/disabled"),
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取优惠券列表"""
|
||||
query = db.query(Coupon)
|
||||
|
||||
if status:
|
||||
query = query.filter(Coupon.status == status)
|
||||
|
||||
coupons = query.order_by(Coupon.created_at.desc()).limit(1000).all()
|
||||
return [
|
||||
{
|
||||
"id": c.id,
|
||||
"code": c.code,
|
||||
"type": c.type,
|
||||
"value": float(c.value) if c.value else 0,
|
||||
"min_amount": float(c.min_amount) if c.min_amount else 0,
|
||||
"valid_from": c.valid_from.isoformat() if c.valid_from else None,
|
||||
"valid_to": c.valid_to.isoformat() if c.valid_to else None,
|
||||
"total_count": c.total_count,
|
||||
"used_count": c.used_count,
|
||||
"status": c.status,
|
||||
"created_at": c.created_at.isoformat() if c.created_at else None,
|
||||
}
|
||||
for c in coupons
|
||||
]
|
||||
|
||||
|
||||
@router.post("/coupons")
|
||||
async def create_coupon(
|
||||
code: str,
|
||||
coupon_type: str,
|
||||
value: float,
|
||||
valid_from: str,
|
||||
valid_to: str,
|
||||
min_amount: float = 0,
|
||||
total_count: int = None,
|
||||
status: str = "active",
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""创建优惠券"""
|
||||
# 检查code是否已存在
|
||||
existing = db.query(Coupon).filter(Coupon.code == code).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="优惠券码已存在")
|
||||
|
||||
coupon = Coupon(
|
||||
id=str(uuid.uuid4()),
|
||||
code=code,
|
||||
type=coupon_type,
|
||||
value=value,
|
||||
min_amount=min_amount,
|
||||
valid_from=datetime.fromisoformat(valid_from),
|
||||
valid_to=datetime.fromisoformat(valid_to),
|
||||
total_count=total_count,
|
||||
status=status,
|
||||
)
|
||||
db.add(coupon)
|
||||
db.commit()
|
||||
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="create_coupon",
|
||||
target_type="coupon",
|
||||
target_id=coupon.id,
|
||||
detail=f"创建优惠券 {code}",
|
||||
)
|
||||
|
||||
return {"message": "优惠券已创建", "id": coupon.id}
|
||||
|
||||
|
||||
@router.put("/coupons/{id}")
|
||||
async def update_coupon(
|
||||
id: str,
|
||||
code: str = None,
|
||||
value: float = None,
|
||||
min_amount: float = None,
|
||||
valid_from: str = None,
|
||||
valid_to: str = None,
|
||||
total_count: int = None,
|
||||
status: str = None,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""更新优惠券"""
|
||||
coupon = db.query(Coupon).filter(Coupon.id == id).first()
|
||||
if not coupon:
|
||||
raise HTTPException(status_code=404, detail="优惠券不存在")
|
||||
|
||||
if code is not None:
|
||||
# 检查新code是否重复
|
||||
if code != coupon.code:
|
||||
dup = db.query(Coupon).filter(Coupon.code == code).first()
|
||||
if dup:
|
||||
raise HTTPException(status_code=400, detail="优惠券码已存在")
|
||||
coupon.code = code
|
||||
if value is not None:
|
||||
coupon.value = value
|
||||
if min_amount is not None:
|
||||
coupon.min_amount = min_amount
|
||||
if valid_from is not None:
|
||||
coupon.valid_from = datetime.fromisoformat(valid_from)
|
||||
if valid_to is not None:
|
||||
coupon.valid_to = datetime.fromisoformat(valid_to)
|
||||
if total_count is not None:
|
||||
coupon.total_count = total_count
|
||||
if status is not None:
|
||||
coupon.status = status
|
||||
|
||||
db.commit()
|
||||
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="update_coupon",
|
||||
target_type="coupon",
|
||||
target_id=id,
|
||||
detail=f"更新优惠券 {coupon.code}",
|
||||
)
|
||||
|
||||
return {"message": "优惠券已更新"}
|
||||
|
||||
|
||||
@router.delete("/coupons/{id}")
|
||||
async def delete_coupon(
|
||||
id: str,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""删除优惠券"""
|
||||
coupon = db.query(Coupon).filter(Coupon.id == id).first()
|
||||
if not coupon:
|
||||
raise HTTPException(status_code=404, detail="优惠券不存在")
|
||||
|
||||
db.delete(coupon)
|
||||
db.commit()
|
||||
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="delete_coupon",
|
||||
target_type="coupon",
|
||||
target_id=id,
|
||||
detail=f"删除优惠券 {coupon.code}",
|
||||
)
|
||||
|
||||
return {"message": "优惠券已删除"}
|
||||
|
||||
|
||||
@router.post("/coupons/{id}/assign")
|
||||
async def assign_coupon(
|
||||
id: str,
|
||||
user_id: str,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""发放优惠券给用户"""
|
||||
coupon = db.query(Coupon).filter(Coupon.id == id).first()
|
||||
if not coupon:
|
||||
raise HTTPException(status_code=404, detail="优惠券不存在")
|
||||
if coupon.status != "active":
|
||||
raise HTTPException(status_code=400, detail="优惠券非活跃状态")
|
||||
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
|
||||
# 检查是否已发放
|
||||
existing = db.query(UserCoupon).filter(
|
||||
UserCoupon.user_id == user_id,
|
||||
UserCoupon.coupon_id == id,
|
||||
).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="该用户已拥有此优惠券")
|
||||
|
||||
user_coupon = UserCoupon(
|
||||
id=str(uuid.uuid4()),
|
||||
user_id=user_id,
|
||||
coupon_id=id,
|
||||
)
|
||||
db.add(user_coupon)
|
||||
db.commit()
|
||||
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="assign_coupon",
|
||||
target_type="coupon",
|
||||
target_id=id,
|
||||
detail=f"发放优惠券 {coupon.code} 给用户 {user.username}",
|
||||
)
|
||||
|
||||
return {"message": "优惠券已发放"}
|
||||
"""
|
||||
应用市场审核 API(管理员)
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from ...database import get_db, SessionLocal
|
||||
from ...models import MarketApp, Coupon, UserCoupon, User, MarketAppVersion
|
||||
from ...auth import get_current_admin
|
||||
from ...middleware.log_service import LogService
|
||||
from app.core.app_security import scan_market_app
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def _snapshot_app_version(app, db, created_by=None):
|
||||
"""Phase 4.1: 写入一条应用版本快照(回滚用)。"""
|
||||
snap = MarketAppVersion(
|
||||
app_id=app.id,
|
||||
version=app.version or "1.0.0",
|
||||
type=app.type,
|
||||
config={
|
||||
"name": app.name,
|
||||
"description": app.description,
|
||||
"icon": app.icon,
|
||||
"type": app.type,
|
||||
"price": float(app.price) if app.price else 0,
|
||||
"skill_content": app.skill_content,
|
||||
"mcp_transport": app.mcp_transport,
|
||||
"agent_endpoint": app.agent_endpoint,
|
||||
"capability": app.capability,
|
||||
},
|
||||
security_scan=app.security_scan,
|
||||
dependencies=app.dependencies,
|
||||
created_by=created_by,
|
||||
created_at=datetime.now(),
|
||||
)
|
||||
db.add(snap)
|
||||
db.commit()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 应用审核管理
|
||||
# ============================================================
|
||||
|
||||
@router.get("/apps/pending")
|
||||
async def get_pending_apps(
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取待审核应用列表"""
|
||||
apps = db.query(MarketApp).filter(
|
||||
MarketApp.status == "pending"
|
||||
).order_by(MarketApp.created_at.desc()).limit(1000).all()
|
||||
|
||||
return [
|
||||
{
|
||||
"id": a.id,
|
||||
"name": a.name,
|
||||
"type": a.type,
|
||||
"source": a.source,
|
||||
"publisher_id": a.publisher_id,
|
||||
"publisher_name": a.publisher_name,
|
||||
"description": a.description,
|
||||
"icon": a.icon,
|
||||
"tutorial": a.tutorial,
|
||||
"price": float(a.price) if a.price else 0,
|
||||
"is_member_free": a.is_member_free,
|
||||
"join_promotion": a.join_promotion,
|
||||
"status": a.status,
|
||||
"rating_avg": float(a.rating_avg) if a.rating_avg else 0,
|
||||
"review_count": a.review_count,
|
||||
"install_count": a.install_count,
|
||||
"skill_content": a.skill_content,
|
||||
"mcp_transport": a.mcp_transport,
|
||||
"agent_endpoint": a.agent_endpoint,
|
||||
"created_at": a.created_at.isoformat() if a.created_at else None,
|
||||
"updated_at": a.updated_at.isoformat() if a.updated_at else None,
|
||||
}
|
||||
for a in apps
|
||||
]
|
||||
|
||||
|
||||
@router.put("/apps/{id}/approve")
|
||||
async def approve_app(
|
||||
id: str,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""审核通过应用"""
|
||||
app = db.query(MarketApp).filter(MarketApp.id == id).first()
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="应用不存在")
|
||||
|
||||
app.status = "published"
|
||||
app.verified = True # Phase 4.1: 审核通过即绿标
|
||||
app.updated_at = datetime.now()
|
||||
db.commit()
|
||||
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="approve_app",
|
||||
target_type="market_app",
|
||||
target_id=id,
|
||||
detail=f"审核通过应用 {app.name}",
|
||||
)
|
||||
|
||||
return {"message": "应用审核通过"}
|
||||
|
||||
|
||||
@router.put("/apps/{id}/reject")
|
||||
async def reject_app(
|
||||
id: str,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""审核拒绝应用"""
|
||||
app = db.query(MarketApp).filter(MarketApp.id == id).first()
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="应用不存在")
|
||||
|
||||
app.status = "removed"
|
||||
app.updated_at = datetime.now()
|
||||
db.commit()
|
||||
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="reject_app",
|
||||
target_type="market_app",
|
||||
target_id=id,
|
||||
detail=f"审核拒绝应用 {app.name}",
|
||||
)
|
||||
|
||||
return {"message": "应用审核已拒绝"}
|
||||
|
||||
|
||||
@router.get("/apps")
|
||||
async def get_all_apps(
|
||||
status: str = Query(None, description="状态筛选: pending/published/removed"),
|
||||
app_type: str = Query(None, description="类型筛选: skill/agent/mcp"),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取所有应用列表(管理员)"""
|
||||
query = db.query(MarketApp)
|
||||
|
||||
if status:
|
||||
query = query.filter(MarketApp.status == status)
|
||||
if app_type:
|
||||
query = query.filter(MarketApp.type == app_type)
|
||||
|
||||
total = query.count()
|
||||
apps = query.order_by(MarketApp.created_at.desc()).offset(
|
||||
(page - 1) * page_size
|
||||
).limit(page_size).all()
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"items": [
|
||||
{
|
||||
"id": a.id,
|
||||
"name": a.name,
|
||||
"type": a.type,
|
||||
"source": a.source,
|
||||
"publisher_id": a.publisher_id,
|
||||
"publisher_name": a.publisher_name,
|
||||
"description": a.description,
|
||||
"icon": a.icon,
|
||||
"tutorial": a.tutorial,
|
||||
"price": float(a.price) if a.price else 0,
|
||||
"is_member_free": a.is_member_free,
|
||||
"join_promotion": a.join_promotion,
|
||||
"status": a.status,
|
||||
"rating_avg": float(a.rating_avg) if a.rating_avg else 0,
|
||||
"review_count": a.review_count,
|
||||
"install_count": a.install_count,
|
||||
"created_at": a.created_at.isoformat() if a.created_at else None,
|
||||
"updated_at": a.updated_at.isoformat() if a.updated_at else None,
|
||||
}
|
||||
for a in apps
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@router.put("/apps/{id}")
|
||||
async def admin_edit_app(
|
||||
id: str,
|
||||
name: str = None,
|
||||
description: str = None,
|
||||
icon: str = None,
|
||||
price: float = None,
|
||||
is_member_free: bool = None,
|
||||
join_promotion: bool = None,
|
||||
tutorial: str = None,
|
||||
version: str = None, # Phase 4.1: 指定新版本号时先快照当前状态
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""管理员编辑应用"""
|
||||
app = db.query(MarketApp).filter(MarketApp.id == id).first()
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="应用不存在")
|
||||
|
||||
if name is not None:
|
||||
app.name = name
|
||||
if description is not None:
|
||||
app.description = description
|
||||
if icon is not None:
|
||||
app.icon = icon
|
||||
if price is not None:
|
||||
app.price = price
|
||||
if is_member_free is not None:
|
||||
app.is_member_free = is_member_free
|
||||
if join_promotion is not None:
|
||||
app.join_promotion = join_promotion
|
||||
if tutorial is not None:
|
||||
app.tutorial = tutorial
|
||||
if version is not None and version != app.version:
|
||||
_snapshot_app_version(app, db, admin_user.id)
|
||||
app.version = version
|
||||
app.updated_at = datetime.now()
|
||||
db.commit()
|
||||
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="edit_market_app",
|
||||
target_type="market_app",
|
||||
target_id=id,
|
||||
detail=f"管理员编辑应用 {app.name}",
|
||||
)
|
||||
|
||||
return {"message": "应用已更新"}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Phase 4.1: 版本历史 / 安检 / 回滚
|
||||
# ============================================================
|
||||
|
||||
@router.get("/apps/{id}/versions")
|
||||
async def get_app_versions(
|
||||
id: str,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取应用的版本快照历史。"""
|
||||
app = db.query(MarketApp).filter(MarketApp.id == id).first()
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="应用不存在")
|
||||
vers = db.query(MarketAppVersion).filter(
|
||||
MarketAppVersion.app_id == id
|
||||
).order_by(MarketAppVersion.created_at.desc()).all()
|
||||
return {
|
||||
"current_version": app.version or "1.0.0",
|
||||
"items": [
|
||||
{
|
||||
"id": v.id,
|
||||
"version": v.version,
|
||||
"type": v.type,
|
||||
"created_at": v.created_at.isoformat() if v.created_at else None,
|
||||
"created_by": v.created_by,
|
||||
"security_scan": v.security_scan,
|
||||
"dependencies": v.dependencies,
|
||||
}
|
||||
for v in vers
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@router.post("/apps/{id}/scan")
|
||||
async def rescan_app(
|
||||
id: str,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""重新执行安全扫描并刷新绿标。"""
|
||||
app = db.query(MarketApp).filter(MarketApp.id == id).first()
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="应用不存在")
|
||||
scan = scan_market_app(
|
||||
app_type=app.type,
|
||||
skill_content=app.skill_content,
|
||||
agent_endpoint=app.agent_endpoint,
|
||||
mcp_transport=app.mcp_transport,
|
||||
mcp_config=app.skill_content if app.type == "mcp" else None,
|
||||
dependencies=app.dependencies,
|
||||
)
|
||||
app.security_scan = scan
|
||||
app.verified = (scan.get("status") == "pass")
|
||||
app.updated_at = datetime.now()
|
||||
db.commit()
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id, admin_name=admin_user.username,
|
||||
action="rescan_market_app", target_type="market_app", target_id=id,
|
||||
detail=f"重新安检应用 {app.name}:{scan.get('status')}",
|
||||
)
|
||||
return {"status": scan.get("status"), "score": scan.get("score"), "security_scan": scan}
|
||||
|
||||
|
||||
@router.post("/apps/{id}/rollback")
|
||||
async def rollback_app(
|
||||
id: str,
|
||||
version_id: str = Query(..., description="要回滚到的版本快照 ID"),
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""回滚应用到指定版本快照。"""
|
||||
app = db.query(MarketApp).filter(MarketApp.id == id).first()
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="应用不存在")
|
||||
snap = db.query(MarketAppVersion).filter(
|
||||
MarketAppVersion.id == version_id, MarketAppVersion.app_id == id
|
||||
).first()
|
||||
if not snap:
|
||||
raise HTTPException(status_code=404, detail="版本快照不存在")
|
||||
cfg = snap.config or {}
|
||||
app.name = cfg.get("name", app.name)
|
||||
app.description = cfg.get("description", app.description)
|
||||
app.icon = cfg.get("icon", app.icon)
|
||||
app.skill_content = cfg.get("skill_content", app.skill_content)
|
||||
app.mcp_transport = cfg.get("mcp_transport", app.mcp_transport)
|
||||
app.agent_endpoint = cfg.get("agent_endpoint", app.agent_endpoint)
|
||||
app.capability = cfg.get("capability", app.capability)
|
||||
app.version = snap.version
|
||||
app.security_scan = snap.security_scan
|
||||
app.updated_at = datetime.now()
|
||||
db.commit()
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id, admin_name=admin_user.username,
|
||||
action="rollback_market_app", target_type="market_app", target_id=id,
|
||||
detail=f"回滚应用 {app.name} 到版本 {snap.version}",
|
||||
)
|
||||
return {"message": "已回滚到版本 " + snap.version}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 优惠券管理
|
||||
# ============================================================
|
||||
|
||||
@router.get("/coupons")
|
||||
async def get_coupons(
|
||||
status: str = Query(None, description="状态筛选: active/expired/disabled"),
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取优惠券列表"""
|
||||
query = db.query(Coupon)
|
||||
|
||||
if status:
|
||||
query = query.filter(Coupon.status == status)
|
||||
|
||||
coupons = query.order_by(Coupon.created_at.desc()).limit(1000).all()
|
||||
return [
|
||||
{
|
||||
"id": c.id,
|
||||
"code": c.code,
|
||||
"type": c.type,
|
||||
"value": float(c.value) if c.value else 0,
|
||||
"min_amount": float(c.min_amount) if c.min_amount else 0,
|
||||
"valid_from": c.valid_from.isoformat() if c.valid_from else None,
|
||||
"valid_to": c.valid_to.isoformat() if c.valid_to else None,
|
||||
"total_count": c.total_count,
|
||||
"used_count": c.used_count,
|
||||
"status": c.status,
|
||||
"created_at": c.created_at.isoformat() if c.created_at else None,
|
||||
}
|
||||
for c in coupons
|
||||
]
|
||||
|
||||
|
||||
@router.post("/coupons")
|
||||
async def create_coupon(
|
||||
code: str,
|
||||
coupon_type: str,
|
||||
value: float,
|
||||
valid_from: str,
|
||||
valid_to: str,
|
||||
min_amount: float = 0,
|
||||
total_count: int = None,
|
||||
status: str = "active",
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""创建优惠券"""
|
||||
# 检查code是否已存在
|
||||
existing = db.query(Coupon).filter(Coupon.code == code).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="优惠券码已存在")
|
||||
|
||||
coupon = Coupon(
|
||||
id=str(uuid.uuid4()),
|
||||
code=code,
|
||||
type=coupon_type,
|
||||
value=value,
|
||||
min_amount=min_amount,
|
||||
valid_from=datetime.fromisoformat(valid_from),
|
||||
valid_to=datetime.fromisoformat(valid_to),
|
||||
total_count=total_count,
|
||||
status=status,
|
||||
)
|
||||
db.add(coupon)
|
||||
db.commit()
|
||||
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="create_coupon",
|
||||
target_type="coupon",
|
||||
target_id=coupon.id,
|
||||
detail=f"创建优惠券 {code}",
|
||||
)
|
||||
|
||||
return {"message": "优惠券已创建", "id": coupon.id}
|
||||
|
||||
|
||||
@router.put("/coupons/{id}")
|
||||
async def update_coupon(
|
||||
id: str,
|
||||
code: str = None,
|
||||
value: float = None,
|
||||
min_amount: float = None,
|
||||
valid_from: str = None,
|
||||
valid_to: str = None,
|
||||
total_count: int = None,
|
||||
status: str = None,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""更新优惠券"""
|
||||
coupon = db.query(Coupon).filter(Coupon.id == id).first()
|
||||
if not coupon:
|
||||
raise HTTPException(status_code=404, detail="优惠券不存在")
|
||||
|
||||
if code is not None:
|
||||
# 检查新code是否重复
|
||||
if code != coupon.code:
|
||||
dup = db.query(Coupon).filter(Coupon.code == code).first()
|
||||
if dup:
|
||||
raise HTTPException(status_code=400, detail="优惠券码已存在")
|
||||
coupon.code = code
|
||||
if value is not None:
|
||||
coupon.value = value
|
||||
if min_amount is not None:
|
||||
coupon.min_amount = min_amount
|
||||
if valid_from is not None:
|
||||
coupon.valid_from = datetime.fromisoformat(valid_from)
|
||||
if valid_to is not None:
|
||||
coupon.valid_to = datetime.fromisoformat(valid_to)
|
||||
if total_count is not None:
|
||||
coupon.total_count = total_count
|
||||
if status is not None:
|
||||
coupon.status = status
|
||||
|
||||
db.commit()
|
||||
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="update_coupon",
|
||||
target_type="coupon",
|
||||
target_id=id,
|
||||
detail=f"更新优惠券 {coupon.code}",
|
||||
)
|
||||
|
||||
return {"message": "优惠券已更新"}
|
||||
|
||||
|
||||
@router.delete("/coupons/{id}")
|
||||
async def delete_coupon(
|
||||
id: str,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""删除优惠券"""
|
||||
coupon = db.query(Coupon).filter(Coupon.id == id).first()
|
||||
if not coupon:
|
||||
raise HTTPException(status_code=404, detail="优惠券不存在")
|
||||
|
||||
db.delete(coupon)
|
||||
db.commit()
|
||||
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="delete_coupon",
|
||||
target_type="coupon",
|
||||
target_id=id,
|
||||
detail=f"删除优惠券 {coupon.code}",
|
||||
)
|
||||
|
||||
return {"message": "优惠券已删除"}
|
||||
|
||||
|
||||
@router.post("/coupons/{id}/assign")
|
||||
async def assign_coupon(
|
||||
id: str,
|
||||
user_id: str,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""发放优惠券给用户"""
|
||||
coupon = db.query(Coupon).filter(Coupon.id == id).first()
|
||||
if not coupon:
|
||||
raise HTTPException(status_code=404, detail="优惠券不存在")
|
||||
if coupon.status != "active":
|
||||
raise HTTPException(status_code=400, detail="优惠券非活跃状态")
|
||||
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
|
||||
# 检查是否已发放
|
||||
existing = db.query(UserCoupon).filter(
|
||||
UserCoupon.user_id == user_id,
|
||||
UserCoupon.coupon_id == id,
|
||||
).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="该用户已拥有此优惠券")
|
||||
|
||||
user_coupon = UserCoupon(
|
||||
id=str(uuid.uuid4()),
|
||||
user_id=user_id,
|
||||
coupon_id=id,
|
||||
)
|
||||
db.add(user_coupon)
|
||||
db.commit()
|
||||
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="assign_coupon",
|
||||
target_type="coupon",
|
||||
target_id=id,
|
||||
detail=f"发放优惠券 {coupon.code} 给用户 {user.username}",
|
||||
)
|
||||
|
||||
return {"message": "优惠券已发放"}
|
||||
Reference in new issue
Block a user