feat: restructure market app with per-market URL scoping
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 41s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 41s
- URL structure changes from /<route> to /<market_slug>/<route> - Root / shows markets listing page - app.py: url_value_preprocessor, url_defaults, hydrate_market (events app pattern) - Browse queries (db_nav, db_products_nocounts, db_products_counts) accept market_id - _productInfo reads g.market.id to scope all queries - save_nav accepts market_id, sets on new NavTop rows - API save_nav passes g.market.id - Scraper default URLs point to /suma-market/ on port 8001 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -34,9 +34,19 @@ def _regular_price_of(p: Product) -> Optional[float]:
|
||||
return None
|
||||
|
||||
# ---------- NAV ----------
|
||||
async def db_nav(session) -> Dict:
|
||||
tops = (await session.execute(select(NavTop))).scalars().all()
|
||||
subs = (await session.execute(select(NavSub))).scalars().all()
|
||||
async def db_nav(session, market_id=None) -> Dict:
|
||||
top_q = select(NavTop).where(NavTop.deleted_at.is_(None))
|
||||
if market_id is not None:
|
||||
top_q = top_q.where(NavTop.market_id == market_id)
|
||||
tops = (await session.execute(top_q)).scalars().all()
|
||||
|
||||
top_ids = [t.id for t in tops]
|
||||
if top_ids:
|
||||
subs = (await session.execute(
|
||||
select(NavSub).where(NavSub.top_id.in_(top_ids), NavSub.deleted_at.is_(None))
|
||||
)).scalars().all()
|
||||
else:
|
||||
subs = []
|
||||
|
||||
subs_by_top: Dict[int, List[Dict]] = {}
|
||||
for s in subs:
|
||||
@@ -274,7 +284,8 @@ async def db_products_nocounts(
|
||||
sort: Optional[str] = None,
|
||||
page_size: int = 20,
|
||||
liked: bool = None,
|
||||
user_id: int=0
|
||||
user_id: int=0,
|
||||
market_id: int | None = None,
|
||||
) -> Dict:
|
||||
BLOCKED_SLUGS = set((config().get("blacklist", {}).get("product", []) or []))
|
||||
base_conditions = []
|
||||
@@ -284,18 +295,21 @@ async def db_products_nocounts(
|
||||
)
|
||||
|
||||
if top_slug:
|
||||
|
||||
q_list_conditions = [
|
||||
Listing.deleted_at.is_(None),
|
||||
NavTop.deleted_at.is_(None),
|
||||
NavTop.slug == top_slug,
|
||||
NavSub.deleted_at.is_(None),
|
||||
NavSub.slug == sub_slug if sub_slug else Listing.sub_id.is_(None),
|
||||
]
|
||||
if market_id is not None:
|
||||
q_list_conditions.append(NavTop.market_id == market_id)
|
||||
|
||||
q_list = (
|
||||
select(Listing.id)
|
||||
.join(NavTop, Listing.top)
|
||||
.outerjoin(NavSub, Listing.sub)
|
||||
.where(
|
||||
Listing.deleted_at.is_(None),
|
||||
NavTop.deleted_at.is_(None),
|
||||
NavTop.slug == top_slug,
|
||||
NavSub.deleted_at.is_(None),
|
||||
NavSub.slug == sub_slug if sub_slug else Listing.sub_id.is_(None),
|
||||
)
|
||||
.where(*q_list_conditions)
|
||||
)
|
||||
|
||||
listing_id = (await session.execute(q_list)).scalars().first()
|
||||
@@ -305,6 +319,20 @@ async def db_products_nocounts(
|
||||
base_conditions.append(Product.slug.in_(
|
||||
select(ListingItem.slug).where(ListingItem.listing_id == listing_id, ListingItem.deleted_at.is_(None))
|
||||
))
|
||||
elif market_id is not None:
|
||||
# Browse all within a specific market: filter products through market's nav hierarchy
|
||||
market_product_slugs = (
|
||||
select(ListingItem.slug)
|
||||
.join(Listing, ListingItem.listing_id == Listing.id)
|
||||
.join(NavTop, Listing.top_id == NavTop.id)
|
||||
.where(
|
||||
ListingItem.deleted_at.is_(None),
|
||||
Listing.deleted_at.is_(None),
|
||||
NavTop.deleted_at.is_(None),
|
||||
NavTop.market_id == market_id,
|
||||
)
|
||||
)
|
||||
base_conditions.append(Product.slug.in_(market_product_slugs))
|
||||
|
||||
base_ids_subq = select(Product.id).where(*base_conditions, Product.deleted_at.is_(None))
|
||||
base_ids = (await session.execute(base_ids_subq)).scalars().all()
|
||||
@@ -461,17 +489,21 @@ async def db_products_counts(
|
||||
top_slug: str | None,
|
||||
sub_slug: str | None,
|
||||
search: Optional[str] = None,
|
||||
user_id: int=0
|
||||
user_id: int=0,
|
||||
market_id: int | None = None,
|
||||
) -> Dict:
|
||||
BLOCKED_SLUGS = set((config().get("blacklist", {}).get("product", []) or []))
|
||||
base_conditions = []
|
||||
|
||||
if top_slug:
|
||||
q_list = select(Listing.id).where(
|
||||
q_list_conditions = [
|
||||
Listing.deleted_at.is_(None),
|
||||
Listing.top.has(slug=top_slug),
|
||||
Listing.sub.has(slug=sub_slug) if sub_slug else Listing.sub_id.is_(None),
|
||||
)
|
||||
]
|
||||
if market_id is not None:
|
||||
q_list_conditions.append(Listing.top.has(market_id=market_id))
|
||||
q_list = select(Listing.id).where(*q_list_conditions)
|
||||
listing_id = (await session.execute(q_list)).scalars().first()
|
||||
if not listing_id:
|
||||
return {
|
||||
@@ -494,7 +526,29 @@ async def db_products_counts(
|
||||
else:
|
||||
base_conditions.append(Product.slug.in_(listing_slug_subquery))
|
||||
else:
|
||||
if BLOCKED_SLUGS:
|
||||
if market_id is not None:
|
||||
# Browse all within a specific market
|
||||
market_product_slugs = (
|
||||
select(ListingItem.slug)
|
||||
.join(Listing, ListingItem.listing_id == Listing.id)
|
||||
.join(NavTop, Listing.top_id == NavTop.id)
|
||||
.where(
|
||||
ListingItem.deleted_at.is_(None),
|
||||
Listing.deleted_at.is_(None),
|
||||
NavTop.deleted_at.is_(None),
|
||||
NavTop.market_id == market_id,
|
||||
)
|
||||
)
|
||||
if BLOCKED_SLUGS:
|
||||
base_conditions.append(
|
||||
and_(
|
||||
Product.slug.in_(market_product_slugs),
|
||||
~Product.slug.in_(BLOCKED_SLUGS),
|
||||
)
|
||||
)
|
||||
else:
|
||||
base_conditions.append(Product.slug.in_(market_product_slugs))
|
||||
elif BLOCKED_SLUGS:
|
||||
base_conditions.append(~Product.slug.in_(BLOCKED_SLUGS))
|
||||
base_ids = (await session.execute(select(Product.id).where(*base_conditions, Product.deleted_at.is_(None)))).scalars().all()
|
||||
if base_ids:
|
||||
@@ -628,7 +682,8 @@ async def db_products(
|
||||
sort: Optional[str] = None,
|
||||
page_size: int = 20,
|
||||
liked: bool = None,
|
||||
user_id: int=0
|
||||
user_id: int=0,
|
||||
market_id: int | None = None,
|
||||
) -> Dict:
|
||||
return {
|
||||
**(await db_products_nocounts(
|
||||
@@ -643,14 +698,16 @@ async def db_products(
|
||||
sort=sort,
|
||||
page_size=page_size,
|
||||
liked=liked,
|
||||
user_id=user_id
|
||||
user_id=user_id,
|
||||
market_id=market_id,
|
||||
)),
|
||||
**(await db_products_counts(
|
||||
session,
|
||||
top_slug=top_slug,
|
||||
sub_slug=sub_slug,
|
||||
search=search,
|
||||
user_id=user_id
|
||||
user_id=user_id,
|
||||
market_id=market_id,
|
||||
)),
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user