Update main.py
Browse files
main.py
CHANGED
|
@@ -13,7 +13,7 @@ from models import (
|
|
| 13 |
from reddit_client import RedditClient
|
| 14 |
from analyzer import call_groq_for_insight
|
| 15 |
from serp_client import get_search_volume, search_competitors, search_funding_news
|
| 16 |
-
from supabase_client import
|
| 17 |
from config import GROQ_MODEL, GROQ_API_KEY, SUPABASE_JWT_SECRET
|
| 18 |
|
| 19 |
# Groq client is imported in analyzer
|
|
@@ -22,7 +22,7 @@ reddit_client = RedditClient()
|
|
| 22 |
|
| 23 |
app = FastAPI(title="Founder Intelligence Engine API", version="2.0")
|
| 24 |
|
| 25 |
-
# CORS
|
| 26 |
app.add_middleware(
|
| 27 |
CORSMiddleware,
|
| 28 |
allow_origins=["*"],
|
|
@@ -42,19 +42,19 @@ async def root():
|
|
| 42 |
@app.post("/auth/signup", response_model=UserProfile)
|
| 43 |
async def signup(user: UserSignup):
|
| 44 |
try:
|
| 45 |
-
# 1. Create auth user
|
| 46 |
-
response =
|
| 47 |
"email": user.email,
|
| 48 |
"password": user.password,
|
| 49 |
"options": {"data": {"full_name": user.full_name}}
|
| 50 |
})
|
| 51 |
user_data = response.user
|
| 52 |
|
| 53 |
-
# 2. Check if profile already exists (
|
| 54 |
-
existing =
|
| 55 |
if not existing.data:
|
| 56 |
# Create profile record
|
| 57 |
-
|
| 58 |
"id": user_data.id,
|
| 59 |
"email": user_data.email,
|
| 60 |
"full_name": user.full_name,
|
|
@@ -73,13 +73,12 @@ async def signup(user: UserSignup):
|
|
| 73 |
)
|
| 74 |
except Exception as e:
|
| 75 |
# If the user already exists in auth but not in profiles, we can still return a helpful message
|
| 76 |
-
# But Supabase will raise an exception for duplicate email.
|
| 77 |
raise HTTPException(status_code=400, detail=str(e))
|
| 78 |
|
| 79 |
@app.post("/auth/login")
|
| 80 |
async def login(creds: UserLogin):
|
| 81 |
try:
|
| 82 |
-
response =
|
| 83 |
"email": creds.email,
|
| 84 |
"password": creds.password
|
| 85 |
})
|
|
@@ -89,34 +88,40 @@ async def login(creds: UserLogin):
|
|
| 89 |
|
| 90 |
@app.get("/auth/me", response_model=UserProfile)
|
| 91 |
async def get_me(user=Depends(get_current_user)):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
return UserProfile(
|
| 93 |
id=user["id"],
|
| 94 |
email=user["email"],
|
| 95 |
-
full_name=
|
| 96 |
-
subscription_tier=
|
| 97 |
-
subscription_status=
|
| 98 |
-
created_at=
|
| 99 |
)
|
| 100 |
|
| 101 |
# ====================== Subscription Plans ======================
|
| 102 |
@app.get("/plans", response_model=List[SubscriptionPlan])
|
| 103 |
async def list_plans():
|
| 104 |
-
plans =
|
| 105 |
return [SubscriptionPlan(**p) for p in plans.data]
|
| 106 |
|
| 107 |
# ====================== Helper: Check subscription limits ======================
|
| 108 |
def check_subscription_limit(user_id: str, requested_posts: int):
|
| 109 |
"""Raise 403 if user exceeds plan limits."""
|
| 110 |
-
profile =
|
| 111 |
tier = profile.data[0]["subscription_tier"] if profile.data else "free"
|
| 112 |
# Fetch plan features
|
| 113 |
-
plan =
|
| 114 |
if plan.data:
|
| 115 |
features = plan.data[0]["features"]
|
| 116 |
posts_limit = features.get("posts_per_month", 10) # default free limit
|
| 117 |
# Count posts this month for this user
|
| 118 |
start_of_month = datetime.now().replace(day=1, hour=0, minute=0, second=0).isoformat()
|
| 119 |
-
count_res =
|
| 120 |
current_count = count_res.count if hasattr(count_res, 'count') else 0
|
| 121 |
if current_count + requested_posts > posts_limit:
|
| 122 |
raise HTTPException(status_code=403, detail=f"Monthly post limit of {posts_limit} reached. Upgrade your plan.")
|
|
@@ -152,8 +157,8 @@ async def analyze(request: AnalysisRequest, user=Depends(get_current_user)):
|
|
| 152 |
"insight_data": insight,
|
| 153 |
"opportunity_score": insight["opportunity_score"]
|
| 154 |
}
|
| 155 |
-
# Insert into Supabase
|
| 156 |
-
|
| 157 |
all_results.append({
|
| 158 |
"subreddit": sub,
|
| 159 |
"post_title": post.get("title"),
|
|
@@ -166,7 +171,7 @@ async def analyze(request: AnalysisRequest, user=Depends(get_current_user)):
|
|
| 166 |
|
| 167 |
@app.get("/insights", response_model=List[InsightResponse])
|
| 168 |
async def get_insights(user=Depends(get_current_user)):
|
| 169 |
-
res =
|
| 170 |
insights = []
|
| 171 |
for row in res.data:
|
| 172 |
insights.append(InsightResponse(
|
|
@@ -183,15 +188,13 @@ async def get_insights(user=Depends(get_current_user)):
|
|
| 183 |
# ====================== Query / RAG ======================
|
| 184 |
@app.post("/query", response_model=QueryResponse)
|
| 185 |
async def query_insights(request: QueryRequest, user=Depends(get_current_user)):
|
| 186 |
-
|
| 187 |
-
insights = supabase.table("insights").select("*").eq("user_id", user["id"]).execute()
|
| 188 |
relevant = []
|
| 189 |
for row in insights.data:
|
| 190 |
data = row["insight_data"]
|
| 191 |
text = f"{data.get('burning_pain','')} {data.get('startup_idea','')} {data.get('customer_type','')}"
|
| 192 |
if request.query.lower() in text.lower():
|
| 193 |
relevant.append(row)
|
| 194 |
-
# If no exact matches, return top N by opportunity_score
|
| 195 |
if not relevant:
|
| 196 |
relevant = sorted(insights.data, key=lambda x: x["opportunity_score"], reverse=True)[:request.n_results]
|
| 197 |
else:
|
|
@@ -200,9 +203,7 @@ async def query_insights(request: QueryRequest, user=Depends(get_current_user)):
|
|
| 200 |
if not relevant:
|
| 201 |
raise HTTPException(status_code=404, detail="No relevant insights found")
|
| 202 |
|
| 203 |
-
# Build context
|
| 204 |
context = "\n\n".join([f"From r/{r['subreddit']}: {r['insight_data'].get('burning_pain','')} {r['insight_data'].get('startup_idea','')}" for r in relevant])
|
| 205 |
-
# Call Groq to answer
|
| 206 |
from groq import Groq
|
| 207 |
groq = Groq(api_key=GROQ_API_KEY)
|
| 208 |
try:
|
|
@@ -259,7 +260,7 @@ Insight: {request.insight}
|
|
| 259 |
# ====================== Trends ======================
|
| 260 |
@app.get("/trends", response_model=TrendResponse)
|
| 261 |
async def trends(subreddit: str = None, user=Depends(get_current_user)):
|
| 262 |
-
query =
|
| 263 |
if subreddit:
|
| 264 |
query = query.eq("subreddit", subreddit)
|
| 265 |
res = query.execute()
|
|
|
|
| 13 |
from reddit_client import RedditClient
|
| 14 |
from analyzer import call_groq_for_insight
|
| 15 |
from serp_client import get_search_volume, search_competitors, search_funding_news
|
| 16 |
+
from supabase_client import supabase_auth, supabase_db
|
| 17 |
from config import GROQ_MODEL, GROQ_API_KEY, SUPABASE_JWT_SECRET
|
| 18 |
|
| 19 |
# Groq client is imported in analyzer
|
|
|
|
| 22 |
|
| 23 |
app = FastAPI(title="Founder Intelligence Engine API", version="2.0")
|
| 24 |
|
| 25 |
+
# CORS
|
| 26 |
app.add_middleware(
|
| 27 |
CORSMiddleware,
|
| 28 |
allow_origins=["*"],
|
|
|
|
| 42 |
@app.post("/auth/signup", response_model=UserProfile)
|
| 43 |
async def signup(user: UserSignup):
|
| 44 |
try:
|
| 45 |
+
# 1. Create auth user (using anon client)
|
| 46 |
+
response = supabase_auth.auth.sign_up({
|
| 47 |
"email": user.email,
|
| 48 |
"password": user.password,
|
| 49 |
"options": {"data": {"full_name": user.full_name}}
|
| 50 |
})
|
| 51 |
user_data = response.user
|
| 52 |
|
| 53 |
+
# 2. Check if profile already exists (using service client to bypass RLS)
|
| 54 |
+
existing = supabase_db.table("profiles").select("*").eq("id", user_data.id).execute()
|
| 55 |
if not existing.data:
|
| 56 |
# Create profile record
|
| 57 |
+
supabase_db.table("profiles").insert({
|
| 58 |
"id": user_data.id,
|
| 59 |
"email": user_data.email,
|
| 60 |
"full_name": user.full_name,
|
|
|
|
| 73 |
)
|
| 74 |
except Exception as e:
|
| 75 |
# If the user already exists in auth but not in profiles, we can still return a helpful message
|
|
|
|
| 76 |
raise HTTPException(status_code=400, detail=str(e))
|
| 77 |
|
| 78 |
@app.post("/auth/login")
|
| 79 |
async def login(creds: UserLogin):
|
| 80 |
try:
|
| 81 |
+
response = supabase_auth.auth.sign_in_with_password({
|
| 82 |
"email": creds.email,
|
| 83 |
"password": creds.password
|
| 84 |
})
|
|
|
|
| 88 |
|
| 89 |
@app.get("/auth/me", response_model=UserProfile)
|
| 90 |
async def get_me(user=Depends(get_current_user)):
|
| 91 |
+
# user already contains profile info from the token validation
|
| 92 |
+
# but we can fetch fresh profile from DB if needed
|
| 93 |
+
profile = supabase_db.table("profiles").select("*").eq("id", user["id"]).execute()
|
| 94 |
+
if not profile.data:
|
| 95 |
+
raise HTTPException(status_code=404, detail="Profile not found")
|
| 96 |
+
p = profile.data[0]
|
| 97 |
return UserProfile(
|
| 98 |
id=user["id"],
|
| 99 |
email=user["email"],
|
| 100 |
+
full_name=p["full_name"],
|
| 101 |
+
subscription_tier=p["subscription_tier"],
|
| 102 |
+
subscription_status=p["subscription_status"],
|
| 103 |
+
created_at=p["created_at"]
|
| 104 |
)
|
| 105 |
|
| 106 |
# ====================== Subscription Plans ======================
|
| 107 |
@app.get("/plans", response_model=List[SubscriptionPlan])
|
| 108 |
async def list_plans():
|
| 109 |
+
plans = supabase_db.table("plans").select("*").execute()
|
| 110 |
return [SubscriptionPlan(**p) for p in plans.data]
|
| 111 |
|
| 112 |
# ====================== Helper: Check subscription limits ======================
|
| 113 |
def check_subscription_limit(user_id: str, requested_posts: int):
|
| 114 |
"""Raise 403 if user exceeds plan limits."""
|
| 115 |
+
profile = supabase_db.table("profiles").select("subscription_tier").eq("id", user_id).execute()
|
| 116 |
tier = profile.data[0]["subscription_tier"] if profile.data else "free"
|
| 117 |
# Fetch plan features
|
| 118 |
+
plan = supabase_db.table("plans").select("features").eq("name", tier.capitalize()).execute()
|
| 119 |
if plan.data:
|
| 120 |
features = plan.data[0]["features"]
|
| 121 |
posts_limit = features.get("posts_per_month", 10) # default free limit
|
| 122 |
# Count posts this month for this user
|
| 123 |
start_of_month = datetime.now().replace(day=1, hour=0, minute=0, second=0).isoformat()
|
| 124 |
+
count_res = supabase_db.table("insights").select("id", count="exact").eq("user_id", user_id).gte("created_at", start_of_month).execute()
|
| 125 |
current_count = count_res.count if hasattr(count_res, 'count') else 0
|
| 126 |
if current_count + requested_posts > posts_limit:
|
| 127 |
raise HTTPException(status_code=403, detail=f"Monthly post limit of {posts_limit} reached. Upgrade your plan.")
|
|
|
|
| 157 |
"insight_data": insight,
|
| 158 |
"opportunity_score": insight["opportunity_score"]
|
| 159 |
}
|
| 160 |
+
# Insert into Supabase using service client
|
| 161 |
+
supabase_db.table("insights").insert(result).execute()
|
| 162 |
all_results.append({
|
| 163 |
"subreddit": sub,
|
| 164 |
"post_title": post.get("title"),
|
|
|
|
| 171 |
|
| 172 |
@app.get("/insights", response_model=List[InsightResponse])
|
| 173 |
async def get_insights(user=Depends(get_current_user)):
|
| 174 |
+
res = supabase_db.table("insights").select("*").eq("user_id", user["id"]).order("created_at", desc=True).execute()
|
| 175 |
insights = []
|
| 176 |
for row in res.data:
|
| 177 |
insights.append(InsightResponse(
|
|
|
|
| 188 |
# ====================== Query / RAG ======================
|
| 189 |
@app.post("/query", response_model=QueryResponse)
|
| 190 |
async def query_insights(request: QueryRequest, user=Depends(get_current_user)):
|
| 191 |
+
insights = supabase_db.table("insights").select("*").eq("user_id", user["id"]).execute()
|
|
|
|
| 192 |
relevant = []
|
| 193 |
for row in insights.data:
|
| 194 |
data = row["insight_data"]
|
| 195 |
text = f"{data.get('burning_pain','')} {data.get('startup_idea','')} {data.get('customer_type','')}"
|
| 196 |
if request.query.lower() in text.lower():
|
| 197 |
relevant.append(row)
|
|
|
|
| 198 |
if not relevant:
|
| 199 |
relevant = sorted(insights.data, key=lambda x: x["opportunity_score"], reverse=True)[:request.n_results]
|
| 200 |
else:
|
|
|
|
| 203 |
if not relevant:
|
| 204 |
raise HTTPException(status_code=404, detail="No relevant insights found")
|
| 205 |
|
|
|
|
| 206 |
context = "\n\n".join([f"From r/{r['subreddit']}: {r['insight_data'].get('burning_pain','')} {r['insight_data'].get('startup_idea','')}" for r in relevant])
|
|
|
|
| 207 |
from groq import Groq
|
| 208 |
groq = Groq(api_key=GROQ_API_KEY)
|
| 209 |
try:
|
|
|
|
| 260 |
# ====================== Trends ======================
|
| 261 |
@app.get("/trends", response_model=TrendResponse)
|
| 262 |
async def trends(subreddit: str = None, user=Depends(get_current_user)):
|
| 263 |
+
query = supabase_db.table("insights").select("timestamp", "subreddit").eq("user_id", user["id"])
|
| 264 |
if subreddit:
|
| 265 |
query = query.eq("subreddit", subreddit)
|
| 266 |
res = query.execute()
|