From d52c4cf4179911c580496633acaf4ac852f77d86 Mon Sep 17 00:00:00 2001 From: zhangzhenghao Date: Sun, 26 Apr 2026 11:53:15 +0800 Subject: [PATCH 1/7] feat: support anonymous users in /api/repo endpoint - Remove @login_required decorator from get_repo - Logged-in users: continue using Gorse recommendation system - Anonymous users: randomly select a repo from GitHub Trending - Use global_github_client for anonymous users (no user token required) - Cache trending data for performance --- app.py | 89 ++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 21 deletions(-) diff --git a/app.py b/app.py index e895bfd..d2bb8b7 100644 --- a/app.py +++ b/app.py @@ -2,6 +2,7 @@ import io import json import logging +import random import requests import os import sys @@ -319,30 +320,76 @@ def convert_github_blob(url: str) -> str: @app.route("/api/repo") @app.route("/api/repo/") -@login_required def get_repo(category: str = ""): - # Get recommended repo - repo_id = None - for _ in range(2): - try: - repo_id = gorse_client.get_recommend(current_user.login, category)[0] - break - except UnknownObjectException: - logging.warn("repo %s not found" % repo_id) - gorse_client.delete_item(repo_id) + """Get a recommended repository. Uses Gorse for logged-in users, + or random trending repo for anonymous users.""" + + if current_user.is_authenticated: + # Get recommended repo for logged-in users + repo_id = None + for _ in range(2): + try: + repo_id = gorse_client.get_recommend(current_user.login, category)[0] + break + except UnknownObjectException: + logging.warn("repo %s not found" % repo_id) + gorse_client.delete_item(repo_id) + + if repo_id is None: + return Response("No repository found", status=404) + + # Check cache first + cache_key = f"repo:{repo_id}" + cached = get_cached(cache_key) + if cached is not None: + return cached + + full_name = repo_id.replace(":", "/") + github_client = Github(current_user.token["access_token"]) + else: + # For anonymous users, get a random trending repo + # First try to get from trending cache + trending_cache_key = "api:trending:all:daily" + trending_data = get_cached(trending_cache_key) + + if trending_data is None: + # Fetch trending data if not cached + url = ( + "https://raw.githubusercontent.com/isboyjc/github-trending-api/main/" + "data/daily/all.json" + ) + try: + resp = requests.get(url, timeout=10) + resp.raise_for_status() + payload = resp.json() + trending_data = payload.get("items", []) + if trending_data: + save_cache(trending_cache_key, trending_data, expiry_hours=1) + except Exception as e: + app.logger.error(f"Error fetching trending for anonymous user: {e}") + return Response("Failed to fetch trending repositories", status=500) + + if not trending_data: + return Response("No trending repositories available", status=404) + + # Randomly select a trending repo + random_repo = random.choice(trending_data) + full_name = random_repo.get("full_name", "") + repo_id = full_name.replace("/", ":").lower() + + if not full_name: + return Response("Invalid trending repository", status=500) + + # Check cache first + cache_key = f"repo:{repo_id}" + cached = get_cached(cache_key) + if cached is not None: + return cached + + # Use global github client for anonymous users + github_client = global_github_client - if repo_id is None: - return Response("No repository found", status=404) - - # Check cache first - cache_key = f"repo:{repo_id}" - cached = get_cached(cache_key) - if cached is not None: - return cached - # Fetch from GitHub API - full_name = repo_id.replace(":", "/") - github_client = Github(current_user.token["access_token"]) repo = github_client.get_repo(full_name) readme = repo.get_readme() download_url = readme.download_url.lower() From 236ac938b70f7abee59719d35a55f8305975ca6d Mon Sep 17 00:00:00 2001 From: zhangzhenghao Date: Sun, 26 Apr 2026 12:34:13 +0800 Subject: [PATCH 2/7] feat: track read repos in session for anonymous users - Remove @login_required from read_repo endpoint - Logged-in users: continue using Gorse feedback system - Anonymous users: save read repos to Flask session - get_repo: filter out read repos when selecting from trending - Auto-reset session when all trending repos have been read --- app.py | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/app.py b/app.py index d2bb8b7..d1d1746 100644 --- a/app.py +++ b/app.py @@ -372,8 +372,20 @@ def get_repo(category: str = ""): if not trending_data: return Response("No trending repositories available", status=404) + # Filter out already read repos from session + read_repos = session.get("read_repos", []) + available_repos = [ + repo for repo in trending_data + if repo.get("full_name", "").replace("/", ":").lower() not in read_repos + ] + + # If all repos have been read, reset session and use all trending repos + if not available_repos: + available_repos = trending_data + session["read_repos"] = [] + # Randomly select a trending repo - random_repo = random.choice(trending_data) + random_repo = random.choice(available_repos) full_name = random_repo.get("full_name", "") repo_id = full_name.replace("/", ":").lower() @@ -486,17 +498,28 @@ def like_repo(repo_name: str): @app.route("/api/read/", methods=["POST"]) -@login_required def read_repo(repo_name: str): """ Insert a "read" feedback. + For logged-in users: insert to Gorse. + For anonymous users: save to session. """ - try: - return gorse_client.insert_feedback( - "read", current_user.login, repo_name.lower(), datetime.now().isoformat(), 1 - ) - except gorse.GorseException as e: - return Response(e.message, status=e.status_code) + repo_name = repo_name.lower() + + if current_user.is_authenticated: + try: + return gorse_client.insert_feedback( + "read", current_user.login, repo_name, datetime.now().isoformat(), 1 + ) + except gorse.GorseException as e: + return Response(e.message, status=e.status_code) + else: + # For anonymous users, save read repos in session + read_repos = session.get("read_repos", []) + if repo_name not in read_repos: + read_repos.append(repo_name) + session["read_repos"] = read_repos + return Response(json.dumps({"success": True}), mimetype="application/json") @app.route("/api/delete/", methods=["POST"]) From 5da46b37d01999a965cf5d6476443b79db527f3e Mon Sep 17 00:00:00 2001 From: zhangzhenghao Date: Sun, 26 Apr 2026 12:38:24 +0800 Subject: [PATCH 3/7] feat: support anonymous users in frontend - Remove requiresAuth from Explore and Explore Topic routes - Add isAuthenticated state check in MainLayout - Hide Favorites button for anonymous users - Hide AI topic tab for anonymous users - Show login icon instead of logout for anonymous users - 401 interceptor only redirects on Favorites page --- frontend/src/layouts/MainLayout.vue | 44 ++++++++++++++++++++++++++--- frontend/src/main.js | 8 ++++-- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/frontend/src/layouts/MainLayout.vue b/frontend/src/layouts/MainLayout.vue index 965cf8b..4c90c76 100644 --- a/frontend/src/layouts/MainLayout.vue +++ b/frontend/src/layouts/MainLayout.vue @@ -4,7 +4,7 @@ - + @@ -20,7 +20,7 @@
Explore Trending - Favorites + Favorites