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
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# BDD ENvironment Variables
DATABASE_URL = "postgresql://user:password@localhost:5432/mydatabase"
DATABASE_URL = "postgresql+asyncpg://user:password@localhost:5432/mydatabase"
ENV = "ENV"
SECRET_KEY= "jwt sercret"
ACCESS_TOKEN_EXPIRE_MINUTES = "in minutes"
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
name = "tracknatrainapi"
version = "0.5.7"
requires-python = ">=3.12"
dependencies = [ "annotated-types==0.7.0", "anyio==4.9.0", "bcrypt==4.3.0", "boto3==1.37.37", "botocore==1.37.37", "cffi==1.17.1", "click==8.1.8", "cryptography==44.0.2", "dnspython==2.7.0", "ecdsa==0.19.1", "email-validator==2.2.0", "exceptiongroup==1.2.2", "fastapi==0.115.12", "greenlet==3.1.1", "h11==0.14.0", "idna==3.10", "jmespath==1.0.1", "passlib[bcrypt]>=1.7.4", "psycopg2-binary==2.9.10", "pyasn1==0.4.8", "pycparser==2.22", "pydantic==2.11.3", "pydantic-core==2.33.1", "python-dateutil==2.9.0.post0", "python-dotenv==1.1.0", "python-jose==3.4.0", "python-multipart==0.0.20", "rsa==4.9", "s3transfer==0.11.5", "six==1.17.0", "sniffio==1.3.1", "sqlalchemy==2.0.40", "starlette==0.46.2", "typing-extensions==4.13.2", "typing-inspection==0.4.0", "urllib3==2.4.0", "uvicorn==0.34.1", "pytest>=7.0", "pytest-asyncio>=0.20", "httpx>=0.24", "pytest-cov>=4.0", "coverage>=6.0",]
dependencies = [ "annotated-types==0.7.0", "anyio==4.9.0", "bcrypt==4.3.0", "boto3==1.37.37", "botocore==1.37.37", "cffi==1.17.1", "click==8.1.8", "cryptography==44.0.2", "dnspython==2.7.0", "ecdsa==0.19.1", "email-validator==2.2.0", "exceptiongroup==1.2.2", "fastapi==0.115.12", "greenlet==3.1.1", "h11==0.14.0", "idna==3.10", "jmespath==1.0.1", "passlib[bcrypt]>=1.7.4", "psycopg2-binary==2.9.10", "pyasn1==0.4.8", "pycparser==2.22", "pydantic==2.11.3", "pydantic-core==2.33.1", "python-dateutil==2.9.0.post0", "python-dotenv==1.1.0", "python-jose==3.4.0", "python-multipart==0.0.20", "rsa==4.9", "s3transfer==0.11.5", "six==1.17.0", "sniffio==1.3.1", "sqlalchemy==2.0.40", "starlette==0.46.2", "typing-extensions==4.13.2", "typing-inspection==0.4.0", "urllib3==2.4.0", "uvicorn==0.34.1", "pytest>=7.0", "pytest-asyncio>=0.20", "httpx>=0.24", "pytest-cov>=4.0", "coverage>=6.0", "asyncpg==0.30.0"]

[build-system]
requires = [ "setuptools>=42", "wheel",]
build-backend = "setuptools.build_meta"

[project.optional-dependencies]
testing = [ "pytest>=7.0", "pytest-asyncio>=0.20", "httpx>=0.24", "pytest-cov>=4.0", "coverage>=6.0",]
testing = [ "pytest>=7.0", "pytest-asyncio>=0.20", "httpx>=0.24", "pytest-cov>=4.0", "coverage>=6.0",]
39 changes: 18 additions & 21 deletions src/adapters/inmemory/repositories/diet.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,27 @@ def __init__(self):
self._macro_plans: dict[UUID, DomainMacroPlan] = {}
self._meal_plans: dict[UUID, DomainMealPlan] = {}

# Diet methods
def add_diet(self, diet: DomainDiet) -> DomainDiet:
async def add_diet(self, diet: DomainDiet) -> DomainDiet:
new_id = uuid4()
diet.id = new_id
if not getattr(diet, 'created_at', None):
diet.created_at = datetime.utcnow()
self._diets[new_id] = diet
return diet

def find_by_id(self, id: UUID) -> Optional[DomainDiet]:
async def find_by_id(self, id: UUID) -> Optional[DomainDiet]:
return self._diets.get(id)

def find_all_owner_diets(self, owner_id: UUID) -> List[DomainDiet]:
async def find_all_owner_diets(self, owner_id: UUID) -> List[DomainDiet]:
return [d for d in self._diets.values() if d.owner_id == owner_id]

def update_diet(self, diet: DomainDiet) -> DomainDiet:
async def update_diet(self, diet: DomainDiet) -> DomainDiet:
if diet.id not in self._diets:
raise NotFoundError(f"Diet {diet.id} not found")
self._diets[diet.id] = diet
return diet

def delete_diet(self, id: UUID) -> None:
async def delete_diet(self, id: UUID) -> None:
self._diets.pop(id, None)
for mid in list(self._macro_plans.keys()):
if self._macro_plans[mid].diet_id == id:
Expand All @@ -42,52 +41,50 @@ def delete_diet(self, id: UUID) -> None:
if self._meal_plans[pid].diet_id == id:
self.delete_meal_plan(pid)

# MacroPlan methods
def add_macro_plan(self, macro_plan: DomainMacroPlan) -> DomainMacroPlan:
async def add_macro_plan(self, macro_plan: DomainMacroPlan) -> DomainMacroPlan:
new_id = uuid4()
macro_plan.id = new_id
self._macro_plans[new_id] = macro_plan
return macro_plan

def find_macro_plan_by_id(self, id: UUID) -> Optional[DomainMacroPlan]:
async def find_macro_plan_by_id(self, id: UUID) -> Optional[DomainMacroPlan]:
return self._macro_plans.get(id)

def find_macro_plans_by_diet_id(self, diet_id: UUID) -> List[DomainMacroPlan]:
async def find_macro_plans_by_diet_id(self, diet_id: UUID) -> List[DomainMacroPlan]:
return [mp for mp in self._macro_plans.values() if mp.diet_id == diet_id]

def find_macro_plans_by_user_id(self, user_id: UUID) -> List[DomainMacroPlan]:
async def find_macro_plans_by_user_id(self, user_id: UUID) -> List[DomainMacroPlan]:
return [mp for mp in self._macro_plans.values() if self._diets.get(mp.diet_id) and self._diets[mp.diet_id].owner_id == user_id]

def update_macro_plan(self, macro_plan: DomainMacroPlan) -> DomainMacroPlan:
async def update_macro_plan(self, macro_plan: DomainMacroPlan) -> DomainMacroPlan:
if macro_plan.id not in self._macro_plans:
raise NotFoundError(f"MacroPlan {macro_plan.id} not found")
self._macro_plans[macro_plan.id] = macro_plan
return macro_plan

def delete_macro_plan(self, id: UUID) -> None:
async def delete_macro_plan(self, id: UUID) -> None:
self._macro_plans.pop(id, None)

# MealPlan methods
def add_meal_plan(self, meal_plan: DomainMealPlan) -> DomainMealPlan:
async def add_meal_plan(self, meal_plan: DomainMealPlan) -> DomainMealPlan:
new_id = uuid4()
meal_plan.id = new_id
self._meal_plans[new_id] = meal_plan
return meal_plan

def find_meal_plan_by_id(self, id: UUID) -> Optional[DomainMealPlan]:
async def find_meal_plan_by_id(self, id: UUID) -> Optional[DomainMealPlan]:
return self._meal_plans.get(id)

def find_meal_plans_by_diet_id(self, diet_id: UUID) -> List[DomainMealPlan]:
async def find_meal_plans_by_diet_id(self, diet_id: UUID) -> List[DomainMealPlan]:
return [mp for mp in self._meal_plans.values() if mp.diet_id == diet_id]

def find_meal_plans_by_user_id(self, user_id: UUID) -> List[DomainMealPlan]:
async def find_meal_plans_by_user_id(self, user_id: UUID) -> List[DomainMealPlan]:
return [mp for mp in self._meal_plans.values() if self._diets.get(mp.diet_id) and self._diets[mp.diet_id].owner_id == user_id]

def update_meal_plan(self, meal_plan: DomainMealPlan) -> DomainMealPlan:
async def update_meal_plan(self, meal_plan: DomainMealPlan) -> DomainMealPlan:
if meal_plan.id not in self._meal_plans:
raise NotFoundError(f"MealPlan {meal_plan.id} not found")
self._meal_plans[meal_plan.id] = meal_plan
return meal_plan

def delete_meal_plan(self, id: UUID) -> None:
self._meal_plans.pop(id, None)
async def delete_meal_plan(self, id: UUID) -> None:
self._meal_plans.pop(id, None)
14 changes: 7 additions & 7 deletions src/adapters/inmemory/repositories/exercise.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,28 @@ class InMemoryExerciseRepository(ExerciseRepository):
def __init__(self):
self._exercises: dict[UUID, DomainExercise] = {}

def add(self, exercise: DomainExercise) -> DomainExercise:
async def add(self, exercise: DomainExercise) -> DomainExercise:
new_id = uuid4()
exercise.id = new_id
if not getattr(exercise, 'created_at', None):
exercise.created_at = datetime.utcnow()
self._exercises[new_id] = exercise
return exercise

def delete(self, id: UUID) -> None:
async def delete(self, id: UUID) -> None:
self._exercises.pop(id, None)

def update(self, exercise: DomainExercise) -> Optional[DomainExercise]:
async def update(self, exercise: DomainExercise) -> Optional[DomainExercise]:
if exercise.id not in self._exercises:
raise NotFoundError(f"Exercise {exercise.id} not found")
self._exercises[exercise.id] = exercise
return exercise

def find_all_owner(self, owner_id: UUID) -> List[DomainExercise]:
async def find_all_owner(self, owner_id: UUID) -> List[DomainExercise]:
return [ex for ex in self._exercises.values() if ex.owner_id == owner_id]

def find_all(self) -> List[DomainExercise]:
async def find_all(self) -> List[DomainExercise]:
return list(self._exercises.values())

def find_by_id(self, id: UUID) -> Optional[DomainExercise]:
return self._exercises.get(id)
async def find_by_id(self, id: UUID) -> Optional[DomainExercise]:
return self._exercises.get(id)
34 changes: 21 additions & 13 deletions src/adapters/inmemory/repositories/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ def __init__(self, profile_repo: InMemoryProfileRepository):
self._members: dict[UUID, List[UUID]] = {}
self._profile_repo = profile_repo

def find_by_id(self, id: UUID) -> Optional[DomainGroup]:
async def find_by_id(self, id: UUID) -> Optional[DomainGroup]:
return self._groups.get(id)

def add(self, group: DomainGroup) -> Optional[DomainGroup]:
async def add(self, group: DomainGroup) -> Optional[DomainGroup]:
new_id = uuid4()
group.id = new_id
if not getattr(group, 'created_at', None):
Expand All @@ -26,50 +26,58 @@ def add(self, group: DomainGroup) -> Optional[DomainGroup]:
self._members[new_id] = []
return group

def delete(self, id: UUID) -> None:
async def delete(self, id: UUID) -> None:
self._groups.pop(id, None)
self._members.pop(id, None)

def update(self, group: DomainGroup) -> Optional[DomainGroup]:
async def update(self, group: DomainGroup) -> Optional[DomainGroup]:
if group.id not in self._groups:
raise NotFoundError(f"Groupe {group.id} not found")
self._groups[group.id] = group
return group

def add_member(self, group_id: UUID, user_id: UUID) -> None:
async def add_member(self, group_id: UUID, user_id: UUID) -> None:
if group_id not in self._groups:
raise NotFoundError(f"Groupe {group_id} not found")
user = self._profile_repo.find_by_id(user_id)
user = await self._profile_repo.find_by_id(user_id)
if not user:
raise NotFoundError(f"Profile {user_id} not found")
members = self._members.setdefault(group_id, [])
if user_id not in members:
members.append(user_id)

def remove_member(self, group_id: UUID, user_id: UUID) -> None:
async def remove_member(self, group_id: UUID, user_id: UUID) -> None:
if group_id not in self._groups:
raise NotFoundError(f"Groupe {group_id} not found")
members = self._members.get(group_id, [])
if user_id not in members:
raise NotFoundError(f"Profile {user_id} not found in group {group_id}")
members.remove(user_id)

def list_members(self, group_id: UUID) -> List[DomainProfile]:
async def list_members(self, group_id: UUID) -> List[DomainProfile]:
if group_id not in self._groups:
raise NotFoundError(f"Groupe {group_id} introuvable")
user_ids = self._members.get(group_id, [])
return [self._profile_repo.find_by_id(uid) for uid in user_ids if self._profile_repo.find_by_id(uid)]
members = []
for uid in user_ids:
try:
profile = await self._profile_repo.find_by_id(uid)
if profile:
members.append(profile)
except:
pass
return members

def find_by_owner_id(self, owner_id: UUID) -> Optional[List[DomainGroup]]:
async def find_by_owner_id(self, owner_id: UUID) -> Optional[List[DomainGroup]]:
return [g for g in self._groups.values() if g.owner_id == owner_id]

def find_all_groups(self) -> Optional[List[DomainGroup]]:
async 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]]:
async def find_groups_by_member_id(self, user_id: UUID) -> Optional[List[DomainGroup]]:
"""Find all groups where the user is a member"""
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 []
return groups if groups else []
16 changes: 8 additions & 8 deletions src/adapters/inmemory/repositories/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,37 @@ def __init__(self, initial: list[DomainProfile] | None = None):
for profile in initial:
self._data[profile.id] = profile

def find_by_email(self, email: str) -> Optional[DomainProfile]:
async def find_by_email(self, email: str) -> Optional[DomainProfile]:
for profile in self._data.values():
if profile.email == email:
return profile
return None

def add(self, profile: DomainProfile) -> DomainProfile:
async def add(self, profile: DomainProfile) -> DomainProfile:
new_id = uuid4()
profile.id = new_id
if not getattr(profile, "created_at", None):
profile.created_at = datetime.utcnow()
self._data[new_id] = profile
return profile

def find_by_id(self, id: UUID) -> Optional[DomainProfile]:
async def find_by_id(self, id: UUID) -> Optional[DomainProfile]:
return self._data.get(id)

def delete(self, id: UUID) -> None:
async def delete(self, id: UUID) -> None:
self._data.pop(id, None)

def update(self, profile: DomainProfile) -> Optional[DomainProfile]:
async def update(self, profile: DomainProfile) -> Optional[DomainProfile]:
if profile.id in self._data:
self._data[profile.id] = profile
return profile
return None

def find_all_users(self) -> List[DomainProfile]:
async def find_all_users(self) -> List[DomainProfile]:
return [p for p in self._data.values() if "user" in (p.roles or [])]

def find_all_coachs(self) -> List[DomainProfile]:
async def find_all_coachs(self) -> List[DomainProfile]:
return [p for p in self._data.values() if "coach" in (p.roles or [])]

def find_all(self) -> List[DomainProfile]:
async def find_all(self) -> List[DomainProfile]:
return list(self._data.values())
32 changes: 16 additions & 16 deletions src/adapters/inmemory/repositories/training.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,78 +13,78 @@ def __init__(self):
self._validates: dict[UUID, DomainValidate] = {}

# Training methods
def find_by_id(self, id: UUID) -> Optional[DomainTraining]:
async def find_by_id(self, id: UUID) -> Optional[DomainTraining]:
return self._trainings.get(id)

def add_training(self, training: DomainTraining) -> DomainTraining:
async def add_training(self, training: DomainTraining) -> DomainTraining:
new_id = uuid4()
training.id = new_id
if not getattr(training, 'created_at', None):
training.created_at = datetime.utcnow()
self._trainings[new_id] = training
return training

def delete_training(self, id: UUID) -> None:
async def delete_training(self, id: UUID) -> None:
self._trainings.pop(id, None)
for task_id, task in list(self._tasks.items()):
if task.training_id == id:
self.delete_task(task_id)

def update_training(self, training: DomainTraining) -> Optional[DomainTraining]:
async def update_training(self, training: DomainTraining) -> Optional[DomainTraining]:
if training.id not in self._trainings:
raise NotFoundError(f"Training {training.id} not found")
self._trainings[training.id] = training
return training

def find_all_owner_trainings(self, owner_id: UUID) -> List[DomainTraining]:
async def find_all_owner_trainings(self, owner_id: UUID) -> List[DomainTraining]:
return [t for t in self._trainings.values() if t.owner_id == owner_id]

# Task methods
def add_task(self, task: DomainTask) -> DomainTask:
async def add_task(self, task: DomainTask) -> DomainTask:
new_id = uuid4()
task.id = new_id
if not getattr(task, 'updated_at', None):
task.updated_at = datetime.utcnow()
self._tasks[new_id] = task
return task

def find_task_by_id(self, id: UUID) -> Optional[DomainTask]:
async def find_task_by_id(self, id: UUID) -> Optional[DomainTask]:
return self._tasks.get(id)

def delete_task(self, id: UUID) -> None:
async def delete_task(self, id: UUID) -> None:
task = self._tasks.pop(id, None)
if task:
for vid, validate in list(self._validates.items()):
if validate.task_id == id:
self.delete_validate(vid)

def update_task(self, task: DomainTask) -> Optional[DomainTask]:
async def update_task(self, task: DomainTask) -> Optional[DomainTask]:
if task.id not in self._tasks:
raise NotFoundError(f"Task {task.id} not found")
self._tasks[task.id] = task
return task

def find_tasks_by_training_id(self, training_id: UUID) -> List[DomainTask]:
async def find_tasks_by_training_id(self, training_id: UUID) -> List[DomainTask]:
return [t for t in self._tasks.values() if t.training_id == training_id]

# Validate methods
def add_validate(self, validate: DomainValidate) -> DomainValidate:
async def add_validate(self, validate: DomainValidate) -> DomainValidate:
new_id = uuid4()
validate.id = new_id
if not getattr(validate, 'succeeded_at', None):
validate.succeeded_at = datetime.utcnow()
self._validates[new_id] = validate
return validate

def find_validate_by_id(self, id: UUID) -> Optional[DomainValidate]:
async def find_validate_by_id(self, id: UUID) -> Optional[DomainValidate]:
return self._validates.get(id)

def find_validate_by_task_id(self, task_id: UUID) -> List[DomainValidate]:
async def find_validate_by_task_id(self, task_id: UUID) -> List[DomainValidate]:
return [v for v in self._validates.values() if v.task_id == task_id]

def find_all_validates_by_training_id(self, training_id: UUID) -> List[DomainValidate]:
async def find_all_validates_by_training_id(self, training_id: UUID) -> List[DomainValidate]:
task_ids = {t.id for t in self._tasks.values() if t.training_id == training_id}
return [v for v in self._validates.values() if v.task_id in task_ids]

def delete_validate(self, id: UUID) -> None:
self._validates.pop(id, None)
async def delete_validate(self, id: UUID) -> None:
self._validates.pop(id, None)
Loading