feat: add demand page modules and admin endpoints

Add full backend support for a demand request page, including:
- New database models for demand hero, feature cards, form configuration, and product recommendations
- Alembic migration for the new tables
- Default seed data for demand page components
- Public site config endpoint exposing demand data
- Admin CRUD operations and configuration for demand modules
- Extended lead query filters for source page, keyword, and date range
- Updated Pydantic schemas for lead query parameters
- Comprehensive test coverage for all new endpoints
This commit is contained in:
duanshuwen
2026-07-07 22:03:57 +08:00
parent d11c361aa2
commit fe85197e68
9 changed files with 749 additions and 15 deletions

View File

@@ -1,6 +1,6 @@
from datetime import datetime
from datetime import datetime, timedelta
from typing import Literal
from pydantic import BaseModel, EmailStr, Field, field_validator
from pydantic import BaseModel, EmailStr, Field, ValidationInfo, field_validator
ProductStatus = Literal["draft", "published", "archived"]
@@ -115,8 +115,23 @@ class AdminProductQuery(BaseModel):
class LeadQuery(BaseModel):
status: LeadStatus | None = None
sourcePage: str | None = None
keyword: str | None = None
createdFrom: datetime | None = None
createdTo: datetime | None = None
take: int = Field(default=100, ge=1, le=200)
@field_validator("createdFrom", "createdTo", mode="before")
@classmethod
def parse_date_only_range(cls, value, info: ValidationInfo):
if isinstance(value, str) and len(value) == 10:
try:
parsed = datetime.strptime(value, "%Y-%m-%d")
return parsed + timedelta(days=1) if info.field_name == "createdTo" else parsed
except ValueError:
return value
return value
class SiteConfigPatchIn(BaseModel):
title: str | None = None
@@ -135,6 +150,15 @@ class SiteConfigPatchIn(BaseModel):
priceAmount: int | None = Field(default=None, ge=0)
priceUnit: str | None = None
tags: list[str] | None = None
steps: list[str] | None = None
destinationLabel: str | None = None
destinationPlaceholder: str | None = None
phoneLabel: str | None = None
phonePlaceholder: str | None = None
noteLabel: str | None = None
notePlaceholder: str | None = None
submitLabel: str | None = None
chips: list[str] | None = None
productIds: list[str] | None = None
status: str | None = None
startsAt: datetime | None = None