from __future__ import annotations import path_setup # noqa: F401 # adds shared_lib to sys.path from pathlib import Path from quart import g, abort from jinja2 import FileSystemLoader, ChoiceLoader from sqlalchemy import select from shared.infrastructure.factory import create_base_app from bp import ( register_cart_overview, register_page_cart, register_cart_global, register_cart_api, register_orders, ) from bp.cart.services import ( get_cart, total, get_calendar_cart_entries, calendar_total, ) from bp.cart.services.page_cart import ( get_cart_for_page, get_calendar_entries_for_page, ) async def _load_cart(): """Load the full cart for the cart app (before each request).""" g.cart = await get_cart(g.s) async def cart_context() -> dict: """ Cart app context processor. - cart / calendar_cart_entries / total / calendar_total: direct DB (cart app owns this data) - cart_count: derived from cart + calendar entries (for _mini.html) - menu_items: fetched from coop internal API When g.page_post exists, cart and calendar_cart_entries are page-scoped. Global cart_count / cart_total stay global for cart-mini. """ from shared.infrastructure.context import base_context from shared.infrastructure.internal_api import get as api_get, dictobj ctx = await base_context() # Cart app owns cart data — use g.cart from _load_cart all_cart = getattr(g, "cart", None) or [] all_cal = await get_calendar_cart_entries(g.s) # Global counts for cart-mini (always global) cart_qty = sum(ci.quantity for ci in all_cart) if all_cart else 0 ctx["cart_count"] = cart_qty + len(all_cal) ctx["cart_total"] = (total(all_cart) or 0) + (calendar_total(all_cal) or 0) # Page-scoped data when viewing a page cart page_post = getattr(g, "page_post", None) if page_post: page_cart = await get_cart_for_page(g.s, page_post.id) page_cal = await get_calendar_entries_for_page(g.s, page_post.id) ctx["cart"] = page_cart ctx["calendar_cart_entries"] = page_cal ctx["page_post"] = page_post ctx["page_config"] = getattr(g, "page_config", None) else: ctx["cart"] = all_cart ctx["calendar_cart_entries"] = all_cal ctx["total"] = total ctx["calendar_total"] = calendar_total # Menu items from coop API (wrapped for attribute access in templates) menu_data = await api_get("coop", "/internal/menu-items") ctx["menu_items"] = dictobj(menu_data) if menu_data else [] return ctx def create_app() -> "Quart": from blog.models.ghost_content import Post from models.page_config import PageConfig app = create_base_app( "cart", context_fn=cart_context, before_request_fns=[_load_cart], ) # App-specific templates override shared templates app_templates = str(Path(__file__).resolve().parent / "templates") app.jinja_loader = ChoiceLoader([ FileSystemLoader(app_templates), app.jinja_loader, ]) # --- Page slug hydration (follows events/market app pattern) --- @app.url_value_preprocessor def pull_page_slug(endpoint, values): if values and "page_slug" in values: g.page_slug = values.pop("page_slug") @app.url_defaults def inject_page_slug(endpoint, values): slug = g.get("page_slug") if slug and "page_slug" not in values: if app.url_map.is_endpoint_expecting(endpoint, "page_slug"): values["page_slug"] = slug @app.before_request async def hydrate_page(): slug = getattr(g, "page_slug", None) if not slug: return post = ( await g.s.execute( select(Post).where(Post.slug == slug, Post.is_page == True) # noqa: E712 ) ).scalar_one_or_none() if not post: abort(404) g.page_post = post g.page_config = ( await g.s.execute( select(PageConfig).where( PageConfig.container_type == "page", PageConfig.container_id == post.id, ) ) ).scalar_one_or_none() # --- Blueprint registration --- # Static prefixes first, dynamic (page_slug) last # Internal API (server-to-server, CSRF-exempt) app.register_blueprint(register_cart_api()) # Orders blueprint app.register_blueprint(register_orders(url_prefix="/orders")) # Global routes (webhook, return, add — specific paths under /) app.register_blueprint( register_cart_global(url_prefix="/"), url_prefix="/", ) # Cart overview at GET / app.register_blueprint( register_cart_overview(url_prefix="/"), url_prefix="/", ) # Page cart at // (dynamic, matched last) app.register_blueprint( register_page_cart(url_prefix="/"), url_prefix="/", ) return app app = create_app()