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/errors.py b/src/apps/integrations/prolific/errors.py new file mode 100644 index 000000000000..aa5145aead48 --- /dev/null +++ b/src/apps/integrations/prolific/errors.py @@ -0,0 +1,7 @@ +from gettext import gettext as _ + +from apps.shared.exception import UnauthorizedError + + +class ProlificInvalidApiTokenError(UnauthorizedError): + message = _("Prolific token is invalid.") 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..882a922b96f9 --- /dev/null +++ b/src/apps/integrations/prolific/service/prolific.py @@ -0,0 +1,39 @@ +import uuid + +import requests + +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 + + +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 ProlificInvalidApiTokenError() + + 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..a4341b040c41 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, + ) + 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={}, + ) 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( diff --git a/src/apps/shared/exception.py b/src/apps/shared/exception.py index 4ef39250654b..b11275e46f95 100644 --- a/src/apps/shared/exception.py +++ b/src/apps/shared/exception.py @@ -70,6 +70,12 @@ class NotFoundError(BaseError): type = ExceptionTypes.NOT_FOUND +class UnauthorizedError(BaseError): + message = _("Unauthorized.") + status_code = status.HTTP_401_UNAUTHORIZED + type = ExceptionTypes.ACCESS_DENIED + + class InternalServerError(BaseError): pass