diff --git a/.github/workflows/time-tracker-v1-on-pull-request-workflow.yml b/.github/workflows/time-tracker-v1-on-pull-request-workflow.yml new file mode 100644 index 00000000..af4d872a --- /dev/null +++ b/.github/workflows/time-tracker-v1-on-pull-request-workflow.yml @@ -0,0 +1,60 @@ +name: Time Tacker V1 CI + +on: + pull_request: + branches: [master] + +jobs: + time-tracker-ci: + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: [3.9] + + steps: + - name: Checking out code from the repository + uses: actions/checkout@v2 + + - name: Setting up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements/time_tracker_api/dev.txt + pip install -r requirements/time_tracker_events/dev.txt + + - name: Login to azure + uses: Azure/login@v1 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: Get vault from azure + uses: Azure/get-keyvault-secrets@v1 + with: + keyvault: "time-tracker-secrets" + secrets: "MS-CLIENT-ID, MS-AUTHORITY, MS-SCOPE, MS-SECRET, MS-ENDPOINT, USERID, AZURE-APP-CONFIGURATION-CONNECTION-STRING, DATABASE-ACCOUNT-URI, DATABASE-MASTER-KEY, DATABASE-NAME, AZURE-STORAGE-ACCOUNT-KEY" + id: timeTrackerAzureVault + + - name: Run tests + env: + MS_AUTHORITY: ${{ steps.timeTrackerAzureVault.outputs.MS-AUTHORITY }} + MS_CLIENT_ID: ${{ steps.timeTrackerAzureVault.outputs.MS-CLIENT-ID }} + MS_SCOPE: ${{ steps.timeTrackerAzureVault.outputs.MS-SCOPE }} + MS_SECRET: ${{ steps.timeTrackerAzureVault.outputs.MS-SECRET }} + MS_ENDPOINT: ${{ steps.timeTrackerAzureVault.outputs.MS-ENDPOINT }} + USERID: ${{ steps.timeTrackerAzureVault.outputs.USERID }} + AZURE_APP_CONFIGURATION_CONNECTION_STRING: ${{ steps.timeTrackerAzureVault.outputs.AZURE-APP-CONFIGURATION-CONNECTION-STRING }} + DATABASE_ACCOUNT_URI: ${{ steps.timeTrackerAzureVault.outputs.DATABASE-ACCOUNT-URI }} + DATABASE_MASTER_KEY: ${{ steps.timeTrackerAzureVault.outputs.DATABASE-MASTER-KEY }} + DATABASE_NAME: ${{ steps.timeTrackerAzureVault.outputs.DATABASE-NAME }} + AZURE_STORAGE_ACCOUNT_KEY: ${{ steps.timeTrackerAzureVault.outputs.AZURE-STORAGE-ACCOUNT-KEY }} + run: | + pytest tests + + - name: Test the build of the app + run: | + docker build . diff --git a/V2/serverless.yml b/V2/serverless.yml index c6c5e34b..12224f2b 100644 --- a/V2/serverless.yml +++ b/V2/serverless.yml @@ -86,3 +86,23 @@ functions: - POST route: time-entries/ authLevel: anonymous + + delete_time_entry: + handler: time_tracker/time_entries/interface.delete_time_entry + events: + - http: true + x-azure-settings: + methods: + - DELETE + route: time-entries/{id} + authLevel: anonymous + + update_time_entry: + handler: time_tracker/time_entries/interface.update_time_entry + events: + - http: true + x-azure-settings: + methods: + - PUT + route: time-entries/{id} + authLevel: anonymous diff --git a/V2/tests/api/azure/time_entry_azure_endpoints_test.py b/V2/tests/api/azure/time_entry_azure_endpoints_test.py index f801dad9..85bb6f71 100644 --- a/V2/tests/api/azure/time_entry_azure_endpoints_test.py +++ b/V2/tests/api/azure/time_entry_azure_endpoints_test.py @@ -1,12 +1,27 @@ +import pytest import json +from faker import Faker import azure.functions as func import time_tracker.time_entries._application._time_entries as azure_time_entries +from time_tracker._infrastructure import DB +from time_tracker.time_entries import _domain as domain_time_entries +from time_tracker.time_entries import _infrastructure as infrastructure_time_entries + TIME_ENTRY_URL = "/api/time-entries/" +@pytest.fixture(name='insert_time_entry') +def _insert_time_entry() -> domain_time_entries.TimeEntry: + def _new_time_entry(time_entry: domain_time_entries.TimeEntry, database: DB): + dao = infrastructure_time_entries.TimeEntriesSQLDao(database) + new_time_entry = dao.create(time_entry) + return new_time_entry + return _new_time_entry + + def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_all_attributes( test_db, time_entry_factory, activity_factory, insert_activity ): @@ -26,3 +41,79 @@ def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_ assert response.status_code == 201 assert time_entry_json_data == time_entry_body + + +def test__delete_time_entries_azure_endpoint__returns_an_time_entry_with_true_deleted__when_its_id_is_found( + test_db, time_entry_factory, insert_time_entry, insert_activity, activity_factory, +): + inserted_activity = insert_activity(activity_factory(), test_db).__dict__ + time_entry_body = time_entry_factory(activity_id=inserted_activity["id"], technologies="[jira,sql]") + inserted_time_entry = insert_time_entry(time_entry_body, test_db) + + req = func.HttpRequest( + method='DELETE', + body=None, + url=TIME_ENTRY_URL, + route_params={"id": inserted_time_entry.id}, + ) + + response = azure_time_entries._delete_time_entry.delete_time_entry(req) + time_entry_json_data = json.loads(response.get_body().decode("utf-8")) + + assert response.status_code == 200 + assert time_entry_json_data['deleted'] is True + + +def test__delete_time_entries_azure_endpoint__returns_a_status_code_400__when_time_entry_recive_invalid_id( +): + req = func.HttpRequest( + method="DELETE", + body=None, + url=TIME_ENTRY_URL, + route_params={"id": "invalid id"}, + ) + + response = azure_time_entries._delete_time_entry.delete_time_entry(req) + + assert response.status_code == 400 + assert response.get_body() == b'Invalid Format ID' + + +def test__update_activity_azure_endpoint__returns_an_activity__when_found_an_activity_to_update( + test_db, time_entry_factory, insert_time_entry, activity_factory, insert_activity +): + inserted_activity = insert_activity(activity_factory(), test_db) + existent_time_entries = time_entry_factory(activity_id=inserted_activity.id, technologies="[jira,sql]") + inserted_time_entries = insert_time_entry(existent_time_entries, test_db).__dict__ + + time_entry_body = {"description": Faker().sentence()} + + req = func.HttpRequest( + method='PUT', + body=json.dumps(time_entry_body).encode("utf-8"), + url=TIME_ENTRY_URL, + route_params={"id": inserted_time_entries["id"]}, + ) + + response = azure_time_entries._update_time_entry.update_time_entry(req) + activitiy_json_data = response.get_body().decode("utf-8") + inserted_time_entries.update(time_entry_body) + + assert response.status_code == 200 + assert activitiy_json_data == json.dumps(inserted_time_entries) + + +def test__update_time_entries_azure_endpoint__returns_a_status_code_400__when_time_entry_recive_invalid_id(): + time_entry_body = {"description": Faker().sentence()} + + req = func.HttpRequest( + method="PUT", + body=json.dumps(time_entry_body).encode("utf-8"), + url=TIME_ENTRY_URL, + route_params={"id": "invalid id"}, + ) + + response = azure_time_entries._update_time_entry.update_time_entry(req) + + assert response.status_code == 400 + assert response.get_body() == b'Invalid ID' diff --git a/V2/tests/fixtures.py b/V2/tests/fixtures.py index 51ee5e5d..8568bdb6 100644 --- a/V2/tests/fixtures.py +++ b/V2/tests/fixtures.py @@ -10,7 +10,10 @@ @pytest.fixture(name='activity_factory') def _activity_factory() -> activities_domain.Activity: def _make_activity( - name: str = Faker().name(), description: str = Faker().sentence(), deleted: bool = False, status: int = 1 + name: str = Faker().name(), + description: str = Faker().sentence(), + deleted: bool = False, + status: int = 1, ): activity = activities_domain.Activity( id=None, @@ -20,6 +23,7 @@ def _make_activity( status=status ) return activity + return _make_activity diff --git a/V2/tests/integration/daos/time_entries_dao_test.py b/V2/tests/integration/daos/time_entries_dao_test.py index 403f80c6..995d4c6d 100644 --- a/V2/tests/integration/daos/time_entries_dao_test.py +++ b/V2/tests/integration/daos/time_entries_dao_test.py @@ -1,5 +1,5 @@ import pytest - +from faker import Faker import time_tracker.time_entries._domain as domain import time_tracker.time_entries._infrastructure as infrastructure @@ -46,3 +46,55 @@ def test__time_entry__returns_None__when_not_saves_correctly( inserted_time_entry = dao.create(time_entry_to_insert) assert inserted_time_entry is None + + +def test_delete__returns_an_time_entry_with_true_deleted__when_an_time_entry_matching_its_id_is_found( + create_fake_dao, test_db, time_entry_factory, insert_activity, activity_factory +): + dao = create_fake_dao(test_db) + inserted_activity = insert_activity(activity_factory(), dao.db) + existent_time_entry = time_entry_factory(activity_id=inserted_activity.id, technologies="[jira,sql]") + inserted_time_entry = dao.create(existent_time_entry) + + result = dao.delete(inserted_time_entry.id) + + assert result.deleted is True + + +def test_delete__returns_none__when_no_time_entry_matching_its_id_is_found( + create_fake_dao, test_db +): + dao = create_fake_dao(test_db) + + result = dao.delete(Faker().pyint()) + + assert result is None + + +def test_update__returns_an_time_entry_dto__when_found_one_time_entry_to_update( + test_db, create_fake_dao, time_entry_factory, insert_activity, activity_factory +): + dao = create_fake_dao(test_db) + inserted_activity = insert_activity(activity_factory(), dao.db) + existent_time_entries = time_entry_factory(activity_id=inserted_activity.id, technologies="[jira,sql]") + inserted_time_entries = dao.create(existent_time_entries).__dict__ + time_entry_id = inserted_time_entries["id"] + inserted_time_entries.update({"description": "description updated"}) + + time_entry = dao.update(time_entry_id=time_entry_id, time_entry_data=inserted_time_entries) + + assert time_entry.id == time_entry_id + assert time_entry.description == "description updated" + + +def test_update__returns_none__when_doesnt_found_one_time_entry_to_update( + test_db, create_fake_dao, time_entry_factory, insert_activity, activity_factory +): + dao = create_fake_dao(test_db) + inserted_activity = insert_activity(activity_factory(), dao.db) + existent_time_entries = time_entry_factory(activity_id=inserted_activity.id, technologies="[jira,sql]") + inserted_time_entries = dao.create(existent_time_entries).__dict__ + + time_entry = dao.update(0, inserted_time_entries) + + assert time_entry is None diff --git a/V2/tests/unit/services/time_entry_service_test.py b/V2/tests/unit/services/time_entry_service_test.py index bd5ce085..0952f8a9 100644 --- a/V2/tests/unit/services/time_entry_service_test.py +++ b/V2/tests/unit/services/time_entry_service_test.py @@ -1,3 +1,5 @@ +from faker import Faker + from time_tracker.time_entries._domain import TimeEntryService @@ -12,3 +14,35 @@ def test__create_time_entries__uses_the_time_entry_dao__to_create_an_time_entry( assert time_entry_dao.create.called assert expected_time_entry == actual_time_entry + + +def test__delete_time_entry__uses_the_time_entry_dao__to_delete_time_entry_selected( + mocker, +): + expected_time_entry = mocker.Mock() + time_entry_dao = mocker.Mock( + delete=mocker.Mock(return_value=expected_time_entry) + ) + + time_entry_service = TimeEntryService(time_entry_dao) + deleted_time_entry = time_entry_service.delete(Faker().pyint()) + + assert time_entry_dao.delete.called + assert expected_time_entry == deleted_time_entry + + +def test__update_time_entry__uses_the_time_entry_dao__to_update_one_time_entry( + mocker, +): + expected_time_entry = mocker.Mock() + time_entry_dao = mocker.Mock( + update=mocker.Mock(return_value=expected_time_entry) + ) + time_entry_service = TimeEntryService(time_entry_dao) + + updated_time_entry = time_entry_service.update( + Faker().pyint(), Faker().pydict() + ) + + assert time_entry_dao.update.called + assert expected_time_entry == updated_time_entry diff --git a/V2/tests/unit/use_cases/time_entries_use_case_test.py b/V2/tests/unit/use_cases/time_entries_use_case_test.py index d2a31eb7..1a679f37 100644 --- a/V2/tests/unit/use_cases/time_entries_use_case_test.py +++ b/V2/tests/unit/use_cases/time_entries_use_case_test.py @@ -1,4 +1,5 @@ from pytest_mock import MockFixture +from faker import Faker from time_tracker.time_entries._domain import _use_cases @@ -16,3 +17,29 @@ def test__create_time_entry_function__uses_the_time_entries_service__to_create_t assert time_entry_service.create.called assert expected_time_entry == actual_time_entry + + +def test__delete_time_entry_function__uses_the_time_entry_service__to_delete_time_entry_selected( + mocker: MockFixture, +): + expected_time_entry = mocker.Mock() + time_entry_service = mocker.Mock(delete=mocker.Mock(return_value=expected_time_entry)) + + time_entry_use_case = _use_cases.DeleteTimeEntryUseCase(time_entry_service) + deleted_time_entry = time_entry_use_case.delete_time_entry(Faker().pyint()) + + assert time_entry_service.delete.called + assert expected_time_entry == deleted_time_entry + + +def test__update_time_entries_function__uses_the_time_entry_service__to_update_an_time_entry( + mocker: MockFixture, +): + expected_time_entry = mocker.Mock() + time_entry_service = mocker.Mock(update=mocker.Mock(return_value=expected_time_entry)) + + time_entry_use_case = _use_cases.UpdateTimeEntryUseCase(time_entry_service) + updated_time_entry = time_entry_use_case.update_time_entry(Faker().uuid4(), Faker().pydict()) + + assert time_entry_service.update.called + assert expected_time_entry == updated_time_entry diff --git a/V2/time_tracker/time_entries/_application/__init__.py b/V2/time_tracker/time_entries/_application/__init__.py index 6e4ba9c3..cedea945 100644 --- a/V2/time_tracker/time_entries/_application/__init__.py +++ b/V2/time_tracker/time_entries/_application/__init__.py @@ -1,2 +1,3 @@ # flake8: noqa -from ._time_entries import create_time_entry \ No newline at end of file +from ._time_entries import create_time_entry, delete_time_entry +from ._time_entries import update_time_entry diff --git a/V2/time_tracker/time_entries/_application/_time_entries/__init__.py b/V2/time_tracker/time_entries/_application/_time_entries/__init__.py index b46cddce..0f6cf2db 100644 --- a/V2/time_tracker/time_entries/_application/_time_entries/__init__.py +++ b/V2/time_tracker/time_entries/_application/_time_entries/__init__.py @@ -1,2 +1,4 @@ # flake8: noqa -from ._create_time_entry import create_time_entry \ No newline at end of file +from ._create_time_entry import create_time_entry +from ._delete_time_entry import delete_time_entry +from ._update_time_entry import update_time_entry diff --git a/V2/time_tracker/time_entries/_application/_time_entries/_delete_time_entry.py b/V2/time_tracker/time_entries/_application/_time_entries/_delete_time_entry.py new file mode 100644 index 00000000..bbf76eab --- /dev/null +++ b/V2/time_tracker/time_entries/_application/_time_entries/_delete_time_entry.py @@ -0,0 +1,36 @@ +import json + +import azure.functions as func + +from ... import _domain +from ... import _infrastructure +from time_tracker._infrastructure import DB + + +def delete_time_entry(req: func.HttpRequest) -> func.HttpResponse: + time_entry_dao = _infrastructure.TimeEntriesSQLDao(DB()) + time_entry_service = _domain.TimeEntryService(time_entry_dao) + use_case = _domain._use_cases.DeleteTimeEntryUseCase(time_entry_service) + + try: + time_entry_id = int(req.route_params.get("id")) + deleted_time_entry = use_case.delete_time_entry(time_entry_id) + if not deleted_time_entry: + return func.HttpResponse( + body="Not found", + status_code=404, + mimetype="application/json" + ) + + return func.HttpResponse( + body=json.dumps(deleted_time_entry.__dict__, default=str), + status_code=200, + mimetype="application/json", + ) + + except ValueError: + return func.HttpResponse( + body=b"Invalid Format ID", + status_code=400, + mimetype="application/json" + ) diff --git a/V2/time_tracker/time_entries/_application/_time_entries/_update_time_entry.py b/V2/time_tracker/time_entries/_application/_time_entries/_update_time_entry.py new file mode 100644 index 00000000..34ffddf3 --- /dev/null +++ b/V2/time_tracker/time_entries/_application/_time_entries/_update_time_entry.py @@ -0,0 +1,47 @@ +import dataclasses +import json +import logging + +import azure.functions as func + +from time_tracker.time_entries._infrastructure import TimeEntriesSQLDao +from time_tracker.time_entries._domain import TimeEntryService, TimeEntry, _use_cases +from time_tracker._infrastructure import DB + + +def update_time_entry(req: func.HttpRequest) -> func.HttpResponse: + logging.info( + 'Python HTTP trigger function processed a request to update an time entry.' + ) + time_entry_id = req.route_params.get('id') + time_entry_data = req.get_json() if req.get_body() else {} + time_entry_keys = [field.name for field in dataclasses.fields(TimeEntry)] + + if all(key in time_entry_keys for key in time_entry_data.keys()): + try: + response = _update(int(time_entry_id), time_entry_data) + status_code = 200 + except ValueError: + response = b'Invalid ID' + status_code = 400 + else: + response = b'Incorrect time entry body' + status_code = 400 + + return func.HttpResponse( + body=response, status_code=status_code, mimetype="application/json" + ) + + +def _update(time_entry_id: int, time_entry_data: dict) -> str: + database = DB() + time_entry_use_case = _use_cases.UpdateTimeEntryUseCase( + _create_time_entry_service(database) + ) + time_entry = time_entry_use_case.update_time_entry(time_entry_id, time_entry_data) + return json.dumps(time_entry.__dict__) if time_entry else b'Not Found' + + +def _create_time_entry_service(db: DB): + time_entry_sql = TimeEntriesSQLDao(db) + return TimeEntryService(time_entry_sql) diff --git a/V2/time_tracker/time_entries/_domain/__init__.py b/V2/time_tracker/time_entries/_domain/__init__.py index a8b2081c..25025657 100644 --- a/V2/time_tracker/time_entries/_domain/__init__.py +++ b/V2/time_tracker/time_entries/_domain/__init__.py @@ -4,4 +4,6 @@ from ._services import TimeEntryService from ._use_cases import ( CreateTimeEntryUseCase, + DeleteTimeEntryUseCase, + UpdateTimeEntryUseCase ) \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_entities/__init__.py b/V2/time_tracker/time_entries/_domain/_entities/__init__.py index 88b4a739..3245a461 100644 --- a/V2/time_tracker/time_entries/_domain/_entities/__init__.py +++ b/V2/time_tracker/time_entries/_domain/_entities/__init__.py @@ -1,2 +1,2 @@ # flake8: noqa -from ._time_entry import TimeEntry \ No newline at end of file +from ._time_entry import TimeEntry diff --git a/V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py b/V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py index e10700ce..3f17d5ee 100644 --- a/V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py +++ b/V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py @@ -1,2 +1,2 @@ # flake8: noqa -from ._time_entries_dao import TimeEntriesDao \ No newline at end of file +from ._time_entries_dao import TimeEntriesDao diff --git a/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py index 5d04c861..8c1dc9d9 100644 --- a/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py +++ b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py @@ -7,3 +7,11 @@ class TimeEntriesDao(abc.ABC): @abc.abstractmethod def create(self, time_entry_data: TimeEntry) -> TimeEntry: pass + + @abc.abstractmethod + def delete(self, id: int) -> TimeEntry: + pass + + @abc.abstractmethod + def update(self, id: int, new_time_entry: dict) -> TimeEntry: + pass diff --git a/V2/time_tracker/time_entries/_domain/_services/__init__.py b/V2/time_tracker/time_entries/_domain/_services/__init__.py index e5e6ba1b..1a06f65b 100644 --- a/V2/time_tracker/time_entries/_domain/_services/__init__.py +++ b/V2/time_tracker/time_entries/_domain/_services/__init__.py @@ -1,2 +1,2 @@ # flake8: noqa -from ._time_entry import TimeEntryService \ No newline at end of file +from ._time_entry import TimeEntryService diff --git a/V2/time_tracker/time_entries/_domain/_services/_time_entry.py b/V2/time_tracker/time_entries/_domain/_services/_time_entry.py index d7aaf3ba..5c32c1e3 100644 --- a/V2/time_tracker/time_entries/_domain/_services/_time_entry.py +++ b/V2/time_tracker/time_entries/_domain/_services/_time_entry.py @@ -8,3 +8,9 @@ def __init__(self, time_entry_dao: TimeEntriesDao): def create(self, time_entry_data: TimeEntry) -> TimeEntry: return self.time_entry_dao.create(time_entry_data) + + def delete(self, id: int) -> TimeEntry: + return self.time_entry_dao.delete(id) + + def update(self, time_entry_id: int, new_time_entry: dict) -> TimeEntry: + return self.time_entry_dao.update(time_entry_id, new_time_entry) diff --git a/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py b/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py index 41aca738..4f0ac92e 100644 --- a/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py +++ b/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py @@ -1,2 +1,4 @@ # flake8: noqa from ._create_time_entry_use_case import CreateTimeEntryUseCase +from ._delete_time_entry_use_case import DeleteTimeEntryUseCase +from ._update_time_entry_use_case import UpdateTimeEntryUseCase diff --git a/V2/time_tracker/time_entries/_domain/_use_cases/_delete_time_entry_use_case.py b/V2/time_tracker/time_entries/_domain/_use_cases/_delete_time_entry_use_case.py new file mode 100644 index 00000000..a195c303 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_use_cases/_delete_time_entry_use_case.py @@ -0,0 +1,10 @@ +from time_tracker.time_entries._domain import TimeEntry, TimeEntryService + + +class DeleteTimeEntryUseCase: + + def __init__(self, time_entry_service: TimeEntryService): + self.time_entry_service = time_entry_service + + def delete_time_entry(self, id: int) -> TimeEntry: + return self.time_entry_service.delete(id) diff --git a/V2/time_tracker/time_entries/_domain/_use_cases/_update_time_entry_use_case.py b/V2/time_tracker/time_entries/_domain/_use_cases/_update_time_entry_use_case.py new file mode 100644 index 00000000..0e2cdf70 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_use_cases/_update_time_entry_use_case.py @@ -0,0 +1,11 @@ +from time_tracker.time_entries._domain import TimeEntryService, TimeEntry + + +class UpdateTimeEntryUseCase: + def __init__(self, time_entry_service: TimeEntryService): + self.time_entry_service = time_entry_service + + def update_time_entry( + self, time_entry_id: int, new_time_entry: dict + ) -> TimeEntry: + return self.time_entry_service.update(time_entry_id, new_time_entry) diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py index d233f3e9..03e2c566 100644 --- a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py @@ -44,6 +44,29 @@ def create(self, time_entry_data: domain.TimeEntry) -> domain.TimeEntry: except sqlalchemy.exc.SQLAlchemyError: return None + def update(self, time_entry_id: int, time_entry_data: dict) -> domain.TimeEntry: + + query = self.time_entry.update().where(self.time_entry.c.id == time_entry_id).values(time_entry_data) + self.db.get_session().execute(query) + query_deleted_time_entry = sqlalchemy.sql.select(self.time_entry).where(self.time_entry.c.id == time_entry_id) + time_entry = self.db.get_session().execute(query_deleted_time_entry).one_or_none() + + return self.__create_time_entry_dto(dict(time_entry)) if time_entry else None + + def delete(self, time_entry_id: int) -> domain.TimeEntry: + query = ( + self.time_entry.update() + .where(self.time_entry.c.id == time_entry_id) + .values({"deleted": True}) + ) + self.db.get_session().execute(query) + query_deleted_time_entry = sqlalchemy.sql.select(self.time_entry).where(self.time_entry.c.id == time_entry_id) + time_entry = self.db.get_session().execute(query_deleted_time_entry).one_or_none() + return self.__create_time_entry_dto(dict(time_entry)) if time_entry else None + def __create_time_entry_dto(self, time_entry: dict) -> domain.TimeEntry: + time_entry.update({ + "start_date": str(time_entry.get("start_date")), + "end_date": str(time_entry.get("end_date"))}) time_entry = {key: time_entry.get(key) for key in self.time_entry_key} return domain.TimeEntry(**time_entry) diff --git a/V2/time_tracker/time_entries/interface.py b/V2/time_tracker/time_entries/interface.py index d0182780..7e1be4ef 100644 --- a/V2/time_tracker/time_entries/interface.py +++ b/V2/time_tracker/time_entries/interface.py @@ -1,2 +1,4 @@ # flake8: noqa -from ._application import create_time_entry \ No newline at end of file +from ._application import create_time_entry +from ._application import delete_time_entry +from ._application import update_time_entry