CodeGovindz commited on
Commit
bc48447
·
1 Parent(s): d8c5526

Add auth endpoints: OTP, registration, profile, emotions

Browse files
Files changed (2) hide show
  1. app.py +380 -0
  2. requirements.txt +2 -0
app.py CHANGED
@@ -7,6 +7,9 @@ import torch.nn as nn
7
  import whisper
8
  import librosa
9
  import asyncio
 
 
 
10
  from typing import List, Dict, Any, Optional
11
  from fastapi import FastAPI, UploadFile, File, HTTPException, Request, Form
12
  from fastapi.middleware.cors import CORSMiddleware
@@ -17,6 +20,8 @@ from transformers import DistilBertTokenizerFast, DistilBertForSequenceClassific
17
  from speechbrain.inference.classifiers import EncoderClassifier
18
  import torchaudio
19
  import json
 
 
20
 
21
  # Setup logging
22
  logging.basicConfig(
@@ -557,3 +562,378 @@ async def predict_text_emotion(files: List[UploadFile] = File(...)):
557
  if __name__ == "__main__":
558
  import uvicorn
559
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  import whisper
8
  import librosa
9
  import asyncio
10
+ import random
11
+ import requests
12
+ from datetime import datetime, timedelta
13
  from typing import List, Dict, Any, Optional
14
  from fastapi import FastAPI, UploadFile, File, HTTPException, Request, Form
15
  from fastapi.middleware.cors import CORSMiddleware
 
20
  from speechbrain.inference.classifiers import EncoderClassifier
21
  import torchaudio
22
  import json
23
+ from pydantic import BaseModel
24
+ from supabase import create_client, Client
25
 
26
  # Setup logging
27
  logging.basicConfig(
 
562
  if __name__ == "__main__":
563
  import uvicorn
564
  uvicorn.run(app, host="0.0.0.0", port=7860)
565
+
566
+
567
+ # =============================================================================
568
+ # AUTHENTICATION & USER MANAGEMENT ENDPOINTS
569
+ # =============================================================================
570
+
571
+ # Supabase configuration
572
+ SUPABASE_URL = os.environ.get("SUPABASE_URL")
573
+ SUPABASE_KEY = os.environ.get("SUPABASE_KEY")
574
+ BREVO_API_KEY = os.environ.get("EMAIL_API")
575
+
576
+ # Initialize Supabase client
577
+ supabase: Client = None
578
+ try:
579
+ if SUPABASE_URL and SUPABASE_KEY:
580
+ supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
581
+ logger.info("Supabase client initialized successfully")
582
+ else:
583
+ logger.warning("Supabase credentials not found in environment variables")
584
+ except Exception as e:
585
+ logger.error(f"Failed to initialize Supabase: {str(e)}")
586
+
587
+ # OTP storage (in-memory, resets on restart)
588
+ otp_store = {}
589
+
590
+ # -------------------------------
591
+ # Request Models for Auth
592
+ # -------------------------------
593
+ class OTPRequest(BaseModel):
594
+ email: str
595
+
596
+ class OTPVerifyRequest(BaseModel):
597
+ email: str
598
+ otp: str
599
+
600
+ class RegisterUserRequest(BaseModel):
601
+ name: str
602
+ email: str
603
+ password: str
604
+
605
+ class SendEmotionRequest(BaseModel):
606
+ email: str
607
+ emotion: str
608
+
609
+ class UpdateProfilePicRequest(BaseModel):
610
+ email: str
611
+ profile_pic_url: str
612
+
613
+ class UpdateProfileRequest(BaseModel):
614
+ email: str
615
+ name: str = None
616
+ age: int = None
617
+ phone: str = None
618
+
619
+ # -------------------------------
620
+ # Helper Function - Send Email via Brevo
621
+ # -------------------------------
622
+ def send_email_brevo(to_email: str, otp: str):
623
+ url = "https://api.brevo.com/v3/smtp/email"
624
+ headers = {
625
+ "accept": "application/json",
626
+ "api-key": BREVO_API_KEY,
627
+ "content-type": "application/json",
628
+ }
629
+ data = {
630
+ "sender": {"name": "मनन", "email": "noreplymanan@gmail.com"},
631
+ "to": [{"email": to_email}],
632
+ "subject": "Your Manan OTP Code",
633
+ "htmlContent": f"""
634
+ <html>
635
+ <body>
636
+ <h2>Your OTP Code</h2>
637
+ <p>Your verification code is: <strong>{otp}</strong></p>
638
+ <p>This code will expire in 5 minutes.</p>
639
+ </body>
640
+ </html>
641
+ """,
642
+ }
643
+
644
+ response = requests.post(url, headers=headers, json=data)
645
+ if response.status_code not in [200, 201]:
646
+ raise HTTPException(status_code=500, detail=f"Email sending failed: {response.text}")
647
+
648
+ # -------------------------------
649
+ # Auth Endpoints
650
+ # -------------------------------
651
+ @app.get("/status")
652
+ def get_status():
653
+ try:
654
+ if supabase is None:
655
+ return {"status": "Supabase not configured", "supabase_data_retrieved": False}
656
+ response = supabase.table('users').select("id").limit(1).execute()
657
+ return {"status": "Connection successful", "supabase_data_retrieved": len(response.data) > 0}
658
+ except Exception as e:
659
+ raise HTTPException(status_code=500, detail=f"Connection failed: {str(e)}")
660
+
661
+ @app.post("/send_otp")
662
+ def send_otp(req: OTPRequest):
663
+ try:
664
+ otp = str(random.randint(100000, 999999))
665
+ otp_store[req.email] = {"otp": otp, "timestamp": datetime.utcnow()}
666
+ logger.info(f"OTP generated for {req.email}")
667
+
668
+ # Send email
669
+ send_email_brevo(req.email, otp)
670
+
671
+ return {"message": f"OTP sent successfully to {req.email}"}
672
+ except Exception as e:
673
+ raise HTTPException(status_code=500, detail=f"Error sending OTP: {str(e)}")
674
+
675
+ @app.post("/check_otp")
676
+ def check_otp(req: OTPVerifyRequest):
677
+ try:
678
+ if req.email not in otp_store:
679
+ raise HTTPException(status_code=404, detail="No OTP found for this email")
680
+
681
+ stored_data = otp_store[req.email]
682
+ stored_otp = stored_data["otp"]
683
+ timestamp = stored_data["timestamp"]
684
+
685
+ # Expiry check (5 minutes)
686
+ if datetime.utcnow() - timestamp > timedelta(minutes=5):
687
+ del otp_store[req.email]
688
+ raise HTTPException(status_code=400, detail="OTP expired")
689
+
690
+ if req.otp == stored_otp:
691
+ # Mark as verified instead of deleting
692
+ otp_store[req.email]["verified"] = True
693
+ otp_store[req.email]["otp"] = None
694
+ return {"verified": True}
695
+ else:
696
+ raise HTTPException(status_code=400, detail="Invalid OTP")
697
+
698
+ except HTTPException:
699
+ raise
700
+ except Exception as e:
701
+ raise HTTPException(status_code=500, detail=f"Error verifying OTP: {str(e)}")
702
+
703
+ @app.post("/register_user")
704
+ def register_user(req: RegisterUserRequest):
705
+ try:
706
+ if supabase is None:
707
+ raise HTTPException(status_code=500, detail="Supabase not configured")
708
+
709
+ # Check OTP verification
710
+ if req.email not in otp_store or not otp_store[req.email].get("verified", False):
711
+ raise HTTPException(status_code=403, detail="Email not verified via OTP")
712
+
713
+ # Insert into Supabase
714
+ response = supabase.table("users").insert({
715
+ "name": req.name,
716
+ "email": req.email,
717
+ "password": req.password
718
+ }).execute()
719
+
720
+ # Cleanup
721
+ del otp_store[req.email]
722
+
723
+ return {"message": "User registered successfully", "data": response.data}
724
+
725
+ except HTTPException:
726
+ raise
727
+ except Exception as e:
728
+ raise HTTPException(status_code=500, detail=f"Error registering user: {str(e)}")
729
+
730
+ @app.get("/get_profile/{email}")
731
+ def get_profile(email: str):
732
+ try:
733
+ if supabase is None:
734
+ raise HTTPException(status_code=500, detail="Supabase not configured")
735
+
736
+ response = supabase.table("users").select("*").eq("email", email).single().execute()
737
+
738
+ if not response.data:
739
+ raise HTTPException(status_code=404, detail="User not found")
740
+
741
+ # Remove password from response for security
742
+ profile_data = response.data.copy()
743
+ profile_data.pop('password', None)
744
+
745
+ return {"profile": profile_data}
746
+
747
+ except HTTPException:
748
+ raise
749
+ except Exception as e:
750
+ raise HTTPException(status_code=500, detail=f"Error fetching profile: {str(e)}")
751
+
752
+ @app.put("/update_profile")
753
+ def update_profile(req: UpdateProfileRequest):
754
+ try:
755
+ if supabase is None:
756
+ raise HTTPException(status_code=500, detail="Supabase not configured")
757
+
758
+ update_data = {}
759
+ if req.name is not None:
760
+ update_data["name"] = req.name
761
+ if req.age is not None:
762
+ update_data["age"] = req.age
763
+ if req.phone is not None:
764
+ update_data["phone"] = req.phone
765
+
766
+ if not update_data:
767
+ raise HTTPException(status_code=400, detail="No data provided for update")
768
+
769
+ response = supabase.table("users").update(update_data).eq("email", req.email).execute()
770
+
771
+ if not response.data:
772
+ raise HTTPException(status_code=404, detail="User not found")
773
+
774
+ return {"message": "Profile updated successfully", "data": response.data}
775
+
776
+ except HTTPException:
777
+ raise
778
+ except Exception as e:
779
+ raise HTTPException(status_code=500, detail=f"Error updating profile: {str(e)}")
780
+
781
+ @app.post("/update_profile_pic")
782
+ def update_profile_pic(req: UpdateProfilePicRequest):
783
+ try:
784
+ if supabase is None:
785
+ raise HTTPException(status_code=500, detail="Supabase not configured")
786
+
787
+ response = supabase.table("users").update({
788
+ "profilepic": req.profile_pic_url
789
+ }).eq("email", req.email).execute()
790
+
791
+ if not response.data:
792
+ raise HTTPException(status_code=404, detail="User not found")
793
+
794
+ return {"message": "Profile picture updated successfully", "data": response.data}
795
+
796
+ except HTTPException:
797
+ raise
798
+ except Exception as e:
799
+ raise HTTPException(status_code=500, detail=f"Error updating profile picture: {str(e)}")
800
+
801
+ @app.get("/get_score/{email}")
802
+ def get_score(email: str):
803
+ try:
804
+ if supabase is None:
805
+ raise HTTPException(status_code=500, detail="Supabase not configured")
806
+
807
+ user_response = supabase.table("users").select("id").eq("email", email).execute()
808
+
809
+ if not user_response.data:
810
+ raise HTTPException(status_code=404, detail="User not found")
811
+
812
+ user_id = user_response.data[0]["id"]
813
+
814
+ predict_response = supabase.table("predict").select("prediction, timestamp") \
815
+ .eq("user_id", user_id).order("timestamp", desc=True).limit(1).execute()
816
+
817
+ if not predict_response.data:
818
+ raise HTTPException(status_code=404, detail="No predictions found for this user")
819
+
820
+ return predict_response.data[0]
821
+
822
+ except HTTPException:
823
+ raise
824
+ except Exception as e:
825
+ raise HTTPException(status_code=500, detail=f"Error fetching score: {str(e)}")
826
+
827
+ @app.post("/send_emotion")
828
+ def send_emotion(req: SendEmotionRequest):
829
+ try:
830
+ if supabase is None:
831
+ raise HTTPException(status_code=500, detail="Supabase not configured")
832
+
833
+ user_response = supabase.table("users").select("id").eq("email", req.email).execute()
834
+
835
+ if not user_response.data:
836
+ raise HTTPException(status_code=404, detail="User not found")
837
+
838
+ user_id = user_response.data[0]["id"]
839
+
840
+ data = {
841
+ "user_id": user_id,
842
+ "prediction": req.emotion,
843
+ "timestamp": datetime.utcnow().isoformat()
844
+ }
845
+
846
+ response = supabase.table("predict").insert(data).execute()
847
+
848
+ return {"message": "Emotion saved successfully", "data": response.data}
849
+
850
+ except HTTPException:
851
+ raise
852
+ except Exception as e:
853
+ raise HTTPException(status_code=500, detail=f"Error saving emotion: {str(e)}")
854
+
855
+ @app.get("/get_emotion/{email}")
856
+ def get_emotion(email: str):
857
+ try:
858
+ if supabase is None:
859
+ raise HTTPException(status_code=500, detail="Supabase not configured")
860
+
861
+ user_response = supabase.table("users").select("id").eq("email", email).execute()
862
+
863
+ if not user_response.data:
864
+ raise HTTPException(status_code=404, detail="User not found")
865
+
866
+ user_id = user_response.data[0]["id"]
867
+
868
+ predict_response = supabase.table("predict").select("prediction") \
869
+ .eq("user_id", user_id).order("timestamp", desc=True).limit(1).execute()
870
+
871
+ if not predict_response.data:
872
+ return {"emotion": "Neutral"}
873
+
874
+ return {"emotion": predict_response.data[0]["prediction"]}
875
+
876
+ except HTTPException:
877
+ raise
878
+ except Exception as e:
879
+ raise HTTPException(status_code=500, detail=f"Error fetching emotion: {str(e)}")
880
+
881
+ @app.get("/get_mental_health_details/{email}")
882
+ def get_mental_health_details(email: str):
883
+ try:
884
+ if supabase is None:
885
+ raise HTTPException(status_code=500, detail="Supabase not configured")
886
+
887
+ user_response = supabase.table("users").select("id").eq("email", email).execute()
888
+ if not user_response.data:
889
+ raise HTTPException(status_code=404, detail="User not found")
890
+ user_id = user_response.data[0]["id"]
891
+
892
+ # Get most recent prediction
893
+ recent_prediction = supabase.table("predict").select("prediction, timestamp") \
894
+ .eq("user_id", user_id).order("timestamp", desc=True).limit(1).execute()
895
+ current_prediction = recent_prediction.data[0]["prediction"] if recent_prediction.data else None
896
+
897
+ # Calculate this week's active days
898
+ week_ago = datetime.utcnow() - timedelta(days=7)
899
+ weekly_predictions = supabase.table("predict").select("timestamp") \
900
+ .eq("user_id", user_id).gte("timestamp", week_ago.isoformat()).execute()
901
+
902
+ if weekly_predictions.data:
903
+ dates = set()
904
+ for p in weekly_predictions.data:
905
+ if p.get("timestamp"):
906
+ try:
907
+ ts_str = p["timestamp"].replace('Z', '+00:00')
908
+ dt = datetime.fromisoformat(ts_str)
909
+ dates.add(dt.date())
910
+ except (ValueError, AttributeError):
911
+ try:
912
+ ts_str = p["timestamp"].split('+')[0].split('Z')[0]
913
+ dt = datetime.strptime(ts_str, '%Y-%m-%dT%H:%M:%S.%f')
914
+ dates.add(dt.date())
915
+ except:
916
+ try:
917
+ dt = datetime.strptime(ts_str, '%Y-%m-%dT%H:%M:%S')
918
+ dates.add(dt.date())
919
+ except:
920
+ pass
921
+ active_days = len(dates)
922
+ else:
923
+ active_days = 0
924
+
925
+ # Total conversations
926
+ total_conversations = supabase.table("predict").select("*", count="exact") \
927
+ .eq("user_id", user_id).execute()
928
+ total_count = total_conversations.count if total_conversations else 0
929
+
930
+ return {
931
+ "current_prediction": current_prediction,
932
+ "active_days_this_week": active_days,
933
+ "total_conversations": total_count
934
+ }
935
+
936
+ except HTTPException:
937
+ raise
938
+ except Exception as e:
939
+ raise HTTPException(status_code=500, detail=f"Error fetching mental health details: {str(e)}")
requirements.txt CHANGED
@@ -17,3 +17,5 @@ audioread==3.0.1
17
  speechbrain==1.0.0
18
  pydantic==2.8.0
19
  python-dotenv==1.0.1
 
 
 
17
  speechbrain==1.0.0
18
  pydantic==2.8.0
19
  python-dotenv==1.0.1
20
+ supabase==2.4.0
21
+ requests==2.31.0