"""导航站路由""" 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]