feat: 新增客片案例与团队共创模块,重构玩法与配置
- 新增客片案例全流程功能,包含前台页面、管理端配置、后端API、数据库迁移与文档 - 新增团队共创详情页面与对应接口 - 重构玩法模块:将静态playData替换为动态API获取,拆分类型定义到playTypes.ts - 优化vite构建配置与环境变量处理,调整详情页操作栏文案 - 更新全量相关文档与配图,新增客片案例、团队共创API文档 - 新增测试用例,完善数据归一化逻辑 - 调整路由配置与环境变量示例文件
This commit is contained in:
1 parent
cda8069630
commit
7ba92f3c5d
76 files changed
+7970
-137
No files matched your search
@@ -16,20 +16,50 @@ from ..database import get_db
|
||||
from ..models import (
|
||||
AdminUser,
|
||||
AuditLog,
|
||||
ConciergeAdvisor,
|
||||
DemandFeatureCard,
|
||||
DemandForm,
|
||||
DemandHero,
|
||||
DestinationHero,
|
||||
HeroSlide,
|
||||
HomeExperience,
|
||||
HomeTeamBuilding,
|
||||
HomeWanfaRecommendation,
|
||||
HomeWildArchive,
|
||||
Lead,
|
||||
MediaAsset,
|
||||
SiteVersion,
|
||||
VehicleOption,
|
||||
WanfaCategory,
|
||||
WanfaRoute,
|
||||
utc_now,
|
||||
)
|
||||
from ..schemas import LeadQuery, LeadStatus, LeadStatusIn, LoginIn, SiteConfigPatchIn, SiteConfigReorderIn
|
||||
from ..schemas import (
|
||||
LeadQuery,
|
||||
LeadStatus,
|
||||
LeadStatusIn,
|
||||
LoginIn,
|
||||
HomeExperienceCreate,
|
||||
HomeExperiencePatch,
|
||||
HomeReorderIn,
|
||||
HomeTeamBuildingCreate,
|
||||
HomeTeamBuildingPatch,
|
||||
HomeWanfaRecommendationCreate,
|
||||
HomeWanfaRecommendationPatch,
|
||||
HomeWildArchiveCreate,
|
||||
HomeWildArchivePatch,
|
||||
SiteConfigPatchIn,
|
||||
SiteConfigReorderIn,
|
||||
WanfaCategoryCreate,
|
||||
WanfaCategoryPatch,
|
||||
WanfaReorderIn,
|
||||
WanfaRouteCreate,
|
||||
WanfaRoutePatch,
|
||||
ConciergeAdvisorCreate,
|
||||
ConciergeAdvisorPatch,
|
||||
)
|
||||
from ..seed import create_media, reset_guizhou_content
|
||||
from ..serializers import encode_value, lead_dict, model_dict
|
||||
from ..serializers import concierge_advisor_dict, encode_value, lead_dict, model_dict
|
||||
from .shared import site_config
|
||||
|
||||
|
||||
@@ -369,6 +399,688 @@ def apply_site_patch(module: str, config: dict, item, body: SiteConfigPatchIn) -
|
||||
setattr(item, field, value)
|
||||
|
||||
|
||||
def wanfa_route_dict(route: WanfaRoute) -> dict:
|
||||
return {
|
||||
"id": route.id,
|
||||
"title": route.title,
|
||||
"subtitle": route.subtitle,
|
||||
"image": route.image,
|
||||
"routeCount": route.routeCount,
|
||||
"demandKeyword": route.demandKeyword,
|
||||
}
|
||||
|
||||
|
||||
def wanfa_category_dict(category: WanfaCategory) -> dict:
|
||||
routes = sorted(category.routes or [], key=lambda item: item.sortOrder)
|
||||
return {
|
||||
"id": category.id,
|
||||
"label": category.label,
|
||||
"routes": [wanfa_route_dict(route) for route in routes],
|
||||
}
|
||||
|
||||
|
||||
def home_wanfa_recommendation_dict(item: HomeWanfaRecommendation) -> dict:
|
||||
category = item.category
|
||||
return model_dict(
|
||||
item,
|
||||
{
|
||||
"categoryLabel": category.label if category else "",
|
||||
"routeCount": len(category.routes) if category else 0,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def wanfa_category_or_error(db: Session, category_id: str) -> WanfaCategory:
|
||||
category = db.get(WanfaCategory, category_id)
|
||||
if not category:
|
||||
site_config_error(404, "玩法分类不存在", "WANFA_CATEGORY_NOT_FOUND", {"categoryId": category_id})
|
||||
return category
|
||||
|
||||
|
||||
def wanfa_route_or_error(db: Session, category: WanfaCategory, route_id: str) -> WanfaRoute:
|
||||
route = db.get(WanfaRoute, route_id)
|
||||
if not route or route.categoryId != category.id:
|
||||
site_config_error(
|
||||
404,
|
||||
"玩法路线不存在",
|
||||
"WANFA_ROUTE_NOT_FOUND",
|
||||
{"categoryId": category.id, "routeId": route_id},
|
||||
)
|
||||
return route
|
||||
|
||||
|
||||
def validate_wanfa_order(item_ids: list[str], current_ids: list[str], code: str) -> None:
|
||||
if len(item_ids) != len(current_ids) or len(set(item_ids)) != len(item_ids) or set(item_ids) != set(current_ids):
|
||||
site_config_error(400, "排序项目必须完整且不能重复", code)
|
||||
|
||||
|
||||
def apply_wanfa_order(items: list, item_ids: list[str]) -> list:
|
||||
by_id = {item.id: item for item in items}
|
||||
ordered = [by_id[item_id] for item_id in item_ids]
|
||||
for index, item in enumerate(ordered):
|
||||
item.sortOrder = index
|
||||
return ordered
|
||||
|
||||
|
||||
def home_error(status_code: int, message: str, code: str, details: dict | None = None) -> None:
|
||||
raise HTTPException(status_code=status_code, detail={"message": message, "code": code, "details": details or {}})
|
||||
|
||||
|
||||
def home_item_or_error(db: Session, model, item_id: str, label: str, code: str):
|
||||
item = db.get(model, item_id)
|
||||
if not item:
|
||||
home_error(404, f"{label}不存在", code, {"id": item_id})
|
||||
return item
|
||||
|
||||
|
||||
def home_items(db: Session, model) -> list:
|
||||
return db.scalars(select(model).order_by(model.sortOrder.asc())).all()
|
||||
|
||||
|
||||
def validate_home_order(item_ids: list[str], current_ids: list[str]) -> None:
|
||||
if len(item_ids) != len(current_ids) or len(set(item_ids)) != len(item_ids) or set(item_ids) != set(current_ids):
|
||||
home_error(400, "排序项目必须完整且不能重复", "HOME_REORDER_INVALID")
|
||||
|
||||
|
||||
def apply_home_order(items: list, item_ids: list[str]) -> list:
|
||||
by_id = {item.id: item for item in items}
|
||||
ordered = [by_id[item_id] for item_id in item_ids]
|
||||
for index, item in enumerate(ordered):
|
||||
item.sortOrder = index
|
||||
return ordered
|
||||
|
||||
|
||||
def create_home_item(db: Session, request: Request, body, model, entity: str):
|
||||
payload = body.model_dump(exclude_none=True)
|
||||
if body.sortOrder is None:
|
||||
payload["sortOrder"] = next_site_sort_order(db, model)
|
||||
item = model(**payload)
|
||||
db.add(item)
|
||||
db.flush()
|
||||
after = model_dict(item)
|
||||
audit(db, get_actor_id(request), "create", entity, item.id, after=after)
|
||||
db.commit()
|
||||
return after
|
||||
|
||||
|
||||
def update_home_item(db: Session, request: Request, item_id: str, body, model, entity: str, label: str, code: str):
|
||||
item = home_item_or_error(db, model, item_id, label, code)
|
||||
before = model_dict(item)
|
||||
for field, value in body.model_dump(exclude_unset=True).items():
|
||||
setattr(item, field, value)
|
||||
db.flush()
|
||||
after = model_dict(item)
|
||||
audit(db, get_actor_id(request), "update", entity, item.id, after=after, before=before)
|
||||
db.commit()
|
||||
return after
|
||||
|
||||
|
||||
def delete_home_item(db: Session, request: Request, item_id: str, model, entity: str, label: str, code: str):
|
||||
item = home_item_or_error(db, model, item_id, label, code)
|
||||
before = model_dict(item)
|
||||
db.delete(item)
|
||||
db.flush()
|
||||
for index, remaining in enumerate(home_items(db, model)):
|
||||
remaining.sortOrder = index
|
||||
result = {"id": item_id}
|
||||
audit(db, get_actor_id(request), "delete", entity, item_id, before=before, after=result)
|
||||
db.commit()
|
||||
return result
|
||||
|
||||
|
||||
def reorder_home_items(db: Session, request: Request, body: HomeReorderIn, model, entity: str):
|
||||
items = home_items(db, model)
|
||||
validate_home_order(body.itemIds, [item.id for item in items])
|
||||
ordered = apply_home_order(items, body.itemIds)
|
||||
db.flush()
|
||||
after = [model_dict(item) for item in ordered]
|
||||
audit(db, get_actor_id(request), "reorder", entity, after=after)
|
||||
db.commit()
|
||||
return {"items": after}
|
||||
|
||||
|
||||
@router.get("/home/experiences")
|
||||
def list_home_experiences(_user: AdminUser = Depends(require_admin), db: Session = Depends(get_db)):
|
||||
return {"items": [model_dict(item) for item in home_items(db, HomeExperience)]}
|
||||
|
||||
|
||||
@router.post("/home/experiences", status_code=status.HTTP_201_CREATED)
|
||||
def create_home_experience(
|
||||
body: HomeExperienceCreate,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
return create_home_item(db, request, body, HomeExperience, "home_experience")
|
||||
|
||||
|
||||
@router.patch("/home/experiences/reorder")
|
||||
def reorder_home_experiences(
|
||||
body: HomeReorderIn,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
return reorder_home_items(db, request, body, HomeExperience, "home_experience")
|
||||
|
||||
|
||||
@router.patch("/home/experiences/{experience_id}")
|
||||
def update_home_experience(
|
||||
experience_id: str,
|
||||
body: HomeExperiencePatch,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
return update_home_item(db, request, experience_id, body, HomeExperience, "home_experience", "体验推荐", "HOME_EXPERIENCE_NOT_FOUND")
|
||||
|
||||
|
||||
@router.delete("/home/experiences/{experience_id}")
|
||||
def delete_home_experience(
|
||||
experience_id: str,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
return delete_home_item(db, request, experience_id, HomeExperience, "home_experience", "体验推荐", "HOME_EXPERIENCE_NOT_FOUND")
|
||||
|
||||
|
||||
@router.get("/home/team-buildings")
|
||||
def list_home_team_buildings(_user: AdminUser = Depends(require_admin), db: Session = Depends(get_db)):
|
||||
return {"items": [model_dict(item) for item in home_items(db, HomeTeamBuilding)]}
|
||||
|
||||
|
||||
@router.post("/home/team-buildings", status_code=status.HTTP_201_CREATED)
|
||||
def create_home_team_building(
|
||||
body: HomeTeamBuildingCreate,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
return create_home_item(db, request, body, HomeTeamBuilding, "home_team_building")
|
||||
|
||||
|
||||
@router.patch("/home/team-buildings/reorder")
|
||||
def reorder_home_team_buildings(
|
||||
body: HomeReorderIn,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
return reorder_home_items(db, request, body, HomeTeamBuilding, "home_team_building")
|
||||
|
||||
|
||||
@router.patch("/home/team-buildings/{team_building_id}")
|
||||
def update_home_team_building(
|
||||
team_building_id: str,
|
||||
body: HomeTeamBuildingPatch,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
return update_home_item(db, request, team_building_id, body, HomeTeamBuilding, "home_team_building", "团队共创", "HOME_TEAM_BUILDING_NOT_FOUND")
|
||||
|
||||
|
||||
@router.delete("/home/team-buildings/{team_building_id}")
|
||||
def delete_home_team_building(
|
||||
team_building_id: str,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
return delete_home_item(db, request, team_building_id, HomeTeamBuilding, "home_team_building", "团队共创", "HOME_TEAM_BUILDING_NOT_FOUND")
|
||||
|
||||
|
||||
@router.get("/home/wild-archives")
|
||||
def list_home_wild_archives(_user: AdminUser = Depends(require_admin), db: Session = Depends(get_db)):
|
||||
return {"items": [model_dict(item) for item in home_items(db, HomeWildArchive)]}
|
||||
|
||||
|
||||
@router.post("/home/wild-archives", status_code=status.HTTP_201_CREATED)
|
||||
def create_home_wild_archive(
|
||||
body: HomeWildArchiveCreate,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
return create_home_item(db, request, body, HomeWildArchive, "home_wild_archive")
|
||||
|
||||
|
||||
@router.patch("/home/wild-archives/reorder")
|
||||
def reorder_home_wild_archives(
|
||||
body: HomeReorderIn,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
return reorder_home_items(db, request, body, HomeWildArchive, "home_wild_archive")
|
||||
|
||||
|
||||
@router.patch("/home/wild-archives/{archive_id}")
|
||||
def update_home_wild_archive(
|
||||
archive_id: str,
|
||||
body: HomeWildArchivePatch,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
return update_home_item(db, request, archive_id, body, HomeWildArchive, "home_wild_archive", "极境视界", "HOME_WILD_ARCHIVE_NOT_FOUND")
|
||||
|
||||
|
||||
@router.delete("/home/wild-archives/{archive_id}")
|
||||
def delete_home_wild_archive(
|
||||
archive_id: str,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
return delete_home_item(db, request, archive_id, HomeWildArchive, "home_wild_archive", "极境视界", "HOME_WILD_ARCHIVE_NOT_FOUND")
|
||||
|
||||
|
||||
def home_wanfa_recommendation_items(db: Session) -> list[HomeWanfaRecommendation]:
|
||||
return db.scalars(
|
||||
select(HomeWanfaRecommendation)
|
||||
.options(selectinload(HomeWanfaRecommendation.category).selectinload(WanfaCategory.routes))
|
||||
.order_by(HomeWanfaRecommendation.sortOrder.asc())
|
||||
).all()
|
||||
|
||||
|
||||
def home_wanfa_recommendation_or_error(db: Session, recommendation_id: str) -> HomeWanfaRecommendation:
|
||||
recommendation = db.get(HomeWanfaRecommendation, recommendation_id)
|
||||
if not recommendation:
|
||||
home_error(404, "首页玩法推荐不存在", "HOME_WANFA_RECOMMENDATION_NOT_FOUND", {"id": recommendation_id})
|
||||
return recommendation
|
||||
|
||||
|
||||
def ensure_home_wanfa_category_available(
|
||||
db: Session,
|
||||
category_id: str,
|
||||
recommendation_id: str | None = None,
|
||||
) -> WanfaCategory:
|
||||
category = wanfa_category_or_error(db, category_id)
|
||||
query = select(func.count()).select_from(HomeWanfaRecommendation).where(
|
||||
HomeWanfaRecommendation.categoryId == category.id
|
||||
)
|
||||
if recommendation_id:
|
||||
query = query.where(HomeWanfaRecommendation.id != recommendation_id)
|
||||
if db.scalar(query):
|
||||
home_error(409, "该玩法分类已经关联到首页推荐", "HOME_WANFA_CATEGORY_DUPLICATE", {"categoryId": category.id})
|
||||
return category
|
||||
|
||||
|
||||
@router.get("/home/play-recommendations")
|
||||
def list_home_wanfa_recommendations(_user: AdminUser = Depends(require_admin), db: Session = Depends(get_db)):
|
||||
return {"items": [home_wanfa_recommendation_dict(item) for item in home_wanfa_recommendation_items(db)]}
|
||||
|
||||
|
||||
@router.post("/home/play-recommendations", status_code=status.HTTP_201_CREATED)
|
||||
def create_home_wanfa_recommendation(
|
||||
body: HomeWanfaRecommendationCreate,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
category = ensure_home_wanfa_category_available(db, body.categoryId)
|
||||
next_sort = db.scalar(select(func.max(HomeWanfaRecommendation.sortOrder)))
|
||||
recommendation = HomeWanfaRecommendation(
|
||||
categoryId=category.id,
|
||||
category=category,
|
||||
isActive=body.isActive,
|
||||
sortOrder=body.sortOrder if body.sortOrder is not None else ((next_sort + 1) if next_sort is not None else 0),
|
||||
)
|
||||
db.add(recommendation)
|
||||
db.flush()
|
||||
after = home_wanfa_recommendation_dict(recommendation)
|
||||
audit(db, get_actor_id(request), "create", "home_wanfa_recommendation", recommendation.id, after=after)
|
||||
db.commit()
|
||||
return after
|
||||
|
||||
|
||||
@router.patch("/home/play-recommendations/reorder")
|
||||
def reorder_home_wanfa_recommendations(
|
||||
body: HomeReorderIn,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
items = home_wanfa_recommendation_items(db)
|
||||
validate_home_order(body.itemIds, [item.id for item in items])
|
||||
ordered = apply_home_order(items, body.itemIds)
|
||||
db.flush()
|
||||
after = [home_wanfa_recommendation_dict(item) for item in ordered]
|
||||
audit(db, get_actor_id(request), "reorder", "home_wanfa_recommendation", after=after)
|
||||
db.commit()
|
||||
return {"items": after}
|
||||
|
||||
|
||||
@router.patch("/home/play-recommendations/{recommendation_id}")
|
||||
def update_home_wanfa_recommendation(
|
||||
recommendation_id: str,
|
||||
body: HomeWanfaRecommendationPatch,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
recommendation = home_wanfa_recommendation_or_error(db, recommendation_id)
|
||||
before = home_wanfa_recommendation_dict(recommendation)
|
||||
if "categoryId" in body.model_fields_set:
|
||||
category = ensure_home_wanfa_category_available(db, body.categoryId, recommendation.id)
|
||||
recommendation.categoryId = category.id
|
||||
recommendation.category = category
|
||||
for field in ("isActive", "sortOrder"):
|
||||
if field in body.model_fields_set:
|
||||
setattr(recommendation, field, getattr(body, field))
|
||||
db.flush()
|
||||
after = home_wanfa_recommendation_dict(recommendation)
|
||||
audit(db, get_actor_id(request), "update", "home_wanfa_recommendation", recommendation.id, after=after, before=before)
|
||||
db.commit()
|
||||
return after
|
||||
|
||||
|
||||
@router.delete("/home/play-recommendations/{recommendation_id}")
|
||||
def delete_home_wanfa_recommendation(
|
||||
recommendation_id: str,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
recommendation = home_wanfa_recommendation_or_error(db, recommendation_id)
|
||||
before = home_wanfa_recommendation_dict(recommendation)
|
||||
db.delete(recommendation)
|
||||
db.flush()
|
||||
for index, remaining in enumerate(home_wanfa_recommendation_items(db)):
|
||||
remaining.sortOrder = index
|
||||
result = {"id": recommendation_id}
|
||||
audit(db, get_actor_id(request), "delete", "home_wanfa_recommendation", recommendation_id, before=before, after=result)
|
||||
db.commit()
|
||||
return result
|
||||
|
||||
|
||||
def concierge_error(status_code: int, message: str, code: str, details: dict | None = None) -> None:
|
||||
raise HTTPException(status_code=status_code, detail={"message": message, "code": code, "details": details or {}})
|
||||
|
||||
|
||||
def concierge_advisor_or_error(db: Session, advisor_id: str) -> ConciergeAdvisor:
|
||||
advisor = db.get(ConciergeAdvisor, advisor_id)
|
||||
if not advisor:
|
||||
concierge_error(404, "管家顾问不存在", "CONCIERGE_ADVISOR_NOT_FOUND", {"advisorId": advisor_id})
|
||||
return advisor
|
||||
|
||||
|
||||
def validate_concierge_order(item_ids: list[str], current_ids: list[str]) -> None:
|
||||
if len(item_ids) != len(current_ids) or len(set(item_ids)) != len(item_ids) or set(item_ids) != set(current_ids):
|
||||
concierge_error(400, "排序项目必须完整且不能重复", "CONCIERGE_REORDER_INVALID")
|
||||
|
||||
|
||||
def apply_concierge_order(items: list[ConciergeAdvisor], item_ids: list[str]) -> list[ConciergeAdvisor]:
|
||||
by_id = {item.id: item for item in items}
|
||||
ordered = [by_id[item_id] for item_id in item_ids]
|
||||
for index, item in enumerate(ordered):
|
||||
item.sortOrder = index
|
||||
return ordered
|
||||
|
||||
|
||||
@router.get("/wanfa/categories")
|
||||
def list_wanfa_categories(_user: AdminUser = Depends(require_admin), db: Session = Depends(get_db)):
|
||||
categories = db.scalars(
|
||||
select(WanfaCategory).options(selectinload(WanfaCategory.routes)).order_by(WanfaCategory.sortOrder.asc())
|
||||
).all()
|
||||
return {"categories": [wanfa_category_dict(category) for category in categories]}
|
||||
|
||||
|
||||
@router.post("/wanfa/categories", status_code=status.HTTP_201_CREATED)
|
||||
def create_wanfa_category(
|
||||
body: WanfaCategoryCreate,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
category = WanfaCategory(label=body.label, sortOrder=next_site_sort_order(db, WanfaCategory))
|
||||
db.add(category)
|
||||
db.flush()
|
||||
after = wanfa_category_dict(category)
|
||||
audit(db, get_actor_id(request), "create", "wanfa_category", category.id, after=after)
|
||||
db.commit()
|
||||
return after
|
||||
|
||||
|
||||
@router.patch("/wanfa/categories/reorder")
|
||||
def reorder_wanfa_categories(
|
||||
body: WanfaReorderIn,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
categories = db.scalars(select(WanfaCategory).order_by(WanfaCategory.sortOrder.asc())).all()
|
||||
validate_wanfa_order(body.itemIds, [category.id for category in categories], "WANFA_CATEGORY_REORDER_INVALID")
|
||||
ordered = apply_wanfa_order(categories, body.itemIds)
|
||||
db.flush()
|
||||
after = [wanfa_category_dict(category) for category in ordered]
|
||||
audit(db, get_actor_id(request), "reorder", "wanfa_category", after=after)
|
||||
db.commit()
|
||||
return {"items": after}
|
||||
|
||||
|
||||
@router.patch("/wanfa/categories/{category_id}")
|
||||
def update_wanfa_category(
|
||||
category_id: str,
|
||||
body: WanfaCategoryPatch,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
category = wanfa_category_or_error(db, category_id)
|
||||
before = wanfa_category_dict(category)
|
||||
if "label" in body.model_fields_set:
|
||||
category.label = body.label
|
||||
db.flush()
|
||||
after = wanfa_category_dict(category)
|
||||
audit(db, get_actor_id(request), "update", "wanfa_category", category.id, after=after, before=before)
|
||||
db.commit()
|
||||
return after
|
||||
|
||||
|
||||
@router.delete("/wanfa/categories/{category_id}")
|
||||
def delete_wanfa_category(
|
||||
category_id: str,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
category = wanfa_category_or_error(db, category_id)
|
||||
if category.routes:
|
||||
site_config_error(409, "玩法分类下仍有路线,不能删除", "WANFA_CATEGORY_NOT_EMPTY", {"categoryId": category.id})
|
||||
recommendation_count = db.scalar(
|
||||
select(func.count())
|
||||
.select_from(HomeWanfaRecommendation)
|
||||
.where(HomeWanfaRecommendation.categoryId == category.id)
|
||||
)
|
||||
if recommendation_count:
|
||||
site_config_error(
|
||||
409,
|
||||
"该玩法分类已被首页推荐,不能删除",
|
||||
"WANFA_CATEGORY_RECOMMENDED",
|
||||
{"categoryId": category.id},
|
||||
)
|
||||
before = wanfa_category_dict(category)
|
||||
db.delete(category)
|
||||
audit(db, get_actor_id(request), "delete", "wanfa_category", category.id, before=before)
|
||||
db.commit()
|
||||
return {"id": category.id}
|
||||
|
||||
|
||||
@router.post("/wanfa/categories/{category_id}/routes", status_code=status.HTTP_201_CREATED)
|
||||
def create_wanfa_route(
|
||||
category_id: str,
|
||||
body: WanfaRouteCreate,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
category = wanfa_category_or_error(db, category_id)
|
||||
next_sort = db.scalar(
|
||||
select(func.max(WanfaRoute.sortOrder)).where(WanfaRoute.categoryId == category.id)
|
||||
)
|
||||
route = WanfaRoute(
|
||||
categoryId=category.id,
|
||||
title=body.title,
|
||||
subtitle=body.subtitle,
|
||||
image=body.image,
|
||||
routeCount=body.routeCount,
|
||||
demandKeyword=body.demandKeyword,
|
||||
sortOrder=(next_sort + 1) if next_sort is not None else 0,
|
||||
)
|
||||
db.add(route)
|
||||
db.flush()
|
||||
after = wanfa_route_dict(route)
|
||||
audit(db, get_actor_id(request), "create", "wanfa_route", route.id, after=after)
|
||||
db.commit()
|
||||
return after
|
||||
|
||||
|
||||
@router.patch("/wanfa/categories/{category_id}/routes/reorder")
|
||||
def reorder_wanfa_routes(
|
||||
category_id: str,
|
||||
body: WanfaReorderIn,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
category = wanfa_category_or_error(db, category_id)
|
||||
routes = list(category.routes or [])
|
||||
validate_wanfa_order(body.itemIds, [route.id for route in routes], "WANFA_ROUTE_REORDER_INVALID")
|
||||
ordered = apply_wanfa_order(routes, body.itemIds)
|
||||
db.flush()
|
||||
after = [wanfa_route_dict(route) for route in ordered]
|
||||
audit(db, get_actor_id(request), "reorder", "wanfa_route", category.id, after=after)
|
||||
db.commit()
|
||||
return {"items": after}
|
||||
|
||||
|
||||
@router.patch("/wanfa/categories/{category_id}/routes/{route_id}")
|
||||
def update_wanfa_route(
|
||||
category_id: str,
|
||||
route_id: str,
|
||||
body: WanfaRoutePatch,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
category = wanfa_category_or_error(db, category_id)
|
||||
route = wanfa_route_or_error(db, category, route_id)
|
||||
before = wanfa_route_dict(route)
|
||||
for field in ("title", "subtitle", "image", "routeCount", "demandKeyword"):
|
||||
if field in body.model_fields_set:
|
||||
setattr(route, field, getattr(body, field))
|
||||
db.flush()
|
||||
after = wanfa_route_dict(route)
|
||||
audit(db, get_actor_id(request), "update", "wanfa_route", route.id, after=after, before=before)
|
||||
db.commit()
|
||||
return after
|
||||
|
||||
|
||||
@router.delete("/wanfa/categories/{category_id}/routes/{route_id}")
|
||||
def delete_wanfa_route(
|
||||
category_id: str,
|
||||
route_id: str,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
category = wanfa_category_or_error(db, category_id)
|
||||
route = wanfa_route_or_error(db, category, route_id)
|
||||
before = wanfa_route_dict(route)
|
||||
db.delete(route)
|
||||
audit(db, get_actor_id(request), "delete", "wanfa_route", route.id, before=before)
|
||||
db.commit()
|
||||
return {"id": route.id}
|
||||
|
||||
|
||||
@router.get("/concierge/advisors")
|
||||
def list_concierge_advisors(_user: AdminUser = Depends(require_admin), db: Session = Depends(get_db)):
|
||||
advisors = db.scalars(select(ConciergeAdvisor).order_by(ConciergeAdvisor.sortOrder.asc())).all()
|
||||
return {"advisors": [concierge_advisor_dict(advisor) for advisor in advisors]}
|
||||
|
||||
|
||||
@router.post("/concierge/advisors", status_code=status.HTTP_201_CREATED)
|
||||
def create_concierge_advisor(
|
||||
body: ConciergeAdvisorCreate,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
payload = body.model_dump(exclude_none=True)
|
||||
payload["details"] = body.details
|
||||
if body.sortOrder is None:
|
||||
payload["sortOrder"] = next_site_sort_order(db, ConciergeAdvisor)
|
||||
advisor = ConciergeAdvisor(**payload)
|
||||
db.add(advisor)
|
||||
db.flush()
|
||||
after = concierge_advisor_dict(advisor)
|
||||
audit(db, get_actor_id(request), "create", "concierge_advisor", advisor.id, after=after)
|
||||
db.commit()
|
||||
return after
|
||||
|
||||
|
||||
@router.patch("/concierge/advisors/reorder")
|
||||
def reorder_concierge_advisors(
|
||||
body: WanfaReorderIn,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
advisors = db.scalars(select(ConciergeAdvisor).order_by(ConciergeAdvisor.sortOrder.asc())).all()
|
||||
validate_concierge_order(body.itemIds, [advisor.id for advisor in advisors])
|
||||
ordered = apply_concierge_order(advisors, body.itemIds)
|
||||
db.flush()
|
||||
after = [concierge_advisor_dict(advisor) for advisor in ordered]
|
||||
audit(db, get_actor_id(request), "reorder", "concierge_advisor", after=after)
|
||||
db.commit()
|
||||
return {"items": after}
|
||||
|
||||
|
||||
@router.patch("/concierge/advisors/{advisor_id}")
|
||||
def update_concierge_advisor(
|
||||
advisor_id: str,
|
||||
body: ConciergeAdvisorPatch,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
advisor = concierge_advisor_or_error(db, advisor_id)
|
||||
before = concierge_advisor_dict(advisor)
|
||||
payload = body.model_dump(exclude_unset=True)
|
||||
if "details" in payload:
|
||||
payload["details"] = body.details or []
|
||||
for field, value in payload.items():
|
||||
setattr(advisor, field, value)
|
||||
db.flush()
|
||||
after = concierge_advisor_dict(advisor)
|
||||
audit(db, get_actor_id(request), "update", "concierge_advisor", advisor.id, after=after, before=before)
|
||||
db.commit()
|
||||
return after
|
||||
|
||||
|
||||
@router.delete("/concierge/advisors/{advisor_id}")
|
||||
def delete_concierge_advisor(
|
||||
advisor_id: str,
|
||||
request: Request,
|
||||
_user: AdminUser = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
advisor = concierge_advisor_or_error(db, advisor_id)
|
||||
before = concierge_advisor_dict(advisor)
|
||||
db.delete(advisor)
|
||||
db.flush()
|
||||
remaining = db.scalars(select(ConciergeAdvisor).order_by(ConciergeAdvisor.sortOrder.asc())).all()
|
||||
for index, item in enumerate(remaining):
|
||||
item.sortOrder = index
|
||||
result = {"id": advisor_id}
|
||||
audit(db, get_actor_id(request), "delete", "concierge_advisor", advisor_id, before=before, after=result)
|
||||
db.commit()
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/auth/login")
|
||||
def login(body: LoginIn, db: Session = Depends(get_db)):
|
||||
user = db.scalar(select(AdminUser).where(AdminUser.email == body.email))
|
||||
|
||||
Reference in new issue
Block a user