5
5
from fastapi import APIRouter , Depends , BackgroundTasks , Form , Request
6
6
from fastapi .responses import RedirectResponse
7
7
from fastapi .templating import Jinja2Templates
8
+ from starlette .datastructures import URLPath
8
9
from pydantic import EmailStr
9
10
from sqlmodel import Session , select
10
11
from utils .models import User , DataIntegrityError , Account
33
34
CredentialsError ,
34
35
PasswordValidationError
35
36
)
36
-
37
+ from routers .dashboard import router as dashboard_router
38
+ from routers .user import router as user_router
37
39
logger = getLogger ("uvicorn.error" )
38
40
39
41
router = APIRouter (prefix = "/account" , tags = ["account" ])
@@ -80,34 +82,15 @@ def validate_password_strength_and_match(
80
82
# --- Routes ---
81
83
82
84
83
- @router .post ("/delete" , response_class = RedirectResponse )
84
- async def delete_account (
85
- email : EmailStr = Form (...),
86
- password : str = Form (...),
87
- account : Account = Depends (get_authenticated_account ),
88
- session : Session = Depends (get_session )
89
- ):
85
+ @router .get ("/logout" , response_class = RedirectResponse )
86
+ def logout ():
90
87
"""
91
- Delete a user account after verifying credentials .
88
+ Log out a user by clearing their cookies .
92
89
"""
93
- # Verify the provided email matches the authenticated user
94
- if email != account .email :
95
- raise CredentialsError (message = "Email does not match authenticated account" )
96
-
97
- # Verify password
98
- if not verify_password (password , account .hashed_password ):
99
- raise PasswordValidationError (
100
- field = "password" ,
101
- message = "Password is incorrect"
102
- )
103
-
104
- # Delete the account and associated user
105
- # Note: The user will be deleted automatically by cascade relationship
106
- session .delete (account )
107
- session .commit ()
108
-
109
- # Log out the user
110
- return RedirectResponse (url = "/account/logout" , status_code = 303 )
90
+ response = RedirectResponse (url = "/" , status_code = 303 )
91
+ response .delete_cookie ("access_token" )
92
+ response .delete_cookie ("refresh_token" )
93
+ return response
111
94
112
95
113
96
@router .get ("/login" )
@@ -120,7 +103,7 @@ async def read_login(
120
103
Render login page or redirect to dashboard if already logged in.
121
104
"""
122
105
if user :
123
- return RedirectResponse (url = "/dashboard" , status_code = 302 )
106
+ return RedirectResponse (url = dashboard_router . url_path_for ( "read_dashboard" ) , status_code = 302 )
124
107
return templates .TemplateResponse (
125
108
"account/login.html" ,
126
109
{"request" : request , "user" : user , "email_updated" : email_updated }
@@ -136,7 +119,7 @@ async def read_register(
136
119
Render registration page or redirect to dashboard if already logged in.
137
120
"""
138
121
if user :
139
- return RedirectResponse (url = "/dashboard" , status_code = 302 )
122
+ return RedirectResponse (url = dashboard_router . url_path_for ( "read_dashboard" ) , status_code = 302 )
140
123
141
124
return templates .TemplateResponse (
142
125
"account/register.html" ,
@@ -154,7 +137,7 @@ async def read_forgot_password(
154
137
Render forgot password page or redirect to dashboard if already logged in.
155
138
"""
156
139
if user :
157
- return RedirectResponse (url = "/dashboard" , status_code = 302 )
140
+ return RedirectResponse (url = dashboard_router . url_path_for ( "read_dashboard" ) , status_code = 302 )
158
141
159
142
return templates .TemplateResponse (
160
143
"account/forgot_password.html" ,
@@ -185,6 +168,36 @@ async def read_reset_password(
185
168
)
186
169
187
170
171
+ @router .post ("/delete" , response_class = RedirectResponse )
172
+ async def delete_account (
173
+ email : EmailStr = Form (...),
174
+ password : str = Form (...),
175
+ account : Account = Depends (get_authenticated_account ),
176
+ session : Session = Depends (get_session )
177
+ ):
178
+ """
179
+ Delete a user account after verifying credentials.
180
+ """
181
+ # Verify the provided email matches the authenticated user
182
+ if email != account .email :
183
+ raise CredentialsError (message = "Email does not match authenticated account" )
184
+
185
+ # Verify password
186
+ if not verify_password (password , account .hashed_password ):
187
+ raise PasswordValidationError (
188
+ field = "password" ,
189
+ message = "Password is incorrect"
190
+ )
191
+
192
+ # Delete the account and associated user
193
+ # Note: The user will be deleted automatically by cascade relationship
194
+ session .delete (account )
195
+ session .commit ()
196
+
197
+ # Log out the user
198
+ return RedirectResponse (url = router .url_path_for ("logout" ), status_code = 303 )
199
+
200
+
188
201
@router .post ("/register" , response_class = RedirectResponse )
189
202
async def register (
190
203
name : str = Form (...),
@@ -222,7 +235,7 @@ async def register(
222
235
refresh_token = create_refresh_token (data = {"sub" : email })
223
236
224
237
# Set cookie
225
- response = RedirectResponse (url = "/" , status_code = 303 )
238
+ response = RedirectResponse (url = dashboard_router . url_path_for ( "read_dashboard" ) , status_code = 303 )
226
239
response .set_cookie (
227
240
key = "access_token" ,
228
241
value = access_token ,
@@ -257,7 +270,7 @@ async def login(
257
270
refresh_token = create_refresh_token (data = {"sub" : account .email })
258
271
259
272
# Set cookie
260
- response = RedirectResponse (url = "/" , status_code = 303 )
273
+ response = RedirectResponse (url = dashboard_router . url_path_for ( "read_dashboard" ) , status_code = 303 )
261
274
response .set_cookie (
262
275
key = "access_token" ,
263
276
value = access_token ,
@@ -287,11 +300,11 @@ async def refresh_token(
287
300
"""
288
301
_ , refresh_token = tokens
289
302
if not refresh_token :
290
- return RedirectResponse (url = "/login" , status_code = 303 )
303
+ return RedirectResponse (url = router . url_path_for ( "read_login" ) , status_code = 303 )
291
304
292
305
decoded_token = validate_token (refresh_token , token_type = "refresh" )
293
306
if not decoded_token :
294
- response = RedirectResponse (url = "/login" , status_code = 303 )
307
+ response = RedirectResponse (url = router . url_path_for ( "read_login" ) , status_code = 303 )
295
308
response .delete_cookie ("access_token" )
296
309
response .delete_cookie ("refresh_token" )
297
310
return response
@@ -300,14 +313,14 @@ async def refresh_token(
300
313
account = session .exec (select (Account ).where (
301
314
Account .email == user_email )).one_or_none ()
302
315
if not account :
303
- return RedirectResponse (url = "/login" , status_code = 303 )
316
+ return RedirectResponse (url = router . url_path_for ( "read_login" ) , status_code = 303 )
304
317
305
318
new_access_token = create_access_token (
306
319
data = {"sub" : account .email , "fresh" : False }
307
320
)
308
321
new_refresh_token = create_refresh_token (data = {"sub" : account .email })
309
322
310
- response = RedirectResponse (url = "/" , status_code = 303 )
323
+ response = RedirectResponse (url = dashboard_router . url_path_for ( "read_dashboard" ) , status_code = 303 )
311
324
response .set_cookie (
312
325
key = "access_token" ,
313
326
value = new_access_token ,
@@ -379,18 +392,7 @@ async def reset_password(
379
392
session .commit ()
380
393
session .refresh (authorized_account )
381
394
382
- return RedirectResponse (url = "/login" , status_code = 303 )
383
-
384
-
385
- @router .get ("/logout" , response_class = RedirectResponse )
386
- def logout ():
387
- """
388
- Log out a user by clearing their cookies.
389
- """
390
- response = RedirectResponse (url = "/" , status_code = 303 )
391
- response .delete_cookie ("access_token" )
392
- response .delete_cookie ("refresh_token" )
393
- return response
395
+ return RedirectResponse (url = router .url_path_for ("read_login" ), status_code = 303 )
394
396
395
397
396
398
@router .post ("/update_email" )
@@ -429,8 +431,12 @@ async def request_email_update(
429
431
session = session
430
432
)
431
433
434
+ # Generate URL with query parameters separately
435
+ profile_path : URLPath = user_router .url_path_for ("read_profile" )
436
+ redirect_url = f"{ profile_path } ?email_update_requested=true"
437
+
432
438
return RedirectResponse (
433
- url = "/profile?email_update_requested=true" ,
439
+ url = redirect_url ,
434
440
status_code = 303
435
441
)
436
442
@@ -461,9 +467,13 @@ async def confirm_email_update(
461
467
access_token = create_access_token (data = {"sub" : new_email , "fresh" : True })
462
468
refresh_token = create_refresh_token (data = {"sub" : new_email })
463
469
470
+ # Generate URL with query parameters separately
471
+ profile_path : URLPath = user_router .url_path_for ("read_profile" )
472
+ redirect_url = f"{ profile_path } ?email_updated=true"
473
+
464
474
# Set cookies before redirecting
465
475
response = RedirectResponse (
466
- url = "/profile?email_updated=true" ,
476
+ url = redirect_url ,
467
477
status_code = 303
468
478
)
469
479
0 commit comments