Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions apiserver/plane/app/views/project/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ def list(self, request, slug):
"inbox_view",
"guest_view_all_features",
"project_lead",
"network",
"created_at",
"updated_at",
"created_by",
Expand Down
26 changes: 19 additions & 7 deletions apiserver/plane/app/views/project/invite.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@
# Module imports
from .base import BaseViewSet, BaseAPIView
from plane.app.serializers import ProjectMemberInviteSerializer

from plane.app.permissions import allow_permission, ROLE

from plane.db.models import (
ProjectMember,
Workspace,
ProjectMemberInvite,
User,
WorkspaceMember,
Project,
IssueUserProperty,
)
from plane.db.models.project import ProjectNetwork


class ProjectInvitationsViewset(BaseViewSet):
Expand Down Expand Up @@ -128,6 +128,7 @@ def get_queryset(self):
.select_related("workspace", "workspace__owner", "project")
)

@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
def create(self, request, slug):
project_ids = request.data.get("project_ids", [])

Expand All @@ -136,11 +137,22 @@ def create(self, request, slug):
member=request.user, workspace__slug=slug, is_active=True
)

if workspace_member.role not in [ROLE.ADMIN.value, ROLE.MEMBER.value]:
return Response(
{"error": "You do not have permission to join the project"},
status=status.HTTP_403_FORBIDDEN,
)
# Get all the projects
projects = (
Project.objects.filter(id__in=project_ids, workspace__slug=slug)
.only("id", "network")
)

# Check if user has permission to join each project
for project in projects:
if (
project.network == ProjectNetwork.SECRET
and workspace_member.role != ROLE.ADMIN.value
):
return Response(
{"error": "Only workspace admins can join private project"},
status=status.HTTP_403_FORBIDDEN,
)

workspace_role = workspace_member.role
workspace = workspace_member.workspace
Expand Down
22 changes: 22 additions & 0 deletions apiserver/plane/db/models/project.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Python imports
import pytz
from uuid import uuid4
from enum import Enum

# Django imports
from django.conf import settings
Expand All @@ -17,6 +18,15 @@
ROLE_CHOICES = ((20, "Admin"), (15, "Member"), (5, "Guest"))


class ProjectNetwork(Enum):
SECRET = 0
PUBLIC = 2

@classmethod
def choices(cls):
return [(0, "Secret"), (2, "Public")]


def get_default_props():
return {
"filters": {
Expand Down Expand Up @@ -113,6 +123,18 @@ class Project(BaseModel):
TIMEZONE_CHOICES = tuple(zip(pytz.all_timezones, pytz.all_timezones))
timezone = models.CharField(max_length=255, default="UTC", choices=TIMEZONE_CHOICES)

@property
def is_secret(self) -> bool:
return self.network == ProjectNetwork.SECRET

@property
def is_public(self) -> bool:
return self.network == ProjectNetwork.PUBLIC

@property
def network_type(self) -> ProjectNetwork:
return ProjectNetwork(self.network)

@property
def cover_image_url(self):
# Return cover image url
Expand Down
6 changes: 6 additions & 0 deletions packages/types/src/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ export enum EUserPermissions {

export type TUserPermissions = EUserPermissions.ADMIN | EUserPermissions.MEMBER | EUserPermissions.GUEST;

// project network
export enum EProjectNetwork {
PRIVATE = 0,
PUBLIC = 2,
}

// project pages
export enum EPageAccess {
PUBLIC = 0,
Expand Down
2 changes: 1 addition & 1 deletion packages/types/src/project/projects.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface IPartialProject {
inbox_view: boolean;
guest_view_all_features?: boolean;
project_lead?: IUserLite | string | null;
network?: number;
// Timestamps
created_at?: Date;
updated_at?: Date;
Expand All @@ -50,7 +51,6 @@ export interface IProject extends IPartialProject {
anchor?: string | null;
is_favorite?: boolean;
members?: string[];
network?: number;
timezone?: string;
}

Expand Down
15 changes: 13 additions & 2 deletions web/core/layouts/auth-layout/project-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import useSWR from "swr";
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
// components
import { EProjectNetwork } from "@plane/types/src/enums";
import { JoinProject } from "@/components/auth-screens";
import { LogoSpinner } from "@/components/common";
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
Expand Down Expand Up @@ -70,6 +71,11 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
workspaceSlug.toString(),
projectId?.toString()
);
const isWorkspaceAdmin = allowPermissions(
[EUserPermissions.ADMIN],
EUserPermissionsLevel.WORKSPACE,
workspaceSlug.toString()
);

// Initialize module timeline chart
useEffect(() => {
Expand Down Expand Up @@ -168,10 +174,15 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
);

// check if the user don't have permission to access the project
if (projectExists && projectId && hasPermissionToCurrentProject === false) return <JoinProject />;
if (
((projectExists?.network && projectExists?.network !== EProjectNetwork.PRIVATE) || isWorkspaceAdmin) &&
projectId &&
hasPermissionToCurrentProject === false
)
return <JoinProject />;

// check if the project info is not found.
if (loader === "loaded" && !projectExists && projectId && !!hasPermissionToCurrentProject === false)
if (loader === "loaded" && projectId && !!hasPermissionToCurrentProject === false)
return (
<div className="grid h-screen place-items-center bg-custom-background-100">
<DetailedEmptyState
Expand Down