From 1cadc7cc56c86b63037616cd72eae8ca73ea3539 Mon Sep 17 00:00:00 2001 From: Jhony Lucas Date: Sun, 29 Sep 2024 19:58:08 -0300 Subject: [PATCH] feat: add GCP email field to Account model and update related functionality --- .../migrations/0018_account_gcp_email.py | 20 ++++++ backend/apps/account/models.py | 1 + backend/apps/account/signals.py | 2 +- backend/apps/account_payment/graphql.py | 63 ++++++++++++++++++- backend/apps/account_payment/webhooks.py | 61 ++++++++++++++++-- 5 files changed, 137 insertions(+), 10 deletions(-) create mode 100644 backend/apps/account/migrations/0018_account_gcp_email.py diff --git a/backend/apps/account/migrations/0018_account_gcp_email.py b/backend/apps/account/migrations/0018_account_gcp_email.py new file mode 100644 index 00000000..bfb4c796 --- /dev/null +++ b/backend/apps/account/migrations/0018_account_gcp_email.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 4.2.16 on 2024-09-29 22:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("account", "0017_account_available_for_research_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="account", + name="gcp_email", + field=models.EmailField( + blank=True, max_length=254, null=True, verbose_name="GCP email" + ), + ), + ] diff --git a/backend/apps/account/models.py b/backend/apps/account/models.py index 52dee4c8..779225cf 100644 --- a/backend/apps/account/models.py +++ b/backend/apps/account/models.py @@ -211,6 +211,7 @@ class Account(BaseModel, AbstractBaseUser, PermissionsMixin): uuid = models.UUIDField(primary_key=False, default=uuid4) email = models.EmailField("Email", unique=True) + gcp_email = models.EmailField("GCP email", null=True, blank=True) # Google Cloud Platform email username = models.CharField("Username", max_length=40, blank=True, null=True, unique=True) first_name = models.CharField("Nome", max_length=40, blank=True) diff --git a/backend/apps/account/signals.py b/backend/apps/account/signals.py index a3d889a7..957d8408 100644 --- a/backend/apps/account/signals.py +++ b/backend/apps/account/signals.py @@ -62,7 +62,7 @@ def create_subscription(user: Account): customer.subscriber = user customer.save() # Add user to Google Group - add_user(user.email) + add_user(user.gcp_email or user.email) @receiver(post_save, sender=Account) diff --git a/backend/apps/account_payment/graphql.py b/backend/apps/account_payment/graphql.py index f69b9ba7..77f98caa 100644 --- a/backend/apps/account_payment/graphql.py +++ b/backend/apps/account_payment/graphql.py @@ -24,7 +24,7 @@ from stripe import SetupIntent from backend.apps.account.models import Account, Subscription -from backend.apps.account_payment.webhooks import add_user, remove_user +from backend.apps.account_payment.webhooks import add_user, is_email_in_group, remove_user from backend.custom.graphql_base import CountableConnection, PlainTextNode if settings.STRIPE_LIVE_MODE: @@ -382,7 +382,7 @@ def mutate(cls, root, info, account_id, subscription_id): subscription = Subscription.objects.get(id=subscription_id) assert admin.id == subscription.admin.id - add_user(account.email) + add_user(account.gcp_email or account.email) subscription.subscribers.add(account) return cls(ok=True) except Exception as e: @@ -408,7 +408,7 @@ def mutate(cls, root, info, account_id, subscription_id): account = Account.objects.get(id=account_id) subscription = Subscription.objects.get(id=subscription_id) assert admin.id == subscription.admin.id - remove_user(account.email) + remove_user(account.gcp_email or account.email) subscription.subscribers.remove(account) return cls(ok=True) except Exception as e: @@ -416,6 +416,61 @@ def mutate(cls, root, info, account_id, subscription_id): return cls(errors=[str(e)]) +class ChangeUserGCPEmail(Mutation): + """Change user GCP email""" + + ok = Boolean() + errors = List(String) + + class Arguments: + email = String(required=True) + + @classmethod + @login_required + def mutate(cls, root, info, email): + try: + user = info.context.user + old_email = user.gcp_email or user.email + + if old_email == email: + return cls(ok=True) + + user.gcp_email = email + user.save() + + if is_email_in_group(old_email): + remove_user(old_email) + + subscription = user.pro_subscription() + if subscription and not is_email_in_group(email): + add_user(email) + + return cls(ok=True) + except Exception as e: + logger.error(e) + return cls(errors=[str(e)]) + + +# Query to check based on a email if the user is in a group +class IsEmailInGoogleGroup(Mutation): + """Check if user is in group""" + + ok = Boolean() + errors = List(String) + + class Arguments: + email = String(required=True) + + @classmethod + @login_required + def mutate(cls, root, info, email): + try: + return cls(ok=is_email_in_group(email)) + except Exception as e: + logger.error(e) + return cls(errors=[str(e)]) + + def get_stripe_promo(promotion_code): """ Helper function to retrieve a Stripe Promotion Code by its code. @@ -441,6 +496,7 @@ def get_stripe_promo(promotion_code): class Query(ObjectType): stripe_price = PlainTextNode.Field(StripePriceNode) all_stripe_price = DjangoFilterConnectionField(StripePriceNode) + is_email_in_google_group = IsEmailInGoogleGroup.Field() class Mutation(ObjectType): @@ -451,6 +507,7 @@ class Mutation(ObjectType): create_stripe_customer_subscription = StripeSubscriptionCustomerCreateMutation.Field() update_stripe_customer_subscription = StripeSubscriptionCustomerDeleteMutation.Field() validate_stripe_coupon = StripeCouponValidationMutation.Field() + change_user_gcp_email = ChangeUserGCPEmail.Field() # Reference diff --git a/backend/apps/account_payment/webhooks.py b/backend/apps/account_payment/webhooks.py index b0f64888..73abf42a 100644 --- a/backend/apps/account_payment/webhooks.py +++ b/backend/apps/account_payment/webhooks.py @@ -10,7 +10,7 @@ from stripe import Customer as StripeCustomer from stripe import Subscription as StripeSubscription -from backend.apps.account.models import Subscription +from backend.apps.account.models import Account, Subscription from backend.custom.client import send_discord_message as send logger = logger.bind(module="payment") @@ -109,6 +109,31 @@ def list_user(group_key: str = None): raise e +def is_email_in_group(email: str, group_key: str = None) -> bool: + """Check if a user is in a Google group.""" + if not group_key: + group_key = settings.GOOGLE_DIRECTORY_GROUP_KEY + if "+" in email and email.index("+") < email.index("@"): + email = email.split("+")[0] + "@" + email.split("@")[1] + + try: + service = get_service() + service.members().get( + groupKey=group_key, + memberKey=email, + ).execute() + return True + except HttpError as e: + if e.resp.status == 404: + return False + else: + logger.error(f"Erro ao verificar o usuário {email} no grupo {group_key}: {e}") + raise e + except Exception as e: + logger.error(f"Erro inesperado ao verificar o usuário {email}: {e}") + raise e + + @webhooks.handler("customer.updated") def update_customer(event: Event, **kwargs): """Propagate customer email update if exists""" @@ -122,21 +147,29 @@ def update_customer(event: Event, **kwargs): def handle_subscription(event: Event): """Handle subscription status""" subscription = get_subscription(event) + account = Account.objects.filter(email=event.customer.email).first() if event.data["object"]["status"] in ["trialing", "active"]: if subscription: logger.info(f"Adicionando a inscrição do cliente {event.customer.email}") subscription.is_active = True subscription.save() + # Add user to google group if subscription exists or not - add_user(event.customer.email) + if account: + add_user(account.gcp_email or account.email) + else: + add_user(event.customer.email) else: if subscription: logger.info(f"Removendo a inscrição do cliente {event.customer.email}") subscription.is_active = False subscription.save() # Remove user from google group if subscription exists or not - remove_user(event.customer.email) + if account: + remove_user(account.gcp_email or account.email) + else: + remove_user(event.customer.email) @webhooks.handler("customer.subscription.updated") @@ -158,28 +191,44 @@ def unsubscribe(event: Event, **kwargs): logger.info(f"Removendo a inscrição do cliente {event.customer.email}") subscription.is_active = False subscription.save() + + account = Account.objects.filter(email=event.customer.email).first() # Remove user from google group if subscription exists or not - remove_user(event.customer.email) + if account: + remove_user(account.gcp_email or account.email) + else: + remove_user(event.customer.email) @webhooks.handler("customer.subscription.paused") def pause_subscription(event: Event, **kwargs): """Pause customer subscription""" + account = Account.objects.filter(email=event.customer.email).first() + if subscription := get_subscription(event): logger.info(f"Pausando a inscrição do cliente {event.customer.email}") subscription.is_active = False subscription.save() - remove_user(event.customer.email) + if account: + remove_user(account.gcp_email or account.email) + else: + remove_user(event.customer.email) @webhooks.handler("customer.subscription.resumed") def resume_subscription(event: Event, **kwargs): """Resume customer subscription""" + account = Account.objects.filter(email=event.customer.email).first() + if subscription := get_subscription(event): logger.info(f"Resumindo a inscrição do cliente {event.customer.email}") subscription.is_active = True subscription.save() - add_user(event.customer.email) + + if account: + add_user(account.gcp_email or account.email) + else: + add_user(event.customer.email) @webhooks.handler("setup_intent.succeeded")