forked from github/plane
dev: instance refactor (#3015)
* dev: remove license engine communication * dev: remove license engine base url * dev: update instance configuration function * chore: removed the print statement * chore: changed config variables * chore: cleanup * chore: added SKIP_ENV_VAR * chore: changed the EMAIL_FROM * dev: patch endpoint for workspace * dev: custom port for takeoff script * chore: changed my sequence * fix: update operaton for member invitations in workspace * clean-up: remove logs --------- Co-authored-by: NarayanBavisetti <narayan3119@gmail.com> Co-authored-by: gurusainath <gurusainath007@gmail.com>
This commit is contained in:
parent
bacddcb348
commit
882c43dcc6
@ -27,4 +27,4 @@ python manage.py configure_instance
|
|||||||
# Create the default bucket
|
# Create the default bucket
|
||||||
python manage.py create_bucket
|
python manage.py create_bucket
|
||||||
|
|
||||||
exec gunicorn -w $GUNICORN_WORKERS -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile -
|
exec gunicorn -w $GUNICORN_WORKERS -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:${PORT:-8000} --max-requests 1200 --max-requests-jitter 1000 --access-logfile -
|
||||||
|
@ -95,6 +95,16 @@ class WorkSpaceMemberInviteSerializer(BaseSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = WorkspaceMemberInvite
|
model = WorkspaceMemberInvite
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
read_only_fields = [
|
||||||
|
"id",
|
||||||
|
"email",
|
||||||
|
"token",
|
||||||
|
"workspace",
|
||||||
|
"message",
|
||||||
|
"responded_at",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class TeamSerializer(BaseSerializer):
|
class TeamSerializer(BaseSerializer):
|
||||||
|
@ -65,6 +65,7 @@ urlpatterns = [
|
|||||||
{
|
{
|
||||||
"delete": "destroy",
|
"delete": "destroy",
|
||||||
"get": "retrieve",
|
"get": "retrieve",
|
||||||
|
"patch": "partial_update",
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
name="workspace-invitations",
|
name="workspace-invitations",
|
||||||
|
@ -34,7 +34,7 @@ from plane.app.serializers import (
|
|||||||
from plane.db.models import User, WorkspaceMemberInvite
|
from plane.db.models import User, WorkspaceMemberInvite
|
||||||
from plane.license.utils.instance_value import get_configuration_value
|
from plane.license.utils.instance_value import get_configuration_value
|
||||||
from plane.bgtasks.forgot_password_task import forgot_password
|
from plane.bgtasks.forgot_password_task import forgot_password
|
||||||
from plane.license.models import Instance, InstanceConfiguration
|
from plane.license.models import Instance
|
||||||
from plane.settings.redis import redis_instance
|
from plane.settings.redis import redis_instance
|
||||||
from plane.bgtasks.magic_link_code_task import magic_link
|
from plane.bgtasks.magic_link_code_task import magic_link
|
||||||
from plane.bgtasks.event_tracking_task import auth_events
|
from plane.bgtasks.event_tracking_task import auth_events
|
||||||
@ -321,8 +321,19 @@ class EmailCheckEndpoint(BaseAPIView):
|
|||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get the configurations
|
# Get configuration values
|
||||||
instance_configuration = InstanceConfiguration.objects.values("key", "value")
|
ENABLE_SIGNUP, ENABLE_MAGIC_LINK_LOGIN = get_configuration_value(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "ENABLE_SIGNUP",
|
||||||
|
"default": os.environ.get("ENABLE_SIGNUP"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "ENABLE_MAGIC_LINK_LOGIN",
|
||||||
|
"default": os.environ.get("ENABLE_MAGIC_LINK_LOGIN"),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
email = request.data.get("email", False)
|
email = request.data.get("email", False)
|
||||||
|
|
||||||
@ -347,12 +358,7 @@ class EmailCheckEndpoint(BaseAPIView):
|
|||||||
if user is None:
|
if user is None:
|
||||||
# Create the user
|
# Create the user
|
||||||
if (
|
if (
|
||||||
get_configuration_value(
|
ENABLE_SIGNUP == "0"
|
||||||
instance_configuration,
|
|
||||||
"ENABLE_SIGNUP",
|
|
||||||
os.environ.get("ENABLE_SIGNUP", "0"),
|
|
||||||
)
|
|
||||||
== "0"
|
|
||||||
and not WorkspaceMemberInvite.objects.filter(
|
and not WorkspaceMemberInvite.objects.filter(
|
||||||
email=email,
|
email=email,
|
||||||
).exists()
|
).exists()
|
||||||
@ -372,13 +378,8 @@ class EmailCheckEndpoint(BaseAPIView):
|
|||||||
is_password_autoset=True,
|
is_password_autoset=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if not bool(
|
if not bool(
|
||||||
get_configuration_value(
|
ENABLE_MAGIC_LINK_LOGIN,
|
||||||
instance_configuration,
|
|
||||||
"ENABLE_MAGIC_LINK_LOGIN",
|
|
||||||
os.environ.get("ENABLE_MAGIC_LINK_LOGIN"),
|
|
||||||
),
|
|
||||||
):
|
):
|
||||||
return Response(
|
return Response(
|
||||||
{"error": "Magic link sign in is disabled."},
|
{"error": "Magic link sign in is disabled."},
|
||||||
@ -386,16 +387,15 @@ class EmailCheckEndpoint(BaseAPIView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Send event
|
# Send event
|
||||||
if settings.POSTHOG_API_KEY and settings.POSTHOG_HOST:
|
auth_events.delay(
|
||||||
auth_events.delay(
|
user=user.id,
|
||||||
user=user.id,
|
email=email,
|
||||||
email=email,
|
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
ip=request.META.get("REMOTE_ADDR"),
|
||||||
ip=request.META.get("REMOTE_ADDR"),
|
event_name="SIGN_IN",
|
||||||
event_name="SIGN_IN",
|
medium="MAGIC_LINK",
|
||||||
medium="MAGIC_LINK",
|
first_time=True,
|
||||||
first_time=True,
|
)
|
||||||
)
|
|
||||||
key, token, current_attempt = generate_magic_token(email=email)
|
key, token, current_attempt = generate_magic_token(email=email)
|
||||||
if not current_attempt:
|
if not current_attempt:
|
||||||
return Response(
|
return Response(
|
||||||
@ -413,28 +413,21 @@ class EmailCheckEndpoint(BaseAPIView):
|
|||||||
else:
|
else:
|
||||||
if user.is_password_autoset:
|
if user.is_password_autoset:
|
||||||
## Generate a random token
|
## Generate a random token
|
||||||
if not bool(
|
if not bool(ENABLE_MAGIC_LINK_LOGIN):
|
||||||
get_configuration_value(
|
|
||||||
instance_configuration,
|
|
||||||
"ENABLE_MAGIC_LINK_LOGIN",
|
|
||||||
os.environ.get("ENABLE_MAGIC_LINK_LOGIN"),
|
|
||||||
),
|
|
||||||
):
|
|
||||||
return Response(
|
return Response(
|
||||||
{"error": "Magic link sign in is disabled."},
|
{"error": "Magic link sign in is disabled."},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
if settings.POSTHOG_API_KEY and settings.POSTHOG_HOST:
|
auth_events.delay(
|
||||||
auth_events.delay(
|
user=user.id,
|
||||||
user=user.id,
|
email=email,
|
||||||
email=email,
|
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
ip=request.META.get("REMOTE_ADDR"),
|
||||||
ip=request.META.get("REMOTE_ADDR"),
|
event_name="SIGN_IN",
|
||||||
event_name="SIGN_IN",
|
medium="MAGIC_LINK",
|
||||||
medium="MAGIC_LINK",
|
first_time=False,
|
||||||
first_time=False,
|
)
|
||||||
)
|
|
||||||
|
|
||||||
# Generate magic token
|
# Generate magic token
|
||||||
key, token, current_attempt = generate_magic_token(email=email)
|
key, token, current_attempt = generate_magic_token(email=email)
|
||||||
@ -454,16 +447,15 @@ class EmailCheckEndpoint(BaseAPIView):
|
|||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if settings.POSTHOG_API_KEY and settings.POSTHOG_HOST:
|
auth_events.delay(
|
||||||
auth_events.delay(
|
user=user.id,
|
||||||
user=user.id,
|
email=email,
|
||||||
email=email,
|
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
ip=request.META.get("REMOTE_ADDR"),
|
||||||
ip=request.META.get("REMOTE_ADDR"),
|
event_name="SIGN_IN",
|
||||||
event_name="SIGN_IN",
|
medium="EMAIL",
|
||||||
medium="EMAIL",
|
first_time=False,
|
||||||
first_time=False,
|
)
|
||||||
)
|
|
||||||
|
|
||||||
# User should enter password to login
|
# User should enter password to login
|
||||||
return Response(
|
return Response(
|
||||||
|
@ -27,7 +27,7 @@ from plane.db.models import (
|
|||||||
ProjectMember,
|
ProjectMember,
|
||||||
)
|
)
|
||||||
from plane.settings.redis import redis_instance
|
from plane.settings.redis import redis_instance
|
||||||
from plane.license.models import InstanceConfiguration, Instance
|
from plane.license.models import Instance
|
||||||
from plane.license.utils.instance_value import get_configuration_value
|
from plane.license.utils.instance_value import get_configuration_value
|
||||||
from plane.bgtasks.event_tracking_task import auth_events
|
from plane.bgtasks.event_tracking_task import auth_events
|
||||||
|
|
||||||
@ -52,8 +52,6 @@ class SignUpEndpoint(BaseAPIView):
|
|||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
instance_configuration = InstanceConfiguration.objects.values("key", "value")
|
|
||||||
|
|
||||||
email = request.data.get("email", False)
|
email = request.data.get("email", False)
|
||||||
password = request.data.get("password", False)
|
password = request.data.get("password", False)
|
||||||
## Raise exception if any of the above are missing
|
## Raise exception if any of the above are missing
|
||||||
@ -73,14 +71,20 @@ class SignUpEndpoint(BaseAPIView):
|
|||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# get configuration values
|
||||||
|
# Get configuration values
|
||||||
|
ENABLE_SIGNUP, = get_configuration_value(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "ENABLE_SIGNUP",
|
||||||
|
"default": os.environ.get("ENABLE_SIGNUP"),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
# If the sign up is not enabled and the user does not have invite disallow him from creating the account
|
# If the sign up is not enabled and the user does not have invite disallow him from creating the account
|
||||||
if (
|
if (
|
||||||
get_configuration_value(
|
ENABLE_SIGNUP == "0"
|
||||||
instance_configuration,
|
|
||||||
"ENABLE_SIGNUP",
|
|
||||||
os.environ.get("ENABLE_SIGNUP", "0"),
|
|
||||||
)
|
|
||||||
== "0"
|
|
||||||
and not WorkspaceMemberInvite.objects.filter(
|
and not WorkspaceMemberInvite.objects.filter(
|
||||||
email=email,
|
email=email,
|
||||||
).exists()
|
).exists()
|
||||||
@ -169,16 +173,17 @@ class SignInEndpoint(BaseAPIView):
|
|||||||
|
|
||||||
# Create the user
|
# Create the user
|
||||||
else:
|
else:
|
||||||
# Get the configurations
|
ENABLE_SIGNUP, = get_configuration_value(
|
||||||
instance_configuration = InstanceConfiguration.objects.values("key", "value")
|
[
|
||||||
|
{
|
||||||
|
"key": "ENABLE_SIGNUP",
|
||||||
|
"default": os.environ.get("ENABLE_SIGNUP"),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
# Create the user
|
# Create the user
|
||||||
if (
|
if (
|
||||||
get_configuration_value(
|
ENABLE_SIGNUP == "0"
|
||||||
instance_configuration,
|
|
||||||
"ENABLE_SIGNUP",
|
|
||||||
os.environ.get("ENABLE_SIGNUP", "0"),
|
|
||||||
)
|
|
||||||
== "0"
|
|
||||||
and not WorkspaceMemberInvite.objects.filter(
|
and not WorkspaceMemberInvite.objects.filter(
|
||||||
email=email,
|
email=email,
|
||||||
).exists()
|
).exists()
|
||||||
@ -264,16 +269,15 @@ class SignInEndpoint(BaseAPIView):
|
|||||||
workspace_member_invites.delete()
|
workspace_member_invites.delete()
|
||||||
project_member_invites.delete()
|
project_member_invites.delete()
|
||||||
# Send event
|
# Send event
|
||||||
if settings.POSTHOG_API_KEY and settings.POSTHOG_HOST:
|
auth_events.delay(
|
||||||
auth_events.delay(
|
user=user.id,
|
||||||
user=user.id,
|
email=email,
|
||||||
email=email,
|
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
ip=request.META.get("REMOTE_ADDR"),
|
||||||
ip=request.META.get("REMOTE_ADDR"),
|
event_name="SIGN_IN",
|
||||||
event_name="SIGN_IN",
|
medium="EMAIL",
|
||||||
medium="EMAIL",
|
first_time=False,
|
||||||
first_time=False,
|
)
|
||||||
)
|
|
||||||
|
|
||||||
access_token, refresh_token = get_tokens_for_user(user)
|
access_token, refresh_token = get_tokens_for_user(user)
|
||||||
data = {
|
data = {
|
||||||
@ -347,16 +351,15 @@ class MagicSignInEndpoint(BaseAPIView):
|
|||||||
status=status.HTTP_403_FORBIDDEN,
|
status=status.HTTP_403_FORBIDDEN,
|
||||||
)
|
)
|
||||||
# Send event
|
# Send event
|
||||||
if settings.POSTHOG_API_KEY and settings.POSTHOG_HOST:
|
auth_events.delay(
|
||||||
auth_events.delay(
|
user=user.id,
|
||||||
user=user.id,
|
email=email,
|
||||||
email=email,
|
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
ip=request.META.get("REMOTE_ADDR"),
|
||||||
ip=request.META.get("REMOTE_ADDR"),
|
event_name="SIGN_IN",
|
||||||
event_name="SIGN_IN",
|
medium="MAGIC_LINK",
|
||||||
medium="MAGIC_LINK",
|
first_time=False,
|
||||||
first_time=False,
|
)
|
||||||
)
|
|
||||||
|
|
||||||
user.is_active = True
|
user.is_active = True
|
||||||
user.is_email_verified = True
|
user.is_email_verified = True
|
||||||
|
@ -11,7 +11,6 @@ from rest_framework.response import Response
|
|||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from .base import BaseAPIView
|
from .base import BaseAPIView
|
||||||
from plane.license.models import InstanceConfiguration
|
|
||||||
from plane.license.utils.instance_value import get_configuration_value
|
from plane.license.utils.instance_value import get_configuration_value
|
||||||
|
|
||||||
|
|
||||||
@ -21,89 +20,101 @@ class ConfigurationEndpoint(BaseAPIView):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
instance_configuration = InstanceConfiguration.objects.values("key", "value")
|
|
||||||
|
# Get all the configuration
|
||||||
|
(
|
||||||
|
GOOGLE_CLIENT_ID,
|
||||||
|
GITHUB_CLIENT_ID,
|
||||||
|
GITHUB_APP_NAME,
|
||||||
|
EMAIL_HOST_USER,
|
||||||
|
EMAIL_HOST_PASSWORD,
|
||||||
|
ENABLE_MAGIC_LINK_LOGIN,
|
||||||
|
ENABLE_EMAIL_PASSWORD,
|
||||||
|
SLACK_CLIENT_ID,
|
||||||
|
POSTHOG_API_KEY,
|
||||||
|
POSTHOG_HOST,
|
||||||
|
UNSPLASH_ACCESS_KEY,
|
||||||
|
OPENAI_API_KEY,
|
||||||
|
) = get_configuration_value(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "GOOGLE_CLIENT_ID",
|
||||||
|
"default": os.environ.get("GOOGLE_CLIENT_ID", None),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "GITHUB_CLIENT_ID",
|
||||||
|
"default": os.environ.get("GITHUB_CLIENT_ID", None),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "GITHUB_APP_NAME",
|
||||||
|
"default": os.environ.get("GITHUB_APP_NAME", None),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "EMAIL_HOST_USER",
|
||||||
|
"default": os.environ.get("EMAIL_HOST_USER", None),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "EMAIL_HOST_PASSWORD",
|
||||||
|
"default": os.environ.get("EMAIL_HOST_PASSWORD", None),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "ENABLE_MAGIC_LINK_LOGIN",
|
||||||
|
"default": os.environ.get("ENABLE_MAGIC_LINK_LOGIN", "1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "ENABLE_EMAIL_PASSWORD",
|
||||||
|
"default": os.environ.get("ENABLE_EMAIL_PASSWORD", "1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "SLACK_CLIENT_ID",
|
||||||
|
"default": os.environ.get("SLACK_CLIENT_ID", "1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "POSTHOG_API_KEY",
|
||||||
|
"default": os.environ.get("POSTHOG_API_KEY", "1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "POSTHOG_HOST",
|
||||||
|
"default": os.environ.get("POSTHOG_HOST", "1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "UNSPLASH_ACCESS_KEY",
|
||||||
|
"default": os.environ.get("UNSPLASH_ACCESS_KEY", "1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "OPENAI_API_KEY",
|
||||||
|
"default": os.environ.get("OPENAI_API_KEY", "1"),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
data = {}
|
data = {}
|
||||||
# Authentication
|
# Authentication
|
||||||
data["google_client_id"] = get_configuration_value(
|
data["google_client_id"] = GOOGLE_CLIENT_ID
|
||||||
instance_configuration,
|
data["github_client_id"] = GITHUB_CLIENT_ID
|
||||||
"GOOGLE_CLIENT_ID",
|
data["github_app_name"] = GITHUB_APP_NAME
|
||||||
os.environ.get("GOOGLE_CLIENT_ID", None),
|
|
||||||
)
|
|
||||||
data["github_client_id"] = get_configuration_value(
|
|
||||||
instance_configuration,
|
|
||||||
"GITHUB_CLIENT_ID",
|
|
||||||
os.environ.get("GITHUB_CLIENT_ID", None),
|
|
||||||
)
|
|
||||||
data["github_app_name"] = get_configuration_value(
|
|
||||||
instance_configuration,
|
|
||||||
"GITHUB_APP_NAME",
|
|
||||||
os.environ.get("GITHUB_APP_NAME", None),
|
|
||||||
)
|
|
||||||
data["magic_login"] = (
|
data["magic_login"] = (
|
||||||
bool(
|
bool(EMAIL_HOST_USER) and bool(EMAIL_HOST_PASSWORD)
|
||||||
get_configuration_value(
|
) and ENABLE_MAGIC_LINK_LOGIN == "1"
|
||||||
instance_configuration,
|
|
||||||
"EMAIL_HOST_USER",
|
|
||||||
os.environ.get("EMAIL_HOST_USER", None),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
and bool(
|
|
||||||
get_configuration_value(
|
|
||||||
instance_configuration,
|
|
||||||
"EMAIL_HOST_PASSWORD",
|
|
||||||
os.environ.get("EMAIL_HOST_PASSWORD", None),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) and get_configuration_value(
|
|
||||||
instance_configuration, "ENABLE_MAGIC_LINK_LOGIN", "1"
|
|
||||||
) == "1"
|
|
||||||
|
|
||||||
data["email_password_login"] = (
|
data["email_password_login"] = ENABLE_EMAIL_PASSWORD == "1"
|
||||||
get_configuration_value(
|
|
||||||
instance_configuration, "ENABLE_EMAIL_PASSWORD", "1"
|
|
||||||
)
|
|
||||||
== "1"
|
|
||||||
)
|
|
||||||
# Slack client
|
# Slack client
|
||||||
data["slack_client_id"] = get_configuration_value(
|
data["slack_client_id"] = SLACK_CLIENT_ID
|
||||||
instance_configuration,
|
|
||||||
"SLACK_CLIENT_ID",
|
|
||||||
os.environ.get("SLACK_CLIENT_ID", None),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Posthog
|
# Posthog
|
||||||
data["posthog_api_key"] = get_configuration_value(
|
data["posthog_api_key"] = POSTHOG_API_KEY
|
||||||
instance_configuration,
|
data["posthog_host"] = POSTHOG_HOST
|
||||||
"POSTHOG_API_KEY",
|
|
||||||
os.environ.get("POSTHOG_API_KEY", None),
|
|
||||||
)
|
|
||||||
data["posthog_host"] = get_configuration_value(
|
|
||||||
instance_configuration,
|
|
||||||
"POSTHOG_HOST",
|
|
||||||
os.environ.get("POSTHOG_HOST", None),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Unsplash
|
# Unsplash
|
||||||
data["has_unsplash_configured"] = bool(
|
data["has_unsplash_configured"] = UNSPLASH_ACCESS_KEY
|
||||||
get_configuration_value(
|
|
||||||
instance_configuration,
|
|
||||||
"UNSPLASH_ACCESS_KEY",
|
|
||||||
os.environ.get("UNSPLASH_ACCESS_KEY", None),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Open AI settings
|
# Open AI settings
|
||||||
data["has_openai_configured"] = bool(
|
data["has_openai_configured"] = bool(OPENAI_API_KEY)
|
||||||
get_configuration_value(
|
|
||||||
instance_configuration,
|
|
||||||
"OPENAI_API_KEY",
|
|
||||||
os.environ.get("OPENAI_API_KEY", None),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
# File size settings
|
||||||
data["file_size_limit"] = float(os.environ.get("FILE_SIZE_LIMIT", 5242880))
|
data["file_size_limit"] = float(os.environ.get("FILE_SIZE_LIMIT", 5242880))
|
||||||
|
|
||||||
|
# is self managed
|
||||||
data["is_self_managed"] = bool(int(os.environ.get("IS_SELF_MANAGED", "1")))
|
data["is_self_managed"] = bool(int(os.environ.get("IS_SELF_MANAGED", "1")))
|
||||||
|
|
||||||
return Response(data, status=status.HTTP_200_OK)
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
import requests
|
import requests
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Third party imports
|
# Third party imports
|
||||||
from openai import OpenAI
|
from openai import OpenAI
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
@ -15,23 +16,31 @@ from plane.app.permissions import ProjectEntityPermission
|
|||||||
from plane.db.models import Workspace, Project
|
from plane.db.models import Workspace, Project
|
||||||
from plane.app.serializers import ProjectLiteSerializer, WorkspaceLiteSerializer
|
from plane.app.serializers import ProjectLiteSerializer, WorkspaceLiteSerializer
|
||||||
from plane.utils.integrations.github import get_release_notes
|
from plane.utils.integrations.github import get_release_notes
|
||||||
from plane.license.models import InstanceConfiguration
|
|
||||||
from plane.license.utils.instance_value import get_configuration_value
|
from plane.license.utils.instance_value import get_configuration_value
|
||||||
|
|
||||||
|
|
||||||
class GPTIntegrationEndpoint(BaseAPIView):
|
class GPTIntegrationEndpoint(BaseAPIView):
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
ProjectEntityPermission,
|
ProjectEntityPermission,
|
||||||
]
|
]
|
||||||
|
|
||||||
def post(self, request, slug, project_id):
|
def post(self, request, slug, project_id):
|
||||||
|
OPENAI_API_KEY, GPT_ENGINE = get_configuration_value(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "OPENAI_API_KEY",
|
||||||
|
"default": os.environ.get("OPENAI_API_KEY", None),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "GPT_ENGINE",
|
||||||
|
"default": os.environ.get("GPT_ENGINE", "gpt-3.5-turbo"),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
# Get the configuration value
|
# Get the configuration value
|
||||||
instance_configuration = InstanceConfiguration.objects.values("key", "value")
|
|
||||||
api_key = get_configuration_value(instance_configuration, "OPENAI_API_KEY", os.environ.get("OPENAI_API_KEY"))
|
|
||||||
gpt_engine = get_configuration_value(instance_configuration, "GPT_ENGINE", os.environ.get("GPT_ENGINE", "gpt-3.5-turbo"))
|
|
||||||
|
|
||||||
# Check the keys
|
# Check the keys
|
||||||
if not api_key or not gpt_engine:
|
if not OPENAI_API_KEY or not GPT_ENGINE:
|
||||||
return Response(
|
return Response(
|
||||||
{"error": "OpenAI API key and engine is required"},
|
{"error": "OpenAI API key and engine is required"},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
@ -48,11 +57,11 @@ class GPTIntegrationEndpoint(BaseAPIView):
|
|||||||
final_text = task + "\n" + prompt
|
final_text = task + "\n" + prompt
|
||||||
|
|
||||||
client = OpenAI(
|
client = OpenAI(
|
||||||
api_key=api_key,
|
api_key=OPENAI_API_KEY,
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.chat.completions.create(
|
response = client.chat.completions.create(
|
||||||
model=gpt_engine,
|
model=GPT_ENGINE,
|
||||||
messages=[{"role": "user", "content": final_text}],
|
messages=[{"role": "user", "content": final_text}],
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -79,13 +88,17 @@ class ReleaseNotesEndpoint(BaseAPIView):
|
|||||||
|
|
||||||
|
|
||||||
class UnsplashEndpoint(BaseAPIView):
|
class UnsplashEndpoint(BaseAPIView):
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
instance_configuration = InstanceConfiguration.objects.values("key", "value")
|
UNSPLASH_ACCESS_KEY, = get_configuration_value(
|
||||||
unsplash_access_key = get_configuration_value(instance_configuration, "UNSPLASH_ACCESS_KEY", os.environ.get("UNSPLASH_ACCESS_KEY"))
|
[
|
||||||
|
{
|
||||||
|
"key": "UNSPLASH_ACCESS_KEY",
|
||||||
|
"default": os.environ.get("UNSPLASH_ACCESS_KEY"),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
# Check unsplash access key
|
# Check unsplash access key
|
||||||
if not unsplash_access_key:
|
if not UNSPLASH_ACCESS_KEY:
|
||||||
return Response([], status=status.HTTP_200_OK)
|
return Response([], status=status.HTTP_200_OK)
|
||||||
|
|
||||||
# Query parameters
|
# Query parameters
|
||||||
@ -94,9 +107,9 @@ class UnsplashEndpoint(BaseAPIView):
|
|||||||
per_page = request.GET.get("per_page", 20)
|
per_page = request.GET.get("per_page", 20)
|
||||||
|
|
||||||
url = (
|
url = (
|
||||||
f"https://api.unsplash.com/search/photos/?client_id={unsplash_access_key}&query={query}&page=${page}&per_page={per_page}"
|
f"https://api.unsplash.com/search/photos/?client_id={UNSPLASH_ACCESS_KEY}&query={query}&page=${page}&per_page={per_page}"
|
||||||
if query
|
if query
|
||||||
else f"https://api.unsplash.com/photos/?client_id={unsplash_access_key}&page={page}&per_page={per_page}"
|
else f"https://api.unsplash.com/photos/?client_id={UNSPLASH_ACCESS_KEY}&page={page}&per_page={per_page}"
|
||||||
)
|
)
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
|
@ -30,7 +30,7 @@ from plane.db.models import (
|
|||||||
)
|
)
|
||||||
from plane.bgtasks.event_tracking_task import auth_events
|
from plane.bgtasks.event_tracking_task import auth_events
|
||||||
from .base import BaseAPIView
|
from .base import BaseAPIView
|
||||||
from plane.license.models import InstanceConfiguration, Instance
|
from plane.license.models import Instance
|
||||||
from plane.license.utils.instance_value import get_configuration_value
|
from plane.license.utils.instance_value import get_configuration_value
|
||||||
|
|
||||||
|
|
||||||
@ -147,18 +147,20 @@ class OauthEndpoint(BaseAPIView):
|
|||||||
id_token = request.data.get("credential", False)
|
id_token = request.data.get("credential", False)
|
||||||
client_id = request.data.get("clientId", False)
|
client_id = request.data.get("clientId", False)
|
||||||
|
|
||||||
instance_configuration = InstanceConfiguration.objects.values(
|
GOOGLE_CLIENT_ID, GITHUB_CLIENT_ID = get_configuration_value(
|
||||||
"key", "value"
|
[
|
||||||
|
{
|
||||||
|
"key": "GOOGLE_CLIENT_ID",
|
||||||
|
"default": os.environ.get("GOOGLE_CLIENT_ID"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "GITHUB_CLIENT_ID",
|
||||||
|
"default": os.environ.get("GITHUB_CLIENT_ID"),
|
||||||
|
},
|
||||||
|
]
|
||||||
)
|
)
|
||||||
if not get_configuration_value(
|
|
||||||
instance_configuration,
|
if not GOOGLE_CLIENT_ID or not GITHUB_CLIENT_ID:
|
||||||
"GOOGLE_CLIENT_ID",
|
|
||||||
os.environ.get("GOOGLE_CLIENT_ID"),
|
|
||||||
) or not get_configuration_value(
|
|
||||||
instance_configuration,
|
|
||||||
"GITHUB_CLIENT_ID",
|
|
||||||
os.environ.get("GITHUB_CLIENT_ID"),
|
|
||||||
):
|
|
||||||
return Response(
|
return Response(
|
||||||
{"error": "Github or Google login is not configured"},
|
{"error": "Github or Google login is not configured"},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
@ -278,16 +280,15 @@ class OauthEndpoint(BaseAPIView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Send event
|
# Send event
|
||||||
if settings.POSTHOG_API_KEY and settings.POSTHOG_HOST:
|
auth_events.delay(
|
||||||
auth_events.delay(
|
user=user.id,
|
||||||
user=user.id,
|
email=email,
|
||||||
email=email,
|
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
ip=request.META.get("REMOTE_ADDR"),
|
||||||
ip=request.META.get("REMOTE_ADDR"),
|
event_name="SIGN_IN",
|
||||||
event_name="SIGN_IN",
|
medium=medium.upper(),
|
||||||
medium=medium.upper(),
|
first_time=False,
|
||||||
first_time=False,
|
)
|
||||||
)
|
|
||||||
|
|
||||||
access_token, refresh_token = get_tokens_for_user(user)
|
access_token, refresh_token = get_tokens_for_user(user)
|
||||||
|
|
||||||
@ -298,17 +299,16 @@ class OauthEndpoint(BaseAPIView):
|
|||||||
return Response(data, status=status.HTTP_200_OK)
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
## Signup Case
|
ENABLE_SIGNUP, = get_configuration_value(
|
||||||
instance_configuration = InstanceConfiguration.objects.values(
|
[
|
||||||
"key", "value"
|
{
|
||||||
|
"key": "ENABLE_SIGNUP",
|
||||||
|
"default": os.environ.get("ENABLE_SIGNUP", "0"),
|
||||||
|
}
|
||||||
|
]
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
get_configuration_value(
|
ENABLE_SIGNUP == "0"
|
||||||
instance_configuration,
|
|
||||||
"ENABLE_SIGNUP",
|
|
||||||
os.environ.get("ENABLE_SIGNUP", "0"),
|
|
||||||
)
|
|
||||||
== "0"
|
|
||||||
and not WorkspaceMemberInvite.objects.filter(
|
and not WorkspaceMemberInvite.objects.filter(
|
||||||
email=email,
|
email=email,
|
||||||
).exists()
|
).exists()
|
||||||
@ -411,16 +411,15 @@ class OauthEndpoint(BaseAPIView):
|
|||||||
project_member_invites.delete()
|
project_member_invites.delete()
|
||||||
|
|
||||||
# Send event
|
# Send event
|
||||||
if settings.POSTHOG_API_KEY and settings.POSTHOG_HOST:
|
auth_events.delay(
|
||||||
auth_events.delay(
|
user=user.id,
|
||||||
user=user.id,
|
email=email,
|
||||||
email=email,
|
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
ip=request.META.get("REMOTE_ADDR"),
|
||||||
ip=request.META.get("REMOTE_ADDR"),
|
event_name="SIGN_IN",
|
||||||
event_name="SIGN_IN",
|
medium=medium.upper(),
|
||||||
medium=medium.upper(),
|
first_time=True,
|
||||||
first_time=True,
|
)
|
||||||
)
|
|
||||||
|
|
||||||
SocialLoginConnection.objects.update_or_create(
|
SocialLoginConnection.objects.update_or_create(
|
||||||
medium=medium,
|
medium=medium,
|
||||||
|
@ -408,15 +408,14 @@ class WorkspaceJoinEndpoint(BaseAPIView):
|
|||||||
workspace_invite.delete()
|
workspace_invite.delete()
|
||||||
|
|
||||||
# Send event
|
# Send event
|
||||||
if settings.POSTHOG_API_KEY and settings.POSTHOG_HOST:
|
workspace_invite_event.delay(
|
||||||
workspace_invite_event.delay(
|
user=user.id if user is not None else None,
|
||||||
user=user.id if user is not None else None,
|
email=email,
|
||||||
email=email,
|
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
ip=request.META.get("REMOTE_ADDR"),
|
||||||
ip=request.META.get("REMOTE_ADDR"),
|
event_name="MEMBER_ACCEPTED",
|
||||||
event_name="MEMBER_ACCEPTED",
|
accepted_from="EMAIL",
|
||||||
accepted_from="EMAIL",
|
)
|
||||||
)
|
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
{"message": "Workspace Invitation Accepted"},
|
{"message": "Workspace Invitation Accepted"},
|
||||||
|
@ -18,7 +18,6 @@ from sentry_sdk import capture_exception
|
|||||||
from plane.db.models import Issue
|
from plane.db.models import Issue
|
||||||
from plane.utils.analytics_plot import build_graph_plot
|
from plane.utils.analytics_plot import build_graph_plot
|
||||||
from plane.utils.issue_filters import issue_filters
|
from plane.utils.issue_filters import issue_filters
|
||||||
from plane.license.models import InstanceConfiguration, Instance
|
|
||||||
from plane.license.utils.instance_value import get_email_configuration
|
from plane.license.utils.instance_value import get_email_configuration
|
||||||
|
|
||||||
row_mapping = {
|
row_mapping = {
|
||||||
@ -52,11 +51,6 @@ def send_export_email(email, slug, csv_buffer, rows):
|
|||||||
|
|
||||||
csv_buffer.seek(0)
|
csv_buffer.seek(0)
|
||||||
|
|
||||||
# Configure email connection from the database
|
|
||||||
instance_configuration = InstanceConfiguration.objects.filter(
|
|
||||||
key__startswith="EMAIL_"
|
|
||||||
).values("key", "value")
|
|
||||||
|
|
||||||
(
|
(
|
||||||
EMAIL_HOST,
|
EMAIL_HOST,
|
||||||
EMAIL_HOST_USER,
|
EMAIL_HOST_USER,
|
||||||
@ -64,7 +58,7 @@ def send_export_email(email, slug, csv_buffer, rows):
|
|||||||
EMAIL_PORT,
|
EMAIL_PORT,
|
||||||
EMAIL_USE_TLS,
|
EMAIL_USE_TLS,
|
||||||
EMAIL_FROM,
|
EMAIL_FROM,
|
||||||
) = get_email_configuration(instance_configuration=instance_configuration)
|
) = get_email_configuration()
|
||||||
|
|
||||||
connection = get_connection(
|
connection = get_connection(
|
||||||
host=EMAIL_HOST,
|
host=EMAIL_HOST,
|
||||||
|
@ -1,50 +1,78 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
import os
|
||||||
|
|
||||||
from posthog import Posthog
|
# third party imports
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
#third party imports
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from sentry_sdk import capture_exception
|
from sentry_sdk import capture_exception
|
||||||
|
from posthog import Posthog
|
||||||
|
|
||||||
|
# module imports
|
||||||
|
from plane.license.utils.instance_value import get_configuration_value
|
||||||
|
|
||||||
|
|
||||||
|
def posthogConfiguration():
|
||||||
|
POSTHOG_API_KEY, POSTHOG_HOST = get_configuration_value(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "POSTHOG_API_KEY",
|
||||||
|
"default": os.environ.get("POSTHOG_API_KEY", None),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "POSTHOG_HOST",
|
||||||
|
"default": os.environ.get("POSTHOG_HOST", None),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if POSTHOG_API_KEY and POSTHOG_HOST:
|
||||||
|
return POSTHOG_API_KEY, POSTHOG_HOST
|
||||||
|
else:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def auth_events(user, email, user_agent, ip, event_name, medium, first_time):
|
def auth_events(user, email, user_agent, ip, event_name, medium, first_time):
|
||||||
try:
|
try:
|
||||||
posthog = Posthog(settings.POSTHOG_API_KEY, host=settings.POSTHOG_HOST)
|
POSTHOG_API_KEY, POSTHOG_HOST = posthogConfiguration()
|
||||||
posthog.capture(
|
|
||||||
email,
|
if POSTHOG_API_KEY and POSTHOG_HOST:
|
||||||
event=event_name,
|
posthog = Posthog(POSTHOG_API_KEY, host=POSTHOG_HOST)
|
||||||
properties={
|
posthog.capture(
|
||||||
"event_id": uuid.uuid4().hex,
|
email,
|
||||||
"user": {"email": email, "id": str(user)},
|
event=event_name,
|
||||||
"device_ctx": {
|
properties={
|
||||||
"ip": ip,
|
"event_id": uuid.uuid4().hex,
|
||||||
"user_agent": user_agent,
|
"user": {"email": email, "id": str(user)},
|
||||||
},
|
"device_ctx": {
|
||||||
"medium": medium,
|
"ip": ip,
|
||||||
"first_time": first_time
|
"user_agent": user_agent,
|
||||||
}
|
},
|
||||||
)
|
"medium": medium,
|
||||||
|
"first_time": first_time
|
||||||
|
}
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
capture_exception(e)
|
capture_exception(e)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def workspace_invite_event(user, email, user_agent, ip, event_name, accepted_from):
|
def workspace_invite_event(user, email, user_agent, ip, event_name, accepted_from):
|
||||||
try:
|
try:
|
||||||
posthog = Posthog(settings.POSTHOG_API_KEY, host=settings.POSTHOG_HOST)
|
POSTHOG_API_KEY, POSTHOG_HOST = posthogConfiguration()
|
||||||
posthog.capture(
|
|
||||||
email,
|
if POSTHOG_API_KEY and POSTHOG_HOST:
|
||||||
event=event_name,
|
posthog = Posthog(POSTHOG_API_KEY, host=POSTHOG_HOST)
|
||||||
properties={
|
posthog.capture(
|
||||||
"event_id": uuid.uuid4().hex,
|
email,
|
||||||
"user": {"email": email, "id": str(user)},
|
event=event_name,
|
||||||
"device_ctx": {
|
properties={
|
||||||
"ip": ip,
|
"event_id": uuid.uuid4().hex,
|
||||||
"user_agent": user_agent,
|
"user": {"email": email, "id": str(user)},
|
||||||
},
|
"device_ctx": {
|
||||||
"accepted_from": accepted_from
|
"ip": ip,
|
||||||
}
|
"user_agent": user_agent,
|
||||||
)
|
},
|
||||||
|
"accepted_from": accepted_from
|
||||||
|
}
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
capture_exception(e)
|
capture_exception(e)
|
@ -14,7 +14,6 @@ from celery import shared_task
|
|||||||
from sentry_sdk import capture_exception
|
from sentry_sdk import capture_exception
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.license.models import InstanceConfiguration, Instance
|
|
||||||
from plane.license.utils.instance_value import get_email_configuration
|
from plane.license.utils.instance_value import get_email_configuration
|
||||||
|
|
||||||
|
|
||||||
@ -26,10 +25,6 @@ def forgot_password(first_name, email, uidb64, token, current_site):
|
|||||||
)
|
)
|
||||||
abs_url = str(current_site) + relative_link
|
abs_url = str(current_site) + relative_link
|
||||||
|
|
||||||
instance_configuration = InstanceConfiguration.objects.filter(
|
|
||||||
key__startswith="EMAIL_"
|
|
||||||
).values("key", "value")
|
|
||||||
|
|
||||||
(
|
(
|
||||||
EMAIL_HOST,
|
EMAIL_HOST,
|
||||||
EMAIL_HOST_USER,
|
EMAIL_HOST_USER,
|
||||||
@ -37,7 +32,7 @@ def forgot_password(first_name, email, uidb64, token, current_site):
|
|||||||
EMAIL_PORT,
|
EMAIL_PORT,
|
||||||
EMAIL_USE_TLS,
|
EMAIL_USE_TLS,
|
||||||
EMAIL_FROM,
|
EMAIL_FROM,
|
||||||
) = get_email_configuration(instance_configuration=instance_configuration)
|
) = get_email_configuration()
|
||||||
|
|
||||||
subject = "A new password to your Plane account has been requested"
|
subject = "A new password to your Plane account has been requested"
|
||||||
|
|
||||||
@ -51,9 +46,6 @@ def forgot_password(first_name, email, uidb64, token, current_site):
|
|||||||
|
|
||||||
text_content = strip_tags(html_content)
|
text_content = strip_tags(html_content)
|
||||||
|
|
||||||
instance_configuration = InstanceConfiguration.objects.filter(
|
|
||||||
key__startswith="EMAIL_"
|
|
||||||
).values("key", "value")
|
|
||||||
connection = get_connection(
|
connection = get_connection(
|
||||||
host=EMAIL_HOST,
|
host=EMAIL_HOST,
|
||||||
port=int(EMAIL_PORT),
|
port=int(EMAIL_PORT),
|
||||||
|
@ -14,17 +14,12 @@ from celery import shared_task
|
|||||||
from sentry_sdk import capture_exception
|
from sentry_sdk import capture_exception
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.license.models import InstanceConfiguration, Instance
|
|
||||||
from plane.license.utils.instance_value import get_email_configuration
|
from plane.license.utils.instance_value import get_email_configuration
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def magic_link(email, key, token, current_site):
|
def magic_link(email, key, token, current_site):
|
||||||
try:
|
try:
|
||||||
instance_configuration = InstanceConfiguration.objects.filter(
|
|
||||||
key__startswith="EMAIL_"
|
|
||||||
).values("key", "value")
|
|
||||||
|
|
||||||
(
|
(
|
||||||
EMAIL_HOST,
|
EMAIL_HOST,
|
||||||
EMAIL_HOST_USER,
|
EMAIL_HOST_USER,
|
||||||
@ -32,7 +27,7 @@ def magic_link(email, key, token, current_site):
|
|||||||
EMAIL_PORT,
|
EMAIL_PORT,
|
||||||
EMAIL_USE_TLS,
|
EMAIL_USE_TLS,
|
||||||
EMAIL_FROM,
|
EMAIL_FROM,
|
||||||
) = get_email_configuration(instance_configuration=instance_configuration)
|
) = get_email_configuration()
|
||||||
|
|
||||||
# Send the mail
|
# Send the mail
|
||||||
subject = f"Your unique Plane login code is {token}"
|
subject = f"Your unique Plane login code is {token}"
|
||||||
|
@ -13,7 +13,6 @@ from sentry_sdk import capture_exception
|
|||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.db.models import Project, User, ProjectMemberInvite
|
from plane.db.models import Project, User, ProjectMemberInvite
|
||||||
from plane.license.models import InstanceConfiguration
|
|
||||||
from plane.license.utils.instance_value import get_email_configuration
|
from plane.license.utils.instance_value import get_email_configuration
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
@ -47,7 +46,6 @@ def project_invitation(email, project_id, token, current_site, invitor):
|
|||||||
project_member_invite.save()
|
project_member_invite.save()
|
||||||
|
|
||||||
# Configure email connection from the database
|
# Configure email connection from the database
|
||||||
instance_configuration = InstanceConfiguration.objects.filter(key__startswith='EMAIL_').values("key", "value")
|
|
||||||
(
|
(
|
||||||
EMAIL_HOST,
|
EMAIL_HOST,
|
||||||
EMAIL_HOST_USER,
|
EMAIL_HOST_USER,
|
||||||
@ -55,7 +53,7 @@ def project_invitation(email, project_id, token, current_site, invitor):
|
|||||||
EMAIL_PORT,
|
EMAIL_PORT,
|
||||||
EMAIL_USE_TLS,
|
EMAIL_USE_TLS,
|
||||||
EMAIL_FROM,
|
EMAIL_FROM,
|
||||||
) = get_email_configuration(instance_configuration=instance_configuration)
|
) = get_email_configuration()
|
||||||
|
|
||||||
connection = get_connection(
|
connection = get_connection(
|
||||||
host=EMAIL_HOST,
|
host=EMAIL_HOST,
|
||||||
|
@ -17,7 +17,6 @@ from slack_sdk.errors import SlackApiError
|
|||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.db.models import Workspace, WorkspaceMemberInvite, User
|
from plane.db.models import Workspace, WorkspaceMemberInvite, User
|
||||||
from plane.license.models import InstanceConfiguration, Instance
|
|
||||||
from plane.license.utils.instance_value import get_email_configuration
|
from plane.license.utils.instance_value import get_email_configuration
|
||||||
|
|
||||||
|
|
||||||
@ -37,9 +36,6 @@ def workspace_invitation(email, workspace_id, token, current_site, invitor):
|
|||||||
# The complete url including the domain
|
# The complete url including the domain
|
||||||
abs_url = str(current_site) + relative_link
|
abs_url = str(current_site) + relative_link
|
||||||
|
|
||||||
instance_configuration = InstanceConfiguration.objects.filter(
|
|
||||||
key__startswith="EMAIL_"
|
|
||||||
).values("key", "value")
|
|
||||||
|
|
||||||
(
|
(
|
||||||
EMAIL_HOST,
|
EMAIL_HOST,
|
||||||
@ -48,7 +44,7 @@ def workspace_invitation(email, workspace_id, token, current_site, invitor):
|
|||||||
EMAIL_PORT,
|
EMAIL_PORT,
|
||||||
EMAIL_USE_TLS,
|
EMAIL_USE_TLS,
|
||||||
EMAIL_FROM,
|
EMAIL_FROM,
|
||||||
) = get_email_configuration(instance_configuration=instance_configuration)
|
) = get_email_configuration()
|
||||||
|
|
||||||
# Subject of the email
|
# Subject of the email
|
||||||
subject = f"{user.first_name or user.display_name or user.email} has invited you to join them in {workspace.name} on Plane"
|
subject = f"{user.first_name or user.display_name or user.email} has invited you to join them in {workspace.name} on Plane"
|
||||||
@ -69,9 +65,6 @@ def workspace_invitation(email, workspace_id, token, current_site, invitor):
|
|||||||
workspace_member_invite.message = text_content
|
workspace_member_invite.message = text_content
|
||||||
workspace_member_invite.save()
|
workspace_member_invite.save()
|
||||||
|
|
||||||
instance_configuration = InstanceConfiguration.objects.filter(
|
|
||||||
key__startswith="EMAIL_"
|
|
||||||
).values("key", "value")
|
|
||||||
connection = get_connection(
|
connection = get_connection(
|
||||||
host=EMAIL_HOST,
|
host=EMAIL_HOST,
|
||||||
port=int(EMAIL_PORT),
|
port=int(EMAIL_PORT),
|
||||||
|
@ -28,10 +28,6 @@ app.conf.beat_schedule = {
|
|||||||
"task": "plane.bgtasks.file_asset_task.delete_file_asset",
|
"task": "plane.bgtasks.file_asset_task.delete_file_asset",
|
||||||
"schedule": crontab(hour=0, minute=0),
|
"schedule": crontab(hour=0, minute=0),
|
||||||
},
|
},
|
||||||
"check-instance-verification": {
|
|
||||||
"task": "plane.license.bgtasks.instance_verification_task.instance_verification_task",
|
|
||||||
"schedule": crontab(minute=0, hour='*/4'),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Load task modules from all registered Django app configs.
|
# Load task modules from all registered Django app configs.
|
||||||
|
@ -1,136 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import requests
|
|
||||||
|
|
||||||
# Django imports
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
# Third party imports
|
|
||||||
from celery import shared_task
|
|
||||||
|
|
||||||
# Module imports
|
|
||||||
from plane.db.models import User
|
|
||||||
from plane.license.models import Instance, InstanceAdmin
|
|
||||||
|
|
||||||
|
|
||||||
def instance_verification(instance):
|
|
||||||
with open("package.json", "r") as file:
|
|
||||||
# Load JSON content from the file
|
|
||||||
data = json.load(file)
|
|
||||||
|
|
||||||
headers = {"Content-Type": "application/json"}
|
|
||||||
payload = {
|
|
||||||
"instance_key": settings.INSTANCE_KEY,
|
|
||||||
"version": data.get("version", 0.1),
|
|
||||||
"machine_signature": os.environ.get("MACHINE_SIGNATURE", "machine-signature"),
|
|
||||||
"user_count": User.objects.filter(is_bot=False).count(),
|
|
||||||
}
|
|
||||||
# Register the instance
|
|
||||||
response = requests.post(
|
|
||||||
f"{settings.LICENSE_ENGINE_BASE_URL}/api/instances/",
|
|
||||||
headers=headers,
|
|
||||||
data=json.dumps(payload),
|
|
||||||
timeout=30,
|
|
||||||
)
|
|
||||||
|
|
||||||
# check response status
|
|
||||||
if response.status_code == 201:
|
|
||||||
data = response.json()
|
|
||||||
# Update instance
|
|
||||||
instance.instance_id = data.get("id")
|
|
||||||
instance.license_key = data.get("license_key")
|
|
||||||
instance.api_key = data.get("api_key")
|
|
||||||
instance.version = data.get("version")
|
|
||||||
instance.user_count = data.get("user_count", 0)
|
|
||||||
instance.is_verified = True
|
|
||||||
instance.save()
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def admin_verification(instance):
|
|
||||||
# Save the user in control center
|
|
||||||
headers = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"x-instance-id": instance.instance_id,
|
|
||||||
"x-api-key": instance.api_key,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get all the unverified instance admins
|
|
||||||
instance_admins = InstanceAdmin.objects.filter(is_verified=False).select_related(
|
|
||||||
"user"
|
|
||||||
)
|
|
||||||
updated_instance_admin = []
|
|
||||||
|
|
||||||
# Verify the instance admin
|
|
||||||
for instance_admin in instance_admins:
|
|
||||||
instance_admin.is_verified = True
|
|
||||||
# Create the admin
|
|
||||||
response = requests.post(
|
|
||||||
f"{settings.LICENSE_ENGINE_BASE_URL}/api/instances/users/register/",
|
|
||||||
headers=headers,
|
|
||||||
data=json.dumps(
|
|
||||||
{
|
|
||||||
"email": str(instance_admin.user.email),
|
|
||||||
"signup_mode": "EMAIL",
|
|
||||||
"is_admin": True,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
timeout=30,
|
|
||||||
)
|
|
||||||
updated_instance_admin.append(instance_admin)
|
|
||||||
|
|
||||||
# update all the instance admins
|
|
||||||
InstanceAdmin.objects.bulk_update(
|
|
||||||
updated_instance_admin, ["is_verified"], batch_size=10
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
def instance_user_count(instance):
|
|
||||||
try:
|
|
||||||
instance_users = User.objects.filter(is_bot=False).count()
|
|
||||||
|
|
||||||
# Update the count in the license engine
|
|
||||||
payload = {
|
|
||||||
"user_count": instance_users,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Save the user in control center
|
|
||||||
headers = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"x-instance-id": instance.instance_id,
|
|
||||||
"x-api-key": instance.api_key,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Update the license engine
|
|
||||||
_ = requests.post(
|
|
||||||
f"{settings.LICENSE_ENGINE_BASE_URL}/api/instances/",
|
|
||||||
headers=headers,
|
|
||||||
data=json.dumps(payload),
|
|
||||||
timeout=30,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
except requests.RequestException:
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
|
||||||
def instance_verification_task():
|
|
||||||
try:
|
|
||||||
# Get the first instance
|
|
||||||
instance = Instance.objects.first()
|
|
||||||
|
|
||||||
# Only register instance if it is not verified
|
|
||||||
if not instance.is_verified:
|
|
||||||
instance_verification(instance=instance)
|
|
||||||
|
|
||||||
# Admin verifications
|
|
||||||
admin_verification(instance=instance)
|
|
||||||
|
|
||||||
# Update user count
|
|
||||||
instance_user_count(instance=instance)
|
|
||||||
|
|
||||||
return
|
|
||||||
except requests.RequestException:
|
|
||||||
return
|
|
@ -30,13 +30,11 @@ class Command(BaseCommand):
|
|||||||
# Load JSON content from the file
|
# Load JSON content from the file
|
||||||
data = json.load(file)
|
data = json.load(file)
|
||||||
|
|
||||||
machine_signature = options.get("machine_signature", False)
|
machine_signature = options.get("machine_signature", "machine-signature")
|
||||||
|
|
||||||
if not machine_signature:
|
if not machine_signature:
|
||||||
raise CommandError("Machine signature is required")
|
raise CommandError("Machine signature is required")
|
||||||
|
|
||||||
# Check if machine is online
|
|
||||||
headers = {"Content-Type": "application/json"}
|
|
||||||
payload = {
|
payload = {
|
||||||
"instance_key": settings.INSTANCE_KEY,
|
"instance_key": settings.INSTANCE_KEY,
|
||||||
"version": data.get("version", 0.1),
|
"version": data.get("version", 0.1),
|
||||||
@ -44,51 +42,21 @@ class Command(BaseCommand):
|
|||||||
"user_count": User.objects.filter(is_bot=False).count(),
|
"user_count": User.objects.filter(is_bot=False).count(),
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
instance = Instance.objects.create(
|
||||||
response = requests.post(
|
instance_name="Plane Free",
|
||||||
f"{settings.LICENSE_ENGINE_BASE_URL}/api/instances/",
|
instance_id=secrets.token_hex(12),
|
||||||
headers=headers,
|
license_key=None,
|
||||||
data=json.dumps(payload),
|
api_key=secrets.token_hex(8),
|
||||||
timeout=30
|
version=payload.get("version"),
|
||||||
)
|
last_checked_at=timezone.now(),
|
||||||
|
user_count=payload.get("user_count", 0),
|
||||||
|
)
|
||||||
|
|
||||||
if response.status_code == 201:
|
self.stdout.write(
|
||||||
data = response.json()
|
self.style.SUCCESS(
|
||||||
# Create instance
|
f"Instance registered"
|
||||||
instance = Instance.objects.create(
|
|
||||||
instance_name="Plane Free",
|
|
||||||
instance_id=data.get("id"),
|
|
||||||
license_key=data.get("license_key"),
|
|
||||||
api_key=data.get("api_key"),
|
|
||||||
version=data.get("version"),
|
|
||||||
last_checked_at=timezone.now(),
|
|
||||||
user_count=data.get("user_count", 0),
|
|
||||||
is_verified=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.stdout.write(
|
|
||||||
self.style.SUCCESS(
|
|
||||||
f"Instance successfully registered and verified"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
except requests.RequestException as _e:
|
|
||||||
instance = Instance.objects.create(
|
|
||||||
instance_name="Plane Free",
|
|
||||||
instance_id=secrets.token_hex(12),
|
|
||||||
license_key=None,
|
|
||||||
api_key=secrets.token_hex(8),
|
|
||||||
version=payload.get("version"),
|
|
||||||
last_checked_at=timezone.now(),
|
|
||||||
user_count=payload.get("user_count", 0),
|
|
||||||
)
|
)
|
||||||
self.stdout.write(
|
)
|
||||||
self.style.SUCCESS(
|
|
||||||
f"Instance successfully registered"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
raise CommandError("Instance could not be registered")
|
|
||||||
else:
|
else:
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.SUCCESS(
|
self.style.SUCCESS(
|
||||||
|
@ -1,63 +1,71 @@
|
|||||||
|
# Python imports
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
# Django imports
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
# Module imports
|
||||||
|
from plane.license.models import InstanceConfiguration
|
||||||
|
from plane.license.utils.encryption import decrypt_data
|
||||||
|
|
||||||
|
|
||||||
# Helper function to return value from the passed key
|
# Helper function to return value from the passed key
|
||||||
def get_configuration_value(query, key, default=None):
|
def get_configuration_value(keys):
|
||||||
for item in query:
|
environment_list = []
|
||||||
if item["key"] == key:
|
if settings.SKIP_ENV_VAR:
|
||||||
return item.get("value", default)
|
# Get the configurations
|
||||||
return default
|
instance_configuration = InstanceConfiguration.objects.values(
|
||||||
|
"key", "value", "is_encrypted"
|
||||||
|
)
|
||||||
|
|
||||||
|
for key in keys:
|
||||||
|
for item in instance_configuration:
|
||||||
|
if key.get("key") == item.get("key"):
|
||||||
|
if item.get("is_encrypted", False):
|
||||||
|
environment_list.append(decrypt_data(item.get("value")))
|
||||||
|
else:
|
||||||
|
environment_list.append(item.get("value"))
|
||||||
|
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
environment_list.append(key.get("default"))
|
||||||
|
else:
|
||||||
|
# Get the configuration from os
|
||||||
|
for key in keys:
|
||||||
|
environment_list.append(os.environ.get(key.get("key"), key.get("default")))
|
||||||
|
|
||||||
|
return tuple(environment_list)
|
||||||
|
|
||||||
|
|
||||||
def get_email_configuration(instance_configuration):
|
def get_email_configuration():
|
||||||
# Get the configuration variables
|
|
||||||
EMAIL_HOST_USER = get_configuration_value(
|
|
||||||
instance_configuration,
|
|
||||||
"EMAIL_HOST_USER",
|
|
||||||
os.environ.get("EMAIL_HOST_USER", None),
|
|
||||||
)
|
|
||||||
|
|
||||||
EMAIL_HOST_PASSWORD = get_configuration_value(
|
|
||||||
instance_configuration,
|
|
||||||
"EMAIL_HOST_PASSWORD",
|
|
||||||
os.environ.get("EMAIL_HOST_PASSWORD", None),
|
|
||||||
)
|
|
||||||
|
|
||||||
EMAIL_HOST = get_configuration_value(
|
|
||||||
instance_configuration,
|
|
||||||
"EMAIL_HOST",
|
|
||||||
os.environ.get("EMAIL_HOST", None),
|
|
||||||
)
|
|
||||||
|
|
||||||
EMAIL_FROM = get_configuration_value(
|
|
||||||
instance_configuration,
|
|
||||||
"EMAIL_FROM",
|
|
||||||
os.environ.get("EMAIL_FROM", None),
|
|
||||||
)
|
|
||||||
|
|
||||||
EMAIL_USE_TLS = get_configuration_value(
|
|
||||||
instance_configuration,
|
|
||||||
"EMAIL_USE_TLS",
|
|
||||||
os.environ.get("EMAIL_USE_TLS", "1"),
|
|
||||||
)
|
|
||||||
|
|
||||||
EMAIL_PORT = get_configuration_value(
|
|
||||||
instance_configuration,
|
|
||||||
"EMAIL_PORT",
|
|
||||||
587,
|
|
||||||
)
|
|
||||||
|
|
||||||
EMAIL_FROM = get_configuration_value(
|
|
||||||
instance_configuration,
|
|
||||||
"EMAIL_FROM",
|
|
||||||
os.environ.get("EMAIL_FROM", "Team Plane <team@mailer.plane.so>"),
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
EMAIL_HOST,
|
get_configuration_value(
|
||||||
EMAIL_HOST_USER,
|
[
|
||||||
EMAIL_HOST_PASSWORD,
|
{
|
||||||
EMAIL_PORT,
|
"key": "EMAIL_HOST",
|
||||||
EMAIL_USE_TLS,
|
"default": os.environ.get("EMAIL_HOST"),
|
||||||
EMAIL_FROM,
|
},
|
||||||
|
{
|
||||||
|
"key": "EMAIL_HOST_USER",
|
||||||
|
"default": os.environ.get("EMAIL_HOST_USER"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "EMAIL_HOST_PASSWORD",
|
||||||
|
"default": os.environ.get("EMAIL_HOST_PASSWORD"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "EMAIL_PORT",
|
||||||
|
"default": os.environ.get("EMAIL_PORT", 587),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "EMAIL_USE_TLS",
|
||||||
|
"default": os.environ.get("EMAIL_USE_TLS", "1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "EMAIL_FROM",
|
||||||
|
"default": os.environ.get("EMAIL_FROM", "Team Plane <team@mailer.plane.so>"),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -287,7 +287,6 @@ CELERY_IMPORTS = (
|
|||||||
"plane.bgtasks.issue_automation_task",
|
"plane.bgtasks.issue_automation_task",
|
||||||
"plane.bgtasks.exporter_expired_task",
|
"plane.bgtasks.exporter_expired_task",
|
||||||
"plane.bgtasks.file_asset_task",
|
"plane.bgtasks.file_asset_task",
|
||||||
"plane.license.bgtasks.instance_verification_task",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Sentry Settings
|
# Sentry Settings
|
||||||
@ -328,12 +327,10 @@ USE_MINIO = int(os.environ.get("USE_MINIO", 0)) == 1
|
|||||||
POSTHOG_API_KEY = os.environ.get("POSTHOG_API_KEY", False)
|
POSTHOG_API_KEY = os.environ.get("POSTHOG_API_KEY", False)
|
||||||
POSTHOG_HOST = os.environ.get("POSTHOG_HOST", False)
|
POSTHOG_HOST = os.environ.get("POSTHOG_HOST", False)
|
||||||
|
|
||||||
# License engine base url
|
|
||||||
LICENSE_ENGINE_BASE_URL = os.environ.get(
|
|
||||||
"LICENSE_ENGINE_BASE_URL", "https://control-center.plane.so"
|
|
||||||
)
|
|
||||||
|
|
||||||
# instance key
|
# instance key
|
||||||
INSTANCE_KEY = os.environ.get(
|
INSTANCE_KEY = os.environ.get(
|
||||||
"INSTANCE_KEY", "ae6517d563dfc13d8270bd45cf17b08f70b37d989128a9dab46ff687603333c3"
|
"INSTANCE_KEY", "ae6517d563dfc13d8270bd45cf17b08f70b37d989128a9dab46ff687603333c3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Skip environment variable configuration
|
||||||
|
SKIP_ENV_VAR = os.environ.get("SKIP_ENV_VAR", "1") == "1"
|
||||||
|
@ -40,7 +40,7 @@ export const WorkspaceMembersListItem: FC<Props> = observer((props) => {
|
|||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
// store
|
// store
|
||||||
const {
|
const {
|
||||||
workspaceMember: { removeMember, updateMember, deleteWorkspaceInvitation },
|
workspaceMember: { removeMember, updateMember, updateMemberInvitation, deleteWorkspaceInvitation },
|
||||||
user: { currentWorkspaceMemberInfo, currentWorkspaceRole, currentUser, currentUserSettings, leaveWorkspace },
|
user: { currentWorkspaceMemberInfo, currentWorkspaceRole, currentUser, currentUserSettings, leaveWorkspace },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
// states
|
// states
|
||||||
@ -206,15 +206,26 @@ export const WorkspaceMembersListItem: FC<Props> = observer((props) => {
|
|||||||
onChange={(value: TUserWorkspaceRole | undefined) => {
|
onChange={(value: TUserWorkspaceRole | undefined) => {
|
||||||
if (!workspaceSlug || !value) return;
|
if (!workspaceSlug || !value) return;
|
||||||
|
|
||||||
updateMember(workspaceSlug.toString(), member.id, {
|
if (!member?.status)
|
||||||
role: value,
|
updateMemberInvitation(workspaceSlug.toString(), member.id, {
|
||||||
}).catch(() => {
|
role: value,
|
||||||
setToastAlert({
|
}).catch(() => {
|
||||||
type: "error",
|
setToastAlert({
|
||||||
title: "Error!",
|
type: "error",
|
||||||
message: "An error occurred while updating member role. Please try again.",
|
title: "Error!",
|
||||||
|
message: "An error occurred while updating member role. Please try again.",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
else
|
||||||
|
updateMember(workspaceSlug.toString(), member.id, {
|
||||||
|
role: value,
|
||||||
|
}).catch(() => {
|
||||||
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
|
message: "An error occurred while updating member role. Please try again.",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
disabled={!hasRoleChangeAccess}
|
disabled={!hasRoleChangeAccess}
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
|
@ -166,6 +166,18 @@ export class WorkspaceService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateWorkspaceInvitation(
|
||||||
|
workspaceSlug: string,
|
||||||
|
invitationId: string,
|
||||||
|
data: Partial<IWorkspaceMember>
|
||||||
|
): Promise<any> {
|
||||||
|
return this.patch(`/api/workspaces/${workspaceSlug}/invitations/${invitationId}/`, data)
|
||||||
|
.then((response) => response?.data)
|
||||||
|
.catch((error) => {
|
||||||
|
throw error?.response?.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async deleteWorkspaceInvitations(workspaceSlug: string, invitationId: string): Promise<any> {
|
async deleteWorkspaceInvitations(workspaceSlug: string, invitationId: string): Promise<any> {
|
||||||
return this.delete(`/api/workspaces/${workspaceSlug}/invitations/${invitationId}/`)
|
return this.delete(`/api/workspaces/${workspaceSlug}/invitations/${invitationId}/`)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
|
@ -19,6 +19,11 @@ export interface IWorkspaceMemberStore {
|
|||||||
updateMember: (workspaceSlug: string, memberId: string, data: Partial<IWorkspaceMember>) => Promise<void>;
|
updateMember: (workspaceSlug: string, memberId: string, data: Partial<IWorkspaceMember>) => Promise<void>;
|
||||||
removeMember: (workspaceSlug: string, memberId: string) => Promise<void>;
|
removeMember: (workspaceSlug: string, memberId: string) => Promise<void>;
|
||||||
inviteMembersToWorkspace: (workspaceSlug: string, data: IWorkspaceBulkInviteFormData) => Promise<any>;
|
inviteMembersToWorkspace: (workspaceSlug: string, data: IWorkspaceBulkInviteFormData) => Promise<any>;
|
||||||
|
updateMemberInvitation: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
memberId: string,
|
||||||
|
data: Partial<IWorkspaceMemberInvitation>
|
||||||
|
) => Promise<void>;
|
||||||
deleteWorkspaceInvitation: (workspaceSlug: string, memberId: string) => Promise<void>;
|
deleteWorkspaceInvitation: (workspaceSlug: string, memberId: string) => Promise<void>;
|
||||||
// computed
|
// computed
|
||||||
workspaceMembers: IWorkspaceMember[] | null;
|
workspaceMembers: IWorkspaceMember[] | null;
|
||||||
@ -53,6 +58,7 @@ export class WorkspaceMemberStore implements IWorkspaceMemberStore {
|
|||||||
updateMember: action,
|
updateMember: action,
|
||||||
removeMember: action,
|
removeMember: action,
|
||||||
inviteMembersToWorkspace: action,
|
inviteMembersToWorkspace: action,
|
||||||
|
updateMemberInvitation: action,
|
||||||
deleteWorkspaceInvitation: action,
|
deleteWorkspaceInvitation: action,
|
||||||
// computed
|
// computed
|
||||||
workspaceMembers: computed,
|
workspaceMembers: computed,
|
||||||
@ -183,6 +189,55 @@ export class WorkspaceMemberStore implements IWorkspaceMemberStore {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update workspace member invitation using workspace slug and member id and data
|
||||||
|
* @param workspaceSlug
|
||||||
|
* @param memberId
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
updateMemberInvitation = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
memberId: string,
|
||||||
|
data: Partial<IWorkspaceMemberInvitation>
|
||||||
|
) => {
|
||||||
|
const originalMemberInvitations = [...this.memberInvitations?.[workspaceSlug]]; // in case of error, we will revert back to original members
|
||||||
|
|
||||||
|
const memberInvitations = [...this.memberInvitations?.[workspaceSlug]];
|
||||||
|
|
||||||
|
const index = memberInvitations.findIndex((m) => m.id === memberId);
|
||||||
|
memberInvitations[index] = { ...memberInvitations[index], ...data };
|
||||||
|
|
||||||
|
// optimistic update
|
||||||
|
runInAction(() => {
|
||||||
|
this.loader = true;
|
||||||
|
this.error = null;
|
||||||
|
this.memberInvitations = {
|
||||||
|
...this.memberInvitations,
|
||||||
|
[workspaceSlug]: memberInvitations,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.workspaceService.updateWorkspaceInvitation(workspaceSlug, memberId, data);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.loader = false;
|
||||||
|
this.error = null;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
runInAction(() => {
|
||||||
|
this.loader = false;
|
||||||
|
this.error = error;
|
||||||
|
this.memberInvitations = {
|
||||||
|
...this.memberInvitations,
|
||||||
|
[workspaceSlug]: originalMemberInvitations,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* delete the workspace invitation
|
* delete the workspace invitation
|
||||||
* @param workspaceSlug
|
* @param workspaceSlug
|
||||||
|
Loading…
Reference in New Issue
Block a user