Row level permissions - POC 1 (#16599)

## Context
This PR adds the core structure for RLS implementation:
- RLS data model
- RLS service layer
- RLS WorkspaceMigration and Syncable Entity + cache + Validations
- RLS resolver layer
- ORM layer with RLS Predicate to ORM WHERE clause conversion with
workspaceMember record transposition

Tests are missing though

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Establishes core row-level permissions infrastructure and enforcement
across the stack.
> 
> - Backend: new `rowLevelPermissionPredicate` and
`rowLevelPermissionPredicateGroup` entities, TypeORM migration, feature
flag `IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED`, flat-entity
maps/cache wiring, services and GraphQL resolvers for CRUD, and
inclusion of `workspaceMember` in auth context
> - ORM: applies row-level permission predicates to SELECT, DELETE, and
SOFT DELETE query builders; propagates context through
GlobalWorkspaceOrmManager/EntityManager
> - GraphQL: generated schema/types/queries/mutations for
creating/updating/deleting/fetching predicates and groups
> - Frontend: settings page adds a gated "Record-level" section
(placeholder) and metadata error handler labels for new entities
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
fe955cc4588a92157afa6795fb574189a4be1e93. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
Weiko
2025-12-31 16:15:17 +01:00
committed by GitHub
parent 888a50c3be
commit 15b21570ae
120 changed files with 5649 additions and 29 deletions
@@ -943,6 +943,26 @@ export type CreateRouteTriggerInput = {
serverlessFunctionId: Scalars['String'];
};
export type CreateRowLevelPermissionPredicateGroupInput = {
logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator;
parentRowLevelPermissionPredicateGroupId?: InputMaybe<Scalars['String']>;
positionInRowLevelPermissionPredicateGroup?: InputMaybe<Scalars['Float']>;
roleId: Scalars['String'];
};
export type CreateRowLevelPermissionPredicateInput = {
fieldMetadataId: Scalars['String'];
objectMetadataId: Scalars['String'];
operand: RowLevelPermissionPredicateOperand;
positionInRowLevelPermissionPredicateGroup?: InputMaybe<Scalars['Float']>;
roleId: Scalars['String'];
rowLevelPermissionPredicateGroupId?: InputMaybe<Scalars['String']>;
subFieldName?: InputMaybe<Scalars['String']>;
value?: InputMaybe<Scalars['JSON']>;
workspaceMemberFieldMetadataId?: InputMaybe<Scalars['String']>;
workspaceMemberSubFieldName?: InputMaybe<Scalars['String']>;
};
export type CreateServerlessFunctionInput = {
code?: InputMaybe<Scalars['JSON']>;
description?: InputMaybe<Scalars['String']>;
@@ -1127,6 +1147,14 @@ export type DeleteOneObjectInput = {
id: Scalars['UUID'];
};
export type DeleteRowLevelPermissionPredicateGroupInput = {
id: Scalars['String'];
};
export type DeleteRowLevelPermissionPredicateInput = {
id: Scalars['String'];
};
export type DeleteSsoInput = {
identityProviderId: Scalars['UUID'];
};
@@ -1321,6 +1349,7 @@ export enum FeatureFlagKey {
IS_POSTGRESQL_INTEGRATION_ENABLED = 'IS_POSTGRESQL_INTEGRATION_ENABLED',
IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED',
IS_RECORD_PAGE_LAYOUT_ENABLED = 'IS_RECORD_PAGE_LAYOUT_ENABLED',
IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED = 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED',
IS_STRIPE_INTEGRATION_ENABLED = 'IS_STRIPE_INTEGRATION_ENABLED',
IS_TIMELINE_ACTIVITY_MIGRATED = 'IS_TIMELINE_ACTIVITY_MIGRATED',
IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED',
@@ -1822,6 +1851,8 @@ export type Mutation = {
createPageLayoutTab: PageLayoutTab;
createPageLayoutWidget: PageLayoutWidget;
createPublicDomain: PublicDomain;
createRowLevelPermissionPredicate: RowLevelPermissionPredicate;
createRowLevelPermissionPredicateGroup: RowLevelPermissionPredicateGroup;
createSAMLIdentityProvider: SetupSsoOutput;
createWebhook: Webhook;
createWorkflowVersionEdge: WorkflowVersionStepChanges;
@@ -1852,6 +1883,8 @@ export type Mutation = {
deletePageLayoutTab: Scalars['Boolean'];
deletePageLayoutWidget: PageLayoutWidget;
deletePublicDomain: Scalars['Boolean'];
deleteRowLevelPermissionPredicate: RowLevelPermissionPredicate;
deleteRowLevelPermissionPredicateGroup: RowLevelPermissionPredicateGroup;
deleteSSOIdentityProvider: DeleteSsoOutput;
deleteTwoFactorAuthenticationMethod: DeleteTwoFactorAuthenticationMethodOutput;
deleteUser: User;
@@ -1946,6 +1979,8 @@ export type Mutation = {
updatePageLayoutWidget: PageLayoutWidget;
updatePageLayoutWithTabsAndWidgets: PageLayout;
updatePasswordViaResetToken: InvalidatePasswordOutput;
updateRowLevelPermissionPredicate: RowLevelPermissionPredicate;
updateRowLevelPermissionPredicateGroup: RowLevelPermissionPredicateGroup;
updateUserEmail: Scalars['Boolean'];
updateWebhook?: Maybe<Webhook>;
updateWorkflowRunStep: WorkflowAction;
@@ -2178,6 +2213,16 @@ export type MutationCreatePublicDomainArgs = {
};
export type MutationCreateRowLevelPermissionPredicateArgs = {
input: CreateRowLevelPermissionPredicateInput;
};
export type MutationCreateRowLevelPermissionPredicateGroupArgs = {
input: CreateRowLevelPermissionPredicateGroupInput;
};
export type MutationCreateSamlIdentityProviderArgs = {
input: SetupSamlSsoInput;
};
@@ -2324,6 +2369,16 @@ export type MutationDeletePublicDomainArgs = {
};
export type MutationDeleteRowLevelPermissionPredicateArgs = {
input: DeleteRowLevelPermissionPredicateInput;
};
export type MutationDeleteRowLevelPermissionPredicateGroupArgs = {
input: DeleteRowLevelPermissionPredicateGroupInput;
};
export type MutationDeleteSsoIdentityProviderArgs = {
input: DeleteSsoInput;
};
@@ -2789,6 +2844,16 @@ export type MutationUpdatePasswordViaResetTokenArgs = {
};
export type MutationUpdateRowLevelPermissionPredicateArgs = {
input: UpdateRowLevelPermissionPredicateInput;
};
export type MutationUpdateRowLevelPermissionPredicateGroupArgs = {
input: UpdateRowLevelPermissionPredicateGroupInput;
};
export type MutationUpdateUserEmailArgs = {
newEmail: Scalars['String'];
verifyEmailRedirectPath?: InputMaybe<Scalars['String']>;
@@ -3307,6 +3372,9 @@ export type Query = {
getQueueJobs: QueueJobsResponse;
getQueueMetrics: QueueMetricsData;
getRoles: Array<Role>;
getRowLevelPermissionPredicate?: Maybe<RowLevelPermissionPredicate>;
getRowLevelPermissionPredicateGroups: Array<RowLevelPermissionPredicateGroup>;
getRowLevelPermissionPredicates: Array<RowLevelPermissionPredicate>;
getSSOIdentityProviders: Array<FindAvailableSsoidpOutput>;
getServerlessFunctionSourceCode?: Maybe<Scalars['JSON']>;
getSystemHealthStatus: SystemHealth;
@@ -3569,6 +3637,22 @@ export type QueryGetQueueMetricsArgs = {
};
export type QueryGetRowLevelPermissionPredicateArgs = {
id: Scalars['String'];
};
export type QueryGetRowLevelPermissionPredicateGroupsArgs = {
roleId?: InputMaybe<Scalars['String']>;
};
export type QueryGetRowLevelPermissionPredicatesArgs = {
objectMetadataId?: InputMaybe<Scalars['String']>;
roleId?: InputMaybe<Scalars['String']>;
};
export type QueryGetServerlessFunctionSourceCodeArgs = {
input: GetServerlessFunctionSourceCodeInput;
};
@@ -3843,6 +3927,54 @@ export type RouteTriggerIdInput = {
id: Scalars['String'];
};
export type RowLevelPermissionPredicate = {
__typename?: 'RowLevelPermissionPredicate';
fieldMetadataId: Scalars['String'];
id: Scalars['String'];
objectMetadataId: Scalars['String'];
operand: RowLevelPermissionPredicateOperand;
positionInRowLevelPermissionPredicateGroup?: Maybe<Scalars['Float']>;
roleId: Scalars['String'];
rowLevelPermissionPredicateGroupId?: Maybe<Scalars['String']>;
subFieldName?: Maybe<Scalars['String']>;
value?: Maybe<Scalars['JSON']>;
workspaceMemberFieldMetadataId?: Maybe<Scalars['String']>;
workspaceMemberSubFieldName?: Maybe<Scalars['String']>;
};
export type RowLevelPermissionPredicateGroup = {
__typename?: 'RowLevelPermissionPredicateGroup';
id: Scalars['String'];
logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator;
parentRowLevelPermissionPredicateGroupId?: Maybe<Scalars['String']>;
positionInRowLevelPermissionPredicateGroup?: Maybe<Scalars['Float']>;
roleId: Scalars['String'];
};
export enum RowLevelPermissionPredicateGroupLogicalOperator {
AND = 'AND',
OR = 'OR'
}
export enum RowLevelPermissionPredicateOperand {
CONTAINS = 'CONTAINS',
DOES_NOT_CONTAIN = 'DOES_NOT_CONTAIN',
GREATER_THAN_OR_EQUAL = 'GREATER_THAN_OR_EQUAL',
IS = 'IS',
IS_AFTER = 'IS_AFTER',
IS_BEFORE = 'IS_BEFORE',
IS_EMPTY = 'IS_EMPTY',
IS_IN_FUTURE = 'IS_IN_FUTURE',
IS_IN_PAST = 'IS_IN_PAST',
IS_NOT = 'IS_NOT',
IS_NOT_EMPTY = 'IS_NOT_EMPTY',
IS_NOT_NULL = 'IS_NOT_NULL',
IS_RELATIVE = 'IS_RELATIVE',
IS_TODAY = 'IS_TODAY',
LESS_THAN_OR_EQUAL = 'LESS_THAN_OR_EQUAL',
VECTOR_SEARCH = 'VECTOR_SEARCH'
}
export type RunWorkflowVersionInput = {
/** Execution result in JSON format */
payload?: InputMaybe<Scalars['JSON']>;
@@ -4437,6 +4569,24 @@ export type UpdateRouteTriggerInputUpdates = {
path: Scalars['String'];
};
export type UpdateRowLevelPermissionPredicateGroupInput = {
id: Scalars['String'];
logicalOperator?: InputMaybe<RowLevelPermissionPredicateGroupLogicalOperator>;
parentRowLevelPermissionPredicateGroupId?: InputMaybe<Scalars['String']>;
positionInRowLevelPermissionPredicateGroup?: InputMaybe<Scalars['Float']>;
};
export type UpdateRowLevelPermissionPredicateInput = {
id: Scalars['String'];
operand?: InputMaybe<RowLevelPermissionPredicateOperand>;
positionInRowLevelPermissionPredicateGroup?: InputMaybe<Scalars['Float']>;
rowLevelPermissionPredicateGroupId?: InputMaybe<Scalars['String']>;
subFieldName?: InputMaybe<Scalars['String']>;
value?: InputMaybe<Scalars['JSON']>;
workspaceMemberFieldMetadataId?: InputMaybe<Scalars['String']>;
workspaceMemberSubFieldName?: InputMaybe<Scalars['String']>;
};
export type UpdateServerlessFunctionInput = {
/** Id of the serverless function to update */
id: Scalars['UUID'];
@@ -926,6 +926,26 @@ export type CreateRouteTriggerInput = {
serverlessFunctionId: Scalars['String'];
};
export type CreateRowLevelPermissionPredicateGroupInput = {
logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator;
parentRowLevelPermissionPredicateGroupId?: InputMaybe<Scalars['String']>;
positionInRowLevelPermissionPredicateGroup?: InputMaybe<Scalars['Float']>;
roleId: Scalars['String'];
};
export type CreateRowLevelPermissionPredicateInput = {
fieldMetadataId: Scalars['String'];
objectMetadataId: Scalars['String'];
operand: RowLevelPermissionPredicateOperand;
positionInRowLevelPermissionPredicateGroup?: InputMaybe<Scalars['Float']>;
roleId: Scalars['String'];
rowLevelPermissionPredicateGroupId?: InputMaybe<Scalars['String']>;
subFieldName?: InputMaybe<Scalars['String']>;
value?: InputMaybe<Scalars['JSON']>;
workspaceMemberFieldMetadataId?: InputMaybe<Scalars['String']>;
workspaceMemberSubFieldName?: InputMaybe<Scalars['String']>;
};
export type CreateServerlessFunctionInput = {
code?: InputMaybe<Scalars['JSON']>;
description?: InputMaybe<Scalars['String']>;
@@ -1110,6 +1130,14 @@ export type DeleteOneObjectInput = {
id: Scalars['UUID'];
};
export type DeleteRowLevelPermissionPredicateGroupInput = {
id: Scalars['String'];
};
export type DeleteRowLevelPermissionPredicateInput = {
id: Scalars['String'];
};
export type DeleteSsoInput = {
identityProviderId: Scalars['UUID'];
};
@@ -1304,6 +1332,7 @@ export enum FeatureFlagKey {
IS_POSTGRESQL_INTEGRATION_ENABLED = 'IS_POSTGRESQL_INTEGRATION_ENABLED',
IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED',
IS_RECORD_PAGE_LAYOUT_ENABLED = 'IS_RECORD_PAGE_LAYOUT_ENABLED',
IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED = 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED',
IS_STRIPE_INTEGRATION_ENABLED = 'IS_STRIPE_INTEGRATION_ENABLED',
IS_TIMELINE_ACTIVITY_MIGRATED = 'IS_TIMELINE_ACTIVITY_MIGRATED',
IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED',
@@ -1796,6 +1825,8 @@ export type Mutation = {
createPageLayoutTab: PageLayoutTab;
createPageLayoutWidget: PageLayoutWidget;
createPublicDomain: PublicDomain;
createRowLevelPermissionPredicate: RowLevelPermissionPredicate;
createRowLevelPermissionPredicateGroup: RowLevelPermissionPredicateGroup;
createSAMLIdentityProvider: SetupSsoOutput;
createWebhook: Webhook;
createWorkflowVersionEdge: WorkflowVersionStepChanges;
@@ -1825,6 +1856,8 @@ export type Mutation = {
deletePageLayoutTab: Scalars['Boolean'];
deletePageLayoutWidget: PageLayoutWidget;
deletePublicDomain: Scalars['Boolean'];
deleteRowLevelPermissionPredicate: RowLevelPermissionPredicate;
deleteRowLevelPermissionPredicateGroup: RowLevelPermissionPredicateGroup;
deleteSSOIdentityProvider: DeleteSsoOutput;
deleteTwoFactorAuthenticationMethod: DeleteTwoFactorAuthenticationMethodOutput;
deleteUser: User;
@@ -1913,6 +1946,8 @@ export type Mutation = {
updatePageLayoutWidget: PageLayoutWidget;
updatePageLayoutWithTabsAndWidgets: PageLayout;
updatePasswordViaResetToken: InvalidatePasswordOutput;
updateRowLevelPermissionPredicate: RowLevelPermissionPredicate;
updateRowLevelPermissionPredicateGroup: RowLevelPermissionPredicateGroup;
updateUserEmail: Scalars['Boolean'];
updateWebhook?: Maybe<Webhook>;
updateWorkflowRunStep: WorkflowAction;
@@ -2135,6 +2170,16 @@ export type MutationCreatePublicDomainArgs = {
};
export type MutationCreateRowLevelPermissionPredicateArgs = {
input: CreateRowLevelPermissionPredicateInput;
};
export type MutationCreateRowLevelPermissionPredicateGroupArgs = {
input: CreateRowLevelPermissionPredicateGroupInput;
};
export type MutationCreateSamlIdentityProviderArgs = {
input: SetupSamlSsoInput;
};
@@ -2276,6 +2321,16 @@ export type MutationDeletePublicDomainArgs = {
};
export type MutationDeleteRowLevelPermissionPredicateArgs = {
input: DeleteRowLevelPermissionPredicateInput;
};
export type MutationDeleteRowLevelPermissionPredicateGroupArgs = {
input: DeleteRowLevelPermissionPredicateGroupInput;
};
export type MutationDeleteSsoIdentityProviderArgs = {
input: DeleteSsoInput;
};
@@ -2710,6 +2765,16 @@ export type MutationUpdatePasswordViaResetTokenArgs = {
};
export type MutationUpdateRowLevelPermissionPredicateArgs = {
input: UpdateRowLevelPermissionPredicateInput;
};
export type MutationUpdateRowLevelPermissionPredicateGroupArgs = {
input: UpdateRowLevelPermissionPredicateGroupInput;
};
export type MutationUpdateUserEmailArgs = {
newEmail: Scalars['String'];
verifyEmailRedirectPath?: InputMaybe<Scalars['String']>;
@@ -3221,6 +3286,9 @@ export type Query = {
getQueueJobs: QueueJobsResponse;
getQueueMetrics: QueueMetricsData;
getRoles: Array<Role>;
getRowLevelPermissionPredicate?: Maybe<RowLevelPermissionPredicate>;
getRowLevelPermissionPredicateGroups: Array<RowLevelPermissionPredicateGroup>;
getRowLevelPermissionPredicates: Array<RowLevelPermissionPredicate>;
getSSOIdentityProviders: Array<FindAvailableSsoidpOutput>;
getServerlessFunctionSourceCode?: Maybe<Scalars['JSON']>;
getSystemHealthStatus: SystemHealth;
@@ -3442,6 +3510,22 @@ export type QueryGetQueueMetricsArgs = {
};
export type QueryGetRowLevelPermissionPredicateArgs = {
id: Scalars['String'];
};
export type QueryGetRowLevelPermissionPredicateGroupsArgs = {
roleId?: InputMaybe<Scalars['String']>;
};
export type QueryGetRowLevelPermissionPredicatesArgs = {
objectMetadataId?: InputMaybe<Scalars['String']>;
roleId?: InputMaybe<Scalars['String']>;
};
export type QueryGetServerlessFunctionSourceCodeArgs = {
input: GetServerlessFunctionSourceCodeInput;
};
@@ -3680,6 +3764,54 @@ export type RouteTriggerIdInput = {
id: Scalars['String'];
};
export type RowLevelPermissionPredicate = {
__typename?: 'RowLevelPermissionPredicate';
fieldMetadataId: Scalars['String'];
id: Scalars['String'];
objectMetadataId: Scalars['String'];
operand: RowLevelPermissionPredicateOperand;
positionInRowLevelPermissionPredicateGroup?: Maybe<Scalars['Float']>;
roleId: Scalars['String'];
rowLevelPermissionPredicateGroupId?: Maybe<Scalars['String']>;
subFieldName?: Maybe<Scalars['String']>;
value?: Maybe<Scalars['JSON']>;
workspaceMemberFieldMetadataId?: Maybe<Scalars['String']>;
workspaceMemberSubFieldName?: Maybe<Scalars['String']>;
};
export type RowLevelPermissionPredicateGroup = {
__typename?: 'RowLevelPermissionPredicateGroup';
id: Scalars['String'];
logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator;
parentRowLevelPermissionPredicateGroupId?: Maybe<Scalars['String']>;
positionInRowLevelPermissionPredicateGroup?: Maybe<Scalars['Float']>;
roleId: Scalars['String'];
};
export enum RowLevelPermissionPredicateGroupLogicalOperator {
AND = 'AND',
OR = 'OR'
}
export enum RowLevelPermissionPredicateOperand {
CONTAINS = 'CONTAINS',
DOES_NOT_CONTAIN = 'DOES_NOT_CONTAIN',
GREATER_THAN_OR_EQUAL = 'GREATER_THAN_OR_EQUAL',
IS = 'IS',
IS_AFTER = 'IS_AFTER',
IS_BEFORE = 'IS_BEFORE',
IS_EMPTY = 'IS_EMPTY',
IS_IN_FUTURE = 'IS_IN_FUTURE',
IS_IN_PAST = 'IS_IN_PAST',
IS_NOT = 'IS_NOT',
IS_NOT_EMPTY = 'IS_NOT_EMPTY',
IS_NOT_NULL = 'IS_NOT_NULL',
IS_RELATIVE = 'IS_RELATIVE',
IS_TODAY = 'IS_TODAY',
LESS_THAN_OR_EQUAL = 'LESS_THAN_OR_EQUAL',
VECTOR_SEARCH = 'VECTOR_SEARCH'
}
export type RunWorkflowVersionInput = {
/** Execution result in JSON format */
payload?: InputMaybe<Scalars['JSON']>;
@@ -4266,6 +4398,24 @@ export type UpdateRouteTriggerInputUpdates = {
path: Scalars['String'];
};
export type UpdateRowLevelPermissionPredicateGroupInput = {
id: Scalars['String'];
logicalOperator?: InputMaybe<RowLevelPermissionPredicateGroupLogicalOperator>;
parentRowLevelPermissionPredicateGroupId?: InputMaybe<Scalars['String']>;
positionInRowLevelPermissionPredicateGroup?: InputMaybe<Scalars['Float']>;
};
export type UpdateRowLevelPermissionPredicateInput = {
id: Scalars['String'];
operand?: InputMaybe<RowLevelPermissionPredicateOperand>;
positionInRowLevelPermissionPredicateGroup?: InputMaybe<Scalars['Float']>;
rowLevelPermissionPredicateGroupId?: InputMaybe<Scalars['String']>;
subFieldName?: InputMaybe<Scalars['String']>;
value?: InputMaybe<Scalars['JSON']>;
workspaceMemberFieldMetadataId?: InputMaybe<Scalars['String']>;
workspaceMemberSubFieldName?: InputMaybe<Scalars['String']>;
};
export type UpdateServerlessFunctionInput = {
/** Id of the serverless function to update */
id: Scalars['UUID'];
@@ -29,6 +29,8 @@ export const useMetadataErrorHandler = () => {
pageLayout: t`page layout`,
pageLayoutTab: t`page layout tab`,
pageLayoutWidget: t`page layout widget`,
rowLevelPermissionPredicate: t`row level permission predicate`,
rowLevelPermissionPredicateGroup: t`row level permission predicate group`,
} as const satisfies Record<AllMetadataName, string>;
const handleMetadataError = useCallback(
@@ -2,15 +2,20 @@ import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMeta
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsRolePermissionsObjectLevelObjectFieldPermissionTable } from '@/settings/roles/role-permissions/object-level-permissions/field-permissions/components/SettingsRolePermissionsObjectLevelObjectFieldPermissionTable';
import { SettingsRolePermissionsObjectLevelObjectFormObjectLevel } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectFormObjectLevel';
import { SettingsRolePermissionsObjectLevelRecordLevelSection } from '@/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelSection';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap';
import { t } from '@lingui/core/macro';
import { useSearchParams } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
import { Button } from 'twenty-ui/input';
import { useFindOneAgentQuery } from '~/generated-metadata/graphql';
import {
FeatureFlagKey,
useFindOneAgentQuery,
} from '~/generated-metadata/graphql';
type SettingsRolePermissionsObjectLevelObjectFormProps = {
roleId: string;
@@ -37,6 +42,10 @@ export const SettingsRolePermissionsObjectLevelObjectForm = ({
objectId: objectMetadataId,
});
const featureFlagsMap = useFeatureFlagsMap();
const isRowLevelPermissionPredicatesEnabled =
featureFlagsMap[FeatureFlagKey.IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED];
const objectMetadataItem = objectMetadata.objectMetadataItem;
const objectLabelSingular = objectMetadataItem.labelSingular;
@@ -113,6 +122,9 @@ export const SettingsRolePermissionsObjectLevelObjectForm = ({
objectMetadataItem={objectMetadataItem}
roleId={roleId}
/>
{isRowLevelPermissionPredicatesEnabled && (
<SettingsRolePermissionsObjectLevelRecordLevelSection />
)}
</SettingsPageContainer>
</SubMenuTopBarContainer>
);
@@ -0,0 +1,21 @@
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { H2Title } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
const StyledEmptyState = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
padding-top: ${({ theme }) => theme.spacing(2)};
`;
export const SettingsRolePermissionsObjectLevelRecordLevelSection = () => {
return (
<Section>
<H2Title
title={t`Record-level`}
description={t`Ability to interact with specific records of this object.`}
/>
<StyledEmptyState>{t`No record-level rules have been set yet.`}</StyledEmptyState>
</Section>
);
};
+1
View File
@@ -19,6 +19,7 @@ declare module 'express-serve-static-core' {
workspaceId?: string;
workspaceMetadataVersion?: number;
workspaceMemberId?: string;
workspaceMember?: WorkspaceMemberWorkspaceEntity;
userWorkspaceId?: string;
authProvider?: AuthProviderEnum | null;
impersonationContext?: AuthContext['impersonationContext'];
@@ -0,0 +1,131 @@
import { type MigrationInterface, type QueryRunner } from 'typeorm';
export class AddRLS1765499361805 implements MigrationInterface {
name = 'AddRLS1765499361805';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TYPE "core"."rowLevelPermissionPredicate_operand_enum" AS ENUM('IS', 'IS_NOT_NULL', 'IS_NOT', 'LESS_THAN_OR_EQUAL', 'GREATER_THAN_OR_EQUAL', 'IS_BEFORE', 'IS_AFTER', 'CONTAINS', 'DOES_NOT_CONTAIN', 'IS_EMPTY', 'IS_NOT_EMPTY', 'IS_RELATIVE', 'IS_IN_PAST', 'IS_IN_FUTURE', 'IS_TODAY', 'VECTOR_SEARCH')`,
);
await queryRunner.query(
`CREATE TABLE "core"."rowLevelPermissionPredicate" ("universalIdentifier" uuid, "applicationId" uuid, "id" uuid NOT NULL DEFAULT uuid_generate_v4(), "fieldMetadataId" uuid NOT NULL, "objectMetadataId" uuid NOT NULL, "operand" "core"."rowLevelPermissionPredicate_operand_enum" NOT NULL DEFAULT 'CONTAINS', "value" jsonb, "subFieldName" text, "workspaceMemberFieldMetadataId" uuid, "workspaceMemberSubFieldName" text, "rowLevelPermissionPredicateGroupId" uuid, "positionInRowLevelPermissionPredicateGroup" double precision, "workspaceId" uuid NOT NULL, "roleId" uuid NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_e7ac2b75856fc7f300b5feb0e39" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_e46f3e01227f1c8ee0c8041821" ON "core"."rowLevelPermissionPredicate" ("workspaceId", "universalIdentifier") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_RLPP_GROUP_ID" ON "core"."rowLevelPermissionPredicate" ("rowLevelPermissionPredicateGroupId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_RLPP_FIELD_METADATA_ID" ON "core"."rowLevelPermissionPredicate" ("fieldMetadataId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_RLPP_WORKSPACE_ID_ROLE_ID_OBJECT_METADATA_ID" ON "core"."rowLevelPermissionPredicate" ("workspaceId", "roleId", "objectMetadataId") `,
);
await queryRunner.query(
`CREATE TYPE "core"."rowLevelPermissionPredicateGroup_logicaloperator_enum" AS ENUM('AND', 'OR')`,
);
await queryRunner.query(
`CREATE TABLE "core"."rowLevelPermissionPredicateGroup" ("universalIdentifier" uuid, "applicationId" uuid, "id" uuid NOT NULL DEFAULT uuid_generate_v4(), "parentRowLevelPermissionPredicateGroupId" uuid, "logicalOperator" "core"."rowLevelPermissionPredicateGroup_logicaloperator_enum" NOT NULL DEFAULT 'AND', "positionInRowLevelPermissionPredicateGroup" double precision, "workspaceId" uuid NOT NULL, "roleId" uuid NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_5084d63eb632c38d70b974841f3" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_a14b5665091e86d461fb585924" ON "core"."rowLevelPermissionPredicateGroup" ("workspaceId", "universalIdentifier") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_RLPPG_WORKSPACE_ID_ROLE_ID" ON "core"."rowLevelPermissionPredicateGroup" ("workspaceId", "roleId") `,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicate" ADD CONSTRAINT "FK_23b36d07d363f81200654fa1334" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicate" ADD CONSTRAINT "FK_eadcbbb92b6d58c6785a780b5b7" FOREIGN KEY ("fieldMetadataId") REFERENCES "core"."fieldMetadata"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicate" ADD CONSTRAINT "FK_d5ee96a9a03761f2c328a29523c" FOREIGN KEY ("objectMetadataId") REFERENCES "core"."objectMetadata"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicate" ADD CONSTRAINT "FK_b575e84d5ba7f183079c5c8c421" FOREIGN KEY ("workspaceMemberFieldMetadataId") REFERENCES "core"."fieldMetadata"("id") ON DELETE SET NULL ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicate" ADD CONSTRAINT "FK_15199deab40d48dd1480a2faf85" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicate" ADD CONSTRAINT "FK_5fb4b0cebaf1b6418412bf65170" FOREIGN KEY ("roleId") REFERENCES "core"."role"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicate" ADD CONSTRAINT "FK_67519abd77fc637444720192737" FOREIGN KEY ("rowLevelPermissionPredicateGroupId") REFERENCES "core"."rowLevelPermissionPredicateGroup"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicateGroup" ADD CONSTRAINT "FK_1e82563accb67114f65a3993b86" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicateGroup" ADD CONSTRAINT "FK_a41b6a06e3a7ded2204b0fc815d" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicateGroup" ADD CONSTRAINT "FK_3f1abc7557a4e9f4334d41b07d7" FOREIGN KEY ("roleId") REFERENCES "core"."role"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicateGroup" ADD CONSTRAINT "FK_25bbd97a29478e18061cb58950f" FOREIGN KEY ("parentRowLevelPermissionPredicateGroupId") REFERENCES "core"."rowLevelPermissionPredicateGroup"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicateGroup" DROP CONSTRAINT "FK_25bbd97a29478e18061cb58950f"`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicateGroup" DROP CONSTRAINT "FK_3f1abc7557a4e9f4334d41b07d7"`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicateGroup" DROP CONSTRAINT "FK_a41b6a06e3a7ded2204b0fc815d"`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicateGroup" DROP CONSTRAINT "FK_1e82563accb67114f65a3993b86"`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicate" DROP CONSTRAINT "FK_67519abd77fc637444720192737"`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicate" DROP CONSTRAINT "FK_5fb4b0cebaf1b6418412bf65170"`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicate" DROP CONSTRAINT "FK_15199deab40d48dd1480a2faf85"`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicate" DROP CONSTRAINT "FK_b575e84d5ba7f183079c5c8c421"`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicate" DROP CONSTRAINT "FK_d5ee96a9a03761f2c328a29523c"`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicate" DROP CONSTRAINT "FK_eadcbbb92b6d58c6785a780b5b7"`,
);
await queryRunner.query(
`ALTER TABLE "core"."rowLevelPermissionPredicate" DROP CONSTRAINT "FK_23b36d07d363f81200654fa1334"`,
);
await queryRunner.query(
`DROP INDEX "core"."IDX_RLPPG_WORKSPACE_ID_ROLE_ID"`,
);
await queryRunner.query(
`DROP INDEX "core"."IDX_a14b5665091e86d461fb585924"`,
);
await queryRunner.query(
`DROP TABLE "core"."rowLevelPermissionPredicateGroup"`,
);
await queryRunner.query(
`DROP TYPE "core"."rowLevelPermissionPredicateGroup_logicaloperator_enum"`,
);
await queryRunner.query(
`DROP INDEX "core"."IDX_RLPP_WORKSPACE_ID_ROLE_ID_OBJECT_METADATA_ID"`,
);
await queryRunner.query(`DROP INDEX "core"."IDX_RLPP_FIELD_METADATA_ID"`);
await queryRunner.query(`DROP INDEX "core"."IDX_RLPP_GROUP_ID"`);
await queryRunner.query(
`DROP INDEX "core"."IDX_e46f3e01227f1c8ee0c8041821"`,
);
await queryRunner.query(`DROP TABLE "core"."rowLevelPermissionPredicate"`);
await queryRunner.query(
`DROP TYPE "core"."rowLevelPermissionPredicate_operand_enum"`,
);
}
}
@@ -6,6 +6,7 @@ interface AuthContextWithDefinedWorkspaceProperties {
workspace: NonNullable<AuthContext['workspace']>;
workspaceMetadataVersion?: string;
workspaceMemberId: AuthContext['workspaceMemberId'];
workspaceMember: AuthContext['workspaceMember'];
userWorkspaceId: AuthContext['userWorkspaceId'];
apiKey: AuthContext['apiKey'];
}
@@ -19,6 +19,7 @@ export const createQueryRunnerContext = ({
userWorkspace,
authProvider,
impersonationContext,
workspaceMember,
} = request;
return {
@@ -32,6 +33,7 @@ export const createQueryRunnerContext = ({
userWorkspace,
authProvider,
impersonationContext,
workspaceMember,
},
};
};
@@ -23,6 +23,8 @@ describe('JwtAuthStrategy', () => {
let applicationRepository: any;
let jwtWrapperService: any;
let permissionsService: any;
let globalWorkspaceOrmManager: any;
let workspaceMemberRepository: any;
const jwt = {
sub: 'sub-default',
@@ -57,6 +59,20 @@ describe('JwtAuthStrategy', () => {
permissionsService = {
userHasWorkspaceSettingPermission: jest.fn(),
};
workspaceMemberRepository = {
findOne: jest.fn(),
};
workspaceMemberRepository.findOne.mockResolvedValue({
id: 'workspace-member-id',
});
globalWorkspaceOrmManager = {
executeInWorkspaceContext: jest.fn(async (_authContext, callback) => {
return await callback();
}),
getRepository: jest.fn(async () => workspaceMemberRepository),
};
});
afterEach(() => {
@@ -80,6 +96,7 @@ describe('JwtAuthStrategy', () => {
userWorkspaceRepository,
apiKeyRepository,
permissionsService,
globalWorkspaceOrmManager,
);
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
@@ -111,6 +128,7 @@ describe('JwtAuthStrategy', () => {
userWorkspaceRepository,
apiKeyRepository,
permissionsService,
globalWorkspaceOrmManager,
);
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
@@ -145,6 +163,7 @@ describe('JwtAuthStrategy', () => {
userWorkspaceRepository,
apiKeyRepository,
permissionsService,
globalWorkspaceOrmManager,
);
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
@@ -179,6 +198,7 @@ describe('JwtAuthStrategy', () => {
userWorkspaceRepository,
apiKeyRepository,
permissionsService,
globalWorkspaceOrmManager,
);
const result = await strategy.validate(payload as JwtPayload);
@@ -220,10 +240,11 @@ describe('JwtAuthStrategy', () => {
userWorkspaceRepository,
apiKeyRepository,
permissionsService,
globalWorkspaceOrmManager,
);
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
new AuthException('UserWorkspaceEntity not found', expect.any(String), {
new AuthException('User not found', expect.any(String), {
userFriendlyMessage: msg`User does not have access to this workspace.`,
}),
);
@@ -231,7 +252,7 @@ describe('JwtAuthStrategy', () => {
try {
await strategy.validate(payload as JwtPayload);
} catch (e) {
expect(e.code).toBe(AuthExceptionCode.USER_WORKSPACE_NOT_FOUND);
expect(e.code).toBe(AuthExceptionCode.USER_NOT_FOUND);
}
});
@@ -261,6 +282,7 @@ describe('JwtAuthStrategy', () => {
userWorkspaceRepository,
apiKeyRepository,
permissionsService,
globalWorkspaceOrmManager,
);
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
@@ -306,6 +328,7 @@ describe('JwtAuthStrategy', () => {
userWorkspaceRepository,
apiKeyRepository,
permissionsService,
globalWorkspaceOrmManager,
);
const user = await strategy.validate(payload as JwtPayload);
@@ -339,6 +362,7 @@ describe('JwtAuthStrategy', () => {
userWorkspaceRepository,
apiKeyRepository,
permissionsService,
globalWorkspaceOrmManager,
);
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
@@ -392,6 +416,7 @@ describe('JwtAuthStrategy', () => {
userWorkspaceRepository,
apiKeyRepository,
permissionsService,
globalWorkspaceOrmManager,
);
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
@@ -438,6 +463,7 @@ describe('JwtAuthStrategy', () => {
userWorkspaceRepository,
apiKeyRepository,
permissionsService,
globalWorkspaceOrmManager,
);
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
@@ -486,6 +512,7 @@ describe('JwtAuthStrategy', () => {
userWorkspaceRepository,
apiKeyRepository,
permissionsService,
globalWorkspaceOrmManager,
);
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
@@ -545,6 +572,7 @@ describe('JwtAuthStrategy', () => {
userWorkspaceRepository,
apiKeyRepository,
permissionsService,
globalWorkspaceOrmManager,
);
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
@@ -599,6 +627,7 @@ describe('JwtAuthStrategy', () => {
userWorkspaceRepository,
apiKeyRepository,
permissionsService,
globalWorkspaceOrmManager,
);
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
@@ -670,6 +699,7 @@ describe('JwtAuthStrategy', () => {
userWorkspaceRepository,
apiKeyRepository,
permissionsService,
globalWorkspaceOrmManager,
);
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
@@ -740,6 +770,7 @@ describe('JwtAuthStrategy', () => {
userWorkspaceRepository,
apiKeyRepository,
permissionsService,
globalWorkspaceOrmManager,
);
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
@@ -811,6 +842,7 @@ describe('JwtAuthStrategy', () => {
userWorkspaceRepository,
apiKeyRepository,
permissionsService,
globalWorkspaceOrmManager,
);
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
@@ -875,6 +907,7 @@ describe('JwtAuthStrategy', () => {
userWorkspaceRepository,
apiKeyRepository,
permissionsService,
globalWorkspaceOrmManager,
);
const result = await strategy.validate(payload as JwtPayload);
@@ -941,6 +974,7 @@ describe('JwtAuthStrategy', () => {
userWorkspaceRepository,
apiKeyRepository,
permissionsService,
globalWorkspaceOrmManager,
);
const result = await strategy.validate(payload as JwtPayload);
@@ -4,11 +4,13 @@ import { InjectRepository } from '@nestjs/typeorm';
import { msg } from '@lingui/core/macro';
import { Strategy } from 'passport-jwt';
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { PermissionFlagType } from 'twenty-shared/constants';
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { Repository } from 'typeorm';
import { ApiKeyEntity } from 'src/engine/core-modules/api-key/api-key.entity';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import {
AuthException,
AuthExceptionCode,
@@ -29,7 +31,9 @@ import { UserEntity } from 'src/engine/core-modules/user/user.entity';
import { userValidator } from 'src/engine/core-modules/user/user.validate';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
@Injectable()
export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
@@ -46,6 +50,7 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
@InjectRepository(ApiKeyEntity)
private readonly apiKeyRepository: Repository<ApiKeyEntity>,
private readonly permissionsService: PermissionsService,
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
) {
const jwtFromRequestFunction = jwtWrapperService.extractJwtFromRequest();
// @ts-expect-error legacy noImplicitAny
@@ -146,6 +151,13 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
where: { id: userId },
});
if (!isDefined(user)) {
throw new AuthException(
'User not found',
AuthExceptionCode.USER_NOT_FOUND,
);
}
if (!payload.userWorkspaceId) {
throw new AuthException(
'UserWorkspaceEntity not found',
@@ -179,7 +191,50 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
workspaceMemberId: payload.workspaceMemberId,
};
return context;
if (
workspace.activationStatus ===
WorkspaceActivationStatus.PENDING_CREATION ||
workspace.activationStatus === WorkspaceActivationStatus.ONGOING_CREATION
) {
return context;
}
const workspaceMember =
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
buildSystemAuthContext(workspace.id),
async () => {
const workspaceMemberRepository =
await this.globalWorkspaceOrmManager.getRepository<WorkspaceMemberWorkspaceEntity>(
workspace.id,
'workspaceMember',
{ shouldBypassPermissionChecks: true },
);
const workspaceMember = await workspaceMemberRepository.findOne({
where: {
userId: user.id,
},
});
assertIsDefinedOrThrow(
workspaceMember,
new AuthException(
'User is not a member of the workspace',
AuthExceptionCode.FORBIDDEN_EXCEPTION,
{
userFriendlyMessage: msg`User is not a member of the workspace.`,
},
),
);
return workspaceMember;
},
);
return {
...context,
workspaceMember,
};
}
private async validateImpersonation(payload: AccessTokenJwtPayload) {
@@ -1,14 +1,16 @@
import { type ApiKeyEntity } from 'src/engine/core-modules/api-key/api-key.entity';
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { type UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { type UserEntity } from 'src/engine/core-modules/user/user.entity';
import { type AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
export type AuthContext = {
user?: UserEntity | null | undefined;
apiKey?: ApiKeyEntity | null | undefined;
workspaceMemberId?: string;
workspaceMember?: WorkspaceMemberWorkspaceEntity;
workspace?: WorkspaceEntity;
application?: ApplicationEntity | null | undefined;
userWorkspaceId?: string;
@@ -60,6 +60,7 @@ import { AiModelsModule } from 'src/engine/metadata-modules/ai/ai-models/ai-mode
import { FlatPageLayoutTabModule } from 'src/engine/metadata-modules/flat-page-layout-tab/flat-page-layout-tab.module';
import { PageLayoutModule } from 'src/engine/metadata-modules/page-layout/page-layout.module';
import { RoleModule } from 'src/engine/metadata-modules/role/role.module';
import { RowLevelPermissionModule } from 'src/engine/metadata-modules/row-level-permission-predicate/row-level-permission.module';
import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module';
import { TrashCleanupModule } from 'src/engine/trash-cleanup/trash-cleanup.module';
import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module';
@@ -81,6 +82,7 @@ import { FileModule } from './file/file.module';
ClientConfigModule,
FeatureFlagModule,
FileModule,
RowLevelPermissionModule,
OpenApiModule,
ApplicationModule,
ApplicationSyncModule,
@@ -150,6 +152,7 @@ import { FileModule } from './file/file.module';
ImpersonationModule,
TrashCleanupModule,
DashboardModule,
RowLevelPermissionModule,
],
exports: [
AuditModule,
@@ -14,5 +14,6 @@ export enum FeatureFlagKey {
IS_DASHBOARD_V2_ENABLED = 'IS_DASHBOARD_V2_ENABLED',
IS_TIMELINE_ACTIVITY_MIGRATED = 'IS_TIMELINE_ACTIVITY_MIGRATED',
IS_GLOBAL_WORKSPACE_DATASOURCE_ENABLED = 'IS_GLOBAL_WORKSPACE_DATASOURCE_ENABLED',
IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED = 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED',
IS_WORKSPACE_CREATION_V2_ENABLED = 'IS_WORKSPACE_CREATION_V2_ENABLED',
}
@@ -11,6 +11,8 @@ import { FLAT_PAGE_LAYOUT_WIDGET_EDITABLE_PROPERTIES } from 'src/engine/metadata
import { FLAT_PAGE_LAYOUT_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-page-layout/constants/flat-page-layout-editable-properties.constant';
import { FLAT_ROLE_TARGET_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-role-target/constants/flat-role-target-editable-properties.constant';
import { FLAT_ROLE_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-role/constants/flat-role-editable-properties.constant';
import { FLAT_ROW_LEVEL_PERMISSION_PREDICATE_GROUP_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-row-level-permission-predicate-group/constants/flat-row-level-permission-predicate-group-editable-properties.constant';
import { FLAT_ROW_LEVEL_PERMISSION_PREDICATE_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/constants/flat-row-level-permission-predicate-editable-properties.constant';
import { FLAT_VIEW_FIELD_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-field/constants/flat-view-field-editable-properties.constant';
import { FLAT_VIEW_FILTER_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-filter/constants/flat-view-filter-editable-properties.constant';
import { FLAT_VIEW_GROUP_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-group/constants/flat-view-group-editable-properties.constant';
@@ -132,6 +134,20 @@ export const ALL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY = {
],
propertiesToStringify: [],
},
rowLevelPermissionPredicate: {
propertiesToCompare: [
...FLAT_ROW_LEVEL_PERMISSION_PREDICATE_EDITABLE_PROPERTIES,
'deletedAt',
],
propertiesToStringify: ['value'],
},
rowLevelPermissionPredicateGroup: {
propertiesToCompare: [
...FLAT_ROW_LEVEL_PERMISSION_PREDICATE_GROUP_EDITABLE_PROPERTIES,
'deletedAt',
],
propertiesToStringify: [],
},
} as const satisfies {
[P in AllMetadataName]: OneFlatEntityConfiguration<P>;
};
@@ -122,4 +122,6 @@ export const ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY = {
flatEntityForeignKeyAggregator: 'tabIds',
},
},
rowLevelPermissionPredicate: {},
rowLevelPermissionPredicateGroup: {},
} as const satisfies MetadataNameAndRelations;
@@ -126,6 +126,23 @@ export const ALL_METADATA_RELATION_PROPERTIES = {
objectMetadata: true,
application: true,
},
rowLevelPermissionPredicate: {
workspace: true,
role: true,
fieldMetadata: true,
workspaceMemberFieldMetadata: true,
objectMetadata: true,
rowLevelPermissionPredicateGroup: true,
application: true,
},
rowLevelPermissionPredicateGroup: {
workspace: true,
role: true,
parentRowLevelPermissionPredicateGroup: true,
childRowLevelPermissionPredicateGroups: true,
rowLevelPermissionPredicates: true,
application: true,
},
} as const satisfies {
[TName in AllMetadataName]: {
[P in ExtractEntityRelatedEntityProperties<MetadataEntity<TName>>]: true;
@@ -64,4 +64,13 @@ export const ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION = {
pageLayoutWidget: {
pageLayoutTab: true,
},
rowLevelPermissionPredicate: {
fieldMetadata: true,
objectMetadata: true,
role: true,
rowLevelPermissionPredicateGroup: true,
},
rowLevelPermissionPredicateGroup: {
role: true,
},
} as const satisfies MetadataRequiredForValidation;
@@ -9,6 +9,8 @@ import { WorkspaceFlatObjectMetadataMapCacheService } from 'src/engine/metadata-
import { WorkspaceFlatPageLayoutTabMapCacheService } from 'src/engine/metadata-modules/flat-page-layout-tab/services/workspace-flat-page-layout-tab-map-cache.service';
import { WorkspaceFlatPageLayoutWidgetMapCacheService } from 'src/engine/metadata-modules/flat-page-layout-widget/services/workspace-flat-page-layout-widget-map-cache.service';
import { WorkspaceFlatPageLayoutMapCacheService } from 'src/engine/metadata-modules/flat-page-layout/services/workspace-flat-page-layout-map-cache.service';
import { WorkspaceFlatRowLevelPermissionPredicateGroupMapCacheService } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/services/workspace-flat-row-level-permission-predicate-group-map-cache.service';
import { WorkspaceFlatRowLevelPermissionPredicateMapCacheService } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/services/workspace-flat-row-level-permission-predicate-map-cache.service';
import { WorkspaceFlatViewFieldMapCacheService } from 'src/engine/metadata-modules/flat-view-field/services/workspace-flat-view-field-map-cache.service';
import { WorkspaceFlatViewFilterMapCacheService } from 'src/engine/metadata-modules/flat-view-filter/services/workspace-flat-view-filter-map-cache.service';
import { WorkspaceFlatViewGroupMapCacheService } from 'src/engine/metadata-modules/flat-view-group/services/workspace-flat-view-group-map-cache.service';
@@ -19,6 +21,8 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
import { PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity';
import { PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity';
import { RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity';
import { RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity';
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity';
@@ -40,6 +44,8 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
PageLayoutEntity,
PageLayoutTabEntity,
PageLayoutWidgetEntity,
RowLevelPermissionPredicateEntity,
RowLevelPermissionPredicateGroupEntity,
]),
],
providers: [
@@ -54,6 +60,8 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
WorkspaceFlatPageLayoutMapCacheService,
WorkspaceFlatPageLayoutTabMapCacheService,
WorkspaceFlatPageLayoutWidgetMapCacheService,
WorkspaceFlatRowLevelPermissionPredicateMapCacheService,
WorkspaceFlatRowLevelPermissionPredicateGroupMapCacheService,
],
exports: [
WorkspaceManyOrAllFlatEntityMapsCacheService,
@@ -67,6 +75,8 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
WorkspaceFlatPageLayoutMapCacheService,
WorkspaceFlatPageLayoutTabMapCacheService,
WorkspaceFlatPageLayoutWidgetMapCacheService,
WorkspaceFlatRowLevelPermissionPredicateMapCacheService,
WorkspaceFlatRowLevelPermissionPredicateGroupMapCacheService,
],
})
export class WorkspaceManyOrAllFlatEntityMapsCacheModule {}
@@ -8,9 +8,9 @@ import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/fla
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type';
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type';
import { type FlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/types/flat-role-target.type';
import { type FlatRole } from 'src/engine/metadata-modules/flat-role/types/flat-role.type';
import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type';
@@ -19,13 +19,17 @@ import { type FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { type IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity';
import { type PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
import { type PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity';
import { type PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity';
import { type RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
import { type RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { type RouteTriggerEntity } from 'src/engine/metadata-modules/route-trigger/route-trigger.entity';
import { type FlatRouteTrigger } from 'src/engine/metadata-modules/route-trigger/types/flat-route-trigger.type';
import { type RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity';
import { type RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity';
import { type FlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group.type';
import { type FlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate.type';
import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
import { type FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
import { type ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
@@ -61,11 +65,6 @@ import {
type DeleteObjectAction,
type UpdateObjectAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/types/workspace-migration-object-action-v2';
import {
type CreatePageLayoutAction,
type DeletePageLayoutAction,
type UpdatePageLayoutAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/page-layout/types/workspace-migration-page-layout-action-v2.type';
import {
type CreatePageLayoutTabAction,
type DeletePageLayoutTabAction,
@@ -76,6 +75,11 @@ import {
type DeletePageLayoutWidgetAction,
type UpdatePageLayoutWidgetAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/page-layout-widget/types/workspace-migration-page-layout-widget-action-v2.type';
import {
type CreatePageLayoutAction,
type DeletePageLayoutAction,
type UpdatePageLayoutAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/page-layout/types/workspace-migration-page-layout-action-v2.type';
import {
type CreateRoleTargetAction,
type DeleteRoleTargetAction,
@@ -91,6 +95,17 @@ import {
type DeleteRouteTriggerAction,
type UpdateRouteTriggerAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/route-trigger/types/workspace-migration-route-trigger-action-v2.type';
import {
type CreateRowLevelPermissionPredicateGroupAction,
type DeleteRowLevelPermissionPredicateGroupAction,
type UpdateRowLevelPermissionPredicateGroupAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/row-level-permission-predicate-group/types/workspace-migration-row-level-permission-predicate-group-action-v2.type';
import {
type CreateRowLevelPermissionPredicateAction,
type DeleteRowLevelPermissionPredicateAction,
type DestroyRowLevelPermissionPredicateAction,
type UpdateRowLevelPermissionPredicateAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/row-level-permission-predicate/types/workspace-migration-row-level-permission-predicate-action-v2.type';
import {
type CreateServerlessFunctionAction,
type DeleteServerlessFunctionAction,
@@ -163,6 +178,26 @@ export type AllFlatEntityTypesByMetadataName = {
flatEntity: FlatViewGroup;
entity: ViewGroupEntity;
};
rowLevelPermissionPredicate: {
actions: {
created: CreateRowLevelPermissionPredicateAction;
updated: UpdateRowLevelPermissionPredicateAction;
deleted:
| DeleteRowLevelPermissionPredicateAction
| DestroyRowLevelPermissionPredicateAction;
};
flatEntity: FlatRowLevelPermissionPredicate;
entity: RowLevelPermissionPredicateEntity;
};
rowLevelPermissionPredicateGroup: {
actions: {
created: CreateRowLevelPermissionPredicateGroupAction;
updated: UpdateRowLevelPermissionPredicateGroupAction;
deleted: DeleteRowLevelPermissionPredicateGroupAction;
};
flatEntity: FlatRowLevelPermissionPredicateGroup;
entity: RowLevelPermissionPredicateGroupEntity;
};
index: {
actions: {
created: CreateIndexAction;
@@ -0,0 +1,9 @@
/* @license Enterprise */
import { type FlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group.type';
export const FLAT_ROW_LEVEL_PERMISSION_PREDICATE_GROUP_EDITABLE_PROPERTIES = [
'logicalOperator',
'positionInRowLevelPermissionPredicateGroup',
'parentRowLevelPermissionPredicateGroupId',
] as const satisfies (keyof FlatRowLevelPermissionPredicateGroup)[];
@@ -0,0 +1,55 @@
/* @license Enterprise */
import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type CreateRowLevelPermissionPredicateGroupInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/create-row-level-permission-predicate-group.input';
import { type FlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group.type';
export const fromCreateRowLevelPermissionPredicateGroupInputToFlatRowLevelPermissionPredicateGroupToCreate =
({
createRowLevelPermissionPredicateGroupInput:
rawCreateRowLevelPermissionPredicateGroupInput,
workspaceId,
}: {
createRowLevelPermissionPredicateGroupInput: CreateRowLevelPermissionPredicateGroupInput;
workspaceId: string;
}): FlatRowLevelPermissionPredicateGroup => {
const sanitizedInput = (
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties as unknown as (
input: CreateRowLevelPermissionPredicateGroupInput,
keys: string[],
) => CreateRowLevelPermissionPredicateGroupInput
)(rawCreateRowLevelPermissionPredicateGroupInput, [
'roleId',
'parentRowLevelPermissionPredicateGroupId',
'logicalOperator',
]);
const { roleId, ...createRowLevelPermissionPredicateGroupInput } =
sanitizedInput;
const createdAt = new Date().toISOString();
const predicateGroupId = v4();
return {
id: predicateGroupId,
workspaceId,
roleId,
childRowLevelPermissionPredicateGroupIds: [],
rowLevelPermissionPredicateIds: [],
createdAt,
updatedAt: createdAt,
deletedAt: null,
universalIdentifier: predicateGroupId,
parentRowLevelPermissionPredicateGroupId:
createRowLevelPermissionPredicateGroupInput.parentRowLevelPermissionPredicateGroupId ??
null,
logicalOperator:
createRowLevelPermissionPredicateGroupInput.logicalOperator,
positionInRowLevelPermissionPredicateGroup:
createRowLevelPermissionPredicateGroupInput.positionInRowLevelPermissionPredicateGroup ??
null,
applicationId: null,
};
};
@@ -0,0 +1,45 @@
/* @license Enterprise */
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
} from 'twenty-shared/utils';
import { type DeleteRowLevelPermissionPredicateGroupInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/delete-row-level-permission-predicate-group.input';
import {
RowLevelPermissionPredicateGroupException,
RowLevelPermissionPredicateGroupExceptionCode,
} from 'src/engine/metadata-modules/row-level-permission-predicate/exceptions/row-level-permission-predicate-group.exception';
import { type FlatRowLevelPermissionPredicateGroupMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group-maps.type';
import { type FlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group.type';
export const fromDeleteRowLevelPermissionPredicateGroupInputToFlatRowLevelPermissionPredicateGroupOrThrow =
({
deleteRowLevelPermissionPredicateGroupInput:
rawDeleteRowLevelPermissionPredicateGroupInput,
flatRowLevelPermissionPredicateGroupMaps,
}: {
deleteRowLevelPermissionPredicateGroupInput: DeleteRowLevelPermissionPredicateGroupInput;
flatRowLevelPermissionPredicateGroupMaps: FlatRowLevelPermissionPredicateGroupMaps;
}): FlatRowLevelPermissionPredicateGroup => {
const { id: predicateGroupId } = extractAndSanitizeObjectStringFields(
rawDeleteRowLevelPermissionPredicateGroupInput,
['id'],
);
const existingFlatPredicateGroup =
flatRowLevelPermissionPredicateGroupMaps.byId[predicateGroupId];
if (!isDefined(existingFlatPredicateGroup)) {
throw new RowLevelPermissionPredicateGroupException(
t`Row level permission predicate group to delete not found`,
RowLevelPermissionPredicateGroupExceptionCode.ROW_LEVEL_PERMISSION_PREDICATE_GROUP_NOT_FOUND,
);
}
return {
...existingFlatPredicateGroup,
deletedAt: new Date().toISOString(),
};
};
@@ -0,0 +1,42 @@
/* @license Enterprise */
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
} from 'twenty-shared/utils';
import { type DestroyRowLevelPermissionPredicateGroupInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/destroy-row-level-permission-predicate-group.input';
import {
RowLevelPermissionPredicateGroupException,
RowLevelPermissionPredicateGroupExceptionCode,
} from 'src/engine/metadata-modules/row-level-permission-predicate/exceptions/row-level-permission-predicate-group.exception';
import { type FlatRowLevelPermissionPredicateGroupMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group-maps.type';
import { type FlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group.type';
export const fromDestroyRowLevelPermissionPredicateGroupInputToFlatRowLevelPermissionPredicateGroupOrThrow =
({
destroyRowLevelPermissionPredicateGroupInput:
rawDestroyRowLevelPermissionPredicateGroupInput,
flatRowLevelPermissionPredicateGroupMaps,
}: {
destroyRowLevelPermissionPredicateGroupInput: DestroyRowLevelPermissionPredicateGroupInput;
flatRowLevelPermissionPredicateGroupMaps: FlatRowLevelPermissionPredicateGroupMaps;
}): FlatRowLevelPermissionPredicateGroup => {
const { id: predicateGroupId } = extractAndSanitizeObjectStringFields(
rawDestroyRowLevelPermissionPredicateGroupInput,
['id'],
);
const existingFlatPredicateGroup =
flatRowLevelPermissionPredicateGroupMaps.byId[predicateGroupId];
if (!isDefined(existingFlatPredicateGroup)) {
throw new RowLevelPermissionPredicateGroupException(
t`Row level permission predicate group to destroy not found`,
RowLevelPermissionPredicateGroupExceptionCode.ROW_LEVEL_PERMISSION_PREDICATE_GROUP_NOT_FOUND,
);
}
return existingFlatPredicateGroup;
};
@@ -0,0 +1,10 @@
/* @license Enterprise */
import { type RowLevelPermissionPredicateGroupDTO } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/row-level-permission-predicate-group.dto';
import { type FlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group.type';
export const fromFlatRowLevelPermissionPredicateGroupToDto = (
flatGroup: FlatRowLevelPermissionPredicateGroup,
): RowLevelPermissionPredicateGroupDTO => ({
...flatGroup,
});
@@ -0,0 +1,55 @@
/* @license Enterprise */
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import { FLAT_ROW_LEVEL_PERMISSION_PREDICATE_GROUP_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-row-level-permission-predicate-group/constants/flat-row-level-permission-predicate-group-editable-properties.constant';
import { type UpdateRowLevelPermissionPredicateGroupInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/update-row-level-permission-predicate-group.input';
import {
RowLevelPermissionPredicateGroupException,
RowLevelPermissionPredicateGroupExceptionCode,
} from 'src/engine/metadata-modules/row-level-permission-predicate/exceptions/row-level-permission-predicate-group.exception';
import { type FlatRowLevelPermissionPredicateGroupMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group-maps.type';
import { type FlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group.type';
import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util';
export const fromUpdateRowLevelPermissionPredicateGroupInputToFlatRowLevelPermissionPredicateGroupToUpdateOrThrow =
({
updateRowLevelPermissionPredicateGroupInput:
rawUpdateRowLevelPermissionPredicateGroupInput,
flatRowLevelPermissionPredicateGroupMaps,
}: {
updateRowLevelPermissionPredicateGroupInput: UpdateRowLevelPermissionPredicateGroupInput;
flatRowLevelPermissionPredicateGroupMaps: FlatRowLevelPermissionPredicateGroupMaps;
}): FlatRowLevelPermissionPredicateGroup => {
const { id: predicateGroupId } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
rawUpdateRowLevelPermissionPredicateGroupInput,
['id'],
);
const existingFlatPredicateGroup =
flatRowLevelPermissionPredicateGroupMaps.byId[predicateGroupId];
if (!isDefined(existingFlatPredicateGroup)) {
throw new RowLevelPermissionPredicateGroupException(
t`Row level permission predicate group to update not found`,
RowLevelPermissionPredicateGroupExceptionCode.ROW_LEVEL_PERMISSION_PREDICATE_GROUP_NOT_FOUND,
);
}
const sanitizedUpdate = extractAndSanitizeObjectStringFields(
rawUpdateRowLevelPermissionPredicateGroupInput,
FLAT_ROW_LEVEL_PERMISSION_PREDICATE_GROUP_EDITABLE_PROPERTIES,
);
return mergeUpdateInExistingRecord({
existing: existingFlatPredicateGroup,
properties: FLAT_ROW_LEVEL_PERMISSION_PREDICATE_GROUP_EDITABLE_PROPERTIES,
update: sanitizedUpdate,
});
};
@@ -0,0 +1,13 @@
/* @license Enterprise */
import { type FlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate.type';
export const FLAT_ROW_LEVEL_PERMISSION_PREDICATE_EDITABLE_PROPERTIES = [
'operand',
'value',
'rowLevelPermissionPredicateGroupId',
'positionInRowLevelPermissionPredicateGroup',
'subFieldName',
'workspaceMemberFieldMetadataId',
'workspaceMemberSubFieldName',
] as const satisfies (keyof FlatRowLevelPermissionPredicate)[];
@@ -0,0 +1,11 @@
/* @license Enterprise */
export const ROW_LEVEL_PERMISSION_PREDICATE_ENTITY_RELATION_PROPERTIES = [
'workspace',
'role',
'fieldMetadata',
'workspaceMemberFieldMetadata',
'objectMetadata',
'rowLevelPermissionPredicateGroup',
'application',
] as const;
@@ -0,0 +1,10 @@
/* @license Enterprise */
export const ROW_LEVEL_PERMISSION_PREDICATE_GROUP_ENTITY_RELATION_PROPERTIES = [
'workspace',
'role',
'parentRowLevelPermissionPredicateGroup',
'childRowLevelPermissionPredicateGroups',
'rowLevelPermissionPredicates',
'application',
] as const;
@@ -0,0 +1,55 @@
/* @license Enterprise */
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service';
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
import { fromRowLevelPermissionPredicateGroupEntityToFlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-row-level-permission-predicate-group-entity-to-flat-row-level-permission-predicate-group.util';
import { RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity';
import { type FlatRowLevelPermissionPredicateGroupMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group-maps.type';
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
@Injectable()
@WorkspaceCache('flatRowLevelPermissionPredicateGroupMaps')
export class WorkspaceFlatRowLevelPermissionPredicateGroupMapCacheService extends WorkspaceCacheProvider<FlatRowLevelPermissionPredicateGroupMaps> {
constructor(
@InjectRepository(RowLevelPermissionPredicateGroupEntity)
private readonly rowLevelPermissionPredicateGroupRepository: Repository<RowLevelPermissionPredicateGroupEntity>,
) {
super();
}
async computeForCache(
workspaceId: string,
): Promise<FlatRowLevelPermissionPredicateGroupMaps> {
const rowLevelPermissionPredicateGroups =
await this.rowLevelPermissionPredicateGroupRepository.find({
where: {
workspaceId,
},
withDeleted: true,
});
const flatRowLevelPermissionPredicateGroupMaps =
createEmptyFlatEntityMaps();
for (const rowLevelPermissionPredicateGroupEntity of rowLevelPermissionPredicateGroups) {
const flatRowLevelPermissionPredicateGroup =
fromRowLevelPermissionPredicateGroupEntityToFlatRowLevelPermissionPredicateGroup(
rowLevelPermissionPredicateGroupEntity,
);
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
flatEntity: flatRowLevelPermissionPredicateGroup,
flatEntityMapsToMutate: flatRowLevelPermissionPredicateGroupMaps,
});
}
return flatRowLevelPermissionPredicateGroupMaps;
}
}
@@ -0,0 +1,54 @@
/* @license Enterprise */
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service';
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
import { fromRowLevelPermissionPredicateEntityToFlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-row-level-permission-predicate-entity-to-flat-row-level-permission-predicate.util';
import { RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity';
import { type FlatRowLevelPermissionPredicateMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-maps.type';
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
@Injectable()
@WorkspaceCache('flatRowLevelPermissionPredicateMaps')
export class WorkspaceFlatRowLevelPermissionPredicateMapCacheService extends WorkspaceCacheProvider<FlatRowLevelPermissionPredicateMaps> {
constructor(
@InjectRepository(RowLevelPermissionPredicateEntity)
private readonly rowLevelPermissionPredicateRepository: Repository<RowLevelPermissionPredicateEntity>,
) {
super();
}
async computeForCache(
workspaceId: string,
): Promise<FlatRowLevelPermissionPredicateMaps> {
const rowLevelPermissionPredicates =
await this.rowLevelPermissionPredicateRepository.find({
where: {
workspaceId,
},
withDeleted: true,
});
const flatRowLevelPermissionPredicateMaps = createEmptyFlatEntityMaps();
for (const rowLevelPermissionPredicateEntity of rowLevelPermissionPredicates) {
const flatRowLevelPermissionPredicate =
fromRowLevelPermissionPredicateEntityToFlatRowLevelPermissionPredicate(
rowLevelPermissionPredicateEntity,
);
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
flatEntity: flatRowLevelPermissionPredicate,
flatEntityMapsToMutate: flatRowLevelPermissionPredicateMaps,
});
}
return flatRowLevelPermissionPredicateMaps;
}
}
@@ -0,0 +1,72 @@
/* @license Enterprise */
import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type CreateRowLevelPermissionPredicateInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/create-row-level-permission-predicate.input';
import { type FlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate.type';
export const fromCreateRowLevelPermissionPredicateInputToFlatRowLevelPermissionPredicateToCreate =
({
createRowLevelPermissionPredicateInput:
rawCreateRowLevelPermissionPredicateInput,
workspaceId,
}: {
createRowLevelPermissionPredicateInput: CreateRowLevelPermissionPredicateInput;
workspaceId: string;
}): FlatRowLevelPermissionPredicate => {
const sanitizedInput = (
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties as unknown as (
input: CreateRowLevelPermissionPredicateInput,
keys: string[],
) => CreateRowLevelPermissionPredicateInput
)(rawCreateRowLevelPermissionPredicateInput, [
'fieldMetadataId',
'objectMetadataId',
'roleId',
'rowLevelPermissionPredicateGroupId',
'workspaceMemberFieldMetadataId',
'subFieldName',
'workspaceMemberSubFieldName',
'operand',
]);
const {
fieldMetadataId,
objectMetadataId,
roleId,
value,
...createRowLevelPermissionPredicateInput
} = sanitizedInput;
const createdAt = new Date().toISOString();
const predicateId = v4();
return {
id: predicateId,
fieldMetadataId,
objectMetadataId,
roleId,
workspaceId,
createdAt,
updatedAt: createdAt,
deletedAt: null,
universalIdentifier: predicateId,
operand: createRowLevelPermissionPredicateInput.operand,
value: value,
rowLevelPermissionPredicateGroupId:
createRowLevelPermissionPredicateInput.rowLevelPermissionPredicateGroupId ??
null,
positionInRowLevelPermissionPredicateGroup:
createRowLevelPermissionPredicateInput.positionInRowLevelPermissionPredicateGroup ??
null,
subFieldName: createRowLevelPermissionPredicateInput.subFieldName ?? null,
workspaceMemberFieldMetadataId:
createRowLevelPermissionPredicateInput.workspaceMemberFieldMetadataId ??
null,
workspaceMemberSubFieldName:
createRowLevelPermissionPredicateInput.workspaceMemberSubFieldName ??
null,
applicationId: null,
};
};
@@ -0,0 +1,45 @@
/* @license Enterprise */
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
} from 'twenty-shared/utils';
import { type DeleteRowLevelPermissionPredicateInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/delete-row-level-permission-predicate.input';
import {
RowLevelPermissionPredicateException,
RowLevelPermissionPredicateExceptionCode,
} from 'src/engine/metadata-modules/row-level-permission-predicate/exceptions/row-level-permission-predicate.exception';
import { type FlatRowLevelPermissionPredicateMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-maps.type';
import { type FlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate.type';
export const fromDeleteRowLevelPermissionPredicateInputToFlatRowLevelPermissionPredicateOrThrow =
({
deleteRowLevelPermissionPredicateInput:
rawDeleteRowLevelPermissionPredicateInput,
flatRowLevelPermissionPredicateMaps,
}: {
deleteRowLevelPermissionPredicateInput: DeleteRowLevelPermissionPredicateInput;
flatRowLevelPermissionPredicateMaps: FlatRowLevelPermissionPredicateMaps;
}): FlatRowLevelPermissionPredicate => {
const { id: predicateId } = extractAndSanitizeObjectStringFields(
rawDeleteRowLevelPermissionPredicateInput,
['id'],
);
const existingFlatPredicate =
flatRowLevelPermissionPredicateMaps.byId[predicateId];
if (!isDefined(existingFlatPredicate)) {
throw new RowLevelPermissionPredicateException(
t`Row level permission predicate to delete not found`,
RowLevelPermissionPredicateExceptionCode.ROW_LEVEL_PERMISSION_PREDICATE_NOT_FOUND,
);
}
return {
...existingFlatPredicate,
deletedAt: new Date().toISOString(),
};
};
@@ -0,0 +1,42 @@
/* @license Enterprise */
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
} from 'twenty-shared/utils';
import { type DestroyRowLevelPermissionPredicateInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/destroy-row-level-permission-predicate.input';
import {
RowLevelPermissionPredicateException,
RowLevelPermissionPredicateExceptionCode,
} from 'src/engine/metadata-modules/row-level-permission-predicate/exceptions/row-level-permission-predicate.exception';
import { type FlatRowLevelPermissionPredicateMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-maps.type';
import { type FlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate.type';
export const fromDestroyRowLevelPermissionPredicateInputToFlatRowLevelPermissionPredicateOrThrow =
({
destroyRowLevelPermissionPredicateInput:
rawDestroyRowLevelPermissionPredicateInput,
flatRowLevelPermissionPredicateMaps,
}: {
destroyRowLevelPermissionPredicateInput: DestroyRowLevelPermissionPredicateInput;
flatRowLevelPermissionPredicateMaps: FlatRowLevelPermissionPredicateMaps;
}): FlatRowLevelPermissionPredicate => {
const { id: predicateId } = extractAndSanitizeObjectStringFields(
rawDestroyRowLevelPermissionPredicateInput,
['id'],
);
const existingFlatPredicate =
flatRowLevelPermissionPredicateMaps.byId[predicateId];
if (!isDefined(existingFlatPredicate)) {
throw new RowLevelPermissionPredicateException(
t`Row level permission predicate to destroy not found`,
RowLevelPermissionPredicateExceptionCode.ROW_LEVEL_PERMISSION_PREDICATE_NOT_FOUND,
);
}
return existingFlatPredicate;
};
@@ -0,0 +1,10 @@
/* @license Enterprise */
import { type RowLevelPermissionPredicateDTO } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/row-level-permission-predicate.dto';
import { type FlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate.type';
export const fromFlatRowLevelPermissionPredicateToDto = (
flatPredicate: FlatRowLevelPermissionPredicate,
): RowLevelPermissionPredicateDTO => ({
...flatPredicate,
});
@@ -0,0 +1,28 @@
/* @license Enterprise */
import { removePropertiesFromRecord } from 'twenty-shared/utils';
import { ROW_LEVEL_PERMISSION_PREDICATE_ENTITY_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/constants/row-level-permission-predicate-entity-relation-properties.constant';
import { type RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity';
import { type FlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate.type';
export const fromRowLevelPermissionPredicateEntityToFlatRowLevelPermissionPredicate =
(
rowLevelPermissionPredicateEntity: RowLevelPermissionPredicateEntity,
): FlatRowLevelPermissionPredicate => {
const rowLevelPermissionPredicateEntityWithoutRelations =
removePropertiesFromRecord(rowLevelPermissionPredicateEntity, [
...ROW_LEVEL_PERMISSION_PREDICATE_ENTITY_RELATION_PROPERTIES,
] as (typeof ROW_LEVEL_PERMISSION_PREDICATE_ENTITY_RELATION_PROPERTIES)[number][]);
return {
...rowLevelPermissionPredicateEntityWithoutRelations,
createdAt: rowLevelPermissionPredicateEntity.createdAt.toISOString(),
updatedAt: rowLevelPermissionPredicateEntity.updatedAt.toISOString(),
deletedAt:
rowLevelPermissionPredicateEntity.deletedAt?.toISOString() ?? null,
universalIdentifier:
rowLevelPermissionPredicateEntityWithoutRelations.universalIdentifier ??
rowLevelPermissionPredicateEntityWithoutRelations.id,
};
};
@@ -0,0 +1,36 @@
/* @license Enterprise */
import { removePropertiesFromRecord } from 'twenty-shared/utils';
import { ROW_LEVEL_PERMISSION_PREDICATE_GROUP_ENTITY_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/constants/row-level-permission-predicate-group-entity-relation-properties.constant';
import { type RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity';
import { type FlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group.type';
export const fromRowLevelPermissionPredicateGroupEntityToFlatRowLevelPermissionPredicateGroup =
(
rowLevelPermissionPredicateGroupEntity: RowLevelPermissionPredicateGroupEntity,
): FlatRowLevelPermissionPredicateGroup => {
const rowLevelPermissionPredicateGroupEntityWithoutRelations =
removePropertiesFromRecord(rowLevelPermissionPredicateGroupEntity, [
...ROW_LEVEL_PERMISSION_PREDICATE_GROUP_ENTITY_RELATION_PROPERTIES,
] as (typeof ROW_LEVEL_PERMISSION_PREDICATE_GROUP_ENTITY_RELATION_PROPERTIES)[number][]);
return {
...rowLevelPermissionPredicateGroupEntityWithoutRelations,
createdAt: rowLevelPermissionPredicateGroupEntity.createdAt.toISOString(),
updatedAt: rowLevelPermissionPredicateGroupEntity.updatedAt.toISOString(),
deletedAt:
rowLevelPermissionPredicateGroupEntity.deletedAt?.toISOString() ?? null,
universalIdentifier:
rowLevelPermissionPredicateGroupEntityWithoutRelations.universalIdentifier ??
rowLevelPermissionPredicateGroupEntityWithoutRelations.id,
childRowLevelPermissionPredicateGroupIds:
rowLevelPermissionPredicateGroupEntity.childRowLevelPermissionPredicateGroups.map(
({ id }) => id,
),
rowLevelPermissionPredicateIds:
rowLevelPermissionPredicateGroupEntity.rowLevelPermissionPredicates.map(
({ id }) => id,
),
};
};
@@ -0,0 +1,55 @@
/* @license Enterprise */
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import { FLAT_ROW_LEVEL_PERMISSION_PREDICATE_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/constants/flat-row-level-permission-predicate-editable-properties.constant';
import { type UpdateRowLevelPermissionPredicateInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/update-row-level-permission-predicate.input';
import {
RowLevelPermissionPredicateException,
RowLevelPermissionPredicateExceptionCode,
} from 'src/engine/metadata-modules/row-level-permission-predicate/exceptions/row-level-permission-predicate.exception';
import { type FlatRowLevelPermissionPredicateMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-maps.type';
import { type FlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate.type';
import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util';
export const fromUpdateRowLevelPermissionPredicateInputToFlatRowLevelPermissionPredicateToUpdateOrThrow =
({
updateRowLevelPermissionPredicateInput:
rawUpdateRowLevelPermissionPredicateInput,
flatRowLevelPermissionPredicateMaps,
}: {
updateRowLevelPermissionPredicateInput: UpdateRowLevelPermissionPredicateInput;
flatRowLevelPermissionPredicateMaps: FlatRowLevelPermissionPredicateMaps;
}): FlatRowLevelPermissionPredicate => {
const { id: predicateId } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
rawUpdateRowLevelPermissionPredicateInput,
['id'],
);
const existingFlatPredicate =
flatRowLevelPermissionPredicateMaps.byId[predicateId];
if (!isDefined(existingFlatPredicate)) {
throw new RowLevelPermissionPredicateException(
t`Row level permission predicate to update not found`,
RowLevelPermissionPredicateExceptionCode.ROW_LEVEL_PERMISSION_PREDICATE_NOT_FOUND,
);
}
const sanitizedUpdate = extractAndSanitizeObjectStringFields(
rawUpdateRowLevelPermissionPredicateInput,
FLAT_ROW_LEVEL_PERMISSION_PREDICATE_EDITABLE_PROPERTIES,
);
return mergeUpdateInExistingRecord({
existing: existingFlatPredicate,
properties: FLAT_ROW_LEVEL_PERMISSION_PREDICATE_EDITABLE_PROPERTIES,
update: sanitizedUpdate,
});
};
@@ -0,0 +1,20 @@
/* @license Enterprise */
import { Field, InputType } from '@nestjs/graphql';
import { RowLevelPermissionPredicateGroupLogicalOperator } from 'src/engine/metadata-modules/row-level-permission-predicate/enums/row-level-permission-predicate-group-logical-operator.enum';
@InputType()
export class CreateRowLevelPermissionPredicateGroupInput {
@Field(() => String, { nullable: true })
parentRowLevelPermissionPredicateGroupId?: string | null;
@Field(() => RowLevelPermissionPredicateGroupLogicalOperator)
logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator;
@Field(() => Number, { nullable: true })
positionInRowLevelPermissionPredicateGroup?: number | null;
@Field(() => String)
roleId: string;
}
@@ -0,0 +1,41 @@
/* @license Enterprise */
import { Field, InputType } from '@nestjs/graphql';
import GraphQLJSON from 'graphql-type-json';
import { RowLevelPermissionPredicateOperand } from 'src/engine/metadata-modules/row-level-permission-predicate/enums/row-level-permission-predicate-operand';
import { RowLevelPermissionPredicateValue } from 'src/engine/metadata-modules/row-level-permission-predicate/types/row-level-permission-predicate-value.type';
@InputType()
export class CreateRowLevelPermissionPredicateInput {
@Field(() => String)
fieldMetadataId: string;
@Field(() => String)
objectMetadataId: string;
@Field(() => RowLevelPermissionPredicateOperand)
operand: RowLevelPermissionPredicateOperand;
@Field(() => GraphQLJSON, { nullable: true })
value: RowLevelPermissionPredicateValue | null;
@Field(() => String, { nullable: true })
subFieldName?: string | null;
@Field(() => String, { nullable: true })
workspaceMemberFieldMetadataId?: string | null;
@Field(() => String, { nullable: true })
workspaceMemberSubFieldName?: string | null;
@Field(() => String, { nullable: true })
rowLevelPermissionPredicateGroupId?: string | null;
@Field(() => Number, { nullable: true })
positionInRowLevelPermissionPredicateGroup?: number | null;
@Field(() => String)
roleId: string;
}
@@ -0,0 +1,9 @@
/* @license Enterprise */
import { Field, InputType } from '@nestjs/graphql';
@InputType()
export class DeleteRowLevelPermissionPredicateGroupInput {
@Field(() => String)
id: string;
}
@@ -0,0 +1,9 @@
/* @license Enterprise */
import { Field, InputType } from '@nestjs/graphql';
@InputType()
export class DeleteRowLevelPermissionPredicateInput {
@Field(() => String)
id: string;
}
@@ -0,0 +1,18 @@
/* @license Enterprise */
import { InputType } from '@nestjs/graphql';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import { IsUUID } from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@InputType()
export class DestroyRowLevelPermissionPredicateGroupInput {
@IDField(() => UUIDScalarType, {
description:
'The id of the row level permission predicate group to destroy.',
})
@IsUUID()
id: string;
}
@@ -0,0 +1,17 @@
/* @license Enterprise */
import { InputType } from '@nestjs/graphql';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import { IsUUID } from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@InputType()
export class DestroyRowLevelPermissionPredicateInput {
@IDField(() => UUIDScalarType, {
description: 'The id of the row level permission predicate to destroy.',
})
@IsUUID()
id: string;
}
@@ -0,0 +1,22 @@
/* @license Enterprise */
import { Field, InputType } from '@nestjs/graphql';
import { RowLevelPermissionPredicateGroupLogicalOperator } from 'src/engine/metadata-modules/row-level-permission-predicate/enums/row-level-permission-predicate-group-logical-operator.enum';
@InputType()
export class UpdateRowLevelPermissionPredicateGroupInput {
@Field(() => String)
id: string;
@Field(() => String, { nullable: true })
parentRowLevelPermissionPredicateGroupId?: string | null;
@Field(() => RowLevelPermissionPredicateGroupLogicalOperator, {
nullable: true,
})
logicalOperator?: RowLevelPermissionPredicateGroupLogicalOperator;
@Field(() => Number, { nullable: true })
positionInRowLevelPermissionPredicateGroup?: number | null;
}
@@ -0,0 +1,35 @@
/* @license Enterprise */
import { Field, InputType } from '@nestjs/graphql';
import GraphQLJSON from 'graphql-type-json';
import { RowLevelPermissionPredicateOperand } from 'src/engine/metadata-modules/row-level-permission-predicate/enums/row-level-permission-predicate-operand';
import { RowLevelPermissionPredicateValue } from 'src/engine/metadata-modules/row-level-permission-predicate/types/row-level-permission-predicate-value.type';
@InputType()
export class UpdateRowLevelPermissionPredicateInput {
@Field(() => String)
id: string;
@Field(() => RowLevelPermissionPredicateOperand, { nullable: true })
operand?: RowLevelPermissionPredicateOperand;
@Field(() => GraphQLJSON, { nullable: true })
value?: RowLevelPermissionPredicateValue;
@Field(() => String, { nullable: true })
subFieldName?: string | null;
@Field(() => String, { nullable: true })
workspaceMemberFieldMetadataId?: string | null;
@Field(() => String, { nullable: true })
workspaceMemberSubFieldName?: string | null;
@Field(() => String, { nullable: true })
rowLevelPermissionPredicateGroupId?: string | null;
@Field(() => Number, { nullable: true })
positionInRowLevelPermissionPredicateGroup?: number | null;
}
@@ -0,0 +1,26 @@
/* @license Enterprise */
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
import { RowLevelPermissionPredicateGroupLogicalOperator } from 'src/engine/metadata-modules/row-level-permission-predicate/enums/row-level-permission-predicate-group-logical-operator.enum';
registerEnumType(RowLevelPermissionPredicateGroupLogicalOperator, {
name: 'RowLevelPermissionPredicateGroupLogicalOperator',
});
@ObjectType('RowLevelPermissionPredicateGroup')
export class RowLevelPermissionPredicateGroupDTO {
@Field(() => String)
id: string;
@Field(() => String, { nullable: true })
parentRowLevelPermissionPredicateGroupId?: string | null;
@Field(() => RowLevelPermissionPredicateGroupLogicalOperator)
logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator;
@Field(() => Number, { nullable: true })
positionInRowLevelPermissionPredicateGroup?: number | null;
@Field(() => String)
roleId: string;
}
@@ -0,0 +1,47 @@
/* @license Enterprise */
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
import GraphQLJSON from 'graphql-type-json';
import { RowLevelPermissionPredicateOperand } from 'src/engine/metadata-modules/row-level-permission-predicate/enums/row-level-permission-predicate-operand';
import { RowLevelPermissionPredicateValue } from 'src/engine/metadata-modules/row-level-permission-predicate/types/row-level-permission-predicate-value.type';
registerEnumType(RowLevelPermissionPredicateOperand, {
name: 'RowLevelPermissionPredicateOperand',
});
@ObjectType('RowLevelPermissionPredicate')
export class RowLevelPermissionPredicateDTO {
@Field(() => String)
id: string;
@Field(() => String)
fieldMetadataId: string;
@Field(() => String)
objectMetadataId: string;
@Field(() => RowLevelPermissionPredicateOperand)
operand: RowLevelPermissionPredicateOperand;
@Field(() => String, { nullable: true })
subFieldName?: string | null;
@Field(() => String, { nullable: true })
workspaceMemberFieldMetadataId?: string | null;
@Field(() => String, { nullable: true })
workspaceMemberSubFieldName?: string | null;
@Field(() => String, { nullable: true })
rowLevelPermissionPredicateGroupId?: string | null;
@Field(() => Number, { nullable: true })
positionInRowLevelPermissionPredicateGroup?: number | null;
@Field(() => String)
roleId: string;
@Field(() => GraphQLJSON, { nullable: true })
value: RowLevelPermissionPredicateValue | null;
}
@@ -0,0 +1,82 @@
/* @license Enterprise */
import {
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
Index,
JoinColumn,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
Relation,
UpdateDateColumn,
} from 'typeorm';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity';
import { RowLevelPermissionPredicateGroupLogicalOperator } from 'src/engine/metadata-modules/row-level-permission-predicate/enums/row-level-permission-predicate-group-logical-operator.enum';
import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/types/syncable-entity.interface';
@Entity({ name: 'rowLevelPermissionPredicateGroup', schema: 'core' })
@Index('IDX_RLPPG_WORKSPACE_ID_ROLE_ID', ['workspaceId', 'roleId'])
export class RowLevelPermissionPredicateGroupEntity
extends SyncableEntity
implements Required<RowLevelPermissionPredicateGroupEntity>
{
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ nullable: true, type: 'uuid' })
parentRowLevelPermissionPredicateGroupId: string | null;
@Column({
type: 'enum',
enum: Object.values(RowLevelPermissionPredicateGroupLogicalOperator),
nullable: false,
default: RowLevelPermissionPredicateGroupLogicalOperator.AND,
})
logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator;
@Column({ nullable: true, type: 'double precision' })
positionInRowLevelPermissionPredicateGroup: number | null;
@Column({ nullable: false, type: 'uuid' })
roleId: string;
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt: Date;
@DeleteDateColumn({ type: 'timestamptz' })
deletedAt: Date | null;
@ManyToOne(() => RoleEntity, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'roleId' })
role: Relation<RoleEntity>;
@ManyToOne(
() => RowLevelPermissionPredicateGroupEntity,
(rowLevelPermissionPredicateGroup) =>
rowLevelPermissionPredicateGroup.childRowLevelPermissionPredicateGroups,
{ onDelete: 'CASCADE' },
)
@JoinColumn({ name: 'parentRowLevelPermissionPredicateGroupId' })
parentRowLevelPermissionPredicateGroup: Relation<RowLevelPermissionPredicateGroupEntity>;
@OneToMany(
() => RowLevelPermissionPredicateGroupEntity,
(rowLevelPermissionPredicateGroup) =>
rowLevelPermissionPredicateGroup.parentRowLevelPermissionPredicateGroup,
)
childRowLevelPermissionPredicateGroups: Relation<RowLevelPermissionPredicateGroupEntity>[];
@OneToMany(
() => RowLevelPermissionPredicateEntity,
(predicate) => predicate.rowLevelPermissionPredicateGroup,
)
rowLevelPermissionPredicates: Relation<RowLevelPermissionPredicateEntity>[];
}
@@ -0,0 +1,115 @@
/* @license Enterprise */
import {
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
Relation,
UpdateDateColumn,
} from 'typeorm';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity';
import { RowLevelPermissionPredicateOperand } from 'src/engine/metadata-modules/row-level-permission-predicate/enums/row-level-permission-predicate-operand';
import { type RowLevelPermissionPredicateValue } from 'src/engine/metadata-modules/row-level-permission-predicate/types/row-level-permission-predicate-value.type';
import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/types/syncable-entity.interface';
@Entity({ name: 'rowLevelPermissionPredicate', schema: 'core' })
@Index('IDX_RLPP_WORKSPACE_ID_ROLE_ID_OBJECT_METADATA_ID', [
'workspaceId',
'roleId',
'objectMetadataId',
])
@Index('IDX_RLPP_FIELD_METADATA_ID', ['fieldMetadataId'])
@Index('IDX_RLPP_GROUP_ID', ['rowLevelPermissionPredicateGroupId'])
export class RowLevelPermissionPredicateEntity
extends SyncableEntity
implements Required<RowLevelPermissionPredicateEntity>
{
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ nullable: false, type: 'uuid' })
fieldMetadataId: string;
@ManyToOne(() => FieldMetadataEntity, {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'fieldMetadataId' })
fieldMetadata: Relation<FieldMetadataEntity>;
@Column({ nullable: false, type: 'uuid' })
objectMetadataId: string;
@ManyToOne(() => ObjectMetadataEntity, {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'objectMetadataId' })
objectMetadata: Relation<ObjectMetadataEntity>;
@Column({
nullable: false,
type: 'enum',
enum: Object.values(RowLevelPermissionPredicateOperand),
default: RowLevelPermissionPredicateOperand.CONTAINS,
})
operand: RowLevelPermissionPredicateOperand;
@Column({ nullable: true, type: 'jsonb' })
value: RowLevelPermissionPredicateValue | null;
@Column({ nullable: true, type: 'text', default: null })
subFieldName: string | null;
@Column({ nullable: true, type: 'uuid' })
workspaceMemberFieldMetadataId: string | null;
@Column({ nullable: true, type: 'text', default: null })
workspaceMemberSubFieldName: string | null;
@ManyToOne(() => FieldMetadataEntity, {
onDelete: 'SET NULL',
})
@JoinColumn({ name: 'workspaceMemberFieldMetadataId' })
workspaceMemberFieldMetadata: Relation<FieldMetadataEntity>;
@Column({ nullable: true, type: 'uuid' })
rowLevelPermissionPredicateGroupId: string | null;
@Column({ nullable: true, type: 'double precision' })
positionInRowLevelPermissionPredicateGroup: number | null;
@Column({ nullable: false, type: 'uuid' })
roleId: string;
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt: Date;
@DeleteDateColumn({ type: 'timestamptz' })
deletedAt: Date | null;
@ManyToOne(() => RoleEntity, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'roleId' })
role: Relation<RoleEntity>;
@ManyToOne(
() => RowLevelPermissionPredicateGroupEntity,
(rowLevelPermissionPredicateGroup) =>
rowLevelPermissionPredicateGroup.rowLevelPermissionPredicates,
{
onDelete: 'CASCADE',
},
)
@JoinColumn({ name: 'rowLevelPermissionPredicateGroupId' })
rowLevelPermissionPredicateGroup: Relation<RowLevelPermissionPredicateGroupEntity>;
}
@@ -0,0 +1,6 @@
/* @license Enterprise */
export enum RowLevelPermissionPredicateGroupLogicalOperator {
AND = 'AND',
OR = 'OR',
}
@@ -0,0 +1,20 @@
/* @license Enterprise */
export enum RowLevelPermissionPredicateOperand {
IS = 'IS',
IS_NOT_NULL = 'IS_NOT_NULL',
IS_NOT = 'IS_NOT',
LESS_THAN_OR_EQUAL = 'LESS_THAN_OR_EQUAL',
GREATER_THAN_OR_EQUAL = 'GREATER_THAN_OR_EQUAL',
IS_BEFORE = 'IS_BEFORE',
IS_AFTER = 'IS_AFTER',
CONTAINS = 'CONTAINS',
DOES_NOT_CONTAIN = 'DOES_NOT_CONTAIN',
IS_EMPTY = 'IS_EMPTY',
IS_NOT_EMPTY = 'IS_NOT_EMPTY',
IS_RELATIVE = 'IS_RELATIVE',
IS_IN_PAST = 'IS_IN_PAST',
IS_IN_FUTURE = 'IS_IN_FUTURE',
IS_TODAY = 'IS_TODAY',
VECTOR_SEARCH = 'VECTOR_SEARCH',
}
@@ -0,0 +1,44 @@
/* @license Enterprise */
import { type MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import {
appendCommonExceptionCode,
CustomException,
} from 'src/utils/custom-exception';
export const RowLevelPermissionPredicateGroupExceptionCode =
appendCommonExceptionCode({
ROW_LEVEL_PERMISSION_PREDICATE_GROUP_NOT_FOUND:
'ROW_LEVEL_PERMISSION_PREDICATE_GROUP_NOT_FOUND',
INVALID_ROW_LEVEL_PERMISSION_PREDICATE_GROUP_DATA:
'INVALID_ROW_LEVEL_PERMISSION_PREDICATE_GROUP_DATA',
ROLE_NOT_FOUND: 'ROLE_NOT_FOUND',
} as const);
const rowLevelPermissionPredicateGroupExceptionUserFriendlyMessages: Record<
keyof typeof RowLevelPermissionPredicateGroupExceptionCode,
MessageDescriptor
> = {
ROW_LEVEL_PERMISSION_PREDICATE_GROUP_NOT_FOUND: msg`Row level permission predicate group not found.`,
INVALID_ROW_LEVEL_PERMISSION_PREDICATE_GROUP_DATA: msg`Invalid row level permission predicate group data.`,
ROLE_NOT_FOUND: msg`Role not found.`,
INTERNAL_SERVER_ERROR: msg`An unexpected error occurred.`,
};
export class RowLevelPermissionPredicateGroupException extends CustomException<
keyof typeof RowLevelPermissionPredicateGroupExceptionCode
> {
constructor(
message: string,
code: keyof typeof RowLevelPermissionPredicateGroupExceptionCode,
{ userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {},
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
rowLevelPermissionPredicateGroupExceptionUserFriendlyMessages[code],
});
}
}
@@ -0,0 +1,48 @@
/* @license Enterprise */
import { type MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import {
appendCommonExceptionCode,
CustomException,
} from 'src/utils/custom-exception';
export const RowLevelPermissionPredicateExceptionCode =
appendCommonExceptionCode({
ROW_LEVEL_PERMISSION_PREDICATE_NOT_FOUND:
'ROW_LEVEL_PERMISSION_PREDICATE_NOT_FOUND',
INVALID_ROW_LEVEL_PERMISSION_PREDICATE_DATA:
'INVALID_ROW_LEVEL_PERMISSION_PREDICATE_DATA',
FIELD_METADATA_NOT_FOUND: 'FIELD_METADATA_NOT_FOUND',
OBJECT_METADATA_NOT_FOUND: 'OBJECT_METADATA_NOT_FOUND',
ROLE_NOT_FOUND: 'ROLE_NOT_FOUND',
} as const);
const rowLevelPermissionPredicateExceptionUserFriendlyMessages: Record<
keyof typeof RowLevelPermissionPredicateExceptionCode,
MessageDescriptor
> = {
ROW_LEVEL_PERMISSION_PREDICATE_NOT_FOUND: msg`Row level permission predicate not found.`,
INVALID_ROW_LEVEL_PERMISSION_PREDICATE_DATA: msg`Invalid row level permission predicate data.`,
FIELD_METADATA_NOT_FOUND: msg`Field metadata not found.`,
OBJECT_METADATA_NOT_FOUND: msg`Object metadata not found.`,
ROLE_NOT_FOUND: msg`Role not found.`,
INTERNAL_SERVER_ERROR: msg`An unexpected error occurred.`,
};
export class RowLevelPermissionPredicateException extends CustomException<
keyof typeof RowLevelPermissionPredicateExceptionCode
> {
constructor(
message: string,
code: keyof typeof RowLevelPermissionPredicateExceptionCode,
{ userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {},
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
rowLevelPermissionPredicateExceptionUserFriendlyMessages[code],
});
}
}
@@ -0,0 +1,81 @@
/* @license Enterprise */
import { UseGuards } from '@nestjs/common';
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { CreateRowLevelPermissionPredicateGroupInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/create-row-level-permission-predicate-group.input';
import { DeleteRowLevelPermissionPredicateGroupInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/delete-row-level-permission-predicate-group.input';
import { UpdateRowLevelPermissionPredicateGroupInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/update-row-level-permission-predicate-group.input';
import { RowLevelPermissionPredicateGroupDTO } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/row-level-permission-predicate-group.dto';
import { RowLevelPermissionPredicateGroupLogicalOperator } from 'src/engine/metadata-modules/row-level-permission-predicate/enums/row-level-permission-predicate-group-logical-operator.enum';
import { RowLevelPermissionPredicateGroupService } from 'src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate-group.service';
@Resolver(() => RowLevelPermissionPredicateGroupDTO)
@UseGuards(WorkspaceAuthGuard, NoPermissionGuard)
export class RowLevelPermissionPredicateGroupResolver {
constructor(
private readonly predicateGroupService: RowLevelPermissionPredicateGroupService,
) {}
@Query(() => [RowLevelPermissionPredicateGroupDTO])
async getRowLevelPermissionPredicateGroups(
@AuthWorkspace() workspace: WorkspaceEntity,
@Args('roleId', { type: () => String, nullable: true }) roleId?: string,
): Promise<RowLevelPermissionPredicateGroupDTO[]> {
if (roleId) {
return this.predicateGroupService.findByRole(workspace.id, roleId);
}
return this.predicateGroupService.findByWorkspaceId(workspace.id);
}
@Mutation(() => RowLevelPermissionPredicateGroupDTO)
async createRowLevelPermissionPredicateGroup(
@Args('input')
input: CreateRowLevelPermissionPredicateGroupInput,
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise<RowLevelPermissionPredicateGroupDTO> {
return this.predicateGroupService.createOne({
createRowLevelPermissionPredicateGroupInput: {
...input,
logicalOperator:
input.logicalOperator as RowLevelPermissionPredicateGroupLogicalOperator,
},
workspaceId: workspace.id,
});
}
@Mutation(() => RowLevelPermissionPredicateGroupDTO)
async updateRowLevelPermissionPredicateGroup(
@Args('input')
input: UpdateRowLevelPermissionPredicateGroupInput,
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise<RowLevelPermissionPredicateGroupDTO> {
return this.predicateGroupService.updateOne({
updateRowLevelPermissionPredicateGroupInput: {
...input,
logicalOperator:
input.logicalOperator as RowLevelPermissionPredicateGroupLogicalOperator,
},
workspaceId: workspace.id,
});
}
@Mutation(() => RowLevelPermissionPredicateGroupDTO)
async deleteRowLevelPermissionPredicateGroup(
@Args('input')
input: DeleteRowLevelPermissionPredicateGroupInput,
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise<RowLevelPermissionPredicateGroupDTO> {
return this.predicateGroupService.deleteOne({
deleteRowLevelPermissionPredicateGroupInput: {
...input,
},
workspaceId: workspace.id,
});
}
}
@@ -0,0 +1,95 @@
/* @license Enterprise */
import { UseGuards } from '@nestjs/common';
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { CreateRowLevelPermissionPredicateInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/create-row-level-permission-predicate.input';
import { DeleteRowLevelPermissionPredicateInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/delete-row-level-permission-predicate.input';
import { UpdateRowLevelPermissionPredicateInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/update-row-level-permission-predicate.input';
import { RowLevelPermissionPredicateDTO } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/row-level-permission-predicate.dto';
import { RowLevelPermissionPredicateOperand } from 'src/engine/metadata-modules/row-level-permission-predicate/enums/row-level-permission-predicate-operand';
import { RowLevelPermissionPredicateService } from 'src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate.service';
@Resolver(() => RowLevelPermissionPredicateDTO)
@UseGuards(WorkspaceAuthGuard, NoPermissionGuard)
export class RowLevelPermissionPredicateResolver {
constructor(
private readonly predicateService: RowLevelPermissionPredicateService,
) {}
@Query(() => [RowLevelPermissionPredicateDTO])
async getRowLevelPermissionPredicates(
@AuthWorkspace() workspace: WorkspaceEntity,
@Args('roleId', { type: () => String, nullable: true }) roleId?: string,
@Args('objectMetadataId', { type: () => String, nullable: true })
objectMetadataId?: string,
): Promise<RowLevelPermissionPredicateDTO[]> {
if (roleId && objectMetadataId) {
return this.predicateService.findByRoleAndObject(
workspace.id,
roleId,
objectMetadataId,
);
}
return this.predicateService.findByWorkspaceId(workspace.id);
}
@Query(() => RowLevelPermissionPredicateDTO, { nullable: true })
async getRowLevelPermissionPredicate(
@Args('id', { type: () => String }) id: string,
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise<RowLevelPermissionPredicateDTO | null> {
return this.predicateService.findById(id, workspace.id);
}
@Mutation(() => RowLevelPermissionPredicateDTO)
async createRowLevelPermissionPredicate(
@Args('input')
input: CreateRowLevelPermissionPredicateInput,
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise<RowLevelPermissionPredicateDTO> {
return this.predicateService.createOne({
createRowLevelPermissionPredicateInput: {
...input,
operand: input.operand as RowLevelPermissionPredicateOperand,
value: input.value,
},
workspaceId: workspace.id,
});
}
@Mutation(() => RowLevelPermissionPredicateDTO)
async updateRowLevelPermissionPredicate(
@Args('input')
input: UpdateRowLevelPermissionPredicateInput,
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise<RowLevelPermissionPredicateDTO> {
return this.predicateService.updateOne({
updateRowLevelPermissionPredicateInput: {
...input,
operand: input.operand as RowLevelPermissionPredicateOperand,
value: input.value,
},
workspaceId: workspace.id,
});
}
@Mutation(() => RowLevelPermissionPredicateDTO)
async deleteRowLevelPermissionPredicate(
@Args('input')
input: DeleteRowLevelPermissionPredicateInput,
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise<RowLevelPermissionPredicateDTO> {
return this.predicateService.deleteOne({
deleteRowLevelPermissionPredicateInput: {
...input,
},
workspaceId: workspace.id,
});
}
}
@@ -0,0 +1,37 @@
/* @license Enterprise */
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity';
import { RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity';
import { RowLevelPermissionPredicateGroupResolver } from 'src/engine/metadata-modules/row-level-permission-predicate/resolvers/row-level-permission-predicate-group.resolver';
import { RowLevelPermissionPredicateResolver } from 'src/engine/metadata-modules/row-level-permission-predicate/resolvers/row-level-permission-predicate.resolver';
import { RowLevelPermissionPredicateGroupService } from 'src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate-group.service';
import { RowLevelPermissionPredicateService } from 'src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate.service';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module';
@Module({
imports: [
TypeOrmModule.forFeature([
RowLevelPermissionPredicateEntity,
RowLevelPermissionPredicateGroupEntity,
]),
WorkspaceCacheModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
WorkspaceMigrationV2Module,
],
providers: [
RowLevelPermissionPredicateService,
RowLevelPermissionPredicateGroupService,
RowLevelPermissionPredicateResolver,
RowLevelPermissionPredicateGroupResolver,
],
exports: [
RowLevelPermissionPredicateService,
RowLevelPermissionPredicateGroupService,
],
})
export class RowLevelPermissionModule {}
@@ -0,0 +1,298 @@
/* @license Enterprise */
import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { fromCreateRowLevelPermissionPredicateGroupInputToFlatRowLevelPermissionPredicateGroupToCreate } from 'src/engine/metadata-modules/flat-row-level-permission-predicate-group/utils/from-create-row-level-permission-predicate-group-input-to-flat-row-level-permission-predicate-group-to-create.util';
import { fromDeleteRowLevelPermissionPredicateGroupInputToFlatRowLevelPermissionPredicateGroupOrThrow } from 'src/engine/metadata-modules/flat-row-level-permission-predicate-group/utils/from-delete-row-level-permission-predicate-group-input-to-flat-row-level-permission-predicate-group-or-throw.util';
import { fromDestroyRowLevelPermissionPredicateGroupInputToFlatRowLevelPermissionPredicateGroupOrThrow } from 'src/engine/metadata-modules/flat-row-level-permission-predicate-group/utils/from-destroy-row-level-permission-predicate-group-input-to-flat-row-level-permission-predicate-group-or-throw.util';
import { fromFlatRowLevelPermissionPredicateGroupToDto } from 'src/engine/metadata-modules/flat-row-level-permission-predicate-group/utils/from-flat-row-level-permission-predicate-group-to-dto.util';
import { fromUpdateRowLevelPermissionPredicateGroupInputToFlatRowLevelPermissionPredicateGroupToUpdateOrThrow } from 'src/engine/metadata-modules/flat-row-level-permission-predicate-group/utils/from-update-row-level-permission-predicate-group-input-to-flat-row-level-permission-predicate-group-to-update-or-throw.util';
import { type CreateRowLevelPermissionPredicateGroupInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/create-row-level-permission-predicate-group.input';
import { type DeleteRowLevelPermissionPredicateGroupInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/delete-row-level-permission-predicate-group.input';
import { type DestroyRowLevelPermissionPredicateGroupInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/destroy-row-level-permission-predicate-group.input';
import { type UpdateRowLevelPermissionPredicateGroupInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/update-row-level-permission-predicate-group.input';
import { RowLevelPermissionPredicateGroupDTO } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/row-level-permission-predicate-group.dto';
import { type FlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group.type';
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
const RLP_CACHE_KEYS = [
'flatRowLevelPermissionPredicateMaps',
'flatRowLevelPermissionPredicateGroupMaps',
] as const;
@Injectable()
export class RowLevelPermissionPredicateGroupService {
constructor(
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
) {}
async createOne({
createRowLevelPermissionPredicateGroupInput,
workspaceId,
}: {
createRowLevelPermissionPredicateGroupInput: CreateRowLevelPermissionPredicateGroupInput;
workspaceId: string;
}): Promise<RowLevelPermissionPredicateGroupDTO> {
const flatGroupToCreate =
fromCreateRowLevelPermissionPredicateGroupInputToFlatRowLevelPermissionPredicateGroupToCreate(
{
createRowLevelPermissionPredicateGroupInput,
workspaceId,
},
);
await this.runMigration({
workspaceId,
flatEntityToCreate: [flatGroupToCreate],
});
const { flatRowLevelPermissionPredicateGroupMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatRowLevelPermissionPredicateGroupMaps'],
},
);
return fromFlatRowLevelPermissionPredicateGroupToDto(
findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: flatGroupToCreate.id,
flatEntityMaps: flatRowLevelPermissionPredicateGroupMaps,
}),
);
}
async updateOne({
updateRowLevelPermissionPredicateGroupInput,
workspaceId,
}: {
updateRowLevelPermissionPredicateGroupInput: UpdateRowLevelPermissionPredicateGroupInput;
workspaceId: string;
}): Promise<RowLevelPermissionPredicateGroupDTO> {
const { flatRowLevelPermissionPredicateGroupMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatRowLevelPermissionPredicateGroupMaps'],
},
);
const flatGroupToUpdate =
fromUpdateRowLevelPermissionPredicateGroupInputToFlatRowLevelPermissionPredicateGroupToUpdateOrThrow(
{
flatRowLevelPermissionPredicateGroupMaps,
updateRowLevelPermissionPredicateGroupInput,
},
);
await this.runMigration({
workspaceId,
flatEntityToUpdate: [flatGroupToUpdate],
});
const { flatRowLevelPermissionPredicateGroupMaps: recomputedFlatMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatRowLevelPermissionPredicateGroupMaps'],
},
);
return fromFlatRowLevelPermissionPredicateGroupToDto(
findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: flatGroupToUpdate.id,
flatEntityMaps: recomputedFlatMaps,
}),
);
}
async deleteOne({
deleteRowLevelPermissionPredicateGroupInput,
workspaceId,
}: {
deleteRowLevelPermissionPredicateGroupInput: DeleteRowLevelPermissionPredicateGroupInput;
workspaceId: string;
}): Promise<RowLevelPermissionPredicateGroupDTO> {
const { flatRowLevelPermissionPredicateGroupMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatRowLevelPermissionPredicateGroupMaps'],
},
);
const flatGroupWithDeletedAt =
fromDeleteRowLevelPermissionPredicateGroupInputToFlatRowLevelPermissionPredicateGroupOrThrow(
{
deleteRowLevelPermissionPredicateGroupInput,
flatRowLevelPermissionPredicateGroupMaps,
},
);
await this.runMigration({
workspaceId,
flatEntityToUpdate: [flatGroupWithDeletedAt],
});
const { flatRowLevelPermissionPredicateGroupMaps: recomputedFlatMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatRowLevelPermissionPredicateGroupMaps'],
},
);
return fromFlatRowLevelPermissionPredicateGroupToDto(
findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: flatGroupWithDeletedAt.id,
flatEntityMaps: recomputedFlatMaps,
}),
);
}
async destroyOne({
destroyRowLevelPermissionPredicateGroupInput,
workspaceId,
}: {
destroyRowLevelPermissionPredicateGroupInput: DestroyRowLevelPermissionPredicateGroupInput;
workspaceId: string;
}): Promise<RowLevelPermissionPredicateGroupDTO> {
const { flatRowLevelPermissionPredicateGroupMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatRowLevelPermissionPredicateGroupMaps'],
},
);
const flatGroupToDelete =
fromDestroyRowLevelPermissionPredicateGroupInputToFlatRowLevelPermissionPredicateGroupOrThrow(
{
destroyRowLevelPermissionPredicateGroupInput,
flatRowLevelPermissionPredicateGroupMaps,
},
);
await this.runMigration({
workspaceId,
flatEntityToDelete: [flatGroupToDelete],
});
return fromFlatRowLevelPermissionPredicateGroupToDto(flatGroupToDelete);
}
async findByWorkspaceId(
workspaceId: string,
): Promise<RowLevelPermissionPredicateGroupDTO[]> {
const { flatRowLevelPermissionPredicateGroupMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatRowLevelPermissionPredicateGroupMaps'],
},
);
return Object.values(flatRowLevelPermissionPredicateGroupMaps.byId)
.filter(isDefined)
.filter((group) => group.deletedAt === null)
.sort(
(a, b) =>
(a.positionInRowLevelPermissionPredicateGroup ?? 0) -
(b.positionInRowLevelPermissionPredicateGroup ?? 0),
)
.map(fromFlatRowLevelPermissionPredicateGroupToDto);
}
async findByRole(
workspaceId: string,
roleId: string,
): Promise<RowLevelPermissionPredicateGroupDTO[]> {
const { flatRowLevelPermissionPredicateGroupMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatRowLevelPermissionPredicateGroupMaps'],
},
);
return Object.values(flatRowLevelPermissionPredicateGroupMaps.byId)
.filter(isDefined)
.filter((group) => group.deletedAt === null && group.roleId === roleId)
.sort(
(a, b) =>
(a.positionInRowLevelPermissionPredicateGroup ?? 0) -
(b.positionInRowLevelPermissionPredicateGroup ?? 0),
)
.map(fromFlatRowLevelPermissionPredicateGroupToDto);
}
async findById(
id: string,
workspaceId: string,
): Promise<RowLevelPermissionPredicateGroupDTO | null> {
const { flatRowLevelPermissionPredicateGroupMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatRowLevelPermissionPredicateGroupMaps'],
},
);
const flatGroup = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: id,
flatEntityMaps: flatRowLevelPermissionPredicateGroupMaps,
});
if (!isDefined(flatGroup) || flatGroup.deletedAt !== null) {
return null;
}
return fromFlatRowLevelPermissionPredicateGroupToDto(flatGroup);
}
private async runMigration({
workspaceId,
flatEntityToCreate = [],
flatEntityToUpdate = [],
flatEntityToDelete = [],
}: {
workspaceId: string;
flatEntityToCreate?: FlatRowLevelPermissionPredicateGroup[];
flatEntityToUpdate?: FlatRowLevelPermissionPredicateGroup[];
flatEntityToDelete?: FlatRowLevelPermissionPredicateGroup[];
}): Promise<void> {
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
rowLevelPermissionPredicateGroup: {
flatEntityToCreate,
flatEntityToDelete,
flatEntityToUpdate,
},
},
workspaceId,
isSystemBuild: false,
},
);
if (isDefined(validateAndBuildResult)) {
throw new WorkspaceMigrationBuilderExceptionV2(
validateAndBuildResult,
'Validation errors occurred while applying row level permission predicate group mutation',
);
}
await this.flatEntityMapsCacheService.invalidateFlatEntityMaps({
workspaceId,
flatMapsKeys: [...RLP_CACHE_KEYS],
});
}
}
@@ -0,0 +1,304 @@
/* @license Enterprise */
import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { fromCreateRowLevelPermissionPredicateInputToFlatRowLevelPermissionPredicateToCreate } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-create-row-level-permission-predicate-input-to-flat-row-level-permission-predicate-to-create.util';
import { fromDeleteRowLevelPermissionPredicateInputToFlatRowLevelPermissionPredicateOrThrow } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-delete-row-level-permission-predicate-input-to-flat-row-level-permission-predicate-or-throw.util';
import { fromDestroyRowLevelPermissionPredicateInputToFlatRowLevelPermissionPredicateOrThrow } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-destroy-row-level-permission-predicate-input-to-flat-row-level-permission-predicate-or-throw.util';
import { fromFlatRowLevelPermissionPredicateToDto } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-flat-row-level-permission-predicate-to-dto.util';
import { fromUpdateRowLevelPermissionPredicateInputToFlatRowLevelPermissionPredicateToUpdateOrThrow } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-update-row-level-permission-predicate-input-to-flat-row-level-permission-predicate-to-update-or-throw.util';
import { type CreateRowLevelPermissionPredicateInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/create-row-level-permission-predicate.input';
import { type DeleteRowLevelPermissionPredicateInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/delete-row-level-permission-predicate.input';
import { type DestroyRowLevelPermissionPredicateInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/destroy-row-level-permission-predicate.input';
import { type UpdateRowLevelPermissionPredicateInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/update-row-level-permission-predicate.input';
import { RowLevelPermissionPredicateDTO } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/row-level-permission-predicate.dto';
import { type FlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate.type';
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
const RLP_CACHE_KEYS = [
'flatRowLevelPermissionPredicateMaps',
'flatRowLevelPermissionPredicateGroupMaps',
] as const;
@Injectable()
export class RowLevelPermissionPredicateService {
constructor(
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
) {}
async createOne({
createRowLevelPermissionPredicateInput,
workspaceId,
}: {
createRowLevelPermissionPredicateInput: CreateRowLevelPermissionPredicateInput;
workspaceId: string;
}): Promise<RowLevelPermissionPredicateDTO> {
const flatPredicateToCreate =
fromCreateRowLevelPermissionPredicateInputToFlatRowLevelPermissionPredicateToCreate(
{
createRowLevelPermissionPredicateInput,
workspaceId,
},
);
await this.runMigration({
workspaceId,
flatEntityToCreate: [flatPredicateToCreate],
});
const { flatRowLevelPermissionPredicateMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatRowLevelPermissionPredicateMaps'],
},
);
return fromFlatRowLevelPermissionPredicateToDto(
findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: flatPredicateToCreate.id,
flatEntityMaps: flatRowLevelPermissionPredicateMaps,
}),
);
}
async updateOne({
updateRowLevelPermissionPredicateInput,
workspaceId,
}: {
updateRowLevelPermissionPredicateInput: UpdateRowLevelPermissionPredicateInput;
workspaceId: string;
}): Promise<RowLevelPermissionPredicateDTO> {
const { flatRowLevelPermissionPredicateMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatRowLevelPermissionPredicateMaps'],
},
);
const flatPredicateToUpdate =
fromUpdateRowLevelPermissionPredicateInputToFlatRowLevelPermissionPredicateToUpdateOrThrow(
{
flatRowLevelPermissionPredicateMaps,
updateRowLevelPermissionPredicateInput,
},
);
await this.runMigration({
workspaceId,
flatEntityToUpdate: [flatPredicateToUpdate],
});
const { flatRowLevelPermissionPredicateMaps: recomputedFlatMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatRowLevelPermissionPredicateMaps'],
},
);
return fromFlatRowLevelPermissionPredicateToDto(
findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: flatPredicateToUpdate.id,
flatEntityMaps: recomputedFlatMaps,
}),
);
}
async deleteOne({
deleteRowLevelPermissionPredicateInput,
workspaceId,
}: {
deleteRowLevelPermissionPredicateInput: DeleteRowLevelPermissionPredicateInput;
workspaceId: string;
}): Promise<RowLevelPermissionPredicateDTO> {
const { flatRowLevelPermissionPredicateMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatRowLevelPermissionPredicateMaps'],
},
);
const flatPredicateWithDeletedAt =
fromDeleteRowLevelPermissionPredicateInputToFlatRowLevelPermissionPredicateOrThrow(
{
deleteRowLevelPermissionPredicateInput,
flatRowLevelPermissionPredicateMaps,
},
);
await this.runMigration({
workspaceId,
flatEntityToUpdate: [flatPredicateWithDeletedAt],
});
const { flatRowLevelPermissionPredicateMaps: recomputedFlatMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatRowLevelPermissionPredicateMaps'],
},
);
return fromFlatRowLevelPermissionPredicateToDto(
findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: flatPredicateWithDeletedAt.id,
flatEntityMaps: recomputedFlatMaps,
}),
);
}
async destroyOne({
destroyRowLevelPermissionPredicateInput,
workspaceId,
}: {
destroyRowLevelPermissionPredicateInput: DestroyRowLevelPermissionPredicateInput;
workspaceId: string;
}): Promise<RowLevelPermissionPredicateDTO> {
const { flatRowLevelPermissionPredicateMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatRowLevelPermissionPredicateMaps'],
},
);
const flatPredicateToDelete =
fromDestroyRowLevelPermissionPredicateInputToFlatRowLevelPermissionPredicateOrThrow(
{
destroyRowLevelPermissionPredicateInput,
flatRowLevelPermissionPredicateMaps,
},
);
await this.runMigration({
workspaceId,
flatEntityToDelete: [flatPredicateToDelete],
});
return fromFlatRowLevelPermissionPredicateToDto(flatPredicateToDelete);
}
async findByWorkspaceId(
workspaceId: string,
): Promise<RowLevelPermissionPredicateDTO[]> {
const { flatRowLevelPermissionPredicateMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatRowLevelPermissionPredicateMaps'],
},
);
return Object.values(flatRowLevelPermissionPredicateMaps.byId)
.filter(isDefined)
.filter((predicate) => predicate.deletedAt === null)
.sort(
(a, b) =>
(a.positionInRowLevelPermissionPredicateGroup ?? 0) -
(b.positionInRowLevelPermissionPredicateGroup ?? 0),
)
.map(fromFlatRowLevelPermissionPredicateToDto);
}
async findByRoleAndObject(
workspaceId: string,
roleId: string,
objectMetadataId: string,
): Promise<RowLevelPermissionPredicateDTO[]> {
const { flatRowLevelPermissionPredicateMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatRowLevelPermissionPredicateMaps'],
},
);
return Object.values(flatRowLevelPermissionPredicateMaps.byId)
.filter(isDefined)
.filter(
(predicate) =>
predicate.deletedAt === null &&
predicate.roleId === roleId &&
predicate.objectMetadataId === objectMetadataId,
)
.sort(
(a, b) =>
(a.positionInRowLevelPermissionPredicateGroup ?? 0) -
(b.positionInRowLevelPermissionPredicateGroup ?? 0),
)
.map(fromFlatRowLevelPermissionPredicateToDto);
}
async findById(
id: string,
workspaceId: string,
): Promise<RowLevelPermissionPredicateDTO | null> {
const { flatRowLevelPermissionPredicateMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatRowLevelPermissionPredicateMaps'],
},
);
const flatPredicate = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: id,
flatEntityMaps: flatRowLevelPermissionPredicateMaps,
});
if (!isDefined(flatPredicate) || flatPredicate.deletedAt !== null) {
return null;
}
return fromFlatRowLevelPermissionPredicateToDto(flatPredicate);
}
private async runMigration({
workspaceId,
flatEntityToCreate = [],
flatEntityToUpdate = [],
flatEntityToDelete = [],
}: {
workspaceId: string;
flatEntityToCreate?: FlatRowLevelPermissionPredicate[];
flatEntityToUpdate?: FlatRowLevelPermissionPredicate[];
flatEntityToDelete?: FlatRowLevelPermissionPredicate[];
}): Promise<void> {
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
rowLevelPermissionPredicate: {
flatEntityToCreate,
flatEntityToDelete,
flatEntityToUpdate,
},
},
workspaceId,
isSystemBuild: false,
},
);
if (isDefined(validateAndBuildResult)) {
throw new WorkspaceMigrationBuilderExceptionV2(
validateAndBuildResult,
'Validation errors occurred while applying row level permission predicate mutation',
);
}
await this.flatEntityMapsCacheService.invalidateFlatEntityMaps({
workspaceId,
flatMapsKeys: [...RLP_CACHE_KEYS],
});
}
}
@@ -0,0 +1,7 @@
/* @license Enterprise */
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group.type';
export type FlatRowLevelPermissionPredicateGroupMaps =
FlatEntityMaps<FlatRowLevelPermissionPredicateGroup>;
@@ -0,0 +1,10 @@
/* @license Enterprise */
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { type RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity';
export type FlatRowLevelPermissionPredicateGroup =
FlatEntityFrom<RowLevelPermissionPredicateGroupEntity> & {
childRowLevelPermissionPredicateGroupIds: string[];
rowLevelPermissionPredicateIds: string[];
};
@@ -0,0 +1,7 @@
/* @license Enterprise */
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate.type';
export type FlatRowLevelPermissionPredicateMaps =
FlatEntityMaps<FlatRowLevelPermissionPredicate>;
@@ -0,0 +1,7 @@
/* @license Enterprise */
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { type RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity';
export type FlatRowLevelPermissionPredicate =
FlatEntityFrom<RowLevelPermissionPredicateEntity>;
@@ -0,0 +1,16 @@
/* @license Enterprise */
export type RelationPredicateValue = {
isCurrentWorkspaceMemberSelected?: boolean;
selectedRecordIds: string[];
};
export type RowLevelPermissionPredicateValue =
| string
| string[]
| boolean
| number
| RelationPredicateValue
| Record<string, unknown>
| null
| undefined;
@@ -14,13 +14,13 @@ import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrap
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { INTERNAL_SERVER_ERROR } from 'src/engine/middlewares/constants/default-error-message.constant';
import { bindDataToRequestObject } from 'src/engine/utils/bind-data-to-request-object.util';
import {
handleException,
handleExceptionAndConvertToGraphQLError,
} from 'src/engine/utils/global-exception-handler.util';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
import { type CustomException } from 'src/utils/custom-exception';
import { bindDataToRequestObject } from 'src/engine/utils/bind-data-to-request-object.util';
@Injectable()
export class MiddlewareService {
@@ -193,6 +193,16 @@ describe('WorkspaceEntityManager', () => {
idByUniversalIdentifier: {},
universalIdentifiersByApplicationId: {},
},
flatRowLevelPermissionPredicateMaps: {
byId: {},
idByUniversalIdentifier: {},
universalIdentifiersByApplicationId: {},
},
flatRowLevelPermissionPredicateGroupMaps: {
byId: {},
idByUniversalIdentifier: {},
universalIdentifiersByApplicationId: {},
},
objectIdByNameSingular: {
'test-entity': 'test-entity-id',
},
@@ -213,8 +223,10 @@ describe('WorkspaceEntityManager', () => {
IS_DASHBOARD_V2_ENABLED: false,
IS_TIMELINE_ACTIVITY_MIGRATED: false,
IS_GLOBAL_WORKSPACE_DATASOURCE_ENABLED: false,
IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED: false,
IS_WORKSPACE_CREATION_V2_ENABLED: false,
},
userWorkspaceRoleMap: {},
eventEmitterService: {
emitMutationEvent: jest.fn(),
emitDatabaseBatchEvent: jest.fn(),
@@ -238,6 +250,7 @@ describe('WorkspaceEntityManager', () => {
IS_WORKFLOW_RUN_STOPPAGE_ENABLED: false,
IS_DASHBOARD_V2_ENABLED: false,
IS_GLOBAL_WORKSPACE_DATASOURCE_ENABLED: false,
IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED: false,
},
permissionsPerRoleId: {},
eventEmitterService: mockInternalContext.eventEmitterService,
@@ -269,6 +282,10 @@ describe('WorkspaceEntityManager', () => {
flatObjectMetadataMaps,
flatFieldMetadataMaps,
flatIndexMaps: mockInternalContext.flatIndexMaps,
flatRowLevelPermissionPredicateMaps:
mockInternalContext.flatRowLevelPermissionPredicateMaps,
flatRowLevelPermissionPredicateGroupMaps:
mockInternalContext.flatRowLevelPermissionPredicateGroupMaps,
objectIdByNameSingular: mockInternalContext.objectIdByNameSingular,
featureFlagsMap: mockInternalContext.featureFlagsMap,
permissionsPerRoleId: mockDataSource.permissionsPerRoleId,
@@ -33,6 +33,7 @@ import { PlainObjectToDatabaseEntityTransformer } from 'typeorm/query-builder/tr
import { type UpsertOptions } from 'typeorm/repository/UpsertOptions';
import { InstanceChecker } from 'typeorm/util/InstanceChecker';
import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
import { type FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
import { type WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
@@ -87,6 +88,12 @@ export class WorkspaceEntityManager extends EntityManager {
return this.connection.eventEmitterService;
}
get authContext(): WorkspaceAuthContext {
const context = getWorkspaceContext();
return context.authContext;
}
get internalContext(): WorkspaceInternalContext {
const context = getWorkspaceContext();
@@ -95,8 +102,13 @@ export class WorkspaceEntityManager extends EntityManager {
flatObjectMetadataMaps: context.flatObjectMetadataMaps,
flatFieldMetadataMaps: context.flatFieldMetadataMaps,
flatIndexMaps: context.flatIndexMaps,
flatRowLevelPermissionPredicateMaps:
context.flatRowLevelPermissionPredicateMaps,
flatRowLevelPermissionPredicateGroupMaps:
context.flatRowLevelPermissionPredicateGroupMaps,
objectIdByNameSingular: context.objectIdByNameSingular,
featureFlagsMap: context.featureFlagsMap,
userWorkspaceRoleMap: context.userWorkspaceRoleMap,
eventEmitterService: this.eventEmitterService,
};
}
@@ -211,7 +223,7 @@ export class WorkspaceEntityManager extends EntityManager {
options?.objectRecordsPermissions ?? {},
this.internalContext,
options?.shouldBypassPermissionChecks ?? false,
{},
this.authContext,
this.getFeatureFlagMap(),
);
}
@@ -88,6 +88,8 @@ export class GlobalWorkspaceOrmManager {
rolesPermissions: permissionsPerRoleId,
ORMEntityMetadatas: entityMetadatas,
userWorkspaceRoleMap,
flatRowLevelPermissionPredicateMaps,
flatRowLevelPermissionPredicateGroupMaps,
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatObjectMetadataMaps',
'flatFieldMetadataMaps',
@@ -96,6 +98,8 @@ export class GlobalWorkspaceOrmManager {
'rolesPermissions',
'ORMEntityMetadatas',
'userWorkspaceRoleMap',
'flatRowLevelPermissionPredicateMaps',
'flatRowLevelPermissionPredicateGroupMaps',
]);
const { idByNameSingular: objectIdByNameSingular } =
@@ -106,6 +110,8 @@ export class GlobalWorkspaceOrmManager {
flatObjectMetadataMaps,
flatFieldMetadataMaps,
flatIndexMaps,
flatRowLevelPermissionPredicateMaps,
flatRowLevelPermissionPredicateGroupMaps,
objectIdByNameSingular,
featureFlagsMap,
permissionsPerRoleId,
@@ -3,6 +3,8 @@ import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/typ
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type FlatRowLevelPermissionPredicateGroupMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group-maps.type';
import { type FlatRowLevelPermissionPredicateMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-maps.type';
import { type WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
export interface WorkspaceInternalContext {
@@ -10,7 +12,10 @@ export interface WorkspaceInternalContext {
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
flatIndexMaps: FlatEntityMaps<FlatIndexMetadata>;
flatRowLevelPermissionPredicateMaps: FlatRowLevelPermissionPredicateMaps;
flatRowLevelPermissionPredicateGroupMaps: FlatRowLevelPermissionPredicateGroupMaps;
objectIdByNameSingular: Record<string, string>;
featureFlagsMap: Record<FeatureFlagKey, boolean>;
userWorkspaceRoleMap: Record<string, string>;
eventEmitterService: WorkspaceEventEmitter;
}
@@ -118,8 +118,19 @@ describe('WorkspaceRepository', () => {
idByUniversalIdentifier: {},
universalIdentifiersByApplicationId: {},
},
flatRowLevelPermissionPredicateMaps: {
byId: {},
idByUniversalIdentifier: {},
universalIdentifiersByApplicationId: {},
},
flatRowLevelPermissionPredicateGroupMaps: {
byId: {},
idByUniversalIdentifier: {},
universalIdentifiersByApplicationId: {},
},
objectIdByNameSingular: {},
featureFlagsMap: {},
featureFlagsMap: {} as FeatureFlagMap,
userWorkspaceRoleMap: {},
eventEmitterService: {} as unknown,
} as unknown as WorkspaceInternalContext;
@@ -23,6 +23,7 @@ import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repositor
import { type WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
import { type WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
import { type WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
import { applyRowLevelPermissionPredicates } from 'src/engine/twenty-orm/utils/apply-row-level-permission-predicates.util';
import { applyTableAliasOnWhereCondition } from 'src/engine/twenty-orm/utils/apply-table-alias-on-where-condition';
import { computeEventSelectQueryBuilder } from 'src/engine/twenty-orm/utils/compute-event-select-query-builder.util';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
@@ -57,7 +58,7 @@ export class WorkspaceDeleteQueryBuilder<
override clone(): this {
const clonedQueryBuilder = super.clone();
return new WorkspaceDeleteQueryBuilder(
const workspaceDeleteQueryBuilder = new WorkspaceDeleteQueryBuilder(
clonedQueryBuilder,
this.objectRecordsPermissions,
this.internalContext,
@@ -65,10 +66,13 @@ export class WorkspaceDeleteQueryBuilder<
this.authContext,
this.featureFlagMap,
) as this;
return workspaceDeleteQueryBuilder;
}
override async execute(): Promise<DeleteResult & { generatedMaps: T[] }> {
try {
this.applyRowLevelPermissionPredicates();
validateQueryIsPermittedOrThrow({
expressionMap: this.expressionMap,
objectsPermissions: this.objectRecordsPermissions,
@@ -157,6 +161,27 @@ export class WorkspaceDeleteQueryBuilder<
return mainAliasTarget;
}
private applyRowLevelPermissionPredicates(): void {
if (this.shouldBypassPermissionChecks) {
return;
}
const mainAliasTarget = this.getMainAliasTarget();
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
applyRowLevelPermissionPredicates({
queryBuilder: this as unknown as WorkspaceSelectQueryBuilder<T>,
objectMetadata,
internalContext: this.internalContext,
authContext: this.authContext,
featureFlagMap: this.featureFlagMap,
});
}
override select(): WorkspaceSelectQueryBuilder<T> {
throw new TwentyORMException(
'This builder cannot morph into a select builder',
@@ -24,6 +24,7 @@ import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/wo
import { WorkspaceInsertQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-insert-query-builder';
import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
import { applyRowLevelPermissionPredicates } from 'src/engine/twenty-orm/utils/apply-row-level-permission-predicates.util';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util';
@@ -58,7 +59,7 @@ export class WorkspaceSelectQueryBuilder<
override clone(): this {
const clonedQueryBuilder = super.clone();
return new WorkspaceSelectQueryBuilder(
const workspaceSelectQueryBuilder = new WorkspaceSelectQueryBuilder(
clonedQueryBuilder,
this.objectRecordsPermissions,
this.internalContext,
@@ -66,6 +67,8 @@ export class WorkspaceSelectQueryBuilder<
this.authContext,
this.featureFlagMap,
) as this;
return workspaceSelectQueryBuilder;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -327,6 +330,7 @@ export class WorkspaceSelectQueryBuilder<
}
private validatePermissions(): void {
this.applyRowLevelPermissionPredicates();
validateQueryIsPermittedOrThrow({
expressionMap: this.expressionMap,
objectsPermissions: this.objectRecordsPermissions,
@@ -338,7 +342,9 @@ export class WorkspaceSelectQueryBuilder<
}
private getMainAliasTarget(): EntityTarget<T> {
const mainAliasTarget = this.expressionMap.mainAlias?.target;
const mainAlias = this.expressionMap.mainAlias;
const mainAliasTarget = mainAlias?.target;
if (!mainAliasTarget) {
throw new TwentyORMException(
@@ -349,4 +355,31 @@ export class WorkspaceSelectQueryBuilder<
return mainAliasTarget;
}
private applyRowLevelPermissionPredicates(): void {
if (this.shouldBypassPermissionChecks) {
return;
}
// Subqueries don't have entity metadata, skip permission predicates
// Permissions are already applied on the original entity query
if (this.expressionMap.mainAlias?.subQuery) {
return;
}
const mainAliasTarget = this.getMainAliasTarget();
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
applyRowLevelPermissionPredicates({
queryBuilder: this,
objectMetadata,
internalContext: this.internalContext,
authContext: this.authContext,
featureFlagMap: this.featureFlagMap,
});
}
}
@@ -22,6 +22,7 @@ import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repositor
import { type WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
import { type WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
import { type WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
import { applyRowLevelPermissionPredicates } from 'src/engine/twenty-orm/utils/apply-row-level-permission-predicates.util';
import { applyTableAliasOnWhereCondition } from 'src/engine/twenty-orm/utils/apply-table-alias-on-where-condition';
import { computeEventSelectQueryBuilder } from 'src/engine/twenty-orm/utils/compute-event-select-query-builder.util';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
@@ -57,7 +58,7 @@ export class WorkspaceSoftDeleteQueryBuilder<
override clone(): this {
const clonedQueryBuilder = super.clone();
return new WorkspaceSoftDeleteQueryBuilder(
const workspaceSoftDeleteQueryBuilder = new WorkspaceSoftDeleteQueryBuilder(
clonedQueryBuilder,
this.objectRecordsPermissions,
this.internalContext,
@@ -65,10 +66,13 @@ export class WorkspaceSoftDeleteQueryBuilder<
this.authContext,
this.featureFlagMap,
) as this;
return workspaceSoftDeleteQueryBuilder;
}
override async execute(): Promise<UpdateResult> {
try {
this.applyRowLevelPermissionPredicates();
validateQueryIsPermittedOrThrow({
expressionMap: this.expressionMap,
objectsPermissions: this.objectRecordsPermissions,
@@ -184,4 +188,25 @@ export class WorkspaceSoftDeleteQueryBuilder<
return mainAliasTarget;
}
private applyRowLevelPermissionPredicates(): void {
if (this.shouldBypassPermissionChecks) {
return;
}
const mainAliasTarget = this.getMainAliasTarget();
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
applyRowLevelPermissionPredicates({
queryBuilder: this as unknown as WorkspaceSelectQueryBuilder<T>,
objectMetadata,
internalContext: this.internalContext,
authContext: this.authContext,
featureFlagMap: this.featureFlagMap,
});
}
}
@@ -30,6 +30,7 @@ import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repositor
import { type WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
import { type WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
import { applyRowLevelPermissionPredicates } from 'src/engine/twenty-orm/utils/apply-row-level-permission-predicates.util';
import { applyTableAliasOnWhereCondition } from 'src/engine/twenty-orm/utils/apply-table-alias-on-where-condition';
import { computeEventSelectQueryBuilder } from 'src/engine/twenty-orm/utils/compute-event-select-query-builder.util';
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
@@ -77,7 +78,7 @@ export class WorkspaceUpdateQueryBuilder<
override clone(): this {
const clonedQueryBuilder = super.clone();
return new WorkspaceUpdateQueryBuilder(
const workspaceUpdateQueryBuilder = new WorkspaceUpdateQueryBuilder(
clonedQueryBuilder as UpdateQueryBuilder<T>,
this.objectRecordsPermissions,
this.internalContext,
@@ -85,6 +86,8 @@ export class WorkspaceUpdateQueryBuilder<
this.authContext,
this.featureFlagMap,
) as this;
return workspaceUpdateQueryBuilder;
}
override async execute(): Promise<UpdateResult> {
@@ -92,7 +95,6 @@ export class WorkspaceUpdateQueryBuilder<
if (this.manyInputs) {
return this.executeMany();
}
validateQueryIsPermittedOrThrow({
expressionMap: this.expressionMap,
objectsPermissions: this.objectRecordsPermissions,
@@ -171,6 +173,8 @@ export class WorkspaceUpdateQueryBuilder<
this.internalContext.flatFieldMetadataMaps,
);
this.applyRowLevelPermissionPredicates();
const result = await super.execute();
const after = await eventSelectQueryBuilder.getMany();
@@ -323,6 +327,8 @@ export class WorkspaceUpdateQueryBuilder<
this.expressionMap.valuesSet = input.partialEntity;
this.where({ id: input.criteria });
this.applyRowLevelPermissionPredicates();
const result = await super.execute();
results.push(result);
@@ -494,4 +500,25 @@ export class WorkspaceUpdateQueryBuilder<
return this;
}
private applyRowLevelPermissionPredicates(): void {
if (this.shouldBypassPermissionChecks) {
return;
}
const mainAliasTarget = this.getMainAliasTarget();
const objectMetadata = getObjectMetadataFromEntityTarget(
mainAliasTarget,
this.internalContext,
);
applyRowLevelPermissionPredicates({
queryBuilder: this as unknown as WorkspaceSelectQueryBuilder<T>,
objectMetadata,
internalContext: this.internalContext,
authContext: this.authContext,
featureFlagMap: this.featureFlagMap,
});
}
}
@@ -10,6 +10,8 @@ import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/typ
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type FlatRowLevelPermissionPredicateGroupMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group-maps.type';
import { type FlatRowLevelPermissionPredicateMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-maps.type';
export type ORMWorkspaceContext = {
authContext: WorkspaceAuthContext;
@@ -21,6 +23,8 @@ export type ORMWorkspaceContext = {
permissionsPerRoleId: ObjectsPermissionsByRoleId;
entityMetadatas: EntityMetadata[];
userWorkspaceRoleMap: Record<string, string>;
flatRowLevelPermissionPredicateMaps: FlatRowLevelPermissionPredicateMaps;
flatRowLevelPermissionPredicateGroupMaps: FlatRowLevelPermissionPredicateGroupMaps;
};
export const workspaceContextStorage =
@@ -0,0 +1,220 @@
/* @license Enterprise */
import {
Brackets,
NotBrackets,
type ObjectLiteral,
type WhereExpressionBuilder,
} from 'typeorm';
import { type FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
import { type WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
import { GraphqlQueryFilterFieldParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser';
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
import { buildRowLevelPermissionRecordFilter } from 'src/engine/twenty-orm/utils/build-row-level-permission-record-filter.util';
type ApplyRowLevelPermissionPredicatesArgs<T extends ObjectLiteral> = {
queryBuilder: WorkspaceSelectQueryBuilder<T>;
objectMetadata: FlatObjectMetadata;
internalContext: WorkspaceInternalContext;
authContext: AuthContext;
featureFlagMap: FeatureFlagMap;
};
export const applyRowLevelPermissionPredicates = <T extends ObjectLiteral>({
queryBuilder,
objectMetadata,
internalContext,
authContext,
featureFlagMap,
}: ApplyRowLevelPermissionPredicatesArgs<T>): void => {
if (
featureFlagMap[
FeatureFlagKey.IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED
] !== true
) {
return;
}
const roleId = authContext.userWorkspaceId
? internalContext.userWorkspaceRoleMap[authContext.userWorkspaceId]
: undefined;
const recordFilter = buildRowLevelPermissionRecordFilter({
flatRowLevelPermissionPredicateMaps:
internalContext.flatRowLevelPermissionPredicateMaps,
flatRowLevelPermissionPredicateGroupMaps:
internalContext.flatRowLevelPermissionPredicateGroupMaps,
flatFieldMetadataMaps: internalContext.flatFieldMetadataMaps,
objectMetadata,
roleId,
authContext,
});
if (!recordFilter || Object.keys(recordFilter).length === 0) {
return;
}
applyObjectRecordFilterToQueryBuilder({
queryBuilder,
objectNameSingular: objectMetadata.nameSingular,
recordFilter,
fieldParser: new GraphqlQueryFilterFieldParser(
objectMetadata,
internalContext.flatFieldMetadataMaps,
),
});
};
const applyObjectRecordFilterToQueryBuilder = <T extends ObjectLiteral>({
queryBuilder,
objectNameSingular,
recordFilter,
fieldParser,
}: {
queryBuilder: WorkspaceSelectQueryBuilder<T>;
objectNameSingular: string;
recordFilter: Record<string, unknown>;
fieldParser: GraphqlQueryFilterFieldParser;
}): void => {
if (!recordFilter || Object.keys(recordFilter).length === 0) {
return;
}
const whereCondition = new Brackets((qb) => {
Object.entries(recordFilter).forEach(([key, value], index) => {
parseKeyFilter({
queryBuilder: qb,
objectNameSingular,
key,
value,
isFirst: index === 0,
fieldParser,
});
});
});
if (queryBuilder.expressionMap.wheres.length === 0) {
queryBuilder.where(whereCondition);
} else {
queryBuilder.andWhere(whereCondition);
}
};
const parseKeyFilter = ({
queryBuilder,
objectNameSingular,
key,
value,
isFirst,
fieldParser,
}: {
queryBuilder: WhereExpressionBuilder;
objectNameSingular: string;
key: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value: any;
isFirst: boolean;
fieldParser: GraphqlQueryFilterFieldParser;
}): void => {
switch (key) {
case 'and': {
const andWhereCondition = new Brackets((qb) => {
value.forEach((filter: Record<string, unknown>, index: number) => {
const whereCondition = new Brackets((qb2) => {
Object.entries(filter).forEach(
([subFilterKey, subFilterValue], subIndex) => {
parseKeyFilter({
queryBuilder: qb2,
objectNameSingular,
key: subFilterKey,
value: subFilterValue,
isFirst: subIndex === 0,
fieldParser,
});
},
);
});
if (index === 0) {
qb.where(whereCondition);
} else {
qb.andWhere(whereCondition);
}
});
});
if (isFirst) {
queryBuilder.where(andWhereCondition);
} else {
queryBuilder.andWhere(andWhereCondition);
}
break;
}
case 'or': {
const orWhereCondition = new Brackets((qb) => {
value.forEach((filter: Record<string, unknown>, index: number) => {
const whereCondition = new Brackets((qb2) => {
Object.entries(filter).forEach(
([subFilterKey, subFilterValue], subIndex) => {
parseKeyFilter({
queryBuilder: qb2,
objectNameSingular,
key: subFilterKey,
value: subFilterValue,
isFirst: subIndex === 0,
fieldParser,
});
},
);
});
if (index === 0) {
qb.where(whereCondition);
} else {
qb.orWhere(whereCondition);
}
});
});
if (isFirst) {
queryBuilder.where(orWhereCondition);
} else {
queryBuilder.andWhere(orWhereCondition);
}
break;
}
case 'not': {
const notWhereCondition = new NotBrackets((qb) => {
Object.entries(value).forEach(
([subFilterKey, subFilterValue], subIndex) => {
parseKeyFilter({
queryBuilder: qb,
objectNameSingular,
key: subFilterKey,
value: subFilterValue,
isFirst: subIndex === 0,
fieldParser,
});
},
);
});
if (isFirst) {
queryBuilder.where(notWhereCondition);
} else {
queryBuilder.andWhere(notWhereCondition);
}
break;
}
default:
fieldParser.parse(queryBuilder, objectNameSingular, key, value, isFirst);
break;
}
};
@@ -0,0 +1,201 @@
/* @license Enterprise */
import {
RecordFilterGroupLogicalOperator,
type CompositeFieldSubFieldName,
type PartialFieldMetadataItemOption,
type RecordGqlOperationFilter,
} from 'twenty-shared/types';
import {
computeRecordGqlOperationFilter,
convertViewFilterValueToString,
getFilterTypeFromFieldType,
isDefined,
type RecordFilter,
type RecordFilterGroup,
} from 'twenty-shared/utils';
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import {
PermissionsException,
PermissionsExceptionCode,
} from 'src/engine/metadata-modules/permissions/permissions.exception';
import { RowLevelPermissionPredicateGroupLogicalOperator } from 'src/engine/metadata-modules/row-level-permission-predicate/enums/row-level-permission-predicate-group-logical-operator.enum';
import { type FlatRowLevelPermissionPredicateGroupMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group-maps.type';
import { type FlatRowLevelPermissionPredicateMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-maps.type';
import { type RowLevelPermissionPredicateValue } from 'src/engine/metadata-modules/row-level-permission-predicate/types/row-level-permission-predicate-value.type';
type BuildRowLevelPermissionRecordFilterArgs = {
flatRowLevelPermissionPredicateMaps: FlatRowLevelPermissionPredicateMaps;
flatRowLevelPermissionPredicateGroupMaps: FlatRowLevelPermissionPredicateGroupMaps;
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
objectMetadata: FlatObjectMetadata;
roleId: string | undefined;
authContext: AuthContext;
};
export const buildRowLevelPermissionRecordFilter = ({
flatRowLevelPermissionPredicateMaps,
flatRowLevelPermissionPredicateGroupMaps,
flatFieldMetadataMaps,
objectMetadata,
roleId,
authContext,
}: BuildRowLevelPermissionRecordFilterArgs): RecordGqlOperationFilter | null => {
if (!isDefined(roleId)) {
return null;
}
const predicates = Object.values(flatRowLevelPermissionPredicateMaps.byId)
.filter(isDefined)
.filter(
(predicate) =>
predicate.roleId === roleId &&
predicate.objectMetadataId === objectMetadata.id &&
!isDefined(predicate.deletedAt),
);
if (predicates.length === 0) {
return null;
}
const fieldMetadataMapById = flatFieldMetadataMaps.byId;
const workspaceMember = authContext.workspaceMember;
const recordFilters = predicates
.map((predicate) => {
const fieldMetadata = fieldMetadataMapById[predicate.fieldMetadataId];
if (!isDefined(fieldMetadata)) {
throw new PermissionsException(
`Field metadata not found for row level predicate ${predicate.id}`,
PermissionsExceptionCode.FIELD_METADATA_NOT_FOUND,
);
}
const workspaceMemberFieldMetadataId =
predicate.workspaceMemberFieldMetadataId;
let predicateValue: RowLevelPermissionPredicateValue = predicate.value;
if (isDefined(workspaceMemberFieldMetadataId)) {
const workspaceMemberFieldMetadata =
fieldMetadataMapById[workspaceMemberFieldMetadataId];
if (!isDefined(workspaceMemberFieldMetadata)) {
throw new PermissionsException(
`Workspace member field metadata not found for row level predicate ${predicate.id}`,
PermissionsExceptionCode.FIELD_METADATA_NOT_FOUND,
);
}
if (!isDefined(workspaceMember)) {
return null;
}
const rawWorkspaceMemberValue = Object.entries(workspaceMember).find(
([key]) => key === workspaceMemberFieldMetadata.name,
)?.[1];
const workspaceMemberSubFieldName =
predicate.workspaceMemberSubFieldName;
if (
isDefined(workspaceMemberSubFieldName) &&
isDefined(rawWorkspaceMemberValue) &&
isCompositeFieldMetadataType(workspaceMemberFieldMetadata.type) &&
typeof rawWorkspaceMemberValue === 'object'
) {
predicateValue = rawWorkspaceMemberValue[workspaceMemberSubFieldName];
} else {
predicateValue = rawWorkspaceMemberValue;
}
if (!isDefined(predicateValue)) {
throw new PermissionsException(
`Workspace member data missing for field ${workspaceMemberFieldMetadata.name}`,
PermissionsExceptionCode.INVALID_ARG,
);
}
}
const effectiveSubFieldName = predicate.subFieldName as
| CompositeFieldSubFieldName
| undefined;
return {
id: predicate.id,
fieldMetadataId: predicate.fieldMetadataId,
value: convertViewFilterValueToString(predicateValue),
type: getFilterTypeFromFieldType(fieldMetadata.type),
operand: predicate.operand as unknown as RecordFilter['operand'],
recordFilterGroupId: predicate.rowLevelPermissionPredicateGroupId,
subFieldName: effectiveSubFieldName,
} satisfies RecordFilter;
})
.filter(isDefined);
const predicateGroupsById = flatRowLevelPermissionPredicateGroupMaps.byId;
const relevantGroupIds = new Set<string>();
for (const predicate of predicates) {
if (isDefined(predicate.rowLevelPermissionPredicateGroupId)) {
relevantGroupIds.add(predicate.rowLevelPermissionPredicateGroupId);
let parentGroupId =
predicateGroupsById[predicate.rowLevelPermissionPredicateGroupId]
?.parentRowLevelPermissionPredicateGroupId;
while (isDefined(parentGroupId) && !relevantGroupIds.has(parentGroupId)) {
relevantGroupIds.add(parentGroupId);
parentGroupId =
predicateGroupsById[parentGroupId]
?.parentRowLevelPermissionPredicateGroupId;
}
}
}
const recordFilterGroups: RecordFilterGroup[] = [...relevantGroupIds]
.map((groupId) => predicateGroupsById[groupId])
.filter(isDefined)
.filter(
(predicateGroup) =>
predicateGroup.roleId === roleId &&
!isDefined(predicateGroup.deletedAt),
)
.map((predicateGroup) => ({
id: predicateGroup.id,
logicalOperator:
predicateGroup.logicalOperator ===
RowLevelPermissionPredicateGroupLogicalOperator.OR
? RecordFilterGroupLogicalOperator.OR
: RecordFilterGroupLogicalOperator.AND,
parentRecordFilterGroupId:
predicateGroup.parentRowLevelPermissionPredicateGroupId,
}));
const fieldMetadataItems = predicates
.map((predicate) => fieldMetadataMapById[predicate.fieldMetadataId])
.filter(isDefined)
.map((field) => ({
id: field.id,
name: field.name,
type: field.type,
label: field.label,
options: field.options as PartialFieldMetadataItemOption[],
}));
return computeRecordGqlOperationFilter({
recordFilters,
recordFilterGroups,
fields: fieldMetadataItems,
filterValueDependencies: {
currentWorkspaceMemberId: authContext.workspaceMemberId,
},
});
};
@@ -16,6 +16,7 @@ export const bindDataToRequestObject = (
request.workspaceId = data.workspace?.id;
request.workspaceMetadataVersion = metadataVersion;
request.workspaceMemberId = data.workspaceMemberId;
request.workspaceMember = data.workspaceMember;
request.userWorkspaceId = data.userWorkspaceId;
request.authProvider = data.authProvider;
request.impersonationContext = data.impersonationContext;
@@ -6,6 +6,8 @@ import { type FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/
import { type FlatRoleTargetByAgentIdMaps } from 'src/engine/metadata-modules/flat-agent/types/flat-role-target-by-agent-id-maps.type';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { type UserWorkspaceRoleMap } from 'src/engine/metadata-modules/role-target/services/workspace-user-workspace-role-map-cache.service';
import { type FlatRowLevelPermissionPredicateGroupMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group-maps.type';
import { type FlatRowLevelPermissionPredicateMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-maps.type';
export const WORKSPACE_CACHE_KEYS_V2 = {
flatObjectMetadataMaps: 'flat-maps:object-metadata',
@@ -32,6 +34,10 @@ export const WORKSPACE_CACHE_KEYS_V2 = {
flatPageLayoutMaps: 'flat-maps:page-layout',
flatPageLayoutWidgetMaps: 'flat-maps:page-layout-widget',
flatPageLayoutTabMaps: 'flat-maps:flatPageLayoutTabMaps',
flatRowLevelPermissionPredicateMaps:
'flat-maps:row-level-permission-predicate',
flatRowLevelPermissionPredicateGroupMaps:
'flat-maps:row-level-permission-predicate-group',
} as const satisfies Record<WorkspaceCacheKeyName, string>;
export type AdditionalCacheDataMaps = {
@@ -42,6 +48,8 @@ export type AdditionalCacheDataMaps = {
flatApplicationMaps: FlatApplicationCacheMaps;
ORMEntityMetadatas: EntityMetadata[];
flatRoleTargetByAgentIdMaps: FlatRoleTargetByAgentIdMaps;
flatRowLevelPermissionPredicateMaps: FlatRowLevelPermissionPredicateMaps;
flatRowLevelPermissionPredicateGroupMaps: FlatRowLevelPermissionPredicateGroupMaps;
};
export type WorkspaceCacheDataMap = AllFlatEntityMaps & AdditionalCacheDataMaps;
@@ -35,6 +35,8 @@ export const fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseE
invalidPageLayoutWidget: 0,
totalErrors: 0,
invalidPageLayoutTab: 0,
invalidRowLevelPermissionPredicate: 0,
invalidRowLevelPermissionPredicateGroup: 0,
},
errors: translatedReport,
};
@@ -26,6 +26,8 @@ import { WorkspaceMigrationV2PageLayoutActionsBuilderService } from 'src/engine/
import { WorkspaceMigrationV2RoleTargetActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/role-target/workspace-migration-v2-role-target-actions-builder.service';
import { WorkspaceMigrationV2RoleActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/role/workspace-migration-v2-role-actions-builder.service';
import { WorkspaceMigrationV2RouteTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/route-trigger/workspace-migration-v2-route-trigger-actions-builder.service';
import { WorkspaceMigrationV2RowLevelPermissionPredicateGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/row-level-permission-predicate-group/workspace-migration-v2-row-level-permission-predicate-group-actions-builder.service';
import { WorkspaceMigrationV2RowLevelPermissionPredicateActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/row-level-permission-predicate/workspace-migration-v2-row-level-permission-predicate-actions-builder.service';
import { WorkspaceMigrationV2ServerlessFunctionActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/serverless-function/workspace-migration-v2-serverless-function-actions-builder.service';
import { WorkspaceMigrationV2ViewFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service';
import { WorkspaceMigrationV2ViewFilterActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-filter/workspace-migration-v2-view-filter-actions-builder.service';
@@ -52,6 +54,8 @@ export class WorkspaceMigrationBuildOrchestratorService {
private readonly workspaceMigrationV2PageLayoutActionsBuilderService: WorkspaceMigrationV2PageLayoutActionsBuilderService,
private readonly workspaceMigrationV2PageLayoutWidgetActionsBuilderService: WorkspaceMigrationV2PageLayoutWidgetActionsBuilderService,
private readonly workspaceMigrationV2PageLayoutTabActionsBuilderService: WorkspaceMigrationV2PageLayoutTabActionsBuilderService,
private readonly workspaceMigrationV2RowLevelPermissionPredicateActionsBuilderService: WorkspaceMigrationV2RowLevelPermissionPredicateActionsBuilderService,
private readonly workspaceMigrationV2RowLevelPermissionPredicateGroupActionsBuilderService: WorkspaceMigrationV2RowLevelPermissionPredicateGroupActionsBuilderService,
) {}
private setupOptimisticCache({
@@ -141,6 +145,8 @@ export class WorkspaceMigrationBuildOrchestratorService {
flatFieldMetadataMaps,
flatViewFilterMaps,
flatViewGroupMaps,
flatRowLevelPermissionPredicateMaps,
flatRowLevelPermissionPredicateGroupMaps,
flatRoleMaps,
flatRoleTargetMaps,
flatAgentMaps,
@@ -409,6 +415,86 @@ export class WorkspaceMigrationBuildOrchestratorService {
}
}
if (isDefined(flatRowLevelPermissionPredicateGroupMaps)) {
const {
from: fromFlatRowLevelPermissionPredicateGroupMaps,
to: toFlatRowLevelPermissionPredicateGroupMaps,
} = flatRowLevelPermissionPredicateGroupMaps;
const rowLevelPermissionPredicateGroupResult =
await this.workspaceMigrationV2RowLevelPermissionPredicateGroupActionsBuilderService.validateAndBuild(
{
additionalCacheDataMaps,
from: fromFlatRowLevelPermissionPredicateGroupMaps,
to: toFlatRowLevelPermissionPredicateGroupMaps,
buildOptions,
dependencyOptimisticFlatEntityMaps: {
flatRoleMaps: optimisticAllFlatEntityMaps.flatRoleMaps,
},
workspaceId,
},
);
this.mergeFlatEntityMapsAndRelatedFlatEntityMapsInAllFlatEntityMapsThroughMutation(
{
allFlatEntityMaps: optimisticAllFlatEntityMaps,
flatEntityMapsAndRelatedFlatEntityMaps:
rowLevelPermissionPredicateGroupResult.optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
},
);
if (rowLevelPermissionPredicateGroupResult.status === 'fail') {
orchestratorFailureReport.rowLevelPermissionPredicateGroup.push(
...rowLevelPermissionPredicateGroupResult.errors,
);
} else {
orchestratorActionsReport.rowLevelPermissionPredicateGroup =
rowLevelPermissionPredicateGroupResult.actions;
}
}
if (isDefined(flatRowLevelPermissionPredicateMaps)) {
const {
from: fromFlatRowLevelPermissionPredicateMaps,
to: toFlatRowLevelPermissionPredicateMaps,
} = flatRowLevelPermissionPredicateMaps;
const rowLevelPermissionPredicateResult =
await this.workspaceMigrationV2RowLevelPermissionPredicateActionsBuilderService.validateAndBuild(
{
additionalCacheDataMaps,
from: fromFlatRowLevelPermissionPredicateMaps,
to: toFlatRowLevelPermissionPredicateMaps,
buildOptions,
dependencyOptimisticFlatEntityMaps: {
flatFieldMetadataMaps:
optimisticAllFlatEntityMaps.flatFieldMetadataMaps,
flatObjectMetadataMaps:
optimisticAllFlatEntityMaps.flatObjectMetadataMaps,
flatRowLevelPermissionPredicateGroupMaps:
optimisticAllFlatEntityMaps.flatRowLevelPermissionPredicateGroupMaps,
flatRoleMaps: optimisticAllFlatEntityMaps.flatRoleMaps,
},
workspaceId,
},
);
this.mergeFlatEntityMapsAndRelatedFlatEntityMapsInAllFlatEntityMapsThroughMutation(
{
allFlatEntityMaps: optimisticAllFlatEntityMaps,
flatEntityMapsAndRelatedFlatEntityMaps:
rowLevelPermissionPredicateResult.optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
},
);
if (rowLevelPermissionPredicateResult.status === 'fail') {
orchestratorFailureReport.rowLevelPermissionPredicate.push(
...rowLevelPermissionPredicateResult.errors,
);
} else {
orchestratorActionsReport.rowLevelPermissionPredicate =
rowLevelPermissionPredicateResult.actions;
}
}
if (isDefined(flatServerlessFunctionMaps)) {
const {
from: fromFlatServerlessFunctionMaps,
@@ -871,6 +957,24 @@ export class WorkspaceMigrationBuildOrchestratorService {
...aggregatedOrchestratorActionsReport.pageLayoutWidget.created,
...aggregatedOrchestratorActionsReport.pageLayoutWidget.updated,
///
// Row level permission predicates
...aggregatedOrchestratorActionsReport.rowLevelPermissionPredicate
.deleted,
...aggregatedOrchestratorActionsReport.rowLevelPermissionPredicate
.created,
...aggregatedOrchestratorActionsReport.rowLevelPermissionPredicate
.updated,
///
// Row level permission predicate groups
...aggregatedOrchestratorActionsReport
.rowLevelPermissionPredicateGroup.deleted,
...aggregatedOrchestratorActionsReport
.rowLevelPermissionPredicateGroup.created,
...aggregatedOrchestratorActionsReport
.rowLevelPermissionPredicateGroup.updated,
///
],
workspaceId,
},
@@ -0,0 +1,28 @@
/* @license Enterprise */
import { type FlatEntityPropertiesUpdates } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-properties-updates.type';
import { type FlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group.type';
export type CreateRowLevelPermissionPredicateGroupAction = {
type: 'create_row_level_permission_predicate_group';
rowLevelPermissionPredicateGroup: FlatRowLevelPermissionPredicateGroup;
};
export type UpdateRowLevelPermissionPredicateGroupAction = {
type: 'update_row_level_permission_predicate_group';
rowLevelPermissionPredicateGroupId: string;
updates: FlatEntityPropertiesUpdates<'rowLevelPermissionPredicateGroup'>;
};
export type DeleteRowLevelPermissionPredicateGroupAction = {
type: 'delete_row_level_permission_predicate_group';
rowLevelPermissionPredicateGroupId: string;
};
export type WorkspaceMigrationRowLevelPermissionPredicateGroupActionV2 =
| CreateRowLevelPermissionPredicateGroupAction
| UpdateRowLevelPermissionPredicateGroupAction
| DeleteRowLevelPermissionPredicateGroupAction;
export type WorkspaceMigrationRowLevelPermissionPredicateGroupActionTypeV2 =
WorkspaceMigrationRowLevelPermissionPredicateGroupActionV2['type'];
@@ -0,0 +1,121 @@
/* @license Enterprise */
import { Injectable } from '@nestjs/common';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { UpdateRowLevelPermissionPredicateGroupAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/row-level-permission-predicate-group/types/workspace-migration-row-level-permission-predicate-group-action-v2.type';
import { WorkspaceEntityMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-entity-migration-builder-v2.service';
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-update-validation-args.type';
import { FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-args.type';
import { FlatEntityValidationReturnType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-result.type';
import { FlatRowLevelPermissionPredicateGroupValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-row-level-permission-predicate-group-validator.service';
@Injectable()
export class WorkspaceMigrationV2RowLevelPermissionPredicateGroupActionsBuilderService extends WorkspaceEntityMigrationBuilderV2Service<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicateGroup
> {
constructor(
private readonly flatRowLevelPermissionPredicateGroupValidatorService: FlatRowLevelPermissionPredicateGroupValidatorService,
) {
super(ALL_METADATA_NAME.rowLevelPermissionPredicateGroup);
}
protected validateFlatEntityCreation(
args: FlatEntityValidationArgs<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicateGroup
>,
): FlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicateGroup,
'created'
> {
const validationResult =
this.flatRowLevelPermissionPredicateGroupValidatorService.validateFlatRowLevelPermissionPredicateGroupCreation(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { flatEntityToValidate } = args;
return {
status: 'success',
action: {
type: 'create_row_level_permission_predicate_group',
rowLevelPermissionPredicateGroup: flatEntityToValidate,
},
};
}
protected validateFlatEntityDeletion(
args: FlatEntityValidationArgs<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicateGroup
>,
): FlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicateGroup,
'deleted'
> {
const validationResult =
this.flatRowLevelPermissionPredicateGroupValidatorService.validateFlatRowLevelPermissionPredicateGroupDeletion(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const {
flatEntityToValidate: { id: predicateGroupId },
} = args;
return {
status: 'success',
action: {
type: 'delete_row_level_permission_predicate_group',
rowLevelPermissionPredicateGroupId: predicateGroupId,
},
};
}
protected validateFlatEntityUpdate(
args: FlatEntityUpdateValidationArgs<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicateGroup
>,
): FlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicateGroup,
'updated'
> {
const validationResult =
this.flatRowLevelPermissionPredicateGroupValidatorService.validateFlatRowLevelPermissionPredicateGroupUpdate(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { flatEntityId, flatEntityUpdates } = args;
const updateAction: UpdateRowLevelPermissionPredicateGroupAction = {
type: 'update_row_level_permission_predicate_group',
rowLevelPermissionPredicateGroupId: flatEntityId,
updates: flatEntityUpdates,
};
return {
status: 'success',
action: updateAction,
};
}
}
@@ -0,0 +1,34 @@
/* @license Enterprise */
import { type FlatEntityPropertiesUpdates } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-properties-updates.type';
import { type FlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate.type';
export type CreateRowLevelPermissionPredicateAction = {
type: 'create_row_level_permission_predicate';
rowLevelPermissionPredicate: FlatRowLevelPermissionPredicate;
};
export type UpdateRowLevelPermissionPredicateAction = {
type: 'update_row_level_permission_predicate';
rowLevelPermissionPredicateId: string;
updates: FlatEntityPropertiesUpdates<'rowLevelPermissionPredicate'>;
};
export type DeleteRowLevelPermissionPredicateAction = {
type: 'delete_row_level_permission_predicate';
rowLevelPermissionPredicateId: string;
};
export type DestroyRowLevelPermissionPredicateAction = {
type: 'destroy_row_level_permission_predicate';
rowLevelPermissionPredicateId: string;
};
export type WorkspaceMigrationRowLevelPermissionPredicateActionV2 =
| CreateRowLevelPermissionPredicateAction
| UpdateRowLevelPermissionPredicateAction
| DeleteRowLevelPermissionPredicateAction
| DestroyRowLevelPermissionPredicateAction;
export type WorkspaceMigrationRowLevelPermissionPredicateActionTypeV2 =
WorkspaceMigrationRowLevelPermissionPredicateActionV2['type'];
@@ -0,0 +1,121 @@
/* @license Enterprise */
import { Injectable } from '@nestjs/common';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { UpdateRowLevelPermissionPredicateAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/row-level-permission-predicate/types/workspace-migration-row-level-permission-predicate-action-v2.type';
import { WorkspaceEntityMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-entity-migration-builder-v2.service';
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-update-validation-args.type';
import { FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-args.type';
import { FlatEntityValidationReturnType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-result.type';
import { FlatRowLevelPermissionPredicateValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-row-level-permission-predicate-validator.service';
@Injectable()
export class WorkspaceMigrationV2RowLevelPermissionPredicateActionsBuilderService extends WorkspaceEntityMigrationBuilderV2Service<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicate
> {
constructor(
private readonly flatRowLevelPermissionPredicateValidatorService: FlatRowLevelPermissionPredicateValidatorService,
) {
super(ALL_METADATA_NAME.rowLevelPermissionPredicate);
}
protected validateFlatEntityCreation(
args: FlatEntityValidationArgs<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicate
>,
): FlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicate,
'created'
> {
const validationResult =
this.flatRowLevelPermissionPredicateValidatorService.validateFlatRowLevelPermissionPredicateCreation(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { flatEntityToValidate } = args;
return {
status: 'success',
action: {
type: 'create_row_level_permission_predicate',
rowLevelPermissionPredicate: flatEntityToValidate,
},
};
}
protected validateFlatEntityDeletion(
args: FlatEntityValidationArgs<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicate
>,
): FlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicate,
'deleted'
> {
const validationResult =
this.flatRowLevelPermissionPredicateValidatorService.validateFlatRowLevelPermissionPredicateDeletion(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const {
flatEntityToValidate: { id: predicateId },
} = args;
return {
status: 'success',
action: {
type: 'delete_row_level_permission_predicate',
rowLevelPermissionPredicateId: predicateId,
},
};
}
protected validateFlatEntityUpdate(
args: FlatEntityUpdateValidationArgs<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicate
>,
): FlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicate,
'updated'
> {
const validationResult =
this.flatRowLevelPermissionPredicateValidatorService.validateFlatRowLevelPermissionPredicateUpdate(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { flatEntityId, flatEntityUpdates } = args;
const updateAction: UpdateRowLevelPermissionPredicateAction = {
type: 'update_row_level_permission_predicate',
rowLevelPermissionPredicateId: flatEntityId,
updates: flatEntityUpdates,
};
return {
status: 'success',
action: updateAction,
};
}
}
@@ -0,0 +1,209 @@
/* @license Enterprise */
import { Injectable } from '@nestjs/common';
import { msg, t } from '@lingui/core/macro';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { RowLevelPermissionPredicateGroupExceptionCode } from 'src/engine/metadata-modules/row-level-permission-predicate/exceptions/row-level-permission-predicate-group.exception';
import { type FlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group.type';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-update-validation-args.type';
import { FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-args.type';
import { fromFlatEntityPropertiesUpdatesToPartialFlatEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-flat-entity-properties-updates-to-partial-flat-entity';
@Injectable()
export class FlatRowLevelPermissionPredicateGroupValidatorService {
validateFlatRowLevelPermissionPredicateGroupCreation({
flatEntityToValidate: flatPredicateGroupToValidate,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
}: FlatEntityValidationArgs<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicateGroup
>): FailedFlatEntityValidation<FlatRowLevelPermissionPredicateGroup> {
const {
flatRowLevelPermissionPredicateGroupMaps:
optimisticFlatPredicateGroupMaps,
flatRoleMaps,
} = optimisticFlatEntityMapsAndRelatedFlatEntityMaps as Partial<{
flatRowLevelPermissionPredicateGroupMaps: typeof optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatRowLevelPermissionPredicateGroupMaps;
flatRoleMaps: typeof optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatRoleMaps;
}>;
const validationResult: FailedFlatEntityValidation<FlatRowLevelPermissionPredicateGroup> =
{
type: 'create_row_level_permission_predicate_group',
errors: [],
flatEntityMinimalInformation: {
id: flatPredicateGroupToValidate.id,
},
};
const existingPredicateGroup =
optimisticFlatPredicateGroupMaps?.byId[flatPredicateGroupToValidate.id];
if (isDefined(existingPredicateGroup)) {
validationResult.errors.push({
code: RowLevelPermissionPredicateGroupExceptionCode.INVALID_ROW_LEVEL_PERMISSION_PREDICATE_GROUP_DATA,
message: t`Row level permission predicate group with this id already exists`,
userFriendlyMessage: msg`Row level permission predicate group with this id already exists`,
});
}
if (
isDefined(
flatPredicateGroupToValidate.parentRowLevelPermissionPredicateGroupId,
) &&
optimisticFlatPredicateGroupMaps
) {
const parentGroup = findFlatEntityByIdInFlatEntityMaps({
flatEntityId:
flatPredicateGroupToValidate.parentRowLevelPermissionPredicateGroupId,
flatEntityMaps: optimisticFlatPredicateGroupMaps,
});
if (!isDefined(parentGroup)) {
validationResult.errors.push({
code: RowLevelPermissionPredicateGroupExceptionCode.INVALID_ROW_LEVEL_PERMISSION_PREDICATE_GROUP_DATA,
message: t`Parent row level permission predicate group not found`,
userFriendlyMessage: msg`Parent row level permission predicate group not found`,
});
}
}
const role = flatRoleMaps
? findFlatEntityByIdInFlatEntityMaps({
flatEntityId: flatPredicateGroupToValidate.roleId,
flatEntityMaps: flatRoleMaps,
})
: undefined;
if (!isDefined(role)) {
validationResult.errors.push({
code: RowLevelPermissionPredicateGroupExceptionCode.ROLE_NOT_FOUND,
message: t`Role not found`,
userFriendlyMessage: msg`Role not found`,
});
}
return validationResult;
}
validateFlatRowLevelPermissionPredicateGroupDeletion({
flatEntityToValidate: flatPredicateGroupToDelete,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
}: FlatEntityValidationArgs<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicateGroup
>): FailedFlatEntityValidation<FlatRowLevelPermissionPredicateGroup> {
const {
flatRowLevelPermissionPredicateGroupMaps:
optimisticFlatPredicateGroupMaps,
} = optimisticFlatEntityMapsAndRelatedFlatEntityMaps as Partial<{
flatRowLevelPermissionPredicateGroupMaps: typeof optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatRowLevelPermissionPredicateGroupMaps;
}>;
const validationResult: FailedFlatEntityValidation<FlatRowLevelPermissionPredicateGroup> =
{
type: 'delete_row_level_permission_predicate_group',
errors: [],
flatEntityMinimalInformation: {
id: flatPredicateGroupToDelete.id,
},
};
const existingPredicateGroup =
optimisticFlatPredicateGroupMaps?.byId[flatPredicateGroupToDelete.id];
if (!isDefined(existingPredicateGroup)) {
validationResult.errors.push({
code: RowLevelPermissionPredicateGroupExceptionCode.ROW_LEVEL_PERMISSION_PREDICATE_GROUP_NOT_FOUND,
message: t`Row level permission predicate group to delete not found`,
userFriendlyMessage: msg`Row level permission predicate group to delete not found`,
});
}
return validationResult;
}
validateFlatRowLevelPermissionPredicateGroupUpdate({
flatEntityId,
flatEntityUpdates,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
}: FlatEntityUpdateValidationArgs<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicateGroup
>): FailedFlatEntityValidation<FlatRowLevelPermissionPredicateGroup> {
const {
flatRowLevelPermissionPredicateGroupMaps:
optimisticFlatPredicateGroupMaps,
flatRoleMaps,
} = optimisticFlatEntityMapsAndRelatedFlatEntityMaps as Partial<{
flatRowLevelPermissionPredicateGroupMaps: typeof optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatRowLevelPermissionPredicateGroupMaps;
flatRoleMaps: typeof optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatRoleMaps;
}>;
const validationResult: FailedFlatEntityValidation<FlatRowLevelPermissionPredicateGroup> =
{
type: 'update_row_level_permission_predicate_group',
errors: [],
flatEntityMinimalInformation: {
id: flatEntityId,
},
};
const existingPredicateGroup =
optimisticFlatPredicateGroupMaps?.byId[flatEntityId];
if (!isDefined(existingPredicateGroup)) {
validationResult.errors.push({
code: RowLevelPermissionPredicateGroupExceptionCode.ROW_LEVEL_PERMISSION_PREDICATE_GROUP_NOT_FOUND,
message: t`Row level permission predicate group to update not found`,
userFriendlyMessage: msg`Row level permission predicate group to update not found`,
});
return validationResult;
}
const updatedPredicateGroup = {
...existingPredicateGroup,
...fromFlatEntityPropertiesUpdatesToPartialFlatEntity({
updates: flatEntityUpdates,
}),
};
if (
isDefined(
updatedPredicateGroup.parentRowLevelPermissionPredicateGroupId,
) &&
optimisticFlatPredicateGroupMaps
) {
const parentGroup = findFlatEntityByIdInFlatEntityMaps({
flatEntityId:
updatedPredicateGroup.parentRowLevelPermissionPredicateGroupId,
flatEntityMaps: optimisticFlatPredicateGroupMaps,
});
if (!isDefined(parentGroup)) {
validationResult.errors.push({
code: RowLevelPermissionPredicateGroupExceptionCode.INVALID_ROW_LEVEL_PERMISSION_PREDICATE_GROUP_DATA,
message: t`Parent row level permission predicate group not found`,
userFriendlyMessage: msg`Parent row level permission predicate group not found`,
});
}
}
const role = flatRoleMaps
? findFlatEntityByIdInFlatEntityMaps({
flatEntityId: updatedPredicateGroup.roleId,
flatEntityMaps: flatRoleMaps,
})
: undefined;
if (!isDefined(role)) {
validationResult.errors.push({
code: RowLevelPermissionPredicateGroupExceptionCode.ROLE_NOT_FOUND,
message: t`Role not found`,
userFriendlyMessage: msg`Role not found`,
});
}
return validationResult;
}
}
@@ -0,0 +1,272 @@
/* @license Enterprise */
import { Injectable } from '@nestjs/common';
import { msg, t } from '@lingui/core/macro';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { RowLevelPermissionPredicateExceptionCode } from 'src/engine/metadata-modules/row-level-permission-predicate/exceptions/row-level-permission-predicate.exception';
import { type FlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate.type';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-update-validation-args.type';
import { FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-args.type';
import { fromFlatEntityPropertiesUpdatesToPartialFlatEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-flat-entity-properties-updates-to-partial-flat-entity';
@Injectable()
export class FlatRowLevelPermissionPredicateValidatorService {
validateFlatRowLevelPermissionPredicateCreation({
flatEntityToValidate: flatPredicateToValidate,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
}: FlatEntityValidationArgs<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicate
>): FailedFlatEntityValidation<FlatRowLevelPermissionPredicate> {
const {
flatRowLevelPermissionPredicateMaps: optimisticFlatPredicateMaps,
flatFieldMetadataMaps,
flatObjectMetadataMaps,
flatRowLevelPermissionPredicateGroupMaps,
flatRoleMaps,
} = optimisticFlatEntityMapsAndRelatedFlatEntityMaps as Partial<{
flatRowLevelPermissionPredicateMaps: typeof optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatRowLevelPermissionPredicateMaps;
flatFieldMetadataMaps: typeof optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatFieldMetadataMaps;
flatObjectMetadataMaps: typeof optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatObjectMetadataMaps;
flatRowLevelPermissionPredicateGroupMaps: typeof optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatRowLevelPermissionPredicateGroupMaps;
flatRoleMaps: typeof optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatRoleMaps;
}>;
const validationResult: FailedFlatEntityValidation<FlatRowLevelPermissionPredicate> =
{
type: 'create_row_level_permission_predicate',
errors: [],
flatEntityMinimalInformation: {
id: flatPredicateToValidate.id,
},
};
const existingPredicate =
optimisticFlatPredicateMaps?.byId[flatPredicateToValidate.id];
if (isDefined(existingPredicate)) {
validationResult.errors.push({
code: RowLevelPermissionPredicateExceptionCode.INVALID_ROW_LEVEL_PERMISSION_PREDICATE_DATA,
message: t`Row level permission predicate with this id already exists`,
userFriendlyMessage: msg`Row level permission predicate with this id already exists`,
});
}
const fieldMetadata = flatFieldMetadataMaps
? findFlatEntityByIdInFlatEntityMaps({
flatEntityId: flatPredicateToValidate.fieldMetadataId,
flatEntityMaps: flatFieldMetadataMaps,
})
: undefined;
if (!isDefined(fieldMetadata)) {
validationResult.errors.push({
code: RowLevelPermissionPredicateExceptionCode.FIELD_METADATA_NOT_FOUND,
message: t`Field metadata not found`,
userFriendlyMessage: msg`Field metadata not found`,
});
}
const objectMetadata = flatObjectMetadataMaps
? findFlatEntityByIdInFlatEntityMaps({
flatEntityId: flatPredicateToValidate.objectMetadataId,
flatEntityMaps: flatObjectMetadataMaps,
})
: undefined;
if (!isDefined(objectMetadata)) {
validationResult.errors.push({
code: RowLevelPermissionPredicateExceptionCode.OBJECT_METADATA_NOT_FOUND,
message: t`Object metadata not found`,
userFriendlyMessage: msg`Object metadata not found`,
});
}
if (
isDefined(flatPredicateToValidate.rowLevelPermissionPredicateGroupId) &&
flatRowLevelPermissionPredicateGroupMaps
) {
const predicateGroup = findFlatEntityByIdInFlatEntityMaps({
flatEntityId:
flatPredicateToValidate.rowLevelPermissionPredicateGroupId,
flatEntityMaps: flatRowLevelPermissionPredicateGroupMaps,
});
if (!isDefined(predicateGroup)) {
validationResult.errors.push({
code: RowLevelPermissionPredicateExceptionCode.INVALID_ROW_LEVEL_PERMISSION_PREDICATE_DATA,
message: t`Row level permission predicate group not found`,
userFriendlyMessage: msg`Row level permission predicate group not found`,
});
}
}
const role = flatRoleMaps
? findFlatEntityByIdInFlatEntityMaps({
flatEntityId: flatPredicateToValidate.roleId,
flatEntityMaps: flatRoleMaps,
})
: undefined;
if (!isDefined(role)) {
validationResult.errors.push({
code: RowLevelPermissionPredicateExceptionCode.ROLE_NOT_FOUND,
message: t`Role not found`,
userFriendlyMessage: msg`Role not found`,
});
}
return validationResult;
}
validateFlatRowLevelPermissionPredicateDeletion({
flatEntityToValidate: flatPredicateToDelete,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
}: FlatEntityValidationArgs<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicate
>): FailedFlatEntityValidation<FlatRowLevelPermissionPredicate> {
const { flatRowLevelPermissionPredicateMaps: optimisticFlatPredicateMaps } =
optimisticFlatEntityMapsAndRelatedFlatEntityMaps as Partial<{
flatRowLevelPermissionPredicateMaps: typeof optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatRowLevelPermissionPredicateMaps;
}>;
const validationResult: FailedFlatEntityValidation<FlatRowLevelPermissionPredicate> =
{
type: 'delete_row_level_permission_predicate',
errors: [],
flatEntityMinimalInformation: {
id: flatPredicateToDelete.id,
},
};
const existingPredicate =
optimisticFlatPredicateMaps?.byId[flatPredicateToDelete.id];
if (!isDefined(existingPredicate)) {
validationResult.errors.push({
code: RowLevelPermissionPredicateExceptionCode.ROW_LEVEL_PERMISSION_PREDICATE_NOT_FOUND,
message: t`Row level permission predicate to delete not found`,
userFriendlyMessage: msg`Row level permission predicate to delete not found`,
});
}
return validationResult;
}
validateFlatRowLevelPermissionPredicateUpdate({
flatEntityId,
flatEntityUpdates,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
}: FlatEntityUpdateValidationArgs<
typeof ALL_METADATA_NAME.rowLevelPermissionPredicate
>): FailedFlatEntityValidation<FlatRowLevelPermissionPredicate> {
const {
flatRowLevelPermissionPredicateMaps: optimisticFlatPredicateMaps,
flatFieldMetadataMaps,
flatObjectMetadataMaps,
flatRowLevelPermissionPredicateGroupMaps,
flatRoleMaps,
} = optimisticFlatEntityMapsAndRelatedFlatEntityMaps as Partial<{
flatRowLevelPermissionPredicateMaps: typeof optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatRowLevelPermissionPredicateMaps;
flatFieldMetadataMaps: typeof optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatFieldMetadataMaps;
flatObjectMetadataMaps: typeof optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatObjectMetadataMaps;
flatRowLevelPermissionPredicateGroupMaps: typeof optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatRowLevelPermissionPredicateGroupMaps;
flatRoleMaps: typeof optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatRoleMaps;
}>;
const validationResult: FailedFlatEntityValidation<FlatRowLevelPermissionPredicate> =
{
type: 'update_row_level_permission_predicate',
errors: [],
flatEntityMinimalInformation: {
id: flatEntityId,
},
};
const existingPredicate = optimisticFlatPredicateMaps?.byId[flatEntityId];
if (!isDefined(existingPredicate)) {
validationResult.errors.push({
code: RowLevelPermissionPredicateExceptionCode.ROW_LEVEL_PERMISSION_PREDICATE_NOT_FOUND,
message: t`Row level permission predicate to update not found`,
userFriendlyMessage: msg`Row level permission predicate to update not found`,
});
return validationResult;
}
const updatedPredicate = {
...existingPredicate,
...fromFlatEntityPropertiesUpdatesToPartialFlatEntity({
updates: flatEntityUpdates,
}),
};
const fieldMetadata = flatFieldMetadataMaps
? findFlatEntityByIdInFlatEntityMaps({
flatEntityId: updatedPredicate.fieldMetadataId,
flatEntityMaps: flatFieldMetadataMaps,
})
: undefined;
if (!isDefined(fieldMetadata)) {
validationResult.errors.push({
code: RowLevelPermissionPredicateExceptionCode.FIELD_METADATA_NOT_FOUND,
message: t`Field metadata not found`,
userFriendlyMessage: msg`Field metadata not found`,
});
}
const objectMetadata = flatObjectMetadataMaps
? findFlatEntityByIdInFlatEntityMaps({
flatEntityId: updatedPredicate.objectMetadataId,
flatEntityMaps: flatObjectMetadataMaps,
})
: undefined;
if (!isDefined(objectMetadata)) {
validationResult.errors.push({
code: RowLevelPermissionPredicateExceptionCode.OBJECT_METADATA_NOT_FOUND,
message: t`Object metadata not found`,
userFriendlyMessage: msg`Object metadata not found`,
});
}
if (
isDefined(updatedPredicate.rowLevelPermissionPredicateGroupId) &&
flatRowLevelPermissionPredicateGroupMaps
) {
const predicateGroup = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: updatedPredicate.rowLevelPermissionPredicateGroupId,
flatEntityMaps: flatRowLevelPermissionPredicateGroupMaps,
});
if (!isDefined(predicateGroup)) {
validationResult.errors.push({
code: RowLevelPermissionPredicateExceptionCode.INVALID_ROW_LEVEL_PERMISSION_PREDICATE_DATA,
message: t`Row level permission predicate group not found`,
userFriendlyMessage: msg`Row level permission predicate group not found`,
});
}
}
const role = flatRoleMaps
? findFlatEntityByIdInFlatEntityMaps({
flatEntityId: updatedPredicate.roleId,
flatEntityMaps: flatRoleMaps,
})
: undefined;
if (!isDefined(role)) {
validationResult.errors.push({
code: RowLevelPermissionPredicateExceptionCode.ROLE_NOT_FOUND,
message: t`Role not found`,
userFriendlyMessage: msg`Role not found`,
});
}
return validationResult;
}
}
@@ -3,17 +3,19 @@ import { Module } from '@nestjs/common';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service';
import { FlatAgentValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-agent-validator.service';
import { FlatPageLayoutValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-page-layout-validator.service';
import { FlatCronTriggerValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-cron-trigger-validator.service';
import { FlatDatabaseEventTriggerValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-database-event-trigger-validator.service';
import { FlatFieldMetadataValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service';
import { FlatIndexValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-index-metadata-validator.service';
import { FlatObjectMetadataValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-object-metadata-validator.service';
import { FlatPageLayoutTabValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-page-layout-tab-validator.service';
import { FlatPageLayoutValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-page-layout-validator.service';
import { FlatPageLayoutWidgetValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-page-layout-widget-validator.service';
import { FlatRoleTargetValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-role-target-validator.service';
import { FlatRoleValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-role-validator.service';
import { FlatRouteTriggerValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-route-trigger-validator.service';
import { FlatRowLevelPermissionPredicateGroupValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-row-level-permission-predicate-group-validator.service';
import { FlatRowLevelPermissionPredicateValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-row-level-permission-predicate-validator.service';
import { FlatServerlessFunctionValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-serverless-function-validator.service';
import { FlatViewFieldValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-field-validator.service';
import { FlatViewFilterValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-filter-validator.service';
@@ -41,6 +43,8 @@ import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace
FlatPageLayoutValidatorService,
FlatPageLayoutWidgetValidatorService,
FlatPageLayoutTabValidatorService,
FlatRowLevelPermissionPredicateValidatorService,
FlatRowLevelPermissionPredicateGroupValidatorService,
],
exports: [
FlatViewValidatorService,
@@ -61,6 +65,8 @@ import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace
FlatPageLayoutValidatorService,
FlatPageLayoutWidgetValidatorService,
FlatPageLayoutTabValidatorService,
FlatRowLevelPermissionPredicateValidatorService,
FlatRowLevelPermissionPredicateGroupValidatorService,
],
})
export class WorkspaceMigrationBuilderValidatorsModule {}
@@ -8,12 +8,14 @@ import { WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService } from 's
import { WorkspaceMigrationV2FieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/workspace-migration-v2-field-actions-builder.service';
import { WorkspaceMigrationV2IndexActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/index/workspace-migration-v2-index-actions-builder.service';
import { WorkspaceMigrationV2ObjectActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/workspace-migration-v2-object-actions-builder.service';
import { WorkspaceMigrationV2PageLayoutActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/page-layout/workspace-migration-v2-page-layout-actions-builder.service';
import { WorkspaceMigrationV2PageLayoutTabActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/page-layout-tab/workspace-migration-v2-page-layout-tab-actions-builder.service';
import { WorkspaceMigrationV2PageLayoutWidgetActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/page-layout-widget/workspace-migration-v2-page-layout-widget-actions-builder.service';
import { WorkspaceMigrationV2PageLayoutActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/page-layout/workspace-migration-v2-page-layout-actions-builder.service';
import { WorkspaceMigrationV2RoleTargetActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/role-target/workspace-migration-v2-role-target-actions-builder.service';
import { WorkspaceMigrationV2RoleActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/role/workspace-migration-v2-role-actions-builder.service';
import { WorkspaceMigrationV2RouteTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/route-trigger/workspace-migration-v2-route-trigger-actions-builder.service';
import { WorkspaceMigrationV2RowLevelPermissionPredicateGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/row-level-permission-predicate-group/workspace-migration-v2-row-level-permission-predicate-group-actions-builder.service';
import { WorkspaceMigrationV2RowLevelPermissionPredicateActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/row-level-permission-predicate/workspace-migration-v2-row-level-permission-predicate-actions-builder.service';
import { WorkspaceMigrationV2ServerlessFunctionActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/serverless-function/workspace-migration-v2-serverless-function-actions-builder.service';
import { WorkspaceMigrationV2ViewFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service';
import { WorkspaceMigrationV2ViewFilterActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-filter/workspace-migration-v2-view-filter-actions-builder.service';
@@ -42,6 +44,8 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
WorkspaceMigrationV2PageLayoutActionsBuilderService,
WorkspaceMigrationV2PageLayoutWidgetActionsBuilderService,
WorkspaceMigrationV2PageLayoutTabActionsBuilderService,
WorkspaceMigrationV2RowLevelPermissionPredicateActionsBuilderService,
WorkspaceMigrationV2RowLevelPermissionPredicateGroupActionsBuilderService,
],
exports: [
WorkspaceMigrationV2ViewActionsBuilderService,
@@ -61,6 +65,8 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
WorkspaceMigrationV2PageLayoutActionsBuilderService,
WorkspaceMigrationV2PageLayoutWidgetActionsBuilderService,
WorkspaceMigrationV2PageLayoutTabActionsBuilderService,
WorkspaceMigrationV2RowLevelPermissionPredicateActionsBuilderService,
WorkspaceMigrationV2RowLevelPermissionPredicateGroupActionsBuilderService,
FlatFieldMetadataTypeValidatorService,
],
})
@@ -0,0 +1,59 @@
/* @license Enterprise */
import { Injectable } from '@nestjs/common';
import {
OptimisticallyApplyActionOnAllFlatEntityMapsArgs,
WorkspaceMigrationRunnerActionHandler,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity';
import { CreateRowLevelPermissionPredicateGroupAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/row-level-permission-predicate-group/types/workspace-migration-row-level-permission-predicate-group-action-v2.type';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
@Injectable()
export class CreateRowLevelPermissionPredicateGroupActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'create_row_level_permission_predicate_group',
) {
optimisticallyApplyActionOnAllFlatEntityMaps({
action,
allFlatEntityMaps,
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<CreateRowLevelPermissionPredicateGroupAction>): Partial<AllFlatEntityMaps> {
const { flatRowLevelPermissionPredicateGroupMaps } = allFlatEntityMaps;
const { rowLevelPermissionPredicateGroup } = action;
const updatedFlatMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: rowLevelPermissionPredicateGroup,
flatEntityMaps: flatRowLevelPermissionPredicateGroupMaps,
});
return {
flatRowLevelPermissionPredicateGroupMaps: updatedFlatMaps,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<CreateRowLevelPermissionPredicateGroupAction>,
): Promise<void> {
const { action, queryRunner, workspaceId } = context;
const { rowLevelPermissionPredicateGroup } = action;
const repository =
queryRunner.manager.getRepository<RowLevelPermissionPredicateGroupEntity>(
RowLevelPermissionPredicateGroupEntity,
);
await repository.insert({
...rowLevelPermissionPredicateGroup,
workspaceId,
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerArgs<CreateRowLevelPermissionPredicateGroupAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,59 @@
/* @license Enterprise */
import { Injectable } from '@nestjs/common';
import {
OptimisticallyApplyActionOnAllFlatEntityMapsArgs,
WorkspaceMigrationRunnerActionHandler,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
import { RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity';
import { DeleteRowLevelPermissionPredicateGroupAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/row-level-permission-predicate-group/types/workspace-migration-row-level-permission-predicate-group-action-v2.type';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
@Injectable()
export class DeleteRowLevelPermissionPredicateGroupActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'delete_row_level_permission_predicate_group',
) {
optimisticallyApplyActionOnAllFlatEntityMaps({
action,
allFlatEntityMaps,
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<DeleteRowLevelPermissionPredicateGroupAction>): Partial<AllFlatEntityMaps> {
const { flatRowLevelPermissionPredicateGroupMaps } = allFlatEntityMaps;
const { rowLevelPermissionPredicateGroupId } = action;
const updatedFlatMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: rowLevelPermissionPredicateGroupId,
flatEntityMaps: flatRowLevelPermissionPredicateGroupMaps,
});
return {
flatRowLevelPermissionPredicateGroupMaps: updatedFlatMaps,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<DeleteRowLevelPermissionPredicateGroupAction>,
): Promise<void> {
const { action, queryRunner, workspaceId } = context;
const { rowLevelPermissionPredicateGroupId } = action;
const repository =
queryRunner.manager.getRepository<RowLevelPermissionPredicateGroupEntity>(
RowLevelPermissionPredicateGroupEntity,
);
await repository.delete({
id: rowLevelPermissionPredicateGroupId,
workspaceId,
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerArgs<DeleteRowLevelPermissionPredicateGroupAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,70 @@
/* @license Enterprise */
import { Injectable } from '@nestjs/common';
import {
OptimisticallyApplyActionOnAllFlatEntityMapsArgs,
WorkspaceMigrationRunnerActionHandler,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import { RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity';
import { UpdateRowLevelPermissionPredicateGroupAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/row-level-permission-predicate-group/types/workspace-migration-row-level-permission-predicate-group-action-v2.type';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
import { fromFlatEntityPropertiesUpdatesToPartialFlatEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-flat-entity-properties-updates-to-partial-flat-entity';
@Injectable()
export class UpdateRowLevelPermissionPredicateGroupActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'update_row_level_permission_predicate_group',
) {
optimisticallyApplyActionOnAllFlatEntityMaps({
action,
allFlatEntityMaps,
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<UpdateRowLevelPermissionPredicateGroupAction>): Partial<AllFlatEntityMaps> {
const { flatRowLevelPermissionPredicateGroupMaps } = allFlatEntityMaps;
const { rowLevelPermissionPredicateGroupId } = action;
const existingGroup = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: rowLevelPermissionPredicateGroupId,
flatEntityMaps: flatRowLevelPermissionPredicateGroupMaps,
});
const updatedGroup = {
...existingGroup,
...fromFlatEntityPropertiesUpdatesToPartialFlatEntity(action),
};
const updatedFlatMaps = replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: updatedGroup,
flatEntityMaps: flatRowLevelPermissionPredicateGroupMaps,
});
return {
flatRowLevelPermissionPredicateGroupMaps: updatedFlatMaps,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<UpdateRowLevelPermissionPredicateGroupAction>,
): Promise<void> {
const { action, queryRunner } = context;
const { rowLevelPermissionPredicateGroupId } = action;
const repository =
queryRunner.manager.getRepository<RowLevelPermissionPredicateGroupEntity>(
RowLevelPermissionPredicateGroupEntity,
);
const update = fromFlatEntityPropertiesUpdatesToPartialFlatEntity(action);
await repository.update(rowLevelPermissionPredicateGroupId, update);
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerArgs<UpdateRowLevelPermissionPredicateGroupAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,59 @@
/* @license Enterprise */
import { Injectable } from '@nestjs/common';
import {
OptimisticallyApplyActionOnAllFlatEntityMapsArgs,
WorkspaceMigrationRunnerActionHandler,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity';
import { CreateRowLevelPermissionPredicateAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/row-level-permission-predicate/types/workspace-migration-row-level-permission-predicate-action-v2.type';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
@Injectable()
export class CreateRowLevelPermissionPredicateActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'create_row_level_permission_predicate',
) {
optimisticallyApplyActionOnAllFlatEntityMaps({
action,
allFlatEntityMaps,
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<CreateRowLevelPermissionPredicateAction>): Partial<AllFlatEntityMaps> {
const { flatRowLevelPermissionPredicateMaps } = allFlatEntityMaps;
const { rowLevelPermissionPredicate } = action;
const updatedFlatMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: rowLevelPermissionPredicate,
flatEntityMaps: flatRowLevelPermissionPredicateMaps,
});
return {
flatRowLevelPermissionPredicateMaps: updatedFlatMaps,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<CreateRowLevelPermissionPredicateAction>,
): Promise<void> {
const { action, queryRunner, workspaceId } = context;
const { rowLevelPermissionPredicate } = action;
const repository =
queryRunner.manager.getRepository<RowLevelPermissionPredicateEntity>(
RowLevelPermissionPredicateEntity,
);
await repository.insert({
...rowLevelPermissionPredicate,
workspaceId,
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerArgs<CreateRowLevelPermissionPredicateAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,59 @@
/* @license Enterprise */
import { Injectable } from '@nestjs/common';
import {
OptimisticallyApplyActionOnAllFlatEntityMapsArgs,
WorkspaceMigrationRunnerActionHandler,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
import { RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity';
import { DeleteRowLevelPermissionPredicateAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/row-level-permission-predicate/types/workspace-migration-row-level-permission-predicate-action-v2.type';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
@Injectable()
export class DeleteRowLevelPermissionPredicateActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'delete_row_level_permission_predicate',
) {
optimisticallyApplyActionOnAllFlatEntityMaps({
action,
allFlatEntityMaps,
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<DeleteRowLevelPermissionPredicateAction>): Partial<AllFlatEntityMaps> {
const { flatRowLevelPermissionPredicateMaps } = allFlatEntityMaps;
const { rowLevelPermissionPredicateId } = action;
const updatedFlatMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: rowLevelPermissionPredicateId,
flatEntityMaps: flatRowLevelPermissionPredicateMaps,
});
return {
flatRowLevelPermissionPredicateMaps: updatedFlatMaps,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<DeleteRowLevelPermissionPredicateAction>,
): Promise<void> {
const { action, queryRunner, workspaceId } = context;
const { rowLevelPermissionPredicateId } = action;
const repository =
queryRunner.manager.getRepository<RowLevelPermissionPredicateEntity>(
RowLevelPermissionPredicateEntity,
);
await repository.delete({
id: rowLevelPermissionPredicateId,
workspaceId,
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerArgs<DeleteRowLevelPermissionPredicateAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,70 @@
/* @license Enterprise */
import { Injectable } from '@nestjs/common';
import {
OptimisticallyApplyActionOnAllFlatEntityMapsArgs,
WorkspaceMigrationRunnerActionHandler,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import { RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity';
import { UpdateRowLevelPermissionPredicateAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/row-level-permission-predicate/types/workspace-migration-row-level-permission-predicate-action-v2.type';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
import { fromFlatEntityPropertiesUpdatesToPartialFlatEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-flat-entity-properties-updates-to-partial-flat-entity';
@Injectable()
export class UpdateRowLevelPermissionPredicateActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'update_row_level_permission_predicate',
) {
optimisticallyApplyActionOnAllFlatEntityMaps({
action,
allFlatEntityMaps,
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<UpdateRowLevelPermissionPredicateAction>): Partial<AllFlatEntityMaps> {
const { flatRowLevelPermissionPredicateMaps } = allFlatEntityMaps;
const { rowLevelPermissionPredicateId } = action;
const existingPredicate = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: rowLevelPermissionPredicateId,
flatEntityMaps: flatRowLevelPermissionPredicateMaps,
});
const updatedPredicate = {
...existingPredicate,
...fromFlatEntityPropertiesUpdatesToPartialFlatEntity(action),
};
const updatedFlatMaps = replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: updatedPredicate,
flatEntityMaps: flatRowLevelPermissionPredicateMaps,
});
return {
flatRowLevelPermissionPredicateMaps: updatedFlatMaps,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<UpdateRowLevelPermissionPredicateAction>,
): Promise<void> {
const { action, queryRunner } = context;
const { rowLevelPermissionPredicateId } = action;
const repository =
queryRunner.manager.getRepository<RowLevelPermissionPredicateEntity>(
RowLevelPermissionPredicateEntity,
);
const update = fromFlatEntityPropertiesUpdatesToPartialFlatEntity(action);
await repository.update(rowLevelPermissionPredicateId, update);
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerArgs<UpdateRowLevelPermissionPredicateAction>,
): Promise<void> {
return;
}
}
@@ -4,9 +4,6 @@ import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-sc
import { CreateAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/create-agent-action-handler.service';
import { DeleteAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/delete-agent-action-handler.service';
import { UpdateAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/update-agent-action-handler.service';
import { CreatePageLayoutActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/page-layout/services/create-page-layout-action-handler.service';
import { DeletePageLayoutActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/page-layout/services/delete-page-layout-action-handler.service';
import { UpdatePageLayoutActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/page-layout/services/update-page-layout-action-handler.service';
import { CreateCronTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/cron-trigger/services/create-cron-trigger-action-handler.service';
import { DeleteCronTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/cron-trigger/services/delete-cron-trigger-action-handler.service';
import { UpdateCronTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/cron-trigger/services/update-cron-trigger-action-handler.service';
@@ -27,6 +24,9 @@ import { UpdatePageLayoutTabActionHandlerService } from 'src/engine/workspace-ma
import { CreatePageLayoutWidgetActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/page-layout-widget/services/create-page-layout-widget-action-handler.service';
import { DeletePageLayoutWidgetActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/page-layout-widget/services/delete-page-layout-widget-action-handler.service';
import { UpdatePageLayoutWidgetActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/page-layout-widget/services/update-page-layout-widget-action-handler.service';
import { CreatePageLayoutActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/page-layout/services/create-page-layout-action-handler.service';
import { DeletePageLayoutActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/page-layout/services/delete-page-layout-action-handler.service';
import { UpdatePageLayoutActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/page-layout/services/update-page-layout-action-handler.service';
import { CreateRoleTargetActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/role-target/services/create-role-target-action-handler.service';
import { DeleteRoleTargetActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/role-target/services/delete-role-target-action-handler.service';
import { UpdateRoleTargetActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/role-target/services/update-role-target-action-handler.service';
@@ -36,6 +36,12 @@ import { UpdateRoleActionHandlerService } from 'src/engine/workspace-manager/wor
import { CreateRouteTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/route-trigger/services/create-route-trigger-action-handler.service';
import { DeleteRouteTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/route-trigger/services/delete-route-trigger-action-handler.service';
import { UpdateRouteTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/route-trigger/services/update-route-trigger-action-handler.service';
import { CreateRowLevelPermissionPredicateGroupActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/row-level-permission-predicate-group/services/create-row-level-permission-predicate-group-action-handler.service';
import { DeleteRowLevelPermissionPredicateGroupActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/row-level-permission-predicate-group/services/delete-row-level-permission-predicate-group-action-handler.service';
import { UpdateRowLevelPermissionPredicateGroupActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/row-level-permission-predicate-group/services/update-row-level-permission-predicate-group-action-handler.service';
import { CreateRowLevelPermissionPredicateActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/row-level-permission-predicate/services/create-row-level-permission-predicate-action-handler.service';
import { DeleteRowLevelPermissionPredicateActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/row-level-permission-predicate/services/delete-row-level-permission-predicate-action-handler.service';
import { UpdateRowLevelPermissionPredicateActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/row-level-permission-predicate/services/update-row-level-permission-predicate-action-handler.service';
import { CreateServerlessFunctionActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/serverless-function/services/create-serverless-function-action-handler.service';
import { DeleteServerlessFunctionActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/serverless-function/services/delete-serverless-function-action-handler.service';
import { UpdateServerlessFunctionActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/serverless-function/services/update-serverless-function-action-handler.service';
@@ -121,6 +127,14 @@ import { UpdateViewActionHandlerService } from 'src/engine/workspace-manager/wor
CreatePageLayoutTabActionHandlerService,
UpdatePageLayoutTabActionHandlerService,
DeletePageLayoutTabActionHandlerService,
CreateRowLevelPermissionPredicateActionHandlerService,
UpdateRowLevelPermissionPredicateActionHandlerService,
DeleteRowLevelPermissionPredicateActionHandlerService,
CreateRowLevelPermissionPredicateGroupActionHandlerService,
UpdateRowLevelPermissionPredicateGroupActionHandlerService,
DeleteRowLevelPermissionPredicateGroupActionHandlerService,
],
})
export class WorkspaceSchemaMigrationRunnerActionHandlersModule {}
@@ -17,6 +17,8 @@ exports[`View Field Resolver - Failing Create Many Operations - v2 should accumu
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [
@@ -91,6 +93,8 @@ exports[`View Field Resolver - Failing Create Many Operations - v2 should accumu
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
@@ -17,6 +17,8 @@ exports[`View Field Resolver - Failing Create Operation - v2 Should fail to crea
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [
@@ -54,6 +56,8 @@ exports[`View Field Resolver - Failing Create Operation - v2 Should fail to crea
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
@@ -85,6 +89,8 @@ exports[`View Field Resolver - Failing Create Operation - v2 should fail to crea
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [
@@ -122,6 +128,8 @@ exports[`View Field Resolver - Failing Create Operation - v2 should fail to crea
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
@@ -153,6 +161,8 @@ exports[`View Field Resolver - Failing Create Operation - v2 should fail to crea
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [
@@ -190,6 +200,8 @@ exports[`View Field Resolver - Failing Create Operation - v2 should fail to crea
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
@@ -17,6 +17,8 @@ exports[`View Field Resolver - Successful object metadata identifier update side
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [
@@ -54,6 +56,8 @@ exports[`View Field Resolver - Successful object metadata identifier update side
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
@@ -85,6 +89,8 @@ exports[`View Field Resolver - Successful object metadata identifier update side
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [
@@ -122,6 +128,8 @@ exports[`View Field Resolver - Successful object metadata identifier update side
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
@@ -153,6 +161,8 @@ exports[`View Field Resolver - Successful object metadata identifier update side
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [
@@ -188,6 +198,8 @@ exports[`View Field Resolver - Successful object metadata identifier update side
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
@@ -219,6 +231,8 @@ exports[`View Field Resolver - Successful object metadata identifier update side
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [
@@ -256,6 +270,8 @@ exports[`View Field Resolver - Successful object metadata identifier update side
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
@@ -287,6 +303,8 @@ exports[`View Field Resolver - Successful object metadata identifier update side
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [
@@ -324,6 +342,8 @@ exports[`View Field Resolver - Successful object metadata identifier update side
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
@@ -33,6 +33,8 @@ exports[`Agent creation should fail when computed name already exists 1`] = `
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [],
@@ -53,6 +55,8 @@ exports[`Agent creation should fail when computed name already exists 1`] = `
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
@@ -100,6 +104,8 @@ exports[`Agent creation should fail when label is empty string 1`] = `
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [],
@@ -120,6 +126,8 @@ exports[`Agent creation should fail when label is empty string 1`] = `
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
@@ -181,6 +189,8 @@ exports[`Agent creation should fail when modelId is empty string 1`] = `
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [],
@@ -201,6 +211,8 @@ exports[`Agent creation should fail when modelId is empty string 1`] = `
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
@@ -262,6 +274,8 @@ exports[`Agent creation should fail when prompt is empty string 1`] = `
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [],
@@ -282,6 +296,8 @@ exports[`Agent creation should fail when prompt is empty string 1`] = `
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
@@ -343,6 +359,8 @@ exports[`Agent creation should fail when responseFormat has invalid type 1`] = `
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [],
@@ -363,6 +381,8 @@ exports[`Agent creation should fail when responseFormat has invalid type 1`] = `
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
@@ -410,6 +430,8 @@ exports[`Agent creation should fail when responseFormat type is json but schema
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [],
@@ -430,6 +452,8 @@ exports[`Agent creation should fail when responseFormat type is json but schema
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
@@ -477,6 +501,8 @@ exports[`Agent creation should fail when responseFormat type is text but schema
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [],
@@ -497,6 +523,8 @@ exports[`Agent creation should fail when responseFormat type is text but schema
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,

Some files were not shown because too many files have changed in this diff Show More