Initial commit: hulai admin API backend
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
+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] = {}
|
||||
@@ -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
|
||||
@@ -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] = []
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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] = {}
|
||||
@@ -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"]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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] = []
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
在新工单中引用
屏蔽一个用户