feat(route-sections): add dynamic route sections
Replace hardcoded homepage featured route groups with a fully managed dynamic system: - add database models RouteSection and RouteSectionProduct for storing route groups and their associated products - create Alembic migration 0004_route_sections for the new tables - extend SiteConfigPatchIn schema with subtitle and productIds fields - refactor shared site_config utility to load dynamic route sections instead of fixed groups - implement admin CRUD API with validation for duplicate/conflicting product associations - update public and admin API documentation to reflect the new system - add default route section seed data and comprehensive test coverage
This commit is contained in:
19
app/seed.py
19
app/seed.py
@@ -21,10 +21,13 @@ from .models import (
|
||||
MediaAsset,
|
||||
Product,
|
||||
ProductImage,
|
||||
RouteSection,
|
||||
RouteSectionProduct,
|
||||
SiteVersion,
|
||||
ThemeCard,
|
||||
utc_now,
|
||||
)
|
||||
from .route_sections import ROUTE_SECTION_DEFAULTS
|
||||
|
||||
|
||||
ROOT_DIR = Path(__file__).resolve().parent.parent
|
||||
@@ -97,7 +100,7 @@ def load_products() -> list[dict]:
|
||||
|
||||
def reset_guizhou_content(db: Session) -> dict:
|
||||
products = load_products()
|
||||
for model in [SiteVersion, CampaignProduct, Campaign, ProductImage, Product, CtaBanner, ThemeCard, HeroSlide, DestinationAlias, Destination, MediaAsset]:
|
||||
for model in [SiteVersion, CampaignProduct, RouteSectionProduct, Campaign, RouteSection, ProductImage, Product, CtaBanner, ThemeCard, HeroSlide, DestinationAlias, Destination, MediaAsset]:
|
||||
db.execute(delete(model))
|
||||
db.flush()
|
||||
|
||||
@@ -178,6 +181,18 @@ def reset_guizhou_content(db: Session) -> dict:
|
||||
for index, product in enumerate(linked_products):
|
||||
db.add(CampaignProduct(campaignId=campaign.id, productId=product.id, sortOrder=index))
|
||||
|
||||
for sort_order, (section_id, title, subtitle, start, end) in enumerate(ROUTE_SECTION_DEFAULTS):
|
||||
section = RouteSection(id=section_id, title=title, subtitle=subtitle, sortOrder=sort_order)
|
||||
db.add(section)
|
||||
db.flush()
|
||||
section_products = db.scalars(
|
||||
select(Product)
|
||||
.where(Product.sourceId >= start + 1, Product.sourceId <= end)
|
||||
.order_by(Product.sourceId.asc())
|
||||
).all()
|
||||
for index, product in enumerate(section_products):
|
||||
db.add(RouteSectionProduct(sectionId=section.id, productId=product.id, sortOrder=index))
|
||||
|
||||
snapshot = SiteVersion(
|
||||
title="guizhou-content-reset",
|
||||
status="published",
|
||||
@@ -187,6 +202,7 @@ def reset_guizhou_content(db: Session) -> dict:
|
||||
"destinations": len(DESTINATIONS),
|
||||
"themeCards": len(THEMES),
|
||||
"products": len(products),
|
||||
"routeSections": len(ROUTE_SECTION_DEFAULTS),
|
||||
},
|
||||
)
|
||||
db.add(snapshot)
|
||||
@@ -197,6 +213,7 @@ def reset_guizhou_content(db: Session) -> dict:
|
||||
"themes": len(THEMES),
|
||||
"ctaBanners": len(CTAS),
|
||||
"products": len(products),
|
||||
"routeSections": len(ROUTE_SECTION_DEFAULTS),
|
||||
"siteVersionId": snapshot.id,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user