Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.git
*.pyc
.env
venv
node_modules
npm-debug.log
24 changes: 23 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,26 @@ yarn-error.log*
.vercel

# Turborepo
.turbo
.turbo

## Django ##
venv
*.pyc
staticfiles
mediafiles
.env
.DS_Store

node_modules/
assets/dist/
npm-debug.log
yarn-error.log

# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
package-lock.json
.vscode
57 changes: 57 additions & 0 deletions apiserver/Dockerfile.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
FROM python:3.8.14-alpine3.16 AS backend

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV PIP_DISABLE_PIP_VERSION_CHECK=1

WORKDIR /code

RUN apk --update --no-cache add \
"libpq~=14" \
"libxslt~=1.1" \
"nodejs-current~=18" \
"xmlsec~=1.2"

COPY requirements.txt ./
COPY requirements ./requirements
RUN apk add libffi-dev
RUN apk --update --no-cache --virtual .build-deps add \
"bash~=5.1" \
"g++~=11.2" \
"gcc~=11.2" \
"cargo~=1.60" \
"git~=2" \
"make~=4.3" \
"postgresql13-dev~=13" \
"libc-dev" \
"linux-headers" \
&& \
pip install -r requirements.txt --compile --no-cache-dir \
&& \
apk del .build-deps


RUN addgroup -S plane && \
adduser -S captain -G plane

RUN chown captain.plane /code

USER captain

# Add in Django deps and generate Django's static files
COPY manage.py manage.py
COPY plane plane/
COPY templates templates/

COPY gunicorn.config.py ./
USER root
RUN apk --update --no-cache add "bash~=5.1"
COPY ./bin ./bin/
USER captain

# Expose container port and run entry point script
EXPOSE 8000

CMD [ "./bin/takeoff" ]

6 changes: 6 additions & 0 deletions apiserver/bin/takeoff
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash
set -e

python manage.py migrate
python manage.py rqworker &
exec gunicorn plane.wsgi -k gthread --workers 8 --bind 0.0.0.0:8000 --config gunicorn.config.py --max-requests 10000 --max-requests-jitter 1000 --access-logfile -
6 changes: 6 additions & 0 deletions apiserver/gunicorn.config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from psycogreen.gevent import patch_psycopg


def post_fork(server, worker):
patch_psycopg()
worker.log.info("Made Psycopg2 Green")
17 changes: 17 additions & 0 deletions apiserver/manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env python
import os
import sys

if __name__ == '__main__':
os.environ.setdefault(
'DJANGO_SETTINGS_MODULE',
'plane.settings.production')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
Empty file added apiserver/plane/__init__.py
Empty file.
Empty file.
5 changes: 5 additions & 0 deletions apiserver/plane/analytics/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class AnalyticsConfig(AppConfig):
name = 'plane.analytics'
Empty file added apiserver/plane/api/__init__.py
Empty file.
5 changes: 5 additions & 0 deletions apiserver/plane/api/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class ApiConfig(AppConfig):
name = "plane.api"
2 changes: 2 additions & 0 deletions apiserver/plane/api/permissions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .workspace import WorkSpaceBasePermission, WorkSpaceAdminPermission
from .project import ProjectBasePermission, ProjectEntityPermission, ProjectMemberPermission
63 changes: 63 additions & 0 deletions apiserver/plane/api/permissions/project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Third Party imports
from rest_framework.permissions import BasePermission, SAFE_METHODS

# Module import
from plane.db.models import WorkspaceMember, ProjectMember


class ProjectBasePermission(BasePermission):
def has_permission(self, request, view):

if request.user.is_anonymous:
return False

## Safe Methods -> Handle the filtering logic in queryset
if request.method in SAFE_METHODS:
return True
## Only workspace owners or admins can create the projects
if request.method == "POST":
return WorkspaceMember.objects.filter(
workspace=view.workspace, member=request.user, role__in=[15, 20]
).exists()

## Only Project Admins can update project attributes
return ProjectMember.objects.filter(
workspace=view.workspace, member=request.user, role=20
).exists()


class ProjectMemberPermission(BasePermission):
def has_permission(self, request, view):

if request.user.is_anonymous:
return False

## Safe Methods -> Handle the filtering logic in queryset
if request.method in SAFE_METHODS:
return True
## Only workspace owners or admins can create the projects
if request.method == "POST":
return WorkspaceMember.objects.filter(
workspace=view.workspace, member=request.user, role__in=[15, 20]
).exists()

## Only Project Admins can update project attributes
return ProjectMember.objects.filter(
workspace=view.workspace, member=request.user, role__in=[15, 20]
).exists()


class ProjectEntityPermission(BasePermission):
def has_permission(self, request, view):

if request.user.is_anonymous:
return False

## Safe Methods -> Handle the filtering logic in queryset
if request.method in SAFE_METHODS:
return True
## Only workspace owners or admins can create the projects

return ProjectMember.objects.filter(
workspace=view.workspace, member=request.user, role__in=[15, 20]
).exists()
43 changes: 43 additions & 0 deletions apiserver/plane/api/permissions/workspace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Third Party imports
from rest_framework.permissions import BasePermission, SAFE_METHODS

# Module imports
from plane.db.models import WorkspaceMember, ProjectMember


# TODO: Move the below logic to python match - python v3.10
class WorkSpaceBasePermission(BasePermission):
def has_permission(self, request, view):
# allow anyone to create a workspace
if request.user.is_anonymous:
return False

if request.method == "POST":
return True

## Safe Methods
if request.method in SAFE_METHODS:
return True

# allow only admins and owners to update the workspace settings
if request.method in ["PUT", "PATCH"]:
return WorkspaceMember.objects.filter(
member=request.user, workspace=view.workspace, role__in=[15, 20]
).exists()

# allow only owner to delete the workspace
if request.method == "DELETE":
return WorkspaceMember.objects.filter(
member=request.user, workspace=view.workspace, role=20
).exists()


class WorkSpaceAdminPermission(BasePermission):
def has_permission(self, request, view):

if request.user.is_anonymous:
return False

return WorkspaceMember.objects.filter(
member=request.user, workspace=view.workspace, role__in=[15, 20]
).exists()
40 changes: 40 additions & 0 deletions apiserver/plane/api/serializers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from .base import BaseSerializer
from .people import (
ChangePasswordSerializer,
ResetPasswordSerializer,
TokenSerializer,
)
from .user import UserSerializer, UserLiteSerializer
from .workspace import (
WorkSpaceSerializer,
WorkSpaceMemberSerializer,
TeamSerializer,
WorkSpaceMemberInviteSerializer,
)
from .project import (
ProjectSerializer,
ProjectDetailSerializer,
ProjectMemberSerializer,
ProjectMemberInviteSerializer,
ProjectIdentifierSerializer,
)
from .state import StateSerializer
from .shortcut import ShortCutSerializer
from .view import ViewSerializer
from .cycle import CycleSerializer, CycleIssueSerializer
from .asset import FileAssetSerializer
from .issue import (
IssueCreateSerializer,
IssueActivitySerializer,
IssueCommentSerializer,
TimeLineIssueSerializer,
IssuePropertySerializer,
IssueLabelSerializer,
BlockerIssueSerializer,
BlockedIssueSerializer,
IssueAssigneeSerializer,
LabelSerializer,
IssueSerializer,
IssueFlatSerializer,
IssueStateSerializer,
)
14 changes: 14 additions & 0 deletions apiserver/plane/api/serializers/asset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from .base import BaseSerializer
from plane.db.models import FileAsset


class FileAssetSerializer(BaseSerializer):
class Meta:
model = FileAsset
fields = "__all__"
read_only_fields = [
"created_by",
"updated_by",
"created_at",
"updated_at",
]
5 changes: 5 additions & 0 deletions apiserver/plane/api/serializers/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from rest_framework import serializers


class BaseSerializer(serializers.ModelSerializer):
id = serializers.PrimaryKeyRelatedField(read_only=True)
33 changes: 33 additions & 0 deletions apiserver/plane/api/serializers/cycle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Module imports
from .base import BaseSerializer
from .user import UserLiteSerializer
from .issue import IssueStateSerializer
from plane.db.models import Cycle, CycleIssue


class CycleSerializer(BaseSerializer):

owned_by = UserLiteSerializer(read_only=True)

class Meta:
model = Cycle
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"owned_by",
]


class CycleIssueSerializer(BaseSerializer):

issue_details = IssueStateSerializer(read_only=True, source="issue")

class Meta:
model = CycleIssue
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"cycle",
]
Loading