Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/adapters/inmemory/repositories/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,10 @@ def find_by_owner_id(self, owner_id: UUID) -> Optional[List[DomainGroup]]:

def find_all_groups(self) -> Optional[List[DomainGroup]]:
return list(self._groups.values())

def find_groups_by_member_id(self, user_id: UUID) -> Optional[List[DomainGroup]]:
groups = []
for group_id, member_ids in self._members.items():
if user_id in member_ids:
groups.append(self._groups[group_id])
return groups if groups else []
4 changes: 4 additions & 0 deletions src/adapters/sqlalchemy/repositories/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,8 @@ def find_by_owner_id(self, owner_id: UUID) -> Optional[List[DomainGroup]]:

def find_all_groups(self) -> Optional[List[DomainGroup]]:
orms = self._session.query(ORMGroup).all()
return [group_from_orm(orm) for orm in orms] if orms else []

def find_groups_by_member_id(self, user_id: UUID) -> Optional[List[DomainGroup]]:
orms = self._session.query(ORMGroup).join(group_users).filter(group_users.c.profile_id == user_id).all()
return [group_from_orm(orm) for orm in orms] if orms else []
4 changes: 4 additions & 0 deletions src/domain/ports/group_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@ def list_members(self, group_id: UUID) -> Optional[list[UUID]]:

@abstractmethod
def find_all_groups(self) -> Optional[list[Group]]:
pass

@abstractmethod
def find_groups_by_member_id(self, user_id: UUID) -> Optional[list[Group]]:
pass
28 changes: 27 additions & 1 deletion src/domain/services/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,30 @@ def get_all_groups(self) -> List[DomainGroup]:
if not groups:
raise NotFoundError("No groups found")

return groups
return groups

def get_my_coaches(self, user_id: UUID) -> List[DomainProfile]:
groups = self._repo.find_groups_by_member_id(user_id)
if not groups:
raise NotFoundError(f"No groups found for user {user_id}")

coach_ids = set()
for group in groups:
coach_ids.add(group.owner_id)

from src.container import container
profile_service = container.get_profile_service()

coaches = []
for coach_id in coach_ids:
try:
coach = profile_service._repo.find_by_id(coach_id)
if coach and "coach" in coach.roles:
coaches.append(coach)
except NotFoundError:
continue

if not coaches:
raise NotFoundError("No coaches found in your groups")

return coaches
12 changes: 12 additions & 0 deletions src/entrypoints/api/routers/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from src.entrypoints.api.deps.auth import require_group_owner_or_admin, get_current_user
from src.entrypoints.api.deps.roles import require_roles
from src.entrypoints.api.schemas.group import GroupCreate, GroupUpdate, GroupRead, GroupMember
from src.entrypoints.api.schemas.profile import CoachProfileRead
from src.container import container


Expand Down Expand Up @@ -103,4 +104,15 @@ def leave_group(group_id: UUID, user=Depends(get_current_user)):
service.remove_member(group_id, UUID(user["sub"]))
except NotFoundError as e:
raise HTTPException(404, str(e))

@router.get("/coachs/mine", response_model=List[CoachProfileRead])
def get_my_coaches(user=Depends(get_current_user)):
"""Get all coaches from groups where the current user is a member"""
service = container.get_group_service()
try:
coaches = service.get_my_coaches(UUID(user["sub"]))
return [CoachProfileRead.model_validate(coach) for coach in coaches]
except NotFoundError as e:
raise HTTPException(404, str(e))


141 changes: 141 additions & 0 deletions src/entrypoints/api/tests/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,147 @@ async def test_28_coach_delete_own_group(self, client, test_state):
)
assert r.status_code == 204

# 29 – Setup pour les tests de coaches: coach crΓ©e un nouveau groupe
async def test_29_setup_coach_create_new_group(self, client, test_state):
payload = {"name": "NewCoachGroup", "description": "Nouveau groupe pour tests coaches"}
r = await client.post(
"/groups",
json=payload,
headers={"Authorization": f"Bearer {test_state['coach_token']}"}
)
assert r.status_code == 201
grp = r.json()
test_state["new_coach_group_uuid"] = grp["id"]

# 30 – Coach ajoute l'utilisateur au nouveau groupe
async def test_30_setup_coach_add_user_to_new_group(self, client, test_state):
r = await client.post(
f"/groups/{test_state['new_coach_group_uuid']}/members/{test_state['user_uuid']}",
headers={"Authorization": f"Bearer {test_state['coach_token']}"}
)
assert r.status_code == 204

# 31 – User rΓ©cupΓ¨re ses coaches (il est membre du groupe du coach) β†’ 200
async def test_31_user_get_my_coaches_success(self, client, test_state):
r = await client.get(
"/groups/coachs/mine",
headers={"Authorization": f"Bearer {test_state['user_token']}"}
)
assert r.status_code == 200
coaches = r.json()
assert isinstance(coaches, list)
assert len(coaches) == 1
assert coaches[0]["id"] == test_state["coach_uuid"]
assert coaches[0]["name"] == "Coach"

# 32 – Admin rΓ©cupΓ¨re ses coaches (il n'est membre d'aucun groupe) β†’ 404
async def test_32_admin_get_my_coaches_not_found(self, client, test_state):
r = await client.get(
"/groups/coachs/mine",
headers={"Authorization": f"Bearer {test_state['admin_token']}"}
)
assert r.status_code == 404

# 33 – User quitte le groupe puis essaie de rΓ©cupΓ©rer ses coaches β†’ 404
async def test_33_user_leave_group_then_get_coaches_not_found(self, client, test_state):
r = await client.delete(
f"/groups/{test_state['new_coach_group_uuid']}/leave",
headers={"Authorization": f"Bearer {test_state['user_token']}"}
)
assert r.status_code == 204

r = await client.get(
"/groups/coachs/mine",
headers={"Authorization": f"Bearer {test_state['user_token']}"}
)
assert r.status_code == 404

# 34 – CrΓ©ation d'un second coach pour tester plusieurs coaches
async def test_34_create_second_coach(self, client, test_state):
payload = {
"email": "coach2@example.com",
"password": "Coach2Pass123!",
"confirm_password": "Coach2Pass123!",
"name": "Coach2",
"sex": "F",
"age": 30
}
r = await client.post("/profiles", json=payload)
assert r.status_code == 201
body = r.json()
test_state["coach2_uuid"] = body["profile"]["id"]

update_data = {"roles": ["user", "coach"]}
r = await client.patch(
f"/profiles/{test_state['coach2_uuid']}/roles",
json=update_data,
headers={"Authorization": f"Bearer {test_state['admin_token']}"}
)
assert r.status_code == 200

r = await client.post(
"/profiles/login",
json={"email": "coach2@example.com", "password": "Coach2Pass123!"}
)
assert r.status_code == 200
test_state["coach2_token"] = r.json()["access_token"]

# 35 – Second coach crΓ©e un groupe
async def test_35_coach2_create_group(self, client, test_state):
payload = {"name": "Coach2Group", "description": "Groupe du second coach"}
r = await client.post(
"/groups",
json=payload,
headers={"Authorization": f"Bearer {test_state['coach2_token']}"}
)
assert r.status_code == 201
grp = r.json()
test_state["coach2_group_uuid"] = grp["id"]

# 36 – Les deux coaches ajoutent l'utilisateur Γ  leurs groupes
async def test_36_both_coaches_add_user(self, client, test_state):
# Coach 1 ajoute user
r = await client.post(
f"/groups/{test_state['new_coach_group_uuid']}/members/{test_state['user_uuid']}",
headers={"Authorization": f"Bearer {test_state['coach_token']}"}
)
assert r.status_code == 204

r = await client.post(
f"/groups/{test_state['coach2_group_uuid']}/members/{test_state['user_uuid']}",
headers={"Authorization": f"Bearer {test_state['coach2_token']}"}
)
assert r.status_code == 204

# 37 – User rΓ©cupΓ¨re ses coaches (2 coaches cette fois) β†’ 200
async def test_37_user_get_multiple_coaches(self, client, test_state):
r = await client.get(
"/groups/coachs/mine",
headers={"Authorization": f"Bearer {test_state['user_token']}"}
)
assert r.status_code == 200
coaches = r.json()
assert isinstance(coaches, list)
assert len(coaches) == 2

coach_ids = [coach["id"] for coach in coaches]
assert test_state["coach_uuid"] in coach_ids
assert test_state["coach2_uuid"] in coach_ids

# 38 – User quitte les groupes de ses coaches β†’ 204
async def test_38_user_leave_coach_groups(self, client, test_state):
r = await client.delete(
f"/groups/{test_state['new_coach_group_uuid']}/leave",
headers={"Authorization": f"Bearer {test_state['user_token']}"}
)
assert r.status_code == 204

r = await client.delete(
f"/groups/{test_state['coach2_group_uuid']}/leave",
headers={"Authorization": f"Bearer {test_state['user_token']}"}
)
assert r.status_code == 204


# crΓ©er un user/ crΓ©er un coach (user)
# admin update role coach add coach
Expand Down
Loading