Coverage for api/account/controller.py: 100.00%
57 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-09-03 15:30 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-09-03 15:30 +0000
1from fastapi import APIRouter, Depends, HTTPException, Response
2from sqlalchemy.ext.asyncio import AsyncSession
4from core.dependencies import get_db
5from core.redis import get_redis
6from core.security import verify_token
7from extensions.smtp import SMTPMailer, get_mailer
8from utils.custom_exception import AuthenticationException, NotFoundException
9from utils.response import APIResponse, common_responses, make_error_examples, parse_responses
11from .schema import PasswordChange, UserProfile, UserUpdate
12from .services import change_password, get_user_by_id, update_user_profile
14router = APIRouter(tags=["Account"])
17def _to_user_profile(user) -> UserProfile:
18 return UserProfile(
19 id=user.id,
20 first_name=user.first_name,
21 last_name=user.last_name,
22 email=user.email,
23 pending_email=user.pending_email,
24 phone=user.phone,
25 status=user.status,
26 created_at=user.created_at,
27 )
30@router.get(
31 "/profile",
32 response_model=APIResponse[UserProfile],
33 summary="Get current user profile",
34 responses=parse_responses(
35 {200: ("User profile retrieved successfully", UserProfile)}, common_responses
36 ),
37)
38async def get_user_profile_api(
39 token: dict = Depends(verify_token), db: AsyncSession = Depends(get_db)
40):
41 """
42 Get the current authenticated user's profile information.
43 """
44 try:
45 user_id = token.get("sub")
46 user = await get_user_by_id(db, user_id)
48 if not user:
49 raise NotFoundException("User not found")
51 user_data = _to_user_profile(user)
53 return APIResponse(code=200, message="User profile retrieved successfully", data=user_data)
54 except NotFoundException:
55 raise HTTPException(status_code=404, detail="User not found")
56 except Exception:
57 raise HTTPException(status_code=500)
60@router.put(
61 "/profile",
62 response_model=APIResponse[UserProfile],
63 response_model_exclude_unset=True,
64 summary="Update current user profile",
65 responses=parse_responses(
66 {
67 200: ("User profile updated successfully", UserProfile),
68 202: ("Email verification required", UserProfile),
69 },
70 common_responses,
71 ),
72)
73async def update_user_profile_api(
74 user_update: UserUpdate,
75 response: Response,
76 token: dict = Depends(verify_token),
77 db: AsyncSession = Depends(get_db),
78 redis_client=Depends(get_redis),
79 mailer: SMTPMailer = Depends(get_mailer),
80):
81 """
82 Update the current authenticated user's profile information (excluding password).
83 """
84 try:
85 user_id = token.get("sub")
86 result = await update_user_profile(db, user_id, user_update, mailer, redis_client)
88 if not result:
89 raise NotFoundException("User not found")
91 user, email_change_requested = result
93 user_data = _to_user_profile(user)
95 if email_change_requested:
96 response.status_code = 202
97 return APIResponse(code=202, message="Email verification required", data=user_data)
99 return APIResponse(code=200, message="User profile updated successfully", data=user_data)
100 except NotFoundException:
101 raise HTTPException(status_code=404, detail="User not found")
102 except ValueError:
103 raise HTTPException(status_code=409, detail="Email already exists")
104 except Exception:
105 raise HTTPException(status_code=500)
108@router.put(
109 "/password",
110 response_model=APIResponse[None],
111 response_model_exclude_unset=True,
112 summary="Change current user password",
113 responses=parse_responses(
114 {
115 200: ("Password changed successfully", None),
116 401: (
117 "Unauthorized",
118 None,
119 make_error_examples(
120 401,
121 {
122 "invalidToken": "Invalid or expired token",
123 "incorrectPassword": "Current password is incorrect",
124 },
125 ),
126 ),
127 },
128 common_responses,
129 ),
130)
131async def change_user_password_api(
132 password_change: PasswordChange,
133 token: dict = Depends(verify_token),
134 db: AsyncSession = Depends(get_db),
135 redis_client=Depends(get_redis),
136):
137 """
138 Change the current authenticated user's password.
139 """
140 try:
141 user_id = token.get("sub")
142 current_session_id = token.get("sid")
143 success = await change_password(
144 db,
145 user_id,
146 password_change,
147 redis_client,
148 current_session_id=current_session_id,
149 )
151 if success:
152 return APIResponse(code=200, message="Password changed successfully")
154 except AuthenticationException:
155 raise HTTPException(status_code=401, detail="Current password is incorrect")
156 except Exception:
157 raise HTTPException(status_code=500)