forked from github/plane
dev: initiate licensing
This commit is contained in:
parent
3bfbc3a132
commit
5b2a1dda72
4
apiserver/package.json
Normal file
4
apiserver/package.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "plane-api",
|
||||||
|
"version": "0.13.2"
|
||||||
|
}
|
@ -1,21 +0,0 @@
|
|||||||
# Generated by Django 4.2.5 on 2023-10-18 12:04
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
import plane.db.models.issue
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('db', '0045_issueactivity_epoch_workspacemember_issue_props_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='issueproperty',
|
|
||||||
name='properties',
|
|
||||||
field=models.JSONField(default=plane.db.models.issue.get_default_properties),
|
|
||||||
),
|
|
||||||
]
|
|
0
apiserver/plane/license/__init__.py
Normal file
0
apiserver/plane/license/__init__.py
Normal file
0
apiserver/plane/license/api/__init__.py
Normal file
0
apiserver/plane/license/api/__init__.py
Normal file
3
apiserver/plane/license/api/views/__init__.py
Normal file
3
apiserver/plane/license/api/views/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from .product import ProductEndpoint
|
||||||
|
from .checkout import CheckoutEndpoint
|
||||||
|
from .instance import InstanceEndpoint
|
71
apiserver/plane/license/api/views/checkout.py
Normal file
71
apiserver/plane/license/api/views/checkout.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# 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)
|
79
apiserver/plane/license/api/views/instance.py
Normal file
79
apiserver/plane/license/api/views/instance.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# Python imports
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# Third party imports
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
|
|
||||||
|
# Module imports
|
||||||
|
from plane.api.views import BaseAPIView
|
||||||
|
from plane.license.models import License
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceEndpoint(BaseAPIView):
|
||||||
|
permission_classes = [
|
||||||
|
AllowAny,
|
||||||
|
]
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
email = request.data.get("email", False)
|
||||||
|
|
||||||
|
with open("package.json", "r") as file:
|
||||||
|
# Load JSON content from the file
|
||||||
|
data = json.load(file)
|
||||||
|
|
||||||
|
if not email:
|
||||||
|
return Response(
|
||||||
|
{"error": "Email is required"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
LICENSE_ENGINE_BASE_URL = os.environ.get("LICENSE_ENGINE_BASE_URL", "")
|
||||||
|
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
|
||||||
|
payload = {"email": email, "version": data.get("version", 0.1)}
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
f"{LICENSE_ENGINE_BASE_URL}/api/instances",
|
||||||
|
headers=headers,
|
||||||
|
data=json.dumps(payload),
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 201:
|
||||||
|
data = response.json()
|
||||||
|
license = License.objects.create(
|
||||||
|
instance_id=data.get("id"),
|
||||||
|
license_key=data.get("license_key"),
|
||||||
|
api_key=data.get("api_key"),
|
||||||
|
version=data.get("version"),
|
||||||
|
email=data.get("email"),
|
||||||
|
)
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"id": str(license.instance_id),
|
||||||
|
"message": "Instance registered succesfully",
|
||||||
|
},
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
{"error": "Unable to create instance"}, status=response.status_code
|
||||||
|
)
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
license = License.objects.first()
|
||||||
|
|
||||||
|
if license is None:
|
||||||
|
return Response({"activated": False}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"instance_id": license.instance_id,
|
||||||
|
"version": license.version,
|
||||||
|
"activated": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response(data, status=status.HTTP_200_OK)
|
38
apiserver/plane/license/api/views/product.py
Normal file
38
apiserver/plane/license/api/views/product.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# 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
|
||||||
|
)
|
5
apiserver/plane/license/apps.py
Normal file
5
apiserver/plane/license/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class LicenseConfig(AppConfig):
|
||||||
|
name = "plane.license"
|
38
apiserver/plane/license/migrations/0001_initial.py
Normal file
38
apiserver/plane/license/migrations/0001_initial.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Generated by Django 4.2.3 on 2023-10-26 14:14
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='License',
|
||||||
|
fields=[
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created 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)),
|
||||||
|
('instance_id', models.CharField(max_length=12, unique=True)),
|
||||||
|
('license_key', models.CharField(max_length=64)),
|
||||||
|
('api_key', models.CharField(max_length=16)),
|
||||||
|
('version', models.CharField(max_length=10)),
|
||||||
|
('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')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'License',
|
||||||
|
'verbose_name_plural': 'Licenses',
|
||||||
|
'db_table': 'licenses',
|
||||||
|
'ordering': ('-created_at',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
19
apiserver/plane/license/migrations/0002_license_email.py
Normal file
19
apiserver/plane/license/migrations/0002_license_email.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# 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,
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,18 @@
|
|||||||
|
# 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),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,18 @@
|
|||||||
|
# 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),
|
||||||
|
),
|
||||||
|
]
|
0
apiserver/plane/license/migrations/__init__.py
Normal file
0
apiserver/plane/license/migrations/__init__.py
Normal file
1
apiserver/plane/license/models/__init__.py
Normal file
1
apiserver/plane/license/models/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .license import License
|
19
apiserver/plane/license/models/license.py
Normal file
19
apiserver/plane/license/models/license.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Django imports
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Module imports
|
||||||
|
from plane.db.models import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class License(BaseModel):
|
||||||
|
instance_id = models.CharField(max_length=25, unique=True)
|
||||||
|
license_key = models.CharField(max_length=256)
|
||||||
|
api_key = models.CharField(max_length=16)
|
||||||
|
version = models.CharField(max_length=10)
|
||||||
|
email = models.CharField(max_length=256)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "License"
|
||||||
|
verbose_name_plural = "Licenses"
|
||||||
|
db_table = "licenses"
|
||||||
|
ordering = ("-created_at",)
|
21
apiserver/plane/license/urls.py
Normal file
21
apiserver/plane/license/urls.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from plane.license.api.views import ProductEndpoint, CheckoutEndpoint, InstanceEndpoint
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/products/",
|
||||||
|
ProductEndpoint.as_view(),
|
||||||
|
name="products",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/create-checkout-session/",
|
||||||
|
CheckoutEndpoint.as_view(),
|
||||||
|
name="checkout",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"instances/",
|
||||||
|
InstanceEndpoint.as_view(),
|
||||||
|
name="instance",
|
||||||
|
),
|
||||||
|
]
|
@ -29,6 +29,7 @@ INSTALLED_APPS = [
|
|||||||
"plane.utils",
|
"plane.utils",
|
||||||
"plane.web",
|
"plane.web",
|
||||||
"plane.middleware",
|
"plane.middleware",
|
||||||
|
"plane.license",
|
||||||
# Third-party things
|
# Third-party things
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
"rest_framework.authtoken",
|
"rest_framework.authtoken",
|
||||||
|
@ -14,6 +14,7 @@ urlpatterns = [
|
|||||||
# path("admin/", admin.site.urls),
|
# path("admin/", admin.site.urls),
|
||||||
path("", TemplateView.as_view(template_name="index.html")),
|
path("", TemplateView.as_view(template_name="index.html")),
|
||||||
path("api/", include("plane.api.urls")),
|
path("api/", include("plane.api.urls")),
|
||||||
|
path("api/licenses/", include("plane.license.urls")),
|
||||||
path("", include("plane.web.urls")),
|
path("", include("plane.web.urls")),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user