import os import sendgrid from sendgrid.helpers.mail import Mail from fastapi import FastAPI, HTTPException, Query from fastapi.middleware.cors import CORSMiddleware from supabase import create_client from dotenv import load_dotenv from datetime import datetime, timedelta, timezone from dateutil.parser import isoparse from app.utils import * from app.models import * from app.services import get_candidates load_dotenv() # Supabase configuration SUPABASE_URL = os.getenv("SUPABASE_URL") SUPABASE_KEY = os.getenv("SUPABASE_KEY") supabase = create_client(SUPABASE_URL, SUPABASE_KEY) # SendGrid configuration SENDGRID_API_KEY = os.getenv("SENDGRID_API_KEY") SENDGRID_SENDER = os.getenv("SENDGRID_SENDER") sg = sendgrid.SendGridAPIClient(api_key=SENDGRID_API_KEY) # JWT Configuration JWT_EXPIRES_IN = os.getenv("JWT_EXPIRES_IN") app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.post("/auth/send-verification") def send_verification(data: EmailRequest): email = data.email # Validate email domain if not is_valid_email(email): raise HTTPException(status_code=400, detail="Email no válido o dominio no permitido") # Rate limiting: check last code sent time last_code = ( supabase.table("auth_code") .select("*") .eq("email", email) .order("created_at", desc=True) .limit(1) .execute() ) if last_code.data: created_at = isoparse(last_code.data[0]["created_at"]) diff = datetime.now(timezone.utc) - created_at if diff.total_seconds() < 60: raise HTTPException(status_code=429, detail="Demasiados intentos, espera 60 segundos") # Generate verification code code = generate_code() expires_at = datetime.now(timezone.utc) + timedelta(seconds=int(JWT_EXPIRES_IN)) # Store code in the database supabase.table("auth_code").delete().eq("email", email).execute() supabase.table("auth_code").insert({ "email": email, "code": code, "expires_at": expires_at.isoformat() }).execute() # Send email with SendGrid message = Mail( from_email=SENDGRID_SENDER, to_emails=email, subject="Your Verification Code", html_content=f"
Your verification code expires in {JWT_EXPIRES_IN} seconds.
" ) try: sg.send(message) except Exception as e: raise HTTPException(status_code=500, detail="Error sending email") return { "success": True, "message": f"Código enviado a {email}", "expires_in": JWT_EXPIRES_IN } @app.post("/auth/verify") def verify(data: VerifyRequest): email = data.email code = data.code # Verify if the email exists record = ( supabase.table("auth_code") .select("*") .eq("email", email) .execute() ) if not record.data: raise HTTPException(status_code=404, detail="Email no encontrado") # Verify if the code matches if record.data[0]["code"] != code: raise HTTPException(status_code=400, detail="Código inválido") # Verify if the code has expired expires_at = isoparse(record.data[0]["expires_at"]) if datetime.now(timezone.utc) > expires_at: raise HTTPException(status_code=400, detail="Código expirado") # Get or create user user_record = ( supabase.table("users") .select("*") .eq("email", email) .execute() ) supabase.table("auth_code").delete().eq("email", email).execute() if not user_record.data: user = supabase.table("users").insert({ "email": email }).execute().data[0] return { "success": True, "user": { "id": user['id'], "email": user["email"], "firstName": None, "lastName": None, "onboardingCompleted": False } } else: user = user_record.data[0] return { "success": True, "user": { "id": user['id'], "email": user['email'], "firstName": user['firstname'], "lastName": user['lastname'], "onboardingCompleted": user['onboardingcompleted'] } } @app.post("/auth/onboarding") def onboarding(data: OnboardingRequest): # Data for User Table firstName = data.firstName lastName = data.lastName university = data.university career = data.career semester = data.semester # Data for UserProfile Table user_id = data.user_id strengths = data.strengths weaknesses = data.weaknesses studyStyle = data.studyStyle careerInterests = data.careerInterests futureRoles = data.futureRoles skillsToLearn = data.skillsToLearn # Update User Table supabase.table("users").update({ "firstname": firstName, "lastname": lastName, "university": university, "career": career, "semester": semester, "onboardingcompleted": True }).eq("id", user_id).execute() # Update UserProfile Table supabase.table("user_profiles").insert({ "user_id": user_id, "strengths": strengths, "weaknesses": weaknesses, "studystyle": studyStyle, "careerinterests": careerInterests, "futureroles": futureRoles, "skillstolearn": skillsToLearn }).execute() return { "success": True, "message": "Onboarding completed successfully" } @app.get("/matches/candidates") def match_candidate( user_id: int = Query(...), limit: int = Query(20), offset: int = Query(0) ): return get_candidates(user_id, limit=limit, offset=offset, supabase=supabase) @app.post("/matches/request") def match_request(data: MatchRequest): user_id = data.user_id candidate_id = data.candidate_id candidate_name = data.candidate_name message = data.message compatibility_score = data.compatibility_score # Insert match request into the database response = supabase.table("matches").insert({ "mentee_id": user_id, "mentor_id": candidate_id, "message": message, "compatibility_score": compatibility_score, }) if response.error: raise HTTPException(status_code=500, detail="Error creating match request") match_data = response.execute().data[0] return { "success": True, "match": { "id": match_data["id"], "mentee_id": match_data["mentee_id"], "mentor_id": match_data["mentor_id"], "message": match_data["message"], "compatibility_score": match_data["compatibility_score"], "created_at": match_data["created_at"] }, "message": f"Solicitud enviada a {candidate_name}. Te notificaremos cuando responda." } @app.get("/matches/skip") def match_skip(): return { "success": True, "message": "Candidato omitido. Mostrando siguiente." } @app.post("/matches/my-matches") def get_matches( data: UserRequest, status: str = Query("all"), role: str = Query("all") ): user_id = data.user_id # Build filters query = supabase.table("matches").select("*") if status != "all": query = query.eq("status", status) if role == "mentor": query = query.eq("mentor_id", user_id) elif role == "mentee": query = query.eq("mentee_id", user_id) else: query = query.or_(f"mentor_id.eq.{user_id},mentee_id.eq.{user_id}") # Execute response = query.execute() matches = response.data full_matches = [] status_count = { "pending": 0, "active": 0, "completed": 0 } for m in matches: if m["status"] in status_count: status_count[m["status"]] += 1 if m["mentee_id"] == user_id: other_user_id = m["mentor_id"] else: other_user_id = m["mentee_id"] user_response = ( supabase.table("users") .select("*") .eq("id", other_user_id) .execute() ) if not user_response.data: continue other_user = user_response.data[0] # Mock last_message = { "content": "Último mensaje de prueba", "sentAt": "2024-11-02T10:20:00Z", "isRead": True } upcoming_session = { "id": "session-mock-1", "scheduledAt": "2024-11-06T16:00:00Z", "duration": 60 } stats = { "totalSessions": 2, "totalMessages": 15 } full_matches.append({ "id": m["id"], "status": m["status"], "matchType": m["match_type"], "compatibilityScore": m["compatibility_score"], "createdAt": m["created_at"], "acceptedAt": m["accepted_at"], "otherUser": { "id": other_user["id"], "firstName": other_user["firstname"], "lastName": other_user["lastname"][0] + '.', "profileImage": other_user["profileimage"], "career": other_user["career"], "semester": other_user["semester"], }, "lastMessage": last_message, "upcomingSession": upcoming_session, "stats": stats }) return { "matches": full_matches, "counts": status_count } @app.post("/matches/{match_id}/respond") def respond_match( match_id: str, data: MatchRespondRequest ): match_res = ( supabase.table("matches") .select("*") .eq("id", match_id) .execute() ) if not match_res.data: raise HTTPException(status_code=404, detail="Match not found") user_id = data.user_id action = data.action message = data.message match_data = match_res.data[0] if match_data["mentor_id"] != user_id: raise HTTPException(status_code=403, detail="Unauthorized action") new_status = "accepted" if action == "accept" else "rejected" update_data = { "status": new_status, "message": message or None, "accepted_at": None, } if action == "accept": update_data["accepted_at"] = datetime.now(timezone.utc).isoformat() update_res = ( supabase.table("matches") .update(update_data) .eq("id", match_id) .execute() ) updated_match = update_res.data[0] points_earned = 50 if action == "accept" else 0 return { "success": True, "match": { "id": updated_match["id"], "status": updated_match["status"], "acceptedAt": updated_match["accepted_at"], }, "pointsEarned": points_earned, "message": ( "Match aceptado! Ahora pueden chatear y agendar sesiones." if action == "accept" else "Has rechazado la solicitud." ) }