feat: self hosted licensing initialize

This commit is contained in:
pablohashescobar 2023-10-30 17:01:30 +05:30
parent 1fde71c633
commit b3868423f6
11 changed files with 58 additions and 199 deletions

View File

@ -1,3 +1 @@
from .product import ProductEndpoint
from .checkout import CheckoutEndpoint
from .instance import InstanceEndpoint from .instance import InstanceEndpoint

View File

@ -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)

View File

@ -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,
} }

View File

@ -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
)

View File

@ -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',),
}, },
), ),

View File

@ -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,
),
]

View File

@ -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),
),
]

View File

@ -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),
),
]

View File

@ -1 +1 @@
from .license import License from .instance import Instance

View File

@ -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",)

View File

@ -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(),