MukeshKapoor25 commited on
Commit
fe2f3e5
·
1 Parent(s): 0011d75

feat(logging): add comprehensive logging across application layers

Browse files

Implement detailed logging in models, services, and routers to improve debugging and monitoring
Configure logging setup in app.py with appropriate log levels and handlers
Add error handling with logging for critical operations in user flows

app/app.py CHANGED
@@ -3,6 +3,29 @@
3
  from fastapi import FastAPI
4
  from fastapi.middleware.cors import CORSMiddleware
5
  from app.routers import user_router, profile_router
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
  app = FastAPI(title="BookMyService User Management Service")
8
 
 
3
  from fastapi import FastAPI
4
  from fastapi.middleware.cors import CORSMiddleware
5
  from app.routers import user_router, profile_router
6
+ import logging
7
+ import sys
8
+
9
+ # Configure logging
10
+ logging.basicConfig(
11
+ level=logging.DEBUG,
12
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
13
+ handlers=[
14
+ logging.StreamHandler(sys.stdout),
15
+ logging.FileHandler('app.log')
16
+ ]
17
+ )
18
+
19
+ # Set specific logger levels
20
+ logging.getLogger("user_router").setLevel(logging.DEBUG)
21
+ logging.getLogger("user_service").setLevel(logging.DEBUG)
22
+ logging.getLogger("otp_model").setLevel(logging.DEBUG)
23
+ logging.getLogger("user_model").setLevel(logging.DEBUG)
24
+ logging.getLogger("profile_service").setLevel(logging.DEBUG)
25
+
26
+ # Reduce noise from other libraries
27
+ logging.getLogger("uvicorn").setLevel(logging.INFO)
28
+ logging.getLogger("fastapi").setLevel(logging.INFO)
29
 
30
  app = FastAPI(title="BookMyService User Management Service")
31
 
app/models/otp_model.py CHANGED
@@ -3,6 +3,9 @@ from app.core.cache_client import get_redis
3
  from app.utils.sms_utils import send_sms_otp
4
  from app.utils.email_utils import send_email_otp
5
  from app.utils.common_utils import is_email
 
 
 
6
 
7
  class BookMyServiceOTPModel:
8
  OTP_TTL = 300 # 5 minutes
@@ -11,18 +14,37 @@ class BookMyServiceOTPModel:
11
 
12
  @staticmethod
13
  async def store_otp(identifier: str, phone: str, otp: str, ttl: int = OTP_TTL):
14
- redis = await get_redis()
 
 
 
 
15
 
16
- # Rate limit: max 3 OTPs per 10 minutes
17
- rate_key = f"otp_rate_limit:{identifier}"
18
- attempts = await redis.incr(rate_key)
19
- if attempts == 1:
20
- await redis.expire(rate_key, BookMyServiceOTPModel.RATE_LIMIT_WINDOW)
21
- elif attempts > BookMyServiceOTPModel.RATE_LIMIT_MAX:
22
- raise HTTPException(status_code=429, detail="Too many OTP requests. Try again later.")
 
 
 
 
 
 
23
 
24
- # Store OTP
25
- await redis.setex(f"bms_otp:{identifier}", ttl, otp)
 
 
 
 
 
 
 
 
 
26
  '''
27
  # Send OTP via SMS, fallback to Email if identifier is email
28
  try:
@@ -41,13 +63,36 @@ class BookMyServiceOTPModel:
41
  '''
42
  @staticmethod
43
  async def verify_otp(identifier: str, otp: str):
44
- redis = await get_redis()
45
- key = f"bms_otp:{identifier}"
46
- stored = await redis.get(key)
47
- if stored and stored == otp:
48
- await redis.delete(key)
49
- return True
50
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
  @staticmethod
53
  async def read_otp(identifier: str):
 
3
  from app.utils.sms_utils import send_sms_otp
4
  from app.utils.email_utils import send_email_otp
5
  from app.utils.common_utils import is_email
6
+ import logging
7
+
8
+ logger = logging.getLogger("otp_model")
9
 
10
  class BookMyServiceOTPModel:
11
  OTP_TTL = 300 # 5 minutes
 
14
 
15
  @staticmethod
16
  async def store_otp(identifier: str, phone: str, otp: str, ttl: int = OTP_TTL):
17
+ logger.info(f"Storing OTP for identifier: {identifier}")
18
+
19
+ try:
20
+ redis = await get_redis()
21
+ logger.debug(f"Redis connection established for OTP storage")
22
 
23
+ # Rate limit: max 3 OTPs per 10 minutes
24
+ rate_key = f"otp_rate_limit:{identifier}"
25
+ logger.debug(f"Checking rate limit with key: {rate_key}")
26
+
27
+ attempts = await redis.incr(rate_key)
28
+ logger.debug(f"Current OTP attempts for {identifier}: {attempts}")
29
+
30
+ if attempts == 1:
31
+ await redis.expire(rate_key, BookMyServiceOTPModel.RATE_LIMIT_WINDOW)
32
+ logger.debug(f"Set rate limit expiry for {identifier}")
33
+ elif attempts > BookMyServiceOTPModel.RATE_LIMIT_MAX:
34
+ logger.warning(f"Rate limit exceeded for {identifier}: {attempts} attempts")
35
+ raise HTTPException(status_code=429, detail="Too many OTP requests. Try again later.")
36
 
37
+ # Store OTP
38
+ otp_key = f"bms_otp:{identifier}"
39
+ await redis.setex(otp_key, ttl, otp)
40
+ logger.info(f"OTP stored successfully for {identifier} with key: {otp_key}, TTL: {ttl}")
41
+
42
+ except HTTPException as e:
43
+ logger.error(f"HTTP error storing OTP for {identifier}: {e.status_code} - {e.detail}")
44
+ raise e
45
+ except Exception as e:
46
+ logger.error(f"Unexpected error storing OTP for {identifier}: {str(e)}", exc_info=True)
47
+ raise HTTPException(status_code=500, detail="Failed to store OTP")
48
  '''
49
  # Send OTP via SMS, fallback to Email if identifier is email
50
  try:
 
63
  '''
64
  @staticmethod
65
  async def verify_otp(identifier: str, otp: str):
66
+ logger.info(f"Verifying OTP for identifier: {identifier}")
67
+ logger.debug(f"Provided OTP: {otp}")
68
+
69
+ try:
70
+ redis = await get_redis()
71
+ logger.debug("Redis connection established for OTP verification")
72
+
73
+ key = f"bms_otp:{identifier}"
74
+ logger.debug(f"Looking up OTP with key: {key}")
75
+
76
+ stored = await redis.get(key)
77
+ logger.debug(f"Stored OTP value: {stored}")
78
+
79
+ if stored:
80
+ logger.debug(f"OTP found in Redis. Comparing: provided='{otp}' vs stored='{stored}'")
81
+ if stored == otp:
82
+ logger.info(f"OTP verification successful for {identifier}")
83
+ await redis.delete(key)
84
+ logger.debug(f"OTP deleted from Redis after successful verification")
85
+ return True
86
+ else:
87
+ logger.warning(f"OTP mismatch for {identifier}: provided='{otp}' vs stored='{stored}'")
88
+ return False
89
+ else:
90
+ logger.warning(f"No OTP found in Redis for identifier: {identifier} with key: {key}")
91
+ return False
92
+
93
+ except Exception as e:
94
+ logger.error(f"Error verifying OTP for {identifier}: {str(e)}", exc_info=True)
95
+ return False
96
 
97
  @staticmethod
98
  async def read_otp(identifier: str):
app/models/user_model.py CHANGED
@@ -2,28 +2,71 @@ from fastapi import HTTPException
2
  from app.core.nosql_client import db
3
  from app.utils.common_utils import is_email # Assumes utility exists
4
  from app.schemas.user_schema import UserRegisterRequest
 
 
 
5
 
6
  class BookMyServiceUserModel:
7
  collection = db["customers"]
8
 
9
  @staticmethod
10
  async def find_by_email(email: str):
11
- return await BookMyServiceUserModel.collection.find_one({"email": email})
 
 
 
 
 
 
 
 
 
 
12
 
13
  @staticmethod
14
  async def find_by_mobile(mobile: str):
15
- return await BookMyServiceUserModel.collection.find_one({"phone": mobile})
 
 
 
 
 
 
 
 
 
 
16
 
17
  @staticmethod
18
  async def find_by_identifier(identifier: str):
19
- if is_email(identifier):
20
- user = await BookMyServiceUserModel.find_by_email(identifier)
21
- else:
22
- user = await BookMyServiceUserModel.find_by_mobile(identifier)
23
-
24
- if not user:
25
- raise HTTPException(status_code=404, detail="User not found with this email or mobile")
26
- return user
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
  @staticmethod
29
  async def exists_by_email_or_phone(email: str, phone: str) -> bool:
 
2
  from app.core.nosql_client import db
3
  from app.utils.common_utils import is_email # Assumes utility exists
4
  from app.schemas.user_schema import UserRegisterRequest
5
+ import logging
6
+
7
+ logger = logging.getLogger("user_model")
8
 
9
  class BookMyServiceUserModel:
10
  collection = db["customers"]
11
 
12
  @staticmethod
13
  async def find_by_email(email: str):
14
+ logger.debug(f"Searching for user by email: {email}")
15
+ try:
16
+ user = await BookMyServiceUserModel.collection.find_one({"email": email})
17
+ if user:
18
+ logger.debug(f"User found by email: {email}")
19
+ else:
20
+ logger.debug(f"No user found with email: {email}")
21
+ return user
22
+ except Exception as e:
23
+ logger.error(f"Error finding user by email {email}: {str(e)}", exc_info=True)
24
+ return None
25
 
26
  @staticmethod
27
  async def find_by_mobile(mobile: str):
28
+ logger.debug(f"Searching for user by mobile: {mobile}")
29
+ try:
30
+ user = await BookMyServiceUserModel.collection.find_one({"phone": mobile})
31
+ if user:
32
+ logger.debug(f"User found by mobile: {mobile}")
33
+ else:
34
+ logger.debug(f"No user found with mobile: {mobile}")
35
+ return user
36
+ except Exception as e:
37
+ logger.error(f"Error finding user by mobile {mobile}: {str(e)}", exc_info=True)
38
+ return None
39
 
40
  @staticmethod
41
  async def find_by_identifier(identifier: str):
42
+ logger.info(f"Finding user by identifier: {identifier}")
43
+
44
+ try:
45
+ # Determine if identifier is email or mobile
46
+ is_email_identifier = is_email(identifier)
47
+ logger.debug(f"Identifier type - is_email: {is_email_identifier}")
48
+
49
+ if is_email_identifier:
50
+ logger.debug(f"Searching by email for identifier: {identifier}")
51
+ user = await BookMyServiceUserModel.find_by_email(identifier)
52
+ else:
53
+ logger.debug(f"Searching by mobile for identifier: {identifier}")
54
+ user = await BookMyServiceUserModel.find_by_mobile(identifier)
55
+
56
+ if not user:
57
+ logger.warning(f"User not found with identifier: {identifier}")
58
+ raise HTTPException(status_code=404, detail="User not found with this email or mobile")
59
+
60
+ logger.info(f"User found successfully for identifier: {identifier}")
61
+ logger.debug(f"User data keys: {list(user.keys()) if user else 'None'}")
62
+ return user
63
+
64
+ except HTTPException as e:
65
+ logger.error(f"HTTP error finding user by identifier {identifier}: {e.status_code} - {e.detail}")
66
+ raise e
67
+ except Exception as e:
68
+ logger.error(f"Unexpected error finding user by identifier {identifier}: {str(e)}", exc_info=True)
69
+ raise HTTPException(status_code=500, detail="Failed to find user")
70
 
71
  @staticmethod
72
  async def exists_by_email_or_phone(email: str, phone: str) -> bool:
app/routers/user_router.py CHANGED
@@ -13,17 +13,25 @@ from app.utils.jwt import create_temp_token, decode_token
13
  from app.utils.social_utils import verify_google_token, verify_apple_token
14
  import logging
15
 
16
- logger = logging.getLogger(__name__)
17
 
18
  router = APIRouter()
19
 
20
  # 🔐 Declare API key header scheme (Swagger shows a single token input box)
21
- api_key_scheme = APIKeyHeader(name="Authorization", auto_error=True)
22
 
23
  # 🔍 Bearer token parser
24
  # More flexible bearer token parser
25
  def get_bearer_token(api_key: str = Security(api_key_scheme)) -> str:
26
  try:
 
 
 
 
 
 
 
 
27
  # If "Bearer " prefix is included, strip it
28
  logger.debug(f"Received Authorization header: {api_key}")
29
  if api_key.lower().startswith("bearer "):
@@ -32,28 +40,54 @@ def get_bearer_token(api_key: str = Security(api_key_scheme)) -> str:
32
  # Else, assume it's already a raw JWT
33
  return api_key
34
 
 
 
 
35
  except Exception as e:
 
36
  raise HTTPException(
37
  status_code=status.HTTP_401_UNAUTHORIZED,
38
- detail="Invalid or missing Authorization token"
39
  )
40
 
41
 
42
 
43
  @router.post("/send-otp")
44
  async def send_otp_handler(payload: OTPRequest):
45
- identifier = payload.email or payload.phone
46
- if not identifier:
47
- raise HTTPException(status_code=400, detail="Email or phone required")
48
 
49
- await UserService.send_otp(identifier, payload.phone or "N/A")
50
-
51
- temp_token = create_temp_token({
52
- "sub": identifier,
53
- "type": "otp_verification"
54
- }, expires_minutes=10)
55
-
56
- return {"message": "OTP sent", "temp_token": temp_token}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
  # 🔐 OTP Login using temporary token
59
  @router.post("/otp-login", response_model=TokenResponse)
@@ -61,11 +95,47 @@ async def otp_login_handler(
61
  payload: OTPVerifyRequest,
62
  temp_token: str = Depends(get_bearer_token)
63
  ):
64
- decoded = decode_token(temp_token)
65
- if not decoded or decoded.get("sub") != payload.login_input or decoded.get("type") != "otp_verification":
66
- raise HTTPException(status_code=401, detail="Invalid or expired OTP session token")
67
 
68
- return await UserService.otp_login_handler(payload.login_input, payload.otp)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
  # 🌐 OAuth Login for Google / Apple
71
  @router.post("/oauth-login", response_model=TokenResponse)
 
13
  from app.utils.social_utils import verify_google_token, verify_apple_token
14
  import logging
15
 
16
+ logger = logging.getLogger("user_router")
17
 
18
  router = APIRouter()
19
 
20
  # 🔐 Declare API key header scheme (Swagger shows a single token input box)
21
+ api_key_scheme = APIKeyHeader(name="Authorization", auto_error=False)
22
 
23
  # 🔍 Bearer token parser
24
  # More flexible bearer token parser
25
  def get_bearer_token(api_key: str = Security(api_key_scheme)) -> str:
26
  try:
27
+ # Check if Authorization header is missing
28
+ if not api_key:
29
+ logger.warning("Missing Authorization header")
30
+ raise HTTPException(
31
+ status_code=status.HTTP_401_UNAUTHORIZED,
32
+ detail="Missing Authorization header"
33
+ )
34
+
35
  # If "Bearer " prefix is included, strip it
36
  logger.debug(f"Received Authorization header: {api_key}")
37
  if api_key.lower().startswith("bearer "):
 
40
  # Else, assume it's already a raw JWT
41
  return api_key
42
 
43
+ except HTTPException:
44
+ # Re-raise HTTP exceptions
45
+ raise
46
  except Exception as e:
47
+ logger.error(f"Error processing Authorization header: {str(e)}")
48
  raise HTTPException(
49
  status_code=status.HTTP_401_UNAUTHORIZED,
50
+ detail="Invalid Authorization header format"
51
  )
52
 
53
 
54
 
55
  @router.post("/send-otp")
56
  async def send_otp_handler(payload: OTPRequest):
57
+ logger.info(f"OTP request started - email: {payload.email}, phone: {payload.phone}")
 
 
58
 
59
+ try:
60
+ # Validate input
61
+ identifier = payload.email or payload.phone
62
+ if not identifier:
63
+ logger.warning("OTP request failed - neither email nor phone provided")
64
+ raise HTTPException(status_code=400, detail="Email or phone required")
65
+
66
+ logger.debug(f"Using identifier: {identifier}")
67
+
68
+ # Send OTP via service
69
+ logger.debug(f"Calling UserService.send_otp with identifier: {identifier}, phone: {payload.phone or 'N/A'}")
70
+ await UserService.send_otp(identifier, payload.phone or "N/A")
71
+ logger.info(f"OTP sent successfully to: {identifier}")
72
+
73
+ # Create temporary token
74
+ logger.debug("Creating temporary token for OTP verification")
75
+ temp_token = create_temp_token({
76
+ "sub": identifier,
77
+ "type": "otp_verification"
78
+ }, expires_minutes=10)
79
+
80
+ logger.info(f"Temporary token created for: {identifier}")
81
+ logger.debug(f"Temp token (first 20 chars): {temp_token[:20]}...")
82
+
83
+ return {"message": "OTP sent", "temp_token": temp_token}
84
+
85
+ except HTTPException as e:
86
+ logger.error(f"OTP request failed - HTTP {e.status_code}: {e.detail}")
87
+ raise e
88
+ except Exception as e:
89
+ logger.error(f"Unexpected error during OTP request: {str(e)}", exc_info=True)
90
+ raise HTTPException(status_code=500, detail="Internal server error during OTP request")
91
 
92
  # 🔐 OTP Login using temporary token
93
  @router.post("/otp-login", response_model=TokenResponse)
 
95
  payload: OTPVerifyRequest,
96
  temp_token: str = Depends(get_bearer_token)
97
  ):
98
+ logger.info(f"OTP login attempt started - login_input: {payload.login_input}")
99
+ logger.debug(f"Received temp_token: {temp_token[:20]}..." if temp_token else "No temp_token received")
 
100
 
101
+ try:
102
+ # Decode and validate temporary token
103
+ logger.debug("Attempting to decode temporary token")
104
+ decoded = decode_token(temp_token)
105
+ logger.debug(f"Decoded token payload: {decoded}")
106
+
107
+ if not decoded:
108
+ logger.warning("Failed to decode temporary token - token is invalid or expired")
109
+ raise HTTPException(status_code=401, detail="Invalid or expired OTP session token")
110
+
111
+ # Validate token subject matches login input
112
+ token_sub = decoded.get("sub")
113
+ token_type = decoded.get("type")
114
+ logger.debug(f"Token subject: {token_sub}, Token type: {token_type}")
115
+
116
+ if token_sub != payload.login_input:
117
+ logger.warning(f"Token subject mismatch - token_sub: {token_sub}, login_input: {payload.login_input}")
118
+ raise HTTPException(status_code=401, detail="Invalid or expired OTP session token")
119
+
120
+ if token_type != "otp_verification":
121
+ logger.warning(f"Invalid token type - expected: otp_verification, got: {token_type}")
122
+ raise HTTPException(status_code=401, detail="Invalid or expired OTP session token")
123
+
124
+ logger.info(f"Temporary token validation successful for: {payload.login_input}")
125
+
126
+ # Call user service for OTP verification and login
127
+ logger.debug(f"Calling UserService.otp_login_handler with identifier: {payload.login_input}, otp: {payload.otp}")
128
+ result = await UserService.otp_login_handler(payload.login_input, payload.otp)
129
+
130
+ logger.info(f"OTP login successful for: {payload.login_input}")
131
+ return result
132
+
133
+ except HTTPException as e:
134
+ logger.error(f"OTP login failed for {payload.login_input} - HTTP {e.status_code}: {e.detail}")
135
+ raise e
136
+ except Exception as e:
137
+ logger.error(f"Unexpected error during OTP login for {payload.login_input}: {str(e)}", exc_info=True)
138
+ raise HTTPException(status_code=500, detail="Internal server error during OTP login")
139
 
140
  # 🌐 OAuth Login for Google / Apple
141
  @router.post("/oauth-login", response_model=TokenResponse)
app/services/user_service.py CHANGED
@@ -14,31 +14,70 @@ logger = logging.getLogger("user_service")
14
  class UserService:
15
  @staticmethod
16
  async def send_otp(identifier: str, phone: str):
17
- # Using dummy OTP for testing
18
- otp = "777777"
19
- await BookMyServiceOTPModel.store_otp(identifier, phone, otp)
20
- logger.debug(f"OTP sent to {identifier}: {otp}")
 
 
 
 
 
 
 
 
 
 
21
 
22
  @staticmethod
23
  async def otp_login_handler(identifier: str, otp: str):
24
- if not await BookMyServiceOTPModel.verify_otp(identifier, otp):
25
- logger.info(f"Invalid or expired OTP for identifier: {identifier}")
26
- raise HTTPException(status_code=400, detail="Invalid or expired OTP")
27
-
28
- user = await BookMyServiceUserModel.find_by_identifier(identifier)
29
- if not user:
30
- logger.info(f"No user found for identifier: {identifier}")
31
- raise HTTPException(status_code=404, detail="User not found")
32
-
33
- token_data = {
34
- "sub": user.get("user_id"),
35
- "user_id": user.get("user_id"),
36
- "email": user.get("email"),
37
- "role": "user",
38
- "exp": datetime.utcnow() + timedelta(hours=8)
39
- }
40
- access_token = jwt.encode(token_data, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
41
- return {"access_token": access_token, "token_type": "bearer", "name": user.get("name")}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
  @staticmethod
44
  async def register(data: UserRegisterRequest, decoded):
 
14
  class UserService:
15
  @staticmethod
16
  async def send_otp(identifier: str, phone: str):
17
+ logger.info(f"UserService.send_otp called - identifier: {identifier}, phone: {phone}")
18
+
19
+ try:
20
+ # Using dummy OTP for testing
21
+ otp = "777777"
22
+ logger.debug(f"Generated OTP: {otp} for identifier: {identifier}")
23
+
24
+ await BookMyServiceOTPModel.store_otp(identifier, phone, otp)
25
+ logger.info(f"OTP stored successfully for identifier: {identifier}")
26
+ logger.debug(f"OTP sent to {identifier}: {otp}")
27
+
28
+ except Exception as e:
29
+ logger.error(f"Error in send_otp for identifier {identifier}: {str(e)}", exc_info=True)
30
+ raise HTTPException(status_code=500, detail="Failed to send OTP")
31
 
32
  @staticmethod
33
  async def otp_login_handler(identifier: str, otp: str):
34
+ logger.info(f"UserService.otp_login_handler called - identifier: {identifier}, otp: {otp}")
35
+
36
+ try:
37
+ # Verify OTP
38
+ logger.debug(f"Verifying OTP for identifier: {identifier}")
39
+ otp_valid = await BookMyServiceOTPModel.verify_otp(identifier, otp)
40
+ logger.debug(f"OTP verification result: {otp_valid}")
41
+
42
+ if not otp_valid:
43
+ logger.warning(f"Invalid or expired OTP for identifier: {identifier}")
44
+ raise HTTPException(status_code=400, detail="Invalid or expired OTP")
45
+
46
+ logger.info(f"OTP verification successful for identifier: {identifier}")
47
+
48
+ # Find user by identifier
49
+ logger.debug(f"Looking up user by identifier: {identifier}")
50
+ user = await BookMyServiceUserModel.find_by_identifier(identifier)
51
+ logger.debug(f"User lookup result: {user is not None}")
52
+
53
+ if not user:
54
+ logger.warning(f"No user found for identifier: {identifier}")
55
+ raise HTTPException(status_code=404, detail="User not found")
56
+
57
+ logger.info(f"User found for identifier: {identifier}, user_id: {user.get('user_id')}")
58
+
59
+ # Create JWT token
60
+ logger.debug("Creating JWT token for authenticated user")
61
+ token_data = {
62
+ "sub": user.get("user_id"),
63
+ "user_id": user.get("user_id"),
64
+ "email": user.get("email"),
65
+ "role": "user",
66
+ "exp": datetime.utcnow() + timedelta(hours=8)
67
+ }
68
+ logger.debug(f"Token data: {token_data}")
69
+
70
+ access_token = jwt.encode(token_data, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
71
+ logger.info(f"JWT token created successfully for user: {user.get('user_id')}")
72
+
73
+ return {"access_token": access_token, "token_type": "bearer", "name": user.get("name")}
74
+
75
+ except HTTPException as e:
76
+ logger.error(f"HTTP error in otp_login_handler for {identifier}: {e.status_code} - {e.detail}")
77
+ raise e
78
+ except Exception as e:
79
+ logger.error(f"Unexpected error in otp_login_handler for {identifier}: {str(e)}", exc_info=True)
80
+ raise HTTPException(status_code=500, detail="Internal server error during login")
81
 
82
  @staticmethod
83
  async def register(data: UserRegisterRequest, decoded):