Skip to content

Commit 49f55ad

Browse files
Fix some redirect paths broken by our refactor
1 parent 757b205 commit 49f55ad

File tree

13 files changed

+143
-74
lines changed

13 files changed

+143
-74
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ Make sure the development database is running and tables and default
164164
permissions/roles are created first.
165165

166166
``` bash
167-
uv run uvicorn main:app --host 0.0.0.0 --port 8000 --reload
167+
uv run python -m uvicorn main:app --host 0.0.0.0 --port 8000 --reload
168168
```
169169

170170
Navigate to http://localhost:8000/

docs/static/documentation.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ docker compose up -d
129129
Make sure the development database is running and tables and default permissions/roles are created first.
130130

131131
``` bash
132-
uv run uvicorn main:app --host 0.0.0.0 --port 8000 --reload
132+
uv run python -m uvicorn main:app --host 0.0.0.0 --port 8000 --reload
133133
```
134134

135135
Navigate to http://localhost:8000/

index.qmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ docker compose up -d
131131
Make sure the development database is running and tables and default permissions/roles are created first.
132132

133133
``` bash
134-
uv run uvicorn main:app --host 0.0.0.0 --port 8000 --reload
134+
uv run python -m uvicorn main:app --host 0.0.0.0 --port 8000 --reload
135135
```
136136

137137
Navigate to http://localhost:8000/

routers/account.py

Lines changed: 60 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from fastapi import APIRouter, Depends, BackgroundTasks, Form, Request
66
from fastapi.responses import RedirectResponse
77
from fastapi.templating import Jinja2Templates
8+
from starlette.datastructures import URLPath
89
from pydantic import EmailStr
910
from sqlmodel import Session, select
1011
from utils.models import User, DataIntegrityError, Account
@@ -33,7 +34,8 @@
3334
CredentialsError,
3435
PasswordValidationError
3536
)
36-
37+
from routers.dashboard import router as dashboard_router
38+
from routers.user import router as user_router
3739
logger = getLogger("uvicorn.error")
3840

3941
router = APIRouter(prefix="/account", tags=["account"])
@@ -80,34 +82,15 @@ def validate_password_strength_and_match(
8082
# --- Routes ---
8183

8284

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():
9087
"""
91-
Delete a user account after verifying credentials.
88+
Log out a user by clearing their cookies.
9289
"""
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
11194

11295

11396
@router.get("/login")
@@ -120,7 +103,7 @@ async def read_login(
120103
Render login page or redirect to dashboard if already logged in.
121104
"""
122105
if user:
123-
return RedirectResponse(url="/dashboard", status_code=302)
106+
return RedirectResponse(url=dashboard_router.url_path_for("read_dashboard"), status_code=302)
124107
return templates.TemplateResponse(
125108
"account/login.html",
126109
{"request": request, "user": user, "email_updated": email_updated}
@@ -136,7 +119,7 @@ async def read_register(
136119
Render registration page or redirect to dashboard if already logged in.
137120
"""
138121
if user:
139-
return RedirectResponse(url="/dashboard", status_code=302)
122+
return RedirectResponse(url=dashboard_router.url_path_for("read_dashboard"), status_code=302)
140123

141124
return templates.TemplateResponse(
142125
"account/register.html",
@@ -154,7 +137,7 @@ async def read_forgot_password(
154137
Render forgot password page or redirect to dashboard if already logged in.
155138
"""
156139
if user:
157-
return RedirectResponse(url="/dashboard", status_code=302)
140+
return RedirectResponse(url=dashboard_router.url_path_for("read_dashboard"), status_code=302)
158141

159142
return templates.TemplateResponse(
160143
"account/forgot_password.html",
@@ -185,6 +168,36 @@ async def read_reset_password(
185168
)
186169

187170

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+
188201
@router.post("/register", response_class=RedirectResponse)
189202
async def register(
190203
name: str = Form(...),
@@ -222,7 +235,7 @@ async def register(
222235
refresh_token = create_refresh_token(data={"sub": email})
223236

224237
# Set cookie
225-
response = RedirectResponse(url="/", status_code=303)
238+
response = RedirectResponse(url=dashboard_router.url_path_for("read_dashboard"), status_code=303)
226239
response.set_cookie(
227240
key="access_token",
228241
value=access_token,
@@ -257,7 +270,7 @@ async def login(
257270
refresh_token = create_refresh_token(data={"sub": account.email})
258271

259272
# Set cookie
260-
response = RedirectResponse(url="/", status_code=303)
273+
response = RedirectResponse(url=dashboard_router.url_path_for("read_dashboard"), status_code=303)
261274
response.set_cookie(
262275
key="access_token",
263276
value=access_token,
@@ -287,11 +300,11 @@ async def refresh_token(
287300
"""
288301
_, refresh_token = tokens
289302
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)
291304

292305
decoded_token = validate_token(refresh_token, token_type="refresh")
293306
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)
295308
response.delete_cookie("access_token")
296309
response.delete_cookie("refresh_token")
297310
return response
@@ -300,14 +313,14 @@ async def refresh_token(
300313
account = session.exec(select(Account).where(
301314
Account.email == user_email)).one_or_none()
302315
if not account:
303-
return RedirectResponse(url="/login", status_code=303)
316+
return RedirectResponse(url=router.url_path_for("read_login"), status_code=303)
304317

305318
new_access_token = create_access_token(
306319
data={"sub": account.email, "fresh": False}
307320
)
308321
new_refresh_token = create_refresh_token(data={"sub": account.email})
309322

310-
response = RedirectResponse(url="/", status_code=303)
323+
response = RedirectResponse(url=dashboard_router.url_path_for("read_dashboard"), status_code=303)
311324
response.set_cookie(
312325
key="access_token",
313326
value=new_access_token,
@@ -379,18 +392,7 @@ async def reset_password(
379392
session.commit()
380393
session.refresh(authorized_account)
381394

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)
394396

395397

396398
@router.post("/update_email")
@@ -429,8 +431,12 @@ async def request_email_update(
429431
session=session
430432
)
431433

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+
432438
return RedirectResponse(
433-
url="/profile?email_update_requested=true",
439+
url=redirect_url,
434440
status_code=303
435441
)
436442

@@ -461,9 +467,13 @@ async def confirm_email_update(
461467
access_token = create_access_token(data={"sub": new_email, "fresh": True})
462468
refresh_token = create_refresh_token(data={"sub": new_email})
463469

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+
464474
# Set cookies before redirecting
465475
response = RedirectResponse(
466-
url="/profile?email_updated=true",
476+
url=redirect_url,
467477
status_code=303
468478
)
469479

routers/organization.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from fastapi.templating import Jinja2Templates
66
from sqlmodel import Session, select
77
from sqlalchemy.orm import selectinload
8-
from utils.db import get_session, default_roles, create_default_roles
8+
from utils.db import get_session, create_default_roles
99
from utils.dependencies import get_authenticated_user, get_user_with_relations
1010
from utils.models import Organization, User, Role, Account, utc_time
1111
from utils.enums import ValidPermissions
@@ -138,7 +138,10 @@ def create_organization(
138138

139139
session.refresh(db_org) # Refresh again to be safe before redirect
140140

141-
return RedirectResponse(url=f"/organizations/{db_org.id}", status_code=303)
141+
return RedirectResponse(
142+
url=router.url_path_for("read_organization", org_id=db_org.id),
143+
status_code=303
144+
)
142145

143146

144147
@router.post("/update/{org_id}", response_class=RedirectResponse)
@@ -177,7 +180,7 @@ def update_organization(
177180
session.add(organization)
178181
session.commit()
179182

180-
return RedirectResponse(url=f"/profile", status_code=303)
183+
return RedirectResponse(url=router.url_path_for("read_organization", org_id=org_id), status_code=303)
181184

182185

183186
@router.post("/delete/{org_id}", response_class=RedirectResponse)
@@ -200,7 +203,7 @@ def delete_organization(
200203
session.delete(organization)
201204
session.commit()
202205

203-
return RedirectResponse(url="/profile", status_code=303)
206+
return RedirectResponse(url="/user/profile", status_code=303)
204207

205208

206209
@router.post("/invite/{org_id}", response_class=RedirectResponse)
@@ -278,6 +281,6 @@ def invite_member(
278281

279282
# Return to the organization page
280283
return RedirectResponse(
281-
url=f"/organizations/{org_id}",
284+
url=router.url_path_for("read_organization", org_id=org_id),
282285
status_code=303
283286
)

routers/role.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from utils.dependencies import get_authenticated_user
1111
from utils.models import Role, Permission, ValidPermissions, utc_time, User, DataIntegrityError
1212
from exceptions.http_exceptions import InsufficientPermissionsError, InvalidPermissionError, RoleAlreadyExistsError, RoleNotFoundError, RoleHasUsersError
13+
from routers.organization import router as organization_router
1314

1415
logger = getLogger("uvicorn.error")
1516

@@ -55,7 +56,10 @@ def create_role(
5556
# Commit transaction
5657
session.commit()
5758

58-
return RedirectResponse(url=f"/organizations/{organization_id}", status_code=303)
59+
return RedirectResponse(
60+
url=organization_router.url_path_for("read_organization", org_id=organization_id),
61+
status_code=303
62+
)
5963

6064

6165
@router.post("/update", response_class=RedirectResponse)
@@ -117,7 +121,10 @@ def update_role(
117121

118122
session.commit()
119123
session.refresh(db_role)
120-
return RedirectResponse(url=f"/organizations/{organization_id}", status_code=303)
124+
return RedirectResponse(
125+
url=organization_router.url_path_for("read_organization", org_id=organization_id),
126+
status_code=303
127+
)
121128

122129

123130
@router.post("/delete", response_class=RedirectResponse)
@@ -149,4 +156,7 @@ def delete_role(
149156
session.delete(db_role)
150157
session.commit()
151158

152-
return RedirectResponse(url=f"/organizations/{organization_id}", status_code=303)
159+
return RedirectResponse(
160+
url=organization_router.url_path_for("read_organization", org_id=organization_id),
161+
status_code=303
162+
)

routers/user.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import Optional, List
55
from fastapi.templating import Jinja2Templates
66
from sqlalchemy.orm import selectinload
7-
from utils.models import User, DataIntegrityError, Role, Organization
7+
from utils.models import User, DataIntegrityError, Organization
88
from utils.db import get_session
99
from utils.dependencies import get_authenticated_user, get_user_with_relations
1010
from utils.images import validate_and_process_image, MAX_FILE_SIZE, MIN_DIMENSION, MAX_DIMENSION, ALLOWED_CONTENT_TYPES
@@ -14,6 +14,7 @@
1414
UserNotFoundError,
1515
OrganizationNotFoundError
1616
)
17+
from routers.organization import router as organization_router
1718

1819
router = APIRouter(prefix="/user", tags=["user"])
1920
templates = Jinja2Templates(directory="templates")
@@ -138,7 +139,7 @@ def update_user_role(
138139
session.commit()
139140

140141
return RedirectResponse(
141-
url=f"/organizations/{organization_id}",
142+
url=organization_router.url_path_for("read_organization", org_id=organization_id),
142143
status_code=303
143144
)
144145

@@ -189,6 +190,6 @@ def remove_user_from_organization(
189190
session.commit()
190191

191192
return RedirectResponse(
192-
url=f"/organizations/{organization_id}",
193+
url=organization_router.url_path_for("read_organization", org_id=organization_id),
193194
status_code=303
194195
)

templates/account/forgot_password.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{% extends "auth_base.html" %}
1+
{% extends "account/auth_base.html" %}
22

33
{% block title %}Forgot Password{% endblock %}
44

templates/account/login.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{% extends "auth_base.html" %}
1+
{% extends "account/auth_base.html" %}
22

33
{% block title %}Login{% endblock %}
44

templates/account/register.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{% extends "auth_base.html" %}
1+
{% extends "account/auth_base.html" %}
22

33
{% block title %}Register{% endblock %}
44

0 commit comments

Comments
 (0)