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 98fc67c4..289791a1 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.environment import get_backend_url from backend.custom.graphql_base import CountableConnection, PlainTextNode @@ -384,7 +384,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: @@ -410,7 +410,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: @@ -418,6 +418,73 @@ 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 + if user is None: + return cls(ok=False, errors=["User is none"]) + + 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): + try: + remove_user(old_email) + except Exception: + pass + + subscription = user.pro_subscription + + if subscription is None: + return cls(ok=True) + + if not is_email_in_group(email): + try: + add_user(email) + except Exception: + pass + + return cls(ok=True) + except Exception as e: + logger.error(e) + return cls(ok=False, 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. @@ -443,6 +510,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): @@ -453,6 +521,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 22132072..fb1a29a4 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 from backend.custom.environment import get_backend_url @@ -87,13 +87,13 @@ def remove_user(email: str, group_key: str = None) -> None: service = get_service() service.members().delete( groupKey=group_key, - memberKey=email, + memberKey=email.lower(), ).execute() except HttpError as e: if e.resp.status == 404: logger.warning(f"{email} já foi removido do google groups") else: - send(f"Verifique o erro ao remover o usuário do google groups: {e}") + send(f"Verifique o erro ao remover o usuário do google groups '{email}': {e}") logger.error(e) raise e @@ -110,6 +110,37 @@ 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() + response = ( + service.members() + .get( + groupKey=group_key, + memberKey=email.lower(), + ) + .execute() + ) + + member_email = response.get("email") + if not member_email: + return False + + return member_email.lower() == email.lower() + except HttpError as e: + logger.error(f"Erro ao verificar o usuário {email} no grupo {group_key}: {e}") + return False + 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""" @@ -123,21 +154,32 @@ 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) + try: + if account: + remove_user(account.gcp_email or account.email) + else: + remove_user(event.customer.email) + except Exception as e: + logger.error(e) @webhooks.handler("customer.subscription.updated") @@ -159,9 +201,14 @@ 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 try: - remove_user(event.customer.email) + if account: + remove_user(account.gcp_email or account.email) + else: + remove_user(event.customer.email) except Exception as e: logger.error(e) @@ -169,29 +216,39 @@ def unsubscribe(event: Event, **kwargs): @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() - try: + try: + if account: + remove_user(account.gcp_email or account.email) + else: remove_user(event.customer.email) - except Exception as e: - logger.error(e) + except Exception as e: + logger.error(e) @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() - try: + try: + if account: + add_user(account.gcp_email or account.email) + else: add_user(event.customer.email) - except Exception as e: - logger.error(e) + except Exception as e: + logger.error(e) @webhooks.handler("setup_intent.succeeded")