Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| from supabase import create_client, Client, ClientOptions | |
| from datetime import datetime, timezone | |
| import os | |
| def get_supabase_client() -> Client: | |
| # Try using environment variables or st.secrets | |
| url = os.environ.get("SUPABASE_URL") | |
| key = os.environ.get("SUPABASE_KEY") | |
| if not url or not key: | |
| try: | |
| if hasattr(st, "secrets") and st.secrets: | |
| url = url or st.secrets.get("SUPABASE_URL") | |
| key = key or st.secrets.get("SUPABASE_KEY") | |
| except Exception: | |
| pass | |
| if not url or not key: | |
| # Try loading from secrets.toml directly | |
| import toml | |
| try: | |
| secrets_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".streamlit", "secrets.toml") | |
| if os.path.exists(secrets_path): | |
| secrets = toml.load(secrets_path) | |
| url = url or secrets.get("SUPABASE_URL") | |
| key = key or secrets.get("SUPABASE_KEY") | |
| except Exception: | |
| pass | |
| if not url or not key: | |
| raise ValueError("SUPABASE_URL and SUPABASE_KEY must be set in environment variables or secrets") | |
| try: | |
| if "supabase_client" not in st.session_state: | |
| st.session_state.supabase_client = create_client(url, key) | |
| return st.session_state.supabase_client | |
| except Exception: | |
| # Fallback to creating a new client if session_state is unavailable | |
| return create_client(url, key) | |
| def get_current_user_id(client=None): | |
| if client is None: | |
| client = get_supabase_client() | |
| if hasattr(client, "current_user_id"): | |
| return client.current_user_id | |
| try: | |
| session = client.auth.get_session() | |
| if session and session.user: | |
| return session.user.id | |
| except Exception: | |
| pass | |
| return None | |
| def get_active_streak(client=None): | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| if not user_id: | |
| return None | |
| res = client.table('user_streaks').select('*').eq('user_id', user_id).limit(1).execute() | |
| if res.data: | |
| return res.data[0] | |
| else: | |
| new_streak = { | |
| "user_id": user_id, | |
| "current_streak": 0, | |
| "longest_streak": 0, | |
| "last_active_date": None | |
| } | |
| res2 = client.table('user_streaks').insert(new_streak).execute() | |
| return res2.data[0] if res2.data else None | |
| def update_streak(streak_data, client=None): | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| if not user_id: | |
| return None | |
| return client.table('user_streaks').update({ | |
| "current_streak": streak_data["current_streak"], | |
| "longest_streak": streak_data["longest_streak"], | |
| "last_active_date": streak_data["last_active_date"], | |
| "updated_at": datetime.now(timezone.utc).isoformat() | |
| }).eq('user_id', user_id).execute() | |
| def get_due_reviews(client=None): | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| if not user_id: | |
| return [] | |
| now_iso = datetime.now(timezone.utc).isoformat() | |
| res = client.table('user_reviews').select('*, problems(*)').eq('user_id', user_id).lte('next_review', now_iso).order('next_review').execute() | |
| return res.data | |
| def update_review(review_id, update_data, client=None): | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| if not user_id: | |
| return None | |
| return client.table('user_reviews').update(update_data).eq('id', review_id).eq('user_id', user_id).execute() | |
| def log_trace(message: str): | |
| import datetime | |
| import os | |
| try: | |
| log_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "sync.log") | |
| with open(log_path, "a", encoding="utf-8") as f: | |
| f.write(f"[{datetime.datetime.now().isoformat()}] {message}\n") | |
| except Exception: | |
| pass | |
| def insert_problem(problem_data, client=None): | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| if not user_id: | |
| log_trace("db: insert_problem failed - no user_id") | |
| return None | |
| problem_data["user_id"] = user_id | |
| log_trace(f"db: Inserting problem '{problem_data['name']}' with code length {len(problem_data.get('reference_code', '') or '')}") | |
| res = client.table('problems').insert(problem_data).execute() | |
| log_trace(f"db: Insert completed, returned ID: {res.data[0]['id'] if res.data else 'None'}") | |
| return res | |
| def update_problem(problem_id, update_data, client=None): | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| if not user_id: | |
| log_trace("db: update_problem failed - no user_id") | |
| return None | |
| log_trace(f"db: Updating problem {problem_id} with keys {list(update_data.keys())} and code length {len(update_data.get('reference_code', '') or '')}") | |
| res = client.table('problems').update(update_data).eq('id', problem_id).eq('user_id', user_id).execute() | |
| log_trace(f"db: Update completed, returned data: {res.data}") | |
| return res | |
| def insert_review(review_data, client=None): | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| if not user_id: | |
| return None | |
| review_data["user_id"] = user_id | |
| return client.table('user_reviews').insert(review_data).execute() | |
| def get_problem_by_doc_id(doc_id, client=None): | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| if not user_id: | |
| return None | |
| res = client.table('problems').select('*').eq('user_id', user_id).eq('google_doc_id', doc_id).execute() | |
| return res.data[0] if res.data else None | |
| def get_problem_by_name(name, client=None): | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| if not user_id: | |
| return None | |
| res = client.table('problems').select('*').eq('user_id', user_id).eq('name', name).execute() | |
| return res.data[0] if res.data else None | |
| def insert_journey_progress(problem_number: int, client=None): | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| if not user_id: | |
| return None | |
| return client.table('user_journey_progress').upsert({ | |
| "user_id": user_id, | |
| "problem_number": problem_number | |
| }, on_conflict="user_id,problem_number").execute() | |
| def delete_journey_progress(problem_number: int, client=None): | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| if not user_id: | |
| return None | |
| return client.table('user_journey_progress').delete().eq('user_id', user_id).eq('problem_number', problem_number).execute() | |
| def toggle_journey_progress(problem_number: int, client=None): | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| if not user_id: | |
| return None | |
| res = client.table('user_journey_progress').select('id').eq('user_id', user_id).eq('problem_number', problem_number).execute() | |
| if res.data: | |
| return delete_journey_progress(problem_number, client) | |
| else: | |
| return insert_journey_progress(problem_number, client) | |
| def get_user_journey_progress(client=None) -> set: | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| if not user_id: | |
| return set() | |
| res = client.table('user_journey_progress').select('problem_number').eq('user_id', user_id).execute() | |
| return {row['problem_number'] for row in res.data} if res.data else set() | |
| def get_problem_titles_by_number(client=None) -> dict: | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| res = client.table('problems').select('problem_number, name, user_id').execute() | |
| mapping = {} | |
| if res.data: | |
| # Sort so that current user's titles override others | |
| rows = sorted(res.data, key=lambda r: 1 if r.get('user_id') == user_id else 0) | |
| for row in rows: | |
| num = row.get('problem_number') | |
| if num is not None: | |
| base_name = row['name'].split(" - ")[0].strip() | |
| mapping[num] = base_name | |
| return mapping | |
| def get_problems_by_number(problem_number: int, client=None) -> list: | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| # Fetch all versions of this problem number | |
| res = client.table('problems').select('*').eq('problem_number', problem_number).execute() | |
| problems = res.data if res.data else [] | |
| if not problems: | |
| return [] | |
| # Prioritize current user's problem | |
| if user_id: | |
| user_probs = [p for p in problems if p.get('user_id') == user_id] | |
| if user_probs: | |
| return user_probs | |
| # Fallback to returning all public versions | |
| return problems | |
| def get_reviews_by_problem_id(problem_id: str, client=None) -> list: | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| if not user_id: | |
| return [] | |
| res = client.table('user_reviews').select('*, problems(*)').eq('user_id', user_id).eq('problem_id', problem_id).execute() | |
| return res.data if res.data else [] | |
| def get_user_score(client=None): | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| if not user_id: | |
| return None | |
| res = client.table('user_scores').select('*').eq('user_id', user_id).limit(1).execute() | |
| if res.data: | |
| return res.data[0] | |
| else: | |
| new_score = { | |
| "user_id": user_id, | |
| "total_score": 0, | |
| "python_challenges_completed": 0, | |
| "sql_challenges_completed": 0 | |
| } | |
| try: | |
| res2 = client.table('user_scores').insert(new_score).execute() | |
| return res2.data[0] if res2.data else None | |
| except Exception: | |
| return None | |
| def increment_user_score(points: int, challenge_type: str, client=None): | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| if not user_id: | |
| return None | |
| current_score = get_user_score(client) | |
| if not current_score: | |
| return None | |
| update_data = { | |
| "total_score": current_score["total_score"] + points, | |
| "updated_at": datetime.now(timezone.utc).isoformat() | |
| } | |
| if challenge_type == "python": | |
| update_data["python_challenges_completed"] = current_score["python_challenges_completed"] + 1 | |
| elif challenge_type == "mysql": | |
| update_data["sql_challenges_completed"] = current_score["sql_challenges_completed"] + 1 | |
| return client.table('user_scores').update(update_data).eq('user_id', user_id).execute() | |
| def add_challenge_history(challenge_type: str, challenge_title: str, points_awarded: int, client=None): | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| if not user_id: | |
| return None | |
| history_data = { | |
| "user_id": user_id, | |
| "challenge_type": challenge_type, | |
| "challenge_title": challenge_title, | |
| "points_awarded": points_awarded | |
| } | |
| return client.table('challenge_history').insert(history_data).execute() | |
| def get_challenge_history(client=None): | |
| if client is None: | |
| client = get_supabase_client() | |
| user_id = get_current_user_id(client) | |
| if not user_id: | |
| return [] | |
| res = client.table('challenge_history').select('*').eq('user_id', user_id).order('completed_at', desc=True).execute() | |
| return res.data if res.data else [] | |