From aa72b3fad0e3a1dbcb694fd588915be04abec5b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Koray=20=C3=96zturkler?= Date: Sat, 14 Feb 2026 12:28:55 -0500 Subject: [PATCH] Update UI tweaks --- functions/auth/oauth_callback.py | 106 +++++++++++++++++++++++-------- src/pages/ParticipantManager.tsx | 36 ++++++----- src/pages/RFIDReader.tsx | 5 +- src/pages/TeamManager.tsx | 26 ++++---- 4 files changed, 114 insertions(+), 59 deletions(-) diff --git a/functions/auth/oauth_callback.py b/functions/auth/oauth_callback.py index 3c5ac7d..389fc1c 100644 --- a/functions/auth/oauth_callback.py +++ b/functions/auth/oauth_callback.py @@ -1,6 +1,8 @@ from dataclasses import asdict import json import os +import time +import threading import requests from firebase_functions import https_fn from firebase_functions.params import SecretParam, StringParam @@ -92,6 +94,63 @@ ) +# --------------------------------------------------------------------------- +# In-memory spreadsheet cache – avoids hitting Google Sheets on every login. +# Cloud Functions reuse warm instances, so the cache survives across +# invocations on the same instance. A TTL (default 5 min) keeps it fresh. +# --------------------------------------------------------------------------- + +SHEET_CACHE_TTL_SECONDS = 300 # 5 minutes + +_sheet_cache_lock = threading.Lock() +_sheet_cache: dict | None = ( + None # {"ts": float, "reg_headers": [...], "reg_rows": [...], "checkin_headers": [...], "checkin_rows": [...]} +) + + +def _fetch_sheets_data() -> dict: + """Fetch both worksheets from Google Sheets and return them as a dict.""" + import gspread + + creds, _ = google.auth.default( + scopes=[ + "https://www.googleapis.com/auth/spreadsheets.readonly", + "https://www.googleapis.com/auth/drive", + ] + ) + gc = gspread.authorize(creds) + spreadsheet = gc.open_by_url(SPREADSHEET_URL.value) + + reg_sheet = spreadsheet.worksheet("Registration Submissions") + checkin_sheet = spreadsheet.worksheet("Check-in") + + # Batch-fetch all values in two calls (instead of per-row later) + reg_all = reg_sheet.get_all_values() + checkin_all = checkin_sheet.get_all_values() + + return { + "ts": time.monotonic(), + "reg_headers": reg_all[0] if reg_all else [], + "reg_rows": reg_all[1:] if len(reg_all) > 1 else [], + "checkin_headers": checkin_all[0] if checkin_all else [], + "checkin_rows": checkin_all[1:] if len(checkin_all) > 1 else [], + } + + +def _get_cached_sheets() -> dict: + """Return cached sheet data, refreshing if stale or missing.""" + global _sheet_cache + with _sheet_cache_lock: + now = time.monotonic() + if _sheet_cache is None or (now - _sheet_cache["ts"]) > SHEET_CACHE_TTL_SECONDS: + print("[sheet-cache] cache miss – fetching from Google Sheets") + _sheet_cache = _fetch_sheets_data() + else: + age = round(now - _sheet_cache["ts"], 1) + print(f"[sheet-cache] cache hit (age {age}s)") + return _sheet_cache + + def _get_col_index_from_headers(headers: list[str], name: str) -> int: """Return 1-based column index for a given header name. @@ -116,8 +175,6 @@ def _get_registration(uid: str, username: str) -> User: Organizers will not need to be present on the spreadsheet """ - import gspread - is_organizer = uid in [ o.strip() for o in ORGANIZERS_LIST.value.split(",") if o.strip() ] @@ -125,40 +182,33 @@ def _get_registration(uid: str, username: str) -> User: if is_organizer: return User(id=uid, role="organizer", username=username) else: - creds, _ = google.auth.default( - scopes=[ - "https://www.googleapis.com/auth/spreadsheets.readonly", - "https://www.googleapis.com/auth/drive", - ] - ) - gc = gspread.authorize(creds) - - spreadsheet = gc.open_by_url( - SPREADSHEET_URL.value, - ) - - print(spreadsheet.worksheets()) - - reg_sheet = spreadsheet.worksheet("Registration Submissions") - checkin_sheet = spreadsheet.worksheet("Check-in") - - reg_headers = reg_sheet.row_values(1) - checkin_headers = checkin_sheet.row_values(1) + sheets = _get_cached_sheets() + reg_headers = sheets["reg_headers"] + checkin_headers = sheets["checkin_headers"] username_col_idx = _get_col_index_from_headers( reg_headers, USERNAME_COL_R.value ) - cell = reg_sheet.find( - username, in_column=username_col_idx, case_sensitive=False - ) + # Search for the user in cached registration rows + username_lower = username.lower() + matched_row_idx: int | None = None + for i, row in enumerate(sheets["reg_rows"]): + cell_val = _get_cell_from_row(row, username_col_idx) + if cell_val.strip().lower() == username_lower: + matched_row_idx = i + break # participant not registered - if not cell: + if matched_row_idx is None: raise ValueError("participant_not_found") - reg_row = reg_sheet.row_values(cell.row) - checkin_row = checkin_sheet.row_values(cell.row) + reg_row = sheets["reg_rows"][matched_row_idx] + checkin_row = ( + sheets["checkin_rows"][matched_row_idx] + if matched_row_idx < len(sheets["checkin_rows"]) + else [] + ) checked_in_idx = _get_col_index_from_headers( checkin_headers, CHECKED_IN_COL_C.value @@ -259,7 +309,7 @@ def _generate_login_token(uid: str, username: str) -> str: user = _get_registration(uid, username) # If login is disabled for participants, reject them - if user.role != "organizer" and os.getenv("FUNCTIONS_EMULATOR") == "false": + if user.role != "organizer" and os.getenv("FUNCTIONS_EMULATOR") != "true": db = firestore.client() event_doc = db.document("event/main").get() if event_doc.exists: diff --git a/src/pages/ParticipantManager.tsx b/src/pages/ParticipantManager.tsx index 862cea2..ac963be 100644 --- a/src/pages/ParticipantManager.tsx +++ b/src/pages/ParticipantManager.tsx @@ -32,13 +32,19 @@ export default function ParticipantManager() { return ( - {user.username} - {`${user.firstName} ${user.lastName}`} - {user.email} - {user.university || "N/A"} + {user.username} + {`${user.firstName} ${user.lastName}`} + {user.email} + + {user.university || "N/A"} + {user.shirtSize} - {user.dietaryRestrictions || "None"} - {user.attendedEvents.join(", ") || "None"} + + {user.dietaryRestrictions || "None"} + + + {user.attendedEvents.join(", ") || "None"} + {resumeURL && (