Initial commit: hulai admin API backend

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
刘涛
2026-03-22 22:01:23 +08:00
共同撰写人 Claude Sonnet 4.6
当前提交 8c0a5f489d
共修改 114 个文件,包含 7619 行新增和 0 行删除
查看文件
+119
查看文件
@@ -0,0 +1,119 @@
from __future__ import annotations
from pydantic import BaseModel
from typing import Any
class StorySchema(BaseModel):
title: str | None = None
content: str | None = None
founding_moment: str | None = None
totem_description: str | None = None
totem_tagline: str | None = None
class SubsidiarySchema(BaseModel):
name: str
role: str | None = None
established: str | None = None
class CertificationSchema(BaseModel):
title: str
detail: str | None = None
class TrademarkSchema(BaseModel):
name: str | None = None
scope: str | None = None
holder: str | None = None
description: str | None = None
class CopyrightSchema(BaseModel):
name: str
reg_no: str | None = None
category: str | None = None
holder: str | None = None
date: str | None = None
description: str | None = None
class GuaranteeSchema(BaseModel):
title: str
detail: str | None = None
class XiaohongshuSchema(BaseModel):
account: str | None = None
verified: bool = False
verified_type: str | None = None
followers: str | None = None
likes: str | None = None
awards: list[str] = []
tagline: str | None = None
tags: list[str] = []
description: str | None = None
class TeamSchema(BaseModel):
summary: str | None = None
class CultureValueSchema(BaseModel):
name: str
expression: str | None = None
class CultureSchema(BaseModel):
core: str | None = None
transparency: str | None = None
trust: str | None = None
grief_award_name: str | None = None
grief_award_description: str | None = None
values: list[CultureValueSchema] = []
class FounderDetailSchema(BaseModel):
name: str | None = None
title: str | None = None
brand_founded: int | None = None
years_in_hulunbuir: str | None = None
background: str | None = None
expertise: list[str] = []
media_presence: list[str] = []
class FounderSchema(BaseModel):
name: str
title: str | None = None
story: str | None = None
class MilestoneSchema(BaseModel):
year: int
event: str
class ServicePrincipleSchema(BaseModel):
text: str
class DifferentiationSchema(BaseModel):
name: str
detail: str | None = None
class ServiceMomentSchema(BaseModel):
step: int
name: str
detail: str | None = None
class ServiceJourneySchema(BaseModel):
title: str | None = None
subtitle: str | None = None
moments: list[ServiceMomentSchema] = []
class AboutStatsSchema(BaseModel):
data: dict[str, Any] = {}
+29
查看文件
@@ -0,0 +1,29 @@
from __future__ import annotations
from pydantic import BaseModel
from typing import Any
class BlogSchema(BaseModel):
title: str
slug: str
cover_image: str | None = None
summary: str | None = None
content: str | None = None
author: str | None = None
category: str | None = None
tags: list[str] = []
published_at: str | None = None
is_visible: bool = True
class BlogResponse(BlogSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
class BlogListResponse(BaseModel):
items: list[BlogResponse]
total: int
+44
查看文件
@@ -0,0 +1,44 @@
from __future__ import annotations
from pydantic import BaseModel
from typing import Any
class TrustStatSchema(BaseModel):
value: str
unit: str | None = None
label: str
class DifferentiatorSchema(BaseModel):
title: str
description: str | None = None
class BrandSchema(BaseModel):
name: str
full_name: str | None = None
domain: str | None = None
url: str | None = None
slogan_emotional: str | None = None
slogan_functional: str | None = None
icp_entity: str | None = None
icp: str | None = None
icp_url: str | None = None
e_contract: str | None = None
cta_buttons: dict[str, Any] | None = None
conversion_path: list[Any] | None = None
class BrandFullSchema(BrandSchema):
id: int
trust_stats: list[TrustStatSchema] = []
differentiators: list[DifferentiatorSchema] = []
class Config:
from_attributes = True
class BrandUpdateRequest(BaseModel):
brand: BrandSchema
trust_stats: list[TrustStatSchema] = []
differentiators: list[DifferentiatorSchema] = []
+26
查看文件
@@ -0,0 +1,26 @@
from __future__ import annotations
from pydantic import BaseModel
class CalendarEventSchema(BaseModel):
date: str | None = None
title: str
description: str | None = None
class CalendarMonthSchema(BaseModel):
year: int
month: int
title: str | None = None
description: str | None = None
weather: str | None = None
highlights: list[str] = []
events: list[CalendarEventSchema] = []
is_available: bool = True
class CalendarMonthResponse(CalendarMonthSchema):
id: int
class Config:
from_attributes = True
+23
查看文件
@@ -0,0 +1,23 @@
from __future__ import annotations
from pydantic import BaseModel
class ContactChannelSchema(BaseModel):
type: str
label: str
value: str | None = None
qr_image: str | None = None
is_primary: bool = False
description: str | None = None
class ContactChannelResponse(ContactChannelSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
class ContactConfigSchema(BaseModel):
security_notice: str | None = None
+15
查看文件
@@ -0,0 +1,15 @@
from __future__ import annotations
from pydantic import BaseModel
class CoursesConfigSchema(BaseModel):
modules: list = []
age_groups: list = []
faqs: list = []
class CoursesConfigResponse(CoursesConfigSchema):
id: int
class Config:
from_attributes = True
+12
查看文件
@@ -0,0 +1,12 @@
from __future__ import annotations
from pydantic import BaseModel
from typing import Any
class CustomizeConfigSchema(BaseModel):
trust_stats: list[Any] = []
durations: list[Any] = []
activities: list[Any] = []
budgets: list[Any] = []
process: list[Any] = []
contact: dict[str, Any] = {}
+38
查看文件
@@ -0,0 +1,38 @@
from __future__ import annotations
from pydantic import BaseModel
from typing import Literal
class CustomizeSubmissionResponse(BaseModel):
id: int
name: str | None = None
phone: str | None = None
wechat: str | None = None
adults: int = 0
children: int = 0
travel_dates: str | None = None
budget: str | None = None
interests: list = []
notes: str | None = None
source: str | None = None
status: str = "pending"
created_at: str | None = None
class Config:
from_attributes = True
@classmethod
def model_validate(cls, obj, **kwargs):
data = super().model_validate(obj, **kwargs)
if obj.created_at:
data.created_at = obj.created_at.strftime("%Y-%m-%d %H:%M:%S")
return data
class CustomizeSubmissionListResponse(BaseModel):
items: list[CustomizeSubmissionResponse]
total: int
class CustomizeStatusUpdate(BaseModel):
status: Literal["pending", "processed"]
+54
查看文件
@@ -0,0 +1,54 @@
from __future__ import annotations
from pydantic import BaseModel
class DestinationConfigSchema(BaseModel):
title: str | None = None
subtitle: str | None = None
intro: str | None = None
closing_title: str | None = None
closing_text: str | None = None
data_sources: str | None = None
honest_title: str | None = None
honest_subtitle: str | None = None
class DestinationItemSchema(BaseModel):
dest_id: str
name: str
tag: str | None = None
highlight: bool = False
class DestinationItemResponse(DestinationItemSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
class DestinationDimensionSchema(BaseModel):
label: str
icon: str | None = None
values: list[str] = []
class DestinationDimensionResponse(DestinationDimensionSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
class DestinationHonestItemSchema(BaseModel):
text: str
class DestinationHonestItemResponse(DestinationHonestItemSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
+35
查看文件
@@ -0,0 +1,35 @@
from __future__ import annotations
from pydantic import BaseModel, field_validator
class DestinationDetailSchema(BaseModel):
name: str
slug: str
subtitle: str | None = None
cover_image: str | None = None
description: str | None = None
location: str | None = None
best_season: str | None = None
duration: str | None = None
highlights: list = []
gallery: list = []
tags: list = []
is_visible: bool = True
@field_validator('highlights', 'gallery', 'tags', mode='before')
@classmethod
def coerce_none_to_list(cls, v):
return v if v is not None else []
class DestinationDetailResponse(DestinationDetailSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
class DestinationDetailListResponse(BaseModel):
items: list[DestinationDetailResponse]
total: int
+19
查看文件
@@ -0,0 +1,19 @@
from __future__ import annotations
from pydantic import BaseModel
class ExportRequest(BaseModel):
modules: list[str] = ["all"]
trigger_rebuild: bool = False
class ExportLogResponse(BaseModel):
id: int
exported_by: str | None
modules: list[str] | None
status: str
message: str | None
created_at: str | None
class Config:
from_attributes = True
+38
查看文件
@@ -0,0 +1,38 @@
from __future__ import annotations
from pydantic import BaseModel
from typing import Any
class RelatedLink(BaseModel):
text: str
url: str
class FaqQuestionSchema(BaseModel):
question_id: str
question: str
answer: str
related_links: list[RelatedLink] = []
class FaqQuestionResponse(FaqQuestionSchema):
id: int
category_id: int
sort_order: int = 0
class Config:
from_attributes = True
class FaqCategorySchema(BaseModel):
category_id: str
name: str
class FaqCategoryResponse(FaqCategorySchema):
id: int
sort_order: int = 0
questions: list[FaqQuestionResponse] = []
class Config:
from_attributes = True
+25
查看文件
@@ -0,0 +1,25 @@
from __future__ import annotations
from pydantic import BaseModel
class GalleryItemSchema(BaseModel):
title: str | None = None
image: str
photographer: str | None = None
location: str | None = None
description: str | None = None
tags: list = []
is_visible: bool = True
class GalleryItemResponse(GalleryItemSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
class GalleryListResponse(BaseModel):
items: list[GalleryItemResponse]
total: int
+24
查看文件
@@ -0,0 +1,24 @@
from __future__ import annotations
from pydantic import BaseModel
from typing import Any
class GuideConfigSchema(BaseModel):
page_intro: str | None = None
class GuideSectionSchema(BaseModel):
section_id: str
title: str | None = None
subtitle: str | None = None
icon: str | None = None
content: str | None = None
data: Any = None
class GuideSectionResponse(GuideSectionSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
+22
查看文件
@@ -0,0 +1,22 @@
from __future__ import annotations
from pydantic import BaseModel
class NavLinkSchema(BaseModel):
text: str
to: str
class NavFooterLinkSchema(BaseModel):
text: str
to: str
class NavFooterGroupSchema(BaseModel):
title: str
links: list[NavFooterLinkSchema] = []
class NavigationSchema(BaseModel):
header: list[NavLinkSchema] = []
footer: list[NavFooterGroupSchema] = []
+26
查看文件
@@ -0,0 +1,26 @@
from __future__ import annotations
from pydantic import BaseModel
class NewsSchema(BaseModel):
title: str
cover_image: str | None = None
summary: str | None = None
content: str | None = None
source: str | None = None
source_url: str | None = None
published_at: str | None = None
is_visible: bool = True
class NewsResponse(NewsSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
class NewsListResponse(BaseModel):
items: list[NewsResponse]
total: int
+18
查看文件
@@ -0,0 +1,18 @@
from __future__ import annotations
from pydantic import BaseModel
class PartnerSchema(BaseModel):
name: str
logo: str | None = None
website: str | None = None
description: str | None = None
category: str | None = None
class PartnerResponse(PartnerSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
+27
查看文件
@@ -0,0 +1,27 @@
from __future__ import annotations
from pydantic import BaseModel, field_validator
class PricingItemSchema(BaseModel):
product_name: str
product_slug: str | None = None
price_from: str | None = None
price_unit: str | None = None
price_label: str | None = None
description: str | None = None
features: list[str] = []
notes: list[str] = []
is_visible: bool = True
@field_validator('features', 'notes', mode='before')
@classmethod
def coerce_none_to_list(cls, v):
return v if v is not None else []
class PricingItemResponse(PricingItemSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
+51
查看文件
@@ -0,0 +1,51 @@
from __future__ import annotations
from pydantic import BaseModel
from typing import Any
class ProductVersionSchema(BaseModel):
version_id: str
name: str
days: int
nights: int
audience: str | None = None
description: str | None = None
highlights: list[str] = []
tag: str | None = None
class ProductVersionResponse(ProductVersionSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
class SummerCampFaqSchema(BaseModel):
question: str
answer: str
class SummerCampSchema(BaseModel):
name: str | None = None
positioning: str | None = None
days: int | None = None
nights: int | None = None
sessions: dict | None = None
difference_from_v9: str | None = None
principles: list[str] = []
activities: list[str] = []
itinerary: list[str] = []
faq: list[SummerCampFaqSchema] = []
class SelectionGuideSchema(BaseModel):
by_vacation_length: list[Any] = []
by_child_age: list[Any] = []
by_preference: list[Any] = []
class ProductConfigSchema(BaseModel):
narrative: str | None = None
pricing_philosophy: str | None = None
+19
查看文件
@@ -0,0 +1,19 @@
from __future__ import annotations
from pydantic import BaseModel
class QualificationSchema(BaseModel):
title: str
issuer: str | None = None
year: str | None = None
image: str | None = None
description: str | None = None
category: str | None = None
class QualificationResponse(QualificationSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
+32
查看文件
@@ -0,0 +1,32 @@
from __future__ import annotations
from pydantic import BaseModel
class ReviewSummarySchema(BaseModel):
total_count: int = 0
approval_rate: str | None = None
keywords: list[str] = []
class ReviewSchema(BaseModel):
nickname: str
travel_date: str | None = None
product_version: str | None = None
screenshot: str | None = None
content: str
scenes: list[str] = []
concerns: list[str] = []
is_visible: bool = True
class ReviewResponse(ReviewSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
class ReviewListResponse(BaseModel):
items: list[ReviewResponse]
total: int
+63
查看文件
@@ -0,0 +1,63 @@
from __future__ import annotations
from pydantic import BaseModel
from typing import Any
class SeasonalConfigSchema(BaseModel):
narrative: str | None = None
style: str | None = None
brand_name: str | None = None
version_label: str | None = None
season_label: str | None = None
class SeasonalVersionSchema(BaseModel):
version_id: str
name: str
days: int
nights: int
tag: str | None = None
line: str | None = None
route: str | None = None
audience: str | None = None
description: str | None = None
highlights: list[str] = []
itinerary: list[str] = []
class SeasonalVersionResponse(SeasonalVersionSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
class SeasonalHighlightSchema(BaseModel):
category: str # shared, south, north
title: str
description: str | None = None
class SeasonalHighlightResponse(SeasonalHighlightSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
class SeasonalTimelineSchema(BaseModel):
version: str | None = None
date: str | None = None
title: str | None = None
changes: list[dict] = []
reason: str | None = None
class SeasonalTimelineResponse(SeasonalTimelineSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
+23
查看文件
@@ -0,0 +1,23 @@
from __future__ import annotations
from pydantic import BaseModel, field_validator
class SelectorConfigSchema(BaseModel):
questions: list = []
rules: list = []
@field_validator('questions', 'rules', mode='before')
@classmethod
def coerce_to_list(cls, v):
if v is None:
return []
if isinstance(v, list):
return v
return [] # dict等旧格式数据降级为空列表
class SelectorConfigResponse(SelectorConfigSchema):
id: int
class Config:
from_attributes = True
+19
查看文件
@@ -0,0 +1,19 @@
from __future__ import annotations
from pydantic import BaseModel
class SeoPageSchema(BaseModel):
title: str | None = None
description: str | None = None
h1: str | None = None
keywords: str | None = None
og_image: str | None = None
tldr: str | None = None
class SeoPageResponse(SeoPageSchema):
id: int
page_key: str
class Config:
from_attributes = True
+18
查看文件
@@ -0,0 +1,18 @@
from __future__ import annotations
from pydantic import BaseModel
class SiteImageSchema(BaseModel):
group_name: str
image_key: str
image_path: str
label: str | None = None
size_hint: str | None = None
description: str | None = None
class SiteImageResponse(SiteImageSchema):
id: int
class Config:
from_attributes = True
+28
查看文件
@@ -0,0 +1,28 @@
from __future__ import annotations
from pydantic import BaseModel
class StorySchema(BaseModel):
title: str
cover_image: str | None = None
summary: str | None = None
content: str | None = None
customer_name: str | None = None
avatar: str | None = None
travel_date: str | None = None
product_name: str | None = None
scenes: list[str] = []
is_visible: bool = True
class StoryResponse(StorySchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
class StoryListResponse(BaseModel):
items: list[StoryResponse]
total: int
+55
查看文件
@@ -0,0 +1,55 @@
from __future__ import annotations
from pydantic import BaseModel
class LoginRequest(BaseModel):
username: str
password: str
class LoginResponse(BaseModel):
access_token: str
token_type: str = "bearer"
class UserInfo(BaseModel):
id: int
username: str
display_name: str | None
role: str
class Config:
from_attributes = True
class ChangePasswordRequest(BaseModel):
old_password: str
new_password: str
class CreateUserRequest(BaseModel):
username: str
password: str
display_name: str | None = None
role: str = "editor"
class UpdateUserRequest(BaseModel):
display_name: str | None = None
role: str | None = None
is_active: bool | None = None
class ResetPasswordRequest(BaseModel):
new_password: str
class UserListResponse(BaseModel):
id: int
username: str
display_name: str | None
role: str
is_active: bool
class Config:
from_attributes = True
+54
查看文件
@@ -0,0 +1,54 @@
from __future__ import annotations
from pydantic import BaseModel
from typing import Any
class VersionUpgradeSchema(BaseModel):
tag: str | None = None
name: str | None = None
description: str | None = None
reason: str | None = None
class VersionHighlightSchema(BaseModel):
label: str | None = None
text: str | None = None
class VersionCompareSchema(BaseModel):
headers: list[str] = []
rows: list[list[str]] = []
class TimelineChangeSchema(BaseModel):
type: str # add, update, remove
text: str
class VersionTimelineSchema(BaseModel):
version: str
date: str | None = None
title: str | None = None
changes: list[TimelineChangeSchema] = []
reason: str | None = None
class VersionTimelineResponse(VersionTimelineSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
class VersionPhilosophySchema(BaseModel):
label: str | None = None
text: str | None = None
class VersionConfigSchema(BaseModel):
stats_iterations: int | None = None
stats_years: int | None = None
stats_guests: str | None = None
quote_text: str | None = None
quote_author: str | None = None
+67
查看文件
@@ -0,0 +1,67 @@
from __future__ import annotations
from pydantic import BaseModel
from typing import Any
class WinterCampConfigSchema(BaseModel):
name: str | None = None
positioning: str | None = None
days: int | None = None
nights: int | None = None
max_families: int | None = None
total_sessions: int | None = None
age_range: str | None = None
deposit: int | None = None
season: str | None = None
route: str | None = None
why_hulunbuir: str | None = None
closing_note: str | None = None
photographer: dict | None = None
winter_clothing: dict | None = None
camp_advantages: list[str] = []
service_config: list[str] = []
camp_essentials: list[str] = []
class WinterCampHotelSchema(BaseModel):
name: str
star: str | None = None
nights: str | None = None
description: str | None = None
class WinterCampHotelResponse(WinterCampHotelSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
class WinterCampItinerarySchema(BaseModel):
day: int
title: str | None = None
summary: str | None = None
highlights: list[str] = []
hotel: str | None = None
class WinterCampItineraryResponse(WinterCampItinerarySchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True
class WinterCampFaqSchema(BaseModel):
question: str
answer: str
class WinterCampFaqResponse(WinterCampFaqSchema):
id: int
sort_order: int = 0
class Config:
from_attributes = True