diff --git a/apiserver/plane/app/views/project/base.py b/apiserver/plane/app/views/project/base.py index 2b9d65e10f7..00e1ad1eb13 100644 --- a/apiserver/plane/app/views/project/base.py +++ b/apiserver/plane/app/views/project/base.py @@ -177,6 +177,7 @@ def list(self, request, slug): "module_view", "page_view", "inbox_view", + "guest_view_all_features", "project_lead", "created_at", "updated_at", diff --git a/packages/types/src/project/projects.d.ts b/packages/types/src/project/projects.d.ts index b04b9828468..40562d362d5 100644 --- a/packages/types/src/project/projects.d.ts +++ b/packages/types/src/project/projects.d.ts @@ -25,6 +25,7 @@ export interface IPartialProject { module_view: boolean; page_view: boolean; inbox_view: boolean; + guest_view_all_features?: boolean; project_lead?: IUserLite | string | null; // Timestamps created_at?: Date; @@ -46,11 +47,8 @@ export interface IProject extends IPartialProject { default_state?: string | null; description?: string; estimate?: string | null; - guest_view_all_features?: boolean; anchor?: string | null; is_favorite?: boolean; - is_issue_type_enabled?: boolean; - is_time_tracking_enabled?: boolean; members?: string[]; network?: number; timezone?: string; diff --git a/web/core/local-db/storage.sqlite.ts b/web/core/local-db/storage.sqlite.ts index fb351add64b..e0395151527 100644 --- a/web/core/local-db/storage.sqlite.ts +++ b/web/core/local-db/storage.sqlite.ts @@ -2,7 +2,7 @@ import * as Comlink from "comlink"; import set from "lodash/set"; // plane import { EIssueGroupBYServerToProperty } from "@plane/constants"; -import { TIssue } from "@plane/types"; +import { TIssue, TIssueParams } from "@plane/types"; // lib import { rootStore } from "@/lib/store-context"; // services @@ -15,6 +15,7 @@ import { addIssuesBulk, syncDeletesToLocal } from "./utils/load-issues"; import { loadWorkSpaceData } from "./utils/load-workspace"; import { issueFilterCountQueryConstructor, issueFilterQueryConstructor } from "./utils/query-constructor"; import { runQuery } from "./utils/query-executor"; +import { sanitizeWorkItemQueries } from "./utils/query-sanitizer.ts"; import { createTables } from "./utils/tables"; import { clearOPFS, getGroupedIssueResults, getSubGroupedIssueResults, log, logError } from "./utils/utils"; @@ -269,7 +270,12 @@ export class Storage { return issue.updated_at; }; - getIssues = async (workspaceSlug: string, projectId: string, queries: any, config: any) => { + getIssues = async ( + workspaceSlug: string, + projectId: string, + queries: Partial> | undefined, + config: any + ) => { log("#### Queries", queries); const currentProjectStatus = this.getStatus(projectId); @@ -294,11 +300,12 @@ export class Storage { } } - const { cursor, group_by, sub_group_by } = queries; + const sanitizedQueries = sanitizeWorkItemQueries(workspaceSlug, projectId, queries); + const { cursor, group_by, sub_group_by } = sanitizedQueries || {}; - const query = issueFilterQueryConstructor(this.workspaceSlug, projectId, queries); + const query = issueFilterQueryConstructor(this.workspaceSlug, projectId, sanitizedQueries); log("#### Query", query); - const countQuery = issueFilterCountQueryConstructor(this.workspaceSlug, projectId, queries); + const countQuery = issueFilterCountQueryConstructor(this.workspaceSlug, projectId, sanitizedQueries); const start = performance.now(); let issuesRaw: any[] = []; let count: any[]; @@ -313,7 +320,7 @@ export class Storage { const { total_count } = count[0]; - const [pageSize, page, offset] = cursor.split(":"); + const [pageSize, page, offset] = cursor && typeof cursor === "string" ? cursor.split(":") : []; const groupByProperty: string = EIssueGroupBYServerToProperty[group_by as keyof typeof EIssueGroupBYServerToProperty]; diff --git a/web/core/local-db/utils/query-sanitizer.ts.ts b/web/core/local-db/utils/query-sanitizer.ts.ts new file mode 100644 index 00000000000..39ee122b850 --- /dev/null +++ b/web/core/local-db/utils/query-sanitizer.ts.ts @@ -0,0 +1,41 @@ +// plane constants +import { EUserPermissions } from "@plane/constants"; +import { TIssueParams } from "@plane/types"; +// root store +import { rootStore } from "@/lib/store-context"; + +export const sanitizeWorkItemQueries = ( + workspaceSlug: string, + projectId: string, + queries: Partial> | undefined +): Partial> | undefined => { + // Get current project details and user id and role for the project + const currentProject = rootStore.projectRoot.project.getProjectById(projectId); + const currentUserId = rootStore.user.data?.id; + const currentUserRole = rootStore.user.permission.projectPermissionsByWorkspaceSlugAndProjectId( + workspaceSlug, + projectId + ); + + // Only apply this restriction for guests when guest_view_all_features is disabled + if ( + currentUserId && + currentUserRole === EUserPermissions.GUEST && + currentProject?.guest_view_all_features === false + ) { + // Sanitize the created_by filter if it doesn't exist or if it exists and includes the current user id + const existingCreatedByFilter = queries?.created_by; + const shouldApplyFilter = + !existingCreatedByFilter || + (typeof existingCreatedByFilter === "string" && existingCreatedByFilter.includes(currentUserId)); + + if (shouldApplyFilter) { + queries = { + ...queries, + created_by: currentUserId, + }; + } + } + + return queries; +}; diff --git a/web/core/services/issue/issue.service.ts b/web/core/services/issue/issue.service.ts index 203b319c7d3..ccd240bfab6 100644 --- a/web/core/services/issue/issue.service.ts +++ b/web/core/services/issue/issue.service.ts @@ -1,6 +1,7 @@ import { EIssueServiceType } from "@plane/constants"; // types import { + TIssueParams, type IIssueDisplayProperties, type TBulkOperationsPayload, type TIssue, @@ -75,7 +76,12 @@ export class IssueService extends APIService { }); } - async getIssues(workspaceSlug: string, projectId: string, queries?: any, config = {}): Promise { + async getIssues( + workspaceSlug: string, + projectId: string, + queries?: Partial>, + config = {} + ): Promise { if (getIssuesShouldFallbackToServer(queries) || this.serviceType !== EIssueServiceType.ISSUES) { return await this.getIssuesFromServer(workspaceSlug, projectId, queries, config); }