Skip to content

Commit 4542f23

Browse files
Added preliminary organizations.html component, moved some helpers to utils/role_org.py, hooked it all up
1 parent a8bee02 commit 4542f23

File tree

6 files changed

+228
-158
lines changed

6 files changed

+228
-158
lines changed

main.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from utils.auth import get_authenticated_user, get_optional_user, NeedsNewTokens, get_user_from_reset_token, PasswordValidationError
1212
from utils.models import User
1313
from utils.db import get_session, set_up_db
14-
14+
from utils.role_org import get_user_organizations, get_organization_roles
1515

1616
logger = logging.getLogger("uvicorn.error")
1717
logger.setLevel(logging.DEBUG)
@@ -242,11 +242,16 @@ async def read_dashboard(
242242

243243
@app.get("/profile")
244244
async def read_profile(
245-
params: dict = Depends(common_authenticated_parameters)
245+
params: dict = Depends(common_authenticated_parameters),
246+
session: Session = Depends(get_session)
246247
):
247248
if not params["user"]:
248-
# Changed to 302
249249
return RedirectResponse(url="/login", status_code=status.HTTP_302_FOUND)
250+
251+
# Get user's organizations
252+
params["organizations"] = get_user_organizations(
253+
params["user"].id, session)
254+
250255
return templates.TemplateResponse(params["request"], "users/profile.html", params)
251256

252257

routers/organization.py

Lines changed: 5 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
from sqlmodel import Session, select
66
from utils.db import get_session
77
from utils.auth import get_authenticated_user
8-
from utils.models import Organization, User, Role, UserOrganizationLink, ValidPermissions, RolePermissionLink, Permission, utc_time
8+
from utils.models import Organization, User, Role, UserOrganizationLink, ValidPermissions, utc_time
99
from datetime import datetime
1010
from sqlalchemy import and_
11-
from typing import List
11+
from utils.role_org import get_organization, check_user_permission
1212

1313
logger = getLogger("uvicorn.error")
1414

@@ -102,128 +102,6 @@ async def as_form(cls, id: int = Form(...), name: str = Form(...)):
102102
return cls(id=id, name=name)
103103

104104

105-
# -- Helper Functions --
106-
107-
def get_user_organizations(
108-
user_id: int,
109-
session: Session,
110-
include_deleted: bool = False
111-
) -> List[Organization]:
112-
"""
113-
Retrieve all organizations a user is a member of.
114-
115-
Args:
116-
user_id: ID of the user
117-
session: Database session
118-
include_deleted: Whether to include soft-deleted organizations
119-
120-
Returns:
121-
List of Organization objects the user belongs to
122-
"""
123-
query = (
124-
select(Organization)
125-
.join(UserOrganizationLink)
126-
.where(UserOrganizationLink.user_id == user_id)
127-
)
128-
129-
if not include_deleted:
130-
query = query.where(Organization.deleted == False)
131-
132-
return list(session.exec(query))
133-
134-
135-
def get_organization(
136-
org_id: int,
137-
user_id: int,
138-
session: Session,
139-
) -> Organization:
140-
"""
141-
Retrieve a specific organization if the user is a member.
142-
143-
Args:
144-
org_id: ID of the organization
145-
user_id: ID of the user
146-
session: Database session
147-
148-
Returns:
149-
Organization object
150-
151-
Raises:
152-
OrganizationNotFoundError: If organization doesn't exist
153-
InsufficientPermissionsError: If user is not a member
154-
"""
155-
# Check if user is a member of the organization
156-
user_org = session.exec(
157-
select(UserOrganizationLink).where(
158-
and_(
159-
UserOrganizationLink.user_id == user_id,
160-
UserOrganizationLink.organization_id == org_id
161-
)
162-
)
163-
).first()
164-
165-
if not user_org:
166-
raise InsufficientPermissionsError()
167-
168-
db_org = session.get(Organization, org_id)
169-
if not db_org or db_org.deleted:
170-
raise OrganizationNotFoundError()
171-
172-
return db_org
173-
174-
175-
def check_user_permission(
176-
user_id: int,
177-
org_id: int,
178-
permission: ValidPermissions,
179-
session: Session,
180-
) -> bool:
181-
"""
182-
Check if user has the specified permission for the organization.
183-
184-
Args:
185-
user_id: ID of the user
186-
org_id: ID of the organization
187-
permission: Permission to check
188-
session: Database session
189-
190-
Returns:
191-
True if user has permission, False otherwise
192-
"""
193-
# Get user's role in the organization
194-
user_org = session.exec(
195-
select(UserOrganizationLink).where(
196-
and_(
197-
UserOrganizationLink.user_id == user_id,
198-
UserOrganizationLink.organization_id == org_id
199-
)
200-
)
201-
).first()
202-
203-
if not user_org:
204-
return False
205-
206-
# Get permission ID
207-
permission_record = session.exec(
208-
select(Permission).where(Permission.name == permission)
209-
).first()
210-
211-
if not permission_record:
212-
return False
213-
214-
# Check if role has the permission
215-
role_permission = session.exec(
216-
select(RolePermissionLink).where(
217-
and_(
218-
RolePermissionLink.role_id == user_org.role_id,
219-
RolePermissionLink.permission_id == permission_record.id
220-
)
221-
)
222-
).first()
223-
224-
return bool(role_permission)
225-
226-
227105
# -- Routes --
228106

229107
@router.post("/", response_class=RedirectResponse)
@@ -268,7 +146,7 @@ def create_organization(
268146
session.add(user_org_link)
269147
session.commit()
270148

271-
return RedirectResponse(url=f"/organizations/{db_org.id}", status_code=303)
149+
return RedirectResponse(url=f"/profile", status_code=303)
272150

273151

274152
@router.put("/{org_id}", response_class=RedirectResponse)
@@ -298,7 +176,7 @@ def update_organization(
298176
session.commit()
299177
session.refresh(organization)
300178

301-
return RedirectResponse(url=f"/organizations/{org.id}", status_code=303)
179+
return RedirectResponse(url=f"/profile", status_code=303)
302180

303181

304182
@router.delete("/{org_id}", response_class=RedirectResponse)
@@ -318,4 +196,4 @@ def delete_organization(
318196
session.add(organization)
319197
session.commit()
320198

321-
return RedirectResponse(url="/organizations", status_code=303)
199+
return RedirectResponse(url="/profile", status_code=303)

routers/role.py

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -107,31 +107,6 @@ async def as_form(
107107
)
108108

109109

110-
# -- Helper Functions --
111-
112-
def get_organization_roles(
113-
organization_id: int,
114-
session: Session,
115-
include_deleted: bool = False
116-
) -> List[Role]:
117-
"""
118-
Retrieve all roles for an organization.
119-
120-
Args:
121-
organization_id: ID of the organization
122-
session: Database session
123-
include_deleted: Whether to include soft-deleted roles
124-
125-
Returns:
126-
List of Role objects with their associated permissions
127-
"""
128-
query = select(Role).where(Role.organization_id == organization_id)
129-
if not include_deleted:
130-
query = query.where(Role.deleted == False)
131-
132-
return list(session.exec(query))
133-
134-
135110
# -- Routes --
136111

137112

@@ -156,7 +131,7 @@ def create_role(
156131
session.add(db_role)
157132
session.commit()
158133

159-
return RedirectResponse(url="/roles", status_code=303)
134+
return RedirectResponse(url="/profile", status_code=303)
160135

161136

162137
@router.put("/{role_id}", response_class=RedirectResponse)
@@ -186,7 +161,7 @@ def update_role(
186161

187162
session.commit()
188163
session.refresh(db_role)
189-
return RedirectResponse(url=f"/roles/{role.id}", status_code=303)
164+
return RedirectResponse(url="/profile", status_code=303)
190165

191166

192167
@router.delete("/{role_id}", response_class=RedirectResponse)
@@ -203,4 +178,4 @@ def delete_role(
203178
db_role.updated_at = utc_time()
204179
session.add(db_role)
205180
session.commit()
206-
return RedirectResponse(url="/roles", status_code=303)
181+
return RedirectResponse(url="/profile", status_code=303)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{% macro render_organizations(organizations) %}
2+
<div class="card mb-4">
3+
<div class="card-header d-flex justify-content-between align-items-center">
4+
Organizations
5+
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="collapse" data-bs-target="#newOrgForm">
6+
Create Organization
7+
</button>
8+
</div>
9+
<div class="card-body">
10+
<!-- New Organization Form -->
11+
<div class="collapse mb-3" id="newOrgForm">
12+
<form action="/organizations/" method="post" class="border rounded p-3 bg-light">
13+
<div class="mb-3">
14+
<label for="name" class="form-label">Organization Name</label>
15+
<input type="text"
16+
class="form-control"
17+
id="name"
18+
name="name"
19+
required
20+
pattern=".{3,}"
21+
title="Organization name must be at least 3 characters long">
22+
</div>
23+
<button type="submit" class="btn btn-primary">Create</button>
24+
</form>
25+
</div>
26+
27+
<!-- Existing Organizations List -->
28+
{% if organizations %}
29+
<div class="list-group">
30+
{% for org in organizations %}
31+
<a href="/organizations/{{ org.id }}" class="list-group-item list-group-item-action">
32+
<div class="d-flex w-100 justify-content-between">
33+
<h5 class="mb-1">{{ org.name }}</h5>
34+
<small>Joined {{ org.created_at.strftime('%Y-%m-%d') }}</small>
35+
</div>
36+
</a>
37+
{% endfor %}
38+
</div>
39+
{% else %}
40+
<p class="text-muted">You are not a member of any organizations.</p>
41+
{% endif %}
42+
</div>
43+
</div>
44+
{% endmacro %}

templates/users/profile.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{% extends "base.html" %}
22
{% from 'components/silhouette.html' import render_silhouette %}
3+
{% from 'components/organizations.html' import render_organizations %}
34

45
{% block title %}Profile{% endblock %}
56

@@ -67,6 +68,9 @@ <h1 class="mb-4">User Profile</h1>
6768
</div>
6869
</div>
6970

71+
<!-- Organizations Section - Add this before the Delete Account section -->
72+
{{ render_organizations(organizations) }}
73+
7074
<!-- Delete Account -->
7175
<div class="card mb-4">
7276
<div class="card-header">

0 commit comments

Comments
 (0)