diff --git a/api/api_models/projects.py b/api/api_models/projects.py
index f52d09c..52485e9 100644
--- a/api/api_models/projects.py
+++ b/api/api_models/projects.py
@@ -21,11 +21,28 @@ class CreateProject(ProjectBase):
project_tools: Optional[List[int]] = Field(None)
+class MemberWithTeamResponse(BaseModel):
+ id: int
+ first_name: str = Field(...)
+ last_name: str = Field(...)
+ username: str = Field(...)
+ email: str = Field(...)
+ profile_pic_url: Optional[str] = None
+ team: Optional[str] = Field(None)
+
+ model_config = ConfigDict(from_attributes=True)
+
+
+# Alias for backward compatibility
+MembersResponse = MemberWithTeamResponse
+
+
class ProjectResponse(ProjectBase):
id: int
created_at: datetime
updated_at: datetime
- members: Optional[list[UserResponse]] = Field(None)
+ manager: Optional[UserResponse] = Field(None)
+ members: Optional[list[MemberWithTeamResponse]] = Field(None)
stacks: Optional[list[Stacks]] = Field(None)
project_tools: Optional[list[Skills]] = Field(None)
status: ProjectStatus
@@ -44,13 +61,3 @@ class UpdateProject(BaseModel):
class ProjectMember(BaseModel):
team: ProjectTeam = Field(...)
-
-
-class MembersResponse(BaseModel):
- id: int
- first_name: str = Field(...)
- last_name: str = Field(...)
- username: str = Field(...)
- email: str = Field(...)
- profile_pic_url: Optional[str] = None
- stack: Optional[Stacks] = Field(None)
diff --git a/api/api_models/technical_task.py b/api/api_models/technical_task.py
index d357297..9c6cc10 100644
--- a/api/api_models/technical_task.py
+++ b/api/api_models/technical_task.py
@@ -22,9 +22,35 @@ class TechnicalTaskResponse(TechnicalTaskBase):
class TechnicalTaskSubmissionBase(BaseModel):
- github_link: Text
- live_demo_url: Optional[Text]
- description: Optional[Text]
+ github_link: Text = Field(...)
+ live_demo_url: Optional[Text] = None
+ description: Optional[Text] = None
+
+
+class UserMinimal(BaseModel):
+ id: int
+ first_name: str
+ last_name: str
+ username: str
+ profile_pic_url: Optional[str] = None
+
+ model_config = ConfigDict(from_attributes=True)
+
+
+class StackMinimal(BaseModel):
+ id: int
+ name: str
+
+ model_config = ConfigDict(from_attributes=True)
+
+
+class TechnicalTaskMinimal(BaseModel):
+ id: int
+ content: str
+ experience_level: ExperienceLevel
+ stack: Optional[StackMinimal] = None
+
+ model_config = ConfigDict(from_attributes=True)
class TechnicalTaskSubmissionResponse(TechnicalTaskSubmissionBase):
@@ -32,5 +58,8 @@ class TechnicalTaskSubmissionResponse(TechnicalTaskSubmissionBase):
created_at: datetime
updated_at: datetime
task_id: int
+ user_id: Optional[int] = None
+ user: Optional[UserMinimal] = None
+ technical_task: Optional[TechnicalTaskMinimal] = None
model_config = ConfigDict(from_attributes=True)
diff --git a/api/api_models/user.py b/api/api_models/user.py
index f4d24a4..a697a91 100644
--- a/api/api_models/user.py
+++ b/api/api_models/user.py
@@ -139,6 +139,8 @@ class OrgChartNode(BaseModel):
role: Optional[Role] = None
stack: Optional[Stacks] = None
manager_id: Optional[int] = None
+ status: str # Added for frontend filtering
+ is_active: bool # Added for frontend filtering
subordinates: List[OrgChartNode] = []
model_config = ConfigDict(from_attributes=True)
@@ -278,6 +280,7 @@ class Token(BaseModel):
is_active: bool = Field(...)
user_status: str = Field(...)
refresh_token: str = Field(...)
+ role: Optional[Role] = Field(None)
class TokenData(BaseModel):
diff --git a/api/routes/project.py b/api/routes/project.py
index 47a82c4..8668199 100644
--- a/api/routes/project.py
+++ b/api/routes/project.py
@@ -51,7 +51,12 @@ def get(project_id: int, db: Session = Depends(get_db)):
@project_router.get("/", status_code=status.HTTP_200_OK, response_model=Page[ProjectResponse])
def get_all(db: Session = Depends(get_db)):
- return paginate(db, _service(db).get_all_query())
+ service = _service(db)
+ page = paginate(db, service.get_all_query())
+ # Enrich members with team data
+ for project in page.items:
+ service._enrich_project_members_with_team(project)
+ return page
@project_router.post("/{project_id}/add/{user_id}", status_code=status.HTTP_201_CREATED)
diff --git a/db/repository/org_chart.py b/db/repository/org_chart.py
index 411eb5c..72c0f73 100644
--- a/db/repository/org_chart.py
+++ b/db/repository/org_chart.py
@@ -13,12 +13,19 @@ class OrgChartRepository(BaseRepository):
def get_user(self, user_id: int) -> Optional[User]:
return self.db.query(User).filter(User.id == user_id).first()
- def get_direct_subordinates(self, user_id: int) -> list[User]:
- return (
- self.db.query(User)
- .filter(User.manager_id == user_id)
- .all()
- )
+ def get_direct_subordinates(self, user_id: int, filter_active: bool = False) -> list[User]:
+ """Get direct subordinates of a user.
+
+ Args:
+ user_id: The manager's user ID
+ filter_active: If True, only return ACCEPTED + is_active users
+ """
+ query = self.db.query(User).filter(User.manager_id == user_id)
+
+ if filter_active:
+ query = query.filter(User.status == "ACCEPTED", User.is_active == True)
+
+ return query.all()
def get_subtree_ids(self, root_id: int, max_depth: int = 5) -> list[dict]:
"""Fetch all descendant user IDs using a recursive CTE, up to max_depth.
@@ -58,13 +65,18 @@ def get_users_by_ids(self, user_ids: list[int]) -> list[User]:
return []
return self.db.query(User).filter(User.id.in_(user_ids)).all()
- def get_root_users(self) -> list[User]:
- """Return users with no manager (org tree roots)."""
- return (
- self.db.query(User)
- .filter(User.manager_id.is_(None))
- .all()
- )
+ def get_root_users(self, filter_active: bool = False) -> list[User]:
+ """Return users with no manager (org tree roots).
+
+ Args:
+ filter_active: If True, only return ACCEPTED + is_active users
+ """
+ query = self.db.query(User).filter(User.manager_id.is_(None))
+
+ if filter_active:
+ query = query.filter(User.status == "ACCEPTED", User.is_active == True)
+
+ return query.all()
def get_ancestor_ids(self, user_id: int, max_depth: int = 50) -> list[int]:
"""Walk the manager chain upward using a recursive CTE.
diff --git a/db/repository/projects.py b/db/repository/projects.py
index 7c4855a..8b82786 100644
--- a/db/repository/projects.py
+++ b/db/repository/projects.py
@@ -30,6 +30,12 @@ def update(self, project: Project, update_data: dict) -> Project:
def delete_with_memberships(self, project_id: int) -> None:
self.db.query(UserProject).filter(UserProject.project_id == project_id).delete()
+ # Delete project_stacks entries
+ from db.models.project_stacks import ProjectStack
+ self.db.query(ProjectStack).filter(ProjectStack.project_id == project_id).delete()
+ # Delete project_skills entries
+ from db.models.project_skills import ProjectSkill
+ self.db.query(ProjectSkill).filter(ProjectSkill.project_id == project_id).delete()
self.db.query(Project).filter(Project.id == project_id).delete()
self.db.commit()
diff --git a/db/repository/users.py b/db/repository/users.py
index 399b729..bdd7dd1 100644
--- a/db/repository/users.py
+++ b/db/repository/users.py
@@ -20,8 +20,18 @@ def get_by_username(self, username: str) -> Optional[User]:
return self.db.query(User).filter(User.username == username).first()
def create(self, user_data: UserSignUp) -> User:
+ from db.models.roles import Role
+ from utils.utils import RoleChoices
+
data = user_data.model_dump().copy()
data.pop("password_confirmation")
+
+ # Ensure role_id is set - default to USER role if not provided
+ if not data.get("role_id"):
+ user_role = self.db.query(Role).filter(Role.name == RoleChoices.USER).first()
+ if user_role:
+ data["role_id"] = user_role.id
+
new_user = User(**data)
return self.save(new_user)
@@ -56,6 +66,8 @@ def update_avatar(self, user: User, url: str) -> User:
def build_search_query(self, skill: Optional[str], stack: Optional[str],
active: Optional[bool], p: Optional[str]):
+ from sqlalchemy import or_
+
query = select(User).order_by(desc(User.created_at))
if skill:
query = query.join(users_skills.UserSkill).join(Skill).filter(
@@ -64,7 +76,17 @@ def build_search_query(self, skill: Optional[str], stack: Optional[str],
if stack:
query = query.filter(User.stack.has(name=stack.capitalize()))
if active is not None:
- query = query.filter(User.is_active == active)
+ if active: # active=True means Directory
+ # Only show ACCEPTED + is_active users
+ query = query.filter(User.is_active.is_(True), User.status == "ACCEPTED")
+ else: # active=False means Applicants
+ # Show: (is_active=false any status) OR (is_active=true but status != ACCEPTED)
+ query = query.filter(
+ or_(
+ User.is_active.is_(False),
+ User.is_active.is_(True) & (User.status != "ACCEPTED")
+ )
+ )
if p:
p_escaped = p.replace("%", r"\%").replace("_", r"\_")
query = query.filter(
diff --git a/scripts/url_mapping.json b/scripts/url_mapping.json
deleted file mode 100644
index 247b0d6..0000000
--- a/scripts/url_mapping.json
+++ /dev/null
@@ -1,624 +0,0 @@
-{
- "AWS/skills/20240803-20-03-57/AWS": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859378/AWS/skills/20240803-20-03-57/AWS.png",
- "AWS/skills/20240803-20-06-47/AWS": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859378/AWS/skills/20240803-20-06-47/AWS.png",
- "AWS/skills/20240803-20-45-22/AWS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859379/AWS/skills/20240803-20-45-22/AWS.png",
- "AWS/skills/20240803-20-47-47/AWS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859381/AWS/skills/20240803-20-47-47/AWS.png",
- "AWS/skills/20240803-21-22-04/AWS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859382/AWS/skills/20240803-21-22-04/AWS.png",
- "AWS/skills/20240803-21-27-40/AWS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859383/AWS/skills/20240803-21-27-40/AWS.png",
- "AWS/skills/20240805-14-59-51/AWS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859384/AWS/skills/20240805-14-59-51/AWS.png",
- "AWS/skills/20240818-21-07-45/AWS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859384/AWS/skills/20240818-21-07-45/AWS.png",
- "AWS/skills/20240830-19-44-41/AWS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859385/AWS/skills/20240830-19-44-41/AWS.png",
- "Android/skills/20240803-19-48-30/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859386/Android/skills/20240803-19-48-30/Android.png",
- "Android/skills/20240803-19-50-17/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859386/Android/skills/20240803-19-50-17/Android.png",
- "Android/skills/20240803-19-51-07/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859387/Android/skills/20240803-19-51-07/Android.png",
- "Android/skills/20240803-19-51-47/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859388/Android/skills/20240803-19-51-47/Android.png",
- "Android/skills/20240803-19-53-00/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859389/Android/skills/20240803-19-53-00/Android.png",
- "Android/skills/20240803-19-53-27/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859390/Android/skills/20240803-19-53-27/Android.png",
- "Android/skills/20240803-19-54-26/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859391/Android/skills/20240803-19-54-26/Android.png",
- "Android/skills/20240803-19-56-10/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859391/Android/skills/20240803-19-56-10/Android.png",
- "Android/skills/20240803-19-56-32/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859392/Android/skills/20240803-19-56-32/Android.png",
- "Android/skills/20240803-19-57-51/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859393/Android/skills/20240803-19-57-51/Android.png",
- "Android/skills/20240803-19-58-51/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859393/Android/skills/20240803-19-58-51/Android.png",
- "Android/skills/20240803-20-03-16/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859394/Android/skills/20240803-20-03-16/Android.png",
- "Android/skills/20240803-20-03-52/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859394/Android/skills/20240803-20-03-52/Android.png",
- "Android/skills/20240803-20-06-42/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859395/Android/skills/20240803-20-06-42/Android.png",
- "Android/skills/20240803-20-45-16/Android.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859397/Android/skills/20240803-20-45-16/Android.png",
- "Android/skills/20240803-20-47-42/Android.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859398/Android/skills/20240803-20-47-42/Android.png",
- "Android/skills/20240803-21-22-03/Android.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859399/Android/skills/20240803-21-22-03/Android.png",
- "Android/skills/20240803-21-27-33/Android.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859400/Android/skills/20240803-21-27-33/Android.png",
- "Android/skills/20240805-14-59-44/Android.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859401/Android/skills/20240805-14-59-44/Android.png",
- "Android/skills/20240818-21-07-35/Android.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859402/Android/skills/20240818-21-07-35/Android.png",
- "Android/skills/20240830-19-44-12/Android.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859402/Android/skills/20240830-19-44-12/Android.png",
- "Angular/skills/20240803-19-48-31/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859403/Angular/skills/20240803-19-48-31/Angular.png",
- "Angular/skills/20240803-19-50-19/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859404/Angular/skills/20240803-19-50-19/Angular.png",
- "Angular/skills/20240803-19-51-08/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859404/Angular/skills/20240803-19-51-08/Angular.png",
- "Angular/skills/20240803-19-51-49/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859405/Angular/skills/20240803-19-51-49/Angular.png",
- "Angular/skills/20240803-19-53-01/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859405/Angular/skills/20240803-19-53-01/Angular.png",
- "Angular/skills/20240803-19-53-28/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859406/Angular/skills/20240803-19-53-28/Angular.png",
- "Angular/skills/20240803-19-54-27/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859407/Angular/skills/20240803-19-54-27/Angular.png",
- "Angular/skills/20240803-19-56-11/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859408/Angular/skills/20240803-19-56-11/Angular.png",
- "Angular/skills/20240803-19-56-33/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859408/Angular/skills/20240803-19-56-33/Angular.png",
- "Angular/skills/20240803-19-57-52/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859409/Angular/skills/20240803-19-57-52/Angular.png",
- "Angular/skills/20240803-19-58-53/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859410/Angular/skills/20240803-19-58-53/Angular.png",
- "Angular/skills/20240803-20-03-17/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859411/Angular/skills/20240803-20-03-17/Angular.png",
- "Angular/skills/20240803-20-03-53/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859411/Angular/skills/20240803-20-03-53/Angular.png",
- "Angular/skills/20240803-20-06-43/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859412/Angular/skills/20240803-20-06-43/Angular.png",
- "Angular/skills/20240803-20-45-18/Angular.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859413/Angular/skills/20240803-20-45-18/Angular.png",
- "Angular/skills/20240803-20-47-43/Angular.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859413/Angular/skills/20240803-20-47-43/Angular.png",
- "Angular/skills/20240803-21-22-03/Angular.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859414/Angular/skills/20240803-21-22-03/Angular.png",
- "Angular/skills/20240803-21-27-34/Angular.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859415/Angular/skills/20240803-21-27-34/Angular.png",
- "Angular/skills/20240805-14-59-46/Angular.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859416/Angular/skills/20240805-14-59-46/Angular.png",
- "Angular/skills/20240818-21-07-38/Angular.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859417/Angular/skills/20240818-21-07-38/Angular.png",
- "Angular/skills/20240830-19-44-19/Angular.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859418/Angular/skills/20240830-19-44-19/Angular.png",
- "Ansible/skills/20240803-20-03-54/Ansible": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859420/Ansible/skills/20240803-20-03-54/Ansible.png",
- "Ansible/skills/20240803-20-06-45/Ansible": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859421/Ansible/skills/20240803-20-06-45/Ansible.png",
- "Ansible/skills/20240803-20-45-19/Ansible.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859421/Ansible/skills/20240803-20-45-19/Ansible.png",
- "Ansible/skills/20240803-20-47-44/Ansible.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859422/Ansible/skills/20240803-20-47-44/Ansible.png",
- "Ansible/skills/20240803-21-22-04/Ansible.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859423/Ansible/skills/20240803-21-22-04/Ansible.png",
- "Ansible/skills/20240803-21-27-36/Ansible.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859424/Ansible/skills/20240803-21-27-36/Ansible.png",
- "Ansible/skills/20240805-14-59-48/Ansible.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859424/Ansible/skills/20240805-14-59-48/Ansible.png",
- "Ansible/skills/20240818-21-07-41/Ansible.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859425/Ansible/skills/20240818-21-07-41/Ansible.png",
- "Ansible/skills/20240830-19-44-27/Ansible.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859426/Ansible/skills/20240830-19-44-27/Ansible.png",
- "Apache/skills/20240803-20-03-56/Apache": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859427/Apache/skills/20240803-20-03-56/Apache.png",
- "Apache/skills/20240803-20-06-46/Apache": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859427/Apache/skills/20240803-20-06-46/Apache.png",
- "Apache/skills/20240803-20-45-21/Apache.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859428/Apache/skills/20240803-20-45-21/Apache.png",
- "Apache/skills/20240803-20-47-46/Apache.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859429/Apache/skills/20240803-20-47-46/Apache.png",
- "Apache/skills/20240803-21-22-04/Apache.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859429/Apache/skills/20240803-21-22-04/Apache.png",
- "Apache/skills/20240803-21-27-38/Apache.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859430/Apache/skills/20240803-21-27-38/Apache.png",
- "Apache/skills/20240805-14-59-50/Apache.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859431/Apache/skills/20240805-14-59-50/Apache.png",
- "Apache/skills/20240818-21-07-43/Apache.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859432/Apache/skills/20240818-21-07-43/Apache.png",
- "Apache/skills/20240830-19-44-33/Apache.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859432/Apache/skills/20240830-19-44-33/Apache.png",
- "Bitbucket/skills/20240803-20-03-58/Bitbucket": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859433/Bitbucket/skills/20240803-20-03-58/Bitbucket.png",
- "Bitbucket/skills/20240803-20-06-48/Bitbucket": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859434/Bitbucket/skills/20240803-20-06-48/Bitbucket.png",
- "Bitbucket/skills/20240803-20-45-24/Bitbucket.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859436/Bitbucket/skills/20240803-20-45-24/Bitbucket.png",
- "Bitbucket/skills/20240803-20-47-49/Bitbucket.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859436/Bitbucket/skills/20240803-20-47-49/Bitbucket.png",
- "Bitbucket/skills/20240803-21-22-05/Bitbucket.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859437/Bitbucket/skills/20240803-21-22-05/Bitbucket.png",
- "Bitbucket/skills/20240803-21-27-42/Bitbucket.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859438/Bitbucket/skills/20240803-21-27-42/Bitbucket.png",
- "Bitbucket/skills/20240805-14-59-54/Bitbucket.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859439/Bitbucket/skills/20240805-14-59-54/Bitbucket.png",
- "Bitbucket/skills/20240818-21-07-46/Bitbucket.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859440/Bitbucket/skills/20240818-21-07-46/Bitbucket.png",
- "Bitbucket/skills/20240830-19-44-47/Bitbucket.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859441/Bitbucket/skills/20240830-19-44-47/Bitbucket.png",
- "Bootstrap/skills/20240803-20-04-00/Bootstrap": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859441/Bootstrap/skills/20240803-20-04-00/Bootstrap.png",
- "Bootstrap/skills/20240803-20-06-50/Bootstrap": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859442/Bootstrap/skills/20240803-20-06-50/Bootstrap.png",
- "Bootstrap/skills/20240803-20-45-25/Bootstrap.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859443/Bootstrap/skills/20240803-20-45-25/Bootstrap.png",
- "Bootstrap/skills/20240803-20-47-50/Bootstrap.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859443/Bootstrap/skills/20240803-20-47-50/Bootstrap.png",
- "Bootstrap/skills/20240803-21-22-05/Bootstrap.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859444/Bootstrap/skills/20240803-21-22-05/Bootstrap.png",
- "Bootstrap/skills/20240803-21-27-44/Bootstrap.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859445/Bootstrap/skills/20240803-21-27-44/Bootstrap.png",
- "Bootstrap/skills/20240805-14-59-55/Bootstrap.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859445/Bootstrap/skills/20240805-14-59-55/Bootstrap.png",
- "Bootstrap/skills/20240818-21-07-48/Bootstrap.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859446/Bootstrap/skills/20240818-21-07-48/Bootstrap.png",
- "Bootstrap/skills/20240830-19-44-53/Bootstrap.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859447/Bootstrap/skills/20240830-19-44-53/Bootstrap.png",
- "Cassandra/skills/20240803-20-04-01/Cassandra": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859448/Cassandra/skills/20240803-20-04-01/Cassandra.png",
- "Cassandra/skills/20240803-20-06-51/Cassandra": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859448/Cassandra/skills/20240803-20-06-51/Cassandra.png",
- "Cassandra/skills/20240803-20-45-27/Cassandra.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859449/Cassandra/skills/20240803-20-45-27/Cassandra.png",
- "Cassandra/skills/20240803-20-47-52/Cassandra.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859449/Cassandra/skills/20240803-20-47-52/Cassandra.png",
- "Cassandra/skills/20240803-21-22-06/Cassandra.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859450/Cassandra/skills/20240803-21-22-06/Cassandra.png",
- "Cassandra/skills/20240803-21-27-46/Cassandra.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859451/Cassandra/skills/20240803-21-27-46/Cassandra.png",
- "Cassandra/skills/20240805-14-59-57/Cassandra.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859451/Cassandra/skills/20240805-14-59-57/Cassandra.png",
- "Cassandra/skills/20240818-21-07-51/Cassandra.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859452/Cassandra/skills/20240818-21-07-51/Cassandra.png",
- "Cassandra/skills/20240830-19-45-01/Cassandra.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859453/Cassandra/skills/20240830-19-45-01/Cassandra.png",
- "Chef/skills/20240803-20-04-03/Chef": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859454/Chef/skills/20240803-20-04-03/Chef.png",
- "Chef/skills/20240803-20-06-52/Chef": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859454/Chef/skills/20240803-20-06-52/Chef.png",
- "Chef/skills/20240803-20-45-28/Chef.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859455/Chef/skills/20240803-20-45-28/Chef.png",
- "Chef/skills/20240803-20-47-53/Chef.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859456/Chef/skills/20240803-20-47-53/Chef.png",
- "Chef/skills/20240803-21-22-06/Chef.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859456/Chef/skills/20240803-21-22-06/Chef.png",
- "Chef/skills/20240803-21-27-47/Chef.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859457/Chef/skills/20240803-21-27-47/Chef.png",
- "Chef/skills/20240805-14-59-59/Chef.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859460/Chef/skills/20240805-14-59-59/Chef.png",
- "Chef/skills/20240818-21-07-57/Chef.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859461/Chef/skills/20240818-21-07-57/Chef.png",
- "Chef/skills/20240830-19-45-07/Chef.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859462/Chef/skills/20240830-19-45-07/Chef.png",
- "Dart/skills/20240803-20-04-04/Dart": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859463/Dart/skills/20240803-20-04-04/Dart.png",
- "Dart/skills/20240803-20-06-54/Dart": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859464/Dart/skills/20240803-20-06-54/Dart.png",
- "Dart/skills/20240803-20-45-30/Dart.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859464/Dart/skills/20240803-20-45-30/Dart.png",
- "Dart/skills/20240803-20-47-55/Dart.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859465/Dart/skills/20240803-20-47-55/Dart.png",
- "Dart/skills/20240803-21-22-06/Dart.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859466/Dart/skills/20240803-21-22-06/Dart.png",
- "Dart/skills/20240803-21-27-49/Dart.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859467/Dart/skills/20240803-21-27-49/Dart.png",
- "Dart/skills/20240805-15-00-01/Dart.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859468/Dart/skills/20240805-15-00-01/Dart.png",
- "Dart/skills/20240818-21-08-00/Dart.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859469/Dart/skills/20240818-21-08-00/Dart.png",
- "Dart/skills/20240830-19-45-16/Dart.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859470/Dart/skills/20240830-19-45-16/Dart.png",
- "Django/skills/20240803-20-04-06/Django": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859470/Django/skills/20240803-20-04-06/Django.png",
- "Django/skills/20240803-20-06-55/Django": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859471/Django/skills/20240803-20-06-55/Django.png",
- "Django/skills/20240803-20-45-31/Django.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859472/Django/skills/20240803-20-45-31/Django.png",
- "Django/skills/20240803-20-47-57/Django.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859472/Django/skills/20240803-20-47-57/Django.png",
- "Django/skills/20240803-21-22-07/Django.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859473/Django/skills/20240803-21-22-07/Django.png",
- "Django/skills/20240803-21-27-51/Django.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859474/Django/skills/20240803-21-27-51/Django.png",
- "Django/skills/20240805-15-00-03/Django.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859474/Django/skills/20240805-15-00-03/Django.png",
- "Django/skills/20240818-21-08-17/Django.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859475/Django/skills/20240818-21-08-17/Django.png",
- "Django/skills/20240830-19-45-25/Django.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859476/Django/skills/20240830-19-45-25/Django.png",
- "Django-E-Commerce-Dockerise/.git/HEAD": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859477/Django-E-Commerce-Dockerise/.git/HEAD",
- "Django-E-Commerce-Dockerise/.git/config": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859479/Django-E-Commerce-Dockerise/.git/config",
- "Django-E-Commerce-Dockerise/.git/description": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859480/Django-E-Commerce-Dockerise/.git/description",
- "Django-E-Commerce-Dockerise/.git/hooks/applypatch-msg.sample": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859480/Django-E-Commerce-Dockerise/.git/hooks/applypatch-msg.sample",
- "Django-E-Commerce-Dockerise/.git/hooks/commit-msg.sample": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859481/Django-E-Commerce-Dockerise/.git/hooks/commit-msg.sample",
- "Django-E-Commerce-Dockerise/.git/hooks/fsmonitor-watchman.sample": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859482/Django-E-Commerce-Dockerise/.git/hooks/fsmonitor-watchman.sample",
- "Django-E-Commerce-Dockerise/.git/hooks/post-update.sample": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859483/Django-E-Commerce-Dockerise/.git/hooks/post-update.sample",
- "Django-E-Commerce-Dockerise/.git/hooks/pre-applypatch.sample": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859484/Django-E-Commerce-Dockerise/.git/hooks/pre-applypatch.sample",
- "Django-E-Commerce-Dockerise/.git/hooks/pre-commit.sample": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859484/Django-E-Commerce-Dockerise/.git/hooks/pre-commit.sample",
- "Django-E-Commerce-Dockerise/.git/hooks/pre-merge-commit.sample": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859485/Django-E-Commerce-Dockerise/.git/hooks/pre-merge-commit.sample",
- "Django-E-Commerce-Dockerise/.git/hooks/pre-push.sample": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859485/Django-E-Commerce-Dockerise/.git/hooks/pre-push.sample",
- "Django-E-Commerce-Dockerise/.git/hooks/pre-rebase.sample": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859486/Django-E-Commerce-Dockerise/.git/hooks/pre-rebase.sample",
- "Django-E-Commerce-Dockerise/.git/hooks/pre-receive.sample": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859486/Django-E-Commerce-Dockerise/.git/hooks/pre-receive.sample",
- "Django-E-Commerce-Dockerise/.git/hooks/prepare-commit-msg.sample": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859487/Django-E-Commerce-Dockerise/.git/hooks/prepare-commit-msg.sample",
- "Django-E-Commerce-Dockerise/.git/hooks/push-to-checkout.sample": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859488/Django-E-Commerce-Dockerise/.git/hooks/push-to-checkout.sample",
- "Django-E-Commerce-Dockerise/.git/hooks/update.sample": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859488/Django-E-Commerce-Dockerise/.git/hooks/update.sample",
- "Django-E-Commerce-Dockerise/.git/index": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859489/Django-E-Commerce-Dockerise/.git/index",
- "Django-E-Commerce-Dockerise/.git/info/exclude": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859490/Django-E-Commerce-Dockerise/.git/info/exclude",
- "Django-E-Commerce-Dockerise/.git/logs/HEAD": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859491/Django-E-Commerce-Dockerise/.git/logs/HEAD",
- "Django-E-Commerce-Dockerise/.git/logs/refs/heads/master": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859491/Django-E-Commerce-Dockerise/.git/logs/refs/heads/master",
- "Django-E-Commerce-Dockerise/.git/logs/refs/remotes/origin/HEAD": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859492/Django-E-Commerce-Dockerise/.git/logs/refs/remotes/origin/HEAD",
- "Django-E-Commerce-Dockerise/.git/objects/pack/pack-1fef77ede18e8bf811f600001b7822c39e9ac654.idx": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859493/Django-E-Commerce-Dockerise/.git/objects/pack/pack-1fef77ede18e8bf811f600001b7822c39e9ac654.idx",
- "Django-E-Commerce-Dockerise/.git/objects/pack/pack-1fef77ede18e8bf811f600001b7822c39e9ac654.pack": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859501/Django-E-Commerce-Dockerise/.git/objects/pack/pack-1fef77ede18e8bf811f600001b7822c39e9ac654.pack",
- "Django-E-Commerce-Dockerise/.git/packed-refs": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859502/Django-E-Commerce-Dockerise/.git/packed-refs",
- "Django-E-Commerce-Dockerise/.git/refs/heads/master": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859502/Django-E-Commerce-Dockerise/.git/refs/heads/master",
- "Django-E-Commerce-Dockerise/.git/refs/remotes/origin/HEAD": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859503/Django-E-Commerce-Dockerise/.git/refs/remotes/origin/HEAD",
- "Django-E-Commerce-Dockerise/.gitignore": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859504/Django-E-Commerce-Dockerise/.gitignore",
- "Django-E-Commerce-Dockerise/Dockerfile": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859504/Django-E-Commerce-Dockerise/Dockerfile",
- "Django-E-Commerce-Dockerise/LICENSE": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859505/Django-E-Commerce-Dockerise/LICENSE",
- "Django-E-Commerce-Dockerise/README.md": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859505/Django-E-Commerce-Dockerise/README.md",
- "Django-E-Commerce-Dockerise/babyshop_app/babyshop/__init__.py": "ERROR: Empty file",
- "Django-E-Commerce-Dockerise/babyshop_app/babyshop/asgi.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859506/Django-E-Commerce-Dockerise/babyshop_app/babyshop/asgi.py",
- "Django-E-Commerce-Dockerise/babyshop_app/babyshop/settings.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859507/Django-E-Commerce-Dockerise/babyshop_app/babyshop/settings.py",
- "Django-E-Commerce-Dockerise/babyshop_app/babyshop/urls.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859509/Django-E-Commerce-Dockerise/babyshop_app/babyshop/urls.py",
- "Django-E-Commerce-Dockerise/babyshop_app/babyshop/wsgi.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859509/Django-E-Commerce-Dockerise/babyshop_app/babyshop/wsgi.py",
- "Django-E-Commerce-Dockerise/babyshop_app/manage.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859510/Django-E-Commerce-Dockerise/babyshop_app/manage.py",
- "Django-E-Commerce-Dockerise/babyshop_app/products/__init__.py": "ERROR: Empty file",
- "Django-E-Commerce-Dockerise/babyshop_app/products/admin.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859511/Django-E-Commerce-Dockerise/babyshop_app/products/admin.py",
- "Django-E-Commerce-Dockerise/babyshop_app/products/apps.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859512/Django-E-Commerce-Dockerise/babyshop_app/products/apps.py",
- "Django-E-Commerce-Dockerise/babyshop_app/products/migrations/0001_initial.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859512/Django-E-Commerce-Dockerise/babyshop_app/products/migrations/0001_initial.py",
- "Django-E-Commerce-Dockerise/babyshop_app/products/migrations/0002_product_price.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859515/Django-E-Commerce-Dockerise/babyshop_app/products/migrations/0002_product_price.py",
- "Django-E-Commerce-Dockerise/babyshop_app/products/migrations/0003_alter_product_name.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859515/Django-E-Commerce-Dockerise/babyshop_app/products/migrations/0003_alter_product_name.py",
- "Django-E-Commerce-Dockerise/babyshop_app/products/migrations/0004_category_product_category.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859516/Django-E-Commerce-Dockerise/babyshop_app/products/migrations/0004_category_product_category.py",
- "Django-E-Commerce-Dockerise/babyshop_app/products/migrations/0005_rename_describtion_product_description.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859517/Django-E-Commerce-Dockerise/babyshop_app/products/migrations/0005_rename_describtion_product_description.py",
- "Django-E-Commerce-Dockerise/babyshop_app/products/migrations/__init__.py": "ERROR: Empty file",
- "Django-E-Commerce-Dockerise/babyshop_app/products/models.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859518/Django-E-Commerce-Dockerise/babyshop_app/products/models.py",
- "Django-E-Commerce-Dockerise/babyshop_app/products/tests.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859518/Django-E-Commerce-Dockerise/babyshop_app/products/tests.py",
- "Django-E-Commerce-Dockerise/babyshop_app/products/urls.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859519/Django-E-Commerce-Dockerise/babyshop_app/products/urls.py",
- "Django-E-Commerce-Dockerise/babyshop_app/products/views.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859519/Django-E-Commerce-Dockerise/babyshop_app/products/views.py",
- "Django-E-Commerce-Dockerise/babyshop_app/templates/login.html": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859520/Django-E-Commerce-Dockerise/babyshop_app/templates/login.html",
- "Django-E-Commerce-Dockerise/babyshop_app/templates/partoftemp/_dashboard.html": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859521/Django-E-Commerce-Dockerise/babyshop_app/templates/partoftemp/_dashboard.html",
- "Django-E-Commerce-Dockerise/babyshop_app/templates/partoftemp/footer.html": "ERROR: Empty file",
- "Django-E-Commerce-Dockerise/babyshop_app/templates/product.html": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859521/Django-E-Commerce-Dockerise/babyshop_app/templates/product.html",
- "Django-E-Commerce-Dockerise/babyshop_app/templates/products.html": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859522/Django-E-Commerce-Dockerise/babyshop_app/templates/products.html",
- "Django-E-Commerce-Dockerise/babyshop_app/templates/register.html": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859523/Django-E-Commerce-Dockerise/babyshop_app/templates/register.html",
- "Django-E-Commerce-Dockerise/babyshop_app/users/__init__.py": "ERROR: Empty file",
- "Django-E-Commerce-Dockerise/babyshop_app/users/admin.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859525/Django-E-Commerce-Dockerise/babyshop_app/users/admin.py",
- "Django-E-Commerce-Dockerise/babyshop_app/users/apps.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859525/Django-E-Commerce-Dockerise/babyshop_app/users/apps.py",
- "Django-E-Commerce-Dockerise/babyshop_app/users/forms.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859526/Django-E-Commerce-Dockerise/babyshop_app/users/forms.py",
- "Django-E-Commerce-Dockerise/babyshop_app/users/migrations/__init__.py": "ERROR: Empty file",
- "Django-E-Commerce-Dockerise/babyshop_app/users/models.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859527/Django-E-Commerce-Dockerise/babyshop_app/users/models.py",
- "Django-E-Commerce-Dockerise/babyshop_app/users/tests.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859528/Django-E-Commerce-Dockerise/babyshop_app/users/tests.py",
- "Django-E-Commerce-Dockerise/babyshop_app/users/urls.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859528/Django-E-Commerce-Dockerise/babyshop_app/users/urls.py",
- "Django-E-Commerce-Dockerise/babyshop_app/users/views.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859529/Django-E-Commerce-Dockerise/babyshop_app/users/views.py",
- "Django-E-Commerce-Dockerise/project_images/capture_20220323080815407.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859541/Django-E-Commerce-Dockerise/project_images/capture_20220323080815407.bmp",
- "Django-E-Commerce-Dockerise/project_images/capture_20220323080840305.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859544/Django-E-Commerce-Dockerise/project_images/capture_20220323080840305.bmp",
- "Django-E-Commerce-Dockerise/project_images/capture_20220323080934541.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859547/Django-E-Commerce-Dockerise/project_images/capture_20220323080934541.bmp",
- "Django-E-Commerce-Dockerise/project_images/capture_20220323080953570.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859551/Django-E-Commerce-Dockerise/project_images/capture_20220323080953570.bmp",
- "Django-E-Commerce-Dockerise/project_images/capture_20220323081016022.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859555/Django-E-Commerce-Dockerise/project_images/capture_20220323081016022.bmp",
- "Django-E-Commerce-Dockerise/project_images/capture_20220323081044867.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859559/Django-E-Commerce-Dockerise/project_images/capture_20220323081044867.bmp",
- "Docker/skills/20240803-20-04-07/Docker": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859560/Docker/skills/20240803-20-04-07/Docker.png",
- "Docker/skills/20240803-20-06-56/Docker": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859561/Docker/skills/20240803-20-06-56/Docker.png",
- "Docker/skills/20240803-20-45-32/Docker.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859562/Docker/skills/20240803-20-45-32/Docker.png",
- "Docker/skills/20240803-20-47-59/Docker.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859563/Docker/skills/20240803-20-47-59/Docker.png",
- "Docker/skills/20240803-21-22-07/Docker.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859565/Docker/skills/20240803-21-22-07/Docker.png",
- "Docker/skills/20240803-21-27-52/Docker.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859566/Docker/skills/20240803-21-27-52/Docker.png",
- "Docker/skills/20240805-15-00-04/Docker.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859567/Docker/skills/20240805-15-00-04/Docker.png",
- "Docker/skills/20240818-21-08-19/Docker.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859568/Docker/skills/20240818-21-08-19/Docker.png",
- "Docker/skills/20240830-19-45-30/Docker.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859573/Docker/skills/20240830-19-45-30/Docker.png",
- "FastAPI/skills/20240803-20-04-09/FastAPI": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859574/FastAPI/skills/20240803-20-04-09/FastAPI.png",
- "FastAPI/skills/20240803-20-06-58/FastAPI": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859575/FastAPI/skills/20240803-20-06-58/FastAPI.png",
- "FastAPI/skills/20240803-20-45-34/FastAPI.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859575/FastAPI/skills/20240803-20-45-34/FastAPI.png",
- "FastAPI/skills/20240803-20-48-00/FastAPI.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859576/FastAPI/skills/20240803-20-48-00/FastAPI.png",
- "FastAPI/skills/20240803-21-22-08/FastAPI.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859577/FastAPI/skills/20240803-21-22-08/FastAPI.png",
- "FastAPI/skills/20240803-21-27-54/FastAPI.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859578/FastAPI/skills/20240803-21-27-54/FastAPI.png",
- "FastAPI/skills/20240805-15-00-07/FastAPI.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859578/FastAPI/skills/20240805-15-00-07/FastAPI.png",
- "FastAPI/skills/20240818-21-08-23/FastAPI.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859579/FastAPI/skills/20240818-21-08-23/FastAPI.png",
- "FastAPI/skills/20240830-19-45-38/FastAPI.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859579/FastAPI/skills/20240830-19-45-38/FastAPI.png",
- "Flask/skills/20240803-20-04-12/Flask": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859581/Flask/skills/20240803-20-04-12/Flask.png",
- "Flask/skills/20240803-20-07-00/Flask": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859582/Flask/skills/20240803-20-07-00/Flask.png",
- "Flask/skills/20240803-20-45-36/Flask.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859583/Flask/skills/20240803-20-45-36/Flask.png",
- "Flask/skills/20240803-20-48-02/Flask.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859585/Flask/skills/20240803-20-48-02/Flask.png",
- "Flask/skills/20240803-21-22-08/Flask.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859585/Flask/skills/20240803-21-22-08/Flask.png",
- "Flask/skills/20240803-21-27-56/Flask.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859586/Flask/skills/20240803-21-27-56/Flask.png",
- "Flask/skills/20240805-15-00-09/Flask.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859587/Flask/skills/20240805-15-00-09/Flask.png",
- "Flask/skills/20240818-21-08-29/Flask.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859588/Flask/skills/20240818-21-08-29/Flask.png",
- "Flask/skills/20240830-19-45-45/Flask.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859588/Flask/skills/20240830-19-45-45/Flask.png",
- "Flutter/skills/20240803-20-04-13/Flutter": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859589/Flutter/skills/20240803-20-04-13/Flutter.png",
- "Flutter/skills/20240803-20-07-01/Flutter": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859589/Flutter/skills/20240803-20-07-01/Flutter.png",
- "Flutter/skills/20240803-20-45-38/Flutter.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859590/Flutter/skills/20240803-20-45-38/Flutter.png",
- "Flutter/skills/20240803-20-48-03/Flutter.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859591/Flutter/skills/20240803-20-48-03/Flutter.png",
- "Flutter/skills/20240803-21-22-08/Flutter.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859592/Flutter/skills/20240803-21-22-08/Flutter.png",
- "Flutter/skills/20240803-21-27-57/Flutter.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859593/Flutter/skills/20240803-21-27-57/Flutter.png",
- "Flutter/skills/20240805-15-00-11/Flutter.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859594/Flutter/skills/20240805-15-00-11/Flutter.png",
- "Flutter/skills/20240818-21-08-31/Flutter.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859594/Flutter/skills/20240818-21-08-31/Flutter.png",
- "Flutter/skills/20240830-19-45-55/Flutter.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859595/Flutter/skills/20240830-19-45-55/Flutter.png",
- "GitLab/skills/20240803-20-04-15/GitLab": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859596/GitLab/skills/20240803-20-04-15/GitLab.png",
- "GitLab/skills/20240803-20-07-02/GitLab": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859597/GitLab/skills/20240803-20-07-02/GitLab.png",
- "GitLab/skills/20240803-20-45-40/GitLab.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859597/GitLab/skills/20240803-20-45-40/GitLab.png",
- "GitLab/skills/20240803-20-48-04/GitLab.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859598/GitLab/skills/20240803-20-48-04/GitLab.png",
- "GitLab/skills/20240803-21-22-09/GitLab.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859598/GitLab/skills/20240803-21-22-09/GitLab.png",
- "GitLab/skills/20240803-21-27-59/GitLab.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859599/GitLab/skills/20240803-21-27-59/GitLab.png",
- "GitLab/skills/20240805-15-00-13/GitLab.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859600/GitLab/skills/20240805-15-00-13/GitLab.png",
- "GitLab/skills/20240818-21-08-33/GitLab.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859600/GitLab/skills/20240818-21-08-33/GitLab.png",
- "GitLab/skills/20240830-19-46-03/GitLab.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859601/GitLab/skills/20240830-19-46-03/GitLab.png",
- "Go/skills/20240803-20-04-16/Go": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859603/Go/skills/20240803-20-04-16/Go.png",
- "Go/skills/20240803-20-07-03/Go": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859603/Go/skills/20240803-20-07-03/Go.png",
- "Go/skills/20240803-20-48-05/Go.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859604/Go/skills/20240803-20-48-05/Go.png",
- "Go/skills/20240803-21-22-09/Go.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859605/Go/skills/20240803-21-22-09/Go.png",
- "Go/skills/20240803-21-28-00/Go.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859606/Go/skills/20240803-21-28-00/Go.png",
- "Go/skills/20240805-15-00-15/Go.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859607/Go/skills/20240805-15-00-15/Go.png",
- "Go/skills/20240818-21-08-37/Go.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859607/Go/skills/20240818-21-08-37/Go.png",
- "Go/skills/20240830-19-46-09/Go.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859608/Go/skills/20240830-19-46-09/Go.png",
- "GraphQL/skills/20240803-20-04-17/GraphQL": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859609/GraphQL/skills/20240803-20-04-17/GraphQL.png",
- "GraphQL/skills/20240803-20-07-05/GraphQL": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859609/GraphQL/skills/20240803-20-07-05/GraphQL.png",
- "GraphQL/skills/20240803-20-48-06/GraphQL.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859610/GraphQL/skills/20240803-20-48-06/GraphQL.png",
- "GraphQL/skills/20240803-21-22-10/GraphQL.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859611/GraphQL/skills/20240803-21-22-10/GraphQL.png",
- "GraphQL/skills/20240803-21-28-03/GraphQL.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859612/GraphQL/skills/20240803-21-28-03/GraphQL.png",
- "GraphQL/skills/20240805-15-00-17/GraphQL.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859613/GraphQL/skills/20240805-15-00-17/GraphQL.png",
- "GraphQL/skills/20240818-21-08-39/GraphQL.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859616/GraphQL/skills/20240818-21-08-39/GraphQL.png",
- "GraphQL/skills/20240830-19-46-15/GraphQL.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859616/GraphQL/skills/20240830-19-46-15/GraphQL.png",
- "Heroku/skills/20240803-20-04-19/Heroku": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859617/Heroku/skills/20240803-20-04-19/Heroku.png",
- "Heroku/skills/20240803-20-07-06/Heroku": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859618/Heroku/skills/20240803-20-07-06/Heroku.png",
- "Heroku/skills/20240803-20-48-08/Heroku.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859619/Heroku/skills/20240803-20-48-08/Heroku.png",
- "Heroku/skills/20240803-21-22-10/Heroku.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859620/Heroku/skills/20240803-21-22-10/Heroku.png",
- "Heroku/skills/20240803-21-28-04/Heroku.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859620/Heroku/skills/20240803-21-28-04/Heroku.png",
- "Heroku/skills/20240805-15-00-19/Heroku.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859621/Heroku/skills/20240805-15-00-19/Heroku.png",
- "Heroku/skills/20240818-21-08-42/Heroku.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859622/Heroku/skills/20240818-21-08-42/Heroku.png",
- "Heroku/skills/20240830-19-46-22/Heroku.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859622/Heroku/skills/20240830-19-46-22/Heroku.png",
- "Hibernate/skills/20240803-20-04-20/Hibernate": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859623/Hibernate/skills/20240803-20-04-20/Hibernate.png",
- "Hibernate/skills/20240803-20-07-07/Hibernate": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859624/Hibernate/skills/20240803-20-07-07/Hibernate.png",
- "Hibernate/skills/20240803-20-48-09/Hibernate.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859624/Hibernate/skills/20240803-20-48-09/Hibernate.png",
- "Hibernate/skills/20240803-21-22-10/Hibernate.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859625/Hibernate/skills/20240803-21-22-10/Hibernate.png",
- "Hibernate/skills/20240803-21-28-06/Hibernate.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859626/Hibernate/skills/20240803-21-28-06/Hibernate.png",
- "Hibernate/skills/20240805-15-00-20/Hibernate.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859627/Hibernate/skills/20240805-15-00-20/Hibernate.png",
- "Hibernate/skills/20240818-21-08-43/Hibernate.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859627/Hibernate/skills/20240818-21-08-43/Hibernate.png",
- "Hibernate/skills/20240830-19-46-28/Hibernate.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859628/Hibernate/skills/20240830-19-46-28/Hibernate.png",
- "Java/skills/20240803-20-04-25/Java": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859628/Java/skills/20240803-20-04-25/Java.png",
- "Java/skills/20240803-20-07-09/Java": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859629/Java/skills/20240803-20-07-09/Java.png",
- "Java/skills/20240803-20-48-12/Java.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859629/Java/skills/20240803-20-48-12/Java.png",
- "Java/skills/20240803-21-22-11/Java.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859630/Java/skills/20240803-21-22-11/Java.png",
- "Java/skills/20240803-21-28-09/Java.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859631/Java/skills/20240803-21-28-09/Java.png",
- "Java/skills/20240805-15-00-24/Java.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859633/Java/skills/20240805-15-00-24/Java.png",
- "Java/skills/20240818-21-08-48/Java.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859633/Java/skills/20240818-21-08-48/Java.png",
- "Java/skills/20240830-19-46-43/Java.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859634/Java/skills/20240830-19-46-43/Java.png",
- "JavaScript/skills/20240803-20-04-26/JavaScript": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859635/JavaScript/skills/20240803-20-04-26/JavaScript.png",
- "JavaScript/skills/20240803-20-07-10/JavaScript": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859636/JavaScript/skills/20240803-20-07-10/JavaScript.png",
- "JavaScript/skills/20240803-20-48-13/JavaScript.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859637/JavaScript/skills/20240803-20-48-13/JavaScript.png",
- "JavaScript/skills/20240803-21-22-11/JavaScript.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859637/JavaScript/skills/20240803-21-22-11/JavaScript.png",
- "JavaScript/skills/20240803-21-28-10/JavaScript.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859638/JavaScript/skills/20240803-21-28-10/JavaScript.png",
- "JavaScript/skills/20240805-15-00-25/JavaScript.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859643/JavaScript/skills/20240805-15-00-25/JavaScript.png",
- "JavaScript/skills/20240818-21-08-50/JavaScript.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859644/JavaScript/skills/20240818-21-08-50/JavaScript.png",
- "JavaScript/skills/20240830-19-46-48/JavaScript.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859645/JavaScript/skills/20240830-19-46-48/JavaScript.png",
- "Jenkins/skills/20240803-20-04-28/Jenkins": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859646/Jenkins/skills/20240803-20-04-28/Jenkins.png",
- "Jenkins/skills/20240803-20-07-11/Jenkins": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859646/Jenkins/skills/20240803-20-07-11/Jenkins.png",
- "Jenkins/skills/20240803-20-48-14/Jenkins.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859648/Jenkins/skills/20240803-20-48-14/Jenkins.png",
- "Jenkins/skills/20240803-21-22-12/Jenkins.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859648/Jenkins/skills/20240803-21-22-12/Jenkins.png",
- "Jenkins/skills/20240803-21-28-11/Jenkins.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859649/Jenkins/skills/20240803-21-28-11/Jenkins.png",
- "Jenkins/skills/20240805-15-00-27/Jenkins.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859650/Jenkins/skills/20240805-15-00-27/Jenkins.png",
- "Jenkins/skills/20240818-21-08-51/Jenkins.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859651/Jenkins/skills/20240818-21-08-51/Jenkins.png",
- "Jenkins/skills/20240830-19-46-55/Jenkins.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859651/Jenkins/skills/20240830-19-46-55/Jenkins.png",
- "Kotlin/skills/20240803-20-04-32/Kotlin": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859652/Kotlin/skills/20240803-20-04-32/Kotlin.png",
- "Kotlin/skills/20240803-20-07-14/Kotlin": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859653/Kotlin/skills/20240803-20-07-14/Kotlin.png",
- "Kotlin/skills/20240803-20-48-16/Kotlin.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859653/Kotlin/skills/20240803-20-48-16/Kotlin.png",
- "Kotlin/skills/20240803-21-22-12/Kotlin.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859654/Kotlin/skills/20240803-21-22-12/Kotlin.png",
- "Kotlin/skills/20240803-21-28-16/Kotlin.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859655/Kotlin/skills/20240803-21-28-16/Kotlin.png",
- "Kotlin/skills/20240805-15-00-30/Kotlin.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859656/Kotlin/skills/20240805-15-00-30/Kotlin.png",
- "Kotlin/skills/20240818-21-09-01/Kotlin.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859656/Kotlin/skills/20240818-21-09-01/Kotlin.png",
- "Kotlin/skills/20240830-19-47-06/Kotlin.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859657/Kotlin/skills/20240830-19-47-06/Kotlin.png",
- "Kubernetes/skills/20240803-20-07-15/Kubernetes": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859658/Kubernetes/skills/20240803-20-07-15/Kubernetes.png",
- "Kubernetes/skills/20240803-20-48-18/Kubernetes.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859658/Kubernetes/skills/20240803-20-48-18/Kubernetes.png",
- "Kubernetes/skills/20240803-21-22-13/Kubernetes.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859659/Kubernetes/skills/20240803-21-22-13/Kubernetes.png",
- "Kubernetes/skills/20240803-21-28-17/Kubernetes.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859660/Kubernetes/skills/20240803-21-28-17/Kubernetes.png",
- "Kubernetes/skills/20240805-15-00-31/Kubernetes.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859661/Kubernetes/skills/20240805-15-00-31/Kubernetes.png",
- "Kubernetes/skills/20240818-21-09-02/Kubernetes.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859661/Kubernetes/skills/20240818-21-09-02/Kubernetes.png",
- "Kubernetes/skills/20240830-19-47-12/Kubernetes.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859662/Kubernetes/skills/20240830-19-47-12/Kubernetes.png",
- "Laravel/skills/20240803-20-07-16/Laravel": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859662/Laravel/skills/20240803-20-07-16/Laravel.png",
- "Laravel/skills/20240803-20-48-19/Laravel.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859663/Laravel/skills/20240803-20-48-19/Laravel.png",
- "Laravel/skills/20240803-21-22-13/Laravel.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859664/Laravel/skills/20240803-21-22-13/Laravel.png",
- "Laravel/skills/20240803-21-28-25/Laravel.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859665/Laravel/skills/20240803-21-28-25/Laravel.png",
- "Laravel/skills/20240805-15-00-32/Laravel.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859665/Laravel/skills/20240805-15-00-32/Laravel.png",
- "Laravel/skills/20240818-21-09-04/Laravel.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859666/Laravel/skills/20240818-21-09-04/Laravel.png",
- "Laravel/skills/20240830-19-47-19/Laravel.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859667/Laravel/skills/20240830-19-47-19/Laravel.png",
- "Lua/skills/20240803-20-07-17/Lua": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859668/Lua/skills/20240803-20-07-17/Lua.png",
- "Lua/skills/20240803-20-48-20/Lua.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859668/Lua/skills/20240803-20-48-20/Lua.png",
- "Lua/skills/20240803-21-22-13/Lua.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859669/Lua/skills/20240803-21-22-13/Lua.png",
- "Lua/skills/20240803-21-28-26/Lua.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859670/Lua/skills/20240803-21-28-26/Lua.png",
- "Lua/skills/20240805-15-00-34/Lua.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859671/Lua/skills/20240805-15-00-34/Lua.png",
- "Lua/skills/20240818-21-09-11/Lua.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859672/Lua/skills/20240818-21-09-11/Lua.png",
- "Lua/skills/20240830-19-47-26/Lua.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859672/Lua/skills/20240830-19-47-26/Lua.png",
- "MongoDB/skills/20240803-20-07-19/MongoDB": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859673/MongoDB/skills/20240803-20-07-19/MongoDB.png",
- "MongoDB/skills/20240803-20-48-22/MongoDB.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859674/MongoDB/skills/20240803-20-48-22/MongoDB.png",
- "MongoDB/skills/20240803-21-22-14/MongoDB.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859676/MongoDB/skills/20240803-21-22-14/MongoDB.png",
- "MongoDB/skills/20240803-21-28-28/MongoDB.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859677/MongoDB/skills/20240803-21-28-28/MongoDB.png",
- "MongoDB/skills/20240805-15-00-37/MongoDB.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859678/MongoDB/skills/20240805-15-00-37/MongoDB.png",
- "MongoDB/skills/20240818-21-09-17/MongoDB.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859679/MongoDB/skills/20240818-21-09-17/MongoDB.png",
- "MongoDB/skills/20240830-19-47-36/MongoDB.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859679/MongoDB/skills/20240830-19-47-36/MongoDB.png",
- "MySQL/skills/20240803-20-07-20/MySQL": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859680/MySQL/skills/20240803-20-07-20/MySQL.png",
- "MySQL/skills/20240803-20-48-23/MySQL.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859681/MySQL/skills/20240803-20-48-23/MySQL.png",
- "MySQL/skills/20240803-21-22-14/MySQL.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859682/MySQL/skills/20240803-21-22-14/MySQL.png",
- "MySQL/skills/20240803-21-28-30/MySQL.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859682/MySQL/skills/20240803-21-28-30/MySQL.png",
- "MySQL/skills/20240805-15-00-38/MySQL.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859683/MySQL/skills/20240805-15-00-38/MySQL.png",
- "MySQL/skills/20240818-21-09-19/MySQL.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859684/MySQL/skills/20240818-21-09-19/MySQL.png",
- "MySQL/skills/20240830-19-47-42/MySQL.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859685/MySQL/skills/20240830-19-47-42/MySQL.png",
- "Neo4j/skills/20240803-20-07-21/Neo4j": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859686/Neo4j/skills/20240803-20-07-21/Neo4j.png",
- "Neo4j/skills/20240803-20-48-25/Neo4j.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859686/Neo4j/skills/20240803-20-48-25/Neo4j.png",
- "Neo4j/skills/20240803-21-22-15/Neo4j.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859687/Neo4j/skills/20240803-21-22-15/Neo4j.png",
- "Neo4j/skills/20240803-21-28-32/Neo4j.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859688/Neo4j/skills/20240803-21-28-32/Neo4j.png",
- "Neo4j/skills/20240805-15-00-40/Neo4j.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859689/Neo4j/skills/20240805-15-00-40/Neo4j.png",
- "Neo4j/skills/20240818-21-09-21/Neo4j.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859689/Neo4j/skills/20240818-21-09-21/Neo4j.png",
- "Neo4j/skills/20240830-19-47-48/Neo4j.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859691/Neo4j/skills/20240830-19-47-48/Neo4j.png",
- "Nginx/skills/20240803-20-07-22/Nginx": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859692/Nginx/skills/20240803-20-07-22/Nginx.png",
- "Nginx/skills/20240803-20-48-26/Nginx.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859693/Nginx/skills/20240803-20-48-26/Nginx.png",
- "Nginx/skills/20240803-21-22-15/Nginx.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859694/Nginx/skills/20240803-21-22-15/Nginx.png",
- "Nginx/skills/20240803-21-28-33/Nginx.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859695/Nginx/skills/20240803-21-28-33/Nginx.png",
- "Nginx/skills/20240805-15-00-42/Nginx.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859696/Nginx/skills/20240805-15-00-42/Nginx.png",
- "Nginx/skills/20240818-21-09-22/Nginx.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859696/Nginx/skills/20240818-21-09-22/Nginx.png",
- "Nginx/skills/20240830-19-47-53/Nginx.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859697/Nginx/skills/20240830-19-47-53/Nginx.png",
- "OpenCV/skills/20240803-20-07-24/OpenCV": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859697/OpenCV/skills/20240803-20-07-24/OpenCV.png",
- "OpenCV/skills/20240803-20-48-27/OpenCV.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859698/OpenCV/skills/20240803-20-48-27/OpenCV.png",
- "OpenCV/skills/20240803-21-22-15/OpenCV.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859699/OpenCV/skills/20240803-21-22-15/OpenCV.png",
- "OpenCV/skills/20240803-21-28-35/OpenCV.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859700/OpenCV/skills/20240803-21-28-35/OpenCV.png",
- "OpenCV/skills/20240805-15-00-43/OpenCV.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859701/OpenCV/skills/20240805-15-00-43/OpenCV.png",
- "OpenCV/skills/20240818-21-09-25/OpenCV.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859701/OpenCV/skills/20240818-21-09-25/OpenCV.png",
- "OpenCV/skills/20240830-19-48-01/OpenCV.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859702/OpenCV/skills/20240830-19-48-01/OpenCV.png",
- "Oracle/skills/20240803-20-07-25/Oracle": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859703/Oracle/skills/20240803-20-07-25/Oracle.png",
- "Oracle/skills/20240803-20-48-28/Oracle.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859704/Oracle/skills/20240803-20-48-28/Oracle.png",
- "Oracle/skills/20240803-21-22-16/Oracle.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859705/Oracle/skills/20240803-21-22-16/Oracle.png",
- "Oracle/skills/20240803-21-28-37/Oracle.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859706/Oracle/skills/20240803-21-28-37/Oracle.png",
- "Oracle/skills/20240805-15-00-44/Oracle.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859706/Oracle/skills/20240805-15-00-44/Oracle.png",
- "Oracle/skills/20240818-21-09-26/Oracle.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859707/Oracle/skills/20240818-21-09-26/Oracle.png",
- "Oracle/skills/20240830-19-48-07/Oracle.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859708/Oracle/skills/20240830-19-48-07/Oracle.png",
- "PHP/skills/20240803-20-07-27/PHP": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859709/PHP/skills/20240803-20-07-27/PHP.png",
- "PHP/skills/20240803-20-48-31/PHP.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859710/PHP/skills/20240803-20-48-31/PHP.png",
- "PHP/skills/20240803-21-22-16/PHP.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859711/PHP/skills/20240803-21-22-16/PHP.png",
- "PHP/skills/20240803-21-28-40/PHP.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859711/PHP/skills/20240803-21-28-40/PHP.png",
- "PHP/skills/20240805-15-00-49/PHP.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859712/PHP/skills/20240805-15-00-49/PHP.png",
- "PHP/skills/20240818-21-09-30/PHP.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859712/PHP/skills/20240818-21-09-30/PHP.png",
- "PHP/skills/20240830-19-48-22/PHP.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859714/PHP/skills/20240830-19-48-22/PHP.png",
- "Perl/skills/20240803-20-07-26/Perl": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859714/Perl/skills/20240803-20-07-26/Perl.png",
- "Perl/skills/20240803-20-48-30/Perl.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859715/Perl/skills/20240803-20-48-30/Perl.png",
- "Perl/skills/20240803-21-22-16/Perl.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859716/Perl/skills/20240803-21-22-16/Perl.png",
- "Perl/skills/20240803-21-28-38/Perl.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859717/Perl/skills/20240803-21-28-38/Perl.png",
- "Perl/skills/20240805-15-00-47/Perl.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859717/Perl/skills/20240805-15-00-47/Perl.png",
- "Perl/skills/20240818-21-09-28/Perl.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859718/Perl/skills/20240818-21-09-28/Perl.png",
- "Perl/skills/20240830-19-48-14/Perl.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859719/Perl/skills/20240830-19-48-14/Perl.png",
- "PostgreSQL/skills/20240803-20-07-28/PostgreSQL": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859720/PostgreSQL/skills/20240803-20-07-28/PostgreSQL.png",
- "PostgreSQL/skills/20240803-20-48-32/PostgreSQL.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859721/PostgreSQL/skills/20240803-20-48-32/PostgreSQL.png",
- "PostgreSQL/skills/20240803-21-22-17/PostgreSQL.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859721/PostgreSQL/skills/20240803-21-22-17/PostgreSQL.png",
- "PostgreSQL/skills/20240803-21-28-42/PostgreSQL.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859722/PostgreSQL/skills/20240803-21-28-42/PostgreSQL.png",
- "PostgreSQL/skills/20240805-15-00-50/PostgreSQL.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859723/PostgreSQL/skills/20240805-15-00-50/PostgreSQL.png",
- "PostgreSQL/skills/20240818-21-09-31/PostgreSQL.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859724/PostgreSQL/skills/20240818-21-09-31/PostgreSQL.png",
- "PostgreSQL/skills/20240830-19-48-28/PostgreSQL.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859724/PostgreSQL/skills/20240830-19-48-28/PostgreSQL.png",
- "PyTorch/skills/20240803-20-07-29/PyTorch": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859725/PyTorch/skills/20240803-20-07-29/PyTorch.png",
- "PyTorch/skills/20240803-20-48-34/PyTorch.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859726/PyTorch/skills/20240803-20-48-34/PyTorch.png",
- "PyTorch/skills/20240803-21-22-17/PyTorch.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859727/PyTorch/skills/20240803-21-22-17/PyTorch.png",
- "PyTorch/skills/20240803-21-28-44/PyTorch.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859727/PyTorch/skills/20240803-21-28-44/PyTorch.png",
- "PyTorch/skills/20240805-15-00-52/PyTorch.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859728/PyTorch/skills/20240805-15-00-52/PyTorch.png",
- "PyTorch/skills/20240818-21-09-33/PyTorch.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859729/PyTorch/skills/20240818-21-09-33/PyTorch.png",
- "PyTorch/skills/20240830-19-48-35/PyTorch.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859730/PyTorch/skills/20240830-19-48-35/PyTorch.png",
- "Python/skills/20240803-20-07-30/Python": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859731/Python/skills/20240803-20-07-30/Python.png",
- "Python/skills/20240803-20-48-36/Python.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859732/Python/skills/20240803-20-48-36/Python.png",
- "Python/skills/20240803-21-22-17/Python.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859733/Python/skills/20240803-21-22-17/Python.png",
- "Python/skills/20240803-21-28-47/Python.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859733/Python/skills/20240803-21-28-47/Python.png",
- "Python/skills/20240805-15-00-54/Python.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859735/Python/skills/20240805-15-00-54/Python.png",
- "Python/skills/20240818-21-09-34/Python.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859736/Python/skills/20240818-21-09-34/Python.png",
- "Python/skills/20240830-19-48-43/Python.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859737/Python/skills/20240830-19-48-43/Python.png",
- "React/skills/20240803-20-07-31/React": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859737/React/skills/20240803-20-07-31/React.png",
- "React/skills/20240803-20-48-37/React.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859738/React/skills/20240803-20-48-37/React.png",
- "React/skills/20240803-21-22-18/React.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859739/React/skills/20240803-21-22-18/React.png",
- "React/skills/20240803-21-28-48/React.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859739/React/skills/20240803-21-28-48/React.png",
- "React/skills/20240805-15-00-55/React.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859740/React/skills/20240805-15-00-55/React.png",
- "React/skills/20240818-21-09-36/React.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859741/React/skills/20240818-21-09-36/React.png",
- "React/skills/20240830-19-48-49/React.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859742/React/skills/20240830-19-48-49/React.png",
- "Redis/skills/20240803-20-07-33/Redis": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859745/Redis/skills/20240803-20-07-33/Redis.png",
- "Redis/skills/20240803-20-48-38/Redis.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859746/Redis/skills/20240803-20-48-38/Redis.png",
- "Redis/skills/20240803-21-22-18/Redis.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859746/Redis/skills/20240803-21-22-18/Redis.png",
- "Redis/skills/20240803-21-28-50/Redis.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859747/Redis/skills/20240803-21-28-50/Redis.png",
- "Redis/skills/20240805-15-00-57/Redis.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859748/Redis/skills/20240805-15-00-57/Redis.png",
- "Redis/skills/20240818-21-09-38/Redis.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859748/Redis/skills/20240818-21-09-38/Redis.png",
- "Redis/skills/20240830-19-48-56/Redis.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859749/Redis/skills/20240830-19-48-56/Redis.png",
- "Redux/skills/20240803-20-07-34/Redux": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859750/Redux/skills/20240803-20-07-34/Redux.png",
- "Redux/skills/20240803-20-48-39/Redux.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859751/Redux/skills/20240803-20-48-39/Redux.png",
- "Redux/skills/20240803-21-22-18/Redux.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859752/Redux/skills/20240803-21-22-18/Redux.png",
- "Redux/skills/20240803-21-28-51/Redux.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859752/Redux/skills/20240803-21-28-51/Redux.png",
- "Redux/skills/20240805-15-00-58/Redux.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859753/Redux/skills/20240805-15-00-58/Redux.png",
- "Redux/skills/20240818-21-09-39/Redux.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859754/Redux/skills/20240818-21-09-39/Redux.png",
- "Redux/skills/20240830-19-49-04/Redux.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859754/Redux/skills/20240830-19-49-04/Redux.png",
- "Redux-Saga/skills/20240803-20-07-35/ReduxSaga": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859755/Redux-Saga/skills/20240803-20-07-35/ReduxSaga.png",
- "Redux-Saga/skills/20240803-20-48-40/ReduxSaga.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859755/Redux-Saga/skills/20240803-20-48-40/ReduxSaga.png",
- "Redux-Saga/skills/20240803-21-22-19/ReduxSaga.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859757/Redux-Saga/skills/20240803-21-22-19/ReduxSaga.png",
- "Redux-Saga/skills/20240803-21-28-52/ReduxSaga.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859759/Redux-Saga/skills/20240803-21-28-52/ReduxSaga.png",
- "Redux-Saga/skills/20240805-15-00-59/ReduxSaga.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859760/Redux-Saga/skills/20240805-15-00-59/ReduxSaga.png",
- "Redux-Saga/skills/20240818-21-09-42/ReduxSaga.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859761/Redux-Saga/skills/20240818-21-09-42/ReduxSaga.png",
- "Redux-Saga/skills/20240830-19-49-11/ReduxSaga.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859761/Redux-Saga/skills/20240830-19-49-11/ReduxSaga.png",
- "Ruby/skills/20240803-20-07-36/Ruby": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859762/Ruby/skills/20240803-20-07-36/Ruby.png",
- "Ruby/skills/20240803-20-48-42/Ruby.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859763/Ruby/skills/20240803-20-48-42/Ruby.png",
- "Ruby/skills/20240803-21-22-19/Ruby.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859763/Ruby/skills/20240803-21-22-19/Ruby.png",
- "Ruby/skills/20240803-21-28-54/Ruby.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859764/Ruby/skills/20240803-21-28-54/Ruby.png",
- "Ruby/skills/20240805-15-01-01/Ruby.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859765/Ruby/skills/20240805-15-01-01/Ruby.png",
- "Ruby/skills/20240818-21-09-44/Ruby.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859765/Ruby/skills/20240818-21-09-44/Ruby.png",
- "Ruby/skills/20240830-19-49-18/Ruby.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859766/Ruby/skills/20240830-19-49-18/Ruby.png",
- "Rust/skills/20240803-20-07-37/Rust": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859767/Rust/skills/20240803-20-07-37/Rust.png",
- "Rust/skills/20240803-20-48-43/Rust.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859768/Rust/skills/20240803-20-48-43/Rust.png",
- "Rust/skills/20240803-21-22-20/Rust.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859768/Rust/skills/20240803-21-22-20/Rust.png",
- "Rust/skills/20240803-21-28-55/Rust.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859769/Rust/skills/20240803-21-28-55/Rust.png",
- "Rust/skills/20240805-15-01-03/Rust.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859770/Rust/skills/20240805-15-01-03/Rust.png",
- "Rust/skills/20240818-21-09-48/Rust.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859771/Rust/skills/20240818-21-09-48/Rust.png",
- "Rust/skills/20240830-19-49-25/Rust.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859771/Rust/skills/20240830-19-49-25/Rust.png",
- "SQLite/skills/20240803-20-07-41/SQLite": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859772/SQLite/skills/20240803-20-07-41/SQLite.png",
- "SQLite/skills/20240803-20-48-46/SQLite.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859773/SQLite/skills/20240803-20-48-46/SQLite.png",
- "SQLite/skills/20240803-21-22-21/SQLite.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859773/SQLite/skills/20240803-21-22-21/SQLite.png",
- "SQLite/skills/20240803-21-28-59/SQLite.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859774/SQLite/skills/20240803-21-28-59/SQLite.png",
- "SQLite/skills/20240805-15-01-08/SQLite.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859775/SQLite/skills/20240805-15-01-08/SQLite.png",
- "SQLite/skills/20240818-21-09-53/SQLite.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859776/SQLite/skills/20240818-21-09-53/SQLite.png",
- "SQLite/skills/20240830-19-49-43/SQLite.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859777/SQLite/skills/20240830-19-49-43/SQLite.png",
- "Scala/skills/20240803-20-07-39/Scala": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859777/Scala/skills/20240803-20-07-39/Scala.png",
- "Scala/skills/20240803-20-48-44/Scala.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859778/Scala/skills/20240803-20-48-44/Scala.png",
- "Scala/skills/20240803-21-22-21/Scala.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859779/Scala/skills/20240803-21-22-21/Scala.png",
- "Scala/skills/20240803-21-28-56/Scala.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859779/Scala/skills/20240803-21-28-56/Scala.png",
- "Scala/skills/20240805-15-01-04/Scala.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859780/Scala/skills/20240805-15-01-04/Scala.png",
- "Scala/skills/20240818-21-09-50/Scala.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859781/Scala/skills/20240818-21-09-50/Scala.png",
- "Scala/skills/20240830-19-49-33/Scala.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859782/Scala/skills/20240830-19-49-33/Scala.png",
- "SundayTechNuggets.jpeg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859785/SundayTechNuggets.jpg",
- "Swift/skills/20240803-20-07-42/Swift": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859786/Swift/skills/20240803-20-07-42/Swift.png",
- "Swift/skills/20240803-20-48-47/Swift.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859786/Swift/skills/20240803-20-48-47/Swift.png",
- "Swift/skills/20240803-21-22-22/Swift.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859787/Swift/skills/20240803-21-22-22/Swift.png",
- "Swift/skills/20240803-21-29-00/Swift.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859788/Swift/skills/20240803-21-29-00/Swift.png",
- "Swift/skills/20240805-15-01-10/Swift.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859788/Swift/skills/20240805-15-01-10/Swift.png",
- "Swift/skills/20240818-21-09-55/Swift.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859789/Swift/skills/20240818-21-09-55/Swift.png",
- "Swift/skills/20240830-19-49-50/Swift.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859790/Swift/skills/20240830-19-49-50/Swift.png",
- "TensorFlow/skills/20240803-20-07-43/TensorFlow": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859790/TensorFlow/skills/20240803-20-07-43/TensorFlow.png",
- "TensorFlow/skills/20240803-20-48-48/TensorFlow.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859791/TensorFlow/skills/20240803-20-48-48/TensorFlow.png",
- "TensorFlow/skills/20240803-21-22-22/TensorFlow.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859792/TensorFlow/skills/20240803-21-22-22/TensorFlow.png",
- "TensorFlow/skills/20240803-21-29-01/TensorFlow.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859792/TensorFlow/skills/20240803-21-29-01/TensorFlow.png",
- "TensorFlow/skills/20240805-15-01-11/TensorFlow.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859793/TensorFlow/skills/20240805-15-01-11/TensorFlow.png",
- "TensorFlow/skills/20240818-21-09-57/TensorFlow.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859794/TensorFlow/skills/20240818-21-09-57/TensorFlow.png",
- "TensorFlow/skills/20240830-19-49-56/TensorFlow.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859794/TensorFlow/skills/20240830-19-49-56/TensorFlow.png",
- "Terraform/skills/20240803-20-07-44/Terraform": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859795/Terraform/skills/20240803-20-07-44/Terraform.png",
- "Terraform/skills/20240803-20-48-49/Terraform.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859796/Terraform/skills/20240803-20-48-49/Terraform.png",
- "Terraform/skills/20240803-21-22-22/Terraform.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859797/Terraform/skills/20240803-21-22-22/Terraform.png",
- "Terraform/skills/20240803-21-29-02/Terraform.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859798/Terraform/skills/20240803-21-29-02/Terraform.png",
- "Terraform/skills/20240805-15-01-12/Terraform.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859799/Terraform/skills/20240805-15-01-12/Terraform.png",
- "Terraform/skills/20240818-21-09-58/Terraform.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859800/Terraform/skills/20240818-21-09-58/Terraform.png",
- "Terraform/skills/20240830-19-50-02/Terraform.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859800/Terraform/skills/20240830-19-50-02/Terraform.png",
- "TypeScript/skills/20240803-20-07-45/TypeScript": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859801/TypeScript/skills/20240803-20-07-45/TypeScript.png",
- "TypeScript/skills/20240803-20-48-51/TypeScript.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859802/TypeScript/skills/20240803-20-48-51/TypeScript.png",
- "TypeScript/skills/20240803-21-22-23/TypeScript.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859802/TypeScript/skills/20240803-21-22-23/TypeScript.png",
- "TypeScript/skills/20240803-21-29-04/TypeScript.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859803/TypeScript/skills/20240803-21-29-04/TypeScript.png",
- "TypeScript/skills/20240805-15-01-14/TypeScript.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859804/TypeScript/skills/20240805-15-01-14/TypeScript.png",
- "TypeScript/skills/20240818-21-10-00/TypeScript.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859805/TypeScript/skills/20240818-21-10-00/TypeScript.png",
- "TypeScript/skills/20240830-19-50-19/TypeScript.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859805/TypeScript/skills/20240830-19-50-19/TypeScript.png",
- "Vagrant/skills/20240803-20-07-47/Vagrant": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859806/Vagrant/skills/20240803-20-07-47/Vagrant.png",
- "Vagrant/skills/20240803-20-48-53/Vagrant.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859807/Vagrant/skills/20240803-20-48-53/Vagrant.png",
- "Vagrant/skills/20240803-21-22-23/Vagrant.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859807/Vagrant/skills/20240803-21-22-23/Vagrant.png",
- "Vagrant/skills/20240803-21-29-06/Vagrant.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859808/Vagrant/skills/20240803-21-29-06/Vagrant.png",
- "Vagrant/skills/20240805-15-01-16/Vagrant.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859809/Vagrant/skills/20240805-15-01-16/Vagrant.png",
- "Vagrant/skills/20240818-21-10-03/Vagrant.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859810/Vagrant/skills/20240818-21-10-03/Vagrant.png",
- "Vagrant/skills/20240830-19-50-30/Vagrant.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859811/Vagrant/skills/20240830-19-50-30/Vagrant.png",
- "Xamarin/skills/20240803-20-07-49/Xamarin": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859812/Xamarin/skills/20240803-20-07-49/Xamarin.png",
- "Xamarin/skills/20240803-20-48-57/Xamarin.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859813/Xamarin/skills/20240803-20-48-57/Xamarin.png",
- "Xamarin/skills/20240803-21-22-24/Xamarin.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859814/Xamarin/skills/20240803-21-22-24/Xamarin.png",
- "Xamarin/skills/20240803-21-29-08/Xamarin.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859814/Xamarin/skills/20240803-21-29-08/Xamarin.png",
- "Xamarin/skills/20240805-15-01-19/Xamarin.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859815/Xamarin/skills/20240805-15-01-19/Xamarin.png",
- "Xamarin/skills/20240818-21-10-07/Xamarin.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859816/Xamarin/skills/20240818-21-10-07/Xamarin.png",
- "Xamarin/skills/20240830-19-50-40/Xamarin.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859816/Xamarin/skills/20240830-19-50-40/Xamarin.png",
- "benedictvimdey9/feed/20231215-10-32-11/WhatsAppImage2023-12-14at8.18.54AM.jpeg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859818/benedictvimdey9/feed/20231215-10-32-11/WhatsAppImage2023-12-14at8.18.54AM.jpg",
- "benedictvimdey9/profile/20231215-10-29-39/WhatsAppImage2023-12-14at8.18.54AM.jpeg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859819/benedictvimdey9/profile/20231215-10-29-39/WhatsAppImage2023-12-14at8.18.54AM.jpg",
- "briannewton5/feed/20231204-20-42-46/obamaawardingobama.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859822/briannewton5/feed/20231204-20-42-46/obamaawardingobama.png",
- "briannewton5/feed/20231204-21-26-43/obamaawardingobama.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859823/briannewton5/feed/20231204-21-26-43/obamaawardingobama.png",
- "briannewton5/profile/20231204-20-49-16/obamaawardingobama.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859824/briannewton5/profile/20231204-20-49-16/obamaawardingobama.png",
- "briannewton5/profile/20231204-21-29-45/IMG_6523.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859825/briannewton5/profile/20231204-21-29-45/IMG_6523.jpg",
- "iOS/skills/20240803-20-04-23/iOS": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859826/iOS/skills/20240803-20-04-23/iOS.png",
- "iOS/skills/20240803-20-07-08/iOS": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859827/iOS/skills/20240803-20-07-08/iOS.png",
- "iOS/skills/20240803-20-48-11/iOS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859828/iOS/skills/20240803-20-48-11/iOS.png",
- "iOS/skills/20240803-21-22-11/iOS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859829/iOS/skills/20240803-21-22-11/iOS.png",
- "iOS/skills/20240803-21-28-07/iOS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859829/iOS/skills/20240803-21-28-07/iOS.png",
- "iOS/skills/20240805-15-00-23/iOS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859830/iOS/skills/20240805-15-00-23/iOS.png",
- "iOS/skills/20240818-21-08-46/iOS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859830/iOS/skills/20240818-21-08-46/iOS.png",
- "iOS/skills/20240830-19-46-36/iOS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859831/iOS/skills/20240830-19-46-36/iOS.png",
- "jQuery/skills/20240803-20-04-29/jQuery": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859832/jQuery/skills/20240803-20-04-29/jQuery.png",
- "jQuery/skills/20240803-20-07-12/jQuery": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859832/jQuery/skills/20240803-20-07-12/jQuery.png",
- "jQuery/skills/20240803-20-48-15/jQuery.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859833/jQuery/skills/20240803-20-48-15/jQuery.png",
- "jQuery/skills/20240803-21-22-12/jQuery.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859834/jQuery/skills/20240803-21-22-12/jQuery.png",
- "jQuery/skills/20240803-21-28-13/jQuery.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859834/jQuery/skills/20240803-21-28-13/jQuery.png",
- "jQuery/skills/20240805-15-00-28/jQuery.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859835/jQuery/skills/20240805-15-00-28/jQuery.png",
- "jQuery/skills/20240818-21-08-53/jQuery.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859836/jQuery/skills/20240818-21-08-53/jQuery.png",
- "jQuery/skills/20240830-19-47-00/jQuery.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859836/jQuery/skills/20240830-19-47-00/jQuery.png",
- "johnsonquame20/profile/20241112-13-30-56/my_avatar.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859838/johnsonquame20/profile/20241112-13-30-56/my_avatar.png",
- "jonathanmarkin8/feed/20240209-06-49-27/1000150577.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859839/jonathanmarkin8/feed/20240209-06-49-27/1000150577.jpg",
- "jonathanmarkin8/profile/20250115-11-54-17/pofile.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859842/jonathanmarkin8/profile/20250115-11-54-17/pofile.jpg",
- "kwesidadson1/profile/20231211-22-08-09/WhatsAppImage2023-08-22at6.23.10PM.jpeg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859843/kwesidadson1/profile/20231211-22-08-09/WhatsAppImage2023-08-22at6.23.10PM.jpg",
- "marvinkudjo45/profile/20231207-21-44-49/thug.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859844/marvinkudjo45/profile/20231207-21-44-49/thug.jpg",
- "neilohene161/profile/20240120-12-27-45/me.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859845/neilohene161/profile/20240120-12-27-45/me.png",
- "philipabakah43/profile/20231218-23-03-37/photo1697650501.jpeg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859846/philipabakah43/profile/20231218-23-03-37/photo1697650501.jpg",
- "ransfordgenesis/feed/20231204-21-17-04/meme.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859847/ransfordgenesis/feed/20231204-21-17-04/meme.jpg",
- "ransfordgenesis/profile/20231207-16-51-17/bighead.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859847/ransfordgenesis/profile/20231207-16-51-17/bighead.jpg",
- "ransfordgenesis/profile/20231207-16-51-21/bighead.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859848/ransfordgenesis/profile/20231207-16-51-21/bighead.jpg",
- "stncrmutilities/AWS/skills/20240803-20-03-57/AWS": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859849/stncrmutilities/AWS/skills/20240803-20-03-57/AWS.png",
- "stncrmutilities/AWS/skills/20240803-20-06-47/AWS": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859850/stncrmutilities/AWS/skills/20240803-20-06-47/AWS.png",
- "stncrmutilities/AWS/skills/20240803-20-45-22/AWS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859852/stncrmutilities/AWS/skills/20240803-20-45-22/AWS.png",
- "stncrmutilities/AWS/skills/20240803-20-47-47/AWS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859852/stncrmutilities/AWS/skills/20240803-20-47-47/AWS.png",
- "stncrmutilities/AWS/skills/20240803-21-22-04/AWS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859853/stncrmutilities/AWS/skills/20240803-21-22-04/AWS.png",
- "stncrmutilities/AWS/skills/20240803-21-27-40/AWS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859853/stncrmutilities/AWS/skills/20240803-21-27-40/AWS.png",
- "stncrmutilities/AWS/skills/20240805-14-59-51/AWS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859854/stncrmutilities/AWS/skills/20240805-14-59-51/AWS.png",
- "stncrmutilities/AWS/skills/20240818-21-07-45/AWS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859855/stncrmutilities/AWS/skills/20240818-21-07-45/AWS.png",
- "stncrmutilities/AWS/skills/20240830-19-44-41/AWS.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859855/stncrmutilities/AWS/skills/20240830-19-44-41/AWS.png",
- "stncrmutilities/Android/skills/20240803-19-48-30/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859856/stncrmutilities/Android/skills/20240803-19-48-30/Android.png",
- "stncrmutilities/Android/skills/20240803-19-50-17/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859857/stncrmutilities/Android/skills/20240803-19-50-17/Android.png",
- "stncrmutilities/Android/skills/20240803-19-51-07/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859858/stncrmutilities/Android/skills/20240803-19-51-07/Android.png",
- "stncrmutilities/Android/skills/20240803-19-51-47/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859859/stncrmutilities/Android/skills/20240803-19-51-47/Android.png",
- "stncrmutilities/Android/skills/20240803-19-53-00/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859859/stncrmutilities/Android/skills/20240803-19-53-00/Android.png",
- "stncrmutilities/Android/skills/20240803-19-53-27/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859860/stncrmutilities/Android/skills/20240803-19-53-27/Android.png",
- "stncrmutilities/Android/skills/20240803-19-54-26/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859861/stncrmutilities/Android/skills/20240803-19-54-26/Android.png",
- "stncrmutilities/Android/skills/20240803-19-56-10/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859861/stncrmutilities/Android/skills/20240803-19-56-10/Android.png",
- "stncrmutilities/Android/skills/20240803-19-56-32/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859862/stncrmutilities/Android/skills/20240803-19-56-32/Android.png",
- "stncrmutilities/Android/skills/20240803-19-57-51/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859863/stncrmutilities/Android/skills/20240803-19-57-51/Android.png",
- "stncrmutilities/Android/skills/20240803-19-58-51/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859863/stncrmutilities/Android/skills/20240803-19-58-51/Android.png",
- "stncrmutilities/Android/skills/20240803-20-03-16/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859864/stncrmutilities/Android/skills/20240803-20-03-16/Android.png",
- "stncrmutilities/Android/skills/20240803-20-03-52/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859865/stncrmutilities/Android/skills/20240803-20-03-52/Android.png",
- "stncrmutilities/Android/skills/20240803-20-06-42/Android": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859866/stncrmutilities/Android/skills/20240803-20-06-42/Android.png",
- "stncrmutilities/Android/skills/20240803-20-45-16/Android.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859867/stncrmutilities/Android/skills/20240803-20-45-16/Android.png",
- "stncrmutilities/Android/skills/20240803-20-47-42/Android.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859867/stncrmutilities/Android/skills/20240803-20-47-42/Android.png",
- "stncrmutilities/Android/skills/20240803-21-22-03/Android.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859868/stncrmutilities/Android/skills/20240803-21-22-03/Android.png",
- "stncrmutilities/Android/skills/20240803-21-27-33/Android.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859869/stncrmutilities/Android/skills/20240803-21-27-33/Android.png",
- "stncrmutilities/Android/skills/20240805-14-59-44/Android.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859869/stncrmutilities/Android/skills/20240805-14-59-44/Android.png",
- "stncrmutilities/Android/skills/20240818-21-07-35/Android.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859870/stncrmutilities/Android/skills/20240818-21-07-35/Android.png",
- "stncrmutilities/Android/skills/20240830-19-44-12/Android.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859871/stncrmutilities/Android/skills/20240830-19-44-12/Android.png",
- "stncrmutilities/Angular/skills/20240803-19-48-31/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859872/stncrmutilities/Angular/skills/20240803-19-48-31/Angular.png",
- "stncrmutilities/Angular/skills/20240803-19-50-19/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859873/stncrmutilities/Angular/skills/20240803-19-50-19/Angular.png",
- "stncrmutilities/Angular/skills/20240803-19-51-08/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859873/stncrmutilities/Angular/skills/20240803-19-51-08/Angular.png",
- "stncrmutilities/Angular/skills/20240803-19-51-49/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859874/stncrmutilities/Angular/skills/20240803-19-51-49/Angular.png",
- "stncrmutilities/Angular/skills/20240803-19-53-01/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859875/stncrmutilities/Angular/skills/20240803-19-53-01/Angular.png",
- "stncrmutilities/Angular/skills/20240803-19-53-28/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859876/stncrmutilities/Angular/skills/20240803-19-53-28/Angular.png",
- "stncrmutilities/Angular/skills/20240803-19-54-27/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859877/stncrmutilities/Angular/skills/20240803-19-54-27/Angular.png",
- "stncrmutilities/Angular/skills/20240803-19-56-11/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859878/stncrmutilities/Angular/skills/20240803-19-56-11/Angular.png",
- "stncrmutilities/Angular/skills/20240803-19-56-33/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859879/stncrmutilities/Angular/skills/20240803-19-56-33/Angular.png",
- "stncrmutilities/Angular/skills/20240803-19-57-52/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859879/stncrmutilities/Angular/skills/20240803-19-57-52/Angular.png",
- "stncrmutilities/Angular/skills/20240803-19-58-53/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859880/stncrmutilities/Angular/skills/20240803-19-58-53/Angular.png",
- "stncrmutilities/Angular/skills/20240803-20-03-17/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859881/stncrmutilities/Angular/skills/20240803-20-03-17/Angular.png",
- "stncrmutilities/Angular/skills/20240803-20-03-53/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859881/stncrmutilities/Angular/skills/20240803-20-03-53/Angular.png",
- "stncrmutilities/Angular/skills/20240803-20-06-43/Angular": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859882/stncrmutilities/Angular/skills/20240803-20-06-43/Angular.png",
- "stncrmutilities/Angular/skills/20240803-20-45-18/Angular.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859883/stncrmutilities/Angular/skills/20240803-20-45-18/Angular.png",
- "stncrmutilities/Angular/skills/20240803-20-47-43/Angular.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859883/stncrmutilities/Angular/skills/20240803-20-47-43/Angular.png",
- "stncrmutilities/Angular/skills/20240803-21-22-03/Angular.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859884/stncrmutilities/Angular/skills/20240803-21-22-03/Angular.png",
- "stncrmutilities/Angular/skills/20240803-21-27-34/Angular.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859885/stncrmutilities/Angular/skills/20240803-21-27-34/Angular.png",
- "stncrmutilities/Angular/skills/20240805-14-59-46/Angular.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859886/stncrmutilities/Angular/skills/20240805-14-59-46/Angular.png",
- "stncrmutilities/Angular/skills/20240818-21-07-38/Angular.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859887/stncrmutilities/Angular/skills/20240818-21-07-38/Angular.png",
- "stncrmutilities/Angular/skills/20240830-19-44-19/Angular.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859887/stncrmutilities/Angular/skills/20240830-19-44-19/Angular.png",
- "stncrmutilities/Ansible/skills/20240803-20-03-54/Ansible": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859888/stncrmutilities/Ansible/skills/20240803-20-03-54/Ansible.png",
- "stncrmutilities/Ansible/skills/20240803-20-06-45/Ansible": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859889/stncrmutilities/Ansible/skills/20240803-20-06-45/Ansible.png",
- "stncrmutilities/Ansible/skills/20240803-20-45-19/Ansible.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859890/stncrmutilities/Ansible/skills/20240803-20-45-19/Ansible.png",
- "stncrmutilities/Ansible/skills/20240803-20-47-44/Ansible.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859890/stncrmutilities/Ansible/skills/20240803-20-47-44/Ansible.png",
- "stncrmutilities/Ansible/skills/20240803-21-22-04/Ansible.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859891/stncrmutilities/Ansible/skills/20240803-21-22-04/Ansible.png",
- "stncrmutilities/Ansible/skills/20240803-21-27-36/Ansible.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859892/stncrmutilities/Ansible/skills/20240803-21-27-36/Ansible.png",
- "stncrmutilities/Ansible/skills/20240805-14-59-48/Ansible.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859892/stncrmutilities/Ansible/skills/20240805-14-59-48/Ansible.png",
- "stncrmutilities/Ansible/skills/20240818-21-07-41/Ansible.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859893/stncrmutilities/Ansible/skills/20240818-21-07-41/Ansible.png",
- "stncrmutilities/Ansible/skills/20240830-19-44-27/Ansible.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859894/stncrmutilities/Ansible/skills/20240830-19-44-27/Ansible.png",
- "tonnybrightsogli176/profile/20231204-21-11-12/bigquery_table.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859897/tonnybrightsogli176/profile/20231204-21-11-12/bigquery_table.png",
- "topboyasante/feed/20231206-12-16-40/jb.webp": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859898/topboyasante/feed/20231206-12-16-40/jb.webp",
- "topboyasante/feed/20231206-21-37-34/IMG_1413.jpeg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859900/topboyasante/feed/20231206-21-37-34/IMG_1413.jpg",
- "topboyasante/profile/20231206-12-08-11/jb.webp": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859901/topboyasante/profile/20231206-12-08-11/jb.webp",
- "tutorials/app/.env": "ERROR: Empty file",
- "tutorials/app/stdocker/manage.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859902/tutorials/app/stdocker/manage.py",
- "tutorials/app/stdocker/stdocker/__init__.py": "ERROR: Empty file",
- "tutorials/app/stdocker/stdocker/asgi.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859903/tutorials/app/stdocker/stdocker/asgi.py",
- "tutorials/app/stdocker/stdocker/settings.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859903/tutorials/app/stdocker/stdocker/settings.py",
- "tutorials/app/stdocker/stdocker/urls.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859905/tutorials/app/stdocker/stdocker/urls.py",
- "tutorials/app/stdocker/stdocker/wsgi.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859906/tutorials/app/stdocker/stdocker/wsgi.py",
- "tutorials/hello/Dockerfile": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859906/tutorials/hello/Dockerfile",
- "tutorials/hello/__pycache__/main.cpython-311.pyc": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859907/tutorials/hello/__pycache__/main.cpython-311.pyc",
- "tutorials/hello/docker-compose.yml": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859907/tutorials/hello/docker-compose.yml",
- "tutorials/hello/main.py": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859908/tutorials/hello/main.py",
- "tutorials/hello/pyproject.toml": "https://res.cloudinary.com/db5tuaqog/raw/upload/v1774859909/tutorials/hello/pyproject.toml",
- "williamtsikata23/feed/20231211-22-49-01/20231203_223901.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859910/williamtsikata23/feed/20231211-22-49-01/20231203_223901.jpg",
- "yawaddodiabene10/feed/20231202-23-37-43/Screenshot2023-11-30232300.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859911/yawaddodiabene10/feed/20231202-23-37-43/Screenshot2023-11-30232300.png",
- "yawaddodiabene10/feed/20231202-23-42-14/Screenshot2023-06-12211851.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859912/yawaddodiabene10/feed/20231202-23-42-14/Screenshot2023-06-12211851.png",
- "yawaddodiabene10/feed/20240408-22-01-52/Screenshot2024-04-08202144.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859913/yawaddodiabene10/feed/20240408-22-01-52/Screenshot2024-04-08202144.png",
- "yawaddodiabene10/profile/20231204-14-39-35/erwinsmith.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859914/yawaddodiabene10/profile/20231204-14-39-35/erwinsmith.jpg",
- "yawaddodiabene10/profile/20231204-19-06-48/Minimalist-4k-Wallpaper-Hd-.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859916/yawaddodiabene10/profile/20231204-19-06-48/Minimalist-4k-Wallpaper-Hd-.jpg",
- "yawaddodiabene10/profile/20231204-19-08-35/erwinsmith.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859917/yawaddodiabene10/profile/20231204-19-08-35/erwinsmith.jpg",
- "yawaddodiabene10/profile/20231204-19-12-02/erwinsmith.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859918/yawaddodiabene10/profile/20231204-19-12-02/erwinsmith.jpg",
- "yawaddodiabene10/profile/20231206-13-33-00/Screenshot2023-12-06112258.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859921/yawaddodiabene10/profile/20231206-13-33-00/Screenshot2023-12-06112258.png",
- "yawaddodiabene10/profile/20231206-13-34-05/erwinsmith.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859922/yawaddodiabene10/profile/20231206-13-34-05/erwinsmith.jpg",
- "yawaddodiabene10/profile/20231210-21-02-40/richard-horvath-RAZU_R66vUc-unsplash.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859923/yawaddodiabene10/profile/20231210-21-02-40/richard-horvath-RAZU_R66vUc-unsplash.jpg",
- "yawaddodiabene10/profile/20231211-22-45-28/richard-horvath-_nWaeTF6qo0-unsplash.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859924/yawaddodiabene10/profile/20231211-22-45-28/richard-horvath-_nWaeTF6qo0-unsplash.jpg",
- "yawaddodiabene10/profile/20231211-22-49-02/richard-horvath-RAZU_R66vUc-unsplash.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859925/yawaddodiabene10/profile/20231211-22-49-02/richard-horvath-RAZU_R66vUc-unsplash.jpg",
- "yawaddodiabene10/profile/20240215-21-36-17/jakob-owens-n5wwck8ES4w-unsplash.jpg": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859927/yawaddodiabene10/profile/20240215-21-36-17/jakob-owens-n5wwck8ES4w-unsplash.jpg",
- "yawaddodiabene10/profile/20240408-22-12-21/7e391e107968361.5fb38e8bb826d.png": "https://res.cloudinary.com/db5tuaqog/image/upload/v1774859928/yawaddodiabene10/profile/20240408-22-12-21/7e391e107968361.5fb38e8bb826d.png"
-}
\ No newline at end of file
diff --git a/services/auth_service.py b/services/auth_service.py
index 255db6f..3e11153 100644
--- a/services/auth_service.py
+++ b/services/auth_service.py
@@ -76,7 +76,8 @@ def login(self, email: str, password: str) -> Token:
refresh_token=get_refresh_token(str(user.id)),
token_type="Bearer",
is_active=user.is_active,
- user_status=user.status
+ user_status=user.status,
+ role=user.role
)
def refresh(self, refresh_token: str) -> Token:
diff --git a/services/org_chart_service.py b/services/org_chart_service.py
index 57675ac..0798398 100644
--- a/services/org_chart_service.py
+++ b/services/org_chart_service.py
@@ -5,6 +5,7 @@
from api.api_models.user import OrgChartNode
from db.models.users import User
from db.repository.org_chart import OrgChartRepository
+from utils.enums import UserStatus
class OrgChartService:
@@ -76,22 +77,51 @@ def _recurse(uid: int) -> Optional[OrgChartNode]:
# Read operations
# ------------------------------------------------------------------
- def get_direct_subordinates(self, user_id: int) -> list[User]:
+ def get_direct_subordinates(self, user_id: int, filter_active: bool = False) -> list[User]:
+ """Get direct subordinates of a user.
+
+ Args:
+ user_id: The manager's user ID
+ filter_active: If True, only return ACCEPTED + is_active users (default: False - show all assigned team members)
+ """
self._get_user_or_404(user_id)
- return self.repo.get_direct_subordinates(user_id)
+ return self.repo.get_direct_subordinates(user_id, filter_active=filter_active)
- def get_subtree(self, root_id: int, max_depth: Optional[int] = None) -> OrgChartNode:
+ def get_subtree(self, root_id: int, max_depth: Optional[int] = None, filter_active: bool = True) -> OrgChartNode:
+ """Get the org chart subtree for a given root user.
+
+ Args:
+ root_id: The root user's ID
+ max_depth: Maximum depth to traverse
+ filter_active: If True, only include ACCEPTED + is_active users (default: True)
+ """
max_depth = max_depth if max_depth is not None else self.DEFAULT_MAX_DEPTH
self._get_user_or_404(root_id)
rows = self.repo.get_subtree_ids(root_id, max_depth)
user_ids = [r["id"] for r in rows]
users = self.repo.get_users_by_ids(user_ids)
users_by_id = {u.id: u for u in users}
- return self._build_tree(users_by_id, root_id, rows)
+ tree_node = self._build_tree_filtered(users_by_id, root_id, rows, filter_active)
+ if tree_node is None:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=(
+ f"User with id {root_id} is not available in the filtered org chart"
+ if filter_active
+ else f"Unable to build org chart subtree for user with id {root_id}"
+ ),
+ )
+ return tree_node
+
+ def get_full_org_chart(self, max_depth: Optional[int] = None, filter_active: bool = True) -> list[OrgChartNode]:
+ """Get the full organizational chart.
- def get_full_org_chart(self, max_depth: Optional[int] = None) -> list[OrgChartNode]:
+ Args:
+ max_depth: Maximum depth to traverse for each root
+ filter_active: If True, only include ACCEPTED + is_active users (default: True)
+ """
max_depth = max_depth if max_depth is not None else self.DEFAULT_MAX_DEPTH
- roots = self.repo.get_root_users()
+ roots = self.repo.get_root_users(filter_active=filter_active)
if not roots:
return []
result = []
@@ -100,9 +130,59 @@ def get_full_org_chart(self, max_depth: Optional[int] = None) -> list[OrgChartNo
user_ids = [r["id"] for r in rows]
users = self.repo.get_users_by_ids(user_ids)
users_by_id = {u.id: u for u in users}
- result.append(self._build_tree(users_by_id, root.id, rows))
+
+ # Only build tree if root user exists (it should since it came from get_root_users)
+ if root.id in users_by_id:
+ tree_node = self._build_tree_filtered(users_by_id, root.id, rows, filter_active)
+ if tree_node:
+ result.append(tree_node)
return result
+ def _build_tree_filtered(self, users_by_id: dict[int, User], root_id: int, rows: list[dict], filter_active: bool) -> Optional[OrgChartNode]:
+ """Build tree with optional filtering of ACCEPTED + is_active users."""
+ children_map: dict[int, list[int]] = {}
+ for row in rows:
+ pid = row["manager_id"]
+ if pid is not None and pid in users_by_id:
+ children_map.setdefault(pid, []).append(row["id"])
+
+ visited: set[int] = set()
+
+ def _recurse(uid: int) -> Optional[OrgChartNode]:
+ if uid in visited or uid not in users_by_id:
+ return None
+ visited.add(uid)
+ u = users_by_id[uid]
+
+ # Skip if filtering is enabled and user doesn't match
+ if filter_active and (u.status != UserStatus.ACCEPTED or not u.is_active):
+ return None
+
+ child_ids = children_map.get(uid, [])
+ subs = []
+ for cid in child_ids:
+ node = _recurse(cid)
+ if node is not None:
+ subs.append(node)
+ return OrgChartNode(
+ id=u.id,
+ first_name=u.first_name,
+ last_name=u.last_name,
+ username=u.username,
+ profile_pic_url=u.profile_pic_url,
+ role=u.role,
+ stack=u.stack,
+ manager_id=u.manager_id,
+ status=u.status,
+ is_active=u.is_active,
+ subordinates=subs,
+ )
+
+ node = _recurse(root_id)
+ if node is None:
+ return None
+ return node
+
def get_manager(self, user_id: int) -> Optional[User]:
user = self._get_user_or_404(user_id)
if user.manager_id is None:
diff --git a/services/project_service.py b/services/project_service.py
index 84e784a..f86d9d4 100644
--- a/services/project_service.py
+++ b/services/project_service.py
@@ -5,6 +5,7 @@
from api.api_models.projects import CreateProject, UpdateProject
from db.models.projects import Project
from db.models.users import User
+from db.models.users_projects import UserProject
from db.repository.projects import ProjectRepository
from db.repository.skills import SkillRepository
from db.repository.stacks import StackRepository
@@ -20,26 +21,39 @@ def __init__(self, project_repo: ProjectRepository, user_repo: UserRepository,
self.stack_repo = stack_repo
self.skill_repo = skill_repo
+ def _enrich_project_members_with_team(self, project: Project) -> Project:
+ """Add team role data from users_projects join table to project members."""
+ if not project.members:
+ return project
+
+ user_projects = self.project_repo.db.query(UserProject).filter(
+ UserProject.project_id == project.id
+ ).all()
+ teams_by_user_id = {user_project.user_id: user_project.team for user_project in user_projects}
+
+ for member in project.members:
+ team = teams_by_user_id.get(member.id)
+ if team is not None:
+ # Dynamically set the team attribute for Pydantic serialization
+ setattr(member, 'team', team)
+ return project
+
def create_project(self, project_data: CreateProject) -> Project:
manager = self.user_repo.get_by_id(project_data.manager_id)
if not manager:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Manager not found")
+ # Members are added separately via /add/{userId} endpoint, not during creation
+ if project_data.members:
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail="Members cannot be added during project creation. "
+ "Use the POST /projects/{project_id}/add/{user_id} endpoint to add members with their team roles."
+ )
+
data = project_data.model_dump(exclude=["members", "stacks", "project_tools"])
new_project = Project(**data)
- if project_data.members:
- seen = []
- for member_id in project_data.members:
- member = self.user_repo.get_by_id(member_id)
- if not member:
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
- detail=f"User {member_id} not found")
- if not member.is_active:
- raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,
- detail=f"{member.first_name} is not an active member")
- new_project.members.append(member)
-
if project_data.stacks:
seen = []
for stack_id in project_data.stacks:
@@ -66,18 +80,21 @@ def create_project(self, project_data: CreateProject) -> Project:
new_project.project_tools.append(skill)
seen.append(skill_id)
- return self.project_repo.save(new_project)
+ saved = self.project_repo.save(new_project)
+ return self._enrich_project_members_with_team(saved)
def update_project(self, project_id: int, update_data: UpdateProject) -> Project:
project = self.project_repo.get_by_id(project_id)
if not project:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
- if update_data.manager_id and update_data.manager_id != project.manager_id:
- if not self.user_repo.get_by_id(update_data.manager_id):
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Manager not found")
- return self.project_repo.update(
- project, update_data.model_dump(exclude=["members", "stacks", "project_tools"])
+ if update_data.manager_id is not None:
+ if update_data.manager_id != project.manager_id:
+ if not self.user_repo.get_by_id(update_data.manager_id):
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Manager not found")
+ updated = self.project_repo.update(
+ project, update_data.model_dump(exclude=["members", "stacks", "project_tools"], exclude_none=True)
)
+ return self._enrich_project_members_with_team(updated)
def delete_project(self, project_id: int) -> None:
project = self.project_repo.get_by_id(project_id)
@@ -89,7 +106,7 @@ def get_project(self, project_id: int) -> Project:
project = self.project_repo.get_by_id(project_id)
if not project:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
- return project
+ return self._enrich_project_members_with_team(project)
def get_all_query(self):
return self.project_repo.get_all_paginated_query()
@@ -119,4 +136,13 @@ def remove_member(self, project_id: int, user_id: int) -> None:
def get_project_members(self, project_id: int, team: Optional[ProjectTeam]) -> List[User]:
if not self.project_repo.get_by_id(project_id):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
- return self.project_repo.get_members(project_id, team)
+ members = self.project_repo.get_members(project_id, team)
+ # Enrich members with team data
+ for member in members:
+ user_project = self.project_repo.db.query(UserProject).filter(
+ UserProject.user_id == member.id,
+ UserProject.project_id == project_id
+ ).first()
+ if user_project:
+ object.__setattr__(member, 'team', user_project.team)
+ return members
diff --git a/services/skill_service.py b/services/skill_service.py
index c79d646..b08ea1e 100644
--- a/services/skill_service.py
+++ b/services/skill_service.py
@@ -45,9 +45,25 @@ def populate_skills(self) -> dict:
from db.database import create_roles
from utils.endpoints_status import create_signup_endpoint
from utils.tools import tools as skills_data, get_skills_image
+ import logging
- create_roles()
- create_signup_endpoint(status=True)
+ logger = logging.getLogger(__name__)
+
+ # Try to create roles - catch expected "already exists" exceptions
+ try:
+ create_roles()
+ except Exception as e:
+ # Expected when roles already exist; log unexpected errors for diagnostics
+ if "already" not in str(e).lower():
+ logger.warning(f"Unexpected error creating roles: {e}")
+
+ # Try to create signup endpoint - catch expected "already exists" exceptions
+ try:
+ create_signup_endpoint(status=True)
+ except Exception as e:
+ # Expected when endpoint already exists; log unexpected errors for diagnostics
+ if "already" not in str(e).lower():
+ logger.warning(f"Unexpected error creating signup endpoint: {e}")
try:
last_skill = None
@@ -65,7 +81,12 @@ def search_skills(self, name: str) -> list[dict]:
skills = self.skill_repo.get_all_flat()
threshold = 78
return [
- {"skill_id": skill.id, "skill_name": skill.name}
+ {
+ "skill_id": skill.id,
+ "skill_name": skill.name,
+
+ "image_url": skill.image_url or ""
+ }
for skill in skills
if fuzz.partial_ratio(name.lower(), skill.name.lower()) >= threshold
]
diff --git a/services/technical_task_service.py b/services/technical_task_service.py
index aa43ea5..72d7aab 100644
--- a/services/technical_task_service.py
+++ b/services/technical_task_service.py
@@ -55,22 +55,62 @@ def delete_task(self, task_id: int) -> None:
# --- Submissions ---
def create_submission(self, current_user: User, data: dict) -> TechnicalTaskSubmission:
- user_exp = get_key_by_value(current_user.years_of_experience)
+ # Check if user has required profile information
+ if not current_user.stack_id:
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail="Please complete your profile by selecting a tech stack before submitting."
+ )
+
+ if current_user.years_of_experience is None:
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail="Please complete your profile by adding years of experience before submitting."
+ )
+
+ try:
+ user_exp = get_key_by_value(current_user.years_of_experience)
+ except ValueError as e:
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail=str(e)
+ )
+
+ if user_exp is None:
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail=(
+ f"Invalid years_of_experience value: {current_user.years_of_experience}. "
+ f"Please update your profile with a valid experience level before submitting."
+ )
+ )
task = self.task_repo.get_by_stack_and_level(current_user.stack_id, user_exp)
+
if not task:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
- detail="Cannot find a task for this submission"
+ detail=(
+ f"No technical task found for your stack and experience level "
+ f"(stack_id={current_user.stack_id}, experience_level={user_exp}). "
+ f"Please contact admin at info@slightlytechie.com."
+ )
)
- if self.submission_repo.get_existing_for_user(current_user.id):
+
+ # Check if user already submitted
+ existing_submission = self.submission_repo.get_existing_for_user(current_user.id)
+ if existing_submission:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
- detail="Multiple submissions not allowed. Kindly contact admin."
+ detail="You have already submitted a task. Multiple submissions are not allowed. Contact admin if you need to update your submission."
)
+
try:
return self.submission_repo.create(task.id, current_user.id, data)
except Exception as e:
- raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail=f"Failed to submit task: {str(e)}"
+ )
def get_all_submissions(self) -> list[TechnicalTaskSubmission]:
submissions = self.submission_repo.get_all()
diff --git a/services/user_service.py b/services/user_service.py
index 4ed8a70..2d792de 100644
--- a/services/user_service.py
+++ b/services/user_service.py
@@ -77,6 +77,10 @@ async def update_user_status(self, user_id: int, new_status: UserStatus) -> User
detail=settings.ERRORS.get("INVALID ID"))
self.user_repo.update_status(user, new_status)
+ # Activate user when status becomes ACCEPTED
+ if new_status == UserStatus.ACCEPTED and not user.is_active:
+ self.user_repo.activate(user)
+
if new_status in (UserStatus.ACCEPTED, UserStatus.REJECTED):
try:
template = self.email_template_repo.get_by_name(new_status.value)
diff --git a/test/test_org_chart.py b/test/test_org_chart.py
index 9ccefa4..bc3741e 100644
--- a/test/test_org_chart.py
+++ b/test/test_org_chart.py
@@ -73,7 +73,11 @@ def org_tree(client, test_user, test_user1, session, admin_headers):
class TestAdminGetFullOrgChart:
"""GET /api/v1/users/org-chart (admin only)"""
- def test_returns_roots(self, client, test_user, test_user1, admin_headers):
+ def test_returns_roots(self, client, test_user, test_user1, admin_headers, session):
+ # Make users ACCEPTED so they appear in org chart
+ _make_admin_accepted(session, test_user)
+ _make_admin_accepted(session, test_user1)
+
res = client.get("/api/v1/users/org-chart", headers=admin_headers)
assert res.status_code == 200
data = res.json()
@@ -82,7 +86,11 @@ def test_returns_roots(self, client, test_user, test_user1, admin_headers):
assert test_user["id"] in root_ids
assert test_user1["id"] in root_ids
- def test_returns_tree_structure(self, client, org_tree, admin_headers):
+ def test_returns_tree_structure(self, client, org_tree, admin_headers, session):
+ # Make users ACCEPTED so they appear in org chart
+ _make_admin_accepted(session, org_tree["root"])
+ _make_admin_accepted(session, org_tree["child"])
+
res = client.get("/api/v1/users/org-chart", headers=admin_headers)
assert res.status_code == 200
data = res.json()
@@ -144,7 +152,11 @@ def test_forbidden_for_non_admin(self, client, test_user, user_headers):
class TestAdminGetUserOrgChart:
"""GET /api/v1/users/{user_id}/org-chart (admin only)"""
- def test_returns_subtree(self, client, org_tree, admin_headers):
+ def test_returns_subtree(self, client, org_tree, admin_headers, session):
+ # Make users ACCEPTED so they appear in org chart
+ _make_admin_accepted(session, org_tree["root"])
+ _make_admin_accepted(session, org_tree["child"])
+
res = client.get(
f"/api/v1/users/{org_tree['root']['id']}/org-chart",
headers=admin_headers,
@@ -155,7 +167,11 @@ def test_returns_subtree(self, client, org_tree, admin_headers):
sub_ids = {s["id"] for s in data["subordinates"]}
assert org_tree["child"]["id"] in sub_ids
- def test_leaf_node_subtree(self, client, org_tree, admin_headers):
+ def test_leaf_node_subtree(self, client, org_tree, admin_headers, session):
+ # Make users ACCEPTED so they appear in org chart
+ _make_admin_accepted(session, org_tree["root"])
+ _make_admin_accepted(session, org_tree["child"])
+
res = client.get(
f"/api/v1/users/{org_tree['child']['id']}/org-chart",
headers=admin_headers,
@@ -317,7 +333,10 @@ def test_view_user_subordinates(self, client, org_tree, accepted_user_headers, s
assert org_tree["child"]["id"] in sub_ids
def test_view_user_org_chart(self, client, org_tree, accepted_user_headers, session):
+ # Make both users ACCEPTED so they appear in org chart
+ _make_admin_accepted(session, org_tree["root"])
_make_admin_accepted(session, org_tree["child"])
+
res = client.get(
f"/api/v1/users/view/{org_tree['root']['id']}/org-chart",
headers=accepted_user_headers,
diff --git a/test/test_profile_page.py b/test/test_profile_page.py
index b83e975..325b77a 100644
--- a/test/test_profile_page.py
+++ b/test/test_profile_page.py
@@ -154,7 +154,14 @@ def test_get_user_info(client, test_user):
assert "phone_number" in response.json()["data"]
-def test_get_all_profile(client, test_user, test_users, test_stacks, populate_skills):
+def test_get_all_profile(client, test_user, test_users, test_stacks, populate_skills, session):
+ # Set test_user to ACCEPTED status so they appear in active directory
+ from db.models.users import User
+ from utils.enums import UserStatus
+ user = session.query(User).filter(User.id == test_user["id"]).first()
+ user.status = UserStatus.ACCEPTED
+ session.commit()
+
login_res = client.post(
"/api/v1/users/login", data={"username": test_user["email"], "password": test_user["password"]})
token = login_res.json()["token"]
diff --git a/test/test_project_manager_validation.py b/test/test_project_manager_validation.py
new file mode 100644
index 0000000..5b654e2
--- /dev/null
+++ b/test/test_project_manager_validation.py
@@ -0,0 +1,166 @@
+"""Tests for Project service manager validation"""
+import pytest
+from unittest.mock import Mock
+from fastapi import HTTPException
+from api.api_models.projects import UpdateProject
+from services.project_service import ProjectService
+from db.repository.projects import ProjectRepository
+from db.repository.users import UserRepository
+from db.repository.skills import SkillRepository
+from db.repository.stacks import StackRepository
+
+
+class TestProjectManagerValidation:
+ """Test project manager assignment and validation"""
+
+ def test_update_manager_validates_manager_exists(self):
+ """Updating project manager should validate manager exists"""
+ project_repo = Mock(spec=ProjectRepository)
+ user_repo = Mock(spec=UserRepository)
+ skill_repo = Mock(spec=SkillRepository)
+ stack_repo = Mock(spec=StackRepository)
+
+ service = ProjectService(project_repo, user_repo, stack_repo, skill_repo)
+
+ # Mock existing project
+ mock_project = Mock()
+ mock_project.id = 1
+ mock_project.manager_id = 1
+ project_repo.get_by_id = Mock(return_value=mock_project)
+
+ # Mock user_repo to return None (manager doesn't exist)
+ user_repo.get_by_id = Mock(return_value=None)
+
+ update_data = UpdateProject(manager_id=999, name=None, description=None, project_type=None, project_priority=None, project_tools=None)
+
+ with pytest.raises(HTTPException) as exc_info:
+ service.update_project(1, update_data)
+
+ assert exc_info.value.status_code == 404
+ assert "Manager not found" in exc_info.value.detail
+
+ def test_update_manager_skips_validation_when_same(self):
+ """Updating manager to same value should skip existence check"""
+ project_repo = Mock(spec=ProjectRepository)
+ user_repo = Mock(spec=UserRepository)
+ skill_repo = Mock(spec=SkillRepository)
+ stack_repo = Mock(spec=StackRepository)
+
+ service = ProjectService(project_repo, user_repo, stack_repo, skill_repo)
+
+ # Mock existing project with manager_id = 5
+ mock_project = Mock()
+ mock_project.id = 1
+ mock_project.manager_id = 5
+ mock_project.members = [] # Add empty members list for _enrich_project_members_with_team
+ project_repo.get_by_id = Mock(return_value=mock_project)
+
+ # Mock update to return project
+ project_repo.update = Mock(return_value=mock_project)
+
+ update_data = UpdateProject(manager_id=5, name=None, description=None, project_type=None, project_priority=None, project_tools=None)
+
+ # Should not raise error - validation skipped when same manager
+ service.update_project(1, update_data)
+
+ # user_repo.get_by_id should NOT be called since manager is same
+ user_repo.get_by_id.assert_not_called()
+
+ def test_update_manager_success_with_valid_manager(self):
+ """Updating project manager should succeed with valid manager"""
+ project_repo = Mock(spec=ProjectRepository)
+ user_repo = Mock(spec=UserRepository)
+ skill_repo = Mock(spec=SkillRepository)
+ stack_repo = Mock(spec=StackRepository)
+
+ service = ProjectService(project_repo, user_repo, stack_repo, skill_repo)
+
+ # Mock existing project
+ mock_project = Mock()
+ mock_project.id = 1
+ mock_project.manager_id = 1
+ mock_project.members = []
+ project_repo.get_by_id = Mock(return_value=mock_project)
+
+ # Mock valid manager exists
+ mock_manager = Mock()
+ mock_manager.id = 5
+ user_repo.get_by_id = Mock(return_value=mock_manager)
+
+ # Mock update to return updated project
+ updated_project = Mock()
+ updated_project.id = 1
+ updated_project.manager_id = 5
+ updated_project.members = [] # Add empty members list for _enrich_project_members_with_team
+ project_repo.update = Mock(return_value=updated_project)
+
+ update_data = UpdateProject(manager_id=5, name=None, description=None, project_type=None, project_priority=None, project_tools=None)
+
+ result = service.update_project(1, update_data)
+
+ # Should succeed
+ assert result is not None
+ user_repo.get_by_id.assert_called_with(5)
+
+ def test_update_project_not_found(self):
+ """Updating non-existent project should raise 404"""
+ project_repo = Mock(spec=ProjectRepository)
+ user_repo = Mock(spec=UserRepository)
+ skill_repo = Mock(spec=SkillRepository)
+ stack_repo = Mock(spec=StackRepository)
+
+ service = ProjectService(project_repo, user_repo, stack_repo, skill_repo)
+
+ # Mock project doesn't exist
+ project_repo.get_by_id = Mock(return_value=None)
+
+ update_data = UpdateProject(manager_id=1, name=None, description=None, project_type=None, project_priority=None, project_tools=None)
+
+ with pytest.raises(HTTPException) as exc_info:
+ service.update_project(999, update_data)
+
+ assert exc_info.value.status_code == 404
+ assert "Project not found" in exc_info.value.detail
+
+ def test_update_exclude_none_values(self):
+ """Update should only send non-None fields to repository"""
+ project_repo = Mock(spec=ProjectRepository)
+ user_repo = Mock(spec=UserRepository)
+ skill_repo = Mock(spec=SkillRepository)
+ stack_repo = Mock(spec=StackRepository)
+
+ service = ProjectService(project_repo, user_repo, stack_repo, skill_repo)
+
+ # Mock existing project
+ mock_project = Mock()
+ mock_project.id = 1
+ mock_project.manager_id = 1
+ mock_project.members = [] # Add empty members list for _enrich_project_members_with_team
+ project_repo.get_by_id = Mock(return_value=mock_project)
+ project_repo.update = Mock(return_value=mock_project)
+
+ # Update with only name (others None)
+ update_data = UpdateProject(
+ name="New Name",
+ description=None,
+ project_type=None,
+ project_priority=None,
+ manager_id=None,
+ project_tools=None
+ )
+
+ service.update_project(1, update_data)
+
+ # Verify project_repo.update was called
+ project_repo.update.assert_called_once()
+
+ # Get the actual call arguments
+ call_args = project_repo.update.call_args
+ update_dict = call_args[0][1] # Second argument is the update dict
+
+ # Verify only non-None fields are in update dict
+ assert "name" in update_dict
+ assert update_dict["name"] == "New Name"
+ # None fields should not be in the dict due to exclude_none=True
+ if "description" in update_dict:
+ assert update_dict["description"] is not None
diff --git a/test/test_projects.py b/test/test_projects.py
index 7190533..89a562c 100644
--- a/test/test_projects.py
+++ b/test/test_projects.py
@@ -171,12 +171,20 @@ def test_add_user_project(client, test_projects, user_cred, test_user1):
assert res.status_code == status.HTTP_201_CREATED
-def test_add_user_project_unauthorized(client, test_projects, test_user, user_cred):
- url = project_url + str(test_projects[2].id) + "/add/" + str(test_user["id"])
+def test_add_user_project_unauthorized(client, test_projects, test_user, test_user1):
+ # test_user1 (non-admin) trying to add a user to test_projects[0] (managed by test_user)
+ # This should be forbidden since test_user1 is not the manager and not an admin
+ login_res = client.post(
+ "/api/v1/users/login",
+ data={"username": test_user1["email"], "password": test_user1["password"]}
+ )
+ token = login_res.json()["token"]
+
+ url = project_url + str(test_projects[0].id) + "/add/" + str(test_user["id"])
data = {
"team": "FRONTEND"
}
- res = client.post(url, json=data, headers={"Authorization": f"{user_cred.token_type} {user_cred.token}"})
+ res = client.post(url, json=data, headers={"Authorization": f"Bearer {token}"})
assert res.status_code == status.HTTP_403_FORBIDDEN
@@ -211,10 +219,21 @@ def test_remove_user_project(client, test_projects, user_cred, test_user1):
assert res.status_code == status.HTTP_204_NO_CONTENT
-def test_remove_user_project_unauthorized(client, test_projects, test_user, user_cred):
- url = project_url + str(test_projects[2].id) + "/remove/" + str(test_user["id"])
+def test_remove_user_project_unauthorized(client, test_projects, test_user, test_user1, user_cred):
+ # First, add test_user to test_projects[0] (managed by test_user, who is admin)
+ test_add_user_project(client, test_projects, user_cred, test_user1)
- res = client.delete(url, headers={"Authorization": f"{user_cred.token_type} {user_cred.token}"})
+ # Now test_user1 (non-admin) tries to remove from test_projects[0] (managed by test_user)
+ # This should be forbidden since test_user1 is not the manager and not an admin
+ login_res = client.post(
+ "/api/v1/users/login",
+ data={"username": test_user1["email"], "password": test_user1["password"]}
+ )
+ token = login_res.json()["token"]
+
+ url = project_url + str(test_projects[0].id) + "/remove/" + str(test_user1["id"])
+
+ res = client.delete(url, headers={"Authorization": f"Bearer {token}"})
assert res.status_code == status.HTTP_403_FORBIDDEN
diff --git a/test/test_skill_service_response.py b/test/test_skill_service_response.py
new file mode 100644
index 0000000..4c0db39
--- /dev/null
+++ b/test/test_skill_service_response.py
@@ -0,0 +1,105 @@
+"""Tests for Skill service response format"""
+import pytest
+from unittest.mock import Mock
+from services.skill_service import SkillService
+from db.repository.skills import SkillRepository
+
+
+class TestSkillServiceResponseFormat:
+ """Test that search_skills returns consistent response format"""
+
+ def test_search_skills_returns_correct_field_names(self):
+ """search_skills should return skill_id, skill_name, image_url"""
+ skill_repo = Mock(spec=SkillRepository)
+ service = SkillService(skill_repo)
+
+ # Mock skill objects
+ mock_skill = Mock()
+ mock_skill.id = 1
+ mock_skill.name = "Python"
+ mock_skill.image_url = "https://example.com/python.png"
+
+ skill_repo.get_all_flat = Mock(return_value=[mock_skill])
+
+ result = service.search_skills("python")
+
+ # Verify response is list of dicts
+ assert len(result) > 0
+ assert isinstance(result, list)
+ assert isinstance(result[0], dict)
+
+ # Verify field names
+ assert "skill_id" in result[0]
+ assert "skill_name" in result[0]
+ assert "image_url" in result[0]
+
+ def test_search_skills_maps_model_fields_correctly(self):
+ """search_skills should map skill model id→skill_id, name→skill_name"""
+ skill_repo = Mock(spec=SkillRepository)
+ service = SkillService(skill_repo)
+
+ mock_skill = Mock()
+ mock_skill.id = 5
+ mock_skill.name = "React"
+ mock_skill.image_url = "https://example.com/react.png"
+
+ skill_repo.get_all_flat = Mock(return_value=[mock_skill])
+
+ result = service.search_skills("react")
+
+ assert result[0]["skill_id"] == 5
+ assert result[0]["skill_name"] == "React"
+ assert result[0]["image_url"] == "https://example.com/react.png"
+
+ def test_search_skills_handles_null_image_url(self):
+ """search_skills should return empty string when image_url is None"""
+ skill_repo = Mock(spec=SkillRepository)
+ service = SkillService(skill_repo)
+
+ mock_skill = Mock()
+ mock_skill.id = 1
+ mock_skill.name = "JavaScript"
+ mock_skill.image_url = None
+
+ skill_repo.get_all_flat = Mock(return_value=[mock_skill])
+
+ result = service.search_skills("javascript")
+
+ # Should have empty string, not None
+ assert result[0]["image_url"] == ""
+
+ def test_search_skills_fuzzy_matching(self):
+ """search_skills should perform fuzzy matching with threshold"""
+ from unittest.mock import MagicMock
+
+ skill_repo = Mock(spec=SkillRepository)
+ service = SkillService(skill_repo)
+
+ # Create Mock skills - use actual strings for name since fuzzy matching needs real strings
+ skill1 = Mock()
+ skill1.id = 1
+ skill1.name = "Python"
+ skill1.image_url = "url1"
+
+ skill2 = Mock()
+ skill2.id = 2
+ skill2.name = "JavaScript"
+ skill2.image_url = "url2"
+
+ skill3 = Mock()
+ skill3.id = 3
+ skill3.name = "TypeScript"
+ skill3.image_url = "url3"
+
+ skills = [skill1, skill2, skill3]
+
+ skill_repo.get_all_flat = Mock(return_value=skills)
+
+ # Search for "java" should match both Java and JavaScript
+ result = service.search_skills("java")
+
+ # Should find JavaScript at minimum
+ assert len(result) > 0
+ # Should contain JavaScript
+ found_js = any(s["skill_name"] == "JavaScript" for s in result)
+ assert found_js
diff --git a/test/test_technical_task_submissions.py b/test/test_technical_task_submissions.py
new file mode 100644
index 0000000..2ee0dd1
--- /dev/null
+++ b/test/test_technical_task_submissions.py
@@ -0,0 +1,134 @@
+"""Tests for Technical Task Submissions API and service"""
+import pytest
+from datetime import datetime
+from unittest.mock import Mock, MagicMock, patch
+from sqlalchemy.orm import Session
+
+from db.models.technical_task import TechnicalTaskSubmission, TechnicalTask
+from db.models.users import User
+from db.models.stacks import Stack
+from services.technical_task_service import TechnicalTaskService
+from db.repository.technical_tasks import TechnicalTaskRepository, TechnicalTaskSubmissionRepository
+
+
+class TestTechnicalTaskSubmissionResponse:
+ """Test that submission responses include nested user and task data"""
+
+ def test_submission_response_includes_user_data(self):
+ """Submission response should include user details"""
+ user = Mock(spec=User)
+ user.id = 1
+ user.first_name = "John"
+ user.last_name = "Doe"
+ user.username = "johndoe"
+ user.profile_pic_url = "https://example.com/pic.jpg"
+
+ submission = Mock(spec=TechnicalTaskSubmission)
+ submission.id = 1
+ submission.user = user
+ submission.github_link = "https://github.com/johndoe/project"
+ submission.live_demo_url = "https://project.example.com"
+ submission.description = "My submission"
+ submission.created_at = datetime.now()
+ submission.updated_at = datetime.now()
+ submission.task_id = 1
+
+ # Verify user data is accessible
+ assert submission.user.id == 1
+ assert submission.user.first_name == "John"
+ assert submission.user.last_name == "Doe"
+ assert submission.user.username == "johndoe"
+ assert submission.user.profile_pic_url is not None
+
+ def test_submission_response_includes_task_data(self):
+ """Submission response should include technical task details with stack"""
+ stack = Mock(spec=Stack)
+ stack.id = 1
+ stack.name = "Backend"
+
+ task = Mock(spec=TechnicalTask)
+ task.id = 1
+ task.content = "Build a REST API"
+ task.experience_level = "JUNIOR"
+ task.stack = stack
+
+ submission = Mock(spec=TechnicalTaskSubmission)
+ submission.id = 1
+ submission.technical_task = task
+ submission.task_id = 1
+
+ # Verify task data is accessible
+ assert submission.technical_task.id == 1
+ assert submission.technical_task.content == "Build a REST API"
+ assert submission.technical_task.experience_level == "JUNIOR"
+ assert submission.technical_task.stack.name == "Backend"
+
+ def test_submission_response_has_all_required_fields(self):
+ """Submission response should have all submission metadata fields"""
+ submission = Mock(spec=TechnicalTaskSubmission)
+ submission.id = 1
+ submission.github_link = "https://github.com/user/repo"
+ submission.live_demo_url = "https://demo.example.com"
+ submission.description = "My work"
+ submission.created_at = datetime.now()
+ submission.updated_at = datetime.now()
+ submission.task_id = 1
+ submission.user_id = 1
+ submission.user = Mock()
+ submission.technical_task = Mock()
+
+ # Verify all fields present
+ assert hasattr(submission, 'id')
+ assert hasattr(submission, 'github_link')
+ assert hasattr(submission, 'live_demo_url')
+ assert hasattr(submission, 'description')
+ assert hasattr(submission, 'created_at')
+ assert hasattr(submission, 'updated_at')
+ assert hasattr(submission, 'task_id')
+ assert hasattr(submission, 'user_id')
+ assert hasattr(submission, 'user')
+ assert hasattr(submission, 'technical_task')
+
+
+class TestTechnicalTaskServiceErrorHandling:
+ """Test error handling in technical task service"""
+
+ def test_create_submission_provides_context_on_task_not_found(self):
+ """When task not found, error message should include stack_id and experience_level"""
+ task_repo = Mock(spec=TechnicalTaskRepository)
+ submission_repo = Mock(spec=TechnicalTaskSubmissionRepository)
+ service = TechnicalTaskService(task_repo, submission_repo)
+
+ user = Mock(spec=User)
+ user.id = 1
+ user.stack_id = 5
+ user.years_of_experience = 0
+
+ # Mock task_repo to return None
+ task_repo.get_by_stack_and_level = Mock(return_value=None)
+
+ # Service should provide context in error
+ from fastapi import HTTPException
+
+ with pytest.raises(HTTPException) as exc_info:
+ service.create_submission(user, {"github_link": "test"})
+
+ error_detail = exc_info.value.detail
+ # Error message should mention stack_id and experience level
+ assert "stack_id=5" in error_detail or "JUNIOR" in error_detail
+
+ def test_create_submission_error_on_invalid_experience(self):
+ """Creating submission should raise error when years_of_experience is invalid"""
+ task_repo = Mock(spec=TechnicalTaskRepository)
+ submission_repo = Mock(spec=TechnicalTaskSubmissionRepository)
+ service = TechnicalTaskService(task_repo, submission_repo)
+
+ user = Mock(spec=User)
+ user.id = 1
+ user.stack_id = 1
+ user.years_of_experience = 999 # Invalid value
+
+ from fastapi import HTTPException
+
+ with pytest.raises(HTTPException):
+ service.create_submission(user, {"github_link": "test"})
diff --git a/utils/email_templates/password-reset.html b/utils/email_templates/password-reset.html
index f29c32a..66c196c 100644
--- a/utils/email_templates/password-reset.html
+++ b/utils/email_templates/password-reset.html
@@ -3,9 +3,10 @@
Password Reset Email
+
-
+
-
-
+
+
+
-
-
-
-
-
- Hi {}
-
-
- You are receiving this mail because you requested for a password reset for your account on Slightly Techie CRM
-
- Kindly click the button below to reset your password
- Reset Your Password
-
- If you are not aware of this request, please disregard this
- email.
-
-
- Contact us on
- info@slightlytechie.com for more
- information
-
-
-
-
+ ST Network
+
+
+
+
+
+
+
+ Hi {}!
+
+
+
+ We received a request to reset the password for your Slightly Techie CRM account. To proceed with resetting your password, click the button below.
+
+
+
+
+
+
+ This link will expire in 24 hours. If you didn't request a password reset, you can safely ignore this email.
+
+
+
+
+
+
+
+ Have questions? Contact us at
+ info@slightlytechie.com
+
+
+
+ © 2026 Slightly Techie. All rights reserved.
+
diff --git a/utils/email_templates/task_template.html b/utils/email_templates/task_template.html
index 5f9c6df..36ec7f2 100644
--- a/utils/email_templates/task_template.html
+++ b/utils/email_templates/task_template.html
@@ -2,10 +2,11 @@
- Password Reset Email
+ New Task Assignment
+
-
+
-
-
+
+
+
-
-
-
-
-
- Hi {}
-
-
- {}
+
ST Network
+
+
+
+
+
+
+
+ Hi {}!
+
+
+
+ {}
+
+
+
+
+
+
+
+
+
+ If you have any questions about this task, please reach out to your team lead or contact support.
+
+
+
+
+
+
+
+ Have questions? Contact us at
+ info@slightlytechie.com
+
+
+
+ © 2026 Slightly Techie. All rights reserved.
+
diff --git a/utils/oauth2.py b/utils/oauth2.py
index c751a78..bbbf301 100644
--- a/utils/oauth2.py
+++ b/utils/oauth2.py
@@ -74,7 +74,10 @@ def verify_refresh_token(token: str):
def get_current_user(
token: str = Depends(oauth2_scheme),
db: Session = Depends(database.get_db)):
-
+ """
+ Base authentication: verifies token and returns user.
+ Does NOT check is_active or status - use permission dependencies for that.
+ """
credential_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
@@ -92,11 +95,6 @@ def get_current_user(
print(f"User with ID {token_data.id} not found in database.")
raise credential_exception
- if not user.is_active:
- if user.status == "CONTACTED":
- return user
- # Redirect the user to a default URL
- raise HTTPException(status_code=302, headers={"Location": "/inactive"})
return user
diff --git a/utils/permissions.py b/utils/permissions.py
index 76d6156..4e98aee 100644
--- a/utils/permissions.py
+++ b/utils/permissions.py
@@ -12,7 +12,7 @@
def is_authenticated(user: UserResponse = Depends(get_current_user)):
"""
No need to do anything because `get_current_user` raises all the errors
- his would be used as a path dependecy so return value is required
+ This would be used as a path dependency so return value is required
Usage: @app.("", dependencies=[Depends(is_authenticated)] )
"""
@@ -22,7 +22,7 @@ def is_authenticated(user: UserResponse = Depends(get_current_user)):
# Admin permission dependency
def is_admin(user: UserResponse = Depends(is_authenticated)):
- if user.role.name != RoleChoices.ADMIN:
+ if not user.role or user.role.name != RoleChoices.ADMIN:
raise ForbiddenError()
return user
@@ -33,6 +33,10 @@ def is_project_manager(
db: Session = Depends(get_db),
user: UserResponse = Depends(get_current_user),
):
+ # Allow admins
+ if user.role and user.role.name == RoleChoices.ADMIN:
+ return user
+
project_id = request.path_params.get("project_id")
if project_id is None:
raise HTTPException(status_code=500, detail="project_id path parameter missing")
@@ -78,8 +82,8 @@ def is_owner(user, obj):
def user_accepted(user: UserResponse = Depends(get_current_user)):
- """Only accepted users can access feeds page"""
- if user.status != UserStatus.ACCEPTED:
+ """Only active and accepted users can access protected resources"""
+ if not user.is_active or user.status != UserStatus.ACCEPTED:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You do not have access to this resource"
diff --git a/utils/utils.py b/utils/utils.py
index fd45c03..9bb552a 100644
--- a/utils/utils.py
+++ b/utils/utils.py
@@ -33,12 +33,18 @@ class RoleChoices():
def get_key_by_value(value):
+ # Map years of experience to ExperienceLevel enum values
+ from utils.enums import ExperienceLevel
+
experience_level_map = {
- "JUNIOR": [0, 1, 2],
- "MID_LEVEL": [3, 4],
- "SENIOR": [5, 6, 7, 8, 9, 10, 11, 12]
+ ExperienceLevel.JUNIOR.value: [0, 1, 2],
+ ExperienceLevel.MID_LEVEL.value: [3, 4],
+ ExperienceLevel.SENIOR.value: [5, 6, 7, 8, 9, 10, 11, 12]
}
for key, values in experience_level_map.items():
if value in values:
return key
- return None
+ raise ValueError(
+ f"Invalid years_of_experience value: {value}. "
+ f"Must be between 0 and 12. Valid ranges: Junior (0-2), Mid-level (3-4), Senior (5-12)."
+ )