Skip to content

Commit fc377f0

Browse files
committed
added unit tests, migration and split admin/user/participant get
1 parent 8ca76c6 commit fc377f0

File tree

6 files changed

+504
-17
lines changed

6 files changed

+504
-17
lines changed

backend/app/interfaces/user_service.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,14 @@ def delete_user_by_email(self, email):
137137
:raises Exception: if user deletion fails
138138
"""
139139
pass
140+
141+
@abstractmethod
142+
def get_admins(self):
143+
"""
144+
Get all admin users
145+
146+
:return: list of UserDTOs for admin users
147+
:rtype: [UserDTO]
148+
:raises Exception: if user retrieval fails
149+
"""
150+
pass

backend/app/routes/auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
33

44
from ..schemas.auth import AuthResponse, LoginRequest, RefreshRequest, Token
5-
from ..schemas.user import UserRole, UserCreateRequest, UserCreateResponse
5+
from ..schemas.user import UserCreateRequest, UserCreateResponse, UserRole
66
from ..services.implementations.auth_service import AuthService
77
from ..services.implementations.user_service import UserService
88
from ..utilities.service_utils import get_auth_service, get_user_service

backend/app/routes/user.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from fastapi import APIRouter, Depends, HTTPException
1+
from typing import Optional
2+
3+
from fastapi import APIRouter, Depends, HTTPException, Query
24

35
from app.middleware.auth import has_roles
46
from app.schemas.user import (
@@ -40,11 +42,17 @@ async def create_user(
4042
# admin only get all users
4143
@router.get("/", response_model=UserListResponse)
4244
async def get_users(
45+
admin: Optional[bool] = Query(
46+
False, description="If true, returns admin users only"
47+
),
4348
user_service: UserService = Depends(get_user_service),
4449
authorized: bool = has_roles([UserRole.ADMIN]),
4550
):
4651
try:
47-
users = user_service.get_users()
52+
if admin:
53+
users = await user_service.get_admins()
54+
else:
55+
users = await user_service.get_users()
4856
return UserListResponse(users=users, total=len(users))
4957
except HTTPException as http_ex:
5058
raise http_ex
@@ -60,7 +68,7 @@ async def get_user(
6068
authorized: bool = has_roles([UserRole.ADMIN]),
6169
):
6270
try:
63-
return user_service.get_user_by_id(user_id)
71+
return await user_service.get_user_by_id(user_id)
6472
except HTTPException as http_ex:
6573
raise http_ex
6674
except Exception as e:
@@ -76,7 +84,7 @@ async def update_user(
7684
authorized: bool = has_roles([UserRole.ADMIN]),
7785
):
7886
try:
79-
return user_service.update_user_by_id(user_id, user_update)
87+
return await user_service.update_user_by_id(user_id, user_update)
8088
except HTTPException as http_ex:
8189
raise http_ex
8290
except Exception as e:
@@ -91,7 +99,7 @@ async def delete_user(
9199
authorized: bool = has_roles([UserRole.ADMIN]),
92100
):
93101
try:
94-
user_service.delete_user_by_id(user_id)
102+
await user_service.delete_user_by_id(user_id)
95103
return {"message": "User deleted successfully"}
96104
except HTTPException as http_ex:
97105
raise http_ex

backend/app/services/implementations/user_service.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ async def create_user(self, user: UserCreateRequest) -> UserCreateResponse:
8282

8383
raise HTTPException(status_code=500, detail=str(e))
8484

85-
def delete_user_by_email(self, email: str):
85+
async def delete_user_by_email(self, email: str):
8686
try:
8787
db_user = self.db.query(User).filter(User.email == email).first()
8888
if not db_user:
@@ -98,7 +98,7 @@ def delete_user_by_email(self, email: str):
9898
self.logger.error(f"Error deleting user with email {email}: {str(e)}")
9999
raise HTTPException(status_code=500, detail=str(e))
100100

101-
def delete_user_by_id(self, user_id: str):
101+
async def delete_user_by_id(self, user_id: str):
102102
try:
103103
db_user = self.db.query(User).filter(User.id == UUID(user_id)).first()
104104
if not db_user:
@@ -116,7 +116,7 @@ def delete_user_by_id(self, user_id: str):
116116
self.logger.error(f"Error deleting user {user_id}: {str(e)}")
117117
raise HTTPException(status_code=500, detail=str(e))
118118

119-
def get_user_id_by_auth_id(self, auth_id: str) -> str:
119+
async def get_user_id_by_auth_id(self, auth_id: str) -> str:
120120
"""Get user ID for a user by their Firebase auth_id"""
121121
user = self.db.query(User).filter(User.auth_id == auth_id).first()
122122
if not user:
@@ -129,7 +129,7 @@ def get_user_by_email(self, email: str):
129129
raise ValueError(f"User with email {email} not found")
130130
return user
131131

132-
def get_user_by_id(self, user_id: str) -> UserResponse:
132+
async def get_user_by_id(self, user_id: str) -> UserResponse:
133133
try:
134134
user = (
135135
self.db.query(User).join(Role).filter(User.id == UUID(user_id)).first()
@@ -159,15 +159,27 @@ def get_user_role_by_auth_id(self, auth_id: str) -> str:
159159
raise ValueError(f"User with auth_id {auth_id} not found")
160160
return user.role.name
161161

162-
def get_users(self) -> List[UserResponse]:
162+
async def get_users(self) -> List[UserResponse]:
163163
try:
164-
users = self.db.query(User).join(Role).all()
164+
# Filter users to only include participants and volunteers (role_id 1 and 2)
165+
users = (
166+
self.db.query(User).join(Role).filter(User.role_id.in_([1, 2])).all()
167+
)
168+
return [UserResponse.model_validate(user) for user in users]
169+
except Exception as e:
170+
self.logger.error(f"Error getting users: {str(e)}")
171+
raise HTTPException(status_code=500, detail=str(e))
172+
173+
async def get_admins(self) -> List[UserResponse]:
174+
try:
175+
# Get only admin users (role_id 3)
176+
users = self.db.query(User).join(Role).filter(User.role_id == 3).all()
165177
return [UserResponse.model_validate(user) for user in users]
166178
except Exception as e:
167-
self.logger.error(f"Error retrieving users: {str(e)}")
179+
self.logger.error(f"Error retrieving admin users: {str(e)}")
168180
raise HTTPException(status_code=500, detail=str(e))
169181

170-
def update_user_by_id(
182+
async def update_user_by_id(
171183
self, user_id: str, user_update: UserUpdateRequest
172184
) -> UserResponse:
173185
try:
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""add approved to users
2+
3+
Revision ID: 8bfb115acac1
4+
Revises: c9bc2b4d1036
5+
Create Date: 2025-06-04 16:50:38.609239
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
import sqlalchemy as sa
12+
from alembic import op
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = "8bfb115acac1"
16+
down_revision: Union[str, None] = "c9bc2b4d1036"
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
# ### commands auto generated by Alembic - please adjust! ###
23+
op.add_column("users", sa.Column("approved", sa.Boolean(), nullable=True))
24+
# ### end Alembic commands ###
25+
26+
27+
def downgrade() -> None:
28+
# ### commands auto generated by Alembic - please adjust! ###
29+
op.drop_column("users", "approved")
30+
# ### end Alembic commands ###

0 commit comments

Comments
 (0)