/* Patch backend.py: 1) Thêm /api/log + /api/logs để nhận client-side errors (React crash, WebGL, promise rejection) từ ErrorBoundary / window.onerror. 2) Thêm /api/attendance — bảng chuyên cần (5×10) cho bia đá ngoài cửa hang. 3) VERSION_ID ổn định: trước đây `uuid.uuid4().hex[:8]` đổi mỗi lần Space restart → client so sánh version.json thấy khác → tưởng "Có bản mới" và tự reload liên tục. Giờ dùng hằng số "v7" khớp cache-bust bundle, chỉ đổi khi ta chủ động deploy bản mới. Chạy trong Dockerfile build stage (backend.py được COPY vào build stage). Nếu pattern không khớp -> exit(1). */ import fs from 'node:fs' const path = 'backend.py' let s = fs.readFileSync(path, 'utf8') // --- VERSION_ID ổn định (khớp ?v=7 cache-bust) --- const oldVer = 'VERSION_ID = uuid.uuid4().hex[:8]' if (!s.includes(oldVer)) { console.error('PATCH FAILED: VERSION_ID marker not found') process.exit(1) } s = s.replace(oldVer, 'VERSION_ID = "v7"') // IMPORTANT: phải chèn TRƯỚC catch-all @app.get("/{path:path}") (serve_static) — // nếu chèn sau, FastAPI match route theo thứ tự đăng ký → GET /api/logs bị // catch-all nuốt, trả index.html thay vì JSON (không đọc được client errors). const markers = [ '@app.get("/{path:path}")', // nơi lý tưởng: chèn ngay trước catch-all 'if __name__ == "__main__":', // fallback cũ (chỉ dùng nếu không tìm thấy catch-all) ] let idx = -1 let used = -1 for (let i = 0; i < markers.length; i++) { const m = markers[i] const at = s.indexOf(m) if (at !== -1) { // tìm đầu dòng để chèn sạch sẽ const lineStart = s.lastIndexOf('\n', at) + 1 idx = lineStart used = i break } } if (idx === -1) { console.error('PATCH FAILED: marker not found') process.exit(1) } const inject = `# ============ Client error log (từ ErrorBoundary / window.onerror) ============ _client_logs = [] @app.post("/api/log") async def api_log(req: dict): entry = {"at": time.time()} if isinstance(req, dict): entry.update(req) _client_logs.append(entry) del _client_logs[:-400] return {"ok": True} @app.get("/api/logs") async def api_logs(): return {"logs": _client_logs[-200:]} # ============ Attendance (chuyên cần cho bia đá) ============ @app.get("/api/attendance") async def api_attendance(): # rooms["lobby"] = {player_id: Player} — định nghĩa ở đầu backend.py plist = list(rooms.get("lobby", {}).values()) names = [p.name for p in plist] rows = [] idx = 0 for r in range(5): row = [] for c in range(10): if idx < len(names): row.append({"name": names[idx], "number": getattr(plist[idx], "number", None)}) idx += 1 else: row.append(None) rows.append(row) return {"rows": rows, "today": len(plist)} ` s = s.slice(0, idx) + inject + s.slice(idx) fs.writeFileSync(path, s) console.log(`patched backend.py ok: VERSION_ID=v7, /api/log + /api/logs + /api/attendance (marker [${used}])`)