diff --git a/backend/bot.py b/backend/bot.py index a8711b2..8aa43d9 100644 --- a/backend/bot.py +++ b/backend/bot.py @@ -35,6 +35,7 @@ from supabase_service import ( ban_user, clear_all_staged, + clear_github_token, count_stats, get_all_users, get_pending_files, @@ -1130,13 +1131,23 @@ async def commit_now_callback(update: Update, context: ContextTypes.DEFAULT_TYPE return WAITING_PROTECTED_BRANCH_NAME else: - error_msg = result.get('message', 'Unknown error') - await channel_logger.log_commit_failed(telegram_id, active_repo, error_msg) - await query.edit_message_text( - f"[X] *Commit failed.*\n\n" - f"Error: {error_msg}\n\n" - f"Your staged files are safe. Try /files again." - ) + if result.get("error") == "invalid_token": + clear_github_token(telegram_id) + await channel_logger.log_commit_failed(telegram_id, active_repo, "invalid_token") + await query.edit_message_text( + "🔑 *Your GitHub token has expired or been revoked.*\n\n" + "Your staged files are safe — no changes were made.\n\n" + "Please run /auth to reconnect your GitHub account and try again.", + parse_mode=ParseMode.MARKDOWN + ) + else: + error_msg = result.get('message', 'Unknown error') + await channel_logger.log_commit_failed(telegram_id, active_repo, error_msg) + await query.edit_message_text( + f"[X] *Commit failed.*\n\n" + f"Error: {error_msg}\n\n" + f"Your staged files are safe. Try /files again." + ) context.user_data.clear() return ConversationHandler.END @@ -1211,9 +1222,19 @@ async def commit_force_callback(update: Update, context: ContextTypes.DEFAULT_TY parse_mode=ParseMode.MARKDOWN ) else: - error_msg = result.get('message', 'Unknown error') - await channel_logger.log_commit_failed(telegram_id, active_repo, error_msg) - await query.edit_message_text(f"[X] Force commit failed: {error_msg}") + if result.get("error") == "invalid_token": + clear_github_token(telegram_id) + await channel_logger.log_commit_failed(telegram_id, active_repo, "invalid_token") + await query.edit_message_text( + "🔑 *Your GitHub token has expired or been revoked.*\n\n" + "Your staged files are safe — no changes were made.\n\n" + "Please run /auth to reconnect your GitHub account and try again.", + parse_mode=ParseMode.MARKDOWN + ) + else: + error_msg = result.get('message', 'Unknown error') + await channel_logger.log_commit_failed(telegram_id, active_repo, error_msg) + await query.edit_message_text(f"[X] Force commit failed: {error_msg}") context.user_data.clear() return ConversationHandler.END diff --git a/backend/github_service.py b/backend/github_service.py index 7458469..118c949 100644 --- a/backend/github_service.py +++ b/backend/github_service.py @@ -240,6 +240,8 @@ def commit_files( } except GithubException as e: + if e.status == 401: + return {"ok": False, "error": "invalid_token", "message": "GitHub token is invalid or expired. Please re-authenticate via /auth."} if e.status == 409: return {"ok": False, "error": "conflict", "message": "SHA conflict on GitHub"} if e.status == 422: diff --git a/backend/routes/staged_files.py b/backend/routes/staged_files.py index 6baf0ea..0da9d0d 100644 --- a/backend/routes/staged_files.py +++ b/backend/routes/staged_files.py @@ -169,6 +169,11 @@ async def commit_direct(payload: DirectCommitPayload, telegram_id: str = Depends status_code=409, detail=f"Conflict in: {', '.join(conflict_files)}. Use /files in Telegram \u2192 Force Commit.", ) + if result.get("error") == "invalid_token": + raise HTTPException( + status_code=401, + detail="GitHub token is invalid or expired. Please re-authenticate via /auth in Telegram.", + ) raise HTTPException( status_code=500, detail=result.get("message", "GitHub commit failed."), diff --git a/backend/supabase_service.py b/backend/supabase_service.py index 9bd8256..290c31a 100644 --- a/backend/supabase_service.py +++ b/backend/supabase_service.py @@ -441,3 +441,43 @@ def update_github_token(telegram_id: str, token: str) -> bool: print(f"[supabase] update_github_token error: {e}") return False + +def clear_github_token(telegram_id: str) -> bool: + """Clear a stale or revoked GitHub token so the user is prompted to re-authenticate.""" + try: + get_client().table("users") \ + .update({"github_token": None}) \ + .eq("telegram_id", telegram_id) \ + .execute() + return True + except Exception as e: + print(f"[supabase] clear_github_token error: {e}") + return False + + +def update_github_username(telegram_id: str, github_username: str) -> None: + """Persist the user's GitHub login so webhooks can map assignee -> telegram_id.""" + if not github_username: + return + try: + get_client().table("users") \ + .update({"github_username": github_username.lower()}) \ + .eq("telegram_id", telegram_id) \ + .execute() + except Exception as e: + print(f"[supabase] update_github_username error: {e}") + + +def get_user_by_github_username(github_username: str) -> dict | None: + """Reverse lookup: GitHub login -> user row. Case-insensitive. None if unmapped.""" + if not github_username: + return None + try: + result = get_client().table("users") \ + .select("*") \ + .ilike("github_username", github_username) \ + .execute() + return result.data[0] if result.data else None + except Exception as e: + print(f"[supabase] get_user_by_github_username error: {e}") + return None