darved2305 commited on
Commit
e560ae3
·
1 Parent(s): d329564

setup reminders for user

Browse files
backend/app/services/sms_sender.py CHANGED
@@ -96,13 +96,16 @@ class SMSSender:
96
  ) -> Dict[str, Any]:
97
  """Send SMS via Twilio."""
98
  try:
 
 
 
99
  result = self.twilio_client.messages.create(
100
  body=message,
101
  from_=settings.TWILIO_FROM_NUMBER,
102
  to=to_number
103
  )
104
 
105
- logger.info(f"SMS sent via Twilio to {masked_phone} (user_id={user_id}, sid={result.sid})")
106
 
107
  return {
108
  "success": True,
@@ -117,7 +120,7 @@ class SMSSender:
117
  }
118
 
119
  except Exception as e:
120
- logger.error(f"Twilio SMS failed to {masked_phone} (user_id={user_id}): {str(e)}")
121
  return {
122
  "success": False,
123
  "status": "failed",
 
96
  ) -> Dict[str, Any]:
97
  """Send SMS via Twilio."""
98
  try:
99
+ logger.info(f"Attempting to send SMS via Twilio to {masked_phone}")
100
+ logger.debug(f"From: {settings.TWILIO_FROM_NUMBER}, To: {to_number}")
101
+
102
  result = self.twilio_client.messages.create(
103
  body=message,
104
  from_=settings.TWILIO_FROM_NUMBER,
105
  to=to_number
106
  )
107
 
108
+ logger.info(f"SMS sent via Twilio to {masked_phone} (user_id={user_id}, sid={result.sid})")
109
 
110
  return {
111
  "success": True,
 
120
  }
121
 
122
  except Exception as e:
123
+ logger.error(f"Twilio SMS failed to {masked_phone} (user_id={user_id}): {type(e).__name__}: {str(e)}")
124
  return {
125
  "success": False,
126
  "status": "failed",
backend/app/settings.py CHANGED
@@ -87,3 +87,13 @@ class Settings(BaseSettings):
87
 
88
  settings = Settings()
89
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
  settings = Settings()
89
 
90
+ # Debug logging to verify SMS configuration
91
+ import logging
92
+ logger = logging.getLogger(__name__)
93
+ logger.info(f"SMS Configuration loaded:")
94
+ logger.info(f" SMS_MODE: {settings.SMS_MODE}")
95
+ logger.info(f" SMS_TEST_TO_NUMBER: {settings.SMS_TEST_TO_NUMBER}")
96
+ logger.info(f" TWILIO_ACCOUNT_SID configured: {bool(settings.TWILIO_ACCOUNT_SID)}")
97
+ logger.info(f" TWILIO_AUTH_TOKEN configured: {bool(settings.TWILIO_AUTH_TOKEN)}")
98
+ logger.info(f" TWILIO_FROM_NUMBER: {settings.TWILIO_FROM_NUMBER}")
99
+
backend/scripts/check_reminders.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ sys.path.insert(0, '/app')
3
+ import asyncio
4
+ from datetime import datetime
5
+ from sqlalchemy import select
6
+ from app.db import async_session_maker
7
+ from app.models import Reminder
8
+
9
+ async def check():
10
+ async with async_session_maker() as db:
11
+ result = await db.execute(
12
+ select(Reminder)
13
+ .where(Reminder.is_enabled == True)
14
+ .order_by(Reminder.next_run_at)
15
+ )
16
+ reminders = result.scalars().all()
17
+
18
+ print('\n' + '='*70)
19
+ print('ACTIVE REMINDERS')
20
+ print('='*70)
21
+
22
+ now = datetime.utcnow()
23
+ for r in reminders:
24
+ if r.next_run_at:
25
+ diff_secs = (r.next_run_at - now).total_seconds()
26
+ mins = int(diff_secs / 60)
27
+ status = 'OVERDUE' if diff_secs < 0 else f'{mins} min'
28
+ print(f'\n{r.title}')
29
+ print(f' Type: {r.type} | Channel: {r.channel}')
30
+ print(f' Next: {r.next_run_at.strftime("%H:%M UTC")} ({status})')
31
+
32
+ print('\n' + '='*70 + '\n')
33
+
34
+ asyncio.run(check())
backend/scripts/create_test_reminder.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Create Test Reminder for E2E Verification
3
+
4
+ Creates a reminder that will fire in 2 minutes to test the full flow.
5
+ Run inside Docker: docker exec ggw-backend python scripts/create_test_reminder.py
6
+ """
7
+ import sys
8
+ sys.path.insert(0, '/app')
9
+
10
+ import asyncio
11
+ from datetime import datetime, timedelta
12
+ from uuid import uuid4
13
+ from sqlalchemy import select
14
+ from app.db import async_session_maker
15
+ from app.models import User, UserProfile, Reminder
16
+ from app.security import hash_password
17
+
18
+ async def create_test_reminder():
19
+ """Create a test reminder that fires soon."""
20
+
21
+ # Import inside function to ensure proper module loading
22
+ from app.db import async_session_maker
23
+ from app.models import User, UserProfile, Reminder
24
+ from sqlalchemy import select
25
+
26
+ print("=" * 60)
27
+ print("CREATING TEST REMINDER")
28
+ print("=" * 60)
29
+
30
+ async with async_session_maker() as db:
31
+ # Find a user with a phone number in their profile
32
+ result = await db.execute(
33
+ select(User).join(UserProfile).where(UserProfile.phone_number.isnot(None)).limit(1)
34
+ )
35
+ user = result.scalar_one_or_none()
36
+
37
+ if not user:
38
+ print("\n❌ No user with phone number found!")
39
+ print(" Please create a user profile with a phone number first.")
40
+
41
+ # List all users
42
+ all_users = await db.execute(select(User).limit(5))
43
+ users = list(all_users.scalars().all())
44
+ if users:
45
+ print("\n Available users:")
46
+ for u in users:
47
+ print(f" - {u.email} (id: {u.id})")
48
+ return
49
+
50
+ # Get user's profile
51
+ profile_result = await db.execute(
52
+ select(UserProfile).where(UserProfile.user_id == user.id)
53
+ )
54
+ profile = profile_result.scalar_one_or_none()
55
+
56
+ print(f"\nFound user: {user.email}")
57
+ print(f"Phone number: ****{profile.phone_number[-4:] if profile and profile.phone_number else 'N/A'}")
58
+
59
+ # Create a reminder that fires in 2 minutes
60
+ next_run = datetime.utcnow() + timedelta(minutes=2)
61
+
62
+ reminder = Reminder(
63
+ user_id=user.id,
64
+ type="test",
65
+ title="🧪 Test Reminder",
66
+ message="This is a test reminder from Lumea Health! If you received this, the SMS system is working correctly.",
67
+ schedule_type="fixed_times",
68
+ schedule_json={"times": [next_run.strftime("%H:%M")]},
69
+ timezone="UTC",
70
+ channel="sms",
71
+ is_enabled=True,
72
+ next_run_at=next_run
73
+ )
74
+
75
+ db.add(reminder)
76
+ await db.commit()
77
+ await db.refresh(reminder)
78
+
79
+ print(f"\n✅ Created test reminder!")
80
+ print(f" ID: {reminder.id}")
81
+ print(f" Title: {reminder.title}")
82
+ print(f" Next Run: {reminder.next_run_at} UTC")
83
+ print(f" (approximately {2} minutes from now)")
84
+
85
+ print(f"\n⏰ The reminder scheduler runs every 60 seconds.")
86
+ print(f" Watch the logs: docker logs -f ggw-backend 2>&1 | grep -i 'reminder\\|SMS\\|twilio'")
87
+
88
+ return reminder
89
+
90
+
91
+ if __name__ == "__main__":
92
+ asyncio.run(create_test_reminder())
backend/scripts/generate_health_reminders.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Generate Health Reminders for Existing Users
3
+
4
+ Creates reminders based on user profile data:
5
+ - Hydration reminders
6
+ - Sleep reminders
7
+ - Medicine reminders (if any)
8
+ - Exercise reminders
9
+ """
10
+ import sys
11
+ sys.path.insert(0, '/app')
12
+
13
+ import asyncio
14
+ from datetime import datetime, timedelta, time as dt_time
15
+ from sqlalchemy import select
16
+ from app.db import async_session_maker
17
+ from app.models import User, UserProfile, Reminder
18
+ from app.services.reminder_service import ReminderService
19
+
20
+ async def generate_reminders():
21
+ print("=" * 70)
22
+ print("GENERATING HEALTH REMINDERS")
23
+ print("=" * 70)
24
+
25
+ async with async_session_maker() as db:
26
+ # Get all users with profiles and phone numbers
27
+ result = await db.execute(
28
+ select(User, UserProfile)
29
+ .join(UserProfile)
30
+ .where(UserProfile.phone_number.isnot(None))
31
+ )
32
+ users = result.all()
33
+
34
+ if not users:
35
+ print("\n❌ No users with phone numbers found!")
36
+ return
37
+
38
+ print(f"\nFound {len(users)} user(s) with phone numbers")
39
+
40
+ for user, profile in users:
41
+ print(f"\n{'─' * 70}")
42
+ print(f"User: {user.email}")
43
+ print(f"Phone: {profile.phone_number}")
44
+ print(f"Age: {profile.age_years}, Activity: {profile.activity_level or 'not set'}")
45
+
46
+ # Delete old non-medicine reminders
47
+ result = await db.execute(
48
+ select(Reminder)
49
+ .where(Reminder.user_id == user.id)
50
+ .where(Reminder.type.in_(['hydration', 'sleep', 'exercise', 'test']))
51
+ )
52
+ old_reminders = list(result.scalars().all())
53
+ for r in old_reminders:
54
+ await db.delete(r)
55
+ await db.commit()
56
+ print(f" Cleaned up {len(old_reminders)} old reminder(s)")
57
+
58
+ reminders_created = []
59
+
60
+ # 1. HYDRATION REMINDER - Every 2 hours during day
61
+ print("\n Creating hydration reminders...")
62
+ now = datetime.utcnow()
63
+ # Schedule next hydration in 3 minutes for testing
64
+ next_hydration = now + timedelta(minutes=3)
65
+
66
+ hydration = Reminder(
67
+ user_id=user.id,
68
+ type='hydration',
69
+ title='💧 Stay Hydrated',
70
+ message='Time to drink water! Staying hydrated helps maintain energy and focus throughout the day. 🚰',
71
+ schedule_type='interval',
72
+ schedule_json={
73
+ 'interval_minutes': 120, # Every 2 hours
74
+ 'start_time': '08:00',
75
+ 'end_time': '22:00'
76
+ },
77
+ timezone='Asia/Kolkata',
78
+ channel='sms',
79
+ is_enabled=True,
80
+ next_run_at=next_hydration
81
+ )
82
+ db.add(hydration)
83
+ reminders_created.append(('💧 Hydration', next_hydration))
84
+
85
+ # 2. SLEEP REMINDER - Every night
86
+ print(" Creating sleep reminder...")
87
+ # Calculate next 22:00 or tomorrow if passed
88
+ now_hour = now.hour
89
+ if now_hour >= 22:
90
+ next_sleep = (now + timedelta(days=1)).replace(hour=22, minute=0, second=0, microsecond=0)
91
+ else:
92
+ next_sleep = now.replace(hour=22, minute=0, second=0, microsecond=0)
93
+
94
+ # For testing, set to 5 minutes from now
95
+ next_sleep = now + timedelta(minutes=5)
96
+
97
+ sleep = Reminder(
98
+ user_id=user.id,
99
+ type='sleep',
100
+ title='🌙 Time for Bed',
101
+ message=f'Wind down time! Aim for 7-8 hours of quality sleep for better health. Good night! 😴',
102
+ schedule_type='fixed_times',
103
+ schedule_json={'times': ['22:00']},
104
+ timezone='Asia/Kolkata',
105
+ channel='sms',
106
+ is_enabled=True,
107
+ next_run_at=next_sleep
108
+ )
109
+ db.add(sleep)
110
+ reminders_created.append(('🌙 Sleep', next_sleep))
111
+
112
+ # 3. EXERCISE REMINDER - if sedentary
113
+ if profile.activity_level in ['sedentary', None]:
114
+ print(" Creating exercise reminder...")
115
+ # Set to 7 minutes from now for testing
116
+ next_exercise = now + timedelta(minutes=7)
117
+
118
+ exercise = Reminder(
119
+ user_id=user.id,
120
+ type='exercise',
121
+ title='🏃 Get Moving!',
122
+ message='Time for some movement! A short 10-minute walk or stretch can boost your energy and improve your health. 💪',
123
+ schedule_type='fixed_times',
124
+ schedule_json={'times': ['10:00', '15:00']},
125
+ timezone='Asia/Kolkata',
126
+ channel='sms',
127
+ is_enabled=True,
128
+ next_run_at=next_exercise
129
+ )
130
+ db.add(exercise)
131
+ reminders_created.append(('🏃 Exercise', next_exercise))
132
+
133
+ await db.commit()
134
+
135
+ print(f"\n ✅ Created {len(reminders_created)} reminders:")
136
+ for title, next_run in reminders_created:
137
+ minutes_until = (next_run - now).total_seconds() / 60
138
+ print(f" {title}: fires in {int(minutes_until)} minutes ({next_run.strftime('%H:%M UTC')})")
139
+
140
+ print(f"\n{'=' * 70}")
141
+ print("✅ REMINDER GENERATION COMPLETE!")
142
+ print("=" * 70)
143
+ print("\n📱 Reminders will be sent via SMS")
144
+ print("🔍 Monitor logs: docker logs -f ggw-backend 2>&1 | grep -i reminder")
145
+ print(f"\n⏰ Next reminders:")
146
+ print(f" - Hydration: ~3 minutes")
147
+ print(f" - Sleep: ~5 minutes")
148
+ print(f" - Exercise: ~7 minutes")
149
+ print("\n" + "=" * 70)
150
+
151
+ asyncio.run(generate_reminders())
backend/scripts/test_reminder_system.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Complete Reminder System Test
3
+
4
+ This script:
5
+ 1. Creates a test user with profile and phone number
6
+ 2. Creates a reminder that fires in the next 2-3 minutes
7
+ 3. Verifies the reminder scheduler picks it up and sends SMS
8
+
9
+ Run from backend directory:
10
+ python scripts/test_reminder_system.py
11
+ """
12
+ import asyncio
13
+ import sys
14
+ from pathlib import Path
15
+ from datetime import datetime, timedelta
16
+ from uuid import uuid4
17
+
18
+ # Add the backend directory to path
19
+ backend_dir = Path(__file__).parent.parent
20
+ sys.path.insert(0, str(backend_dir))
21
+
22
+ # Load environment variables
23
+ from dotenv import load_dotenv
24
+ load_dotenv(backend_dir / ".env")
25
+
26
+ from sqlalchemy import select
27
+ from app.db import async_session_maker, init_db
28
+ from app.models import User, UserProfile, Reminder
29
+ from app.security import hash_password
30
+
31
+
32
+ async def create_test_user_and_reminder():
33
+ """Create a test user with profile and a reminder that fires soon."""
34
+
35
+ print("=" * 70)
36
+ print("CO-CODE REMINDER SYSTEM TEST")
37
+ print("=" * 70)
38
+
39
+ # Initialize database
40
+ print("\n1. Initializing database...")
41
+ await init_db()
42
+ print(" ✅ Database initialized")
43
+
44
+ async with async_session_maker() as db:
45
+ # Check for existing test user
46
+ print("\n2. Creating/finding test user...")
47
+ result = await db.execute(
48
+ select(User).where(User.email == "reminder_test@cocode.com")
49
+ )
50
+ user = result.scalar_one_or_none()
51
+
52
+ if user:
53
+ print(f" ℹ️ Found existing test user: {user.email} (ID: {user.id})")
54
+ else:
55
+ # Create test user
56
+ user = User(
57
+ id=uuid4(),
58
+ email="reminder_test@cocode.com",
59
+ full_name="Reminder Test User",
60
+ password_hash=hash_password("testpass123"),
61
+ is_active=True
62
+ )
63
+ db.add(user)
64
+ await db.commit()
65
+ await db.refresh(user)
66
+ print(f" ✅ Created test user: {user.email} (ID: {user.id})")
67
+
68
+ # Check for profile
69
+ print("\n3. Creating/updating user profile...")
70
+ result = await db.execute(
71
+ select(UserProfile).where(UserProfile.user_id == user.id)
72
+ )
73
+ profile = result.scalar_one_or_none()
74
+
75
+ # Get phone number from environment
76
+ import os
77
+ phone_number = os.getenv("SMS_TEST_TO_NUMBER", "+919004281995")
78
+
79
+ if profile:
80
+ profile.phone_number = phone_number
81
+ profile.full_name = "Reminder Test User"
82
+ profile.age_years = 30
83
+ print(f" ℹ️ Updated existing profile with phone: {phone_number}")
84
+ else:
85
+ profile = UserProfile(
86
+ user_id=user.id,
87
+ full_name="Reminder Test User",
88
+ phone_number=phone_number,
89
+ age_years=30,
90
+ sex_at_birth="male",
91
+ height_cm=175,
92
+ weight_kg=70
93
+ )
94
+ db.add(profile)
95
+ print(f" ✅ Created profile with phone: {phone_number}")
96
+
97
+ await db.commit()
98
+ await db.refresh(profile)
99
+
100
+ # Delete any existing test reminders for this user
101
+ print("\n4. Cleaning up old test reminders...")
102
+ result = await db.execute(
103
+ select(Reminder).where(Reminder.user_id == user.id)
104
+ )
105
+ old_reminders = list(result.scalars().all())
106
+ for reminder in old_reminders:
107
+ await db.delete(reminder)
108
+ await db.commit()
109
+ print(f" ✅ Deleted {len(old_reminders)} old reminder(s)")
110
+
111
+ # Create a new reminder that fires in 2 minutes
112
+ print("\n5. Creating test reminder...")
113
+ next_run = datetime.utcnow() + timedelta(minutes=2)
114
+
115
+ reminder = Reminder(
116
+ user_id=user.id,
117
+ type="test",
118
+ title="🧪 Test Reminder",
119
+ message=f"This is a test reminder from Co-Code! If you receive this SMS, the reminder system is working perfectly. Time: {datetime.utcnow().strftime('%H:%M:%S UTC')}",
120
+ schedule_type="once",
121
+ schedule_json={"time": next_run.isoformat()},
122
+ timezone="UTC",
123
+ channel="sms",
124
+ is_enabled=True,
125
+ next_run_at=next_run
126
+ )
127
+
128
+ db.add(reminder)
129
+ await db.commit()
130
+ await db.refresh(reminder)
131
+
132
+ print(f" ✅ Created test reminder:")
133
+ print(f" ID: {reminder.id}")
134
+ print(f" Title: {reminder.title}")
135
+ print(f" Next run: {next_run.strftime('%Y-%m-%d %H:%M:%S UTC')}")
136
+ print(f" Phone: {phone_number}")
137
+
138
+ # Calculate time until reminder fires
139
+ seconds_until = (next_run - datetime.utcnow()).total_seconds()
140
+ minutes = int(seconds_until // 60)
141
+ seconds = int(seconds_until % 60)
142
+
143
+ print(f"\n{'=' * 70}")
144
+ print(f"⏰ REMINDER WILL FIRE IN: {minutes} minutes and {seconds} seconds")
145
+ print(f"{'=' * 70}")
146
+ print(f"\n📱 Watch for SMS to: {phone_number}")
147
+ print(f"📋 Check Docker logs: docker logs ggw-backend -f")
148
+ print(f"🔍 Look for: 'Reminder scheduler tick: processed'")
149
+ print(f"\nThe reminder scheduler checks every 60 seconds.")
150
+ print(f"Your reminder will be sent at approximately: {next_run.strftime('%H:%M:%S UTC')}")
151
+ print(f"\n{'=' * 70}\n")
152
+
153
+ return {
154
+ "user_id": str(user.id),
155
+ "reminder_id": str(reminder.id),
156
+ "phone_number": phone_number,
157
+ "next_run": next_run.isoformat(),
158
+ "seconds_until": int(seconds_until)
159
+ }
160
+
161
+
162
+ async def monitor_logs():
163
+ """Monitor Docker logs for reminder processing."""
164
+ print("\n🔍 Monitoring logs for reminder processing...")
165
+ print(" (Press Ctrl+C to stop)\n")
166
+
167
+ import subprocess
168
+
169
+ try:
170
+ process = subprocess.Popen(
171
+ ["docker", "logs", "ggw-backend", "-f"],
172
+ stdout=subprocess.PIPE,
173
+ stderr=subprocess.STDOUT,
174
+ text=True,
175
+ bufsize=1
176
+ )
177
+
178
+ for line in process.stdout:
179
+ if any(keyword in line.lower() for keyword in ["reminder", "sms", "twilio", "processed"]):
180
+ print(f" {line.strip()}")
181
+
182
+ except KeyboardInterrupt:
183
+ print("\n\n Monitoring stopped by user")
184
+ process.kill()
185
+
186
+
187
+ if __name__ == "__main__":
188
+ try:
189
+ result = asyncio.run(create_test_user_and_reminder())
190
+
191
+ print("\n" + "=" * 70)
192
+ print("TEST SETUP COMPLETE!")
193
+ print("=" * 70)
194
+ print("\nWould you like to monitor logs? (Y/n): ", end="")
195
+
196
+ choice = input().strip().lower()
197
+ if choice in ["", "y", "yes"]:
198
+ asyncio.run(monitor_logs())
199
+ else:
200
+ print("\n✅ Setup complete. Check SMS and Docker logs in ~2 minutes.")
201
+
202
+ except Exception as e:
203
+ print(f"\n❌ ERROR: {e}")
204
+ import traceback
205
+ traceback.print_exc()
206
+ sys.exit(1)
backend/scripts/test_sms_api.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test SMS API Endpoint in Docker
3
+
4
+ Tests the /api/sms/test endpoint by logging in and sending a test SMS.
5
+ """
6
+ import httpx
7
+ import asyncio
8
+
9
+ BASE_URL = "http://localhost:8000"
10
+
11
+
12
+ async def test_sms_via_api():
13
+ """Test SMS sending via API endpoint."""
14
+
15
+ print("=" * 60)
16
+ print("TESTING SMS VIA API ENDPOINT")
17
+ print("=" * 60)
18
+
19
+ async with httpx.AsyncClient() as client:
20
+ # Step 1: Login to get JWT token
21
+ print("\n1. Logging in to get JWT token...")
22
+
23
+ # Try with a test user - you may need to create one first
24
+ login_data = {
25
+ "username": "test@example.com", # Update with your test user
26
+ "password": "testpassword" # Update with your test password
27
+ }
28
+
29
+ try:
30
+ response = await client.post(
31
+ f"{BASE_URL}/api/auth/token",
32
+ data=login_data,
33
+ headers={"Content-Type": "application/x-www-form-urlencoded"}
34
+ )
35
+
36
+ if response.status_code != 200:
37
+ print(f" ❌ Login failed: {response.status_code}")
38
+ print(f" Response: {response.text}")
39
+ print("\n 💡 Tip: You may need to register a user first or use existing credentials")
40
+ return False
41
+
42
+ token_data = response.json()
43
+ token = token_data.get("access_token")
44
+ print(f" ✅ Got JWT token: {token[:20]}...")
45
+
46
+ except Exception as e:
47
+ print(f" ❌ Login error: {e}")
48
+ return False
49
+
50
+ # Step 2: Call the SMS test endpoint
51
+ print("\n2. Calling /api/sms/test endpoint...")
52
+
53
+ try:
54
+ response = await client.post(
55
+ f"{BASE_URL}/api/sms/test",
56
+ json={"message": "🏥 Hello from API test! Lumea Health SMS is working."},
57
+ headers={
58
+ "Authorization": f"Bearer {token}",
59
+ "Content-Type": "application/json"
60
+ }
61
+ )
62
+
63
+ print(f" Status Code: {response.status_code}")
64
+ result = response.json()
65
+ print(f" Response: {result}")
66
+
67
+ if response.status_code == 200 and result.get("success"):
68
+ print("\n✅ SMS test successful!")
69
+ return True
70
+ else:
71
+ print(f"\n❌ SMS test failed: {result.get('message', 'Unknown error')}")
72
+ return False
73
+
74
+ except Exception as e:
75
+ print(f" ❌ API call error: {e}")
76
+ return False
77
+
78
+
79
+ if __name__ == "__main__":
80
+ print("\n" + "=" * 60)
81
+ print("LUMEA HEALTH - API SMS TEST")
82
+ print("=" * 60)
83
+
84
+ success = asyncio.run(test_sms_via_api())
85
+
86
+ print("\n" + "=" * 60)
87
+ if success:
88
+ print("✅ API SMS test completed successfully!")
89
+ else:
90
+ print("❌ API SMS test failed. Check configuration.")
91
+ print("=" * 60 + "\n")
backend/scripts/test_twilio_sms.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test Twilio SMS Integration
3
+
4
+ Simple standalone script to test Twilio SMS sending.
5
+ Run from the backend directory:
6
+ python scripts/test_twilio_sms.py
7
+ """
8
+ import os
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ # Add the backend directory to path
13
+ backend_dir = Path(__file__).parent.parent
14
+ sys.path.insert(0, str(backend_dir))
15
+
16
+ # Load environment variables from .env
17
+ from dotenv import load_dotenv
18
+ load_dotenv(backend_dir / ".env")
19
+
20
+ def test_twilio_sms():
21
+ """Test sending SMS via Twilio."""
22
+
23
+ # Print configuration (mask sensitive data)
24
+ print("=" * 60)
25
+ print("TWILIO SMS TEST")
26
+ print("=" * 60)
27
+
28
+ # Get credentials from environment
29
+ account_sid = os.getenv("TWILIO_ACCOUNT_SID")
30
+ auth_token = os.getenv("TWILIO_AUTH_TOKEN")
31
+ from_number = os.getenv("TWILIO_FROM_NUMBER")
32
+ to_number = os.getenv("SMS_TEST_TO_NUMBER")
33
+ sms_mode = os.getenv("SMS_MODE", "mock")
34
+
35
+ print(f"\nConfiguration:")
36
+ print(f" SMS_MODE: {sms_mode}")
37
+ print(f" TWILIO_ACCOUNT_SID: {'***' + account_sid[-4:] if account_sid else 'NOT SET'}")
38
+ print(f" TWILIO_AUTH_TOKEN: {'***' + auth_token[-4:] if auth_token else 'NOT SET'}")
39
+ print(f" TWILIO_FROM_NUMBER: {from_number or 'NOT SET'}")
40
+ print(f" SMS_TEST_TO_NUMBER: {to_number or 'NOT SET'}")
41
+
42
+ # Validate configuration
43
+ if not all([account_sid, auth_token, from_number, to_number]):
44
+ print("\n❌ ERROR: Missing Twilio configuration!")
45
+ print("Please set all required environment variables in .env:")
46
+ print(" - TWILIO_ACCOUNT_SID")
47
+ print(" - TWILIO_AUTH_TOKEN")
48
+ print(" - TWILIO_FROM_NUMBER")
49
+ print(" - SMS_TEST_TO_NUMBER")
50
+ return False
51
+
52
+ if sms_mode != "twilio":
53
+ print(f"\n⚠️ WARNING: SMS_MODE is '{sms_mode}', not 'twilio'")
54
+ print(" Change SMS_MODE=twilio in .env to send real SMS")
55
+
56
+ print("\n" + "-" * 60)
57
+ print("Attempting to send test SMS...")
58
+ print("-" * 60)
59
+
60
+ try:
61
+ from twilio.rest import Client
62
+
63
+ client = Client(account_sid, auth_token)
64
+
65
+ message = client.messages.create(
66
+ body="🏥 Lumea Health Test: Your Twilio SMS configuration is working correctly! This is a test message.",
67
+ from_=from_number,
68
+ to=to_number
69
+ )
70
+
71
+ print(f"\n✅ SUCCESS! SMS sent successfully!")
72
+ print(f" Message SID: {message.sid}")
73
+ print(f" Status: {message.status}")
74
+ print(f" From: {from_number}")
75
+ print(f" To: {to_number}")
76
+ print(f" Date Created: {message.date_created}")
77
+
78
+ return True
79
+
80
+ except Exception as e:
81
+ print(f"\n❌ ERROR: Failed to send SMS")
82
+ print(f" Error Type: {type(e).__name__}")
83
+ print(f" Error Message: {str(e)}")
84
+
85
+ # Common error explanations
86
+ error_msg = str(e).lower()
87
+ if "authenticate" in error_msg or "credentials" in error_msg:
88
+ print("\n 💡 Tip: Check your TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN")
89
+ elif "unverified" in error_msg:
90
+ print("\n 💡 Tip: In trial mode, you can only send to verified numbers.")
91
+ print(" Verify your phone number at: https://console.twilio.com/")
92
+ elif "invalid" in error_msg and "number" in error_msg:
93
+ print("\n 💡 Tip: Check phone number format. Should be E.164 format: +1234567890")
94
+
95
+ return False
96
+
97
+
98
+ def test_api_endpoint():
99
+ """Test the /api/sms/test endpoint."""
100
+ print("\n" + "=" * 60)
101
+ print("TESTING API ENDPOINT")
102
+ print("=" * 60)
103
+
104
+ print("\nTo test via API, make a POST request:")
105
+ print(' curl -X POST http://localhost:8000/api/sms/test \\')
106
+ print(' -H "Content-Type: application/json" \\')
107
+ print(' -H "Authorization: Bearer <your_jwt_token>" \\')
108
+ print(' -d \'{"message": "Test message from API"}\'')
109
+
110
+
111
+ if __name__ == "__main__":
112
+ print("\n" + "=" * 60)
113
+ print("LUMEA HEALTH - TWILIO SMS TEST SCRIPT")
114
+ print("=" * 60)
115
+
116
+ success = test_twilio_sms()
117
+ test_api_endpoint()
118
+
119
+ print("\n" + "=" * 60)
120
+ if success:
121
+ print("✅ All tests passed! Twilio is configured correctly.")
122
+ else:
123
+ print("❌ Some tests failed. Please check the configuration above.")
124
+ print("=" * 60 + "\n")
125
+
126
+ sys.exit(0 if success else 1)
docker-compose.yml CHANGED
@@ -28,12 +28,17 @@ services:
28
  BUILDKIT_INLINE_CACHE: 1
29
  container_name: ggw-backend
30
  restart: unless-stopped
 
 
 
31
  ports:
32
  - "8000:8000"
33
  # Ensures `host.docker.internal` works on Linux (needed for Ollama on host).
34
  extra_hosts:
35
  - "host.docker.internal:host-gateway"
36
  volumes:
 
 
37
  # Mount source code for hot reload
38
  - ./backend/app:/app/app:cached
39
  # Mount Alembic migrations so schema fixes don't require rebuilding the image
@@ -79,6 +84,15 @@ services:
79
  - NEO4J_PASSWORD=${NEO4J_PASSWORD:-changeme}
80
  - MEM0_COLLECTION=${MEM0_COLLECTION:-user_memories}
81
  - GRAPHITI_DATABASE=${GRAPHITI_DATABASE:-neo4j}
 
 
 
 
 
 
 
 
 
82
  healthcheck:
83
  test: ["CMD", "curl", "-f", "http://localhost:8000/"]
84
  interval: 30s
 
28
  BUILDKIT_INLINE_CACHE: 1
29
  container_name: ggw-backend
30
  restart: unless-stopped
31
+ # Load environment variables from root .env file
32
+ env_file:
33
+ - .env
34
  ports:
35
  - "8000:8000"
36
  # Ensures `host.docker.internal` works on Linux (needed for Ollama on host).
37
  extra_hosts:
38
  - "host.docker.internal:host-gateway"
39
  volumes:
40
+ # Mount .env file for configuration
41
+ - ./.env:/app/.env:ro
42
  # Mount source code for hot reload
43
  - ./backend/app:/app/app:cached
44
  # Mount Alembic migrations so schema fixes don't require rebuilding the image
 
84
  - NEO4J_PASSWORD=${NEO4J_PASSWORD:-changeme}
85
  - MEM0_COLLECTION=${MEM0_COLLECTION:-user_memories}
86
  - GRAPHITI_DATABASE=${GRAPHITI_DATABASE:-neo4j}
87
+ # SMS Configuration (Twilio or mock mode)
88
+ - SMS_MODE=${SMS_MODE:-mock}
89
+ - SMS_TEST_TO_NUMBER=${SMS_TEST_TO_NUMBER:-}
90
+ - TWILIO_ACCOUNT_SID=${TWILIO_ACCOUNT_SID:-}
91
+ - TWILIO_AUTH_TOKEN=${TWILIO_AUTH_TOKEN:-}
92
+ - TWILIO_FROM_NUMBER=${TWILIO_FROM_NUMBER:-}
93
+ # Reminder Scheduler Configuration
94
+ - REMINDER_SCHEDULER_ENABLED=${REMINDER_SCHEDULER_ENABLED:-true}
95
+ - REMINDER_CHECK_INTERVAL_SECONDS=${REMINDER_CHECK_INTERVAL_SECONDS:-60}
96
  healthcheck:
97
  test: ["CMD", "curl", "-f", "http://localhost:8000/"]
98
  interval: 30s