diff --git a/api/app/settings/common.py b/api/app/settings/common.py index aafd4afea939..f37f8a4ba462 100644 --- a/api/app/settings/common.py +++ b/api/app/settings/common.py @@ -917,13 +917,6 @@ GITHUB_APP_ID: int = env.int("GITHUB_APP_ID", default=0) GITHUB_WEBHOOK_SECRET = env.str("GITHUB_WEBHOOK_SECRET", default="") -# MailerLite -MAILERLITE_BASE_URL = env.str( - "MAILERLITE_BASE_URL", default="https://api.mailerlite.com/api/v2/" -) -MAILERLITE_API_KEY = env.str("MAILERLITE_API_KEY", None) -MAILERLITE_NEW_USER_GROUP_ID = env.int("MAILERLITE_NEW_USER_GROUP_ID", None) - # Additional functionality for using SAML in Flagsmith SaaS SAML_INSTALLED = importlib.util.find_spec("saml") is not None diff --git a/api/organisations/models.py b/api/organisations/models.py index b87217b2181c..780f24a3c573 100644 --- a/api/organisations/models.py +++ b/api/organisations/models.py @@ -50,7 +50,6 @@ ) from organisations.subscriptions.metadata import BaseSubscriptionMetadata from organisations.subscriptions.xero.metadata import XeroSubscriptionMetadata -from users.utils.mailer_lite import MailerLite from webhooks.models import AbstractBaseExportableWebhookModel environment_cache = caches[settings.ENVIRONMENT_CACHE_NAME] @@ -280,13 +279,6 @@ def update_hubspot_active_subscription(self): update_hubspot_active_subscription.delay(args=(self.id,)) - @hook(AFTER_SAVE, when="cancellation_date", has_changed=True) - @hook(AFTER_SAVE, when="subscription_id", has_changed=True) - def update_mailer_lite_subscribers(self): - if settings.MAILERLITE_API_KEY: - mailer_lite = MailerLite() - mailer_lite.update_organisation_users(self.organisation.id) - def save_as_free_subscription(self): """ Wipes a subscription to a normal free plan. diff --git a/api/tests/unit/organisations/test_unit_organisations_models.py b/api/tests/unit/organisations/test_unit_organisations_models.py index cc015a85d671..85466cd1aaa5 100644 --- a/api/tests/unit/organisations/test_unit_organisations_models.py +++ b/api/tests/unit/organisations/test_unit_organisations_models.py @@ -5,7 +5,6 @@ from django.conf import settings from django.utils import timezone from pytest_mock import MockerFixture -from rest_framework.test import override_settings from environments.models import Environment from organisations.chargebee.metadata import ChargebeeObjMetadata @@ -194,63 +193,6 @@ def test_organisation_default_subscription_have_one_max_seat( assert subscription.max_seats == 1 -@override_settings(MAILERLITE_API_KEY="some-test-key") -def test_updating_subscription_id_calls_mailer_lite_update_organisation_users( - mocker, db, organisation, subscription -): - # Given - mocked_mailer_lite = mocker.MagicMock() - mocker.patch("organisations.models.MailerLite", return_value=mocked_mailer_lite) - - # When - subscription.subscription_id = "some-id" - subscription.save() - - # Then - mocked_mailer_lite.update_organisation_users.assert_called_with(organisation.id) - - -@override_settings(MAILERLITE_API_KEY="some-test-key") -def test_updating_a_cancelled_subscription_calls_mailer_lite_update_organisation_users( - mocker, db, organisation, subscription -): - # Given - mocked_mailer_lite = mocker.MagicMock() - mocker.patch("organisations.models.MailerLite", return_value=mocked_mailer_lite) - - subscription.cancellation_date = datetime.now() - subscription.save() - - # reset the mock to remove the call by saving the subscription above - mocked_mailer_lite.reset_mock() - - # When - subscription.cancellation_date = None - subscription.save() - - # Then - mocked_mailer_lite.update_organisation_users.assert_called_with(organisation.id) - - -@override_settings(MAILERLITE_API_KEY="some-test-key") -def test_cancelling_a_subscription_calls_mailer_lite_update_organisation_users( - mocker, db, organisation, subscription -): - # Given - - mocked_mailer_lite = mocker.MagicMock() - mocker.patch("organisations.models.MailerLite", return_value=mocked_mailer_lite) - - # When - subscription.cancellation_date = datetime.now() - subscription.save() - - # Then - mocked_mailer_lite.update_organisation_users.assert_called_once_with( - organisation.id - ) - - def test_organisation_is_paid_returns_false_if_subscription_does_not_exists(db): # Given organisation = Organisation.objects.create(name="Test org") diff --git a/api/tests/unit/users/test_unit_users_models.py b/api/tests/unit/users/test_unit_users_models.py index 3ca3f3e444b1..fcc17d4a8157 100644 --- a/api/tests/unit/users/test_unit_users_models.py +++ b/api/tests/unit/users/test_unit_users_models.py @@ -1,5 +1,3 @@ -from unittest import mock - import pytest from django.db.utils import IntegrityError @@ -156,56 +154,6 @@ def test_has_organisation_permission_is_false_when_user_does_not_have_permission ) -@pytest.mark.django_db -def test_creating_a_user_calls_mailer_lite_subscribe(mocker): - # Given - mailer_lite_mock = mocker.patch("users.models.mailer_lite") - # When - user = FFAdminUser.objects.create( - email="test@mail.com", - ) - # Then - mailer_lite_mock.subscribe.assert_called_with(user) - - -@pytest.mark.django_db -def test_user_add_organisation_does_not_call_mailer_lite_subscribe_for_unpaid_organisation( - mocker, -): - user = FFAdminUser.objects.create(email="test@example.com") - organisation = Organisation.objects.create(name="Test Organisation") - mailer_lite_mock = mocker.patch("users.models.mailer_lite") - mocker.patch( - "organisations.models.Organisation.is_paid", - new_callable=mock.PropertyMock, - return_value=False, - ) - # When - user.add_organisation(organisation, OrganisationRole.USER) - - # Then - mailer_lite_mock.subscribe.assert_not_called() - - -@pytest.mark.django_db -def test_user_add_organisation_calls_mailer_lite_subscribe_for_paid_organisation( - mocker, -): - mailer_lite_mock = mocker.patch("users.models.mailer_lite") - user = FFAdminUser.objects.create(email="test@example.com") - organisation = Organisation.objects.create(name="Test Organisation") - mocker.patch( - "organisations.models.Organisation.is_paid", - new_callable=mock.PropertyMock, - return_value=True, - ) - # When - user.add_organisation(organisation, OrganisationRole.USER) - - # Then - mailer_lite_mock.subscribe.assert_called_with(user) - - def test_user_add_organisation_adds_user_to_the_default_user_permission_group( test_user, organisation, default_user_permission_group, user_permission_group ): diff --git a/api/tests/unit/users/utils/test_unit_users_mailer_lite.py b/api/tests/unit/users/utils/test_unit_users_mailer_lite.py deleted file mode 100644 index 4ed747aafa9d..000000000000 --- a/api/tests/unit/users/utils/test_unit_users_mailer_lite.py +++ /dev/null @@ -1,140 +0,0 @@ -import json - -import pytest - -from users.models import FFAdminUser -from users.utils.mailer_lite import ( - BatchSubscribe, - MailerLite, - _get_request_body_from_user, -) - - -@pytest.mark.django_db -def test_mailer_lite_subscribe_calls_post_with_correct_arguments(mocker, settings): - # Given - mock_session = mocker.MagicMock() - - base_url = "http//localhost/mailer/test/" - settings.MAILERLITE_BASE_URL = base_url - resource = "/test" - user = FFAdminUser.objects.create( - email="test_user", - first_name="test", - last_name="test", - marketing_consent_given=True, - ) - mailer_lite = MailerLite(session=mock_session) - - mocker.patch("users.utils.mailer_lite.MailerLite.resource", resource) - mocked_headers = mocker.patch( - "users.utils.mailer_lite.MailerLiteBaseClient.request_headers", - ) - # When - mailer_lite._subscribe(user) - # Then - mock_session.post.assert_called_with( - base_url + resource, - data=json.dumps( - {"email": user.email, "name": "test test", "fields": {"is_paid": False}} - ), - headers=mocked_headers, - ) - - -@pytest.mark.django_db -def test_batch_subscribe__subscribe_calls_batch_send_correct_number_of_times(mocker): - # Given - user1 = FFAdminUser.objects.create( - email="test_user1", first_name="test", last_name="test" - ) - user2 = FFAdminUser.objects.create( - email="test_user2", first_name="test", last_name="test" - ) - user3 = FFAdminUser.objects.create( - email="test_user3", first_name="test", last_name="test" - ) - - users = [user1, user2, user3] - - mock_session = mocker.MagicMock() - - # When - with BatchSubscribe(batch_size=2, session=mock_session) as batch: - for user in users: - batch.subscribe(user) - - # Then - # assert that batch_send is called (using requests made) twice, first time for - # hitting the maximum limit and second time - # for exiting the context manager - assert mock_session.post.call_count == 2 - - -@pytest.mark.django_db -def test_batch_subscribe__subscribe_populates_batch_correctly(mocker): - # Given - user1 = FFAdminUser.objects.create( - email="test_user1", first_name="test", last_name="test" - ) - user2 = FFAdminUser.objects.create( - email="test_user2", first_name="test", last_name="test" - ) - users = [user1, user2] - # When - with BatchSubscribe(mocker.MagicMock()) as batch: - for user in users: - batch.subscribe(user) - # Then - len(batch._batch) == len(users) - assert batch._batch[0]["body"]["email"] == users[0].email - assert batch._batch[1]["body"]["email"] == users[1].email - - -@pytest.mark.django_db -def test_get_request_body_from_user_with_paid_organisations( - organisation, chargebee_subscription -): - # Given - user = FFAdminUser.objects.create( - email="test_user1", first_name="test", last_name="test" - ) - - user.add_organisation(organisation) - - # When - data = _get_request_body_from_user(user) - - # Then - assert data == { - "email": user.email, - "name": "test test", - "fields": {"is_paid": True}, - } - - -def test_batch_subscribe_batch_send_makes_correct_post_request(mocker, settings): - # Given - mock_session = mocker.MagicMock() - mocked_headers = mocker.patch( - "users.utils.mailer_lite.MailerLiteBaseClient.request_headers", - ) - - base_url = "http//localhost/mailer/test/" - settings.MAILERLITE_BASE_URL = base_url - resource = "batch" - - batch = BatchSubscribe(session=mock_session) - test_batch_data = [1, 2, 3] - - mocker.patch.object(batch, "_batch", test_batch_data.copy()) - - # When - batch.batch_send() - # Then - mock_session.post.assert_called_with( - base_url + resource, - data=json.dumps({"requests": test_batch_data}), - headers=mocked_headers, - ) - assert batch._batch == [] diff --git a/api/users/models.py b/api/users/models.py index cb3f30227e21..07e84513078a 100644 --- a/api/users/models.py +++ b/api/users/models.py @@ -36,7 +36,6 @@ from users.auth_type import AuthType from users.constants import DEFAULT_DELETE_ORPHAN_ORGANISATIONS_VALUE from users.exceptions import InvalidInviteError -from users.utils.mailer_lite import MailerLite if typing.TYPE_CHECKING: from environments.models import Environment @@ -47,7 +46,6 @@ ) logger = logging.getLogger(__name__) -mailer_lite = MailerLite() class SignUpType(models.TextChoices): @@ -125,10 +123,6 @@ class Meta: def __str__(self): return self.email - @hook(AFTER_CREATE) - def subscribe_to_mailing_list(self): - mailer_lite.subscribe(self) - @hook(AFTER_CREATE) def schedule_hubspot_tracking(self) -> None: if settings.ENABLE_HUBSPOT_LEAD_TRACKING: @@ -223,9 +217,6 @@ def get_admin_organisations(self): ) def add_organisation(self, organisation, role=OrganisationRole.USER): - if organisation.is_paid: - mailer_lite.subscribe(self) - UserOrganisation.objects.create( user=self, organisation=organisation, role=role.name ) diff --git a/api/users/utils/mailer_lite.py b/api/users/utils/mailer_lite.py deleted file mode 100644 index 9a1b36015482..000000000000 --- a/api/users/utils/mailer_lite.py +++ /dev/null @@ -1,99 +0,0 @@ -import json -import typing -from contextlib import AbstractContextManager - -import requests -from django.conf import settings - -from users import models -from util.util import postpone - -MAX_BATCH_SIZE = 50 - - -class MailerLiteBaseClient: - resource = None - request_headers = { - "X-MailerLite-ApiKey": settings.MAILERLITE_API_KEY, - "Content-Type": "application/json", - } - - def __init__(self, session: requests.Session = None): - self.base_url = settings.MAILERLITE_BASE_URL - self.session = session or requests.Session() - - def _post(self, data): - url = self.base_url + self.resource - self.session.post(url, data=json.dumps(data), headers=self.request_headers) - - -class MailerLite(MailerLiteBaseClient): - resource = f"groups/{settings.MAILERLITE_NEW_USER_GROUP_ID}/subscribers" - - @postpone - def subscribe(self, user: "models.FFAdminUser"): - self._subscribe(user) - - @postpone - def update_organisation_users(self, organisation_id: int): - return self._update_organisation_users(organisation_id) - - def _subscribe(self, user: "models.FFAdminUser"): - if not user.marketing_consent_given: - return - data = _get_request_body_from_user(user) - self._post(data) - - def _update_organisation_users(self, organisation_id: int): - users = models.FFAdminUser.objects.filter( - organisations__id=organisation_id, marketing_consent_given=True - ) - with BatchSubscribe() as batch: - for user in users: - batch.subscribe(user) - - -class BatchSubscribe(MailerLiteBaseClient, AbstractContextManager): - resource = "batch" - - def __init__(self, *args, batch_size: int = MAX_BATCH_SIZE, **kwargs): - super().__init__(*args, **kwargs) - self._batch = [] - - if batch_size > MAX_BATCH_SIZE: - raise ValueError("Batch size cannot be greater than %d.", MAX_BATCH_SIZE) - - self.max_batch_size = batch_size - - def _get_raw_subscribe_request(self, user): - return { - "method": "POST", - "path": "/api/v2/subscribers", - "body": _get_request_body_from_user(user), - } - - def batch_send(self): - data = {"requests": self._batch} - self._post(data) - self._batch.clear() - - def subscribe(self, user: "models.FFAdminUser"): - if len(self._batch) >= self.max_batch_size: - self.batch_send() - self._batch.append(self._get_raw_subscribe_request(user)) - - def __exit__(self, exc_type, exc_value, exc_traceback): - self.batch_send() - - -def _get_request_body_from_user(user: "models.FFAdminUser") -> typing.Mapping: - """Returns request body/payload for /subscribe request""" - return { - "email": user.email, - "name": user.get_full_name(), - "fields": { - "is_paid": user.organisations.filter( - subscription__cancellation_date=None - ).exists() - }, - } diff --git a/infrastructure/aws/production/ecs-task-definition-web.json b/infrastructure/aws/production/ecs-task-definition-web.json index 1a5092aa7293..2f5d1f1e5ce0 100644 --- a/infrastructure/aws/production/ecs-task-definition-web.json +++ b/infrastructure/aws/production/ecs-task-definition-web.json @@ -115,10 +115,6 @@ "name": "INFLUXDB_URL", "value": "https://eu-central-1-1.aws.cloud2.influxdata.com" }, - { - "name": "MAILERLITE_NEW_USER_GROUP_ID", - "value": "110227625" - }, { "name": "OAUTH_CLIENT_ID", "value": "232959427810-br6ltnrgouktp0ngsbs04o14ueb9rch0.apps.googleusercontent.com"