MukeshKapoor25 commited on
Commit
4e94087
·
1 Parent(s): 79d7b1c

notfication ms integration

Browse files
app/auth/controllers/service_partners_router.py CHANGED
@@ -31,7 +31,7 @@ async def send_service_partners_otp(request: ServiceProfessionalSendOTPRequest):
31
  2. Verifies service professional exists with this phone in scm_service_partners collection
32
  3. Validates professional is active
33
  4. Generates random 6-digit OTP
34
- 5. Sends OTP via WATI WhatsApp API
35
  6. Stores OTP in Redis with 5-minute expiration
36
 
37
  **Security:**
 
31
  2. Verifies service professional exists with this phone in scm_service_partners collection
32
  3. Validates professional is active
33
  4. Generates random 6-digit OTP
34
+ 5. Sends OTP via notification-ms (WhatsApp/SMS)
35
  6. Stores OTP in Redis with 5-minute expiration
36
 
37
  **Security:**
app/auth/controllers/staff_router.py CHANGED
@@ -35,7 +35,7 @@ async def send_staff_otp(request: StaffSendOTPRequest):
35
  2. Verifies staff user exists with this phone
36
  3. Validates user is staff role (not admin)
37
  4. Generates random 6-digit OTP
38
- 5. Sends OTP via WATI WhatsApp API
39
  6. Stores OTP in database with 5-minute expiration
40
 
41
  **Security:**
@@ -123,7 +123,7 @@ async def staff_login_mobile_otp(
123
 
124
  **Security:**
125
  - Only allows staff/employee roles (not admin/super_admin)
126
- - OTP validation via WATI WhatsApp
127
  - OTP expires in 5 minutes
128
  - Maximum 3 verification attempts
129
  - One-time use only
 
35
  2. Verifies staff user exists with this phone
36
  3. Validates user is staff role (not admin)
37
  4. Generates random 6-digit OTP
38
+ 5. Sends OTP via notification-ms (WhatsApp/SMS)
39
  6. Stores OTP in database with 5-minute expiration
40
 
41
  **Security:**
 
123
 
124
  **Security:**
125
  - Only allows staff/employee roles (not admin/super_admin)
126
+ - OTP validation via notification-ms
127
  - OTP expires in 5 minutes
128
  - Maximum 3 verification attempts
129
  - One-time use only
app/auth/services/customer_auth_service.py CHANGED
@@ -55,7 +55,7 @@ class CustomerAuthService:
55
 
56
  async def send_otp(self, mobile: str) -> Tuple[bool, str, int]:
57
  """
58
- Send OTP to customer mobile number via WATI WhatsApp API.
59
 
60
  Args:
61
  mobile: Customer mobile number
@@ -84,7 +84,6 @@ class CustomerAuthService:
84
  "expires_at": expires_at.isoformat(),
85
  "attempts": 0,
86
  "verified": False,
87
- "wati_message_id": None,
88
  "delivery_status": "pending"
89
  }
90
 
@@ -106,7 +105,6 @@ class CustomerAuthService:
106
  otp=otp,
107
  expiry_minutes=expiry_minutes,
108
  merchant_id=merchant_id or "",
109
- template_name=settings.WATI_OTP_TEMPLATE_NAME,
110
  )
111
 
112
  if queued:
 
55
 
56
  async def send_otp(self, mobile: str) -> Tuple[bool, str, int]:
57
  """
58
+ Send OTP to customer mobile number via notification-ms.
59
 
60
  Args:
61
  mobile: Customer mobile number
 
84
  "expires_at": expires_at.isoformat(),
85
  "attempts": 0,
86
  "verified": False,
 
87
  "delivery_status": "pending"
88
  }
89
 
 
105
  otp=otp,
106
  expiry_minutes=expiry_minutes,
107
  merchant_id=merchant_id or "",
 
108
  )
109
 
110
  if queued:
app/auth/services/service_partners_auth_service.py CHANGED
@@ -71,7 +71,7 @@ class ServiceProfessionalAuthService:
71
 
72
  async def send_otp(self, phone: str) -> Tuple[bool, str, int]:
73
  """
74
- Send OTP to service professional mobile number via WATI WhatsApp API.
75
 
76
  Args:
77
  phone: Service professional mobile number
@@ -116,7 +116,6 @@ class ServiceProfessionalAuthService:
116
  "expires_at": expires_at.isoformat(),
117
  "attempts": 0,
118
  "verified": False,
119
- "wati_message_id": None,
120
  "delivery_status": "pending"
121
  }
122
 
@@ -135,7 +134,6 @@ class ServiceProfessionalAuthService:
135
  otp=otp,
136
  expiry_minutes=expiry_minutes,
137
  merchant_id=merchant_id,
138
- template_name=settings.WATI_STAFF_OTP_TEMPLATE_NAME,
139
  )
140
 
141
  if queued:
 
71
 
72
  async def send_otp(self, phone: str) -> Tuple[bool, str, int]:
73
  """
74
+ Send OTP to service professional mobile number via notification-ms.
75
 
76
  Args:
77
  phone: Service professional mobile number
 
116
  "expires_at": expires_at.isoformat(),
117
  "attempts": 0,
118
  "verified": False,
 
119
  "delivery_status": "pending"
120
  }
121
 
 
134
  otp=otp,
135
  expiry_minutes=expiry_minutes,
136
  merchant_id=merchant_id,
 
137
  )
138
 
139
  if queued:
app/auth/services/staff_auth_service.py CHANGED
@@ -148,7 +148,6 @@ class StaffAuthService:
148
  otp=otp,
149
  expiry_minutes=expiry_minutes,
150
  merchant_id=staff["merchant_id"],
151
- template_name=settings.WATI_STAFF_OTP_TEMPLATE_NAME,
152
  )
153
 
154
  if queued:
 
148
  otp=otp,
149
  expiry_minutes=expiry_minutes,
150
  merchant_id=staff["merchant_id"],
 
151
  )
152
 
153
  if queued:
app/core/config.py CHANGED
@@ -57,18 +57,10 @@ class Settings(BaseSettings):
57
  TWILIO_AUTH_TOKEN: Optional[str] = os.getenv("TWILIO_AUTH_TOKEN")
58
  TWILIO_PHONE_NUMBER: Optional[str] = os.getenv("TWILIO_PHONE_NUMBER")
59
 
60
- # WATI WhatsApp template names (credentials are per-merchant in notification-ms)
61
- WATI_OTP_TEMPLATE_NAME: str = os.getenv("WATI_OTP_TEMPLATE_NAME", "cust_otp")
62
- WATI_STAFF_OTP_TEMPLATE_NAME: str = os.getenv("WATI_STAFF_OTP_TEMPLATE_NAME", "staff_otp_login")
63
-
64
- # SMTP Configuration (email is routed through notification-ms per-merchant)
65
- # Kept as optional stubs so existing env files don't cause validation errors
66
- SMTP_HOST: Optional[str] = None
67
- SMTP_PORT: int = 587
68
- SMTP_USERNAME: Optional[str] = None
69
- SMTP_PASSWORD: Optional[str] = None
70
- SMTP_FROM_EMAIL: Optional[str] = None
71
- SMTP_USE_TLS: bool = True
72
 
73
  # Logging
74
  LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
 
57
  TWILIO_AUTH_TOKEN: Optional[str] = os.getenv("TWILIO_AUTH_TOKEN")
58
  TWILIO_PHONE_NUMBER: Optional[str] = os.getenv("TWILIO_PHONE_NUMBER")
59
 
60
+ # Notification template names (resolved by notification-ms dispatcher)
61
+ NOTIFICATION_TEMPLATE_OTP_VERIFICATION: str = os.getenv("NOTIFICATION_TEMPLATE_OTP_VERIFICATION", "otp")
62
+ NOTIFICATION_TEMPLATE_CREDENTIALS: str = os.getenv("NOTIFICATION_TEMPLATE_CREDENTIALS", "otp")
63
+ NOTIFICATION_TEMPLATE_PASSWORD_RESET: str = os.getenv("NOTIFICATION_TEMPLATE_PASSWORD_RESET", "otp")
 
 
 
 
 
 
 
 
64
 
65
  # Logging
66
  LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
app/system_users/services/service.py CHANGED
@@ -932,7 +932,6 @@ class SystemUserService:
932
  name=user.first_name or user.username,
933
  new_password=reset_link,
934
  merchant_id=user.merchant_id or "",
935
- template_name="password_reset_link",
936
  )
937
  logger.info(f"Password reset notification queued for {user.phone}")
938
  except Exception as nq_err:
@@ -991,7 +990,6 @@ class SystemUserService:
991
  otp=otp,
992
  expiry_minutes=expiry_minutes,
993
  merchant_id=user.merchant_id or "",
994
- template_name="otp",
995
  )
996
 
997
  if queued:
 
932
  name=user.first_name or user.username,
933
  new_password=reset_link,
934
  merchant_id=user.merchant_id or "",
 
935
  )
936
  logger.info(f"Password reset notification queued for {user.phone}")
937
  except Exception as nq_err:
 
990
  otp=otp,
991
  expiry_minutes=expiry_minutes,
992
  merchant_id=user.merchant_id or "",
 
993
  )
994
 
995
  if queued:
app/utils/notification_queue.py CHANGED
@@ -3,26 +3,33 @@ Notification queue utility for auth-ms.
3
  Creates notification docs in MongoDB (notifications collection) and pushes
4
  notification_id to Redis queue for the notification-ms worker to consume.
5
 
6
- Uses the same queue name and doc shape as notification-ms so the worker
7
- picks them up seamlessly.
8
-
9
- Templates are company-level. merchant_name is passed as a variable
10
- in template_data for rendering (if applicable).
11
  """
12
  import json
13
  import uuid
14
  from datetime import datetime
15
  from typing import Optional, Dict, Any, List
 
16
  from app.core.logging import get_logger
 
17
  from app.cache import cache_service
18
  from app.nosql import get_database
19
 
20
  logger = get_logger(__name__)
21
 
22
- QUEUE_NAME = "notifications:queue"
23
  COLLECTION = "notifications"
24
 
25
 
 
 
 
 
 
 
 
 
26
  class NotificationQueue:
27
  """
28
  Enqueues notifications via MongoDB + Redis so notification-ms worker
@@ -36,9 +43,9 @@ class NotificationQueue:
36
  channels: List[str],
37
  template_name: str,
38
  template_data: Dict[str, Any],
39
- merchant_id: str,
40
  merchant_name: str = "",
41
- priority: str = "high",
42
  source: str = "auth-ms",
43
  metadata: Optional[Dict[str, Any]] = None,
44
  ) -> bool:
@@ -74,15 +81,16 @@ class NotificationQueue:
74
  await db[COLLECTION].insert_one(doc)
75
 
76
  redis_client = cache_service.get_client()
77
- redis_client.lpush(QUEUE_NAME, json.dumps({"notification_id": notification_id}))
 
 
 
78
 
79
  logger.info(
80
  "Notification queued",
81
  extra={
82
- "event": "enqueued",
83
  "notification_id": notification_id,
84
- "template_name": template_name,
85
- "queue": QUEUE_NAME,
86
  "channels": channels,
87
  "recipient": recipient,
88
  },
@@ -93,7 +101,7 @@ class NotificationQueue:
93
  logger.error(
94
  "Notification queue failed",
95
  exc_info=True,
96
- extra={"event": "enqueue_failure", "template_name": template_name, "recipient": recipient},
97
  )
98
  return False
99
 
@@ -106,17 +114,17 @@ class NotificationQueue:
106
  expiry_minutes: int = 5,
107
  merchant_id: str = "",
108
  merchant_name: str = "",
109
- template_name: str = "otp_verification",
110
- channels: Optional[List[str]] = None,
111
  ) -> bool:
112
- """Queue OTP notification via WhatsApp + SMS."""
113
  if channels is None:
114
- channels = ["whatsapp", "sms"]
115
 
116
  return await NotificationQueue._enqueue(
117
  recipient=recipient,
118
- channels=channels,
119
- template_name=template_name,
120
  template_data={
121
  "otp": otp,
122
  "expiry_minutes": str(expiry_minutes),
@@ -128,28 +136,27 @@ class NotificationQueue:
128
  )
129
 
130
  @staticmethod
131
- async def send_welcome_credentials(
132
  recipient: str,
133
- employee_name: str,
134
  username: str,
135
  password: str,
136
  merchant_id: str = "",
137
  merchant_name: str = "",
138
- template_name: str = "welcome_credentials",
139
- channels: Optional[List[str]] = None,
140
  ) -> bool:
141
- """Queue welcome credentials notification via WhatsApp + SMS."""
142
  if channels is None:
143
- channels = ["whatsapp", "sms"]
144
 
145
  return await NotificationQueue._enqueue(
146
  recipient=recipient,
147
- channels=channels,
148
- template_name=template_name,
149
  template_data={
150
- "name": employee_name,
151
- "username": username,
152
- "password": password,
153
  "merchant_name": merchant_name,
154
  },
155
  merchant_id=merchant_id,
@@ -164,25 +171,20 @@ class NotificationQueue:
164
  new_password: str,
165
  merchant_id: str = "",
166
  merchant_name: str = "",
167
- template_name: str = "otp",
168
- channels: Optional[List[str]] = None,
169
  ) -> bool:
170
- """Queue password reset notification via WhatsApp + SMS.
171
-
172
- Uses the 'otp' template key (maps to staff_otp_login in WATI and sms_otp_temp
173
- for SMS via merchant settings) with new_password as the 'otp' parameter.
174
- The dispatcher resolves the correct per-channel template from merchant settings.
175
- """
176
  if channels is None:
177
- channels = ["whatsapp", "sms"]
178
 
179
  return await NotificationQueue._enqueue(
180
  recipient=recipient,
181
- channels=channels,
182
- template_name=template_name,
183
  template_data={
184
  "otp": new_password,
185
- "expiry_minutes": "0", # not applicable for password reset
186
  "merchant_name": merchant_name,
187
  },
188
  merchant_id=merchant_id,
 
3
  Creates notification docs in MongoDB (notifications collection) and pushes
4
  notification_id to Redis queue for the notification-ms worker to consume.
5
 
6
+ auth-ms does NOT handle WATI/SMTP directly all delivery is delegated to
7
+ notification-ms which resolves per-merchant channel credentials.
 
 
 
8
  """
9
  import json
10
  import uuid
11
  from datetime import datetime
12
  from typing import Optional, Dict, Any, List
13
+ from enum import Enum
14
  from app.core.logging import get_logger
15
+ from app.core.config import settings
16
  from app.cache import cache_service
17
  from app.nosql import get_database
18
 
19
  logger = get_logger(__name__)
20
 
21
+ NOTIFICATION_QUEUE_NAME = "notifications:queue"
22
  COLLECTION = "notifications"
23
 
24
 
25
+ class NotificationChannel(str, Enum):
26
+ """Supported notification channels."""
27
+ WHATSAPP = "whatsapp"
28
+ SMS = "sms"
29
+ EMAIL = "email"
30
+ PUSH = "push"
31
+
32
+
33
  class NotificationQueue:
34
  """
35
  Enqueues notifications via MongoDB + Redis so notification-ms worker
 
43
  channels: List[str],
44
  template_name: str,
45
  template_data: Dict[str, Any],
46
+ merchant_id: str = "",
47
  merchant_name: str = "",
48
+ priority: str = "normal",
49
  source: str = "auth-ms",
50
  metadata: Optional[Dict[str, Any]] = None,
51
  ) -> bool:
 
81
  await db[COLLECTION].insert_one(doc)
82
 
83
  redis_client = cache_service.get_client()
84
+ redis_client.lpush(
85
+ NOTIFICATION_QUEUE_NAME,
86
+ json.dumps({"notification_id": notification_id}),
87
+ )
88
 
89
  logger.info(
90
  "Notification queued",
91
  extra={
 
92
  "notification_id": notification_id,
93
+ "template": template_name,
 
94
  "channels": channels,
95
  "recipient": recipient,
96
  },
 
101
  logger.error(
102
  "Notification queue failed",
103
  exc_info=True,
104
+ extra={"template": template_name, "recipient": recipient},
105
  )
106
  return False
107
 
 
114
  expiry_minutes: int = 5,
115
  merchant_id: str = "",
116
  merchant_name: str = "",
117
+ channels: Optional[List[NotificationChannel]] = None,
118
+ template_name: Optional[str] = None,
119
  ) -> bool:
120
+ """Queue OTP notification. Delivery handled by notification-ms."""
121
  if channels is None:
122
+ channels = [NotificationChannel.WHATSAPP, NotificationChannel.SMS]
123
 
124
  return await NotificationQueue._enqueue(
125
  recipient=recipient,
126
+ channels=[ch.value for ch in channels],
127
+ template_name=template_name or settings.NOTIFICATION_TEMPLATE_OTP_VERIFICATION,
128
  template_data={
129
  "otp": otp,
130
  "expiry_minutes": str(expiry_minutes),
 
136
  )
137
 
138
  @staticmethod
139
+ async def send_credentials(
140
  recipient: str,
141
+ name: str,
142
  username: str,
143
  password: str,
144
  merchant_id: str = "",
145
  merchant_name: str = "",
146
+ channels: Optional[List[NotificationChannel]] = None,
147
+ template_name: Optional[str] = None,
148
  ) -> bool:
149
+ """Queue welcome credentials notification. Delivery handled by notification-ms."""
150
  if channels is None:
151
+ channels = [NotificationChannel.WHATSAPP, NotificationChannel.SMS]
152
 
153
  return await NotificationQueue._enqueue(
154
  recipient=recipient,
155
+ channels=[ch.value for ch in channels],
156
+ template_name=template_name or settings.NOTIFICATION_TEMPLATE_CREDENTIALS,
157
  template_data={
158
+ "otp": password,
159
+ "expiry_minutes": "0",
 
160
  "merchant_name": merchant_name,
161
  },
162
  merchant_id=merchant_id,
 
171
  new_password: str,
172
  merchant_id: str = "",
173
  merchant_name: str = "",
174
+ channels: Optional[List[NotificationChannel]] = None,
175
+ template_name: Optional[str] = None,
176
  ) -> bool:
177
+ """Queue password reset notification. Delivery handled by notification-ms."""
 
 
 
 
 
178
  if channels is None:
179
+ channels = [NotificationChannel.WHATSAPP, NotificationChannel.SMS]
180
 
181
  return await NotificationQueue._enqueue(
182
  recipient=recipient,
183
+ channels=[ch.value for ch in channels],
184
+ template_name=template_name or settings.NOTIFICATION_TEMPLATE_PASSWORD_RESET,
185
  template_data={
186
  "otp": new_password,
187
+ "expiry_minutes": "0",
188
  "merchant_name": merchant_name,
189
  },
190
  merchant_id=merchant_id,