diff --git a/organizator_api/app/applications/application/response.py b/organizator_api/app/applications/application/response.py index 56a51c2e..e038409f 100644 --- a/organizator_api/app/applications/application/response.py +++ b/organizator_api/app/applications/application/response.py @@ -5,6 +5,7 @@ from app.applications.domain.models.application import Application from app.events.application.response import EventResponse from app.events.domain.models.event import Event +from app.users.application.response import UserResponse from app.users.domain.models.user import User @@ -33,3 +34,11 @@ def to_dict_without_user(self) -> dict[str, Any]: "created_at": self.created_at.strftime("%Y-%m-%dT%H:%M:%SZ"), "updated_at": self.updated_at.strftime("%Y-%m-%dT%H:%M:%SZ"), } + + def to_dict_without_event(self) -> dict[str, Any]: + return { + "id": self.id, + "user": UserResponse.from_user(self.user).to_dict(), + "created_at": self.created_at.strftime("%Y-%m-%dT%H:%M:%SZ"), + "updated_at": self.updated_at.strftime("%Y-%m-%dT%H:%M:%SZ"), + } diff --git a/organizator_api/app/applications/domain/repositories.py b/organizator_api/app/applications/domain/repositories.py index d2154121..7cfd96f2 100644 --- a/organizator_api/app/applications/domain/repositories.py +++ b/organizator_api/app/applications/domain/repositories.py @@ -1,7 +1,9 @@ +import uuid from abc import ABC, abstractmethod from typing import List from app.applications.domain.models.application import Application +from app.events.domain.models.event import Event from app.users.domain.models.user import User @@ -13,3 +15,7 @@ def create(self, application: Application) -> None: @abstractmethod def get_by_user(self, user: User) -> List[Application]: pass + + @abstractmethod + def get_by_event(self, event_id: uuid.UUID) -> List[Application]: + pass diff --git a/organizator_api/app/applications/domain/usecases/get_applications_by_event_use_case.py b/organizator_api/app/applications/domain/usecases/get_applications_by_event_use_case.py new file mode 100644 index 00000000..f47cce53 --- /dev/null +++ b/organizator_api/app/applications/domain/usecases/get_applications_by_event_use_case.py @@ -0,0 +1,27 @@ +import uuid +from typing import List + +from app.applications.domain.models.application import Application +from app.applications.infrastructure.repository_factories import ( + ApplicationRepositoryFactory, +) +from app.events.domain.models.event import Event +from app.events.domain.usecases.get_event_use_case import GetEventUseCase +from app.users.domain.exceptions import OnlyAuthorizedToOrganizer +from app.users.domain.models.user import UserRoles +from app.users.domain.usecases.get_role_by_token_use_case import GetRoleByTokenUseCase + + +class GetApplicationsByEventUseCase: + def __init__(self) -> None: + self.application_repository = ApplicationRepositoryFactory.create() + + def execute(self, token: uuid.UUID, event_id: uuid.UUID) -> List[Application]: + role = GetRoleByTokenUseCase().execute(token=token) + + if role != UserRoles.ORGANIZER_ADMIN and role != UserRoles.ORGANIZER: + raise OnlyAuthorizedToOrganizer + + event = GetEventUseCase().execute(event_id=event_id) + + return self.application_repository.get_by_event(event_id=event.id) diff --git a/organizator_api/app/applications/infrastructure/http/urls.py b/organizator_api/app/applications/infrastructure/http/urls.py index 33ff80db..8c97ba4b 100644 --- a/organizator_api/app/applications/infrastructure/http/urls.py +++ b/organizator_api/app/applications/infrastructure/http/urls.py @@ -3,9 +3,11 @@ from app.applications.infrastructure.http.views import ( create_new_application, get_applications_by_token, + get_applications_by_event, ) urlpatterns = [ path("new", create_new_application), path("myevents", get_applications_by_token), + path("participants/", get_applications_by_event), ] diff --git a/organizator_api/app/applications/infrastructure/http/views.py b/organizator_api/app/applications/infrastructure/http/views.py index f7589b21..f30515ca 100644 --- a/organizator_api/app/applications/infrastructure/http/views.py +++ b/organizator_api/app/applications/infrastructure/http/views.py @@ -12,11 +12,14 @@ from app.applications.domain.usecases.create_new_application_use_case import ( CreateNewApplicationUseCase, ) +from app.applications.domain.usecases.get_applications_by_event_use_case import ( + GetApplicationsByEventUseCase, +) from app.applications.domain.usecases.get_applications_by_token_use_case import ( GetApplicationsByTokenUseCase, ) from app.events.domain.exceptions import EventNotFound -from app.users.domain.exceptions import UserNotFound +from app.users.domain.exceptions import UserNotFound, OnlyAuthorizedToOrganizer @require_http_methods(["POST"]) @@ -77,3 +80,34 @@ def get_applications_by_token(request: HttpRequest) -> HttpResponse: ) return HttpResponse(status=200, content=json.dumps(applications_response)) + + +@require_http_methods(["GET"]) +def get_applications_by_event( + request: HttpRequest, event_id: uuid.UUID +) -> HttpResponse: + token = request.headers.get("Authorization") + if not token: + return HttpResponse(status=401, content="Unauthorized") + + try: + token_to_uuid = uuid.UUID(token) + except ValueError: + return HttpResponse(status=400, content="Invalid token") + + try: + applications = GetApplicationsByEventUseCase().execute( + token=token_to_uuid, event_id=event_id + ) + except EventNotFound: + return HttpResponse(status=404, content="Event not found") + except OnlyAuthorizedToOrganizer: + return HttpResponse(status=401, content="Only authorized to organizer") + + applications_response = [] + for application in applications: + applications_response.append( + ApplicationResponse.from_application(application).to_dict_without_event() + ) + + return HttpResponse(status=200, content=json.dumps(applications_response)) diff --git a/organizator_api/app/applications/infrastructure/persistence/orm_applications_respository.py b/organizator_api/app/applications/infrastructure/persistence/orm_applications_respository.py index 772b9969..f9ce219a 100644 --- a/organizator_api/app/applications/infrastructure/persistence/orm_applications_respository.py +++ b/organizator_api/app/applications/infrastructure/persistence/orm_applications_respository.py @@ -1,3 +1,4 @@ +import uuid from typing import List from django.db import IntegrityError @@ -38,6 +39,14 @@ def get_by_user(self, user: User) -> List[Application]: for application in ORMEventApplication.objects.filter(user=user_orm) ] + def get_by_event(self, event_id: uuid.UUID) -> List[Application]: + event_orm = ORMEvent.objects.get(id=event_id) + + return [ + self._to_domain_model(application) + for application in ORMEventApplication.objects.filter(event=event_orm) + ] + def _to_domain_model(self, orm_application: ORMEventApplication) -> Application: return Application( id=orm_application.id, diff --git a/organizator_api/tests/applications/domain/usecases/test_get_applications_by_event_use_case.py b/organizator_api/tests/applications/domain/usecases/test_get_applications_by_event_use_case.py new file mode 100644 index 00000000..e11c1b8e --- /dev/null +++ b/organizator_api/tests/applications/domain/usecases/test_get_applications_by_event_use_case.py @@ -0,0 +1,89 @@ +import uuid + +from app.applications.domain.usecases.get_applications_by_event_use_case import ( + GetApplicationsByEventUseCase, +) +from app.events.domain.exceptions import EventNotFound +from app.users.domain.exceptions import OnlyAuthorizedToOrganizer +from app.users.domain.models.user import UserRoles +from tests.api_tests import ApiTests +from tests.applications.domain.ApplicationFactory import ApplicationFactory +from tests.events.domain.EventFactory import EventFactory +from tests.users.domain.UserFactory import UserFactory + + +class TestGetApplicationsByEventUseCase(ApiTests): + def setUp(self) -> None: + super().setUp() + self.application_repository.clear() + self.user_repository.clear() + self.event_repository.clear() + + self.user_token_participant = uuid.UUID("eb41b762-5988-4fa3-8942-7a91ccb00686") + self.user_token_organizer = uuid.UUID("eb41b762-5988-4fa3-8942-7a91ccb00687") + + self.user_participant = UserFactory().create( + new_id=uuid.UUID("eb41b762-5988-4fa3-8942-7a91ccb00686"), + token=self.user_token_participant, + username="john", + email="john@test.com", + ) + self.user_repository.create(self.user_participant) + + self.user_organizer = UserFactory().create( + new_id=uuid.UUID("eb41b762-5988-4fa3-8942-7a91ccb00687"), + token=self.user_token_organizer, + username="jane", + email="jane@test.com", + role=UserRoles.ORGANIZER, + ) + self.user_repository.create(self.user_organizer) + + self.event_id = uuid.UUID("eb41b762-5988-4fa3-8942-7a91ccb00686") + self.event = EventFactory().create(new_id=self.event_id, name="HackUPC 2024") + self.event_repository.create(self.event) + + def test__given_a_participant_token__when_get_application_by_event_id__then_only_authorized_to_organizer_is_raised( + self, + ) -> None: + # When / Then + with self.assertRaises(OnlyAuthorizedToOrganizer): + GetApplicationsByEventUseCase().execute( + event_id=self.event_id, token=self.user_token_participant + ) + + def test__given_a_non_existing_event__when_get_application_by_event_id__then_event_not_found_is_raised( + self, + ) -> None: + # When / Then + with self.assertRaises(EventNotFound): + GetApplicationsByEventUseCase().execute( + event_id=uuid.uuid4(), token=self.user_token_organizer + ) + + def test__given_applications_in_the_bd__when_get_applications_by_event_id__then_a_list_of_applications_is_returned( + self, + ) -> None: + # Given + application1 = ApplicationFactory().create( + new_id=uuid.UUID("eb41b762-5988-4fa3-8942-7a91ccb00686"), + user=self.user_participant, + event=self.event, + ) + application2 = ApplicationFactory().create( + new_id=uuid.UUID("eb41b762-5988-4fa3-8942-7a91ccb00687"), + user=self.user_organizer, + event=self.event, + ) + self.application_repository.create(application1) + self.application_repository.create(application2) + + # When + applications = GetApplicationsByEventUseCase().execute( + event_id=self.event_id, token=self.user_token_organizer + ) + + # Then + self.assertEqual(len(applications), 2) + self.assertEqual(applications[0].id, application1.id) + self.assertEqual(applications[1].id, application2.id) diff --git a/organizator_api/tests/applications/infrastructure/http/test_view_get_participants_in_events.py b/organizator_api/tests/applications/infrastructure/http/test_view_get_participants_in_events.py new file mode 100644 index 00000000..07a09b22 --- /dev/null +++ b/organizator_api/tests/applications/infrastructure/http/test_view_get_participants_in_events.py @@ -0,0 +1,154 @@ +import uuid +from datetime import datetime, timezone + +from app.users.domain.models.user import UserRoles +from tests.api_tests import ApiTests +from tests.applications.domain.ApplicationFactory import ApplicationFactory +from tests.events.domain.EventFactory import EventFactory +from tests.users.domain.UserFactory import UserFactory + + +class TestViewGetParticipantsInEvents(ApiTests): + def setUp(self) -> None: + super().setUp() + self.application_repository.clear() + self.user_repository.clear() + self.event_repository.clear() + + self.user_token_participant = "eb41b762-5988-4fa3-8942-7a91ccb00686" + self.user_participant = UserFactory().create( + new_id=uuid.UUID("eb41b762-5988-4fa3-8942-7a91ccb00686"), + token=uuid.UUID(self.user_token_participant), + username="john", + email="john@test.com", + ) + self.user_repository.create(self.user_participant) + + self.user_token_organizer = "eb41b762-5988-4fa3-8942-7a91ccb00687" + user_organizer = UserFactory().create( + new_id=uuid.UUID("eb41b762-5988-4fa3-8942-7a91ccb00687"), + token=uuid.UUID(self.user_token_organizer), + username="jane", + email="jane@test.com", + role=UserRoles.ORGANIZER, + ) + self.user_repository.create(user_organizer) + + self.event_id = uuid.UUID("eb41b762-5988-4fa3-8942-7a91ccb00686") + self.event = EventFactory().create(new_id=self.event_id, name="HackUPC 2024") + self.event_repository.create(self.event) + + def test__when_get_participants_in_events_without_header__then_unauthorized_is_returned( + self, + ) -> None: + # When + response = self.client.get( + "/organizator-api/applications/participants/eb41b762-5988-4fa3-8942-7a91ccb00686", + content_type="application/json", + ) + + # Then + self.assertEqual(response.status_code, 401) + self.assertEqual(response.content, b"Unauthorized") + + def test__given_a_invalid_token__when_get_participants_in_events__then_invalid_token_is_returned( + self, + ) -> None: + # When + headers = {"HTTP_Authorization": "invalid_token"} + response = self.client.get( + "/organizator-api/applications/participants/eb41b762-5988-4fa3-8942-7a91ccb00686", + content_type="application/json", + **headers # type: ignore + ) + + # Then + self.assertEqual(response.status_code, 400) + self.assertEqual(response.content, b"Invalid token") + + def test__given_a_non_existing_event_token__when_get_participants_in_events__then_event_not_found_is_returned( + self, + ) -> None: + # Given + headers = {"HTTP_Authorization": self.user_token_organizer} + + # When + response = self.client.get( + "/organizator-api/applications/participants/eb41b762-5988-4fa3-8942-7a91ccb00685", + content_type="application/json", + **headers # type: ignore + ) + + # Then + self.assertEqual(response.content, b"Event not found") + self.assertEqual(response.status_code, 404) + + def test__given_a_participant_user_and_a_existing_event__when_get_participants_in_events__then_only_authorized_to_organizers_is_returned( + self, + ) -> None: + # Given + headers = {"HTTP_Authorization": self.user_token_participant} + + # When + response = self.client.get( + "/organizator-api/applications/participants/eb41b762-5988-4fa3-8942-7a91ccb00686", + content_type="application/json", + **headers # type: ignore + ) + + # Then + self.assertEqual(response.content, b"Only authorized to organizer") + self.assertEqual(response.status_code, 401) + + def test__given_a_organizer_user_and_a_existing_event_without_participants__when_get_participants_in_events__then_empty_list_is_returned( + self, + ) -> None: + # Given + headers = {"HTTP_Authorization": self.user_token_organizer} + + # When + response = self.client.get( + "/organizator-api/applications/participants/eb41b762-5988-4fa3-8942-7a91ccb00686", + content_type="application/json", + **headers # type: ignore + ) + + # Then + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b"[]") + + def test__given_a_organizer_user_and_a_existing_event_with_participants__when_get_participants_in_events__then_participants_list_is_returned( + self, + ) -> None: + # Given + application1 = ApplicationFactory().create( + new_id=uuid.UUID("eb41b762-5988-4fa3-8942-7a91ccb00686"), + user=self.user_participant, + event=self.event, + created_at=datetime(2024, 1, 9, 10, 47, 0, tzinfo=timezone.utc), + updated_at=datetime(2024, 1, 9, 10, 47, 0, tzinfo=timezone.utc), + ) + application2 = ApplicationFactory().create( + new_id=uuid.UUID("eb41b762-5988-4fa3-8942-7a91ccb00687"), + event=self.event, + created_at=datetime(2024, 1, 9, 10, 47, 0, tzinfo=timezone.utc), + updated_at=datetime(2024, 1, 9, 10, 47, 0, tzinfo=timezone.utc), + ) + self.application_repository.create(application1) + self.application_repository.create(application2) + + headers = {"HTTP_Authorization": self.user_token_organizer} + + # When + response = self.client.get( + "/organizator-api/applications/participants/eb41b762-5988-4fa3-8942-7a91ccb00686", + content_type="application/json", + **headers # type: ignore + ) + + # Then + self.assertEqual(response.status_code, 200) + self.assertEqual( + response.content, + b'[{"id": "eb41b762-5988-4fa3-8942-7a91ccb00686", "user": {"id": "eb41b762-5988-4fa3-8942-7a91ccb00686", "username": "john", "email": "john@test.com", "first_name": "Carlota", "last_name": "Catot", "bio": "The user that is using this application", "profile_image": "profile_picture.png", "role": "Participant", "date_of_birth": "07/05/1996", "study": true, "work": false, "university": "Universitat Polit\\u00e8cnica de Catalunya", "degree": "Computer Science", "expected_graduation": "01/05/2024", "current_job_role": "", "tshirt": "", "gender": "", "alimentary_restrictions": "", "github": "", "linkedin": "", "devpost": "", "webpage": ""}, "created_at": "2024-01-09T10:47:00Z", "updated_at": "2024-01-09T10:47:00Z"}, {"id": "eb41b762-5988-4fa3-8942-7a91ccb00687", "user": {"id": "ef6f6fb3-ba12-43dd-a0da-95de8125b1cc", "username": "carlotacb", "email": "carlota@hackupc.com", "first_name": "Carlota", "last_name": "Catot", "bio": "The user that is using this application", "profile_image": "profile_picture.png", "role": "Participant", "date_of_birth": "07/05/1996", "study": true, "work": false, "university": "Universitat Polit\\u00e8cnica de Catalunya", "degree": "Computer Science", "expected_graduation": "01/05/2024", "current_job_role": "", "tshirt": "", "gender": "", "alimentary_restrictions": "", "github": "", "linkedin": "", "devpost": "", "webpage": ""}, "created_at": "2024-01-09T10:47:00Z", "updated_at": "2024-01-09T10:47:00Z"}]', + ) diff --git a/organizator_api/tests/applications/infrastructure/persistence/test_orm_application_repository_get_by_event.py b/organizator_api/tests/applications/infrastructure/persistence/test_orm_application_repository_get_by_event.py new file mode 100644 index 00000000..fe78908a --- /dev/null +++ b/organizator_api/tests/applications/infrastructure/persistence/test_orm_application_repository_get_by_event.py @@ -0,0 +1,57 @@ +import uuid +from datetime import datetime, timezone + +from app.applications.domain.models.application import Application +from app.applications.infrastructure.persistence.orm_applications_respository import ( + ORMApplicationRepository, +) +from tests.api_tests import ApiTests + + +class TestORMApplicationRepositoryGetByEvent(ApiTests): + def test__given_no_applications_for_the_event__when_get_by_event__then_return_empty_list( + self, + ) -> None: + # Given + event = self.given_event_in_orm( + new_id=uuid.UUID("ef6f6fb3-ba12-43dd-a0da-95de8125b1cc"), name="event" + ) + + # When + response = ORMApplicationRepository().get_by_event(event_id=event.id) + + # Then + self.assertEqual([], response) + + def test__given_a_application_for_the_event__when_get_by_event__then_return_the_application( + self, + ) -> None: + # Given + user = self.given_user_in_orm( + new_id=uuid.UUID("ef6f6fb3-ba12-43dd-a0da-95de8125b1cc"), + username="john", + email="john@test.com", + ) + event = self.given_event_in_orm( + new_id=uuid.UUID("ef6f6fb3-ba46-43dd-a0da-95de8125b1cc"), name="event" + ) + application = Application( + id=uuid.UUID("ef6f6fb3-ba46-43dd-a0da-95de8125b1cc"), + user=user, + event=event, + created_at=datetime.now(tz=timezone.utc), + updated_at=datetime.now(tz=timezone.utc), + ) + + ORMApplicationRepository().create(application=application) + + # When + response = ORMApplicationRepository().get_by_event(event_id=event.id) + + # Then + self.assertEqual(1, len(response)) + self.assertEqual(application.id, response[0].id) + self.assertEqual(application.user.id, response[0].user.id) + self.assertEqual(application.event.id, response[0].event.id) + self.assertEqual(application.created_at, response[0].created_at) + self.assertEqual(application.updated_at, response[0].updated_at) diff --git a/organizator_api/tests/applications/mocks/application_repository_mock.py b/organizator_api/tests/applications/mocks/application_repository_mock.py index c620fb6f..f5048bc8 100644 --- a/organizator_api/tests/applications/mocks/application_repository_mock.py +++ b/organizator_api/tests/applications/mocks/application_repository_mock.py @@ -1,3 +1,4 @@ +import uuid from typing import List from app.applications.domain.exceptions import ApplicationAlreadyExists @@ -25,6 +26,14 @@ def get_by_user(self, user: User) -> List[Application]: return applications + def get_by_event(self, event_id: uuid.UUID) -> List[Application]: + applications = [] + for a in self.applications: + if event_id == a.event.id: + applications.append(a) + + return applications + def get_all(self) -> List[Application]: return self.applications