forked from github/plane
chore: instance (#2955)
This commit is contained in:
parent
59110c7205
commit
79fa860b28
@ -14,6 +14,11 @@ PGHOST="plane-db"
|
||||
PGDATABASE="plane"
|
||||
DATABASE_URL=postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}/${PGDATABASE}
|
||||
|
||||
# Oauth variables
|
||||
GOOGLE_CLIENT_ID=""
|
||||
GITHUB_CLIENT_ID=""
|
||||
GITHUB_CLIENT_SECRET=""
|
||||
|
||||
# Redis Settings
|
||||
REDIS_HOST="plane-redis"
|
||||
REDIS_PORT="6379"
|
||||
@ -50,7 +55,6 @@ NGINX_PORT=80
|
||||
# SignUps
|
||||
ENABLE_SIGNUP="1"
|
||||
|
||||
|
||||
# Enable Email/Password Signup
|
||||
ENABLE_EMAIL_PASSWORD="1"
|
||||
|
||||
|
@ -21,7 +21,8 @@ from plane.db.models import (
|
||||
from .base import BaseSerializer
|
||||
from .cycle import CycleSerializer, CycleLiteSerializer
|
||||
from .module import ModuleSerializer, ModuleLiteSerializer
|
||||
|
||||
from .user import UserLiteSerializer
|
||||
from .state import StateLiteSerializer
|
||||
|
||||
class IssueSerializer(BaseSerializer):
|
||||
assignees = serializers.ListField(
|
||||
@ -331,12 +332,23 @@ class ModuleIssueSerializer(BaseSerializer):
|
||||
]
|
||||
|
||||
|
||||
class IssueExpandSerializer(BaseSerializer):
|
||||
# Serialize the related cycle. It's a OneToOne relation.
|
||||
cycle = CycleLiteSerializer(source="issue_cycle.cycle", read_only=True)
|
||||
class LabelLiteSerializer(BaseSerializer):
|
||||
|
||||
# Serialize the related module. It's a OneToOne relation.
|
||||
class Meta:
|
||||
model = Label
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"color",
|
||||
]
|
||||
|
||||
|
||||
class IssueExpandSerializer(BaseSerializer):
|
||||
cycle = CycleLiteSerializer(source="issue_cycle.cycle", read_only=True)
|
||||
module = ModuleLiteSerializer(source="issue_module.module", read_only=True)
|
||||
labels = LabelLiteSerializer(read_only=True, many=True)
|
||||
assignees = UserLiteSerializer(read_only=True, many=True)
|
||||
state = StateLiteSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Issue
|
||||
@ -349,4 +361,4 @@ class IssueExpandSerializer(BaseSerializer):
|
||||
"updated_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
]
|
||||
|
@ -11,10 +11,6 @@ class UserLiteSerializer(BaseSerializer):
|
||||
"first_name",
|
||||
"last_name",
|
||||
"avatar",
|
||||
"is_bot",
|
||||
"display_name",
|
||||
]
|
||||
read_only_fields = [
|
||||
"id",
|
||||
"is_bot",
|
||||
]
|
||||
read_only_fields = fields
|
@ -169,8 +169,8 @@ class ChangePasswordSerializer(serializers.Serializer):
|
||||
Serializer for password change endpoint.
|
||||
"""
|
||||
old_password = serializers.CharField(required=True)
|
||||
new_password = serializers.CharField(required=True)
|
||||
confirm_password = serializers.CharField(required=True)
|
||||
new_password = serializers.CharField(required=True, min_length=8)
|
||||
confirm_password = serializers.CharField(required=True, min_length=8)
|
||||
|
||||
def validate(self, data):
|
||||
if data.get("old_password") == data.get("new_password"):
|
||||
@ -187,9 +187,7 @@ class ChangePasswordSerializer(serializers.Serializer):
|
||||
|
||||
|
||||
class ResetPasswordSerializer(serializers.Serializer):
|
||||
model = User
|
||||
|
||||
"""
|
||||
Serializer for password change endpoint.
|
||||
"""
|
||||
new_password = serializers.CharField(required=True)
|
||||
new_password = serializers.CharField(required=True, min_length=8)
|
||||
|
@ -105,17 +105,21 @@ class ForgotPasswordEndpoint(BaseAPIView):
|
||||
def post(self, request):
|
||||
email = request.data.get("email")
|
||||
|
||||
if User.objects.filter(email=email).exists():
|
||||
user = User.objects.get(email=email)
|
||||
uidb64 = urlsafe_base64_encode(smart_bytes(user.id))
|
||||
token = PasswordResetTokenGenerator().make_token(user)
|
||||
try:
|
||||
validate_email(email)
|
||||
except ValidationError:
|
||||
return Response({"error": "Please enter a valid email"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Get the user
|
||||
user = User.objects.filter(email=email).first()
|
||||
if user:
|
||||
# Get the reset token for user
|
||||
uidb64, token = get_tokens_for_user(user=user)
|
||||
current_site = request.META.get("HTTP_ORIGIN")
|
||||
|
||||
# send the forgot password email
|
||||
forgot_password.delay(
|
||||
user.first_name, user.email, uidb64, token, current_site
|
||||
)
|
||||
|
||||
return Response(
|
||||
{"message": "Check your email to reset your password"},
|
||||
status=status.HTTP_200_OK,
|
||||
@ -130,14 +134,18 @@ class ResetPasswordEndpoint(BaseAPIView):
|
||||
|
||||
def post(self, request, uidb64, token):
|
||||
try:
|
||||
# Decode the id from the uidb64
|
||||
id = smart_str(urlsafe_base64_decode(uidb64))
|
||||
user = User.objects.get(id=id)
|
||||
|
||||
# check if the token is valid for the user
|
||||
if not PasswordResetTokenGenerator().check_token(user, token):
|
||||
return Response(
|
||||
{"error": "Token is invalid"},
|
||||
status=status.HTTP_401_UNAUTHORIZED,
|
||||
)
|
||||
|
||||
# Reset the password
|
||||
serializer = ResetPasswordSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
# set_password also hashes the password that the user will get
|
||||
@ -145,9 +153,9 @@ class ResetPasswordEndpoint(BaseAPIView):
|
||||
user.is_password_autoset = False
|
||||
user.save()
|
||||
|
||||
# Log the user in
|
||||
# Generate access token for the user
|
||||
access_token, refresh_token = get_tokens_for_user(user)
|
||||
|
||||
data = {
|
||||
"access_token": access_token,
|
||||
"refresh_token": refresh_token,
|
||||
@ -166,7 +174,6 @@ class ResetPasswordEndpoint(BaseAPIView):
|
||||
class ChangePasswordEndpoint(BaseAPIView):
|
||||
def post(self, request):
|
||||
serializer = ChangePasswordSerializer(data=request.data)
|
||||
|
||||
user = User.objects.get(pk=request.user.id)
|
||||
if serializer.is_valid():
|
||||
if not user.check_password(serializer.data.get("old_password")):
|
||||
@ -218,16 +225,15 @@ class EmailCheckEndpoint(BaseAPIView):
|
||||
]
|
||||
|
||||
def post(self, request):
|
||||
# get the email
|
||||
|
||||
# Check the instance registration
|
||||
instance = Instance.objects.first()
|
||||
if instance is None:
|
||||
if instance is None or not instance.is_setup_done:
|
||||
return Response(
|
||||
{"error": "Instance is not configured"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Get the configurations
|
||||
instance_configuration = InstanceConfiguration.objects.values("key", "value")
|
||||
|
||||
email = request.data.get("email", False)
|
||||
@ -267,7 +273,7 @@ class EmailCheckEndpoint(BaseAPIView):
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
||||
# Create the user with default values
|
||||
user = User.objects.create(
|
||||
email=email,
|
||||
username=uuid.uuid4().hex,
|
||||
@ -325,7 +331,7 @@ class EmailCheckEndpoint(BaseAPIView):
|
||||
first_time=True,
|
||||
)
|
||||
# Automatically send the email
|
||||
return Response({"is_password_autoset": user.is_password_autoset}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response({"is_password_autoset": user.is_password_autoset}, status=status.HTTP_200_OK)
|
||||
# Existing user
|
||||
else:
|
||||
if type == "magic_code":
|
||||
|
@ -10,14 +10,12 @@ from django.utils import timezone
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import validate_email
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.hashers import make_password
|
||||
|
||||
# Third party imports
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework import status
|
||||
from rest_framework_simplejwt.tokens import RefreshToken
|
||||
|
||||
from sentry_sdk import capture_message
|
||||
|
||||
# Module imports
|
||||
@ -33,7 +31,6 @@ from plane.settings.redis import redis_instance
|
||||
from plane.license.models import InstanceConfiguration, Instance
|
||||
from plane.license.utils.instance_value import get_configuration_value
|
||||
from plane.bgtasks.event_tracking_task import auth_events
|
||||
from plane.bgtasks.magic_link_code_task import magic_link
|
||||
from plane.bgtasks.user_count_task import update_user_instance_user_count
|
||||
|
||||
|
||||
@ -49,6 +46,14 @@ class SignUpEndpoint(BaseAPIView):
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
def post(self, request):
|
||||
# Check if the instance configuration is done
|
||||
instance = Instance.objects.first()
|
||||
if instance is None or not instance.is_setup_done:
|
||||
return Response(
|
||||
{"error": "Instance is not configured"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
instance_configuration = InstanceConfiguration.objects.values("key", "value")
|
||||
|
||||
email = request.data.get("email", False)
|
||||
@ -71,6 +76,7 @@ class SignUpEndpoint(BaseAPIView):
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# If the sign up is not enabled and the user does not have invite disallow him from creating the account
|
||||
if (
|
||||
get_configuration_value(
|
||||
instance_configuration,
|
||||
@ -124,6 +130,14 @@ class SignInEndpoint(BaseAPIView):
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
def post(self, request):
|
||||
# Check if the instance configuration is done
|
||||
instance = Instance.objects.first()
|
||||
if instance is None or not instance.is_setup_done:
|
||||
return Response(
|
||||
{"error": "Instance is not configured"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
email = request.data.get("email", False)
|
||||
password = request.data.get("password", False)
|
||||
|
||||
@ -144,14 +158,6 @@ class SignInEndpoint(BaseAPIView):
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Check if the instance setup is done or not
|
||||
instance = Instance.objects.first()
|
||||
if instance is None or not instance.is_setup_done:
|
||||
return Response(
|
||||
{"error": "Instance is not configured"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Get the user
|
||||
user = User.objects.filter(email=email).first()
|
||||
|
||||
@ -288,6 +294,7 @@ class MagicSignInEndpoint(BaseAPIView):
|
||||
]
|
||||
|
||||
def post(self, request):
|
||||
# Check if the instance configuration is done
|
||||
instance = Instance.objects.first()
|
||||
if instance is None or not instance.is_setup_done:
|
||||
return Response(
|
||||
|
@ -303,14 +303,6 @@ class OauthEndpoint(BaseAPIView):
|
||||
instance_configuration = InstanceConfiguration.objects.values(
|
||||
"key", "value"
|
||||
)
|
||||
# Check if instance is registered or not
|
||||
instance = Instance.objects.first()
|
||||
if instance is None and not instance.is_setup_done:
|
||||
return Response(
|
||||
{"error": "Instance is not configured"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if (
|
||||
get_configuration_value(
|
||||
instance_configuration,
|
||||
|
@ -1,7 +1,8 @@
|
||||
# Python imports
|
||||
import csv
|
||||
import io
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
|
||||
# Django imports
|
||||
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||
@ -17,8 +18,8 @@ from sentry_sdk import capture_exception
|
||||
from plane.db.models import Issue
|
||||
from plane.utils.analytics_plot import build_graph_plot
|
||||
from plane.utils.issue_filters import issue_filters
|
||||
from plane.license.models import InstanceConfiguration
|
||||
from plane.license.utils.instance_value import get_configuration_value
|
||||
from plane.license.models import InstanceConfiguration, Instance
|
||||
from plane.license.utils.instance_value import get_email_configuration
|
||||
|
||||
row_mapping = {
|
||||
"state__name": "State",
|
||||
@ -43,7 +44,7 @@ CYCLE_ID = "issue_cycle__cycle_id"
|
||||
MODULE_ID = "issue_module__module_id"
|
||||
|
||||
|
||||
def send_export_email(email, slug, csv_buffer):
|
||||
def send_export_email(email, slug, csv_buffer, rows):
|
||||
"""Helper function to send export email."""
|
||||
subject = "Your Export is ready"
|
||||
html_content = render_to_string("emails/exports/analytics.html", {})
|
||||
@ -55,47 +56,58 @@ def send_export_email(email, slug, csv_buffer):
|
||||
instance_configuration = InstanceConfiguration.objects.filter(
|
||||
key__startswith="EMAIL_"
|
||||
).values("key", "value")
|
||||
|
||||
(
|
||||
EMAIL_HOST,
|
||||
EMAIL_HOST_USER,
|
||||
EMAIL_HOST_PASSWORD,
|
||||
EMAIL_PORT,
|
||||
EMAIL_USE_TLS,
|
||||
EMAIL_FROM,
|
||||
) = get_email_configuration(instance_configuration=instance_configuration)
|
||||
|
||||
# Send the email if the users don't have smtp configured
|
||||
if EMAIL_HOST and EMAIL_HOST_USER and EMAIL_HOST_PASSWORD:
|
||||
# Check the instance registration
|
||||
instance = Instance.objects.first()
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-instance-id": instance.instance_id,
|
||||
"x-api-key": instance.api_key,
|
||||
}
|
||||
|
||||
payload = {
|
||||
"email": email,
|
||||
"slug": slug,
|
||||
"rows": rows,
|
||||
}
|
||||
|
||||
_ = requests.post(
|
||||
f"{settings.LICENSE_ENGINE_BASE_URL}/api/instances/users/analytics/",
|
||||
headers=headers,
|
||||
json=payload,
|
||||
)
|
||||
return
|
||||
|
||||
connection = get_connection(
|
||||
host=get_configuration_value(
|
||||
instance_configuration, "EMAIL_HOST", os.environ.get("EMAIL_HOST")
|
||||
),
|
||||
port=int(
|
||||
get_configuration_value(
|
||||
instance_configuration, "EMAIL_PORT", os.environ.get("EMAIL_PORT")
|
||||
)
|
||||
),
|
||||
username=get_configuration_value(
|
||||
instance_configuration,
|
||||
"EMAIL_HOST_USER",
|
||||
os.environ.get("EMAIL_HOST_USER"),
|
||||
),
|
||||
password=get_configuration_value(
|
||||
instance_configuration,
|
||||
"EMAIL_HOST_PASSWORD",
|
||||
os.environ.get("EMAIL_HOST_PASSWORD"),
|
||||
),
|
||||
use_tls=bool(
|
||||
get_configuration_value(
|
||||
instance_configuration,
|
||||
"EMAIL_USE_TLS",
|
||||
os.environ.get("EMAIL_USE_TLS", "1"),
|
||||
)
|
||||
),
|
||||
host=EMAIL_HOST,
|
||||
port=int(EMAIL_PORT),
|
||||
username=EMAIL_HOST_USER,
|
||||
password=EMAIL_HOST_PASSWORD,
|
||||
use_tls=bool(EMAIL_USE_TLS),
|
||||
)
|
||||
|
||||
msg = EmailMultiAlternatives(
|
||||
subject=subject,
|
||||
body=text_content,
|
||||
from_email=get_configuration_value(
|
||||
instance_configuration,
|
||||
"EMAIL_FROM",
|
||||
os.environ.get("EMAIL_FROM", "Team Plane <team@mailer.plane.so>"),
|
||||
),
|
||||
from_email=EMAIL_FROM,
|
||||
to=[email],
|
||||
connection=connection,
|
||||
)
|
||||
msg.attach(f"{slug}-analytics.csv", csv_buffer.getvalue())
|
||||
msg.send(fail_silently=False)
|
||||
return
|
||||
|
||||
|
||||
def get_assignee_details(slug, filters):
|
||||
@ -463,8 +475,11 @@ def analytic_export_task(email, data, slug):
|
||||
)
|
||||
|
||||
csv_buffer = generate_csv_from_rows(rows)
|
||||
send_export_email(email, slug, csv_buffer)
|
||||
send_export_email(email, slug, csv_buffer, rows)
|
||||
return
|
||||
except Exception as e:
|
||||
print(e)
|
||||
if settings.DEBUG:
|
||||
print(e)
|
||||
capture_exception(e)
|
||||
return
|
||||
|
@ -40,13 +40,10 @@ def forgot_password(first_name, email, uidb64, token, current_site):
|
||||
) = get_email_configuration(instance_configuration=instance_configuration)
|
||||
|
||||
# Send the email if the users don't have smtp configured
|
||||
if not EMAIL_HOST or not EMAIL_HOST_USER or not EMAIL_HOST_PASSWORD:
|
||||
if not (EMAIL_HOST and EMAIL_HOST_USER and EMAIL_HOST_PASSWORD):
|
||||
# Check the instance registration
|
||||
instance = Instance.objects.first()
|
||||
|
||||
# send the emails through control center
|
||||
license_engine_base_url = os.environ.get("LICENSE_ENGINE_BASE_URL", False)
|
||||
|
||||
# headers
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
@ -61,7 +58,7 @@ def forgot_password(first_name, email, uidb64, token, current_site):
|
||||
}
|
||||
|
||||
_ = requests.post(
|
||||
f"{license_engine_base_url}/api/instances/users/forgot-password/",
|
||||
f"{settings.LICENSE_ENGINE_BASE_URL}/api/instances/users/forgot-password/",
|
||||
headers=headers,
|
||||
data=json.dumps(payload),
|
||||
)
|
||||
|
@ -21,7 +21,6 @@ from plane.license.utils.instance_value import get_email_configuration
|
||||
@shared_task
|
||||
def magic_link(email, key, token, current_site):
|
||||
try:
|
||||
|
||||
instance_configuration = InstanceConfiguration.objects.filter(
|
||||
key__startswith="EMAIL_"
|
||||
).values("key", "value")
|
||||
@ -36,13 +35,10 @@ def magic_link(email, key, token, current_site):
|
||||
) = get_email_configuration(instance_configuration=instance_configuration)
|
||||
|
||||
# Send the email if the users don't have smtp configured
|
||||
if not EMAIL_HOST or not EMAIL_HOST_USER or not EMAIL_HOST_PASSWORD:
|
||||
if not (EMAIL_HOST and EMAIL_HOST_USER and EMAIL_HOST_PASSWORD):
|
||||
# Check the instance registration
|
||||
instance = Instance.objects.first()
|
||||
|
||||
# send the emails through control center
|
||||
license_engine_base_url = os.environ.get("LICENSE_ENGINE_BASE_URL", False)
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-instance-id": instance.instance_id,
|
||||
@ -55,7 +51,7 @@ def magic_link(email, key, token, current_site):
|
||||
}
|
||||
|
||||
_ = requests.post(
|
||||
f"{license_engine_base_url}/api/instances/users/magic-code/",
|
||||
f"{settings.LICENSE_ENGINE_BASE_URL}/api/instances/users/magic-code/",
|
||||
headers=headers,
|
||||
data=json.dumps(payload),
|
||||
)
|
||||
|
@ -32,13 +32,9 @@ def update_user_instance_user_count():
|
||||
"x-api-key": instance.api_key,
|
||||
}
|
||||
|
||||
license_engine_base_url = os.environ.get("LICENSE_ENGINE_BASE_URL")
|
||||
if not license_engine_base_url:
|
||||
raise Exception("License Engine base url is required")
|
||||
|
||||
# Update the license engine
|
||||
_ = requests.post(
|
||||
f"{license_engine_base_url}/api/instances/",
|
||||
f"{settings.LICENSE_ENGINE_BASE_URL}/api/instances/",
|
||||
headers=headers,
|
||||
data=json.dumps(payload),
|
||||
)
|
||||
|
@ -51,15 +51,10 @@ def workspace_invitation(email, workspace_id, token, current_site, invitor):
|
||||
) = get_email_configuration(instance_configuration=instance_configuration)
|
||||
|
||||
# Send the email if the users don't have smtp configured
|
||||
if not EMAIL_HOST or not EMAIL_HOST_USER or not EMAIL_HOST_PASSWORD:
|
||||
if not (EMAIL_HOST and EMAIL_HOST_USER and EMAIL_HOST_PASSWORD):
|
||||
# Check the instance registration
|
||||
instance = Instance.objects.first()
|
||||
|
||||
# send the emails through control center
|
||||
license_engine_base_url = os.environ.get("LICENSE_ENGINE_BASE_URL", False)
|
||||
if not license_engine_base_url:
|
||||
raise Exception("License engine base url is required")
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-instance-id": instance.instance_id,
|
||||
@ -73,7 +68,7 @@ def workspace_invitation(email, workspace_id, token, current_site, invitor):
|
||||
"email": email,
|
||||
}
|
||||
_ = requests.post(
|
||||
f"{license_engine_base_url}/api/instances/users/workspace-invitation/",
|
||||
f"{settings.LICENSE_ENGINE_BASE_URL}/api/instances/users/workspace-invitation/",
|
||||
headers=headers,
|
||||
data=json.dumps(payload),
|
||||
)
|
||||
|
@ -11,6 +11,7 @@ from django.utils import timezone
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.core.validators import validate_email
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.conf import settings
|
||||
|
||||
# Third party imports
|
||||
from rest_framework import status
|
||||
@ -34,7 +35,6 @@ from plane.db.models import User
|
||||
from plane.license.utils.encryption import encrypt_data
|
||||
from plane.settings.redis import redis_instance
|
||||
from plane.bgtasks.magic_link_code_task import magic_link
|
||||
from plane.license.utils.instance_value import get_configuration_value
|
||||
|
||||
|
||||
class InstanceEndpoint(BaseAPIView):
|
||||
@ -57,25 +57,17 @@ class InstanceEndpoint(BaseAPIView):
|
||||
# Load JSON content from the file
|
||||
data = json.load(file)
|
||||
|
||||
license_engine_base_url = os.environ.get("LICENSE_ENGINE_BASE_URL")
|
||||
|
||||
if not license_engine_base_url:
|
||||
raise Response(
|
||||
{"error": "LICENSE_ENGINE_BASE_URL is required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
payload = {
|
||||
"instance_key": os.environ.get("INSTANCE_KEY"),
|
||||
"instance_key":settings.INSTANCE_KEY,
|
||||
"version": data.get("version", 0.1),
|
||||
"machine_signature": os.environ.get("MACHINE_SIGNATURE"),
|
||||
"user_count": User.objects.filter(is_bot=False).count(),
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{license_engine_base_url}/api/instances/",
|
||||
f"{settings.LICENSE_ENGINE_BASE_URL}/api/instances/",
|
||||
headers=headers,
|
||||
data=json.dumps(payload),
|
||||
)
|
||||
@ -130,6 +122,24 @@ class InstanceEndpoint(BaseAPIView):
|
||||
serializer = InstanceSerializer(instance, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
# Save the user in control center
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-instance-id": instance.instance_id,
|
||||
"x-api-key": instance.api_key,
|
||||
}
|
||||
# Update instance settings in the license engine
|
||||
_ = requests.patch(
|
||||
f"{settings.LICENSE_ENGINE_BASE_URL}/api/instances/",
|
||||
headers=headers,
|
||||
data=json.dumps(
|
||||
{
|
||||
"is_support_required": serializer.data["is_support_required"],
|
||||
"is_telemetry_enabled": serializer.data["is_telemetry_enabled"],
|
||||
"version": serializer.data["version"],
|
||||
}
|
||||
),
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@ -251,7 +261,6 @@ class AdminMagicSignInGenerateEndpoint(BaseAPIView):
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
||||
if not email:
|
||||
return Response(
|
||||
{"error": "Please provide a valid email address"},
|
||||
@ -409,13 +418,6 @@ class AdminSetUserPasswordEndpoint(BaseAPIView):
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
license_engine_base_url = os.environ.get("LICENSE_ENGINE_BASE_URL", False)
|
||||
if not license_engine_base_url:
|
||||
return Response(
|
||||
{"error": "License engine base url is required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Save the user in control center
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
@ -423,14 +425,14 @@ class AdminSetUserPasswordEndpoint(BaseAPIView):
|
||||
"x-api-key": instance.api_key,
|
||||
}
|
||||
_ = requests.patch(
|
||||
f"{license_engine_base_url}/api/instances/",
|
||||
f"{settings.LICENSE_ENGINE_BASE_URL}/api/instances/",
|
||||
headers=headers,
|
||||
data=json.dumps({"is_setup_done": True}),
|
||||
)
|
||||
|
||||
# Also register the user as admin
|
||||
_ = requests.post(
|
||||
f"{license_engine_base_url}/api/instances/users/register/",
|
||||
f"{settings.LICENSE_ENGINE_BASE_URL}/api/instances/users/register/",
|
||||
headers=headers,
|
||||
data=json.dumps(
|
||||
{
|
||||
@ -472,24 +474,20 @@ class SignUpScreenVisitedEndpoint(BaseAPIView):
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
license_engine_base_url = os.environ.get("LICENSE_ENGINE_BASE_URL", False)
|
||||
|
||||
if not license_engine_base_url:
|
||||
return Response(
|
||||
{"error": "License engine base url is required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
if not instance.is_signup_screen_visited:
|
||||
instance.is_signup_screen_visited = True
|
||||
instance.save()
|
||||
# set the headers
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-instance-id": instance.instance_id,
|
||||
"x-api-key": instance.api_key,
|
||||
}
|
||||
# create the payload
|
||||
payload = {"is_signup_screen_visited": True}
|
||||
_ = requests.patch(
|
||||
f"{settings.LICENSE_ENGINE_BASE_URL}/api/instances/",
|
||||
headers=headers,
|
||||
data=json.dumps(payload),
|
||||
)
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-instance-id": instance.instance_id,
|
||||
"x-api-key": instance.api_key,
|
||||
}
|
||||
|
||||
payload = {"is_signup_screen_visited": True}
|
||||
response = requests.patch(
|
||||
f"{license_engine_base_url}/api/instances/",
|
||||
headers=headers,
|
||||
data=json.dumps(payload),
|
||||
)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
@ -6,6 +6,7 @@ import requests
|
||||
# Django imports
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
|
||||
# Module imports
|
||||
from plane.license.models import Instance
|
||||
@ -30,31 +31,22 @@ class Command(BaseCommand):
|
||||
data = json.load(file)
|
||||
|
||||
machine_signature = options.get("machine_signature", False)
|
||||
instance_key = os.environ.get("INSTANCE_KEY", False)
|
||||
|
||||
# Raise an exception if the admin email is not provided
|
||||
if not instance_key:
|
||||
raise CommandError("INSTANCE_KEY is required")
|
||||
|
||||
|
||||
if not machine_signature:
|
||||
raise CommandError("Machine signature is required")
|
||||
|
||||
license_engine_base_url = os.environ.get("LICENSE_ENGINE_BASE_URL")
|
||||
|
||||
if not license_engine_base_url:
|
||||
raise CommandError("LICENSE_ENGINE_BASE_URL is required")
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
payload = {
|
||||
"instance_key": instance_key,
|
||||
"instance_key": settings.INSTANCE_KEY,
|
||||
"version": data.get("version", 0.1),
|
||||
"machine_signature": machine_signature,
|
||||
"user_count": User.objects.filter(is_bot=False).count(),
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{license_engine_base_url}/api/instances/",
|
||||
f"{settings.LICENSE_ENGINE_BASE_URL}/api/instances/",
|
||||
headers=headers,
|
||||
data=json.dumps(payload),
|
||||
)
|
||||
|
@ -324,4 +324,10 @@ USE_MINIO = int(os.environ.get("USE_MINIO", 0)) == 1
|
||||
|
||||
# Posthog settings
|
||||
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 = os.environ.get("INSTANCE_KEY", "ae6517d563dfc13d8270bd45cf17b08f70b37d989128a9dab46ff687603333c3")
|
||||
|
@ -149,20 +149,20 @@
|
||||
padding-top: 10px !important;
|
||||
}
|
||||
.r13-o {
|
||||
border-bottom-color: #efefef !important;
|
||||
border-bottom-color: #d9e4ff !important;
|
||||
border-bottom-width: 1px !important;
|
||||
border-left-color: #efefef !important;
|
||||
border-left-color: #d9e4ff !important;
|
||||
border-left-width: 1px !important;
|
||||
border-right-color: #efefef !important;
|
||||
border-right-color: #d9e4ff !important;
|
||||
border-right-width: 1px !important;
|
||||
border-style: solid !important;
|
||||
border-top-color: #efefef !important;
|
||||
border-top-color: #d9e4ff !important;
|
||||
border-top-width: 1px !important;
|
||||
margin: 0 auto 0 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
.r14-i {
|
||||
background-color: #e3e6f1 !important;
|
||||
background-color: #ecf1ff !important;
|
||||
padding-bottom: 10px !important;
|
||||
padding-left: 10px !important;
|
||||
padding-right: 10px !important;
|
||||
@ -225,16 +225,11 @@
|
||||
padding-top: 5px !important;
|
||||
}
|
||||
.r24-o {
|
||||
border-style: solid !important;
|
||||
margin-right: 8px !important;
|
||||
width: 32px !important;
|
||||
}
|
||||
.r25-o {
|
||||
border-style: solid !important;
|
||||
margin-right: 0px !important;
|
||||
width: 32px !important;
|
||||
}
|
||||
.r26-i {
|
||||
.r25-i {
|
||||
padding-bottom: 0px !important;
|
||||
padding-top: 5px !important;
|
||||
text-align: center !important;
|
||||
@ -664,17 +659,17 @@
|
||||
width="100%"
|
||||
class="r13-o"
|
||||
style="
|
||||
background-color: #e3e6f1;
|
||||
border-bottom-color: #efefef;
|
||||
background-color: #ecf1ff;
|
||||
border-bottom-color: #d9e4ff;
|
||||
border-bottom-width: 1px;
|
||||
border-collapse: separate;
|
||||
border-left-color: #efefef;
|
||||
border-left-color: #d9e4ff;
|
||||
border-left-width: 1px;
|
||||
border-radius: 5px;
|
||||
border-right-color: #efefef;
|
||||
border-right-color: #d9e4ff;
|
||||
border-right-width: 1px;
|
||||
border-style: solid;
|
||||
border-top-color: #efefef;
|
||||
border-top-color: #d9e4ff;
|
||||
border-top-width: 1px;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
@ -690,7 +685,7 @@
|
||||
font-family: georgia, serif;
|
||||
font-size: 16px;
|
||||
word-break: break-word;
|
||||
background-color: #e3e6f1;
|
||||
background-color: #ecf1ff;
|
||||
border-radius: 4px;
|
||||
line-height: 3;
|
||||
padding-bottom: 10px;
|
||||
@ -714,10 +709,10 @@
|
||||
<p style="margin: 0">
|
||||
<span
|
||||
style="
|
||||
color: #716c6c;
|
||||
color: #5f5e5e;
|
||||
font-family: Arial,
|
||||
helvetica, sans-serif;
|
||||
font-size: 15px;
|
||||
font-size: 14px;
|
||||
"
|
||||
>Please copy and paste this
|
||||
on the screen where you
|
||||
@ -1251,13 +1246,14 @@
|
||||
role="presentation"
|
||||
width="100%"
|
||||
class="r22-o"
|
||||
class="r24-o"
|
||||
style="
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
"
|
||||
>
|
||||
<tr>
|
||||
<td
|
||||
class="r23-i"
|
||||
class="r17-i"
|
||||
style="
|
||||
font-size: 0px;
|
||||
line-height: 0px;
|
||||
@ -1309,7 +1305,7 @@
|
||||
border="0"
|
||||
role="presentation"
|
||||
width="100%"
|
||||
class="r25-o"
|
||||
class="r24-o"
|
||||
style="
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
@ -1441,7 +1437,7 @@
|
||||
<td
|
||||
align="center"
|
||||
valign="top"
|
||||
class="r26-i nl2go-default-textstyle"
|
||||
class="r25-i nl2go-default-textstyle"
|
||||
style="
|
||||
color: #3b3f44;
|
||||
font-family: georgia, serif;
|
||||
|
@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html>
|
||||
Dear {{username}},<br/>
|
||||
Your requested Issue's data has been successfully exported from Plane. The export includes all relevant information about issues you requested from your selected projects.</br>
|
||||
Please find the attachment and download the CSV file. If you have any questions or need further assistance, please don't hesitate to contact our support team at <a href = "mailto: engineering@plane.com">engineering@plane.so</a>. We're here to help!</br>
|
||||
Thank you for using Plane. We hope this export will aid you in effectively managing your projects.</br>
|
||||
Regards,
|
||||
Team Plane
|
||||
</html>
|
@ -71,7 +71,6 @@ services:
|
||||
- ENABLE_MAGIC_LINK_LOGIN=${ENABLE_MAGIC_LINK_LOGIN:-0}
|
||||
- SECRET_KEY=${SECRET_KEY:-60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5}
|
||||
- WEB_URL=$SERVICE_FQDN_PLANE_8082
|
||||
- LICENSE_ENGINE_BASE_URL=${LICENSE_ENGINE_BASE_URL:-"https://control-center.plane.so"}
|
||||
depends_on:
|
||||
- plane-db
|
||||
- plane-redis
|
||||
@ -117,7 +116,6 @@ services:
|
||||
- DEFAULT_PASSWORD=${DEFAULT_PASSWORD:-password123}
|
||||
- ENABLE_SIGNUP=${ENABLE_SIGNUP:-1}
|
||||
- SECRET_KEY=${SECRET_KEY:-60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5}
|
||||
- LICENSE_ENGINE_BASE_URL=${LICENSE_ENGINE_BASE_URL:-"https://control-center.plane.so"}
|
||||
depends_on:
|
||||
- api
|
||||
- plane-db
|
||||
@ -164,7 +162,6 @@ services:
|
||||
- DEFAULT_PASSWORD=${DEFAULT_PASSWORD:-password123}
|
||||
- ENABLE_SIGNUP=${ENABLE_SIGNUP:-1}
|
||||
- SECRET_KEY=${SECRET_KEY:-60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5}
|
||||
- LICENSE_ENGINE_BASE_URL=${LICENSE_ENGINE_BASE_URL:-"https://control-center.plane.so"}
|
||||
depends_on:
|
||||
- api
|
||||
- plane-db
|
||||
|
@ -5,15 +5,16 @@ x-app-env : &app-env
|
||||
- NGINX_PORT=${NGINX_PORT:-80}
|
||||
- WEB_URL=${WEB_URL:-http://localhost}
|
||||
- DEBUG=${DEBUG:-0}
|
||||
- NEXT_PUBLIC_ENABLE_OAUTH=${NEXT_PUBLIC_ENABLE_OAUTH:-0}
|
||||
- NEXT_PUBLIC_DEPLOY_URL=${NEXT_PUBLIC_DEPLOY_URL:-http://localhost/spaces}
|
||||
- DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE:-plane.settings.production} # deprecated
|
||||
- NEXT_PUBLIC_ENABLE_OAUTH=${NEXT_PUBLIC_ENABLE_OAUTH:-0} # deprecated
|
||||
- NEXT_PUBLIC_DEPLOY_URL=${NEXT_PUBLIC_DEPLOY_URL:-http://localhost/spaces} # deprecated
|
||||
- SENTRY_DSN=${SENTRY_DSN:-""}
|
||||
- SENTRY_ENVIRONMENT=${SENTRY_ENVIRONMENT:-"production"}
|
||||
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:-""}
|
||||
- GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID:-""}
|
||||
- GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET:-""}
|
||||
- DOCKERIZED=${DOCKERIZED:-1} # deprecated
|
||||
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-""}
|
||||
- SENTRY_ENVIRONMENT=${SENTRY_ENVIRONMENT:-"production"}
|
||||
- ADMIN_EMAIL=${ADMIN_EMAIL:-""}
|
||||
- LICENSE_ENGINE_BASE_URL=${LICENSE_ENGINE_BASE_URL:-""}
|
||||
# Gunicorn Workers
|
||||
- GUNICORN_WORKERS=${GUNICORN_WORKERS:-2}
|
||||
#DB SETTINGS
|
||||
@ -28,12 +29,12 @@ x-app-env : &app-env
|
||||
- REDIS_HOST=${REDIS_HOST:-plane-redis}
|
||||
- REDIS_PORT=${REDIS_PORT:-6379}
|
||||
- REDIS_URL=${REDIS_URL:-redis://${REDIS_HOST}:6379/}
|
||||
# EMAIL SETTINGS
|
||||
# EMAIL SETTINGS - Deprecated can be configured through admin panel
|
||||
- EMAIL_HOST=${EMAIL_HOST:-""}
|
||||
- EMAIL_HOST_USER=${EMAIL_HOST_USER:-""}
|
||||
- EMAIL_HOST_PASSWORD=${EMAIL_HOST_PASSWORD:-""}
|
||||
- EMAIL_PORT=${EMAIL_PORT:-587}
|
||||
- EMAIL_FROM=${EMAIL_FROM:-"Team Plane <team@mailer.plane.so>"}
|
||||
- EMAIL_FROM=${EMAIL_FROM:-"Team Plane <team@mailer.plane.so>"}
|
||||
- EMAIL_USE_TLS=${EMAIL_USE_TLS:-1}
|
||||
- EMAIL_USE_SSL=${EMAIL_USE_SSL:-0}
|
||||
- DEFAULT_EMAIL=${DEFAULT_EMAIL:-captain@plane.so}
|
||||
@ -42,10 +43,11 @@ x-app-env : &app-env
|
||||
- OPENAI_API_BASE=${OPENAI_API_BASE:-https://api.openai.com/v1}
|
||||
- OPENAI_API_KEY=${OPENAI_API_KEY:-"sk-"}
|
||||
- GPT_ENGINE=${GPT_ENGINE:-"gpt-3.5-turbo"}
|
||||
# LOGIN/SIGNUP SETTINGS
|
||||
# LOGIN/SIGNUP SETTINGS - Deprecated can be configured through admin panel
|
||||
- ENABLE_SIGNUP=${ENABLE_SIGNUP:-1}
|
||||
- ENABLE_EMAIL_PASSWORD=${ENABLE_EMAIL_PASSWORD:-1}
|
||||
- ENABLE_MAGIC_LINK_LOGIN=${ENABLE_MAGIC_LINK_LOGIN:-0}
|
||||
# Application secret
|
||||
- SECRET_KEY=${SECRET_KEY:-60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5}
|
||||
# DATA STORE SETTINGS
|
||||
- USE_MINIO=${USE_MINIO:-1}
|
||||
|
@ -10,10 +10,12 @@ DEBUG=0
|
||||
NEXT_PUBLIC_ENABLE_OAUTH=0
|
||||
NEXT_PUBLIC_DEPLOY_URL=http://localhost/spaces
|
||||
SENTRY_DSN=""
|
||||
SENTRY_ENVIRONMENT="production"
|
||||
GOOGLE_CLIENT_ID=""
|
||||
GITHUB_CLIENT_ID=""
|
||||
GITHUB_CLIENT_SECRET=""
|
||||
DOCKERIZED=1 # deprecated
|
||||
CORS_ALLOWED_ORIGINS="http://localhost"
|
||||
SENTRY_ENVIRONMENT="production"
|
||||
|
||||
#DB SETTINGS
|
||||
PGHOST=plane-db
|
||||
@ -34,7 +36,7 @@ EMAIL_HOST=""
|
||||
EMAIL_HOST_USER=""
|
||||
EMAIL_HOST_PASSWORD=""
|
||||
EMAIL_PORT=587
|
||||
EMAIL_FROM="Team Plane <team@mailer.plane.so>"
|
||||
EMAIL_FROM="Team Plane <team@mailer.plane.so>"
|
||||
EMAIL_USE_TLS=1
|
||||
EMAIL_USE_SSL=0
|
||||
|
||||
@ -63,9 +65,3 @@ FILE_SIZE_LIMIT=5242880
|
||||
|
||||
# Gunicorn Workers
|
||||
GUNICORN_WORKERS=2
|
||||
|
||||
# Admin Email
|
||||
ADMIN_EMAIL=""
|
||||
|
||||
# License Engine url
|
||||
LICENSE_ENGINE_BASE_URL=""
|
Loading…
Reference in New Issue
Block a user