Spaces:
Sleeping
Sleeping
| 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=["*"], | |
| ) | |
| 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"<h1>{code}</h1><p>Your verification code expires in {JWT_EXPIRES_IN} seconds.</p>" | |
| ) | |
| 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 | |
| } | |
| 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'] | |
| } | |
| } | |
| 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" | |
| } | |
| 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) | |
| 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." | |
| } | |
| def match_skip(): | |
| return { | |
| "success": True, | |
| "message": "Candidato omitido. Mostrando siguiente." | |
| } | |
| 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 | |
| } | |
| 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." | |
| ) | |
| } |