Skip to content

Commit d5c8e23

Browse files
Move private helper to shared utils and add custom exception message
1 parent 8243f05 commit d5c8e23

5 files changed

Lines changed: 73 additions & 68 deletions

File tree

exceptions/http_exceptions.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,11 @@ def __init__(self, field: str, message: str):
4343

4444

4545
class InsufficientPermissionsError(HTTPException):
46-
def __init__(self):
47-
super().__init__(
48-
status_code=403, detail="You don't have permission to perform this action"
49-
)
46+
def __init__(
47+
self,
48+
message: str = "You don't have permission to perform this action",
49+
):
50+
super().__init__(status_code=403, detail=message)
5051

5152

5253
class OrganizationSetupError(HTTPException):

routers/core/invitation.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,9 @@
3030
)
3131
from exceptions.exceptions import EmailSendFailedError
3232
from utils.core.htmx import is_htmx_request, append_toast
33-
34-
# Import the account router to generate URLs for login/register
33+
from utils.core.organizations import load_org_for_members_partial
3534
from routers.core.account import router as account_router
36-
from routers.core.organization import (
37-
router as org_router,
38-
) # Already imported, check usage
39-
from routers.core.user import _load_org_for_members_partial
35+
from routers.core.organization import router as org_router
4036

4137
# Setup logger
4238
logger = getLogger("uvicorn.error")
@@ -85,7 +81,9 @@ async def create_invitation(
8581

8682
# Check if the current user has permission to invite users to this organization
8783
if not current_user.has_permission(ValidPermissions.INVITE_USER, organization):
88-
raise InsufficientPermissionsError()
84+
raise InsufficientPermissionsError(
85+
"You don't have permission to invite users to this organization"
86+
)
8987

9088
# Verify the role exists and belongs to this organization
9189
role = session.get(Role, role_id)
@@ -164,7 +162,7 @@ async def create_invitation(
164162
# HTMX: return partial; non-HTMX: PRG redirect
165163
if is_htmx_request(request):
166164
organization, user_permissions, active_invitations = (
167-
_load_org_for_members_partial(session, organization_id, current_user)
165+
load_org_for_members_partial(session, organization_id, current_user)
168166
)
169167
response = templates.TemplateResponse(
170168
request,
@@ -204,7 +202,9 @@ async def delete_invitation(
204202
raise OrganizationNotFoundError()
205203

206204
if not current_user.has_permission(ValidPermissions.INVITE_USER, organization):
207-
raise InsufficientPermissionsError()
205+
raise InsufficientPermissionsError(
206+
"You don't have permission to cancel invitations for this organization"
207+
)
208208

209209
invitation = session.get(Invitation, invitation_id)
210210
if not invitation or invitation.organization_id != organization_id:
@@ -215,7 +215,7 @@ async def delete_invitation(
215215

216216
if is_htmx_request(request):
217217
organization, user_permissions, active_invitations = (
218-
_load_org_for_members_partial(session, organization_id, current_user)
218+
load_org_for_members_partial(session, organization_id, current_user)
219219
)
220220
response = templates.TemplateResponse(
221221
request,

routers/core/role.py

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
utc_now,
1616
User,
1717
DataIntegrityError,
18-
Organization,
1918
)
19+
from utils.core.organizations import load_org_for_roles_partial
2020
from utils.core.enums import ValidPermissions
2121
from utils.app.enums import AppPermissions
2222
from exceptions.http_exceptions import (
@@ -36,26 +36,6 @@
3636
templates = Jinja2Templates(directory="templates")
3737

3838

39-
def _load_org_for_roles_partial(
40-
session: Session, organization_id: int, user: User
41-
) -> tuple:
42-
"""Re-query org with roles/users/permissions and compute user_permissions."""
43-
organization = session.exec(
44-
select(Organization)
45-
.where(Organization.id == organization_id)
46-
.options(
47-
selectinload(Organization.roles).selectinload(Role.users),
48-
selectinload(Organization.roles).selectinload(Role.permissions),
49-
)
50-
).first()
51-
user_permissions = set()
52-
for role in user.roles:
53-
if role.organization_id == organization_id:
54-
for permission in role.permissions:
55-
user_permissions.add(permission.name)
56-
return organization, user_permissions
57-
58-
5939
# --- Routes ---
6040

6141

@@ -114,7 +94,7 @@ def create_role(
11494
raise RoleAlreadyExistsError()
11595

11696
if is_htmx_request(request):
117-
organization, user_permissions = _load_org_for_roles_partial(
97+
organization, user_permissions = load_org_for_roles_partial(
11898
session, organization_id, user
11999
)
120100
response = templates.TemplateResponse(
@@ -221,7 +201,7 @@ def update_role(
221201
session.refresh(db_role)
222202

223203
if is_htmx_request(request):
224-
organization, user_permissions = _load_org_for_roles_partial(
204+
organization, user_permissions = load_org_for_roles_partial(
225205
session, organization_id, user
226206
)
227207
response = templates.TemplateResponse(
@@ -282,7 +262,7 @@ def delete_role(
282262
session.commit()
283263

284264
if is_htmx_request(request):
285-
organization, user_permissions = _load_org_for_roles_partial(
265+
organization, user_permissions = load_org_for_roles_partial(
286266
session, organization_id, user
287267
)
288268
response = templates.TemplateResponse(

routers/core/user.py

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@
1010
AccountEmail,
1111
DataIntegrityError,
1212
Organization,
13-
Role,
14-
Invitation,
1513
)
14+
from utils.core.organizations import load_org_for_members_partial
1615
from utils.core.auth import MAX_EMAILS_PER_ACCOUNT
1716
from utils.core.dependencies import (
1817
get_authenticated_user,
@@ -40,32 +39,6 @@
4039
templates = Jinja2Templates(directory="templates")
4140

4241

43-
def _load_org_for_members_partial(
44-
session: Session, organization_id: int, user: User
45-
) -> tuple:
46-
"""Re-query org with members fully loaded and compute user_permissions."""
47-
organization = session.exec(
48-
select(Organization)
49-
.where(Organization.id == organization_id)
50-
.options(
51-
selectinload(Organization.roles)
52-
.selectinload(Role.users)
53-
.selectinload(User.account),
54-
selectinload(Organization.roles)
55-
.selectinload(Role.users)
56-
.selectinload(User.roles),
57-
selectinload(Organization.roles).selectinload(Role.permissions),
58-
)
59-
).first()
60-
user_permissions = set()
61-
for role in user.roles:
62-
if role.organization_id == organization_id:
63-
for permission in role.permissions:
64-
user_permissions.add(permission.name)
65-
active_invitations = Invitation.get_active_for_org(session, organization_id)
66-
return organization, user_permissions, active_invitations
67-
68-
6942
# --- Routes ---
7043

7144

@@ -267,7 +240,7 @@ def update_user_role(
267240

268241
if is_htmx_request(request):
269242
organization, user_permissions, active_invitations = (
270-
_load_org_for_members_partial(session, organization_id, user)
243+
load_org_for_members_partial(session, organization_id, user)
271244
)
272245
response = templates.TemplateResponse(
273246
request,
@@ -341,7 +314,7 @@ def remove_user_from_organization(
341314

342315
if is_htmx_request(request):
343316
organization, user_permissions, active_invitations = (
344-
_load_org_for_members_partial(session, organization_id, user)
317+
load_org_for_members_partial(session, organization_id, user)
345318
)
346319
response = templates.TemplateResponse(
347320
request,

utils/core/organizations.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from sqlmodel import Session, select
2+
from sqlalchemy.orm import selectinload
3+
4+
from utils.core.models import Organization, Role, User, Invitation
5+
6+
7+
def _user_permissions_for_org(user: User, organization_id: int) -> set[str]:
8+
user_permissions: set[str] = set()
9+
for role in user.roles:
10+
if role.organization_id == organization_id:
11+
for permission in role.permissions:
12+
user_permissions.add(permission.name)
13+
return user_permissions
14+
15+
16+
def load_org_for_members_partial(
17+
session: Session, organization_id: int, user: User
18+
) -> tuple[Organization | None, set[str], list[Invitation]]:
19+
"""Re-query org with members fully loaded and compute user_permissions."""
20+
organization = session.exec(
21+
select(Organization)
22+
.where(Organization.id == organization_id)
23+
.options(
24+
selectinload(Organization.roles)
25+
.selectinload(Role.users)
26+
.selectinload(User.account),
27+
selectinload(Organization.roles)
28+
.selectinload(Role.users)
29+
.selectinload(User.roles),
30+
selectinload(Organization.roles).selectinload(Role.permissions),
31+
)
32+
).first()
33+
user_permissions = _user_permissions_for_org(user, organization_id)
34+
active_invitations = Invitation.get_active_for_org(session, organization_id)
35+
return organization, user_permissions, active_invitations
36+
37+
38+
def load_org_for_roles_partial(
39+
session: Session, organization_id: int, user: User
40+
) -> tuple[Organization | None, set[str]]:
41+
"""Re-query org with roles/users/permissions and compute user_permissions."""
42+
organization = session.exec(
43+
select(Organization)
44+
.where(Organization.id == organization_id)
45+
.options(
46+
selectinload(Organization.roles).selectinload(Role.users),
47+
selectinload(Organization.roles).selectinload(Role.permissions),
48+
)
49+
).first()
50+
user_permissions = _user_permissions_for_org(user, organization_id)
51+
return organization, user_permissions

0 commit comments

Comments
 (0)