MukeshKapoor25 commited on
Commit
2e26396
·
1 Parent(s): 981259a

feat(profile): implement jwt-protected profile endpoint with service layer

Browse files

- Add JWT authentication middleware with token verification
- Create profile service to fetch customer data from database
- Implement error handling and logging for profile operations

app/routers/profile_router.py CHANGED
@@ -1,11 +1,52 @@
1
 
2
  ## bookmyservice-ums/app/routers/profile_router.py
3
 
4
- from fastapi import APIRouter
 
 
 
 
 
 
 
5
 
6
  router = APIRouter()
7
 
8
- @router.get("/me")
9
- def get_profile():
10
- return {"user": "Sample user profile - implement real logic here"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
 
1
 
2
  ## bookmyservice-ums/app/routers/profile_router.py
3
 
4
+ from fastapi import APIRouter, Depends, HTTPException, status
5
+ from typing import Dict, Any
6
+ import logging
7
+
8
+ from app.utils.jwt import get_current_user_id
9
+ from app.services.profile_service import profile_service
10
+
11
+ logger = logging.getLogger(__name__)
12
 
13
  router = APIRouter()
14
 
15
+ @router.get("/me", response_model=Dict[str, Any])
16
+ async def get_profile(current_user_id: str = Depends(get_current_user_id)):
17
+ """
18
+ Get current user's profile from customers collection.
19
+
20
+ This endpoint is JWT protected and requires a valid Bearer token.
21
+
22
+ Args:
23
+ current_user_id (str): User ID extracted from JWT token
24
+
25
+ Returns:
26
+ Dict[str, Any]: Customer profile data
27
+
28
+ Raises:
29
+ HTTPException: 401 if token is invalid, 404 if profile not found
30
+ """
31
+ try:
32
+ logger.info(f"Profile request for user: {current_user_id}")
33
+
34
+ # Fetch customer profile using the service
35
+ profile_data = await profile_service.get_customer_profile(current_user_id)
36
+
37
+ return {
38
+ "success": True,
39
+ "message": "Profile retrieved successfully",
40
+ "data": profile_data
41
+ }
42
+
43
+ except HTTPException:
44
+ # Re-raise HTTP exceptions from service
45
+ raise
46
+ except Exception as e:
47
+ logger.error(f"Unexpected error in get_profile: {str(e)}")
48
+ raise HTTPException(
49
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
50
+ detail="Internal server error"
51
+ )
52
 
app/services/profile_service.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Profile service for customer profile operations.
3
+ """
4
+
5
+ import logging
6
+ from typing import Optional, Dict, Any
7
+ from bson import ObjectId
8
+ from app.core.nosql_client import db
9
+ from fastapi import HTTPException, status
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ class ProfileService:
14
+ """Service class for profile-related operations."""
15
+
16
+ @staticmethod
17
+ async def get_customer_profile(user_id: str) -> Dict[str, Any]:
18
+ """
19
+ Fetch customer profile from the customers collection.
20
+
21
+ Args:
22
+ user_id (str): The user ID from JWT token
23
+
24
+ Returns:
25
+ Dict[str, Any]: Customer profile data
26
+
27
+ Raises:
28
+ HTTPException: If customer not found or database error
29
+ """
30
+ try:
31
+ # Convert string ID to ObjectId if needed
32
+ if ObjectId.is_valid(user_id):
33
+ query = {"_id": ObjectId(user_id)}
34
+ else:
35
+ # If not a valid ObjectId, try searching by other fields
36
+ query = {"$or": [
37
+ {"user_id": user_id},
38
+ {"email": user_id},
39
+ {"mobile": user_id}
40
+ ]}
41
+
42
+ logger.info(f"Fetching profile for user: {user_id}")
43
+
44
+ # Fetch customer from customers collection
45
+ customer = await db.customers.find_one(query)
46
+
47
+ if not customer:
48
+ logger.warning(f"Customer not found for user_id: {user_id}")
49
+ raise HTTPException(
50
+ status_code=status.HTTP_404_NOT_FOUND,
51
+ detail="Customer profile not found"
52
+ )
53
+
54
+ # Convert ObjectId to string for JSON serialization
55
+ if "_id" in customer:
56
+ customer["_id"] = str(customer["_id"])
57
+
58
+ logger.info(f"Successfully fetched profile for user: {user_id}")
59
+ return customer
60
+
61
+ except HTTPException:
62
+ # Re-raise HTTP exceptions
63
+ raise
64
+ except Exception as e:
65
+ logger.error(f"Error fetching customer profile for user {user_id}: {str(e)}")
66
+ raise HTTPException(
67
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
68
+ detail="Internal server error while fetching profile"
69
+ )
70
+
71
+ # Create service instance
72
+ profile_service = ProfileService()
app/utils/jwt.py CHANGED
@@ -3,10 +3,16 @@
3
 
4
  from jose import jwt, JWTError
5
  from datetime import datetime, timedelta
 
 
 
 
6
 
7
- SECRET_KEY = "secret-key-placeholder"
8
  ALGORITHM = "HS256"
9
 
 
 
10
 
11
  def create_access_token(data: dict, expires_minutes: int = 60):
12
  to_encode = data.copy()
@@ -21,4 +27,36 @@ def decode_token(token: str) -> dict:
21
  try:
22
  return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
23
  except JWTError:
24
- return {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  from jose import jwt, JWTError
5
  from datetime import datetime, timedelta
6
+ from fastapi import Depends, HTTPException, status
7
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
8
+ from typing import Optional
9
+ import os
10
 
11
+ SECRET_KEY = os.getenv("JWT_SECRET_KEY", "secret-key-placeholder")
12
  ALGORITHM = "HS256"
13
 
14
+ # Security scheme
15
+ security = HTTPBearer()
16
 
17
  def create_access_token(data: dict, expires_minutes: int = 60):
18
  to_encode = data.copy()
 
27
  try:
28
  return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
29
  except JWTError:
30
+ return {}
31
+
32
+ def verify_token(token: str) -> dict:
33
+ """
34
+ Verify and decode JWT token, raise HTTPException if invalid.
35
+ """
36
+ credentials_exception = HTTPException(
37
+ status_code=status.HTTP_401_UNAUTHORIZED,
38
+ detail="Could not validate credentials",
39
+ headers={"WWW-Authenticate": "Bearer"},
40
+ )
41
+
42
+ try:
43
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
44
+ user_id: str = payload.get("sub")
45
+ if user_id is None:
46
+ raise credentials_exception
47
+ return payload
48
+ except JWTError:
49
+ raise credentials_exception
50
+
51
+ async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)) -> dict:
52
+ """
53
+ Dependency to get current authenticated user from JWT token.
54
+ """
55
+ token = credentials.credentials
56
+ return verify_token(token)
57
+
58
+ async def get_current_user_id(current_user: dict = Depends(get_current_user)) -> str:
59
+ """
60
+ Dependency to get current user ID.
61
+ """
62
+ return current_user.get("sub")