Skip to content

Commit

Permalink
feat: allow each user to have their own todos
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianvitterso committed Nov 11, 2022
1 parent 2755265 commit 9456ab8
Show file tree
Hide file tree
Showing 13 changed files with 101 additions and 71 deletions.
1 change: 1 addition & 0 deletions api/src/entities/TodoItem.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@dataclass(frozen=True)
class TodoItem:
id: str
user_id: str
title: str
is_completed: bool = False

Expand Down
31 changes: 23 additions & 8 deletions api/src/features/todo/todo_feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from fastapi import APIRouter, Depends
from starlette.responses import JSONResponse

from authentication.authentication import auth_with_jwt
from authentication.models import User
from common.responses import create_response
from data_providers.get_repository import get_todo_repository
from data_providers.repository_interfaces.TodoRepositoryInterface import (
Expand All @@ -24,14 +26,22 @@

@router.post("", operation_id="create", response_model=AddTodoResponse)
@create_response(JSONResponse)
def add_todo(data: AddTodoRequest, todo_repository: TodoRepositoryInterface = Depends(get_todo_repository)):
return add_todo_use_case(data=data, todo_repository=todo_repository).dict()
def add_todo(
data: AddTodoRequest,
user: User = Depends(auth_with_jwt),
todo_repository: TodoRepositoryInterface = Depends(get_todo_repository),
):
return add_todo_use_case(data=data, user_id=user.user_id, todo_repository=todo_repository).dict()


@router.get("/{id}", operation_id="get_by_id", response_model=GetTodoByIdResponse)
@create_response(JSONResponse)
def get_todo_by_id(id: str, todo_repository: TodoRepositoryInterface = Depends(get_todo_repository)):
return get_todo_by_id_use_case(id=id, todo_repository=todo_repository).dict()
def get_todo_by_id(
id: str,
user: User = Depends(auth_with_jwt),
todo_repository: TodoRepositoryInterface = Depends(get_todo_repository),
):
return get_todo_by_id_use_case(id=id, user_id=user.user_id, todo_repository=todo_repository).dict()


@router.delete("/{id}", operation_id="delete_by_id", response_model=DeleteTodoByIdResponse)
Expand All @@ -42,13 +52,18 @@ def delete_todo_by_id(id: str, todo_repository: TodoRepositoryInterface = Depend

@router.get("", operation_id="get_all", response_model=List[GetTodoAllResponse])
@create_response(JSONResponse)
def get_todo_all(todo_repository: TodoRepositoryInterface = Depends(get_todo_repository)):
return [todo.dict() for todo in get_todo_all_use_case(todo_repository=todo_repository)]
def get_todo_all(
user: User = Depends(auth_with_jwt), todo_repository: TodoRepositoryInterface = Depends(get_todo_repository)
):
return [todo.dict() for todo in get_todo_all_use_case(user_id=user.user_id, todo_repository=todo_repository)]


@router.put("/{id}", operation_id="update_by_id", response_model=UpdateTodoResponse)
@create_response(JSONResponse)
def update_todo(
id: str, data: UpdateTodoRequest, todo_repository: TodoRepositoryInterface = Depends(get_todo_repository)
id: str,
data: UpdateTodoRequest,
user: User = Depends(auth_with_jwt),
todo_repository: TodoRepositoryInterface = Depends(get_todo_repository),
):
return update_todo_use_case(id=id, data=data, todo_repository=todo_repository).dict()
return update_todo_use_case(id=id, data=data, user_id=user.user_id, todo_repository=todo_repository).dict()
3 changes: 2 additions & 1 deletion api/src/features/todo/use_cases/add_todo.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ def from_entity(todo_item: TodoItem) -> "AddTodoResponse":

def add_todo_use_case(
data: AddTodoRequest,
user_id: str,
todo_repository: TodoRepositoryInterface,
) -> AddTodoResponse:
todo_item = TodoItem(id=str(uuid.uuid4()), title=data.title)
todo_item = TodoItem(id=str(uuid.uuid4()), title=data.title, user_id=user_id)
todo_repository.create(todo_item)
return AddTodoResponse.from_entity(todo_item)
13 changes: 6 additions & 7 deletions api/src/features/todo/use_cases/get_todo_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@ def from_entity(todo_item: TodoItem):


def get_todo_all_use_case(
user_id: str,
todo_repository: TodoRepositoryInterface,
) -> List[GetTodoAllResponse]:
response: List[GetTodoAllResponse] = []
todo_items: List[TodoItem] = todo_repository.get_all()

for todo_item in todo_items:
response.append(GetTodoAllResponse.from_entity(todo_item))

return response
return [
GetTodoAllResponse.from_entity(todo_item)
for todo_item in todo_repository.get_all()
if todo_item.user_id == user_id
]
5 changes: 4 additions & 1 deletion api/src/features/todo/use_cases/get_todo_by_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from pydantic import BaseModel, Field

from common.exceptions import NotFoundException
from data_providers.repository_interfaces.TodoRepositoryInterface import (
TodoRepositoryInterface,
)
Expand All @@ -18,6 +19,8 @@ def from_entity(todo_item: TodoItem) -> "GetTodoByIdResponse":
return GetTodoByIdResponse(id=todo_item.id, title=todo_item.title, is_completed=todo_item.is_completed)


def get_todo_by_id_use_case(id: str, todo_repository: TodoRepositoryInterface) -> GetTodoByIdResponse:
def get_todo_by_id_use_case(id: str, user_id: str, todo_repository: TodoRepositoryInterface) -> GetTodoByIdResponse:
todo_item = todo_repository.get(id)
if todo_item.user_id != user_id:
raise NotFoundException
return GetTodoByIdResponse.from_entity(cast(TodoItem, todo_item))
7 changes: 6 additions & 1 deletion api/src/features/todo/use_cases/update_todo.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from pydantic import BaseModel, Field

from common.exceptions import NotFoundException
from data_providers.repository_interfaces.TodoRepositoryInterface import (
TodoRepositoryInterface,
)
Expand All @@ -23,10 +24,14 @@ class UpdateTodoResponse(BaseModel):
def update_todo_use_case(
id: str,
data: UpdateTodoRequest,
user_id: str,
todo_repository: TodoRepositoryInterface,
) -> UpdateTodoResponse:
todo_item = todo_repository.get(id)
updated_todo_item = TodoItem(id=todo_item.id, title=data.title, is_completed=data.is_completed)
if todo_item.user_id != user_id:
raise NotFoundException

updated_todo_item = TodoItem(id=todo_item.id, title=data.title, is_completed=data.is_completed, user_id=user_id)
if todo_repository.update(updated_todo_item):
return UpdateTodoResponse(success=True)
return UpdateTodoResponse(success=False)
Original file line number Diff line number Diff line change
Expand Up @@ -3,103 +3,109 @@
from common.exceptions import NotFoundException, ValidationException
from data_providers.clients.mongodb.MongoDatabaseClient import MongoDatabaseClient
from data_providers.repositories.TodoRepository import TodoRepository
from data_providers.repository_interfaces.TodoRepositoryInterface import (
TodoRepositoryInterface,
)
from entities.TodoItem import TodoItem


class TestTodoRepository:
@pytest.fixture(autouse=True)
def _setup_repository(self, test_client: MongoDatabaseClient):
self.repository = TodoRepository(client=test_client)
self.repository: TodoRepositoryInterface = TodoRepository(client=test_client)

def test_create(self):
todo_item = TodoItem(id="1234", title="todo 1")
todo_item = TodoItem(id="1234", title="todo 1", user_id="xyz")
self.repository.create(todo_item)
assert len(self.repository.get_all()) == 1

def test_create_already_exists(self):
todo_item_1 = TodoItem(id="1234", title="todo 1")
todo_item_1 = TodoItem(id="1234", title="todo 1", user_id="xyz")
self.repository.create(todo_item_1)
with pytest.raises(ValidationException):
todo_item_2 = TodoItem(id="1234", title="todo 1")
todo_item_2 = TodoItem(id="1234", title="todo 1", user_id="xyz")
self.repository.create(todo_item_2)

def test_find_item_that_exist(self):
documents = [
{"_id": "81549300", "title": "todo 1"},
{"_id": "1a2b", "title": "todo 2"},
{"_id": "987321", "title": "todo 3"},
{
"_id": "987456",
"title": "todo 4",
},
{"_id": "81549300", "title": "todo 1", "user_id": "xyz"},
{"_id": "1a2b", "title": "todo 2", "user_id": "xyz"},
{"_id": "987321", "title": "todo 3", "user_id": "abc"},
{"_id": "987456", "title": "todo 4", "user_id": "abc"},
]
self.repository.client.insert_many(documents)
todo_item = self.repository.find_one({"title": "todo 2"})
assert todo_item.id == "1a2b"
assert self.repository.find_one({"title": "todo 2", "user_id": "xyz"}).id == "1a2b"

def test_find_item_that_does_not_exist(self):
documents = [
{"_id": "81549300", "title": "todo 1"},
{"_id": "1a2b", "title": "todo 2"},
{"_id": "987321", "title": "todo 3"},
{
"_id": "987456",
"title": "todo 4",
},
{"_id": "81549300", "title": "todo 1", "user_id": "xyz"},
{"_id": "1a2b", "title": "todo 2", "user_id": "xyz"},
{"_id": "987321", "title": "todo 3", "user_id": "abc"},
{"_id": "987456", "title": "todo 4", "user_id": "abc"},
]
self.repository.client.insert_many(documents)
assert self.repository.find_one({"_id": "invalid"}) is None

def test_find_item_of_other_user(self):
documents = [
{"_id": "81549300", "title": "todo 1", "user_id": "xyz"},
{"_id": "1a2b", "title": "todo 2", "user_id": "xyz"},
{"_id": "987321", "title": "todo 3", "user_id": "abc"},
{"_id": "987456", "title": "todo 4", "user_id": "abc"},
]
self.repository.client.insert_many(documents)
assert self.repository.find_one({"_id": "1a2b", "user_id": "abc"}) is None

def test_get_item_that_does_exist(self):
documents = [
{"_id": "81549300", "title": "todo 1"},
{"_id": "1a2b", "title": "todo 2"},
{"_id": "987321", "title": "todo 3"},
{
"_id": "987456",
"title": "todo 4",
},
{"_id": "81549300", "title": "todo 1", "user_id": "xyz"},
{"_id": "1a2b", "title": "todo 2", "user_id": "xyz"},
{"_id": "987321", "title": "todo 3", "user_id": "abc"},
{"_id": "987456", "title": "todo 4", "user_id": "abc"},
]
self.repository.client.insert_many(documents)
todo_item = self.repository.get("987321")
assert todo_item.id == "987321"
assert self.repository.get("987321").id == "987321"

def test_get_item_that_does_not_exist(self):
documents = [
{"_id": "81549300", "title": "todo 1"},
{"_id": "1a2b", "title": "todo 2"},
{"_id": "987321", "title": "todo 3"},
{
"_id": "987456",
"title": "todo 4",
},
{"_id": "81549300", "title": "todo 1", "user_id": "xyz"},
{"_id": "1a2b", "title": "todo 2", "user_id": "xyz"},
{"_id": "987321", "title": "todo 3", "user_id": "abc"},
{"_id": "987456", "title": "todo 4", "user_id": "abc"},
]
self.repository.client.insert_many(documents)
with pytest.raises(NotFoundException):
self.repository.get("invalid")

def test_update_item(self):
todo_item = TodoItem(id="81549300", title="todo 1")
todo_item = TodoItem(id="81549300", title="todo 1", user_id="xyz")
self.repository.create(todo_item)
todo_item_to_update = TodoItem(id="81549300", title="Updated title")
todo_item_to_update = TodoItem(id="81549300", title="Updated title", user_id="xyz")
self.repository.update(todo_item=todo_item_to_update)
assert self.repository.get("81549300").title == "Updated title"

def test_update_item_that_does_not_exist(self):
todo_item_to_update = TodoItem(id="unknown", title="Updated title")
todo_item_to_update = TodoItem(id="unknown", title="Updated title", user_id="xyz")
with pytest.raises(NotFoundException):
self.repository.update(todo_item_to_update)

def test_delete(self):
documents = [{"_id": "81549300", "title": "todo 1"}, {"_id": "1a2b", "title": "todo 2"}]
documents = [
{"_id": "81549300", "title": "todo 1", "user_id": "xyz"},
{"_id": "1a2b", "title": "todo 2", "user_id": "xyz"},
]
self.repository.client.insert_many(documents)
assert len(self.repository.get_all()) == 2
self.repository.delete("81549300")
assert len(self.repository.get_all()) == 1
assert self.repository.get_all() == [self.repository.get("1a2b")]

def test_delete_all(self):
documents = [{"_id": "81549300", "title": "todo 1"}, {"_id": "1a2b", "title": "todo 2"}]
documents = [
{"_id": "81549300", "title": "todo 1", "user_id": "xyz"},
{"_id": "1a2b", "title": "todo 2", "user_id": "xyz"},
]
self.repository.client.insert_many(documents)
assert len(self.repository.get_all()) == 2
self.repository.delete_all()
assert len(self.repository.get_all()) == 0
6 changes: 3 additions & 3 deletions api/src/tests/unit/entities/test_todo_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

def test_todo_item_init():
id = str(uuid.uuid4())
todo = TodoItem(id=id, title="title 1", is_completed=False)
todo = TodoItem(id=id, title="title 1", is_completed=False, user_id="xyz")
assert todo.id == id
assert todo.title == "title 1"
assert not todo.is_completed


def test_todo_item_from_dict():
id = str(uuid.uuid4())
init_dict = {"id": id, "title": "title 1", "is_completed": False}
init_dict = {"id": id, "title": "title 1", "is_completed": False, "user_id": "xyz"}
todo = TodoItem.from_dict(init_dict)

assert todo.id == id
Expand All @@ -23,7 +23,7 @@ def test_todo_item_from_dict():

def test_todo_item_comparison():
id = str(uuid.uuid4())
init_dict = {"id": id, "title": "title 1", "is_completed": False}
init_dict = {"id": id, "title": "title 1", "is_completed": False, "user_id": "xyz"}
todo1 = TodoItem.from_dict(init_dict)
todo2 = TodoItem.from_dict(init_dict)

Expand Down
6 changes: 3 additions & 3 deletions api/src/tests/unit/features/todo/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
@pytest.fixture(scope="function")
def todo_test_data() -> Dict[str, dict]:
return {
"dh2109": {"_id": "dh2109", "title": "item 1", "is_completed": False},
"1417b8": {"_id": "1417b8", "title": "item 2", "is_completed": True},
"abcdefg": {"_id": "abcdefg", "title": "item 3", "is_completed": False},
"dh2109": {"_id": "dh2109", "title": "item 1", "is_completed": False, "user_id": "xyz"},
"1417b8": {"_id": "1417b8", "title": "item 2", "is_completed": True, "user_id": "xyz"},
"abcdefg": {"_id": "abcdefg", "title": "item 3", "is_completed": False, "user_id": "xyz"},
}


Expand Down
4 changes: 2 additions & 2 deletions api/src/tests/unit/features/todo/use_cases/test_add_todo.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@

def test_add_with_valid_title_should_return_todo(todo_repository: TodoRepositoryInterface):
data = AddTodoRequest(title="new todo")
result = add_todo_use_case(data, todo_repository=todo_repository)
result = add_todo_use_case(data, user_id="xyz", todo_repository=todo_repository)
assert result.title == data.title


def test_add_with_empty_title_should_throw_validation_error(todo_repository: TodoRepositoryInterface):
with pytest.raises(ValidationError):
data = AddTodoRequest(title="")
add_todo_use_case(data, todo_repository=todo_repository)
add_todo_use_case(data, user_id="xyz", todo_repository=todo_repository)
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@


def test_get_todos_should_return_todos(todo_repository: TodoRepositoryInterface, todo_test_data: Dict[str, dict]):
todos = get_todo_all_use_case(todo_repository=todo_repository)
todos = get_todo_all_use_case(user_id="xyz", todo_repository=todo_repository)
assert len(todos) == len(todo_test_data.keys())
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@

def test_get_todo_by_id_should_return_todo(todo_repository: TodoRepositoryInterface, todo_test_data: Dict[str, dict]):
id = "dh2109"
todo: GetTodoByIdResponse = get_todo_by_id_use_case(id, todo_repository=todo_repository)
todo: GetTodoByIdResponse = get_todo_by_id_use_case(id, user_id="xyz", todo_repository=todo_repository)
assert todo.title == todo_test_data[id]["title"]


def test_get_todo_by_id_should_throw_todo_not_found_error(todo_repository: TodoRepositoryInterface):
id = "unknown"
with pytest.raises(NotFoundException):
get_todo_by_id_use_case(id, todo_repository=todo_repository)
get_todo_by_id_use_case(id, user_id="xyz", todo_repository=todo_repository)
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
def test_update_todo_should_return_success(todo_repository: TodoRepositoryInterface):
id = "dh2109"
data = UpdateTodoRequest(title="new title", is_completed=False)
result = update_todo_use_case(id, data, todo_repository=todo_repository)
result = update_todo_use_case(id, data, user_id="xyz", todo_repository=todo_repository)
assert result.success

0 comments on commit 9456ab8

Please sign in to comment.