"""City / destination depth aggregation (city_details + related content).""" from __future__ import annotations from typing import Any from app.data.city_details_data import ( CITY_DETAILS_BY_SLUG, CONTENT_ITEMS, EXTRA_ROUTES, ) from app.data import community_data from app.services.pb_repo import q, safe_first, safe_list, use_pb from app.services.pocketbase import pb from app.services import community_store def get_city_detail_record(slug: str) -> dict[str, Any] | None: if use_pb(): row = safe_first("city_details", filter=f"citySlug={q(slug)}") if row: return { "citySlug": row.get("citySlug", slug), "guide": row.get("guide") or {}, "pros": row.get("pros") or {}, "reviews": row.get("reviews") or {}, "cost": row.get("cost") or {}, "people": row.get("people") or {}, "chat": row.get("chat") or {}, "photos": row.get("photos") or {}, "weather": row.get("weather") or {}, "trends": row.get("trends") or {}, "demographics": row.get("demographics") or {}, "relatedContent": row.get("relatedContent") or [], } return CITY_DETAILS_BY_SLUG.get(slug) async def get_destination_full(slug: str) -> dict[str, Any] | None: city = await pb.get_destination(slug) if not city: return None detail = get_city_detail_record(slug) content: list[dict] = [] if use_pb(): for item in safe_list("content_items"): slugs = item.get("citySlugs") or [] if slug in slugs and item.get("status", "published") == "published": content.append(_public_content(item)) if not content: content = [_public_content(c) for c in CONTENT_ITEMS if slug in (c.get("citySlugs") or [])] city_name = city.get("name", "") meetups = [ m for m in community_store.list_meetups(upcoming=True) if m.get("destination_slug") == slug or m.get("city") == city_name ][:10] discussions = [ d for d in community_store.list_discussions() if city_name and (city_name in d.get("title", "") or city_name in d.get("excerpt", "")) ][:10] return { "ok": True, "city": city, "detail": detail, "content": content, "meetups": meetups, "discussions": discussions, } def _public_content(item: dict) -> dict: return { "slug": item.get("slug", ""), "type": item.get("type", "guide"), "title": item.get("title", ""), "subtitle": item.get("subtitle") or item.get("subtitleEn") or "", "description": item.get("description", ""), "targetUrl": item.get("targetUrl", ""), "ctaLabel": item.get("ctaLabel", "查看"), "citySlugs": item.get("citySlugs") or [], } def list_routes_enriched(destinations: list[dict] | None = None) -> list[dict]: dest_by_slug = {d.get("slug"): d for d in (destinations or [])} routes: list[dict] = [] if use_pb(): for r in safe_list("routes"): if r.get("status") and r.get("status") != "published": continue routes.append(_route_from_pb(r, dest_by_slug)) if not routes: for r in community_data.NOMAD_ROUTES: routes.append({ "id": r.get("id") or r.get("slug", ""), "slug": r.get("slug", ""), "title": r.get("title", ""), "duration_days": r.get("duration_days", 0), "budget": r.get("budget", 0), "description": r.get("description", ""), "stops": r.get("stops") or [], "emoji": r.get("emoji", "🗺️"), "cities": [dest_by_slug[s] for s in (r.get("stops") or []) if s in dest_by_slug], }) for r in EXTRA_ROUTES: if any(x["slug"] == r["slug"] for x in routes): continue routes.append({ "id": r["slug"], "slug": r["slug"], "title": r["title"], "duration_days": r.get("durationDays", 0), "budget": r.get("budget", 0), "description": r.get("description", ""), "stops": r.get("citySlugs") or [], "emoji": r.get("emoji", "🗺️"), "cities": [dest_by_slug[s] for s in (r.get("citySlugs") or []) if s in dest_by_slug], }) return routes def _route_from_pb(r: dict, dest_by_slug: dict) -> dict: slugs = r.get("citySlugs") or [] return { "id": r.get("id") or r.get("slug", ""), "slug": r.get("slug", ""), "title": r.get("title", ""), "duration_days": int(r.get("durationDays") or 0), "budget": int(r.get("budget") or 0), "description": r.get("description", ""), "stops": slugs, "emoji": r.get("emoji", "🗺️"), "cities": [dest_by_slug[s] for s in slugs if s in dest_by_slug], }