feat: 新增客片案例与团队共创模块,重构玩法与配置

- 新增客片案例全流程功能,包含前台页面、管理端配置、后端API、数据库迁移与文档
- 新增团队共创详情页面与对应接口
- 重构玩法模块:将静态playData替换为动态API获取,拆分类型定义到playTypes.ts
- 优化vite构建配置与环境变量处理,调整详情页操作栏文案
- 更新全量相关文档与配图,新增客片案例、团队共创API文档
- 新增测试用例,完善数据归一化逻辑
- 调整路由配置与环境变量示例文件
This commit is contained in:
duanshuwen committed 2026-08-19 20:23:04 +08:00
1 parent cda8069630
commit 7ba92f3c5d
76 files changed
+7970 -137

No files matched your search

+714 -2
View File
@@ -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))