Spaces:
Sleeping
Sleeping
saadrizvi09 commited on
Commit Β·
92375e9
1
Parent(s): 9d905d6
cache
Browse files- routers/orders.py +103 -6
routers/orders.py
CHANGED
|
@@ -104,12 +104,45 @@ class MenuItemUpdate(BaseModel):
|
|
| 104 |
category_veg: bool | None = None
|
| 105 |
|
| 106 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 108 |
# CART
|
| 109 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 110 |
|
| 111 |
-
def _enrich_cart(sb, session_id: str) -> dict:
|
| 112 |
-
"""Return enriched cart data for a session
|
| 113 |
cart = sb.table("carts").select("id, version").eq("session_id", session_id).execute()
|
| 114 |
if not cart.data:
|
| 115 |
return {"items": [], "version": 0, "total": 0}
|
|
@@ -125,10 +158,13 @@ def _enrich_cart(sb, session_id: str) -> dict:
|
|
| 125 |
if not items.data:
|
| 126 |
return {"items": [], "version": cart.data[0]["version"], "total": 0}
|
| 127 |
|
| 128 |
-
#
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
enriched = []
|
| 134 |
for item in items.data:
|
|
@@ -195,6 +231,64 @@ async def add_to_cart(data: AddToCartRequest):
|
|
| 195 |
return result
|
| 196 |
|
| 197 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
@router.post("/cart/remove")
|
| 199 |
async def remove_from_cart(data: RemoveFromCartRequest):
|
| 200 |
sb = get_supabase()
|
|
@@ -651,6 +745,7 @@ async def create_menu_item(data: MenuItemCreate, authorization: str = Header(Non
|
|
| 651 |
"availability": data.availability,
|
| 652 |
}).execute()
|
| 653 |
|
|
|
|
| 654 |
return result.data[0] if result.data else {"message": "Created"}
|
| 655 |
|
| 656 |
|
|
@@ -666,6 +761,7 @@ async def update_menu_item(item_id: int, data: MenuItemUpdate, authorization: st
|
|
| 666 |
raise HTTPException(status_code=400, detail="Nothing to update")
|
| 667 |
|
| 668 |
sb.table("menu").update(update_data).eq("id", item_id).execute()
|
|
|
|
| 669 |
return {"message": "Updated"}
|
| 670 |
|
| 671 |
|
|
@@ -677,4 +773,5 @@ async def delete_menu_item(item_id: int, authorization: str = Header(None)):
|
|
| 677 |
|
| 678 |
sb = get_supabase()
|
| 679 |
sb.table("menu").delete().eq("id", item_id).execute()
|
|
|
|
| 680 |
return {"message": "Deleted"}
|
|
|
|
| 104 |
category_veg: bool | None = None
|
| 105 |
|
| 106 |
|
| 107 |
+
class CartBatchOp(BaseModel):
|
| 108 |
+
menu_item_id: int
|
| 109 |
+
delta: int # +1 to add, -N to remove N
|
| 110 |
+
|
| 111 |
+
class CartBatchRequest(BaseModel):
|
| 112 |
+
session_id: str
|
| 113 |
+
participant_id: str | None = None
|
| 114 |
+
operations: list[CartBatchOp]
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 118 |
+
# IN-MEMORY MENU CACHE (avoids re-fetching on every cart op)
|
| 119 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 120 |
+
import time as _time
|
| 121 |
+
|
| 122 |
+
_menu_cache: dict[int, dict] = {} # restaurant_id β {data, ts}
|
| 123 |
+
_MENU_CACHE_TTL = 120 # seconds
|
| 124 |
+
|
| 125 |
+
def _get_menu_map(sb, restaurant_id: int) -> dict[int, dict]:
|
| 126 |
+
"""Return menu items indexed by id, cached for 120s."""
|
| 127 |
+
cached = _menu_cache.get(restaurant_id)
|
| 128 |
+
if cached and (_time.time() - cached["ts"]) < _MENU_CACHE_TTL:
|
| 129 |
+
return cached["data"]
|
| 130 |
+
rows = sb.table("menu").select("id, dish_name, price, category, image_link, variant_name").eq("restaurant_id", restaurant_id).execute()
|
| 131 |
+
menu_map = {m["id"]: m for m in (rows.data or [])}
|
| 132 |
+
_menu_cache[restaurant_id] = {"data": menu_map, "ts": _time.time()}
|
| 133 |
+
return menu_map
|
| 134 |
+
|
| 135 |
+
def invalidate_menu_cache(restaurant_id: int):
|
| 136 |
+
"""Call after menu CRUD to bust cache."""
|
| 137 |
+
_menu_cache.pop(restaurant_id, None)
|
| 138 |
+
|
| 139 |
+
|
| 140 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 141 |
# CART
|
| 142 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 143 |
|
| 144 |
+
def _enrich_cart(sb, session_id: str, restaurant_id: int | None = None) -> dict:
|
| 145 |
+
"""Return enriched cart data for a session. Uses menu cache if restaurant_id is provided."""
|
| 146 |
cart = sb.table("carts").select("id, version").eq("session_id", session_id).execute()
|
| 147 |
if not cart.data:
|
| 148 |
return {"items": [], "version": 0, "total": 0}
|
|
|
|
| 158 |
if not items.data:
|
| 159 |
return {"items": [], "version": cart.data[0]["version"], "total": 0}
|
| 160 |
|
| 161 |
+
# Use cache if restaurant_id provided, else fetch by ids
|
| 162 |
+
if restaurant_id:
|
| 163 |
+
menu_map = _get_menu_map(sb, restaurant_id)
|
| 164 |
+
else:
|
| 165 |
+
menu_ids = list({i["menu_item_id"] for i in items.data})
|
| 166 |
+
menu_rows = sb.table("menu").select("id, dish_name, price, category, image_link, variant_name").in_("id", menu_ids).execute()
|
| 167 |
+
menu_map = {m["id"]: m for m in (menu_rows.data or [])}
|
| 168 |
|
| 169 |
enriched = []
|
| 170 |
for item in items.data:
|
|
|
|
| 231 |
return result
|
| 232 |
|
| 233 |
|
| 234 |
+
@router.post("/cart/batch")
|
| 235 |
+
async def batch_cart(data: CartBatchRequest):
|
| 236 |
+
"""Process multiple cart add/remove operations in a single request.
|
| 237 |
+
Each operation has menu_item_id and delta (+N to add, -N to remove).
|
| 238 |
+
This eliminates per-item round trips for rapid tapping."""
|
| 239 |
+
sb = get_supabase()
|
| 240 |
+
|
| 241 |
+
# 1. Check payment lock + get cart (single query each)
|
| 242 |
+
session = sb.table("sessions").select("payment_lock, restaurant_id").eq("id", data.session_id).execute()
|
| 243 |
+
if not session.data:
|
| 244 |
+
raise HTTPException(status_code=404, detail="Session not found")
|
| 245 |
+
if session.data[0].get("payment_lock"):
|
| 246 |
+
raise HTTPException(status_code=423, detail="Cart is locked β payment in progress")
|
| 247 |
+
restaurant_id = session.data[0]["restaurant_id"]
|
| 248 |
+
|
| 249 |
+
cart = sb.table("carts").select("id, version").eq("session_id", data.session_id).execute()
|
| 250 |
+
if not cart.data:
|
| 251 |
+
raise HTTPException(status_code=404, detail="Cart not found")
|
| 252 |
+
cart_id = cart.data[0]["id"]
|
| 253 |
+
|
| 254 |
+
# 2. Fetch ALL current cart items in one query
|
| 255 |
+
existing_items = sb.table("cart_items").select("id, menu_item_id, quantity").eq("cart_id", cart_id).execute()
|
| 256 |
+
existing_map: dict[int, dict] = {}
|
| 257 |
+
for it in (existing_items.data or []):
|
| 258 |
+
existing_map[it["menu_item_id"]] = it
|
| 259 |
+
|
| 260 |
+
# 3. Process all operations (minimal DB writes)
|
| 261 |
+
for op in data.operations:
|
| 262 |
+
cur = existing_map.get(op.menu_item_id)
|
| 263 |
+
if cur:
|
| 264 |
+
new_qty = cur["quantity"] + op.delta
|
| 265 |
+
if new_qty <= 0:
|
| 266 |
+
sb.table("cart_items").delete().eq("id", cur["id"]).execute()
|
| 267 |
+
del existing_map[op.menu_item_id]
|
| 268 |
+
else:
|
| 269 |
+
sb.table("cart_items").update({"quantity": new_qty}).eq("id", cur["id"]).execute()
|
| 270 |
+
cur["quantity"] = new_qty
|
| 271 |
+
elif op.delta > 0:
|
| 272 |
+
sb.table("cart_items").insert({
|
| 273 |
+
"cart_id": cart_id,
|
| 274 |
+
"menu_item_id": op.menu_item_id,
|
| 275 |
+
"quantity": op.delta,
|
| 276 |
+
"added_by": data.participant_id,
|
| 277 |
+
}).execute()
|
| 278 |
+
|
| 279 |
+
# 4. Bump version ONCE for entire batch
|
| 280 |
+
new_ver = cart.data[0]["version"] + 1
|
| 281 |
+
sb.table("carts").update({"version": new_ver, "updated_at": datetime.now(timezone.utc).isoformat()}).eq("id", cart_id).execute()
|
| 282 |
+
|
| 283 |
+
# 5. Enrich with cached menu data
|
| 284 |
+
result = _enrich_cart(sb, data.session_id, restaurant_id)
|
| 285 |
+
|
| 286 |
+
# 6. Broadcast in background
|
| 287 |
+
asyncio.create_task(ws_manager.broadcast(data.session_id, {"type": "cart_update", "cart": result}))
|
| 288 |
+
|
| 289 |
+
return result
|
| 290 |
+
|
| 291 |
+
|
| 292 |
@router.post("/cart/remove")
|
| 293 |
async def remove_from_cart(data: RemoveFromCartRequest):
|
| 294 |
sb = get_supabase()
|
|
|
|
| 745 |
"availability": data.availability,
|
| 746 |
}).execute()
|
| 747 |
|
| 748 |
+
invalidate_menu_cache(data.restaurant_id)
|
| 749 |
return result.data[0] if result.data else {"message": "Created"}
|
| 750 |
|
| 751 |
|
|
|
|
| 761 |
raise HTTPException(status_code=400, detail="Nothing to update")
|
| 762 |
|
| 763 |
sb.table("menu").update(update_data).eq("id", item_id).execute()
|
| 764 |
+
invalidate_menu_cache(payload["restaurant_id"])
|
| 765 |
return {"message": "Updated"}
|
| 766 |
|
| 767 |
|
|
|
|
| 773 |
|
| 774 |
sb = get_supabase()
|
| 775 |
sb.table("menu").delete().eq("id", item_id).execute()
|
| 776 |
+
invalidate_menu_cache(payload["restaurant_id"])
|
| 777 |
return {"message": "Deleted"}
|