Initial commit: hulai admin API backend
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
+361
@@ -0,0 +1,361 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.about import (
|
||||
AboutStory, AboutSubsidiary, AboutCertification, AboutTrademark,
|
||||
AboutCopyright, AboutGuarantee, AboutXiaohongshu, AboutTeam,
|
||||
AboutCulture, AboutCultureValue,
|
||||
AboutFounderDetail, AboutFounder, AboutMilestone,
|
||||
AboutServicePrinciple, AboutDifferentiation,
|
||||
AboutServiceJourney, AboutServiceMoment, AboutStats,
|
||||
)
|
||||
from app.schemas.about import (
|
||||
StorySchema, SubsidiarySchema, CertificationSchema, TrademarkSchema,
|
||||
CopyrightSchema, GuaranteeSchema, XiaohongshuSchema, TeamSchema,
|
||||
CultureSchema, FounderDetailSchema, FounderSchema, MilestoneSchema,
|
||||
ServicePrincipleSchema, DifferentiationSchema,
|
||||
ServiceJourneySchema, ServiceMomentSchema, AboutStatsSchema,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/about", tags=["关于我们"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
# --- Story ---
|
||||
@router.get("/story", response_model=StorySchema)
|
||||
def get_story(db: Session = Depends(get_db)):
|
||||
story = db.query(AboutStory).first()
|
||||
if not story:
|
||||
return StorySchema()
|
||||
return StorySchema(
|
||||
title=story.title, content=story.content, founding_moment=story.founding_moment,
|
||||
totem_description=story.totem_description, totem_tagline=story.totem_tagline,
|
||||
)
|
||||
|
||||
|
||||
@router.put("/story")
|
||||
def update_story(req: StorySchema, db: Session = Depends(get_db)):
|
||||
story = db.query(AboutStory).first()
|
||||
if not story:
|
||||
story = AboutStory()
|
||||
db.add(story)
|
||||
story.title = req.title
|
||||
story.content = req.content
|
||||
story.founding_moment = req.founding_moment
|
||||
story.totem_description = req.totem_description
|
||||
story.totem_tagline = req.totem_tagline
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
# --- Subsidiaries ---
|
||||
@router.get("/subsidiaries", response_model=list[SubsidiarySchema])
|
||||
def get_subsidiaries(db: Session = Depends(get_db)):
|
||||
items = db.query(AboutSubsidiary).order_by(AboutSubsidiary.sort_order).all()
|
||||
return [SubsidiarySchema(name=s.name, role=s.role, established=s.established) for s in items]
|
||||
|
||||
|
||||
@router.put("/subsidiaries")
|
||||
def update_subsidiaries(req: list[SubsidiarySchema], db: Session = Depends(get_db)):
|
||||
db.query(AboutSubsidiary).delete()
|
||||
for i, s in enumerate(req):
|
||||
db.add(AboutSubsidiary(name=s.name, role=s.role, established=s.established, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
# --- Certifications ---
|
||||
@router.get("/certifications", response_model=list[CertificationSchema])
|
||||
def get_certifications(db: Session = Depends(get_db)):
|
||||
items = db.query(AboutCertification).order_by(AboutCertification.sort_order).all()
|
||||
return [CertificationSchema(title=c.title, detail=c.detail) for c in items]
|
||||
|
||||
|
||||
@router.put("/certifications")
|
||||
def update_certifications(req: list[CertificationSchema], db: Session = Depends(get_db)):
|
||||
db.query(AboutCertification).delete()
|
||||
for i, c in enumerate(req):
|
||||
db.add(AboutCertification(title=c.title, detail=c.detail, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
# --- Trademark ---
|
||||
@router.get("/trademark", response_model=TrademarkSchema)
|
||||
def get_trademark(db: Session = Depends(get_db)):
|
||||
tm = db.query(AboutTrademark).first()
|
||||
if not tm:
|
||||
return TrademarkSchema()
|
||||
return TrademarkSchema(name=tm.name, scope=tm.scope, holder=tm.holder, description=tm.description)
|
||||
|
||||
|
||||
@router.put("/trademark")
|
||||
def update_trademark(req: TrademarkSchema, db: Session = Depends(get_db)):
|
||||
tm = db.query(AboutTrademark).first()
|
||||
if not tm:
|
||||
tm = AboutTrademark()
|
||||
db.add(tm)
|
||||
tm.name = req.name
|
||||
tm.scope = req.scope
|
||||
tm.holder = req.holder
|
||||
tm.description = req.description
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
# --- Copyrights ---
|
||||
@router.get("/copyrights", response_model=list[CopyrightSchema])
|
||||
def get_copyrights(db: Session = Depends(get_db)):
|
||||
items = db.query(AboutCopyright).order_by(AboutCopyright.sort_order).all()
|
||||
return [CopyrightSchema(name=c.name, reg_no=c.reg_no, category=c.category, holder=c.holder, date=c.date, description=c.description) for c in items]
|
||||
|
||||
|
||||
@router.put("/copyrights")
|
||||
def update_copyrights(req: list[CopyrightSchema], db: Session = Depends(get_db)):
|
||||
db.query(AboutCopyright).delete()
|
||||
for i, c in enumerate(req):
|
||||
db.add(AboutCopyright(name=c.name, reg_no=c.reg_no, category=c.category, holder=c.holder, date=c.date, description=c.description, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
# --- Guarantees ---
|
||||
@router.get("/guarantees", response_model=list[GuaranteeSchema])
|
||||
def get_guarantees(db: Session = Depends(get_db)):
|
||||
items = db.query(AboutGuarantee).order_by(AboutGuarantee.sort_order).all()
|
||||
return [GuaranteeSchema(title=g.title, detail=g.detail) for g in items]
|
||||
|
||||
|
||||
@router.put("/guarantees")
|
||||
def update_guarantees(req: list[GuaranteeSchema], db: Session = Depends(get_db)):
|
||||
db.query(AboutGuarantee).delete()
|
||||
for i, g in enumerate(req):
|
||||
db.add(AboutGuarantee(title=g.title, detail=g.detail, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
# --- Xiaohongshu ---
|
||||
@router.get("/xiaohongshu", response_model=XiaohongshuSchema)
|
||||
def get_xiaohongshu(db: Session = Depends(get_db)):
|
||||
xhs = db.query(AboutXiaohongshu).first()
|
||||
if not xhs:
|
||||
return XiaohongshuSchema()
|
||||
return XiaohongshuSchema(
|
||||
account=xhs.account, verified=xhs.verified, verified_type=xhs.verified_type,
|
||||
followers=xhs.followers, likes=xhs.likes, awards=xhs.awards or [],
|
||||
tagline=xhs.tagline, tags=xhs.tags or [], description=xhs.description,
|
||||
)
|
||||
|
||||
|
||||
@router.put("/xiaohongshu")
|
||||
def update_xiaohongshu(req: XiaohongshuSchema, db: Session = Depends(get_db)):
|
||||
xhs = db.query(AboutXiaohongshu).first()
|
||||
if not xhs:
|
||||
xhs = AboutXiaohongshu()
|
||||
db.add(xhs)
|
||||
for key, val in req.model_dump().items():
|
||||
setattr(xhs, key, val)
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
# --- Team ---
|
||||
@router.get("/team", response_model=TeamSchema)
|
||||
def get_team(db: Session = Depends(get_db)):
|
||||
team = db.query(AboutTeam).first()
|
||||
if not team:
|
||||
return TeamSchema()
|
||||
return TeamSchema(summary=team.summary)
|
||||
|
||||
|
||||
@router.put("/team")
|
||||
def update_team(req: TeamSchema, db: Session = Depends(get_db)):
|
||||
team = db.query(AboutTeam).first()
|
||||
if not team:
|
||||
team = AboutTeam()
|
||||
db.add(team)
|
||||
team.summary = req.summary
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
# --- Culture ---
|
||||
@router.get("/culture", response_model=CultureSchema)
|
||||
def get_culture(db: Session = Depends(get_db)):
|
||||
culture = db.query(AboutCulture).first()
|
||||
if not culture:
|
||||
return CultureSchema()
|
||||
return CultureSchema(
|
||||
core=culture.core,
|
||||
transparency=culture.transparency,
|
||||
trust=culture.trust,
|
||||
grief_award_name=culture.grief_award_name,
|
||||
grief_award_description=culture.grief_award_description,
|
||||
values=[{"name": v.name, "expression": v.expression} for v in culture.values],
|
||||
)
|
||||
|
||||
|
||||
@router.put("/culture")
|
||||
def update_culture(req: CultureSchema, db: Session = Depends(get_db)):
|
||||
culture = db.query(AboutCulture).first()
|
||||
if not culture:
|
||||
culture = AboutCulture()
|
||||
db.add(culture)
|
||||
db.flush()
|
||||
culture.core = req.core
|
||||
culture.transparency = req.transparency
|
||||
culture.trust = req.trust
|
||||
culture.grief_award_name = req.grief_award_name
|
||||
culture.grief_award_description = req.grief_award_description
|
||||
|
||||
db.query(AboutCultureValue).filter_by(culture_id=culture.id).delete()
|
||||
for i, v in enumerate(req.values):
|
||||
db.add(AboutCultureValue(culture_id=culture.id, name=v.name, expression=v.expression, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
# --- Founder Detail (单个创始人详细) ---
|
||||
@router.get("/founder-detail", response_model=FounderDetailSchema)
|
||||
def get_founder_detail(db: Session = Depends(get_db)):
|
||||
fd = db.query(AboutFounderDetail).first()
|
||||
if not fd:
|
||||
return FounderDetailSchema()
|
||||
return FounderDetailSchema(
|
||||
name=fd.name, title=fd.title, brand_founded=fd.brand_founded,
|
||||
years_in_hulunbuir=fd.years_in_hulunbuir, background=fd.background,
|
||||
expertise=fd.expertise or [], media_presence=fd.media_presence or [],
|
||||
)
|
||||
|
||||
|
||||
@router.put("/founder-detail")
|
||||
def update_founder_detail(req: FounderDetailSchema, db: Session = Depends(get_db)):
|
||||
fd = db.query(AboutFounderDetail).first()
|
||||
if not fd:
|
||||
fd = AboutFounderDetail()
|
||||
db.add(fd)
|
||||
fd.name = req.name
|
||||
fd.title = req.title
|
||||
fd.brand_founded = req.brand_founded
|
||||
fd.years_in_hulunbuir = req.years_in_hulunbuir
|
||||
fd.background = req.background
|
||||
fd.expertise = req.expertise
|
||||
fd.media_presence = req.media_presence
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
# --- Founders (三位创始人简介列表) ---
|
||||
@router.get("/founders", response_model=list[FounderSchema])
|
||||
def get_founders(db: Session = Depends(get_db)):
|
||||
items = db.query(AboutFounder).order_by(AboutFounder.sort_order).all()
|
||||
return [FounderSchema(name=f.name, title=f.title, story=f.story) for f in items]
|
||||
|
||||
|
||||
@router.put("/founders")
|
||||
def update_founders(req: list[FounderSchema], db: Session = Depends(get_db)):
|
||||
db.query(AboutFounder).delete()
|
||||
for i, f in enumerate(req):
|
||||
db.add(AboutFounder(name=f.name, title=f.title, story=f.story, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
# --- Milestones ---
|
||||
@router.get("/milestones", response_model=list[MilestoneSchema])
|
||||
def get_milestones(db: Session = Depends(get_db)):
|
||||
items = db.query(AboutMilestone).order_by(AboutMilestone.sort_order).all()
|
||||
return [MilestoneSchema(year=m.year, event=m.event) for m in items]
|
||||
|
||||
|
||||
@router.put("/milestones")
|
||||
def update_milestones(req: list[MilestoneSchema], db: Session = Depends(get_db)):
|
||||
db.query(AboutMilestone).delete()
|
||||
for i, m in enumerate(req):
|
||||
db.add(AboutMilestone(year=m.year, event=m.event, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
# --- Service Principles ---
|
||||
@router.get("/service-principles", response_model=list[ServicePrincipleSchema])
|
||||
def get_service_principles(db: Session = Depends(get_db)):
|
||||
items = db.query(AboutServicePrinciple).order_by(AboutServicePrinciple.sort_order).all()
|
||||
return [ServicePrincipleSchema(text=p.text) for p in items]
|
||||
|
||||
|
||||
@router.put("/service-principles")
|
||||
def update_service_principles(req: list[ServicePrincipleSchema], db: Session = Depends(get_db)):
|
||||
db.query(AboutServicePrinciple).delete()
|
||||
for i, p in enumerate(req):
|
||||
db.add(AboutServicePrinciple(text=p.text, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
# --- Differentiation ---
|
||||
@router.get("/differentiation", response_model=list[DifferentiationSchema])
|
||||
def get_differentiation(db: Session = Depends(get_db)):
|
||||
items = db.query(AboutDifferentiation).order_by(AboutDifferentiation.sort_order).all()
|
||||
return [DifferentiationSchema(name=d.name, detail=d.detail) for d in items]
|
||||
|
||||
|
||||
@router.put("/differentiation")
|
||||
def update_differentiation(req: list[DifferentiationSchema], db: Session = Depends(get_db)):
|
||||
db.query(AboutDifferentiation).delete()
|
||||
for i, d in enumerate(req):
|
||||
db.add(AboutDifferentiation(name=d.name, detail=d.detail, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
# --- Service Journey ---
|
||||
@router.get("/service-journey", response_model=ServiceJourneySchema)
|
||||
def get_service_journey(db: Session = Depends(get_db)):
|
||||
sj = db.query(AboutServiceJourney).first()
|
||||
if not sj:
|
||||
return ServiceJourneySchema()
|
||||
return ServiceJourneySchema(
|
||||
title=sj.title,
|
||||
subtitle=sj.subtitle,
|
||||
moments=[ServiceMomentSchema(step=m.step, name=m.name, detail=m.detail) for m in sj.moments],
|
||||
)
|
||||
|
||||
|
||||
@router.put("/service-journey")
|
||||
def update_service_journey(req: ServiceJourneySchema, db: Session = Depends(get_db)):
|
||||
sj = db.query(AboutServiceJourney).first()
|
||||
if not sj:
|
||||
sj = AboutServiceJourney()
|
||||
db.add(sj)
|
||||
db.flush()
|
||||
sj.title = req.title
|
||||
sj.subtitle = req.subtitle
|
||||
db.query(AboutServiceMoment).filter_by(journey_id=sj.id).delete()
|
||||
for i, m in enumerate(req.moments):
|
||||
db.add(AboutServiceMoment(journey_id=sj.id, step=m.step, name=m.name, detail=m.detail, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
# --- Stats ---
|
||||
@router.get("/stats", response_model=AboutStatsSchema)
|
||||
def get_stats(db: Session = Depends(get_db)):
|
||||
s = db.query(AboutStats).first()
|
||||
if not s:
|
||||
return AboutStatsSchema()
|
||||
return AboutStatsSchema(data=s.data or {})
|
||||
|
||||
|
||||
@router.put("/stats")
|
||||
def update_stats(req: AboutStatsSchema, db: Session = Depends(get_db)):
|
||||
s = db.query(AboutStats).first()
|
||||
if not s:
|
||||
s = AboutStats()
|
||||
db.add(s)
|
||||
s.data = req.data
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import verify_password, create_access_token, hash_password, get_current_user
|
||||
from app.models.user import AdminUser
|
||||
from app.schemas.user import LoginRequest, LoginResponse, UserInfo, ChangePasswordRequest, CreateUserRequest, UpdateUserRequest, ResetPasswordRequest, UserListResponse
|
||||
|
||||
router = APIRouter(prefix="/api/auth", tags=["认证"])
|
||||
|
||||
|
||||
@router.post("/login", response_model=LoginResponse)
|
||||
def login(req: LoginRequest, db: Session = Depends(get_db)):
|
||||
user = db.query(AdminUser).filter(AdminUser.username == req.username).first()
|
||||
if not user or not verify_password(req.password, user.password_hash):
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="用户名或密码错误")
|
||||
if not user.is_active:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="账号已被禁用")
|
||||
token = create_access_token({"sub": user.username, "role": user.role})
|
||||
return LoginResponse(access_token=token)
|
||||
|
||||
|
||||
@router.get("/me", response_model=UserInfo)
|
||||
def get_me(current_user: AdminUser = Depends(get_current_user)):
|
||||
return current_user
|
||||
|
||||
|
||||
@router.put("/password")
|
||||
def change_password(req: ChangePasswordRequest, current_user: AdminUser = Depends(get_current_user), db: Session = Depends(get_db)):
|
||||
if not verify_password(req.old_password, current_user.password_hash):
|
||||
raise HTTPException(status_code=400, detail="原密码错误")
|
||||
current_user.password_hash = hash_password(req.new_password)
|
||||
db.commit()
|
||||
return {"message": "密码修改成功"}
|
||||
|
||||
|
||||
# ===== 用户管理(仅 admin 角色可用)=====
|
||||
|
||||
def require_admin(current_user: AdminUser = Depends(get_current_user)):
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="仅管理员可操作")
|
||||
return current_user
|
||||
|
||||
|
||||
@router.get("/users", response_model=list[UserListResponse])
|
||||
def list_users(db: Session = Depends(get_db), _: AdminUser = Depends(require_admin)):
|
||||
users = db.query(AdminUser).order_by(AdminUser.id).all()
|
||||
return [UserListResponse.model_validate(u) for u in users]
|
||||
|
||||
|
||||
@router.post("/users", response_model=UserListResponse, status_code=201)
|
||||
def create_user(req: CreateUserRequest, db: Session = Depends(get_db), _: AdminUser = Depends(require_admin)):
|
||||
if db.query(AdminUser).filter(AdminUser.username == req.username).first():
|
||||
raise HTTPException(status_code=400, detail="用户名已存在")
|
||||
user = AdminUser(
|
||||
username=req.username,
|
||||
password_hash=hash_password(req.password),
|
||||
display_name=req.display_name,
|
||||
role=req.role,
|
||||
is_active=True,
|
||||
)
|
||||
db.add(user)
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
return UserListResponse.model_validate(user)
|
||||
|
||||
|
||||
@router.put("/users/{user_id}", response_model=UserListResponse)
|
||||
def update_user(user_id: int, req: UpdateUserRequest, db: Session = Depends(get_db), _: AdminUser = Depends(require_admin)):
|
||||
user = db.query(AdminUser).get(user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
if req.display_name is not None:
|
||||
user.display_name = req.display_name
|
||||
if req.role is not None:
|
||||
user.role = req.role
|
||||
if req.is_active is not None:
|
||||
user.is_active = req.is_active
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
return UserListResponse.model_validate(user)
|
||||
|
||||
|
||||
@router.put("/users/{user_id}/reset-password")
|
||||
def reset_password(user_id: int, req: ResetPasswordRequest, db: Session = Depends(get_db), _: AdminUser = Depends(require_admin)):
|
||||
user = db.query(AdminUser).get(user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
user.password_hash = hash_password(req.new_password)
|
||||
db.commit()
|
||||
return {"message": f"已重置 {user.username} 的密码"}
|
||||
|
||||
|
||||
@router.delete("/users/{user_id}")
|
||||
def delete_user(user_id: int, db: Session = Depends(get_db), current_user: AdminUser = Depends(require_admin)):
|
||||
user = db.query(AdminUser).get(user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
if user.id == current_user.id:
|
||||
raise HTTPException(status_code=400, detail="不能删除自己")
|
||||
db.delete(user)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
@@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func as sqlfunc
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.blog import Blog
|
||||
from app.schemas.blog import BlogSchema, BlogResponse, BlogListResponse
|
||||
|
||||
router = APIRouter(prefix="/api/blog", tags=["博客文章"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
@router.get("/", response_model=BlogListResponse)
|
||||
def list_blogs(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
keyword: str = Query("", description="搜索关键词"),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
query = db.query(Blog)
|
||||
if keyword:
|
||||
query = query.filter(Blog.title.contains(keyword) | Blog.summary.contains(keyword))
|
||||
total = query.count()
|
||||
items = query.order_by(Blog.sort_order, Blog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||
return BlogListResponse(
|
||||
items=[BlogResponse.model_validate(b) for b in items],
|
||||
total=total,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/", response_model=BlogResponse, status_code=201)
|
||||
def create_blog(req: BlogSchema, db: Session = Depends(get_db)):
|
||||
if db.query(Blog).filter(Blog.slug == req.slug).first():
|
||||
raise HTTPException(status_code=400, detail="该 slug 已存在")
|
||||
max_order = db.query(sqlfunc.max(Blog.sort_order)).scalar() or 0
|
||||
blog = Blog(**req.model_dump(), sort_order=max_order + 1)
|
||||
db.add(blog)
|
||||
db.commit()
|
||||
db.refresh(blog)
|
||||
return BlogResponse.model_validate(blog)
|
||||
|
||||
|
||||
@router.put("/{blog_id}", response_model=BlogResponse)
|
||||
def update_blog(blog_id: int, req: BlogSchema, db: Session = Depends(get_db)):
|
||||
blog = db.query(Blog).get(blog_id)
|
||||
if not blog:
|
||||
raise HTTPException(status_code=404, detail="文章不存在")
|
||||
existing = db.query(Blog).filter(Blog.slug == req.slug, Blog.id != blog_id).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="该 slug 已被其他文章使用")
|
||||
for key, val in req.model_dump().items():
|
||||
setattr(blog, key, val)
|
||||
db.commit()
|
||||
db.refresh(blog)
|
||||
return BlogResponse.model_validate(blog)
|
||||
|
||||
|
||||
@router.delete("/{blog_id}")
|
||||
def delete_blog(blog_id: int, db: Session = Depends(get_db)):
|
||||
blog = db.query(Blog).get(blog_id)
|
||||
if not blog:
|
||||
raise HTTPException(status_code=404, detail="文章不存在")
|
||||
db.delete(blog)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
|
||||
|
||||
@router.put("/reorder/batch")
|
||||
def reorder_blogs(ids: list[int], db: Session = Depends(get_db)):
|
||||
for i, bid in enumerate(ids):
|
||||
db.query(Blog).filter(Blog.id == bid).update({"sort_order": i})
|
||||
db.commit()
|
||||
return {"message": "排序更新成功"}
|
||||
@@ -0,0 +1,59 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.brand import Brand, BrandTrustStat, BrandDifferentiator
|
||||
from app.schemas.brand import BrandFullSchema, BrandUpdateRequest
|
||||
|
||||
router = APIRouter(prefix="/api/brand", tags=["品牌"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
@router.get("/", response_model=BrandFullSchema)
|
||||
def get_brand(db: Session = Depends(get_db)):
|
||||
brand = db.query(Brand).first()
|
||||
if not brand:
|
||||
raise HTTPException(status_code=404, detail="品牌信息未初始化")
|
||||
return BrandFullSchema(
|
||||
id=brand.id,
|
||||
name=brand.name,
|
||||
full_name=brand.full_name,
|
||||
domain=brand.domain,
|
||||
url=brand.url,
|
||||
slogan_emotional=brand.slogan_emotional,
|
||||
slogan_functional=brand.slogan_functional,
|
||||
icp_entity=brand.icp_entity,
|
||||
icp=brand.icp,
|
||||
icp_url=brand.icp_url,
|
||||
e_contract=brand.e_contract,
|
||||
trust_stats=[{"value": s.value, "unit": s.unit, "label": s.label} for s in brand.trust_stats],
|
||||
differentiators=[{"title": d.title, "description": d.description} for d in brand.differentiators],
|
||||
cta_buttons=brand.cta_buttons,
|
||||
conversion_path=brand.conversion_path,
|
||||
)
|
||||
|
||||
|
||||
@router.put("/")
|
||||
def update_brand(req: BrandUpdateRequest, db: Session = Depends(get_db)):
|
||||
brand = db.query(Brand).first()
|
||||
if not brand:
|
||||
brand = Brand()
|
||||
db.add(brand)
|
||||
|
||||
for key, val in req.brand.model_dump().items():
|
||||
setattr(brand, key, val)
|
||||
db.flush()
|
||||
|
||||
# Replace trust stats
|
||||
db.query(BrandTrustStat).filter(BrandTrustStat.brand_id == brand.id).delete()
|
||||
for i, stat in enumerate(req.trust_stats):
|
||||
db.add(BrandTrustStat(brand_id=brand.id, value=stat.value, unit=stat.unit, label=stat.label, sort_order=i))
|
||||
|
||||
# Replace differentiators
|
||||
db.query(BrandDifferentiator).filter(BrandDifferentiator.brand_id == brand.id).delete()
|
||||
for i, diff in enumerate(req.differentiators):
|
||||
db.add(BrandDifferentiator(brand_id=brand.id, title=diff.title, description=diff.description, sort_order=i))
|
||||
|
||||
db.commit()
|
||||
return {"message": "品牌信息更新成功"}
|
||||
@@ -0,0 +1,65 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.calendar import CalendarMonth
|
||||
from app.schemas.calendar import CalendarMonthSchema, CalendarMonthResponse
|
||||
|
||||
router = APIRouter(prefix="/api/calendar", tags=["行程日历"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
MONTH_NAMES = ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"]
|
||||
|
||||
|
||||
@router.get("/", response_model=list[CalendarMonthResponse])
|
||||
def list_calendar(
|
||||
year: int = Query(..., description="年份,如 2025"),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
items = db.query(CalendarMonth).filter(CalendarMonth.year == year).order_by(CalendarMonth.month).all()
|
||||
# 如果该年份还没有数据,自动初始化12个月份
|
||||
if not items:
|
||||
for m in range(1, 13):
|
||||
month_record = CalendarMonth(
|
||||
year=year,
|
||||
month=m,
|
||||
title=MONTH_NAMES[m - 1],
|
||||
highlights=[],
|
||||
events=[],
|
||||
is_available=False,
|
||||
)
|
||||
db.add(month_record)
|
||||
db.commit()
|
||||
items = db.query(CalendarMonth).filter(CalendarMonth.year == year).order_by(CalendarMonth.month).all()
|
||||
return [CalendarMonthResponse.model_validate(item) for item in items]
|
||||
|
||||
|
||||
@router.put("/{year}/{month}", response_model=CalendarMonthResponse)
|
||||
def update_calendar_month(year: int, month: int, req: CalendarMonthSchema, db: Session = Depends(get_db)):
|
||||
item = db.query(CalendarMonth).filter(CalendarMonth.year == year, CalendarMonth.month == month).first()
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="日历月份不存在")
|
||||
for key, val in req.model_dump().items():
|
||||
setattr(item, key, val)
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
return CalendarMonthResponse.model_validate(item)
|
||||
|
||||
|
||||
@router.post("/init")
|
||||
def init_calendar_year(year: int = Query(..., description="要初始化的年份"), db: Session = Depends(get_db)):
|
||||
existing = db.query(CalendarMonth).filter(CalendarMonth.year == year).count()
|
||||
if existing > 0:
|
||||
return {"message": f"{year}年日历已存在,共{existing}条记录"}
|
||||
for m in range(1, 13):
|
||||
db.add(CalendarMonth(
|
||||
year=year,
|
||||
month=m,
|
||||
title=MONTH_NAMES[m - 1],
|
||||
highlights=[],
|
||||
events=[],
|
||||
is_available=False,
|
||||
))
|
||||
db.commit()
|
||||
return {"message": f"{year}年12个月份初始化成功"}
|
||||
@@ -0,0 +1,68 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func as sqlfunc
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.contact import ContactChannel, ContactConfig
|
||||
from app.schemas.contact import ContactChannelSchema, ContactChannelResponse, ContactConfigSchema
|
||||
|
||||
router = APIRouter(prefix="/api/contact", tags=["联系方式"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
@router.get("/channels", response_model=list[ContactChannelResponse])
|
||||
def list_channels(db: Session = Depends(get_db)):
|
||||
channels = db.query(ContactChannel).order_by(ContactChannel.sort_order).all()
|
||||
return [ContactChannelResponse.model_validate(c) for c in channels]
|
||||
|
||||
|
||||
@router.post("/channels", response_model=ContactChannelResponse, status_code=201)
|
||||
def create_channel(req: ContactChannelSchema, db: Session = Depends(get_db)):
|
||||
max_order = db.query(sqlfunc.max(ContactChannel.sort_order)).scalar() or 0
|
||||
ch = ContactChannel(**req.model_dump(), sort_order=max_order + 1)
|
||||
db.add(ch)
|
||||
db.commit()
|
||||
db.refresh(ch)
|
||||
return ContactChannelResponse.model_validate(ch)
|
||||
|
||||
|
||||
@router.put("/channels/{ch_id}", response_model=ContactChannelResponse)
|
||||
def update_channel(ch_id: int, req: ContactChannelSchema, db: Session = Depends(get_db)):
|
||||
ch = db.query(ContactChannel).get(ch_id)
|
||||
if not ch:
|
||||
raise HTTPException(status_code=404, detail="联系方式不存在")
|
||||
for key, val in req.model_dump().items():
|
||||
setattr(ch, key, val)
|
||||
db.commit()
|
||||
db.refresh(ch)
|
||||
return ContactChannelResponse.model_validate(ch)
|
||||
|
||||
|
||||
@router.delete("/channels/{ch_id}")
|
||||
def delete_channel(ch_id: int, db: Session = Depends(get_db)):
|
||||
ch = db.query(ContactChannel).get(ch_id)
|
||||
if not ch:
|
||||
raise HTTPException(status_code=404, detail="联系方式不存在")
|
||||
db.delete(ch)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
|
||||
|
||||
@router.get("/config", response_model=ContactConfigSchema)
|
||||
def get_config(db: Session = Depends(get_db)):
|
||||
config = db.query(ContactConfig).first()
|
||||
if not config:
|
||||
return ContactConfigSchema()
|
||||
return ContactConfigSchema(security_notice=config.security_notice)
|
||||
|
||||
|
||||
@router.put("/config")
|
||||
def update_config(req: ContactConfigSchema, db: Session = Depends(get_db)):
|
||||
config = db.query(ContactConfig).first()
|
||||
if not config:
|
||||
config = ContactConfig()
|
||||
db.add(config)
|
||||
config.security_notice = req.security_notice
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
@@ -0,0 +1,39 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.courses import CoursesConfig
|
||||
from app.schemas.courses import CoursesConfigSchema, CoursesConfigResponse
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/courses",
|
||||
tags=["研学课程"],
|
||||
dependencies=[Depends(get_current_user)],
|
||||
)
|
||||
|
||||
|
||||
def _get_or_create(db: Session) -> CoursesConfig:
|
||||
config = db.query(CoursesConfig).first()
|
||||
if not config:
|
||||
config = CoursesConfig(modules=[], age_groups=[], faqs=[])
|
||||
db.add(config)
|
||||
db.commit()
|
||||
return config
|
||||
|
||||
|
||||
@router.get("/", response_model=CoursesConfigResponse)
|
||||
def get_config(db: Session = Depends(get_db)):
|
||||
return CoursesConfigResponse.model_validate(_get_or_create(db))
|
||||
|
||||
|
||||
@router.put("/", response_model=CoursesConfigResponse)
|
||||
def update_config(req: CoursesConfigSchema, db: Session = Depends(get_db)):
|
||||
config = _get_or_create(db)
|
||||
config.modules = req.modules
|
||||
config.age_groups = req.age_groups
|
||||
config.faqs = req.faqs
|
||||
db.commit()
|
||||
db.refresh(config)
|
||||
return CoursesConfigResponse.model_validate(config)
|
||||
@@ -0,0 +1,104 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.customize_submission import CustomizeSubmission
|
||||
from app.models.customize_config import CustomizeConfig
|
||||
from app.schemas.customize_submission import (
|
||||
CustomizeSubmissionResponse,
|
||||
CustomizeSubmissionListResponse,
|
||||
CustomizeStatusUpdate,
|
||||
)
|
||||
from app.schemas.customize_config import CustomizeConfigSchema
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/customize",
|
||||
tags=["定制表单"],
|
||||
dependencies=[Depends(get_current_user)],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/submissions", response_model=CustomizeSubmissionListResponse)
|
||||
def list_submissions(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
keyword: str = Query("", description="搜索姓名/电话/微信"),
|
||||
status: str = Query("", description="状态筛选: pending/processed"),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
query = db.query(CustomizeSubmission)
|
||||
if keyword:
|
||||
query = query.filter(
|
||||
CustomizeSubmission.name.contains(keyword)
|
||||
| CustomizeSubmission.phone.contains(keyword)
|
||||
| CustomizeSubmission.wechat.contains(keyword)
|
||||
)
|
||||
if status in ("pending", "processed"):
|
||||
query = query.filter(CustomizeSubmission.status == status)
|
||||
total = query.count()
|
||||
items = (
|
||||
query.order_by(CustomizeSubmission.id.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
return CustomizeSubmissionListResponse(
|
||||
items=[CustomizeSubmissionResponse.model_validate(i) for i in items],
|
||||
total=total,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/submissions/{submission_id}", response_model=CustomizeSubmissionResponse)
|
||||
def get_submission(submission_id: int, db: Session = Depends(get_db)):
|
||||
item = db.query(CustomizeSubmission).get(submission_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="记录不存在")
|
||||
return CustomizeSubmissionResponse.model_validate(item)
|
||||
|
||||
|
||||
@router.put("/submissions/{submission_id}/status")
|
||||
def update_status(
|
||||
submission_id: int,
|
||||
req: CustomizeStatusUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
item = db.query(CustomizeSubmission).get(submission_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="记录不存在")
|
||||
item.status = req.status
|
||||
db.commit()
|
||||
return {"message": "状态更新成功"}
|
||||
|
||||
|
||||
# --- Customize Config ---
|
||||
@router.get("/config", response_model=CustomizeConfigSchema)
|
||||
def get_config(db: Session = Depends(get_db)):
|
||||
cfg = db.query(CustomizeConfig).first()
|
||||
if not cfg:
|
||||
return CustomizeConfigSchema()
|
||||
return CustomizeConfigSchema(
|
||||
trust_stats=cfg.trust_stats or [],
|
||||
durations=cfg.durations or [],
|
||||
activities=cfg.activities or [],
|
||||
budgets=cfg.budgets or [],
|
||||
process=cfg.process or [],
|
||||
contact=cfg.contact or {},
|
||||
)
|
||||
|
||||
|
||||
@router.put("/config")
|
||||
def update_config(req: CustomizeConfigSchema, db: Session = Depends(get_db)):
|
||||
cfg = db.query(CustomizeConfig).first()
|
||||
if not cfg:
|
||||
cfg = CustomizeConfig()
|
||||
db.add(cfg)
|
||||
cfg.trust_stats = req.trust_stats
|
||||
cfg.durations = req.durations
|
||||
cfg.activities = req.activities
|
||||
cfg.budgets = req.budgets
|
||||
cfg.process = req.process
|
||||
cfg.contact = req.contact
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
@@ -0,0 +1,118 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.destination import DestinationConfig, DestinationItem, DestinationDimension, DestinationHonestItem
|
||||
from app.schemas.destination import (
|
||||
DestinationConfigSchema, DestinationItemSchema, DestinationItemResponse,
|
||||
DestinationDimensionSchema, DestinationDimensionResponse,
|
||||
DestinationHonestItemSchema, DestinationHonestItemResponse,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/destinations", tags=["目的地对比"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
def _get_config(db: Session) -> DestinationConfig:
|
||||
config = db.query(DestinationConfig).first()
|
||||
if not config:
|
||||
config = DestinationConfig()
|
||||
db.add(config)
|
||||
db.flush()
|
||||
return config
|
||||
|
||||
|
||||
@router.get("/config", response_model=DestinationConfigSchema)
|
||||
def get_config(db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
return DestinationConfigSchema(
|
||||
title=c.title, subtitle=c.subtitle, intro=c.intro,
|
||||
closing_title=c.closing_title, closing_text=c.closing_text,
|
||||
data_sources=c.data_sources, honest_title=c.honest_title, honest_subtitle=c.honest_subtitle,
|
||||
)
|
||||
|
||||
|
||||
@router.put("/config")
|
||||
def update_config(req: DestinationConfigSchema, db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
c.title = req.title
|
||||
c.subtitle = req.subtitle
|
||||
c.intro = req.intro
|
||||
c.closing_title = req.closing_title
|
||||
c.closing_text = req.closing_text
|
||||
c.data_sources = req.data_sources
|
||||
c.honest_title = req.honest_title
|
||||
c.honest_subtitle = req.honest_subtitle
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
# --- Destination items ---
|
||||
@router.get("/items", response_model=list[DestinationItemResponse])
|
||||
def list_items(db: Session = Depends(get_db)):
|
||||
rows = db.query(DestinationItem).order_by(DestinationItem.sort_order).all()
|
||||
return [DestinationItemResponse(id=d.id, dest_id=d.dest_id, name=d.name, tag=d.tag, highlight=d.highlight, sort_order=d.sort_order) for d in rows]
|
||||
|
||||
|
||||
@router.put("/items")
|
||||
def update_items(items: list[DestinationItemSchema], db: Session = Depends(get_db)):
|
||||
db.query(DestinationItem).delete()
|
||||
for i, d in enumerate(items):
|
||||
db.add(DestinationItem(dest_id=d.dest_id, name=d.name, tag=d.tag, highlight=d.highlight, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "目的地更新成功"}
|
||||
|
||||
|
||||
# --- Dimensions ---
|
||||
@router.get("/dimensions", response_model=list[DestinationDimensionResponse])
|
||||
def list_dimensions(db: Session = Depends(get_db)):
|
||||
rows = db.query(DestinationDimension).order_by(DestinationDimension.sort_order).all()
|
||||
return [DestinationDimensionResponse(id=d.id, label=d.label, icon=d.icon, values=d.values or [], sort_order=d.sort_order) for d in rows]
|
||||
|
||||
|
||||
@router.post("/dimensions", response_model=DestinationDimensionResponse, status_code=201)
|
||||
def create_dimension(req: DestinationDimensionSchema, db: Session = Depends(get_db)):
|
||||
d = DestinationDimension(label=req.label, icon=req.icon, values=req.values)
|
||||
db.add(d)
|
||||
db.commit()
|
||||
db.refresh(d)
|
||||
return DestinationDimensionResponse(id=d.id, label=d.label, icon=d.icon, values=d.values or [], sort_order=d.sort_order)
|
||||
|
||||
|
||||
@router.put("/dimensions/{d_id}")
|
||||
def update_dimension(d_id: int, req: DestinationDimensionSchema, db: Session = Depends(get_db)):
|
||||
d = db.query(DestinationDimension).get(d_id)
|
||||
if not d:
|
||||
raise HTTPException(status_code=404, detail="对比维度不存在")
|
||||
d.label = req.label
|
||||
d.icon = req.icon
|
||||
d.values = req.values
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
@router.delete("/dimensions/{d_id}")
|
||||
def delete_dimension(d_id: int, db: Session = Depends(get_db)):
|
||||
d = db.query(DestinationDimension).get(d_id)
|
||||
if not d:
|
||||
raise HTTPException(status_code=404, detail="对比维度不存在")
|
||||
db.delete(d)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
|
||||
|
||||
# --- Honest items ---
|
||||
@router.get("/honest-items", response_model=list[DestinationHonestItemResponse])
|
||||
def list_honest_items(db: Session = Depends(get_db)):
|
||||
rows = db.query(DestinationHonestItem).order_by(DestinationHonestItem.sort_order).all()
|
||||
return [DestinationHonestItemResponse(id=h.id, text=h.text, sort_order=h.sort_order) for h in rows]
|
||||
|
||||
|
||||
@router.put("/honest-items")
|
||||
def update_honest_items(items: list[DestinationHonestItemSchema], db: Session = Depends(get_db)):
|
||||
db.query(DestinationHonestItem).delete()
|
||||
for i, h in enumerate(items):
|
||||
db.add(DestinationHonestItem(text=h.text, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "坦诚说明更新成功"}
|
||||
@@ -0,0 +1,92 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func as sqlfunc
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.destination_detail import DestinationDetail
|
||||
from app.schemas.destination_detail import (
|
||||
DestinationDetailSchema,
|
||||
DestinationDetailResponse,
|
||||
DestinationDetailListResponse,
|
||||
)
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/destinations-detail",
|
||||
tags=["景点详情"],
|
||||
dependencies=[Depends(get_current_user)],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/", response_model=DestinationDetailListResponse)
|
||||
def list_items(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
keyword: str = Query("", description="搜索关键词"),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
query = db.query(DestinationDetail)
|
||||
if keyword:
|
||||
query = query.filter(
|
||||
DestinationDetail.name.contains(keyword)
|
||||
| DestinationDetail.location.contains(keyword)
|
||||
)
|
||||
total = query.count()
|
||||
items = (
|
||||
query.order_by(DestinationDetail.sort_order, DestinationDetail.id.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
return DestinationDetailListResponse(
|
||||
items=[DestinationDetailResponse.model_validate(i) for i in items],
|
||||
total=total,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/", response_model=DestinationDetailResponse, status_code=201)
|
||||
def create_item(req: DestinationDetailSchema, db: Session = Depends(get_db)):
|
||||
if db.query(DestinationDetail).filter(DestinationDetail.slug == req.slug).first():
|
||||
raise HTTPException(status_code=400, detail="该 slug 已存在")
|
||||
max_order = db.query(sqlfunc.max(DestinationDetail.sort_order)).scalar() or 0
|
||||
item = DestinationDetail(**req.model_dump(), sort_order=max_order + 1)
|
||||
db.add(item)
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
return DestinationDetailResponse.model_validate(item)
|
||||
|
||||
|
||||
@router.put("/{item_id}", response_model=DestinationDetailResponse)
|
||||
def update_item(item_id: int, req: DestinationDetailSchema, db: Session = Depends(get_db)):
|
||||
item = db.query(DestinationDetail).get(item_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="景点不存在")
|
||||
existing = db.query(DestinationDetail).filter(
|
||||
DestinationDetail.slug == req.slug, DestinationDetail.id != item_id
|
||||
).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="该 slug 已被其他景点使用")
|
||||
for key, val in req.model_dump().items():
|
||||
setattr(item, key, val)
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
return DestinationDetailResponse.model_validate(item)
|
||||
|
||||
|
||||
@router.delete("/{item_id}")
|
||||
def delete_item(item_id: int, db: Session = Depends(get_db)):
|
||||
item = db.query(DestinationDetail).get(item_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="景点不存在")
|
||||
db.delete(item)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
|
||||
|
||||
@router.put("/reorder/batch")
|
||||
def reorder_items(ids: list[int], db: Session = Depends(get_db)):
|
||||
for i, iid in enumerate(ids):
|
||||
db.query(DestinationDetail).filter(DestinationDetail.id == iid).update({"sort_order": i})
|
||||
db.commit()
|
||||
return {"message": "排序更新成功"}
|
||||
@@ -0,0 +1,60 @@
|
||||
from __future__ import annotations
|
||||
import subprocess
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.config import settings
|
||||
from app.models.user import AdminUser
|
||||
from app.models.export_log import ExportLog
|
||||
from app.schemas.export import ExportRequest, ExportLogResponse
|
||||
from app.services.export_service import export_modules
|
||||
|
||||
router = APIRouter(prefix="/api/export", tags=["导出"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
@router.post("/")
|
||||
def do_export(req: ExportRequest, db: Session = Depends(get_db), current_user: AdminUser = Depends(get_current_user)):
|
||||
results = export_modules(db, req.modules)
|
||||
all_success = all(v == "success" for v in results.values())
|
||||
|
||||
# Optionally trigger rebuild
|
||||
rebuild_msg = ""
|
||||
if req.trigger_rebuild and all_success:
|
||||
try:
|
||||
subprocess.run(
|
||||
["npx", "nuxi", "generate"],
|
||||
cwd=settings.NUXT_PROJECT_PATH,
|
||||
timeout=300,
|
||||
capture_output=True,
|
||||
)
|
||||
rebuild_msg = " | 静态站已重新构建"
|
||||
except Exception as e:
|
||||
rebuild_msg = f" | 构建失败: {str(e)}"
|
||||
|
||||
# Log
|
||||
log = ExportLog(
|
||||
exported_by=current_user.username,
|
||||
modules=req.modules if "all" not in req.modules else ["all"],
|
||||
status="success" if all_success else "failed",
|
||||
message=str(results) + rebuild_msg,
|
||||
)
|
||||
db.add(log)
|
||||
db.commit()
|
||||
|
||||
return {"results": results, "rebuild": rebuild_msg}
|
||||
|
||||
|
||||
@router.get("/log", response_model=list[ExportLogResponse])
|
||||
def get_export_log(db: Session = Depends(get_db)):
|
||||
logs = db.query(ExportLog).order_by(ExportLog.created_at.desc()).limit(50).all()
|
||||
return [
|
||||
ExportLogResponse(
|
||||
id=l.id, exported_by=l.exported_by, modules=l.modules,
|
||||
status=l.status, message=l.message,
|
||||
created_at=l.created_at.isoformat() if l.created_at else None,
|
||||
)
|
||||
for l in logs
|
||||
]
|
||||
@@ -0,0 +1,90 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func as sqlfunc
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.faq import FaqCategory, FaqQuestion
|
||||
from app.schemas.faq import FaqCategorySchema, FaqCategoryResponse, FaqQuestionSchema, FaqQuestionResponse
|
||||
|
||||
router = APIRouter(prefix="/api/faq", tags=["常见问答"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
@router.get("/categories", response_model=list[FaqCategoryResponse])
|
||||
def list_categories(db: Session = Depends(get_db)):
|
||||
cats = db.query(FaqCategory).order_by(FaqCategory.sort_order).all()
|
||||
return [FaqCategoryResponse.model_validate(c) for c in cats]
|
||||
|
||||
|
||||
@router.post("/categories", response_model=FaqCategoryResponse, status_code=201)
|
||||
def create_category(req: FaqCategorySchema, db: Session = Depends(get_db)):
|
||||
max_order = db.query(sqlfunc.max(FaqCategory.sort_order)).scalar() or 0
|
||||
cat = FaqCategory(category_id=req.category_id, name=req.name, sort_order=max_order + 1)
|
||||
db.add(cat)
|
||||
db.commit()
|
||||
db.refresh(cat)
|
||||
return FaqCategoryResponse.model_validate(cat)
|
||||
|
||||
|
||||
@router.put("/categories/{cat_id}")
|
||||
def update_category(cat_id: int, req: FaqCategorySchema, db: Session = Depends(get_db)):
|
||||
cat = db.query(FaqCategory).get(cat_id)
|
||||
if not cat:
|
||||
raise HTTPException(status_code=404, detail="分类不存在")
|
||||
cat.category_id = req.category_id
|
||||
cat.name = req.name
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
@router.delete("/categories/{cat_id}")
|
||||
def delete_category(cat_id: int, db: Session = Depends(get_db)):
|
||||
cat = db.query(FaqCategory).get(cat_id)
|
||||
if not cat:
|
||||
raise HTTPException(status_code=404, detail="分类不存在")
|
||||
db.delete(cat)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
|
||||
|
||||
@router.post("/questions", response_model=FaqQuestionResponse, status_code=201)
|
||||
def create_question(req: FaqQuestionSchema, category_id: int = None, db: Session = Depends(get_db)):
|
||||
if not category_id:
|
||||
raise HTTPException(status_code=400, detail="必须指定分类")
|
||||
max_order = db.query(sqlfunc.max(FaqQuestion.sort_order)).filter(FaqQuestion.category_id == category_id).scalar() or 0
|
||||
q = FaqQuestion(
|
||||
category_id=category_id,
|
||||
question_id=req.question_id,
|
||||
question=req.question,
|
||||
answer=req.answer,
|
||||
related_links=[link.model_dump() for link in req.related_links],
|
||||
sort_order=max_order + 1,
|
||||
)
|
||||
db.add(q)
|
||||
db.commit()
|
||||
db.refresh(q)
|
||||
return FaqQuestionResponse.model_validate(q)
|
||||
|
||||
|
||||
@router.put("/questions/{q_id}")
|
||||
def update_question(q_id: int, req: FaqQuestionSchema, db: Session = Depends(get_db)):
|
||||
q = db.query(FaqQuestion).get(q_id)
|
||||
if not q:
|
||||
raise HTTPException(status_code=404, detail="问题不存在")
|
||||
q.question_id = req.question_id
|
||||
q.question = req.question
|
||||
q.answer = req.answer
|
||||
q.related_links = [link.model_dump() for link in req.related_links]
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
@router.delete("/questions/{q_id}")
|
||||
def delete_question(q_id: int, db: Session = Depends(get_db)):
|
||||
q = db.query(FaqQuestion).get(q_id)
|
||||
if not q:
|
||||
raise HTTPException(status_code=404, detail="问题不存在")
|
||||
db.delete(q)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
@@ -0,0 +1,82 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func as sqlfunc
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.gallery import GalleryItem
|
||||
from app.schemas.gallery import GalleryItemSchema, GalleryItemResponse, GalleryListResponse
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/gallery",
|
||||
tags=["旅拍作品"],
|
||||
dependencies=[Depends(get_current_user)],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/", response_model=GalleryListResponse)
|
||||
def list_items(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
keyword: str = Query("", description="搜索关键词"),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
query = db.query(GalleryItem)
|
||||
if keyword:
|
||||
query = query.filter(
|
||||
GalleryItem.title.contains(keyword)
|
||||
| GalleryItem.location.contains(keyword)
|
||||
| GalleryItem.photographer.contains(keyword)
|
||||
)
|
||||
total = query.count()
|
||||
items = (
|
||||
query.order_by(GalleryItem.sort_order, GalleryItem.id.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
return GalleryListResponse(
|
||||
items=[GalleryItemResponse.model_validate(i) for i in items],
|
||||
total=total,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/", response_model=GalleryItemResponse, status_code=201)
|
||||
def create_item(req: GalleryItemSchema, db: Session = Depends(get_db)):
|
||||
max_order = db.query(sqlfunc.max(GalleryItem.sort_order)).scalar() or 0
|
||||
item = GalleryItem(**req.model_dump(), sort_order=max_order + 1)
|
||||
db.add(item)
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
return GalleryItemResponse.model_validate(item)
|
||||
|
||||
|
||||
@router.put("/{item_id}", response_model=GalleryItemResponse)
|
||||
def update_item(item_id: int, req: GalleryItemSchema, db: Session = Depends(get_db)):
|
||||
item = db.query(GalleryItem).get(item_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="作品不存在")
|
||||
for key, val in req.model_dump().items():
|
||||
setattr(item, key, val)
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
return GalleryItemResponse.model_validate(item)
|
||||
|
||||
|
||||
@router.delete("/{item_id}")
|
||||
def delete_item(item_id: int, db: Session = Depends(get_db)):
|
||||
item = db.query(GalleryItem).get(item_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="作品不存在")
|
||||
db.delete(item)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
|
||||
|
||||
@router.put("/reorder/batch")
|
||||
def reorder_items(ids: list[int], db: Session = Depends(get_db)):
|
||||
for i, iid in enumerate(ids):
|
||||
db.query(GalleryItem).filter(GalleryItem.id == iid).update({"sort_order": i})
|
||||
db.commit()
|
||||
return {"message": "排序更新成功"}
|
||||
@@ -0,0 +1,55 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.guide import GuideConfig, GuideSection
|
||||
from app.schemas.guide import GuideConfigSchema, GuideSectionSchema, GuideSectionResponse
|
||||
|
||||
router = APIRouter(prefix="/api/guides", tags=["出行指南"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
def _get_config(db: Session) -> GuideConfig:
|
||||
config = db.query(GuideConfig).first()
|
||||
if not config:
|
||||
config = GuideConfig()
|
||||
db.add(config)
|
||||
db.flush()
|
||||
return config
|
||||
|
||||
|
||||
@router.get("/config", response_model=GuideConfigSchema)
|
||||
def get_config(db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
return GuideConfigSchema(page_intro=c.page_intro)
|
||||
|
||||
|
||||
@router.put("/config")
|
||||
def update_config(req: GuideConfigSchema, db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
c.page_intro = req.page_intro
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
@router.get("/sections", response_model=list[GuideSectionResponse])
|
||||
def list_sections(db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
sections = db.query(GuideSection).filter_by(config_id=c.id).order_by(GuideSection.sort_order).all()
|
||||
return [GuideSectionResponse.model_validate(s) for s in sections]
|
||||
|
||||
|
||||
@router.put("/sections/{section_id}")
|
||||
def update_section(section_id: str, req: GuideSectionSchema, db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
section = db.query(GuideSection).filter_by(config_id=c.id, section_id=section_id).first()
|
||||
if not section:
|
||||
raise HTTPException(status_code=404, detail=f"章节 {section_id} 不存在")
|
||||
section.title = req.title
|
||||
section.subtitle = req.subtitle
|
||||
section.icon = req.icon
|
||||
section.content = req.content
|
||||
section.data = req.data
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
@@ -0,0 +1,44 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.navigation import NavHeader, NavFooterGroup, NavFooterLink
|
||||
from app.schemas.navigation import NavigationSchema
|
||||
|
||||
router = APIRouter(prefix="/api/navigation", tags=["导航菜单"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
@router.get("/", response_model=NavigationSchema)
|
||||
def get_navigation(db: Session = Depends(get_db)):
|
||||
headers = db.query(NavHeader).order_by(NavHeader.sort_order).all()
|
||||
groups = db.query(NavFooterGroup).order_by(NavFooterGroup.sort_order).all()
|
||||
return NavigationSchema(
|
||||
header=[{"text": h.text, "to": h.to_path} for h in headers],
|
||||
footer=[
|
||||
{"title": g.title, "links": [{"text": l.text, "to": l.to_path} for l in g.links]}
|
||||
for g in groups
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@router.put("/")
|
||||
def update_navigation(req: NavigationSchema, db: Session = Depends(get_db)):
|
||||
# Replace header
|
||||
db.query(NavHeader).delete()
|
||||
for i, h in enumerate(req.header):
|
||||
db.add(NavHeader(text=h.text, to_path=h.to, sort_order=i))
|
||||
|
||||
# Replace footer
|
||||
db.query(NavFooterLink).delete()
|
||||
db.query(NavFooterGroup).delete()
|
||||
for i, g in enumerate(req.footer):
|
||||
group = NavFooterGroup(title=g.title, sort_order=i)
|
||||
db.add(group)
|
||||
db.flush()
|
||||
for j, link in enumerate(g.links):
|
||||
db.add(NavFooterLink(group_id=group.id, text=link.text, to_path=link.to, sort_order=j))
|
||||
|
||||
db.commit()
|
||||
return {"message": "导航更新成功"}
|
||||
@@ -0,0 +1,69 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func as sqlfunc
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.news import News
|
||||
from app.schemas.news import NewsSchema, NewsResponse, NewsListResponse
|
||||
|
||||
router = APIRouter(prefix="/api/news", tags=["新闻动态"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
@router.get("/", response_model=NewsListResponse)
|
||||
def list_news(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
keyword: str = Query("", description="搜索关键词"),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
query = db.query(News)
|
||||
if keyword:
|
||||
query = query.filter(News.title.contains(keyword) | News.summary.contains(keyword))
|
||||
total = query.count()
|
||||
items = query.order_by(News.sort_order, News.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||
return NewsListResponse(
|
||||
items=[NewsResponse.model_validate(n) for n in items],
|
||||
total=total,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/", response_model=NewsResponse, status_code=201)
|
||||
def create_news(req: NewsSchema, db: Session = Depends(get_db)):
|
||||
max_order = db.query(sqlfunc.max(News.sort_order)).scalar() or 0
|
||||
news = News(**req.model_dump(), sort_order=max_order + 1)
|
||||
db.add(news)
|
||||
db.commit()
|
||||
db.refresh(news)
|
||||
return NewsResponse.model_validate(news)
|
||||
|
||||
|
||||
@router.put("/{news_id}", response_model=NewsResponse)
|
||||
def update_news(news_id: int, req: NewsSchema, db: Session = Depends(get_db)):
|
||||
news = db.query(News).get(news_id)
|
||||
if not news:
|
||||
raise HTTPException(status_code=404, detail="新闻不存在")
|
||||
for key, val in req.model_dump().items():
|
||||
setattr(news, key, val)
|
||||
db.commit()
|
||||
db.refresh(news)
|
||||
return NewsResponse.model_validate(news)
|
||||
|
||||
|
||||
@router.delete("/{news_id}")
|
||||
def delete_news(news_id: int, db: Session = Depends(get_db)):
|
||||
news = db.query(News).get(news_id)
|
||||
if not news:
|
||||
raise HTTPException(status_code=404, detail="新闻不存在")
|
||||
db.delete(news)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
|
||||
|
||||
@router.put("/reorder/batch")
|
||||
def reorder_news(ids: list[int], db: Session = Depends(get_db)):
|
||||
for i, nid in enumerate(ids):
|
||||
db.query(News).filter(News.id == nid).update({"sort_order": i})
|
||||
db.commit()
|
||||
return {"message": "排序更新成功"}
|
||||
@@ -0,0 +1,57 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func as sqlfunc
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.partner import Partner
|
||||
from app.schemas.partner import PartnerSchema, PartnerResponse
|
||||
|
||||
router = APIRouter(prefix="/api/partners", tags=["合作伙伴"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
@router.get("/", response_model=list[PartnerResponse])
|
||||
def list_partners(db: Session = Depends(get_db)):
|
||||
items = db.query(Partner).order_by(Partner.sort_order).all()
|
||||
return [PartnerResponse.model_validate(item) for item in items]
|
||||
|
||||
|
||||
@router.post("/", response_model=PartnerResponse, status_code=201)
|
||||
def create_partner(req: PartnerSchema, db: Session = Depends(get_db)):
|
||||
max_order = db.query(sqlfunc.max(Partner.sort_order)).scalar() or 0
|
||||
item = Partner(**req.model_dump(), sort_order=max_order + 1)
|
||||
db.add(item)
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
return PartnerResponse.model_validate(item)
|
||||
|
||||
|
||||
@router.put("/{item_id}", response_model=PartnerResponse)
|
||||
def update_partner(item_id: int, req: PartnerSchema, db: Session = Depends(get_db)):
|
||||
item = db.query(Partner).get(item_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="合作伙伴不存在")
|
||||
for key, val in req.model_dump().items():
|
||||
setattr(item, key, val)
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
return PartnerResponse.model_validate(item)
|
||||
|
||||
|
||||
@router.delete("/{item_id}")
|
||||
def delete_partner(item_id: int, db: Session = Depends(get_db)):
|
||||
item = db.query(Partner).get(item_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="合作伙伴不存在")
|
||||
db.delete(item)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
|
||||
|
||||
@router.put("/reorder/batch")
|
||||
def reorder_partners(ids: list[int], db: Session = Depends(get_db)):
|
||||
for i, pid in enumerate(ids):
|
||||
db.query(Partner).filter(Partner.id == pid).update({"sort_order": i})
|
||||
db.commit()
|
||||
return {"message": "排序更新成功"}
|
||||
@@ -0,0 +1,57 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func as sqlfunc
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.pricing import PricingItem
|
||||
from app.schemas.pricing import PricingItemSchema, PricingItemResponse
|
||||
|
||||
router = APIRouter(prefix="/api/pricing", tags=["价格信息"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
@router.get("/", response_model=list[PricingItemResponse])
|
||||
def list_pricing(db: Session = Depends(get_db)):
|
||||
items = db.query(PricingItem).order_by(PricingItem.sort_order).all()
|
||||
return [PricingItemResponse.model_validate(item) for item in items]
|
||||
|
||||
|
||||
@router.post("/", response_model=PricingItemResponse, status_code=201)
|
||||
def create_pricing(req: PricingItemSchema, db: Session = Depends(get_db)):
|
||||
max_order = db.query(sqlfunc.max(PricingItem.sort_order)).scalar() or 0
|
||||
item = PricingItem(**req.model_dump(), sort_order=max_order + 1)
|
||||
db.add(item)
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
return PricingItemResponse.model_validate(item)
|
||||
|
||||
|
||||
@router.put("/{item_id}", response_model=PricingItemResponse)
|
||||
def update_pricing(item_id: int, req: PricingItemSchema, db: Session = Depends(get_db)):
|
||||
item = db.query(PricingItem).get(item_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="价格信息不存在")
|
||||
for key, val in req.model_dump().items():
|
||||
setattr(item, key, val)
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
return PricingItemResponse.model_validate(item)
|
||||
|
||||
|
||||
@router.delete("/{item_id}")
|
||||
def delete_pricing(item_id: int, db: Session = Depends(get_db)):
|
||||
item = db.query(PricingItem).get(item_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="价格信息不存在")
|
||||
db.delete(item)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
|
||||
|
||||
@router.put("/reorder/batch")
|
||||
def reorder_pricing(ids: list[int], db: Session = Depends(get_db)):
|
||||
for i, pid in enumerate(ids):
|
||||
db.query(PricingItem).filter(PricingItem.id == pid).update({"sort_order": i})
|
||||
db.commit()
|
||||
return {"message": "排序更新成功"}
|
||||
@@ -0,0 +1,187 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.product import (
|
||||
ProductConfig, ProductVersion, ProductVersionHighlight,
|
||||
SummerCamp, SummerCampPrinciple, SummerCampActivity, SummerCampItinerary, SummerCampFaq,
|
||||
SelectionGuide,
|
||||
)
|
||||
from app.schemas.product import (
|
||||
ProductConfigSchema, ProductVersionSchema, ProductVersionResponse,
|
||||
SummerCampSchema, SelectionGuideSchema,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/products", tags=["产品管理"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
def _get_config(db: Session) -> ProductConfig:
|
||||
config = db.query(ProductConfig).first()
|
||||
if not config:
|
||||
config = ProductConfig()
|
||||
db.add(config)
|
||||
db.flush()
|
||||
return config
|
||||
|
||||
|
||||
@router.get("/config", response_model=ProductConfigSchema)
|
||||
def get_config(db: Session = Depends(get_db)):
|
||||
config = _get_config(db)
|
||||
return ProductConfigSchema(narrative=config.narrative, pricing_philosophy=config.pricing_philosophy)
|
||||
|
||||
|
||||
@router.put("/config")
|
||||
def update_config(req: ProductConfigSchema, db: Session = Depends(get_db)):
|
||||
config = _get_config(db)
|
||||
config.narrative = req.narrative
|
||||
config.pricing_philosophy = req.pricing_philosophy
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
@router.get("/versions", response_model=list[ProductVersionResponse])
|
||||
def list_versions(db: Session = Depends(get_db)):
|
||||
config = _get_config(db)
|
||||
versions = db.query(ProductVersion).filter_by(config_id=config.id).order_by(ProductVersion.sort_order).all()
|
||||
result = []
|
||||
for v in versions:
|
||||
resp = ProductVersionResponse(
|
||||
id=v.id, version_id=v.version_id, name=v.name, days=v.days, nights=v.nights,
|
||||
audience=v.audience, description=v.description, tag=v.tag, sort_order=v.sort_order,
|
||||
highlights=[h.text for h in v.highlights],
|
||||
)
|
||||
result.append(resp)
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/versions", response_model=ProductVersionResponse, status_code=201)
|
||||
def create_version(req: ProductVersionSchema, db: Session = Depends(get_db)):
|
||||
config = _get_config(db)
|
||||
v = ProductVersion(
|
||||
config_id=config.id, version_id=req.version_id, name=req.name,
|
||||
days=req.days, nights=req.nights, audience=req.audience,
|
||||
description=req.description, tag=req.tag,
|
||||
)
|
||||
db.add(v)
|
||||
db.flush()
|
||||
for i, h in enumerate(req.highlights):
|
||||
db.add(ProductVersionHighlight(version_id=v.id, text=h, sort_order=i))
|
||||
db.commit()
|
||||
db.refresh(v)
|
||||
return ProductVersionResponse(
|
||||
id=v.id, version_id=v.version_id, name=v.name, days=v.days, nights=v.nights,
|
||||
audience=v.audience, description=v.description, tag=v.tag, sort_order=v.sort_order,
|
||||
highlights=[h.text for h in v.highlights],
|
||||
)
|
||||
|
||||
|
||||
@router.put("/versions/{v_id}")
|
||||
def update_version(v_id: int, req: ProductVersionSchema, db: Session = Depends(get_db)):
|
||||
v = db.query(ProductVersion).get(v_id)
|
||||
if not v:
|
||||
raise HTTPException(status_code=404, detail="版本不存在")
|
||||
v.version_id = req.version_id
|
||||
v.name = req.name
|
||||
v.days = req.days
|
||||
v.nights = req.nights
|
||||
v.audience = req.audience
|
||||
v.description = req.description
|
||||
v.tag = req.tag
|
||||
# Replace highlights
|
||||
db.query(ProductVersionHighlight).filter_by(version_id=v.id).delete()
|
||||
for i, h in enumerate(req.highlights):
|
||||
db.add(ProductVersionHighlight(version_id=v.id, text=h, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
@router.delete("/versions/{v_id}")
|
||||
def delete_version(v_id: int, db: Session = Depends(get_db)):
|
||||
v = db.query(ProductVersion).get(v_id)
|
||||
if not v:
|
||||
raise HTTPException(status_code=404, detail="版本不存在")
|
||||
db.delete(v)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
|
||||
|
||||
@router.get("/summer-camp", response_model=SummerCampSchema)
|
||||
def get_summer_camp(db: Session = Depends(get_db)):
|
||||
config = _get_config(db)
|
||||
camp = db.query(SummerCamp).filter_by(config_id=config.id).first()
|
||||
if not camp:
|
||||
return SummerCampSchema()
|
||||
return SummerCampSchema(
|
||||
name=camp.name, positioning=camp.positioning, days=camp.days, nights=camp.nights,
|
||||
sessions=camp.sessions_json, difference_from_v9=camp.difference_from_v9,
|
||||
principles=[p.text for p in camp.principles],
|
||||
activities=[a.text for a in camp.activities],
|
||||
itinerary=[it.text for it in camp.itinerary],
|
||||
faq=[{"question": f.question, "answer": f.answer} for f in camp.faq],
|
||||
)
|
||||
|
||||
|
||||
@router.put("/summer-camp")
|
||||
def update_summer_camp(req: SummerCampSchema, db: Session = Depends(get_db)):
|
||||
config = _get_config(db)
|
||||
camp = db.query(SummerCamp).filter_by(config_id=config.id).first()
|
||||
if not camp:
|
||||
camp = SummerCamp(config_id=config.id)
|
||||
db.add(camp)
|
||||
db.flush()
|
||||
|
||||
camp.name = req.name
|
||||
camp.positioning = req.positioning
|
||||
camp.days = req.days
|
||||
camp.nights = req.nights
|
||||
camp.sessions_json = req.sessions
|
||||
camp.difference_from_v9 = req.difference_from_v9
|
||||
|
||||
# Replace sub-collections
|
||||
db.query(SummerCampPrinciple).filter_by(camp_id=camp.id).delete()
|
||||
for i, p in enumerate(req.principles):
|
||||
db.add(SummerCampPrinciple(camp_id=camp.id, text=p, sort_order=i))
|
||||
|
||||
db.query(SummerCampActivity).filter_by(camp_id=camp.id).delete()
|
||||
for i, a in enumerate(req.activities):
|
||||
db.add(SummerCampActivity(camp_id=camp.id, text=a, sort_order=i))
|
||||
|
||||
db.query(SummerCampItinerary).filter_by(camp_id=camp.id).delete()
|
||||
for i, it in enumerate(req.itinerary):
|
||||
db.add(SummerCampItinerary(camp_id=camp.id, text=it, sort_order=i))
|
||||
|
||||
db.query(SummerCampFaq).filter_by(camp_id=camp.id).delete()
|
||||
for i, f in enumerate(req.faq):
|
||||
db.add(SummerCampFaq(camp_id=camp.id, question=f.question, answer=f.answer, sort_order=i))
|
||||
|
||||
db.commit()
|
||||
return {"message": "夏令营更新成功"}
|
||||
|
||||
|
||||
@router.get("/selection-guide", response_model=SelectionGuideSchema)
|
||||
def get_selection_guide(db: Session = Depends(get_db)):
|
||||
config = _get_config(db)
|
||||
guide = db.query(SelectionGuide).filter_by(config_id=config.id).first()
|
||||
if not guide:
|
||||
return SelectionGuideSchema()
|
||||
return SelectionGuideSchema(
|
||||
by_vacation_length=guide.by_vacation_length or [],
|
||||
by_child_age=guide.by_child_age or [],
|
||||
by_preference=guide.by_preference or [],
|
||||
)
|
||||
|
||||
|
||||
@router.put("/selection-guide")
|
||||
def update_selection_guide(req: SelectionGuideSchema, db: Session = Depends(get_db)):
|
||||
config = _get_config(db)
|
||||
guide = db.query(SelectionGuide).filter_by(config_id=config.id).first()
|
||||
if not guide:
|
||||
guide = SelectionGuide(config_id=config.id)
|
||||
db.add(guide)
|
||||
guide.by_vacation_length = req.by_vacation_length
|
||||
guide.by_child_age = req.by_child_age
|
||||
guide.by_preference = req.by_preference
|
||||
db.commit()
|
||||
return {"message": "选品指南更新成功"}
|
||||
@@ -0,0 +1,57 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func as sqlfunc
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.qualification import Qualification
|
||||
from app.schemas.qualification import QualificationSchema, QualificationResponse
|
||||
|
||||
router = APIRouter(prefix="/api/qualifications", tags=["资质荣誉"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
@router.get("/", response_model=list[QualificationResponse])
|
||||
def list_qualifications(db: Session = Depends(get_db)):
|
||||
items = db.query(Qualification).order_by(Qualification.sort_order).all()
|
||||
return [QualificationResponse.model_validate(item) for item in items]
|
||||
|
||||
|
||||
@router.post("/", response_model=QualificationResponse, status_code=201)
|
||||
def create_qualification(req: QualificationSchema, db: Session = Depends(get_db)):
|
||||
max_order = db.query(sqlfunc.max(Qualification.sort_order)).scalar() or 0
|
||||
item = Qualification(**req.model_dump(), sort_order=max_order + 1)
|
||||
db.add(item)
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
return QualificationResponse.model_validate(item)
|
||||
|
||||
|
||||
@router.put("/{item_id}", response_model=QualificationResponse)
|
||||
def update_qualification(item_id: int, req: QualificationSchema, db: Session = Depends(get_db)):
|
||||
item = db.query(Qualification).get(item_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="资质信息不存在")
|
||||
for key, val in req.model_dump().items():
|
||||
setattr(item, key, val)
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
return QualificationResponse.model_validate(item)
|
||||
|
||||
|
||||
@router.delete("/{item_id}")
|
||||
def delete_qualification(item_id: int, db: Session = Depends(get_db)):
|
||||
item = db.query(Qualification).get(item_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="资质信息不存在")
|
||||
db.delete(item)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
|
||||
|
||||
@router.put("/reorder/batch")
|
||||
def reorder_qualifications(ids: list[int], db: Session = Depends(get_db)):
|
||||
for i, qid in enumerate(ids):
|
||||
db.query(Qualification).filter(Qualification.id == qid).update({"sort_order": i})
|
||||
db.commit()
|
||||
return {"message": "排序更新成功"}
|
||||
@@ -0,0 +1,100 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func as sqlfunc
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.review import Review, ReviewSummary
|
||||
from app.schemas.review import ReviewSchema, ReviewResponse, ReviewListResponse, ReviewSummarySchema
|
||||
|
||||
router = APIRouter(prefix="/api/reviews", tags=["客户评价"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
@router.get("/summary", response_model=ReviewSummarySchema)
|
||||
def get_summary(db: Session = Depends(get_db)):
|
||||
summary = db.query(ReviewSummary).first()
|
||||
if not summary:
|
||||
return ReviewSummarySchema()
|
||||
return ReviewSummarySchema(total_count=summary.total_count, approval_rate=summary.approval_rate, keywords=summary.keywords or [])
|
||||
|
||||
|
||||
@router.put("/summary")
|
||||
def update_summary(req: ReviewSummarySchema, db: Session = Depends(get_db)):
|
||||
summary = db.query(ReviewSummary).first()
|
||||
if not summary:
|
||||
summary = ReviewSummary()
|
||||
db.add(summary)
|
||||
summary.total_count = req.total_count
|
||||
summary.approval_rate = req.approval_rate
|
||||
summary.keywords = req.keywords
|
||||
db.commit()
|
||||
return {"message": "评价概要更新成功"}
|
||||
|
||||
|
||||
@router.get("/", response_model=ReviewListResponse)
|
||||
def list_reviews(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
keyword: str = Query("", description="搜索关键词"),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
query = db.query(Review)
|
||||
if keyword:
|
||||
query = query.filter(Review.nickname.contains(keyword) | Review.content.contains(keyword))
|
||||
total = query.count()
|
||||
items = query.order_by(Review.sort_order, Review.id).offset((page - 1) * page_size).limit(page_size).all()
|
||||
return ReviewListResponse(
|
||||
items=[ReviewResponse.model_validate(r) for r in items],
|
||||
total=total,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/", response_model=ReviewResponse, status_code=201)
|
||||
def create_review(req: ReviewSchema, db: Session = Depends(get_db)):
|
||||
max_order = db.query(sqlfunc.max(Review.sort_order)).scalar() or 0
|
||||
review = Review(
|
||||
nickname=req.nickname,
|
||||
travel_date=req.travel_date,
|
||||
product_version=req.product_version,
|
||||
screenshot=req.screenshot,
|
||||
content=req.content,
|
||||
scenes=req.scenes,
|
||||
concerns=req.concerns,
|
||||
is_visible=req.is_visible,
|
||||
sort_order=max_order + 1,
|
||||
)
|
||||
db.add(review)
|
||||
db.commit()
|
||||
db.refresh(review)
|
||||
return ReviewResponse.model_validate(review)
|
||||
|
||||
|
||||
@router.put("/{review_id}", response_model=ReviewResponse)
|
||||
def update_review(review_id: int, req: ReviewSchema, db: Session = Depends(get_db)):
|
||||
review = db.query(Review).get(review_id)
|
||||
if not review:
|
||||
raise HTTPException(status_code=404, detail="评价不存在")
|
||||
for key, val in req.model_dump().items():
|
||||
setattr(review, key, val)
|
||||
db.commit()
|
||||
db.refresh(review)
|
||||
return ReviewResponse.model_validate(review)
|
||||
|
||||
|
||||
@router.delete("/{review_id}")
|
||||
def delete_review(review_id: int, db: Session = Depends(get_db)):
|
||||
review = db.query(Review).get(review_id)
|
||||
if not review:
|
||||
raise HTTPException(status_code=404, detail="评价不存在")
|
||||
db.delete(review)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
|
||||
|
||||
@router.put("/reorder/batch")
|
||||
def reorder_reviews(ids: list[int], db: Session = Depends(get_db)):
|
||||
for i, rid in enumerate(ids):
|
||||
db.query(Review).filter(Review.id == rid).update({"sort_order": i})
|
||||
db.commit()
|
||||
return {"message": "排序更新成功"}
|
||||
@@ -0,0 +1,174 @@
|
||||
"""
|
||||
Seasonal products router: serves both autumn (游牧的森林) and winter (嗨冰雪).
|
||||
Mounted twice at /api/autumn-products and /api/winter-products.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.seasonal_product import (
|
||||
SeasonalProductConfig, SeasonalProductVersion,
|
||||
SeasonalProductHighlight, SeasonalProductTimeline,
|
||||
)
|
||||
from app.schemas.seasonal_product import (
|
||||
SeasonalConfigSchema, SeasonalVersionSchema, SeasonalVersionResponse,
|
||||
SeasonalHighlightSchema, SeasonalHighlightResponse,
|
||||
SeasonalTimelineSchema, SeasonalTimelineResponse,
|
||||
)
|
||||
|
||||
|
||||
def create_seasonal_router(season: str, label: str) -> APIRouter:
|
||||
router = APIRouter(prefix=f"/api/{season}-products", tags=[f"{label}产品"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
def _get_config(db: Session) -> SeasonalProductConfig:
|
||||
config = db.query(SeasonalProductConfig).filter_by(season=season).first()
|
||||
if not config:
|
||||
config = SeasonalProductConfig(season=season)
|
||||
db.add(config)
|
||||
db.flush()
|
||||
return config
|
||||
|
||||
@router.get("/config", response_model=SeasonalConfigSchema)
|
||||
def get_config(db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
return SeasonalConfigSchema(
|
||||
narrative=c.narrative, style=c.style, brand_name=c.brand_name,
|
||||
version_label=c.version_label, season_label=c.season_label,
|
||||
)
|
||||
|
||||
@router.put("/config")
|
||||
def update_config(req: SeasonalConfigSchema, db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
c.narrative = req.narrative
|
||||
c.style = req.style
|
||||
c.brand_name = req.brand_name
|
||||
c.version_label = req.version_label
|
||||
c.season_label = req.season_label
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
# --- Versions ---
|
||||
@router.get("/versions", response_model=list[SeasonalVersionResponse])
|
||||
def list_versions(db: Session = Depends(get_db)):
|
||||
rows = db.query(SeasonalProductVersion).filter_by(season=season).order_by(SeasonalProductVersion.sort_order).all()
|
||||
return [
|
||||
SeasonalVersionResponse(
|
||||
id=v.id, version_id=v.version_id, name=v.name, days=v.days, nights=v.nights,
|
||||
tag=v.tag, line=v.line, route=v.route, audience=v.audience,
|
||||
description=v.description, highlights=v.highlights or [], itinerary=v.itinerary or [],
|
||||
sort_order=v.sort_order,
|
||||
)
|
||||
for v in rows
|
||||
]
|
||||
|
||||
@router.post("/versions", response_model=SeasonalVersionResponse, status_code=201)
|
||||
def create_version(req: SeasonalVersionSchema, db: Session = Depends(get_db)):
|
||||
v = SeasonalProductVersion(
|
||||
season=season, version_id=req.version_id, name=req.name,
|
||||
days=req.days, nights=req.nights, tag=req.tag, line=req.line,
|
||||
route=req.route, audience=req.audience, description=req.description,
|
||||
highlights=req.highlights, itinerary=req.itinerary,
|
||||
)
|
||||
db.add(v)
|
||||
db.commit()
|
||||
db.refresh(v)
|
||||
return SeasonalVersionResponse(
|
||||
id=v.id, version_id=v.version_id, name=v.name, days=v.days, nights=v.nights,
|
||||
tag=v.tag, line=v.line, route=v.route, audience=v.audience,
|
||||
description=v.description, highlights=v.highlights or [], itinerary=v.itinerary or [],
|
||||
sort_order=v.sort_order,
|
||||
)
|
||||
|
||||
@router.put("/versions/{v_id}")
|
||||
def update_version(v_id: int, req: SeasonalVersionSchema, db: Session = Depends(get_db)):
|
||||
v = db.query(SeasonalProductVersion).get(v_id)
|
||||
if not v or v.season != season:
|
||||
raise HTTPException(status_code=404, detail="版本不存在")
|
||||
v.version_id = req.version_id
|
||||
v.name = req.name
|
||||
v.days = req.days
|
||||
v.nights = req.nights
|
||||
v.tag = req.tag
|
||||
v.line = req.line
|
||||
v.route = req.route
|
||||
v.audience = req.audience
|
||||
v.description = req.description
|
||||
v.highlights = req.highlights
|
||||
v.itinerary = req.itinerary
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
@router.delete("/versions/{v_id}")
|
||||
def delete_version(v_id: int, db: Session = Depends(get_db)):
|
||||
v = db.query(SeasonalProductVersion).get(v_id)
|
||||
if not v or v.season != season:
|
||||
raise HTTPException(status_code=404, detail="版本不存在")
|
||||
db.delete(v)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
|
||||
# --- Highlights ---
|
||||
@router.get("/highlights", response_model=list[SeasonalHighlightResponse])
|
||||
def list_highlights(category: str = None, db: Session = Depends(get_db)):
|
||||
q = db.query(SeasonalProductHighlight).filter_by(season=season)
|
||||
if category:
|
||||
q = q.filter_by(category=category)
|
||||
return [
|
||||
SeasonalHighlightResponse(id=h.id, category=h.category, title=h.title, description=h.description, sort_order=h.sort_order)
|
||||
for h in q.order_by(SeasonalProductHighlight.sort_order).all()
|
||||
]
|
||||
|
||||
@router.put("/highlights")
|
||||
def update_highlights(items: list[SeasonalHighlightSchema], db: Session = Depends(get_db)):
|
||||
db.query(SeasonalProductHighlight).filter_by(season=season).delete()
|
||||
for i, h in enumerate(items):
|
||||
db.add(SeasonalProductHighlight(season=season, category=h.category, title=h.title, description=h.description, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "亮点更新成功"}
|
||||
|
||||
# --- Timeline ---
|
||||
@router.get("/timeline", response_model=list[SeasonalTimelineResponse])
|
||||
def list_timeline(db: Session = Depends(get_db)):
|
||||
rows = db.query(SeasonalProductTimeline).filter_by(season=season).order_by(SeasonalProductTimeline.sort_order).all()
|
||||
return [
|
||||
SeasonalTimelineResponse(id=t.id, version=t.version, date=t.date, title=t.title, changes=t.changes or [], reason=t.reason, sort_order=t.sort_order)
|
||||
for t in rows
|
||||
]
|
||||
|
||||
@router.post("/timeline", response_model=SeasonalTimelineResponse, status_code=201)
|
||||
def create_timeline(req: SeasonalTimelineSchema, db: Session = Depends(get_db)):
|
||||
t = SeasonalProductTimeline(season=season, version=req.version, date=req.date, title=req.title, changes=req.changes, reason=req.reason)
|
||||
db.add(t)
|
||||
db.commit()
|
||||
db.refresh(t)
|
||||
return SeasonalTimelineResponse(id=t.id, version=t.version, date=t.date, title=t.title, changes=t.changes or [], reason=t.reason, sort_order=t.sort_order)
|
||||
|
||||
@router.put("/timeline/{t_id}")
|
||||
def update_timeline(t_id: int, req: SeasonalTimelineSchema, db: Session = Depends(get_db)):
|
||||
t = db.query(SeasonalProductTimeline).get(t_id)
|
||||
if not t or t.season != season:
|
||||
raise HTTPException(status_code=404, detail="时间线不存在")
|
||||
t.version = req.version
|
||||
t.date = req.date
|
||||
t.title = req.title
|
||||
t.changes = req.changes
|
||||
t.reason = req.reason
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
@router.delete("/timeline/{t_id}")
|
||||
def delete_timeline(t_id: int, db: Session = Depends(get_db)):
|
||||
t = db.query(SeasonalProductTimeline).get(t_id)
|
||||
if not t or t.season != season:
|
||||
raise HTTPException(status_code=404, detail="时间线不存在")
|
||||
db.delete(t)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
|
||||
return router
|
||||
|
||||
|
||||
autumn_router = create_seasonal_router("autumn", "秋季")
|
||||
winter_router = create_seasonal_router("winter", "冬季")
|
||||
@@ -0,0 +1,38 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.selector import SelectorConfig
|
||||
from app.schemas.selector import SelectorConfigSchema, SelectorConfigResponse
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/selector",
|
||||
tags=["选版工具"],
|
||||
dependencies=[Depends(get_current_user)],
|
||||
)
|
||||
|
||||
|
||||
def _get_or_create(db: Session) -> SelectorConfig:
|
||||
config = db.query(SelectorConfig).first()
|
||||
if not config:
|
||||
config = SelectorConfig(questions=[], rules=[])
|
||||
db.add(config)
|
||||
db.commit()
|
||||
return config
|
||||
|
||||
|
||||
@router.get("/", response_model=SelectorConfigResponse)
|
||||
def get_config(db: Session = Depends(get_db)):
|
||||
return SelectorConfigResponse.model_validate(_get_or_create(db))
|
||||
|
||||
|
||||
@router.put("/", response_model=SelectorConfigResponse)
|
||||
def update_config(req: SelectorConfigSchema, db: Session = Depends(get_db)):
|
||||
config = _get_or_create(db)
|
||||
config.questions = req.questions
|
||||
config.rules = req.rules
|
||||
db.commit()
|
||||
db.refresh(config)
|
||||
return SelectorConfigResponse.model_validate(config)
|
||||
@@ -0,0 +1,32 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.seo import SeoPage
|
||||
from app.schemas.seo import SeoPageSchema, SeoPageResponse
|
||||
|
||||
router = APIRouter(prefix="/api/seo", tags=["SEO配置"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
@router.get("/pages", response_model=list[SeoPageResponse])
|
||||
def list_pages(db: Session = Depends(get_db)):
|
||||
pages = db.query(SeoPage).all()
|
||||
return [SeoPageResponse.model_validate(p) for p in pages]
|
||||
|
||||
|
||||
@router.put("/pages/{page_key}", response_model=SeoPageResponse)
|
||||
def update_page(page_key: str, req: SeoPageSchema, db: Session = Depends(get_db)):
|
||||
page = db.query(SeoPage).filter(SeoPage.page_key == page_key).first()
|
||||
if not page:
|
||||
raise HTTPException(status_code=404, detail=f"页面 {page_key} 不存在")
|
||||
page.title = req.title
|
||||
page.description = req.description
|
||||
page.h1 = req.h1
|
||||
page.keywords = req.keywords
|
||||
page.og_image = req.og_image
|
||||
page.tldr = req.tldr
|
||||
db.commit()
|
||||
db.refresh(page)
|
||||
return SeoPageResponse.model_validate(page)
|
||||
@@ -0,0 +1,49 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.site_image import SiteImage
|
||||
from app.schemas.site_image import SiteImageSchema, SiteImageResponse
|
||||
|
||||
router = APIRouter(prefix="/api/site-images", tags=["网站图片"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
@router.get("/", response_model=list[SiteImageResponse])
|
||||
def list_images(db: Session = Depends(get_db)):
|
||||
images = db.query(SiteImage).order_by(SiteImage.group_name, SiteImage.image_key).all()
|
||||
return [SiteImageResponse.model_validate(img) for img in images]
|
||||
|
||||
|
||||
@router.put("/{image_id}", response_model=SiteImageResponse)
|
||||
def update_image(image_id: int, req: SiteImageSchema, db: Session = Depends(get_db)):
|
||||
img = db.query(SiteImage).get(image_id)
|
||||
if not img:
|
||||
raise HTTPException(status_code=404, detail="图片不存在")
|
||||
img.image_path = req.image_path
|
||||
img.label = req.label
|
||||
img.size_hint = req.size_hint
|
||||
img.description = req.description
|
||||
db.commit()
|
||||
db.refresh(img)
|
||||
return SiteImageResponse.model_validate(img)
|
||||
|
||||
|
||||
@router.post("/", response_model=SiteImageResponse, status_code=201)
|
||||
def create_image(req: SiteImageSchema, db: Session = Depends(get_db)):
|
||||
img = SiteImage(**req.model_dump())
|
||||
db.add(img)
|
||||
db.commit()
|
||||
db.refresh(img)
|
||||
return SiteImageResponse.model_validate(img)
|
||||
|
||||
|
||||
@router.delete("/{image_id}")
|
||||
def delete_image(image_id: int, db: Session = Depends(get_db)):
|
||||
img = db.query(SiteImage).get(image_id)
|
||||
if not img:
|
||||
raise HTTPException(status_code=404, detail="图片不存在")
|
||||
db.delete(img)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
@@ -0,0 +1,69 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func as sqlfunc
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.story import Story
|
||||
from app.schemas.story import StorySchema, StoryResponse, StoryListResponse
|
||||
|
||||
router = APIRouter(prefix="/api/stories", tags=["客户故事"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
@router.get("/", response_model=StoryListResponse)
|
||||
def list_stories(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
keyword: str = Query("", description="搜索关键词"),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
query = db.query(Story)
|
||||
if keyword:
|
||||
query = query.filter(Story.title.contains(keyword) | Story.customer_name.contains(keyword))
|
||||
total = query.count()
|
||||
items = query.order_by(Story.sort_order, Story.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||
return StoryListResponse(
|
||||
items=[StoryResponse.model_validate(s) for s in items],
|
||||
total=total,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/", response_model=StoryResponse, status_code=201)
|
||||
def create_story(req: StorySchema, db: Session = Depends(get_db)):
|
||||
max_order = db.query(sqlfunc.max(Story.sort_order)).scalar() or 0
|
||||
story = Story(**req.model_dump(), sort_order=max_order + 1)
|
||||
db.add(story)
|
||||
db.commit()
|
||||
db.refresh(story)
|
||||
return StoryResponse.model_validate(story)
|
||||
|
||||
|
||||
@router.put("/{story_id}", response_model=StoryResponse)
|
||||
def update_story(story_id: int, req: StorySchema, db: Session = Depends(get_db)):
|
||||
story = db.query(Story).get(story_id)
|
||||
if not story:
|
||||
raise HTTPException(status_code=404, detail="故事不存在")
|
||||
for key, val in req.model_dump().items():
|
||||
setattr(story, key, val)
|
||||
db.commit()
|
||||
db.refresh(story)
|
||||
return StoryResponse.model_validate(story)
|
||||
|
||||
|
||||
@router.delete("/{story_id}")
|
||||
def delete_story(story_id: int, db: Session = Depends(get_db)):
|
||||
story = db.query(Story).get(story_id)
|
||||
if not story:
|
||||
raise HTTPException(status_code=404, detail="故事不存在")
|
||||
db.delete(story)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
|
||||
|
||||
@router.put("/reorder/batch")
|
||||
def reorder_stories(ids: list[int], db: Session = Depends(get_db)):
|
||||
for i, sid in enumerate(ids):
|
||||
db.query(Story).filter(Story.id == sid).update({"sort_order": i})
|
||||
db.commit()
|
||||
return {"message": "排序更新成功"}
|
||||
@@ -0,0 +1,36 @@
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from fastapi import APIRouter, Depends, UploadFile, File, HTTPException
|
||||
|
||||
from app.auth import get_current_user
|
||||
from app.config import settings
|
||||
|
||||
router = APIRouter(prefix="/api/upload", tags=["文件上传"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".webp", ".gif"}
|
||||
MAX_SIZE = 5 * 1024 * 1024 # 5MB
|
||||
|
||||
|
||||
@router.post("/image", status_code=201)
|
||||
async def upload_image(file: UploadFile = File(...), subdir: str = "uploads"):
|
||||
ext = os.path.splitext(file.filename)[1].lower()
|
||||
if ext not in ALLOWED_EXTENSIONS:
|
||||
raise HTTPException(status_code=400, detail=f"不支持的文件格式: {ext}")
|
||||
|
||||
content = await file.read()
|
||||
if len(content) > MAX_SIZE:
|
||||
raise HTTPException(status_code=400, detail="文件大小不能超过5MB")
|
||||
|
||||
target_dir = os.path.join(settings.UPLOAD_DIR, subdir)
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
|
||||
filename = f"{uuid.uuid4().hex}{ext}"
|
||||
filepath = os.path.join(target_dir, filename)
|
||||
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(content)
|
||||
|
||||
relative_path = f"/images/{subdir}/{filename}"
|
||||
return {"url": relative_path, "filename": filename}
|
||||
@@ -0,0 +1,172 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.version import (
|
||||
VersionConfig, VersionUpgrade, VersionHighlight, VersionCompare,
|
||||
VersionTimeline, VersionTimelineChange, VersionPhilosophy,
|
||||
)
|
||||
from app.schemas.version import (
|
||||
VersionConfigSchema, VersionUpgradeSchema, VersionHighlightSchema,
|
||||
VersionCompareSchema, VersionTimelineSchema, VersionTimelineResponse,
|
||||
VersionPhilosophySchema,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/versions", tags=["版本历史"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
def _get_config(db: Session) -> VersionConfig:
|
||||
config = db.query(VersionConfig).first()
|
||||
if not config:
|
||||
config = VersionConfig()
|
||||
db.add(config)
|
||||
db.flush()
|
||||
return config
|
||||
|
||||
|
||||
@router.get("/config", response_model=VersionConfigSchema)
|
||||
def get_config(db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
return VersionConfigSchema(
|
||||
stats_iterations=c.stats_iterations, stats_years=c.stats_years,
|
||||
stats_guests=c.stats_guests, quote_text=c.quote_text, quote_author=c.quote_author,
|
||||
)
|
||||
|
||||
|
||||
@router.put("/config")
|
||||
def update_config(req: VersionConfigSchema, db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
for key, val in req.model_dump().items():
|
||||
setattr(c, key, val)
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
@router.get("/upgrades", response_model=list[VersionUpgradeSchema])
|
||||
def get_upgrades(db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
items = db.query(VersionUpgrade).filter_by(config_id=c.id).order_by(VersionUpgrade.sort_order).all()
|
||||
return [VersionUpgradeSchema(tag=u.tag, name=u.name, description=u.description, reason=u.reason) for u in items]
|
||||
|
||||
|
||||
@router.put("/upgrades")
|
||||
def update_upgrades(req: list[VersionUpgradeSchema], db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
db.query(VersionUpgrade).filter_by(config_id=c.id).delete()
|
||||
for i, u in enumerate(req):
|
||||
db.add(VersionUpgrade(config_id=c.id, tag=u.tag, name=u.name, description=u.description, reason=u.reason, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
@router.get("/highlights", response_model=list[VersionHighlightSchema])
|
||||
def get_highlights(db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
items = db.query(VersionHighlight).filter_by(config_id=c.id).order_by(VersionHighlight.sort_order).all()
|
||||
return [VersionHighlightSchema(label=h.label, text=h.text) for h in items]
|
||||
|
||||
|
||||
@router.put("/highlights")
|
||||
def update_highlights(req: list[VersionHighlightSchema], db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
db.query(VersionHighlight).filter_by(config_id=c.id).delete()
|
||||
for i, h in enumerate(req):
|
||||
db.add(VersionHighlight(config_id=c.id, label=h.label, text=h.text, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
@router.get("/compare", response_model=VersionCompareSchema)
|
||||
def get_compare(db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
cmp = db.query(VersionCompare).filter_by(config_id=c.id).first()
|
||||
if not cmp:
|
||||
return VersionCompareSchema()
|
||||
return VersionCompareSchema(headers=cmp.headers or [], rows=cmp.rows or [])
|
||||
|
||||
|
||||
@router.put("/compare")
|
||||
def update_compare(req: VersionCompareSchema, db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
cmp = db.query(VersionCompare).filter_by(config_id=c.id).first()
|
||||
if not cmp:
|
||||
cmp = VersionCompare(config_id=c.id)
|
||||
db.add(cmp)
|
||||
cmp.headers = req.headers
|
||||
cmp.rows = req.rows
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
@router.get("/timeline", response_model=list[VersionTimelineResponse])
|
||||
def get_timeline(db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
items = db.query(VersionTimeline).filter_by(config_id=c.id).order_by(VersionTimeline.sort_order).all()
|
||||
return [
|
||||
VersionTimelineResponse(
|
||||
id=t.id, version=t.version, date=t.date, title=t.title, reason=t.reason, sort_order=t.sort_order,
|
||||
changes=[{"type": ch.type, "text": ch.text} for ch in t.changes],
|
||||
)
|
||||
for t in items
|
||||
]
|
||||
|
||||
|
||||
@router.post("/timeline", response_model=VersionTimelineResponse, status_code=201)
|
||||
def create_timeline(req: VersionTimelineSchema, db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
t = VersionTimeline(config_id=c.id, version=req.version, date=req.date, title=req.title, reason=req.reason)
|
||||
db.add(t)
|
||||
db.flush()
|
||||
for i, ch in enumerate(req.changes):
|
||||
db.add(VersionTimelineChange(timeline_id=t.id, type=ch.type, text=ch.text, sort_order=i))
|
||||
db.commit()
|
||||
db.refresh(t)
|
||||
return VersionTimelineResponse(
|
||||
id=t.id, version=t.version, date=t.date, title=t.title, reason=t.reason, sort_order=t.sort_order,
|
||||
changes=[{"type": ch.type, "text": ch.text} for ch in t.changes],
|
||||
)
|
||||
|
||||
|
||||
@router.put("/timeline/{t_id}")
|
||||
def update_timeline(t_id: int, req: VersionTimelineSchema, db: Session = Depends(get_db)):
|
||||
t = db.query(VersionTimeline).get(t_id)
|
||||
if not t:
|
||||
raise HTTPException(status_code=404, detail="时间线条目不存在")
|
||||
t.version = req.version
|
||||
t.date = req.date
|
||||
t.title = req.title
|
||||
t.reason = req.reason
|
||||
db.query(VersionTimelineChange).filter_by(timeline_id=t.id).delete()
|
||||
for i, ch in enumerate(req.changes):
|
||||
db.add(VersionTimelineChange(timeline_id=t.id, type=ch.type, text=ch.text, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
@router.delete("/timeline/{t_id}")
|
||||
def delete_timeline(t_id: int, db: Session = Depends(get_db)):
|
||||
t = db.query(VersionTimeline).get(t_id)
|
||||
if not t:
|
||||
raise HTTPException(status_code=404, detail="时间线条目不存在")
|
||||
db.delete(t)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
|
||||
|
||||
@router.get("/philosophy", response_model=list[VersionPhilosophySchema])
|
||||
def get_philosophy(db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
items = db.query(VersionPhilosophy).filter_by(config_id=c.id).order_by(VersionPhilosophy.sort_order).all()
|
||||
return [VersionPhilosophySchema(label=p.label, text=p.text) for p in items]
|
||||
|
||||
|
||||
@router.put("/philosophy")
|
||||
def update_philosophy(req: list[VersionPhilosophySchema], db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
db.query(VersionPhilosophy).filter_by(config_id=c.id).delete()
|
||||
for i, p in enumerate(req):
|
||||
db.add(VersionPhilosophy(config_id=c.id, label=p.label, text=p.text, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
@@ -0,0 +1,133 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth import get_current_user
|
||||
from app.models.winter_camp import WinterCampConfig, WinterCampHotel, WinterCampItinerary, WinterCampFaq
|
||||
from app.schemas.winter_camp import (
|
||||
WinterCampConfigSchema, WinterCampHotelSchema, WinterCampHotelResponse,
|
||||
WinterCampItinerarySchema, WinterCampItineraryResponse,
|
||||
WinterCampFaqSchema, WinterCampFaqResponse,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/winter-camp", tags=["冬季营"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
|
||||
def _get_config(db: Session) -> WinterCampConfig:
|
||||
config = db.query(WinterCampConfig).first()
|
||||
if not config:
|
||||
config = WinterCampConfig()
|
||||
db.add(config)
|
||||
db.flush()
|
||||
return config
|
||||
|
||||
|
||||
@router.get("/config", response_model=WinterCampConfigSchema)
|
||||
def get_config(db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
return WinterCampConfigSchema(
|
||||
name=c.name, positioning=c.positioning, days=c.days, nights=c.nights,
|
||||
max_families=c.max_families, total_sessions=c.total_sessions,
|
||||
age_range=c.age_range, deposit=c.deposit, season=c.season, route=c.route,
|
||||
why_hulunbuir=c.why_hulunbuir, closing_note=c.closing_note,
|
||||
photographer=c.photographer, winter_clothing=c.winter_clothing,
|
||||
camp_advantages=c.camp_advantages or [], service_config=c.service_config or [],
|
||||
camp_essentials=c.camp_essentials or [],
|
||||
)
|
||||
|
||||
|
||||
@router.put("/config")
|
||||
def update_config(req: WinterCampConfigSchema, db: Session = Depends(get_db)):
|
||||
c = _get_config(db)
|
||||
c.name = req.name
|
||||
c.positioning = req.positioning
|
||||
c.days = req.days
|
||||
c.nights = req.nights
|
||||
c.max_families = req.max_families
|
||||
c.total_sessions = req.total_sessions
|
||||
c.age_range = req.age_range
|
||||
c.deposit = req.deposit
|
||||
c.season = req.season
|
||||
c.route = req.route
|
||||
c.why_hulunbuir = req.why_hulunbuir
|
||||
c.closing_note = req.closing_note
|
||||
c.photographer = req.photographer
|
||||
c.winter_clothing = req.winter_clothing
|
||||
c.camp_advantages = req.camp_advantages
|
||||
c.service_config = req.service_config
|
||||
c.camp_essentials = req.camp_essentials
|
||||
db.commit()
|
||||
return {"message": "冬季营配置更新成功"}
|
||||
|
||||
|
||||
# --- Hotels ---
|
||||
@router.get("/hotels", response_model=list[WinterCampHotelResponse])
|
||||
def list_hotels(db: Session = Depends(get_db)):
|
||||
rows = db.query(WinterCampHotel).order_by(WinterCampHotel.sort_order).all()
|
||||
return [WinterCampHotelResponse(id=h.id, name=h.name, star=h.star, nights=h.nights, description=h.description, sort_order=h.sort_order) for h in rows]
|
||||
|
||||
|
||||
@router.put("/hotels")
|
||||
def update_hotels(items: list[WinterCampHotelSchema], db: Session = Depends(get_db)):
|
||||
db.query(WinterCampHotel).delete()
|
||||
for i, h in enumerate(items):
|
||||
db.add(WinterCampHotel(name=h.name, star=h.star, nights=h.nights, description=h.description, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "酒店信息更新成功"}
|
||||
|
||||
|
||||
# --- Itinerary ---
|
||||
@router.get("/itinerary", response_model=list[WinterCampItineraryResponse])
|
||||
def list_itinerary(db: Session = Depends(get_db)):
|
||||
rows = db.query(WinterCampItinerary).order_by(WinterCampItinerary.sort_order).all()
|
||||
return [
|
||||
WinterCampItineraryResponse(id=d.id, day=d.day, title=d.title, summary=d.summary, highlights=d.highlights or [], hotel=d.hotel, sort_order=d.sort_order)
|
||||
for d in rows
|
||||
]
|
||||
|
||||
|
||||
@router.put("/itinerary")
|
||||
def update_itinerary(items: list[WinterCampItinerarySchema], db: Session = Depends(get_db)):
|
||||
db.query(WinterCampItinerary).delete()
|
||||
for i, d in enumerate(items):
|
||||
db.add(WinterCampItinerary(day=d.day, title=d.title, summary=d.summary, highlights=d.highlights, hotel=d.hotel, sort_order=i))
|
||||
db.commit()
|
||||
return {"message": "行程更新成功"}
|
||||
|
||||
|
||||
# --- FAQ ---
|
||||
@router.get("/faq", response_model=list[WinterCampFaqResponse])
|
||||
def list_faq(db: Session = Depends(get_db)):
|
||||
rows = db.query(WinterCampFaq).order_by(WinterCampFaq.sort_order).all()
|
||||
return [WinterCampFaqResponse(id=f.id, question=f.question, answer=f.answer, sort_order=f.sort_order) for f in rows]
|
||||
|
||||
|
||||
@router.post("/faq", response_model=WinterCampFaqResponse, status_code=201)
|
||||
def create_faq(req: WinterCampFaqSchema, db: Session = Depends(get_db)):
|
||||
f = WinterCampFaq(question=req.question, answer=req.answer)
|
||||
db.add(f)
|
||||
db.commit()
|
||||
db.refresh(f)
|
||||
return WinterCampFaqResponse(id=f.id, question=f.question, answer=f.answer, sort_order=f.sort_order)
|
||||
|
||||
|
||||
@router.put("/faq/{f_id}")
|
||||
def update_faq(f_id: int, req: WinterCampFaqSchema, db: Session = Depends(get_db)):
|
||||
f = db.query(WinterCampFaq).get(f_id)
|
||||
if not f:
|
||||
raise HTTPException(status_code=404, detail="FAQ不存在")
|
||||
f.question = req.question
|
||||
f.answer = req.answer
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
|
||||
@router.delete("/faq/{f_id}")
|
||||
def delete_faq(f_id: int, db: Session = Depends(get_db)):
|
||||
f = db.query(WinterCampFaq).get(f_id)
|
||||
if not f:
|
||||
raise HTTPException(status_code=404, detail="FAQ不存在")
|
||||
db.delete(f)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
在新工单中引用
屏蔽一个用户