From 7a5803beccd27157094e3bfe881a2a52f282c5e6 Mon Sep 17 00:00:00 2001 From: Hamza Date: Thu, 16 Jan 2025 16:30:52 +0100 Subject: [PATCH 1/4] feat: add prolific integration (M2-8325) --- src/apps/integrations/domain.py | 1 + src/apps/integrations/prolific/__init__.py | 0 src/apps/integrations/prolific/domain.py | 17 ++++++++ .../integrations/prolific/service/__init__.py | 0 .../integrations/prolific/service/prolific.py | 39 +++++++++++++++++++ src/apps/integrations/service/integrations.py | 25 ++++++++++++ 6 files changed, 82 insertions(+) create mode 100644 src/apps/integrations/prolific/__init__.py create mode 100644 src/apps/integrations/prolific/domain.py create mode 100644 src/apps/integrations/prolific/service/__init__.py create mode 100644 src/apps/integrations/prolific/service/prolific.py diff --git a/src/apps/integrations/domain.py b/src/apps/integrations/domain.py index 3372d6031eb7..7dd20e8237e8 100644 --- a/src/apps/integrations/domain.py +++ b/src/apps/integrations/domain.py @@ -9,6 +9,7 @@ class AvailableIntegrations(str, Enum): LORIS = "LORIS" + PROLIFIC = "PROLIFIC" FUTURE = "FUTURE" diff --git a/src/apps/integrations/prolific/__init__.py b/src/apps/integrations/prolific/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/apps/integrations/prolific/domain.py b/src/apps/integrations/prolific/domain.py new file mode 100644 index 000000000000..bf6c38fedbc2 --- /dev/null +++ b/src/apps/integrations/prolific/domain.py @@ -0,0 +1,17 @@ +import json + +from pydantic import BaseModel + +from apps.integrations.db.schemas import IntegrationsSchema + + +class ProlificIntegration(BaseModel): + api_key: str + + @classmethod + def from_schema(cls, schema: IntegrationsSchema): + configuration = json.loads(schema.configuration.replace("'", '"')) + return cls(api_key=configuration["api_key"]) + + def __repr__(self): + return "ProlificIntegration()" diff --git a/src/apps/integrations/prolific/service/__init__.py b/src/apps/integrations/prolific/service/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/apps/integrations/prolific/service/prolific.py b/src/apps/integrations/prolific/service/prolific.py new file mode 100644 index 000000000000..781274de13ec --- /dev/null +++ b/src/apps/integrations/prolific/service/prolific.py @@ -0,0 +1,39 @@ +import uuid + +import requests +from fastapi import HTTPException + +from apps.integrations.crud.integrations import IntegrationsCRUD +from apps.integrations.db.schemas import IntegrationsSchema +from apps.integrations.domain import AvailableIntegrations +from apps.integrations.prolific.domain import ProlificIntegration +from apps.users.domain import User + + +class ProlificIntegrationService: + def __init__(self, applet_id: uuid.UUID, session, user: User) -> None: + self.applet_id = applet_id + self.session = session + self.user = user + self.type = AvailableIntegrations.PROLIFIC + + async def create_prolific_integration(self, api_key: str) -> ProlificIntegration: + prolific_response = requests.get( + "https://api.prolific.com/api/v1/users/me/", + headers={"Authorization": f"Token {api_key}", "Content-Type": "application/json"}, + ) + + if prolific_response.status_code != 200: + raise HTTPException(status_code=prolific_response.status_code, detail="Prolific token is invalid") + + integration_schema = await IntegrationsCRUD(self.session).create( + IntegrationsSchema( + applet_id=self.applet_id, + type=self.type, + configuration={ + "api_key": api_key, + }, + ) + ) + + return ProlificIntegration.from_schema(integration_schema) diff --git a/src/apps/integrations/service/integrations.py b/src/apps/integrations/service/integrations.py index c85442b36ac1..33d63ff8b444 100644 --- a/src/apps/integrations/service/integrations.py +++ b/src/apps/integrations/service/integrations.py @@ -7,6 +7,7 @@ ) from apps.integrations.loris.domain.loris_integrations import LorisIntegrationPublic from apps.integrations.loris.service.loris import LorisIntegrationService +from apps.integrations.prolific.service.prolific import ProlificIntegrationService from apps.integrations.service.future_integration import FutureIntegrationService from apps.users.domain import User @@ -43,6 +44,24 @@ async def create_integration(self, newIntegration: Integration) -> Integration: applet_id=newIntegration.applet_id, configuration=loris_integration, ) + case AvailableIntegrations.PROLIFIC: + expected_keys = ["api_key"] + if None in [newIntegration.configuration.get(k, None) for k in expected_keys]: + raise UnexpectedPropertiesForIntegration( + provided_keys=list(newIntegration.configuration.keys()), + expected_keys=expected_keys, + integration_type=AvailableIntegrations.PROLIFIC, + ) + prolific_integration = await ProlificIntegrationService( + newIntegration.applet_id, + self.session, + self.user, + ).create_prolific_integration(api_key=newIntegration.configuration["api_key"]) + return Integration( + integration_type=AvailableIntegrations.PROLIFIC, + applet_id=newIntegration.applet_id, + configuration=prolific_integration, + ) case AvailableIntegrations.FUTURE: expected_keys = ["endpoint", "api_key"] if None in [newIntegration.configuration.get(k, None) for k in expected_keys]: @@ -82,6 +101,12 @@ async def retrieve_integration(self, applet_id, integration_type) -> Integration applet_id=applet_id, configuration=loris_integration, ) + case AvailableIntegrations.PROLIFIC: + return Integration( + integration_type=AvailableIntegrations.PROLIFIC, + applet_id=applet_id, + configuration={}, # Configuration is empty as we don't want to share the api_key + ) case AvailableIntegrations.FUTURE: future_integration = FutureIntegrationPublic.from_schema(integration_schema) return Integration( From 62189f17de11253e518bbc07987b92e89af40d33 Mon Sep 17 00:00:00 2001 From: Hamza Date: Tue, 28 Jan 2025 20:09:11 +0100 Subject: [PATCH 2/4] fixup! feat: add prolific integration (M2-8325) --- src/apps/integrations/prolific/errors.py | 6 ++++++ src/apps/integrations/prolific/service/prolific.py | 4 ++-- src/apps/integrations/service/integrations.py | 4 ++-- src/apps/shared/exception.py | 5 +++++ 4 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 src/apps/integrations/prolific/errors.py diff --git a/src/apps/integrations/prolific/errors.py b/src/apps/integrations/prolific/errors.py new file mode 100644 index 000000000000..024c6bf2315b --- /dev/null +++ b/src/apps/integrations/prolific/errors.py @@ -0,0 +1,6 @@ +from gettext import gettext as _ + +from apps.shared.exception import UnauthorizedError + +class ProlificInvalidApiTokenError(UnauthorizedError): + message = _("Prolific token is invalid.") \ No newline at end of file diff --git a/src/apps/integrations/prolific/service/prolific.py b/src/apps/integrations/prolific/service/prolific.py index 781274de13ec..882a922b96f9 100644 --- a/src/apps/integrations/prolific/service/prolific.py +++ b/src/apps/integrations/prolific/service/prolific.py @@ -1,12 +1,12 @@ import uuid import requests -from fastapi import HTTPException from apps.integrations.crud.integrations import IntegrationsCRUD from apps.integrations.db.schemas import IntegrationsSchema from apps.integrations.domain import AvailableIntegrations from apps.integrations.prolific.domain import ProlificIntegration +from apps.integrations.prolific.errors import ProlificInvalidApiTokenError from apps.users.domain import User @@ -24,7 +24,7 @@ async def create_prolific_integration(self, api_key: str) -> ProlificIntegration ) if prolific_response.status_code != 200: - raise HTTPException(status_code=prolific_response.status_code, detail="Prolific token is invalid") + raise ProlificInvalidApiTokenError() integration_schema = await IntegrationsCRUD(self.session).create( IntegrationsSchema( diff --git a/src/apps/integrations/service/integrations.py b/src/apps/integrations/service/integrations.py index 33d63ff8b444..a4341b040c41 100644 --- a/src/apps/integrations/service/integrations.py +++ b/src/apps/integrations/service/integrations.py @@ -52,7 +52,7 @@ async def create_integration(self, newIntegration: Integration) -> Integration: expected_keys=expected_keys, integration_type=AvailableIntegrations.PROLIFIC, ) - prolific_integration = await ProlificIntegrationService( + await ProlificIntegrationService( newIntegration.applet_id, self.session, self.user, @@ -60,7 +60,7 @@ async def create_integration(self, newIntegration: Integration) -> Integration: return Integration( integration_type=AvailableIntegrations.PROLIFIC, applet_id=newIntegration.applet_id, - configuration=prolific_integration, + configuration={}, ) case AvailableIntegrations.FUTURE: expected_keys = ["endpoint", "api_key"] diff --git a/src/apps/shared/exception.py b/src/apps/shared/exception.py index 4ef39250654b..d7ba1c7f0029 100644 --- a/src/apps/shared/exception.py +++ b/src/apps/shared/exception.py @@ -69,6 +69,11 @@ class NotFoundError(BaseError): status_code = status.HTTP_404_NOT_FOUND type = ExceptionTypes.NOT_FOUND +class UnauthorizedError(BaseError): + message = _("Unauthorized.") + status_code = status.HTTP_401_UNAUTHORIZED + type = ExceptionTypes.ACCESS_DENIED + class InternalServerError(BaseError): pass From a24ed71967ff023ffaaa0313df25b60796308df2 Mon Sep 17 00:00:00 2001 From: Hamza Date: Wed, 29 Jan 2025 16:27:35 +0100 Subject: [PATCH 3/4] fixup! feat: add prolific integration (M2-8325) --- src/apps/integrations/prolific/errors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/integrations/prolific/errors.py b/src/apps/integrations/prolific/errors.py index 024c6bf2315b..9b0c8def05bb 100644 --- a/src/apps/integrations/prolific/errors.py +++ b/src/apps/integrations/prolific/errors.py @@ -3,4 +3,4 @@ from apps.shared.exception import UnauthorizedError class ProlificInvalidApiTokenError(UnauthorizedError): - message = _("Prolific token is invalid.") \ No newline at end of file + message = _("Prolific token is invalid.") From c73d1bdb46597bb3542caedb1d041710bcaf7fae Mon Sep 17 00:00:00 2001 From: Hamza Date: Wed, 29 Jan 2025 16:32:23 +0100 Subject: [PATCH 4/4] fixup! feat: add prolific integration (M2-8325) --- src/apps/integrations/prolific/errors.py | 1 + src/apps/shared/exception.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/apps/integrations/prolific/errors.py b/src/apps/integrations/prolific/errors.py index 9b0c8def05bb..aa5145aead48 100644 --- a/src/apps/integrations/prolific/errors.py +++ b/src/apps/integrations/prolific/errors.py @@ -2,5 +2,6 @@ from apps.shared.exception import UnauthorizedError + class ProlificInvalidApiTokenError(UnauthorizedError): message = _("Prolific token is invalid.") diff --git a/src/apps/shared/exception.py b/src/apps/shared/exception.py index d7ba1c7f0029..b11275e46f95 100644 --- a/src/apps/shared/exception.py +++ b/src/apps/shared/exception.py @@ -69,6 +69,7 @@ class NotFoundError(BaseError): status_code = status.HTTP_404_NOT_FOUND type = ExceptionTypes.NOT_FOUND + class UnauthorizedError(BaseError): message = _("Unauthorized.") status_code = status.HTTP_401_UNAUTHORIZED