diff --git a/README.md b/README.md index 7ca8eb4d..54e730da 100644 --- a/README.md +++ b/README.md @@ -93,4 +93,32 @@ 5. To fix lint issues ``` ruff check --fix - ``` \ No newline at end of file + ``` + +## API Documentation + +### Tasks + +#### Add Label to Task +``` +POST /v1/tasks/{task_id}/labels + +Add a label to an existing task. + +Request Body: +{ + "label_id": "string" // ID of the label to add +} + +Responses: +200 OK - Label added successfully +400 Bad Request - Invalid input (e.g., invalid label ID format) +404 Not Found - Task or label not found +500 Internal Server Error - Unexpected server error + +Example: +POST /v1/tasks/123/labels +{ + "label_id": "456" +} +``` \ No newline at end of file diff --git a/todo/serializers/add_label_serializer.py b/todo/serializers/add_label_serializer.py new file mode 100644 index 00000000..cccd8edd --- /dev/null +++ b/todo/serializers/add_label_serializer.py @@ -0,0 +1,12 @@ +from rest_framework import serializers +from todo.models.common.pyobjectid import PyObjectId + + +class AddLabelSerializer(serializers.Serializer): + label_id = serializers.CharField(required=True) + + def validate_label_id(self, value): + try: + return PyObjectId(value) + except Exception: + raise serializers.ValidationError("Invalid label ID format") \ No newline at end of file diff --git a/todo/services/task_service.py b/todo/services/task_service.py index 70de8c86..4db6e7f1 100644 --- a/todo/services/task_service.py +++ b/todo/services/task_service.py @@ -221,3 +221,45 @@ def create_task(cls, dto: CreateTaskDTO) -> CreateTaskResponse: ], ) ) + + @staticmethod + def add_label_to_task(task_id: PyObjectId, label_id: PyObjectId) -> None: + """ + Add a label to a task. + + Args: + task_id: The ID of the task + label_id: The ID of the label to add + + Raises: + ValueError: If the task or label doesn't exist + """ + from todo.repositories.task_repository import TaskRepository + from todo.repositories.label_repository import LabelRepository + + # Check if task exists + task = TaskRepository.get_by_id(task_id) + if not task: + raise ValueError(ApiErrorResponse( + statusCode=404, + message="Task not found", + errors=[{"detail": f"Task with ID {task_id} does not exist"}] + )) + + # Check if label exists + label = LabelRepository.get_by_id(label_id) + if not label: + raise ValueError(ApiErrorResponse( + statusCode=404, + message="Label not found", + errors=[{"detail": f"Label with ID {label_id} does not exist"}] + )) + + # Initialize labels list if None + if task.labels is None: + task.labels = [] + + # Add label if not already present + if label_id not in task.labels: + task.labels.append(label_id) + TaskRepository.update(task) diff --git a/todo/urls.py b/todo/urls.py index 9264115f..611b5706 100644 --- a/todo/urls.py +++ b/todo/urls.py @@ -5,5 +5,6 @@ urlpatterns = [ path("tasks", TaskView.as_view(), name="tasks"), + path("tasks//labels", TaskView.as_view({"post": "post_label"}), name="task_labels"), path("health", HealthView.as_view(), name="health"), ] diff --git a/todo/views/task.py b/todo/views/task.py index 96cfa45a..15d43ee7 100644 --- a/todo/views/task.py +++ b/todo/views/task.py @@ -6,11 +6,13 @@ from todo.serializers.get_tasks_serializer import GetTaskQueryParamsSerializer from todo.serializers.create_task_serializer import CreateTaskSerializer +from todo.serializers.add_label_serializer import AddLabelSerializer from todo.services.task_service import TaskService from todo.dto.task_dto import CreateTaskDTO from todo.dto.responses.error_response import ApiErrorResponse, ApiErrorDetail, ApiErrorSource from todo.dto.responses.create_task_response import CreateTaskResponse from todo.constants.messages import ApiErrors +from todo.models.common.pyobjectid import PyObjectId class TaskView(APIView): def get(self, request: Request): @@ -59,6 +61,51 @@ def post(self, request: Request): data=fallback_response.model_dump(mode="json"), status=status.HTTP_500_INTERNAL_SERVER_ERROR ) + def post_label(self, request: Request, task_id: str): + """ + Add a label to a task. + + Args: + request: HTTP request containing label data + task_id: ID of the task to add the label to + + Returns: + Response: HTTP response with success or error details + """ + try: + task_id = PyObjectId(task_id) + except Exception: + return Response( + data=ApiErrorResponse( + statusCode=400, + message="Invalid task ID format", + errors=[{"detail": "The provided task ID is not valid"}] + ).model_dump(mode="json"), + status=status.HTTP_400_BAD_REQUEST + ) + + serializer = AddLabelSerializer(data=request.data) + if not serializer.is_valid(): + return self._handle_validation_errors(serializer.errors) + + try: + TaskService.add_label_to_task(task_id, serializer.validated_data["label_id"]) + return Response(status=status.HTTP_200_OK) + + except ValueError as e: + if isinstance(e.args[0], ApiErrorResponse): + error_response = e.args[0] + return Response(data=error_response.model_dump(mode="json"), status=error_response.statusCode) + + fallback_response = ApiErrorResponse( + statusCode=500, + message=ApiErrors.UNEXPECTED_ERROR_OCCURRED, + errors=[{"detail": str(e) if settings.DEBUG else ApiErrors.INTERNAL_SERVER_ERROR}], + ) + return Response( + data=fallback_response.model_dump(mode="json"), status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) + def _handle_validation_errors(self, errors): formatted_errors = [] for field, messages in errors.items():