Files

361 lines
12 KiB
Python

"""导航站路由"""
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import func as sa_func
from pydantic import BaseModel
from typing import Optional
from database import get_db
from models.user import User
from models.nav_category import NavCategory
from models.nav_link import NavLink
from routers.auth import get_current_user, get_admin_user
router = APIRouter()
# ========== Schemas ==========
class NavCategoryCreate(BaseModel):
name: str
icon: str = ""
class NavCategoryUpdate(BaseModel):
name: Optional[str] = None
icon: Optional[str] = None
sort_order: Optional[int] = None
is_active: Optional[bool] = None
class NavLinkCreate(BaseModel):
category_id: int
name: str
url: str
icon: str = ""
description: str = ""
class NavLinkUpdate(BaseModel):
category_id: Optional[int] = None
name: Optional[str] = None
url: Optional[str] = None
icon: Optional[str] = None
description: Optional[str] = None
sort_order: Optional[int] = None
is_active: Optional[bool] = None
class NavLinkSubmit(BaseModel):
"""用户提交导航链接"""
category_id: int
name: str
url: str
icon: str = ""
description: str = ""
class NavLinkReview(BaseModel):
"""审核操作"""
action: str # approve / reject
reject_reason: str = ""
# ========== 管理员接口 ==========
@router.get("/admin/categories")
def admin_list_categories(
db: Session = Depends(get_db),
admin: User = Depends(get_admin_user),
):
"""获取所有导航分类(含禁用)"""
cats = db.query(NavCategory).order_by(NavCategory.sort_order, NavCategory.id).all()
return [
{
"id": c.id, "name": c.name, "icon": c.icon,
"sort_order": c.sort_order, "is_active": c.is_active,
"link_count": db.query(sa_func.count(NavLink.id)).filter(NavLink.category_id == c.id).scalar() or 0,
}
for c in cats
]
@router.post("/admin/categories")
def admin_create_category(
data: NavCategoryCreate,
db: Session = Depends(get_db),
admin: User = Depends(get_admin_user),
):
"""新增导航分类"""
existing = db.query(NavCategory).filter(NavCategory.name == data.name).first()
if existing:
raise HTTPException(status_code=400, detail="分类名称已存在")
max_order = db.query(sa_func.max(NavCategory.sort_order)).scalar() or 0
cat = NavCategory(name=data.name, icon=data.icon, sort_order=max_order + 1)
db.add(cat)
db.commit()
db.refresh(cat)
return {"id": cat.id, "name": cat.name, "icon": cat.icon, "sort_order": cat.sort_order, "is_active": cat.is_active}
@router.put("/admin/categories/{cat_id}")
def admin_update_category(
cat_id: int,
data: NavCategoryUpdate,
db: Session = Depends(get_db),
admin: User = Depends(get_admin_user),
):
"""编辑导航分类"""
cat = db.query(NavCategory).filter(NavCategory.id == cat_id).first()
if not cat:
raise HTTPException(status_code=404, detail="分类不存在")
if data.name is not None:
dup = db.query(NavCategory).filter(NavCategory.name == data.name, NavCategory.id != cat_id).first()
if dup:
raise HTTPException(status_code=400, detail="分类名称已存在")
cat.name = data.name
if data.icon is not None:
cat.icon = data.icon
if data.sort_order is not None:
cat.sort_order = data.sort_order
if data.is_active is not None:
cat.is_active = data.is_active
db.commit()
db.refresh(cat)
return {"id": cat.id, "name": cat.name, "icon": cat.icon, "sort_order": cat.sort_order, "is_active": cat.is_active}
@router.delete("/admin/categories/{cat_id}")
def admin_delete_category(
cat_id: int,
db: Session = Depends(get_db),
admin: User = Depends(get_admin_user),
):
"""删除导航分类(级联删除链接)"""
cat = db.query(NavCategory).filter(NavCategory.id == cat_id).first()
if not cat:
raise HTTPException(status_code=404, detail="分类不存在")
db.query(NavLink).filter(NavLink.category_id == cat_id).delete()
db.delete(cat)
db.commit()
return {"message": "删除成功"}
@router.get("/admin/links")
def admin_list_links(
category_id: Optional[int] = None,
status: Optional[str] = None,
db: Session = Depends(get_db),
admin: User = Depends(get_admin_user),
):
"""获取导航链接列表"""
query = db.query(NavLink)
if category_id is not None:
query = query.filter(NavLink.category_id == category_id)
if status is not None:
query = query.filter(NavLink.status == status)
links = query.order_by(NavLink.sort_order, NavLink.id).all()
return [
{
"id": l.id, "category_id": l.category_id, "name": l.name,
"url": l.url, "icon": l.icon, "description": l.description,
"sort_order": l.sort_order, "is_active": l.is_active,
"status": l.status, "submitted_by": l.submitted_by,
"reject_reason": l.reject_reason or "",
}
for l in links
]
@router.post("/admin/links")
def admin_create_link(
data: NavLinkCreate,
db: Session = Depends(get_db),
admin: User = Depends(get_admin_user),
):
"""新增导航链接"""
cat = db.query(NavCategory).filter(NavCategory.id == data.category_id).first()
if not cat:
raise HTTPException(status_code=400, detail="分类不存在")
max_order = db.query(sa_func.max(NavLink.sort_order)).filter(NavLink.category_id == data.category_id).scalar() or 0
link = NavLink(
category_id=data.category_id, name=data.name, url=data.url,
icon=data.icon, description=data.description, sort_order=max_order + 1,
)
db.add(link)
db.commit()
db.refresh(link)
return {
"id": link.id, "category_id": link.category_id, "name": link.name,
"url": link.url, "icon": link.icon, "description": link.description,
"sort_order": link.sort_order, "is_active": link.is_active,
}
@router.put("/admin/links/{link_id}")
def admin_update_link(
link_id: int,
data: NavLinkUpdate,
db: Session = Depends(get_db),
admin: User = Depends(get_admin_user),
):
"""编辑导航链接"""
link = db.query(NavLink).filter(NavLink.id == link_id).first()
if not link:
raise HTTPException(status_code=404, detail="链接不存在")
if data.category_id is not None:
link.category_id = data.category_id
if data.name is not None:
link.name = data.name
if data.url is not None:
link.url = data.url
if data.icon is not None:
link.icon = data.icon
if data.description is not None:
link.description = data.description
if data.sort_order is not None:
link.sort_order = data.sort_order
if data.is_active is not None:
link.is_active = data.is_active
db.commit()
db.refresh(link)
return {
"id": link.id, "category_id": link.category_id, "name": link.name,
"url": link.url, "icon": link.icon, "description": link.description,
"sort_order": link.sort_order, "is_active": link.is_active,
}
@router.delete("/admin/links/{link_id}")
def admin_delete_link(
link_id: int,
db: Session = Depends(get_db),
admin: User = Depends(get_admin_user),
):
"""删除导航链接"""
link = db.query(NavLink).filter(NavLink.id == link_id).first()
if not link:
raise HTTPException(status_code=404, detail="链接不存在")
db.delete(link)
db.commit()
return {"message": "删除成功"}
@router.get("/admin/pending-count")
def admin_pending_count(
db: Session = Depends(get_db),
admin: User = Depends(get_admin_user),
):
"""获取待审核数量"""
count = db.query(sa_func.count(NavLink.id)).filter(NavLink.status == "pending").scalar() or 0
return {"count": count}
@router.put("/admin/links/{link_id}/review")
def admin_review_link(
link_id: int,
data: NavLinkReview,
db: Session = Depends(get_db),
admin: User = Depends(get_admin_user),
):
"""审核导航链接"""
link = db.query(NavLink).filter(NavLink.id == link_id).first()
if not link:
raise HTTPException(status_code=404, detail="链接不存在")
if data.action == "approve":
link.status = "approved"
link.is_active = True
link.reject_reason = ""
elif data.action == "reject":
link.status = "rejected"
link.is_active = False
link.reject_reason = data.reject_reason
else:
raise HTTPException(status_code=400, detail="无效操作,请使用 approve 或 reject")
db.commit()
db.refresh(link)
return {
"id": link.id, "status": link.status,
"is_active": link.is_active, "reject_reason": link.reject_reason or "",
}
# ========== 用户提交接口 ==========
@router.post("/submit")
def user_submit_link(
data: NavLinkSubmit,
db: Session = Depends(get_db),
user: User = Depends(get_current_user),
):
"""用户提交导航网站(需管理员审核)"""
cat = db.query(NavCategory).filter(NavCategory.id == data.category_id).first()
if not cat:
raise HTTPException(status_code=400, detail="分类不存在")
existing = db.query(NavLink).filter(NavLink.url == data.url).first()
if existing:
raise HTTPException(status_code=400, detail="该网站已被提交过")
link = NavLink(
category_id=data.category_id, name=data.name, url=data.url,
icon=data.icon, description=data.description,
status="pending", submitted_by=user.id, is_active=False,
)
db.add(link)
db.commit()
db.refresh(link)
return {
"id": link.id, "name": link.name, "url": link.url,
"status": link.status, "message": "提交成功,等待管理员审核",
}
@router.get("/my-submissions")
def user_my_submissions(
db: Session = Depends(get_db),
user: User = Depends(get_current_user),
):
"""用户查看自己提交的记录"""
links = db.query(NavLink).filter(NavLink.submitted_by == user.id).order_by(NavLink.id.desc()).all()
return [
{
"id": l.id, "name": l.name, "url": l.url, "icon": l.icon,
"description": l.description, "status": l.status,
"reject_reason": l.reject_reason or "",
"created_at": l.created_at.isoformat() if l.created_at else "",
}
for l in links
]
# ========== 公开接口 ==========
@router.get("/public")
def get_public_nav(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""获取所有启用分类及其下启用的链接"""
cats = db.query(NavCategory).filter(NavCategory.is_active == True).order_by(NavCategory.sort_order, NavCategory.id).all()
result = []
for c in cats:
links = (
db.query(NavLink)
.filter(NavLink.category_id == c.id, NavLink.is_active == True, NavLink.status == "approved")
.order_by(NavLink.sort_order, NavLink.id)
.all()
)
if links:
result.append({
"id": c.id, "name": c.name, "icon": c.icon,
"links": [
{"id": l.id, "name": l.name, "url": l.url, "icon": l.icon, "description": l.description}
for l in links
],
})
return result
@router.get("/public/categories")
def get_public_categories(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""获取所有启用分类(供用户提交时选择)"""
cats = db.query(NavCategory).filter(NavCategory.is_active == True).order_by(NavCategory.sort_order, NavCategory.id).all()
return [{"id": c.id, "name": c.name, "icon": c.icon} for c in cats]