From 398a9db23392952354e6300211f480b213ad9a84 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Thu, 19 Jan 2023 12:48:46 +0530 Subject: [PATCH 1/2] feat: create model for api token and endpoint for creating api tokens --- apiserver/plane/api/serializers/__init__.py | 4 +- apiserver/plane/api/serializers/api_token.py | 8 ++++ apiserver/plane/api/urls.py | 6 +++ apiserver/plane/api/views/__init__.py | 2 + apiserver/plane/api/views/api_token.py | 37 +++++++++++++++++++ apiserver/plane/db/models/__init__.py | 2 + apiserver/plane/db/models/api_token.py | 39 ++++++++++++++++++++ apiserver/plane/db/models/user.py | 3 +- 8 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 apiserver/plane/api/serializers/api_token.py create mode 100644 apiserver/plane/api/views/api_token.py create mode 100644 apiserver/plane/db/models/api_token.py diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/api/serializers/__init__.py index ba494ec9e55..8d43d90ffed 100644 --- a/apiserver/plane/api/serializers/__init__.py +++ b/apiserver/plane/api/serializers/__init__.py @@ -38,4 +38,6 @@ IssueStateSerializer, ) -from .module import ModuleWriteSerializer, ModuleSerializer, ModuleIssueSerializer \ No newline at end of file +from .module import ModuleWriteSerializer, ModuleSerializer, ModuleIssueSerializer + +from .api_token import APITokenSerializer \ No newline at end of file diff --git a/apiserver/plane/api/serializers/api_token.py b/apiserver/plane/api/serializers/api_token.py new file mode 100644 index 00000000000..247b3f0e7fa --- /dev/null +++ b/apiserver/plane/api/serializers/api_token.py @@ -0,0 +1,8 @@ +from .base import BaseSerializer +from plane.db.models import APIToken + + +class APITokenSerializer(BaseSerializer): + class Meta: + model = APIToken + fields = "__all__" diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index 050ce9c95f3..61afec877f1 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -83,6 +83,9 @@ ModuleViewSet, ModuleIssueViewSet, ## End Modules + # Api Tokens + ApiTokenEndpoint, + ## End Api Tokens ) @@ -673,4 +676,7 @@ name="project-module-issues", ), ## End Modules + # API Tokens + path("api-tokens/", ApiTokenEndpoint.as_view(), name="api-token"), + ## End API Tokens ] diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 1cadd95e6a0..c14d97db97a 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -71,3 +71,5 @@ ) from .module import ModuleViewSet, ModuleIssueViewSet + +from .api_token import ApiTokenEndpoint \ No newline at end of file diff --git a/apiserver/plane/api/views/api_token.py b/apiserver/plane/api/views/api_token.py new file mode 100644 index 00000000000..de68e7f0e0e --- /dev/null +++ b/apiserver/plane/api/views/api_token.py @@ -0,0 +1,37 @@ +# Python import +from uuid import uuid4 +from datetime import timedelta + +# Third party +from rest_framework.response import Response +from rest_framework import status +from sentry_sdk import capture_exception +from rest_framework_simplejwt.tokens import RefreshToken + +# Module import +from .base import BaseAPIView +from plane.db.models import User, APIToken +from plane.api.serializers import APITokenSerializer + + +class ApiTokenEndpoint(BaseAPIView): + def post(self, request): + try: + + label = request.data.get("label", str(uuid4().hex)) + + api_token = APIToken.objects.create( + label=label, + user=request.user, + ) + + serializer = APITokenSerializer(api_token) + return Response(serializer.data, status=status.HTTP_201_CREATED) + + except Exception as e: + print(e) + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) diff --git a/apiserver/plane/db/models/__init__.py b/apiserver/plane/db/models/__init__.py index b6a19b67aaf..ef7ad5b8d49 100644 --- a/apiserver/plane/db/models/__init__.py +++ b/apiserver/plane/db/models/__init__.py @@ -38,3 +38,5 @@ from .view import View from .module import Module, ModuleMember, ModuleIssue, ModuleLink + +from .api_token import APIToken \ No newline at end of file diff --git a/apiserver/plane/db/models/api_token.py b/apiserver/plane/db/models/api_token.py new file mode 100644 index 00000000000..32ba013bc60 --- /dev/null +++ b/apiserver/plane/db/models/api_token.py @@ -0,0 +1,39 @@ +# Python imports +from uuid import uuid4 + +# Django imports +from django.db import models +from django.conf import settings + +from .base import BaseModel + + +def generate_label_token(): + return uuid4().hex + + +def generate_token(): + return uuid4().hex + uuid4().hex + + +class APIToken(BaseModel): + + token = models.CharField(max_length=255, unique=True, default=generate_token) + label = models.CharField(max_length=255, default=generate_label_token) + user = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + related_name="bot_tokens", + ) + user_type = models.PositiveSmallIntegerField( + choices=((0, "Human"), (1, "Bot")), default=0 + ) + + class Meta: + verbose_name = "API Token" + verbose_name_plural = "API Tokems" + db_table = "api_tokens" + ordering = ("-created_at",) + + def __str__(self): + return str(self.user.name) diff --git a/apiserver/plane/db/models/user.py b/apiserver/plane/db/models/user.py index 1621b19ea0e..896681808a2 100644 --- a/apiserver/plane/db/models/user.py +++ b/apiserver/plane/db/models/user.py @@ -68,6 +68,7 @@ class User(AbstractBaseUser, PermissionsMixin): last_workspace_id = models.UUIDField(null=True) my_issues_prop = models.JSONField(null=True) role = models.CharField(max_length=300, null=True, blank=True) + is_bot = models.BooleanField(default=False) USERNAME_FIELD = "email" @@ -101,7 +102,7 @@ def save(self, *args, **kwargs): @receiver(post_save, sender=User) def send_welcome_email(sender, instance, created, **kwargs): try: - if created: + if created and not instance.is_bot: first_name = instance.first_name.capitalize() to_email = instance.email from_email_string = f"Team Plane " From b75c180ce8b5659cf43975bc7c2fa95e9ef3cbc0 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 21 Jan 2023 12:27:45 +0530 Subject: [PATCH 2/2] feat: add list and delete endpoints for tokens --- apiserver/plane/api/urls.py | 1 + apiserver/plane/api/views/api_token.py | 33 ++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index 61afec877f1..10d920a9457 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -678,5 +678,6 @@ ## End Modules # API Tokens path("api-tokens/", ApiTokenEndpoint.as_view(), name="api-token"), + path("api-tokens//", ApiTokenEndpoint.as_view(), name="api-token"), ## End API Tokens ] diff --git a/apiserver/plane/api/views/api_token.py b/apiserver/plane/api/views/api_token.py index de68e7f0e0e..4ed3d9de028 100644 --- a/apiserver/plane/api/views/api_token.py +++ b/apiserver/plane/api/views/api_token.py @@ -1,16 +1,14 @@ # Python import from uuid import uuid4 -from datetime import timedelta # Third party from rest_framework.response import Response from rest_framework import status from sentry_sdk import capture_exception -from rest_framework_simplejwt.tokens import RefreshToken # Module import from .base import BaseAPIView -from plane.db.models import User, APIToken +from plane.db.models import APIToken from plane.api.serializers import APITokenSerializer @@ -29,7 +27,34 @@ def post(self, request): return Response(serializer.data, status=status.HTTP_201_CREATED) except Exception as e: - print(e) + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + def get(self, request): + try: + api_tokens = APIToken.objects.filter(user=request.user) + serializer = APITokenSerializer(api_tokens, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + def delete(self, request, pk): + try: + api_token = APIToken.objects.get(pk=pk) + api_token.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + except APIToken.DoesNotExist: + return Response( + {"error": "Token does not exists"}, status=status.HTTP_400_BAD_REQUEST + ) + except Exception as e: capture_exception(e) return Response( {"error": "Something went wrong please try again later"},