saadrizvi09 commited on
Commit
92375e9
Β·
1 Parent(s): 9d905d6
Files changed (1) hide show
  1. 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 (single batch query)."""
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
- # Batch-fetch all menu items at once instead of N queries
129
- menu_ids = list({i["menu_item_id"] for i in items.data})
130
- menu_rows = sb.table("menu").select("id, dish_name, price, category, image_link, variant_name").in_("id", menu_ids).execute()
131
- menu_map = {m["id"]: m for m in (menu_rows.data or [])}
 
 
 
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"}