saadrizvi09 commited on
Commit
9d905d6
Β·
1 Parent(s): 21793ce

websocket fix

Browse files
Files changed (3) hide show
  1. routers/orders.py +58 -9
  2. routers/ws.py +13 -0
  3. schema.sql +3 -1
routers/orders.py CHANGED
@@ -30,6 +30,19 @@ router = APIRouter(tags=["orders"])
30
 
31
  # In-memory payment auto-unlock timers
32
  _payment_timers: dict[str, asyncio.Task] = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
 
35
  # ═══════════════════════════════════════════════════════════
@@ -173,10 +186,11 @@ async def add_to_cart(data: AddToCartRequest):
173
  new_ver = cart.data[0]["version"] + 1
174
  sb.table("carts").update({"version": new_ver, "updated_at": datetime.now(timezone.utc).isoformat()}).eq("id", cart_id).execute()
175
 
 
176
  result = _enrich_cart(sb, data.session_id)
177
 
178
- # Broadcast via WebSocket
179
- await ws_manager.broadcast(data.session_id, {"type": "cart_update", "cart": result})
180
 
181
  return result
182
 
@@ -190,7 +204,7 @@ async def remove_from_cart(data: RemoveFromCartRequest):
190
 
191
  sb.table("cart_items").delete().eq("id", data.cart_item_id).execute()
192
  result = _enrich_cart(sb, data.session_id)
193
- await ws_manager.broadcast(data.session_id, {"type": "cart_update", "cart": result})
194
  return result
195
 
196
 
@@ -207,7 +221,7 @@ async def update_cart_quantity(data: UpdateCartQtyRequest):
207
  sb.table("cart_items").update({"quantity": data.quantity}).eq("id", data.cart_item_id).execute()
208
 
209
  result = _enrich_cart(sb, data.session_id)
210
- await ws_manager.broadcast(data.session_id, {"type": "cart_update", "cart": result})
211
  return result
212
 
213
 
@@ -284,13 +298,19 @@ async def get_session_status(session_id: str):
284
  .execute()
285
  )
286
 
 
 
 
 
287
  return {
288
  "session_status": s["status"],
289
  "payment_lock": s.get("payment_lock", False),
290
  "payment_lock_at": s.get("payment_lock_at"),
 
291
  "chef_eta_minutes": s.get("chef_eta_minutes"),
292
  "chef_eta_set_at": s.get("chef_eta_set_at"),
293
  "order": order.data[0] if order.data else None,
 
294
  }
295
 
296
 
@@ -308,10 +328,12 @@ async def lock_payment(data: PaymentLockRequest):
308
  raise HTTPException(status_code=423, detail="Payment already locked")
309
 
310
  now = datetime.now(timezone.utc).isoformat()
311
- sb.table("sessions").update({
312
  "payment_lock": True,
313
  "payment_lock_at": now,
314
- }).eq("id", data.session_id).execute()
 
 
315
 
316
  # Broadcast lock
317
  await ws_manager.broadcast(data.session_id, {
@@ -326,7 +348,8 @@ async def lock_payment(data: PaymentLockRequest):
326
  sb2 = get_supabase()
327
  s = sb2.table("sessions").select("payment_lock, status").eq("id", data.session_id).execute()
328
  if s.data and s.data[0].get("payment_lock") and s.data[0]["status"] == "active":
329
- sb2.table("sessions").update({"payment_lock": False, "payment_lock_at": None}).eq("id", data.session_id).execute()
 
330
  await ws_manager.broadcast(data.session_id, {"type": "payment_unlocked"})
331
 
332
  if data.session_id in _payment_timers:
@@ -336,6 +359,30 @@ async def lock_payment(data: PaymentLockRequest):
336
  return {"message": "Cart locked for payment", "timeout_seconds": 120}
337
 
338
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  @router.post("/payment/confirm")
340
  async def confirm_payment(data: PaymentConfirmRequest):
341
  sb = get_supabase()
@@ -407,10 +454,12 @@ async def confirm_payment(data: PaymentConfirmRequest):
407
  }).execute()
408
 
409
  # Complete session
410
- sb.table("sessions").update({
411
  "status": "completed",
412
  "payment_lock": False,
413
- }).eq("id", data.session_id).execute()
 
 
414
 
415
  # Table β†’ dirty
416
  sb.table("restaurant_tables").update({"status": "dirty"}).eq("id", s["table_id"]).execute()
 
30
 
31
  # In-memory payment auto-unlock timers
32
  _payment_timers: dict[str, asyncio.Task] = {}
33
+ # In-memory payment lock owner tracking (session_id β†’ participant_id)
34
+ _payment_lock_owners: dict[str, str] = {}
35
+
36
+
37
+ def _safe_session_update(sb, session_id: str, fields: dict):
38
+ """Update session, retrying without new columns if they don't exist yet."""
39
+ try:
40
+ sb.table("sessions").update(fields).eq("id", session_id).execute()
41
+ except Exception:
42
+ # Fallback: strip columns that may not exist in DB yet
43
+ safe = {k: v for k, v in fields.items() if k in ("status", "payment_lock")}
44
+ if safe:
45
+ sb.table("sessions").update(safe).eq("id", session_id).execute()
46
 
47
 
48
  # ═══════════════════════════════════════════════════════════
 
186
  new_ver = cart.data[0]["version"] + 1
187
  sb.table("carts").update({"version": new_ver, "updated_at": datetime.now(timezone.utc).isoformat()}).eq("id", cart_id).execute()
188
 
189
+ # Enrich cart once
190
  result = _enrich_cart(sb, data.session_id)
191
 
192
+ # Broadcast in background (non-blocking)
193
+ asyncio.create_task(ws_manager.broadcast(data.session_id, {"type": "cart_update", "cart": result}))
194
 
195
  return result
196
 
 
204
 
205
  sb.table("cart_items").delete().eq("id", data.cart_item_id).execute()
206
  result = _enrich_cart(sb, data.session_id)
207
+ asyncio.create_task(ws_manager.broadcast(data.session_id, {"type": "cart_update", "cart": result}))
208
  return result
209
 
210
 
 
221
  sb.table("cart_items").update({"quantity": data.quantity}).eq("id", data.cart_item_id).execute()
222
 
223
  result = _enrich_cart(sb, data.session_id)
224
+ asyncio.create_task(ws_manager.broadcast(data.session_id, {"type": "cart_update", "cart": result}))
225
  return result
226
 
227
 
 
298
  .execute()
299
  )
300
 
301
+ # Count participants for split bill
302
+ participants = sb.table("participants").select("id").eq("session_id", session_id).execute()
303
+ participant_count = len(participants.data) if participants.data else 1
304
+
305
  return {
306
  "session_status": s["status"],
307
  "payment_lock": s.get("payment_lock", False),
308
  "payment_lock_at": s.get("payment_lock_at"),
309
+ "payment_locked_by": s.get("payment_locked_by") or _payment_lock_owners.get(session_id),
310
  "chef_eta_minutes": s.get("chef_eta_minutes"),
311
  "chef_eta_set_at": s.get("chef_eta_set_at"),
312
  "order": order.data[0] if order.data else None,
313
+ "participant_count": participant_count,
314
  }
315
 
316
 
 
328
  raise HTTPException(status_code=423, detail="Payment already locked")
329
 
330
  now = datetime.now(timezone.utc).isoformat()
331
+ _safe_session_update(sb, data.session_id, {
332
  "payment_lock": True,
333
  "payment_lock_at": now,
334
+ "payment_locked_by": data.participant_id,
335
+ })
336
+ _payment_lock_owners[data.session_id] = data.participant_id
337
 
338
  # Broadcast lock
339
  await ws_manager.broadcast(data.session_id, {
 
348
  sb2 = get_supabase()
349
  s = sb2.table("sessions").select("payment_lock, status").eq("id", data.session_id).execute()
350
  if s.data and s.data[0].get("payment_lock") and s.data[0]["status"] == "active":
351
+ _safe_session_update(sb2, data.session_id, {"payment_lock": False, "payment_lock_at": None, "payment_locked_by": None})
352
+ _payment_lock_owners.pop(data.session_id, None)
353
  await ws_manager.broadcast(data.session_id, {"type": "payment_unlocked"})
354
 
355
  if data.session_id in _payment_timers:
 
359
  return {"message": "Cart locked for payment", "timeout_seconds": 120}
360
 
361
 
362
+ @router.post("/payment/unlock")
363
+ async def unlock_payment(data: PaymentLockRequest):
364
+ """Unlock payment (back button) β€” only the locker can unlock."""
365
+ sb = get_supabase()
366
+ session = sb.table("sessions").select("*").eq("id", data.session_id).execute()
367
+ if not session.data:
368
+ raise HTTPException(status_code=404, detail="Session not found")
369
+ if not session.data[0].get("payment_lock"):
370
+ return {"message": "Already unlocked"}
371
+
372
+ _safe_session_update(sb, data.session_id, {
373
+ "payment_lock": False,
374
+ "payment_lock_at": None,
375
+ "payment_locked_by": None,
376
+ })
377
+ _payment_lock_owners.pop(data.session_id, None)
378
+ if data.session_id in _payment_timers:
379
+ _payment_timers[data.session_id].cancel()
380
+ del _payment_timers[data.session_id]
381
+
382
+ await ws_manager.broadcast(data.session_id, {"type": "payment_unlocked"})
383
+ return {"message": "Payment unlocked"}
384
+
385
+
386
  @router.post("/payment/confirm")
387
  async def confirm_payment(data: PaymentConfirmRequest):
388
  sb = get_supabase()
 
454
  }).execute()
455
 
456
  # Complete session
457
+ _safe_session_update(sb, data.session_id, {
458
  "status": "completed",
459
  "payment_lock": False,
460
+ "payment_locked_by": None,
461
+ })
462
+ _payment_lock_owners.pop(data.session_id, None)
463
 
464
  # Table β†’ dirty
465
  sb.table("restaurant_tables").update({"status": "dirty"}).eq("id", s["table_id"]).execute()
routers/ws.py CHANGED
@@ -18,6 +18,14 @@ from supabase_client import get_supabase
18
  router = APIRouter()
19
  logger = logging.getLogger(__name__)
20
 
 
 
 
 
 
 
 
 
21
 
22
  # ── Shared helpers: build state payloads ───────────────────
23
 
@@ -80,12 +88,17 @@ def _get_session_state(session_id: str) -> dict:
80
  .limit(1)
81
  .execute()
82
  )
 
 
 
83
  return {
84
  "session_status": s["status"],
85
  "payment_lock": s.get("payment_lock", False),
 
86
  "chef_eta_minutes": s.get("chef_eta_minutes"),
87
  "chef_eta_set_at": s.get("chef_eta_set_at"),
88
  "order": order.data[0] if order.data else None,
 
89
  }
90
 
91
 
 
18
  router = APIRouter()
19
  logger = logging.getLogger(__name__)
20
 
21
+ # Import in-memory lock owners from orders module (lazy to avoid circular)
22
+ def _get_lock_owner(session_id: str) -> str | None:
23
+ try:
24
+ from routers.orders import _payment_lock_owners
25
+ return _payment_lock_owners.get(session_id)
26
+ except Exception:
27
+ return None
28
+
29
 
30
  # ── Shared helpers: build state payloads ───────────────────
31
 
 
88
  .limit(1)
89
  .execute()
90
  )
91
+ # Count participants
92
+ participants = sb.table("participants").select("id", count="exact").eq("session_id", session_id).execute()
93
+ participant_count = participants.count if participants.count else 1
94
  return {
95
  "session_status": s["status"],
96
  "payment_lock": s.get("payment_lock", False),
97
+ "payment_locked_by": s.get("payment_locked_by") or _get_lock_owner(session_id),
98
  "chef_eta_minutes": s.get("chef_eta_minutes"),
99
  "chef_eta_set_at": s.get("chef_eta_set_at"),
100
  "order": order.data[0] if order.data else None,
101
+ "participant_count": participant_count,
102
  }
103
 
104
 
schema.sql CHANGED
@@ -244,7 +244,9 @@ CREATE TABLE IF NOT EXISTS sessions (
244
  status session_status DEFAULT 'active',
245
  created_at TIMESTAMPTZ DEFAULT now(),
246
  expires_at TIMESTAMPTZ,
247
- payment_lock BOOLEAN DEFAULT FALSE,
 
 
248
  FOREIGN KEY (table_id) REFERENCES restaurant_tables(id),
249
  FOREIGN KEY (restaurant_id) REFERENCES restaurants(id)
250
  );
 
244
  status session_status DEFAULT 'active',
245
  created_at TIMESTAMPTZ DEFAULT now(),
246
  expires_at TIMESTAMPTZ,
247
+ payment_lock BOOLEAN DEFAULT FALSE,
248
+ payment_locked_by TEXT,
249
+ payment_lock_at TIMESTAMPTZ,
250
  FOREIGN KEY (table_id) REFERENCES restaurant_tables(id),
251
  FOREIGN KEY (restaurant_id) REFERENCES restaurants(id)
252
  );