Skip to content

Commit 013eea7

Browse files
committed
feat: create teams
1 parent e02e1c8 commit 013eea7

File tree

8 files changed

+329
-0
lines changed

8 files changed

+329
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from pydantic import BaseModel
2+
from todo.dto.team_dto import TeamDTO
3+
4+
5+
class CreateTeamResponse(BaseModel):
6+
team: TeamDTO
7+
message: str = "Team created successfully"

todo/dto/team_dto.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from pydantic import BaseModel
2+
from typing import List, Optional
3+
from datetime import datetime
4+
5+
6+
class CreateTeamDTO(BaseModel):
7+
name: str
8+
description: Optional[str] = None
9+
member_ids: List[str] = []
10+
poc_id: str
11+
12+
13+
class TeamDTO(BaseModel):
14+
id: str
15+
name: str
16+
description: Optional[str] = None
17+
poc_id: str
18+
created_by: str
19+
updated_by: str
20+
created_at: datetime
21+
updated_at: datetime

todo/models/team.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from pydantic import Field
2+
from typing import ClassVar
3+
from datetime import datetime, timezone
4+
5+
from todo.models.common.document import Document
6+
from todo.models.common.pyobjectid import PyObjectId
7+
8+
9+
class TeamModel(Document):
10+
"""
11+
Model for teams.
12+
"""
13+
collection_name: ClassVar[str] = "teams"
14+
15+
name: str
16+
description: str | None = None
17+
poc_id: PyObjectId
18+
created_by: PyObjectId
19+
updated_by: PyObjectId
20+
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
21+
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
22+
is_deleted: bool = False
23+
24+
25+
class UserTeamDetailsModel(Document):
26+
"""
27+
Model for user-team relationships.
28+
"""
29+
collection_name: ClassVar[str] = "userTeamDetails"
30+
31+
user_id: PyObjectId
32+
team_id: PyObjectId
33+
is_active: bool = True
34+
role_id: str
35+
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
36+
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
37+
created_by: PyObjectId
38+
updated_by: PyObjectId
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from datetime import datetime, timezone
2+
from typing import Optional
3+
from bson import ObjectId
4+
5+
from todo.models.team import TeamModel, UserTeamDetailsModel
6+
from todo.repositories.common.mongo_repository import MongoRepository
7+
from todo.models.common.pyobjectid import PyObjectId
8+
9+
10+
class TeamRepository(MongoRepository):
11+
collection_name = TeamModel.collection_name
12+
13+
@classmethod
14+
def create(cls, team: TeamModel) -> TeamModel:
15+
"""
16+
Creates a new team in the repository.
17+
"""
18+
teams_collection = cls.get_collection()
19+
team.created_at = datetime.now(timezone.utc)
20+
team.updated_at = datetime.now(timezone.utc)
21+
22+
team_dict = team.model_dump(mode="json", by_alias=True, exclude_none=True)
23+
insert_result = teams_collection.insert_one(team_dict)
24+
team.id = insert_result.inserted_id
25+
return team
26+
27+
@classmethod
28+
def get_by_id(cls, team_id: str) -> Optional[TeamModel]:
29+
"""
30+
Get a team by its ID.
31+
"""
32+
teams_collection = cls.get_collection()
33+
try:
34+
team_data = teams_collection.find_one({"_id": ObjectId(team_id), "is_deleted": False})
35+
if team_data:
36+
return TeamModel(**team_data)
37+
return None
38+
except Exception:
39+
return None
40+
41+
42+
class UserTeamDetailsRepository(MongoRepository):
43+
collection_name = UserTeamDetailsModel.collection_name
44+
45+
@classmethod
46+
def create(cls, user_team: UserTeamDetailsModel) -> UserTeamDetailsModel:
47+
"""
48+
Creates a new user-team relationship.
49+
"""
50+
collection = cls.get_collection()
51+
user_team.created_at = datetime.now(timezone.utc)
52+
user_team.updated_at = datetime.now(timezone.utc)
53+
54+
user_team_dict = user_team.model_dump(mode="json", by_alias=True, exclude_none=True)
55+
insert_result = collection.insert_one(user_team_dict)
56+
user_team.id = insert_result.inserted_id
57+
return user_team
58+
59+
@classmethod
60+
def create_many(cls, user_teams: list[UserTeamDetailsModel]) -> list[UserTeamDetailsModel]:
61+
"""
62+
Creates multiple user-team relationships.
63+
"""
64+
collection = cls.get_collection()
65+
current_time = datetime.now(timezone.utc)
66+
67+
for user_team in user_teams:
68+
user_team.created_at = current_time
69+
user_team.updated_at = current_time
70+
71+
user_teams_dicts = [user_team.model_dump(mode="json", by_alias=True, exclude_none=True)
72+
for user_team in user_teams]
73+
insert_result = collection.insert_many(user_teams_dicts)
74+
75+
# Set the inserted IDs
76+
for i, user_team in enumerate(user_teams):
77+
user_team.id = insert_result.inserted_ids[i]
78+
79+
return user_teams
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from rest_framework import serializers
2+
3+
4+
class CreateTeamSerializer(serializers.Serializer):
5+
name = serializers.CharField(max_length=100)
6+
description = serializers.CharField(max_length=500, required=False, allow_blank=True)
7+
member_ids = serializers.ListField(
8+
child=serializers.CharField(),
9+
required=False,
10+
default=list
11+
)
12+
poc_id = serializers.CharField(required=True, allow_null=False)

todo/services/team_service.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
from todo.dto.team_dto import CreateTeamDTO, TeamDTO
2+
from todo.dto.responses.create_team_response import CreateTeamResponse
3+
from todo.models.team import TeamModel, UserTeamDetailsModel
4+
from todo.models.common.pyobjectid import PyObjectId
5+
from todo.repositories.team_repository import TeamRepository, UserTeamDetailsRepository
6+
from todo.repositories.user_repository import UserRepository
7+
8+
9+
class TeamService:
10+
@classmethod
11+
def create_team(cls, dto: CreateTeamDTO, created_by_user_id: str) -> CreateTeamResponse:
12+
"""
13+
Create a new team with members and POC.
14+
"""
15+
try:
16+
# Validate that all member IDs exist
17+
if dto.member_ids:
18+
for user_id in dto.member_ids:
19+
user = UserRepository.get_by_id(user_id)
20+
if not user:
21+
raise ValueError(f"User with ID {user_id} not found")
22+
23+
# Validate POC exists if provided
24+
if dto.poc_id:
25+
poc_user = UserRepository.get_by_id(dto.poc_id)
26+
if not poc_user:
27+
raise ValueError(f"POC user with ID {dto.poc_id} not found")
28+
29+
# Create team
30+
team = TeamModel(
31+
name=dto.name,
32+
description=dto.description,
33+
poc_id=PyObjectId(dto.poc_id) if dto.poc_id else None,
34+
created_by=PyObjectId(created_by_user_id),
35+
updated_by=PyObjectId(created_by_user_id)
36+
)
37+
38+
created_team = TeamRepository.create(team)
39+
40+
# Create user-team relationships
41+
user_teams = []
42+
43+
# Add members to the team
44+
if dto.member_ids:
45+
for user_id in dto.member_ids:
46+
user_team = UserTeamDetailsModel(
47+
user_id=PyObjectId(user_id),
48+
team_id=created_team.id,
49+
role_id="1",
50+
created_by=PyObjectId(created_by_user_id),
51+
updated_by=PyObjectId(created_by_user_id)
52+
)
53+
user_teams.append(user_team)
54+
55+
# Add creator if not already in member_ids
56+
if created_by_user_id not in dto.member_ids:
57+
creator_user_team = UserTeamDetailsModel(
58+
user_id=PyObjectId(created_by_user_id),
59+
team_id=created_team.id,
60+
role_id="1",
61+
created_by=PyObjectId(created_by_user_id),
62+
updated_by=PyObjectId(created_by_user_id)
63+
)
64+
user_teams.append(creator_user_team)
65+
66+
# Create all user-team relationships
67+
if user_teams:
68+
UserTeamDetailsRepository.create_many(user_teams)
69+
70+
# Convert to DTO
71+
team_dto = TeamDTO(
72+
id=str(created_team.id),
73+
name=created_team.name,
74+
description=created_team.description,
75+
poc_id=str(created_team.poc_id) if created_team.poc_id else None,
76+
created_by=str(created_team.created_by),
77+
updated_by=str(created_team.updated_by),
78+
created_at=created_team.created_at,
79+
updated_at=created_team.updated_at,
80+
)
81+
82+
return CreateTeamResponse(team=team_dto)
83+
84+
except Exception as e:
85+
raise ValueError(str(e))

todo/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from todo.views.task import TaskListView, TaskDetailView
33
from todo.views.label import LabelListView
44
from todo.views.health import HealthView
5+
from todo.views.team import TeamListView
56
from todo.views.auth import (
67
GoogleLoginView,
78
GoogleCallbackView,
@@ -12,6 +13,7 @@
1213
urlpatterns = [
1314
path("tasks", TaskListView.as_view(), name="tasks"),
1415
path("tasks/<str:task_id>", TaskDetailView.as_view(), name="task_detail"),
16+
path("teams", TeamListView.as_view(), name="teams"),
1517
path("health", HealthView.as_view(), name="health"),
1618
path("labels", LabelListView.as_view(), name="labels"),
1719
path("auth/google/login/", GoogleLoginView.as_view(), name="google_login"),

todo/views/team.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
from rest_framework.views import APIView
2+
from rest_framework.response import Response
3+
from rest_framework import status
4+
from rest_framework.request import Request
5+
from django.conf import settings
6+
7+
from todo.serializers.create_team_serializer import CreateTeamSerializer
8+
from todo.services.team_service import TeamService
9+
from todo.dto.team_dto import CreateTeamDTO
10+
from todo.dto.responses.create_team_response import CreateTeamResponse
11+
from todo.dto.responses.error_response import ApiErrorResponse
12+
from todo.constants.messages import ApiErrors
13+
14+
15+
class TeamListView(APIView):
16+
def post(self, request: Request):
17+
"""
18+
Create a new team.
19+
"""
20+
serializer = CreateTeamSerializer(data=request.data)
21+
22+
if not serializer.is_valid():
23+
return self._handle_validation_errors(serializer.errors)
24+
25+
try:
26+
dto = CreateTeamDTO(**serializer.validated_data)
27+
created_by_user_id = request.user_id
28+
response: CreateTeamResponse = TeamService.create_team(dto, created_by_user_id)
29+
30+
return Response(
31+
data=response.model_dump(mode="json"),
32+
status=status.HTTP_201_CREATED
33+
)
34+
35+
except ValueError as e:
36+
if isinstance(e.args[0], ApiErrorResponse):
37+
error_response = e.args[0]
38+
return Response(
39+
data=error_response.model_dump(mode="json"),
40+
status=error_response.statusCode
41+
)
42+
43+
fallback_response = ApiErrorResponse(
44+
statusCode=500,
45+
message=ApiErrors.UNEXPECTED_ERROR_OCCURRED,
46+
errors=[{"detail": str(e) if settings.DEBUG else ApiErrors.INTERNAL_SERVER_ERROR}],
47+
)
48+
return Response(
49+
data=fallback_response.model_dump(mode="json"),
50+
status=status.HTTP_500_INTERNAL_SERVER_ERROR
51+
)
52+
53+
def _handle_validation_errors(self, errors):
54+
from todo.dto.responses.error_response import ApiErrorDetail, ApiErrorSource
55+
56+
formatted_errors = []
57+
for field, messages in errors.items():
58+
if isinstance(messages, list):
59+
for message in messages:
60+
formatted_errors.append(
61+
ApiErrorDetail(
62+
source={ApiErrorSource.PARAMETER: field},
63+
title=ApiErrors.VALIDATION_ERROR,
64+
detail=str(message),
65+
)
66+
)
67+
else:
68+
formatted_errors.append(
69+
ApiErrorDetail(
70+
source={ApiErrorSource.PARAMETER: field},
71+
title=ApiErrors.VALIDATION_ERROR,
72+
detail=str(messages)
73+
)
74+
)
75+
76+
error_response = ApiErrorResponse(
77+
statusCode=400,
78+
message=ApiErrors.VALIDATION_ERROR,
79+
errors=formatted_errors
80+
)
81+
82+
return Response(
83+
data=error_response.model_dump(mode="json"),
84+
status=status.HTTP_400_BAD_REQUEST
85+
)

0 commit comments

Comments
 (0)