Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ dist-ssr
*.sln
*.sw?

# Env files
.env
Backend/.env
Frontend/.env

# Python
__pycache__/
*.py[cod]
Expand Down
20 changes: 20 additions & 0 deletions Backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from dotenv import load_dotenv
from contextlib import asynccontextmanager
from app.routes import ai
from supabase import create_client, AsyncClient

# Load environment variables
load_dotenv()
Expand All @@ -32,9 +33,28 @@ async def create_tables():
@asynccontextmanager
async def lifespan(app: FastAPI):
print("App is starting...")
# Initialize Supabase async client (non-fatal if missing envs)
supabase_url = os.getenv("SUPABASE_URL")
supabase_key = os.getenv("SUPABASE_KEY")
if supabase_url and supabase_key:
try:
app.state.supabase = create_client(supabase_url, supabase_key, supabase_cls=AsyncClient)
except Exception as exc: # broad to avoid startup crash; logs include stack
app.state.supabase = None
logging.exception("Failed to initialize Supabase AsyncClient: %s", exc)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove redundant exception parameter from logging.exception.

The logging.exception() method automatically includes the exception traceback and message, so passing exc as a format parameter is redundant.

🔎 Proposed fix
-            logging.exception("Failed to initialize Supabase AsyncClient: %s", exc)
+            logging.exception("Failed to initialize Supabase AsyncClient")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
logging.exception("Failed to initialize Supabase AsyncClient: %s", exc)
logging.exception("Failed to initialize Supabase AsyncClient")
🧰 Tools
🪛 Ruff (0.14.10)

44-44: Redundant exception object included in logging.exception call

(TRY401)

🤖 Prompt for AI Agents
In Backend/app/main.py around line 44, the call to logging.exception currently
passes the caught exception object as a format parameter which is redundant
because logging.exception automatically includes the current exception info;
remove the extra `exc` parameter and call logging.exception with just the
message ("Failed to initialize Supabase AsyncClient") so the traceback and
exception message are logged once correctly.

else:
app.state.supabase = None
logging.warning("Supabase configuration missing; related endpoints will return 500 until configured.")

await create_tables()
await seed_db()
yield
# Close Supabase async client if initialized
supabase_client = getattr(app.state, "supabase", None)
if supabase_client:
close = getattr(supabase_client, "aclose", None)
if close:
await close()
print("App is shutting down...")


Expand Down
41 changes: 23 additions & 18 deletions Backend/app/routes/ai.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
# FastAPI router for AI-powered endpoints, including trending niches
from fastapi import APIRouter, HTTPException, Query
from fastapi import APIRouter, HTTPException, Query, Request
from datetime import date
import asyncio
import os
import requests
import json
from supabase import create_client, Client
from supabase import AsyncClient
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

# Initialize router
router = APIRouter()

# Load environment variables for Supabase and Gemini
SUPABASE_URL = os.environ.get("SUPABASE_URL")
SUPABASE_KEY = os.environ.get("SUPABASE_KEY")
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")

# Validate required environment variables
if not all([SUPABASE_URL, SUPABASE_KEY, GEMINI_API_KEY]):
raise ValueError("Missing required environment variables: SUPABASE_URL, SUPABASE_KEY, GEMINI_API_KEY")
def get_supabase_client(request: Request) -> AsyncClient:
supabase = getattr(request.app.state, "supabase", None)
if supabase is None:
raise HTTPException(status_code=500, detail="Supabase configuration missing on server.")
return supabase

supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)

def get_gemini_api_key() -> str:
api_key = os.getenv("GEMINI_API_KEY")
if not api_key:
raise HTTPException(status_code=500, detail="Gemini API key not configured on server.")
return api_key

def fetch_from_gemini():
prompt = (
"List the top 6 trending content niches for creators and brands this week. For each, provide: name (the niche), insight (a short qualitative reason why it's trending), and global_activity (a number from 1 to 5, where 5 means very high global activity in this category, and 1 means low).Return as a JSON array of objects with keys: name, insight, global_activity."
)
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-lite:generateContent?key={GEMINI_API_KEY}"
gemini_api_key = get_gemini_api_key()
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-lite:generateContent?key={gemini_api_key}"
# Set up retry strategy
retry_strategy = Retry(
total=3,
Expand All @@ -53,32 +57,33 @@ def fetch_from_gemini():
return json.loads(text)

@router.get("/api/trending-niches")
def trending_niches():
async def trending_niches(request: Request):
"""
API endpoint to get trending niches for the current day.
- If today's data exists in Supabase, return it.
- Otherwise, fetch from Gemini, store in Supabase, and return the new data.
- If Gemini fails, fallback to the most recent data available.
"""
today = str(date.today())
supabase = get_supabase_client(request)
# Check if today's data exists in Supabase
result = supabase.table("trending_niches").select("*").eq("fetched_at", today).execute()
result = await supabase.table("trending_niches").select("*").eq("fetched_at", today).execute()
if not result.data:
# Fetch from Gemini and store
try:
niches = fetch_from_gemini()
niches = await asyncio.to_thread(fetch_from_gemini)
for niche in niches:
supabase.table("trending_niches").insert({
await supabase.table("trending_niches").insert({
"name": niche["name"],
"insight": niche["insight"],
"global_activity": int(niche["global_activity"]),
"fetched_at": today
}).execute()
result = supabase.table("trending_niches").select("*").eq("fetched_at", today).execute()
result = await supabase.table("trending_niches").select("*").eq("fetched_at", today).execute()
except Exception as e:
print("Gemini fetch failed:", e)
# fallback: serve most recent data
result = supabase.table("trending_niches").select("*").order("fetched_at", desc=True).limit(6).execute()
result = await supabase.table("trending_niches").select("*").order("fetched_at", desc=True).limit(6).execute()
return result.data

youtube_router = APIRouter(prefix="/youtube", tags=["YouTube"])
Expand Down
82 changes: 82 additions & 0 deletions Backend/test_ai_fix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env python
"""
Test to verify the ai.py fix: import-time env validation is removed.
This demonstrates the fix works without needing Supabase/Gemini keys at import.
"""
import os
import sys

# Clear all env vars that were previously required at import
os.environ.pop('SUPABASE_URL', None)
os.environ.pop('SUPABASE_KEY', None)
os.environ.pop('GEMINI_API_KEY', None)

print("=" * 70)
print("TEST: Import ai.py with missing env vars (should NOT crash)")
print("=" * 70)

try:
# This import would fail BEFORE the fix with:
# ValueError: Missing required environment variables: SUPABASE_URL, SUPABASE_KEY, GEMINI_API_KEY
from app.routes import ai
print("✓ SUCCESS: ai.py imported without crashing!")
print(" - No ValueError on missing SUPABASE_URL/SUPABASE_KEY/GEMINI_API_KEY")
print(" - Router and helper functions are available")
print("\nHelper functions available:")
print(f" - get_supabase_client: {hasattr(ai, 'get_supabase_client')}")
print(f" - get_gemini_api_key: {hasattr(ai, 'get_gemini_api_key')}")
print(f" - fetch_from_gemini: {hasattr(ai, 'fetch_from_gemini')}")
print(f" - trending_niches: {hasattr(ai, 'trending_niches')}")

print("\n" + "=" * 70)
print("TEST: Verify per-request env validation")
print("=" * 70)

# Test 1: Try to get Supabase client without env vars
print("\nTest 1: get_supabase_client() without SUPABASE_URL/KEY...")
try:
client = ai.get_supabase_client()
print(" ✗ FAIL: Should have raised HTTPException")
except Exception as e:
if "Supabase configuration missing" in str(e):
print(f" ✓ PASS: Raised HTTPException with message: {e}")
else:
print(f" ✗ FAIL: Wrong exception: {type(e).__name__}: {e}")

# Test 2: Try to get Gemini key without env var
print("\nTest 2: get_gemini_api_key() without GEMINI_API_KEY...")
try:
key = ai.get_gemini_api_key()
print(" ✗ FAIL: Should have raised HTTPException")
except Exception as e:
if "Gemini API key not configured" in str(e):
print(f" ✓ PASS: Raised HTTPException with message: {e}")
else:
print(f" ✗ FAIL: Wrong exception: {type(e).__name__}: {e}")

# Test 3: Set env vars and verify client creation would proceed
print("\nTest 3: Setting env vars and retrying get_supabase_client()...")
os.environ['SUPABASE_URL'] = 'https://test.supabase.co'
os.environ['SUPABASE_KEY'] = 'test_key_123'
try:
# Note: This will fail when trying to actually connect, but the env check passes
client = ai.get_supabase_client()
print(" - Got to client creation (would connect to real Supabase now)")
except Exception as e:
if "Supabase configuration missing" not in str(e):
print(f" ✓ PASS: Passed env check, failed on actual connection: {type(e).__name__}")
else:
print(f" ✗ FAIL: Still failing on env check: {e}")

print("\n" + "=" * 70)
print("SUMMARY")
print("=" * 70)
print("✓ Fix verified: ai.py no longer crashes at import time")
print("✓ Env validation moved to per-request helpers")
print("✓ Returns clear 500 errors when config is missing")
print("✓ API can start and serve other routes while missing keys")

except ImportError as e:
print(f"✗ FAIL: Could not import app.routes.ai: {e}")
print(" Make sure fastapi and supabase packages are installed")
sys.exit(1)
Loading