forked from github/plane
feat: self hosted licensing initialize
This commit is contained in:
parent
1fde71c633
commit
b3868423f6
@ -1,3 +1 @@
|
|||||||
from .product import ProductEndpoint
|
|
||||||
from .checkout import CheckoutEndpoint
|
|
||||||
from .instance import InstanceEndpoint
|
from .instance import InstanceEndpoint
|
@ -1,71 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import requests
|
|
||||||
|
|
||||||
# Django imports
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
# Third party imports
|
|
||||||
from rest_framework import status
|
|
||||||
from rest_framework.response import Response
|
|
||||||
|
|
||||||
# Module imports
|
|
||||||
from plane.api.views.base import BaseAPIView
|
|
||||||
from plane.db.models import Workspace, WorkspaceMember
|
|
||||||
from plane.license.models import License
|
|
||||||
|
|
||||||
class CheckoutEndpoint(BaseAPIView):
|
|
||||||
|
|
||||||
def post(self, request, slug):
|
|
||||||
LICENSE_ENGINE_BASE_URL = os.environ.get("LICENSE_ENGINE_BASE_URL", "")
|
|
||||||
|
|
||||||
license = License.objects.first()
|
|
||||||
|
|
||||||
if license is None:
|
|
||||||
return Response({"error": "Instance is not activated"}, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
|
|
||||||
price_id = request.data.get("price_id", False)
|
|
||||||
|
|
||||||
if not price_id :
|
|
||||||
return Response(
|
|
||||||
{"error": "Price ID is required"},
|
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
|
||||||
)
|
|
||||||
|
|
||||||
workspace = Workspace.objects.get(slug=slug)
|
|
||||||
total_workspace_members = WorkspaceMember.objects.filter(workspace__slug=slug).count()
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
"user": {
|
|
||||||
"id": str(request.user.id),
|
|
||||||
"first_name": request.user.first_name,
|
|
||||||
"last_name": request.user.last_name,
|
|
||||||
"email": request.user.email,
|
|
||||||
},
|
|
||||||
"workspace": {
|
|
||||||
"id": str(workspace.id),
|
|
||||||
"name": str(workspace.name),
|
|
||||||
"slug": str(slug),
|
|
||||||
},
|
|
||||||
"priceId": price_id,
|
|
||||||
"seats": total_workspace_members,
|
|
||||||
"return_url": settings.WEB_URL,
|
|
||||||
}
|
|
||||||
|
|
||||||
headers = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"X-Api-Key": str(license.api_key),
|
|
||||||
}
|
|
||||||
|
|
||||||
response = requests.post(
|
|
||||||
f"{LICENSE_ENGINE_BASE_URL}/api/checkout/create-session",
|
|
||||||
data=json.dumps(payload),
|
|
||||||
headers=headers,
|
|
||||||
)
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
return Response(response.json(), status=status.HTTP_200_OK)
|
|
||||||
|
|
||||||
return Response({"error": "Unable to create a checkout try again later"}, status=response.status_code)
|
|
@ -2,6 +2,10 @@
|
|||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import requests
|
import requests
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
# Django imports
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@ -10,7 +14,8 @@ from rest_framework.permissions import AllowAny
|
|||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.api.views import BaseAPIView
|
from plane.api.views import BaseAPIView
|
||||||
from plane.license.models import License
|
from plane.license.models import Instance
|
||||||
|
from plane.db.models import User
|
||||||
|
|
||||||
|
|
||||||
class InstanceEndpoint(BaseAPIView):
|
class InstanceEndpoint(BaseAPIView):
|
||||||
@ -20,17 +25,35 @@ class InstanceEndpoint(BaseAPIView):
|
|||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
email = request.data.get("email", False)
|
email = request.data.get("email", False)
|
||||||
|
password = request.data.get("password", False)
|
||||||
|
|
||||||
with open("package.json", "r") as file:
|
with open("package.json", "r") as file:
|
||||||
# Load JSON content from the file
|
# Load JSON content from the file
|
||||||
data = json.load(file)
|
data = json.load(file)
|
||||||
|
|
||||||
if not email:
|
if not email or not password:
|
||||||
return Response(
|
return Response(
|
||||||
{"error": "Email is required"},
|
{"error": "Email and Password are required"},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
user = User.objects.filter(email=email).first()
|
||||||
|
if user is None:
|
||||||
|
user = User.objects.create(
|
||||||
|
email=email,
|
||||||
|
username=uuid.uuid4().hex,
|
||||||
|
)
|
||||||
|
user.set_password(password)
|
||||||
|
user.save()
|
||||||
|
else:
|
||||||
|
if not user.check_password(password):
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"error": "Sorry, we could not find a user with the provided credentials. Please try again."
|
||||||
|
},
|
||||||
|
status=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
)
|
||||||
|
|
||||||
LICENSE_ENGINE_BASE_URL = os.environ.get("LICENSE_ENGINE_BASE_URL", "")
|
LICENSE_ENGINE_BASE_URL = os.environ.get("LICENSE_ENGINE_BASE_URL", "")
|
||||||
|
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {"Content-Type": "application/json"}
|
||||||
@ -45,16 +68,18 @@ class InstanceEndpoint(BaseAPIView):
|
|||||||
|
|
||||||
if response.status_code == 201:
|
if response.status_code == 201:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
license = License.objects.create(
|
instance = Instance.objects.create(
|
||||||
instance_id=data.get("id"),
|
instance_id=data.get("id"),
|
||||||
license_key=data.get("license_key"),
|
license_key=data.get("license_key"),
|
||||||
api_key=data.get("api_key"),
|
api_key=data.get("api_key"),
|
||||||
version=data.get("version"),
|
version=data.get("version"),
|
||||||
email=data.get("email"),
|
email=data.get("email"),
|
||||||
|
user=user,
|
||||||
|
last_checked_at=timezone.now(),
|
||||||
)
|
)
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"id": str(license.instance_id),
|
"id": str(instance.instance_id),
|
||||||
"message": "Instance registered succesfully",
|
"message": "Instance registered succesfully",
|
||||||
},
|
},
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
@ -65,14 +90,14 @@ class InstanceEndpoint(BaseAPIView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
license = License.objects.first()
|
instance = Instance.objects.first()
|
||||||
|
|
||||||
if license is None:
|
if instance is None:
|
||||||
return Response({"activated": False}, status=status.HTTP_200_OK)
|
return Response({"activated": False}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"instance_id": license.instance_id,
|
"instance_id": instance.instance_id,
|
||||||
"version": license.version,
|
"version": instance.version,
|
||||||
"activated": True,
|
"activated": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
import os
|
|
||||||
import requests
|
|
||||||
|
|
||||||
# Third party imports
|
|
||||||
from rest_framework import status
|
|
||||||
from rest_framework.response import Response
|
|
||||||
|
|
||||||
# Module imports
|
|
||||||
from plane.api.views.base import BaseAPIView
|
|
||||||
from plane.license.models import License
|
|
||||||
|
|
||||||
|
|
||||||
class ProductEndpoint(BaseAPIView):
|
|
||||||
def get(self, request, slug):
|
|
||||||
LICENSE_ENGINE_BASE_URL = os.environ.get("LICENSE_ENGINE_BASE_URL", "")
|
|
||||||
|
|
||||||
license = License.objects.first()
|
|
||||||
|
|
||||||
if license is None:
|
|
||||||
return Response(
|
|
||||||
{"error": "Instance is not activated"},
|
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
|
||||||
)
|
|
||||||
|
|
||||||
# # Request the licensing engine
|
|
||||||
|
|
||||||
response = requests.get(
|
|
||||||
f"{LICENSE_ENGINE_BASE_URL}/api/products",
|
|
||||||
headers={
|
|
||||||
"X-Api-Key": license.api_key,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if response.status_code == 200:
|
|
||||||
return Response(response.json(), status=status.HTTP_200_OK)
|
|
||||||
return Response(
|
|
||||||
{"error": "Unable to fetch products"}, status=response.status_code
|
|
||||||
)
|
|
@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 4.2.3 on 2023-10-26 14:14
|
# Generated by Django 4.2.3 on 2023-10-30 11:26
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
@ -16,22 +16,24 @@ class Migration(migrations.Migration):
|
|||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='License',
|
name='Instance',
|
||||||
fields=[
|
fields=[
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
|
||||||
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||||
('instance_id', models.CharField(max_length=12, unique=True)),
|
('instance_id', models.CharField(max_length=25, unique=True)),
|
||||||
('license_key', models.CharField(max_length=64)),
|
('license_key', models.CharField(max_length=256)),
|
||||||
('api_key', models.CharField(max_length=16)),
|
('api_key', models.CharField(max_length=16)),
|
||||||
('version', models.CharField(max_length=10)),
|
('version', models.CharField(max_length=10)),
|
||||||
|
('email', models.CharField(max_length=256)),
|
||||||
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
|
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
|
||||||
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
|
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
|
||||||
|
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='instance_owner', to=settings.AUTH_USER_MODEL)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'License',
|
'verbose_name': 'Instance',
|
||||||
'verbose_name_plural': 'Licenses',
|
'verbose_name_plural': 'Instances',
|
||||||
'db_table': 'licenses',
|
'db_table': 'instances',
|
||||||
'ordering': ('-created_at',),
|
'ordering': ('-created_at',),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 4.2.3 on 2023-10-26 15:22
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('license', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='license',
|
|
||||||
name='email',
|
|
||||||
field=models.CharField(default='email@plane.so', max_length=256),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 4.2.3 on 2023-10-26 15:24
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('license', '0002_license_email'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='license',
|
|
||||||
name='license_key',
|
|
||||||
field=models.CharField(max_length=256),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 4.2.3 on 2023-10-26 15:26
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('license', '0003_alter_license_license_key'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='license',
|
|
||||||
name='instance_id',
|
|
||||||
field=models.CharField(max_length=25, unique=True),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1 +1 @@
|
|||||||
from .license import License
|
from .instance import Instance
|
@ -1,19 +1,27 @@
|
|||||||
# Django imports
|
# Django imports
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.db.models import BaseModel
|
from plane.db.models import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class License(BaseModel):
|
class Instance(BaseModel):
|
||||||
instance_id = models.CharField(max_length=25, unique=True)
|
instance_id = models.CharField(max_length=25, unique=True)
|
||||||
license_key = models.CharField(max_length=256)
|
license_key = models.CharField(max_length=256)
|
||||||
api_key = models.CharField(max_length=16)
|
api_key = models.CharField(max_length=16)
|
||||||
version = models.CharField(max_length=10)
|
version = models.CharField(max_length=10)
|
||||||
email = models.CharField(max_length=256)
|
email = models.CharField(max_length=256)
|
||||||
|
user = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
related_name="instance_owner",
|
||||||
|
)
|
||||||
|
last_checked_at = models.DateTimeField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "License"
|
verbose_name = "Instance"
|
||||||
verbose_name_plural = "Licenses"
|
verbose_name_plural = "Instances"
|
||||||
db_table = "licenses"
|
db_table = "instances"
|
||||||
ordering = ("-created_at",)
|
ordering = ("-created_at",)
|
@ -1,18 +1,8 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from plane.license.api.views import ProductEndpoint, CheckoutEndpoint, InstanceEndpoint
|
from plane.license.api.views import InstanceEndpoint
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(
|
|
||||||
"workspaces/<str:slug>/products/",
|
|
||||||
ProductEndpoint.as_view(),
|
|
||||||
name="products",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"workspaces/<str:slug>/create-checkout-session/",
|
|
||||||
CheckoutEndpoint.as_view(),
|
|
||||||
name="checkout",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"instances/",
|
"instances/",
|
||||||
InstanceEndpoint.as_view(),
|
InstanceEndpoint.as_view(),
|
||||||
|
Loading…
Reference in New Issue
Block a user