diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql index bc6cd87d69..3c22724f72 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql @@ -244,7 +244,7 @@ type FieldPermission { canUpdateFieldValue: Boolean } -type PermissionFlag { +type RolePermissionFlag { id: UUID! roleId: UUID! flag: PermissionFlagType! @@ -276,7 +276,7 @@ type Role { canUpdateAllObjectRecords: Boolean! canSoftDeleteAllObjectRecords: Boolean! canDestroyAllObjectRecords: Boolean! - permissionFlags: [PermissionFlag!] + permissionFlags: [RolePermissionFlag!] objectPermissions: [ObjectPermission!] fieldPermissions: [FieldPermission!] rowLevelPermissionPredicates: [RowLevelPermissionPredicate!] @@ -2884,6 +2884,7 @@ enum AllMetadataName { pageLayoutTab commandMenuItem navigationMenuItem + rolePermissionFlag permissionFlag objectPermission fieldPermission @@ -3237,7 +3238,7 @@ type Mutation { updateOneRole(updateRoleInput: UpdateRoleInput!): Role! deleteOneRole(roleId: UUID!): String! upsertObjectPermissions(upsertObjectPermissionsInput: UpsertObjectPermissionsInput!): [ObjectPermission!]! - upsertPermissionFlags(upsertPermissionFlagsInput: UpsertPermissionFlagsInput!): [PermissionFlag!]! + upsertPermissionFlags(upsertPermissionFlagsInput: UpsertPermissionFlagsInput!): [RolePermissionFlag!]! upsertFieldPermissions(upsertFieldPermissionsInput: UpsertFieldPermissionsInput!): [FieldPermission!]! upsertRowLevelPermissionPredicates(input: UpsertRowLevelPermissionPredicatesInput!): UpsertRowLevelPermissionPredicatesResult! assignRoleToAgent(agentId: UUID!, roleId: UUID!): Boolean! diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts index ae804d07cc..b4ba05a9dd 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts @@ -190,11 +190,11 @@ export interface FieldPermission { __typename: 'FieldPermission' } -export interface PermissionFlag { +export interface RolePermissionFlag { id: Scalars['UUID'] roleId: Scalars['UUID'] flag: PermissionFlagType - __typename: 'PermissionFlag' + __typename: 'RolePermissionFlag' } export interface ApiKeyForRole { @@ -224,7 +224,7 @@ export interface Role { canUpdateAllObjectRecords: Scalars['Boolean'] canSoftDeleteAllObjectRecords: Scalars['Boolean'] canDestroyAllObjectRecords: Scalars['Boolean'] - permissionFlags?: PermissionFlag[] + permissionFlags?: RolePermissionFlag[] objectPermissions?: ObjectPermission[] fieldPermissions?: FieldPermission[] rowLevelPermissionPredicates?: RowLevelPermissionPredicate[] @@ -2516,7 +2516,7 @@ export interface CollectionHash { __typename: 'CollectionHash' } -export type AllMetadataName = 'fieldMetadata' | 'objectMetadata' | 'view' | 'viewField' | 'viewFieldGroup' | 'viewGroup' | 'viewSort' | 'rowLevelPermissionPredicate' | 'rowLevelPermissionPredicateGroup' | 'viewFilterGroup' | 'index' | 'logicFunction' | 'viewFilter' | 'role' | 'roleTarget' | 'agent' | 'skill' | 'pageLayout' | 'pageLayoutWidget' | 'pageLayoutTab' | 'commandMenuItem' | 'navigationMenuItem' | 'permissionFlag' | 'objectPermission' | 'fieldPermission' | 'frontComponent' | 'webhook' | 'applicationVariable' | 'connectionProvider' +export type AllMetadataName = 'fieldMetadata' | 'objectMetadata' | 'view' | 'viewField' | 'viewFieldGroup' | 'viewGroup' | 'viewSort' | 'rowLevelPermissionPredicate' | 'rowLevelPermissionPredicateGroup' | 'viewFilterGroup' | 'index' | 'logicFunction' | 'viewFilter' | 'role' | 'roleTarget' | 'agent' | 'skill' | 'pageLayout' | 'pageLayoutWidget' | 'pageLayoutTab' | 'commandMenuItem' | 'navigationMenuItem' | 'rolePermissionFlag' | 'permissionFlag' | 'objectPermission' | 'fieldPermission' | 'frontComponent' | 'webhook' | 'applicationVariable' | 'connectionProvider' export interface MinimalObjectMetadata { id: Scalars['UUID'] @@ -2772,7 +2772,7 @@ export interface Mutation { updateOneRole: Role deleteOneRole: Scalars['String'] upsertObjectPermissions: ObjectPermission[] - upsertPermissionFlags: PermissionFlag[] + upsertPermissionFlags: RolePermissionFlag[] upsertFieldPermissions: FieldPermission[] upsertRowLevelPermissionPredicates: UpsertRowLevelPermissionPredicatesResult assignRoleToAgent: Scalars['Boolean'] @@ -3072,7 +3072,7 @@ export interface FieldPermissionGenqlSelection{ __scalar?: boolean | number } -export interface PermissionFlagGenqlSelection{ +export interface RolePermissionFlagGenqlSelection{ id?: boolean | number roleId?: boolean | number flag?: boolean | number @@ -3108,7 +3108,7 @@ export interface RoleGenqlSelection{ canUpdateAllObjectRecords?: boolean | number canSoftDeleteAllObjectRecords?: boolean | number canDestroyAllObjectRecords?: boolean | number - permissionFlags?: PermissionFlagGenqlSelection + permissionFlags?: RolePermissionFlagGenqlSelection objectPermissions?: ObjectPermissionGenqlSelection fieldPermissions?: FieldPermissionGenqlSelection rowLevelPermissionPredicates?: RowLevelPermissionPredicateGenqlSelection @@ -5847,7 +5847,7 @@ export interface MutationGenqlSelection{ updateOneRole?: (RoleGenqlSelection & { __args: {updateRoleInput: UpdateRoleInput} }) deleteOneRole?: { __args: {roleId: Scalars['UUID']} } upsertObjectPermissions?: (ObjectPermissionGenqlSelection & { __args: {upsertObjectPermissionsInput: UpsertObjectPermissionsInput} }) - upsertPermissionFlags?: (PermissionFlagGenqlSelection & { __args: {upsertPermissionFlagsInput: UpsertPermissionFlagsInput} }) + upsertPermissionFlags?: (RolePermissionFlagGenqlSelection & { __args: {upsertPermissionFlagsInput: UpsertPermissionFlagsInput} }) upsertFieldPermissions?: (FieldPermissionGenqlSelection & { __args: {upsertFieldPermissionsInput: UpsertFieldPermissionsInput} }) upsertRowLevelPermissionPredicates?: (UpsertRowLevelPermissionPredicatesResultGenqlSelection & { __args: {input: UpsertRowLevelPermissionPredicatesInput} }) assignRoleToAgent?: { __args: {agentId: Scalars['UUID'], roleId: Scalars['UUID']} } @@ -6426,10 +6426,10 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null - const PermissionFlag_possibleTypes: string[] = ['PermissionFlag'] - export const isPermissionFlag = (obj?: { __typename?: any } | null): obj is PermissionFlag => { - if (!obj?.__typename) throw new Error('__typename is missing in "isPermissionFlag"') - return PermissionFlag_possibleTypes.includes(obj.__typename) + const RolePermissionFlag_possibleTypes: string[] = ['RolePermissionFlag'] + export const isRolePermissionFlag = (obj?: { __typename?: any } | null): obj is RolePermissionFlag => { + if (!obj?.__typename) throw new Error('__typename is missing in "isRolePermissionFlag"') + return RolePermissionFlag_possibleTypes.includes(obj.__typename) } @@ -8899,6 +8899,7 @@ export const enumAllMetadataName = { pageLayoutTab: 'pageLayoutTab' as const, commandMenuItem: 'commandMenuItem' as const, navigationMenuItem: 'navigationMenuItem' as const, + rolePermissionFlag: 'rolePermissionFlag' as const, permissionFlag: 'permissionFlag' as const, objectPermission: 'objectPermission' as const, fieldPermission: 'fieldPermission' as const, diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts index 14d5344b22..40277af795 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/types.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts @@ -500,7 +500,7 @@ export default { 1 ] }, - "PermissionFlag": { + "RolePermissionFlag": { "id": [ 3 ], diff --git a/packages/twenty-docs/developers/extend/apps/config/roles.mdx b/packages/twenty-docs/developers/extend/apps/config/roles.mdx index 814c03b3d7..811589ef64 100644 --- a/packages/twenty-docs/developers/extend/apps/config/roles.mdx +++ b/packages/twenty-docs/developers/extend/apps/config/roles.mdx @@ -1,7 +1,7 @@ --- title: Roles & Permissions description: Declare what objects and fields your app's logic functions and front components can read and write. -icon: "shield-halved" +icon: 'shield-halved' --- A **role** is a permission set: which objects an app can read or write, which fields it can see, and which platform-level capabilities it can use. Every app's logic functions and front components inherit the permissions of the role marked with `defineApplicationRole()` (see [The default function role](#the-default-function-role) below). @@ -40,7 +40,8 @@ export default defineRole({ objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, fieldUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name + .universalIdentifier, canReadFieldValue: false, canUpdateFieldValue: false, }, @@ -80,6 +81,7 @@ export default defineApplicationRole({ `defineApplicationRole()` is a thin wrapper around `defineRole()` that flags **the** role used as your application's default at install time. Validation is identical to `defineRole`, but the build pipeline auto-wires its `universalIdentifier` into the application manifest's `defaultRoleUniversalIdentifier` — so you do not need to reference it from [`defineApplication`](/developers/extend/apps/config/application) yourself. Notes: + - Exactly **one** `defineApplicationRole(...)` is allowed per app — the manifest build will fail if it finds more than one. - Use `defineRole()` (not `defineApplicationRole()`) for any **additional** roles your app ships. - Setting `defaultRoleUniversalIdentifier` explicitly on `defineApplication()` is still supported for backward compatibility, but is deprecated in favor of `defineApplicationRole()`. diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 35534a4f72..4de0c4c797 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -201,6 +201,7 @@ export enum AllMetadataName { pageLayoutWidget = 'pageLayoutWidget', permissionFlag = 'permissionFlag', role = 'role', + rolePermissionFlag = 'rolePermissionFlag', roleTarget = 'roleTarget', rowLevelPermissionPredicate = 'rowLevelPermissionPredicate', rowLevelPermissionPredicateGroup = 'rowLevelPermissionPredicateGroup', @@ -2562,7 +2563,7 @@ export type Mutation = { upsertFieldPermissions: Array; upsertFieldsWidget: View; upsertObjectPermissions: Array; - upsertPermissionFlags: Array; + upsertPermissionFlags: Array; upsertRowLevelPermissionPredicates: UpsertRowLevelPermissionPredicatesResult; upsertViewWidget: View; validateApprovedAccessDomain: ApprovedAccessDomain; @@ -3941,13 +3942,6 @@ export type PathCommandMenuItemPayload = { path: Scalars['String']; }; -export type PermissionFlag = { - __typename?: 'PermissionFlag'; - flag: PermissionFlagType; - id: Scalars['UUID']; - roleId: Scalars['UUID']; -}; - export enum PermissionFlagType { AI = 'AI', AI_SETTINGS = 'AI_SETTINGS', @@ -4636,13 +4630,20 @@ export type Role = { isEditable: Scalars['Boolean']; label: Scalars['String']; objectPermissions?: Maybe>; - permissionFlags?: Maybe>; + permissionFlags?: Maybe>; rowLevelPermissionPredicateGroups?: Maybe>; rowLevelPermissionPredicates?: Maybe>; universalIdentifier?: Maybe; workspaceMembers: Array; }; +export type RolePermissionFlag = { + __typename?: 'RolePermissionFlag'; + flag: PermissionFlagType; + id: Scalars['UUID']; + roleId: Scalars['UUID']; +}; + export type RotateClientSecret = { __typename?: 'RotateClientSecret'; clientSecret: Scalars['String']; @@ -7329,10 +7330,10 @@ export type FieldPermissionFragmentFragment = { __typename?: 'FieldPermission', export type ObjectPermissionFragmentFragment = { __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null, rowLevelPermissionPredicates?: Array<{ __typename?: 'RowLevelPermissionPredicate', id: string, fieldMetadataId: string, objectMetadataId: string, operand: RowLevelPermissionPredicateOperand, subFieldName?: string | null, workspaceMemberFieldMetadataId?: string | null, workspaceMemberSubFieldName?: string | null, rowLevelPermissionPredicateGroupId?: string | null, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, value?: any | null }> | null, rowLevelPermissionPredicateGroups?: Array<{ __typename?: 'RowLevelPermissionPredicateGroup', id: string, parentRowLevelPermissionPredicateGroupId?: string | null, logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, objectMetadataId: string }> | null }; -export type PermissionFlagFragmentFragment = { __typename?: 'PermissionFlag', id: string, flag: PermissionFlagType, roleId: string }; - export type RoleFragmentFragment = { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean }; +export type RolePermissionFlagFragmentFragment = { __typename?: 'RolePermissionFlag', id: string, flag: PermissionFlagType, roleId: string }; + export type RowLevelPermissionPredicateFragmentFragment = { __typename?: 'RowLevelPermissionPredicate', id: string, fieldMetadataId: string, objectMetadataId: string, operand: RowLevelPermissionPredicateOperand, subFieldName?: string | null, workspaceMemberFieldMetadataId?: string | null, workspaceMemberSubFieldName?: string | null, rowLevelPermissionPredicateGroupId?: string | null, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, value?: any | null }; export type RowLevelPermissionPredicateGroupFragmentFragment = { __typename?: 'RowLevelPermissionPredicateGroup', id: string, parentRowLevelPermissionPredicateGroupId?: string | null, logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, objectMetadataId: string }; @@ -7385,7 +7386,7 @@ export type UpsertPermissionFlagsMutationVariables = Exact<{ }>; -export type UpsertPermissionFlagsMutation = { __typename?: 'Mutation', upsertPermissionFlags: Array<{ __typename?: 'PermissionFlag', id: string, flag: PermissionFlagType, roleId: string }> }; +export type UpsertPermissionFlagsMutation = { __typename?: 'Mutation', upsertPermissionFlags: Array<{ __typename?: 'RolePermissionFlag', id: string, flag: PermissionFlagType, roleId: string }> }; export type UpsertRowLevelPermissionPredicatesMutationVariables = Exact<{ input: UpsertRowLevelPermissionPredicatesInput; @@ -7397,7 +7398,7 @@ export type UpsertRowLevelPermissionPredicatesMutation = { __typename?: 'Mutatio export type GetRolesQueryVariables = Exact<{ [key: string]: never; }>; -export type GetRolesQuery = { __typename?: 'Query', getRoles: Array<{ __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, userWorkspaceId?: string | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array, applicationId?: string | null, createdAt: string, updatedAt: string }>, apiKeys: Array<{ __typename?: 'ApiKeyForRole', id: string, name: string, expiresAt: string, revokedAt?: string | null }>, permissionFlags?: Array<{ __typename?: 'PermissionFlag', id: string, flag: PermissionFlagType, roleId: string }> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null, rowLevelPermissionPredicates?: Array<{ __typename?: 'RowLevelPermissionPredicate', id: string, fieldMetadataId: string, objectMetadataId: string, operand: RowLevelPermissionPredicateOperand, subFieldName?: string | null, workspaceMemberFieldMetadataId?: string | null, workspaceMemberSubFieldName?: string | null, rowLevelPermissionPredicateGroupId?: string | null, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, value?: any | null }> | null, rowLevelPermissionPredicateGroups?: Array<{ __typename?: 'RowLevelPermissionPredicateGroup', id: string, parentRowLevelPermissionPredicateGroupId?: string | null, logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, objectMetadataId: string }> | null }> | null, fieldPermissions?: Array<{ __typename?: 'FieldPermission', objectMetadataId: string, fieldMetadataId: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null, id: string, roleId: string }> | null, rowLevelPermissionPredicates?: Array<{ __typename?: 'RowLevelPermissionPredicate', id: string, fieldMetadataId: string, objectMetadataId: string, operand: RowLevelPermissionPredicateOperand, subFieldName?: string | null, workspaceMemberFieldMetadataId?: string | null, workspaceMemberSubFieldName?: string | null, rowLevelPermissionPredicateGroupId?: string | null, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, value?: any | null }> | null, rowLevelPermissionPredicateGroups?: Array<{ __typename?: 'RowLevelPermissionPredicateGroup', id: string, parentRowLevelPermissionPredicateGroupId?: string | null, logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, objectMetadataId: string }> | null }> }; +export type GetRolesQuery = { __typename?: 'Query', getRoles: Array<{ __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, userWorkspaceId?: string | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array, applicationId?: string | null, createdAt: string, updatedAt: string }>, apiKeys: Array<{ __typename?: 'ApiKeyForRole', id: string, name: string, expiresAt: string, revokedAt?: string | null }>, permissionFlags?: Array<{ __typename?: 'RolePermissionFlag', id: string, flag: PermissionFlagType, roleId: string }> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null, rowLevelPermissionPredicates?: Array<{ __typename?: 'RowLevelPermissionPredicate', id: string, fieldMetadataId: string, objectMetadataId: string, operand: RowLevelPermissionPredicateOperand, subFieldName?: string | null, workspaceMemberFieldMetadataId?: string | null, workspaceMemberSubFieldName?: string | null, rowLevelPermissionPredicateGroupId?: string | null, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, value?: any | null }> | null, rowLevelPermissionPredicateGroups?: Array<{ __typename?: 'RowLevelPermissionPredicateGroup', id: string, parentRowLevelPermissionPredicateGroupId?: string | null, logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, objectMetadataId: string }> | null }> | null, fieldPermissions?: Array<{ __typename?: 'FieldPermission', objectMetadataId: string, fieldMetadataId: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null, id: string, roleId: string }> | null, rowLevelPermissionPredicates?: Array<{ __typename?: 'RowLevelPermissionPredicate', id: string, fieldMetadataId: string, objectMetadataId: string, operand: RowLevelPermissionPredicateOperand, subFieldName?: string | null, workspaceMemberFieldMetadataId?: string | null, workspaceMemberSubFieldName?: string | null, rowLevelPermissionPredicateGroupId?: string | null, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, value?: any | null }> | null, rowLevelPermissionPredicateGroups?: Array<{ __typename?: 'RowLevelPermissionPredicateGroup', id: string, parentRowLevelPermissionPredicateGroupId?: string | null, logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, objectMetadataId: string }> | null }> }; export type CreateApprovedAccessDomainMutationVariables = Exact<{ input: CreateApprovedAccessDomainInput; @@ -7943,7 +7944,7 @@ export const ApiKeyFragmentFragmentDoc = {"kind":"Document","definitions":[{"kin export const WebhookFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WebhookFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Webhook"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"targetUrl"}},{"kind":"Field","name":{"kind":"Name","value":"operations"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"secret"}}]}}]} as unknown as DocumentNode; export const ApiKeyForRoleFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApiKeyForRoleFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApiKeyForRole"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"expiresAt"}},{"kind":"Field","name":{"kind":"Name","value":"revokedAt"}}]}}]} as unknown as DocumentNode; export const FieldPermissionFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FieldPermissionFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FieldPermission"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"fieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"canReadFieldValue"}},{"kind":"Field","name":{"kind":"Name","value":"canUpdateFieldValue"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}}]}}]} as unknown as DocumentNode; -export const PermissionFlagFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PermissionFlagFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PermissionFlag"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"flag"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}}]}}]} as unknown as DocumentNode; +export const RolePermissionFlagFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RolePermissionFlagFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RolePermissionFlag"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"flag"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}}]}}]} as unknown as DocumentNode; export const WorkspaceMemberQueryFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceMemberQueryFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceMember"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"colorScheme"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"locale"}},{"kind":"Field","name":{"kind":"Name","value":"userEmail"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"timeZone"}},{"kind":"Field","name":{"kind":"Name","value":"dateFormat"}},{"kind":"Field","name":{"kind":"Name","value":"timeFormat"}},{"kind":"Field","name":{"kind":"Name","value":"calendarStartDay"}},{"kind":"Field","name":{"kind":"Name","value":"numberFormat"}}]}}]} as unknown as DocumentNode; export const PartialWorkspaceMemberQueryFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PartialWorkspaceMemberQueryFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceMember"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"userEmail"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}}]}}]} as unknown as DocumentNode; export const DeletedWorkspaceMemberQueryFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"DeletedWorkspaceMemberQueryFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"DeletedWorkspaceMember"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"userEmail"}}]}}]} as unknown as DocumentNode; @@ -8151,9 +8152,9 @@ export const UpdateOneRoleDocument = {"kind":"Document","definitions":[{"kind":" export const UpdateWorkspaceMemberRoleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWorkspaceMemberRole"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceMemberId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"roleId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateWorkspaceMemberRole"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceMemberId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceMemberId"}}},{"kind":"Argument","name":{"kind":"Name","value":"roleId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"roleId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceMemberQueryFragment"}},{"kind":"Field","name":{"kind":"Name","value":"roles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RoleFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceMemberQueryFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceMember"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"colorScheme"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"locale"}},{"kind":"Field","name":{"kind":"Name","value":"userEmail"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"timeZone"}},{"kind":"Field","name":{"kind":"Name","value":"dateFormat"}},{"kind":"Field","name":{"kind":"Name","value":"timeFormat"}},{"kind":"Field","name":{"kind":"Name","value":"calendarStartDay"}},{"kind":"Field","name":{"kind":"Name","value":"numberFormat"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RoleFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Role"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"canUpdateAllSettings"}},{"kind":"Field","name":{"kind":"Name","value":"canAccessAllTools"}},{"kind":"Field","name":{"kind":"Name","value":"isEditable"}},{"kind":"Field","name":{"kind":"Name","value":"canReadAllObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canUpdateAllObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canSoftDeleteAllObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canDestroyAllObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canBeAssignedToUsers"}},{"kind":"Field","name":{"kind":"Name","value":"canBeAssignedToAgents"}},{"kind":"Field","name":{"kind":"Name","value":"canBeAssignedToApiKeys"}}]}}]} as unknown as DocumentNode; export const UpsertFieldPermissionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpsertFieldPermissions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"upsertFieldPermissionsInput"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpsertFieldPermissionsInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"upsertFieldPermissions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"upsertFieldPermissionsInput"},"value":{"kind":"Variable","name":{"kind":"Name","value":"upsertFieldPermissionsInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FieldPermissionFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FieldPermissionFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FieldPermission"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"fieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"canReadFieldValue"}},{"kind":"Field","name":{"kind":"Name","value":"canUpdateFieldValue"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}}]}}]} as unknown as DocumentNode; export const UpsertObjectPermissionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpsertObjectPermissions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"upsertObjectPermissionsInput"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpsertObjectPermissionsInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"upsertObjectPermissions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"upsertObjectPermissionsInput"},"value":{"kind":"Variable","name":{"kind":"Name","value":"upsertObjectPermissionsInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ObjectPermissionFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RowLevelPermissionPredicateFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RowLevelPermissionPredicate"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"fieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"operand"}},{"kind":"Field","name":{"kind":"Name","value":"subFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberSubFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"rowLevelPermissionPredicateGroupId"}},{"kind":"Field","name":{"kind":"Name","value":"positionInRowLevelPermissionPredicateGroup"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RowLevelPermissionPredicateGroupFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RowLevelPermissionPredicateGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"parentRowLevelPermissionPredicateGroupId"}},{"kind":"Field","name":{"kind":"Name","value":"logicalOperator"}},{"kind":"Field","name":{"kind":"Name","value":"positionInRowLevelPermissionPredicateGroup"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}},{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ObjectPermissionFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ObjectPermission"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"canReadObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canUpdateObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canSoftDeleteObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canDestroyObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"restrictedFields"}},{"kind":"Field","name":{"kind":"Name","value":"rowLevelPermissionPredicates"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RowLevelPermissionPredicateFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"rowLevelPermissionPredicateGroups"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RowLevelPermissionPredicateGroupFragment"}}]}}]}}]} as unknown as DocumentNode; -export const UpsertPermissionFlagsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpsertPermissionFlags"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"upsertPermissionFlagsInput"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpsertPermissionFlagsInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"upsertPermissionFlags"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"upsertPermissionFlagsInput"},"value":{"kind":"Variable","name":{"kind":"Name","value":"upsertPermissionFlagsInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PermissionFlagFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PermissionFlagFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PermissionFlag"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"flag"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}}]}}]} as unknown as DocumentNode; +export const UpsertPermissionFlagsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpsertPermissionFlags"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"upsertPermissionFlagsInput"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpsertPermissionFlagsInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"upsertPermissionFlags"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"upsertPermissionFlagsInput"},"value":{"kind":"Variable","name":{"kind":"Name","value":"upsertPermissionFlagsInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RolePermissionFlagFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RolePermissionFlagFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RolePermissionFlag"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"flag"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}}]}}]} as unknown as DocumentNode; export const UpsertRowLevelPermissionPredicatesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpsertRowLevelPermissionPredicates"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpsertRowLevelPermissionPredicatesInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"upsertRowLevelPermissionPredicates"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"predicates"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RowLevelPermissionPredicateFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"predicateGroups"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RowLevelPermissionPredicateGroupFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RowLevelPermissionPredicateFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RowLevelPermissionPredicate"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"fieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"operand"}},{"kind":"Field","name":{"kind":"Name","value":"subFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberSubFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"rowLevelPermissionPredicateGroupId"}},{"kind":"Field","name":{"kind":"Name","value":"positionInRowLevelPermissionPredicateGroup"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RowLevelPermissionPredicateGroupFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RowLevelPermissionPredicateGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"parentRowLevelPermissionPredicateGroupId"}},{"kind":"Field","name":{"kind":"Name","value":"logicalOperator"}},{"kind":"Field","name":{"kind":"Name","value":"positionInRowLevelPermissionPredicateGroup"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}},{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}}]}}]} as unknown as DocumentNode; -export const GetRolesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RoleFragment"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMembers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PartialWorkspaceMemberQueryFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"agents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AgentFields"}}]}},{"kind":"Field","name":{"kind":"Name","value":"apiKeys"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApiKeyForRoleFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"permissionFlags"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PermissionFlagFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"objectPermissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ObjectPermissionFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fieldPermissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FieldPermissionFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"rowLevelPermissionPredicates"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RowLevelPermissionPredicateFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"rowLevelPermissionPredicateGroups"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RowLevelPermissionPredicateGroupFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RowLevelPermissionPredicateFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RowLevelPermissionPredicate"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"fieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"operand"}},{"kind":"Field","name":{"kind":"Name","value":"subFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberSubFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"rowLevelPermissionPredicateGroupId"}},{"kind":"Field","name":{"kind":"Name","value":"positionInRowLevelPermissionPredicateGroup"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RowLevelPermissionPredicateGroupFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RowLevelPermissionPredicateGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"parentRowLevelPermissionPredicateGroupId"}},{"kind":"Field","name":{"kind":"Name","value":"logicalOperator"}},{"kind":"Field","name":{"kind":"Name","value":"positionInRowLevelPermissionPredicateGroup"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}},{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RoleFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Role"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"canUpdateAllSettings"}},{"kind":"Field","name":{"kind":"Name","value":"canAccessAllTools"}},{"kind":"Field","name":{"kind":"Name","value":"isEditable"}},{"kind":"Field","name":{"kind":"Name","value":"canReadAllObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canUpdateAllObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canSoftDeleteAllObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canDestroyAllObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canBeAssignedToUsers"}},{"kind":"Field","name":{"kind":"Name","value":"canBeAssignedToAgents"}},{"kind":"Field","name":{"kind":"Name","value":"canBeAssignedToApiKeys"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PartialWorkspaceMemberQueryFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceMember"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"userEmail"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AgentFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Agent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"prompt"}},{"kind":"Field","name":{"kind":"Name","value":"modelId"}},{"kind":"Field","name":{"kind":"Name","value":"responseFormat"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"modelConfiguration"}},{"kind":"Field","name":{"kind":"Name","value":"evaluationInputs"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApiKeyForRoleFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApiKeyForRole"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"expiresAt"}},{"kind":"Field","name":{"kind":"Name","value":"revokedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PermissionFlagFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PermissionFlag"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"flag"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ObjectPermissionFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ObjectPermission"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"canReadObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canUpdateObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canSoftDeleteObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canDestroyObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"restrictedFields"}},{"kind":"Field","name":{"kind":"Name","value":"rowLevelPermissionPredicates"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RowLevelPermissionPredicateFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"rowLevelPermissionPredicateGroups"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RowLevelPermissionPredicateGroupFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FieldPermissionFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FieldPermission"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"fieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"canReadFieldValue"}},{"kind":"Field","name":{"kind":"Name","value":"canUpdateFieldValue"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}}]}}]} as unknown as DocumentNode; +export const GetRolesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RoleFragment"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMembers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PartialWorkspaceMemberQueryFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"agents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AgentFields"}}]}},{"kind":"Field","name":{"kind":"Name","value":"apiKeys"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApiKeyForRoleFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"permissionFlags"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RolePermissionFlagFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"objectPermissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ObjectPermissionFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fieldPermissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FieldPermissionFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"rowLevelPermissionPredicates"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RowLevelPermissionPredicateFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"rowLevelPermissionPredicateGroups"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RowLevelPermissionPredicateGroupFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RowLevelPermissionPredicateFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RowLevelPermissionPredicate"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"fieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"operand"}},{"kind":"Field","name":{"kind":"Name","value":"subFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberSubFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"rowLevelPermissionPredicateGroupId"}},{"kind":"Field","name":{"kind":"Name","value":"positionInRowLevelPermissionPredicateGroup"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RowLevelPermissionPredicateGroupFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RowLevelPermissionPredicateGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"parentRowLevelPermissionPredicateGroupId"}},{"kind":"Field","name":{"kind":"Name","value":"logicalOperator"}},{"kind":"Field","name":{"kind":"Name","value":"positionInRowLevelPermissionPredicateGroup"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}},{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RoleFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Role"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"canUpdateAllSettings"}},{"kind":"Field","name":{"kind":"Name","value":"canAccessAllTools"}},{"kind":"Field","name":{"kind":"Name","value":"isEditable"}},{"kind":"Field","name":{"kind":"Name","value":"canReadAllObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canUpdateAllObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canSoftDeleteAllObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canDestroyAllObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canBeAssignedToUsers"}},{"kind":"Field","name":{"kind":"Name","value":"canBeAssignedToAgents"}},{"kind":"Field","name":{"kind":"Name","value":"canBeAssignedToApiKeys"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PartialWorkspaceMemberQueryFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceMember"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"userEmail"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AgentFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Agent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"prompt"}},{"kind":"Field","name":{"kind":"Name","value":"modelId"}},{"kind":"Field","name":{"kind":"Name","value":"responseFormat"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"modelConfiguration"}},{"kind":"Field","name":{"kind":"Name","value":"evaluationInputs"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApiKeyForRoleFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApiKeyForRole"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"expiresAt"}},{"kind":"Field","name":{"kind":"Name","value":"revokedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RolePermissionFlagFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RolePermissionFlag"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"flag"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ObjectPermissionFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ObjectPermission"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"canReadObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canUpdateObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canSoftDeleteObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canDestroyObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"restrictedFields"}},{"kind":"Field","name":{"kind":"Name","value":"rowLevelPermissionPredicates"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RowLevelPermissionPredicateFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"rowLevelPermissionPredicateGroups"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RowLevelPermissionPredicateGroupFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FieldPermissionFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FieldPermission"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"fieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"canReadFieldValue"}},{"kind":"Field","name":{"kind":"Name","value":"canUpdateFieldValue"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}}]}}]} as unknown as DocumentNode; export const CreateApprovedAccessDomainDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateApprovedAccessDomain"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateApprovedAccessDomainInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createApprovedAccessDomain"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"domain"}},{"kind":"Field","name":{"kind":"Name","value":"isValidated"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]} as unknown as DocumentNode; export const CreateOidcIdentityProviderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOIDCIdentityProvider"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SetupOIDCSsoInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOIDCIdentityProvider"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"issuer"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]} as unknown as DocumentNode; export const CreateSamlIdentityProviderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateSAMLIdentityProvider"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SetupSAMLSsoInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createSAMLIdentityProvider"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"issuer"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]} as unknown as DocumentNode; diff --git a/packages/twenty-front/src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts b/packages/twenty-front/src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts index 270086b0c3..b54566a012 100644 --- a/packages/twenty-front/src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts +++ b/packages/twenty-front/src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts @@ -30,6 +30,7 @@ export const useMetadataErrorHandler = () => { viewFilter: t`view filter`, index: t`index`, logicFunction: t`logic function`, + rolePermissionFlag: t`role permission flag`, permissionFlag: t`permission flag`, objectPermission: t`object permission`, fieldPermission: t`field permission`, diff --git a/packages/twenty-front/src/modules/settings/roles/graphql/fragments/permissionFlagFragment.ts b/packages/twenty-front/src/modules/settings/roles/graphql/fragments/permissionFlagFragment.ts deleted file mode 100644 index 58c1ef429a..0000000000 --- a/packages/twenty-front/src/modules/settings/roles/graphql/fragments/permissionFlagFragment.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { gql } from '@apollo/client'; - -export const PERMISSION_FLAG_FRAGMENT = gql` - fragment PermissionFlagFragment on PermissionFlag { - id - flag - roleId - } -`; diff --git a/packages/twenty-front/src/modules/settings/roles/graphql/fragments/rolePermissionFlagFragment.ts b/packages/twenty-front/src/modules/settings/roles/graphql/fragments/rolePermissionFlagFragment.ts new file mode 100644 index 0000000000..8e0839ae50 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/roles/graphql/fragments/rolePermissionFlagFragment.ts @@ -0,0 +1,9 @@ +import { gql } from '@apollo/client'; + +export const ROLE_PERMISSION_FLAG_FRAGMENT = gql` + fragment RolePermissionFlagFragment on RolePermissionFlag { + id + flag + roleId + } +`; diff --git a/packages/twenty-front/src/modules/settings/roles/graphql/mutations/upsertPermissionFlagsMutation.ts b/packages/twenty-front/src/modules/settings/roles/graphql/mutations/upsertPermissionFlagsMutation.ts index 2e4d1c4759..567b3e0f76 100644 --- a/packages/twenty-front/src/modules/settings/roles/graphql/mutations/upsertPermissionFlagsMutation.ts +++ b/packages/twenty-front/src/modules/settings/roles/graphql/mutations/upsertPermissionFlagsMutation.ts @@ -1,15 +1,15 @@ -import { PERMISSION_FLAG_FRAGMENT } from '@/settings/roles/graphql/fragments/permissionFlagFragment'; +import { ROLE_PERMISSION_FLAG_FRAGMENT } from '@/settings/roles/graphql/fragments/rolePermissionFlagFragment'; import { gql } from '@apollo/client'; export const UPSERT_PERMISSION_FLAGS = gql` - ${PERMISSION_FLAG_FRAGMENT} + ${ROLE_PERMISSION_FLAG_FRAGMENT} mutation UpsertPermissionFlags( $upsertPermissionFlagsInput: UpsertPermissionFlagsInput! ) { upsertPermissionFlags( upsertPermissionFlagsInput: $upsertPermissionFlagsInput ) { - ...PermissionFlagFragment + ...RolePermissionFlagFragment } } `; diff --git a/packages/twenty-front/src/modules/settings/roles/graphql/queries/getRolesQuery.ts b/packages/twenty-front/src/modules/settings/roles/graphql/queries/getRolesQuery.ts index 4fe85d8604..840c7f0ecf 100644 --- a/packages/twenty-front/src/modules/settings/roles/graphql/queries/getRolesQuery.ts +++ b/packages/twenty-front/src/modules/settings/roles/graphql/queries/getRolesQuery.ts @@ -2,7 +2,7 @@ import { AGENT_FRAGMENT } from '@/ai/graphql/fragments/agentFragment'; import { API_KEY_FOR_ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/apiKeyForRoleFragment'; import { FIELD_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/fieldPermissionFragment'; import { OBJECT_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/objectPermissionFragment'; -import { PERMISSION_FLAG_FRAGMENT } from '@/settings/roles/graphql/fragments/permissionFlagFragment'; +import { ROLE_PERMISSION_FLAG_FRAGMENT } from '@/settings/roles/graphql/fragments/rolePermissionFlagFragment'; import { ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/roleFragment'; import { ROW_LEVEL_PERMISSION_PREDICATE_FRAGMENT } from '@/settings/roles/graphql/fragments/rowLevelPermissionPredicateFragment'; import { ROW_LEVEL_PERMISSION_PREDICATE_GROUP_FRAGMENT } from '@/settings/roles/graphql/fragments/rowLevelPermissionPredicateGroupFragment'; @@ -14,7 +14,7 @@ export const GET_ROLES = gql` ${ROLE_FRAGMENT} ${AGENT_FRAGMENT} ${API_KEY_FOR_ROLE_FRAGMENT} - ${PERMISSION_FLAG_FRAGMENT} + ${ROLE_PERMISSION_FLAG_FRAGMENT} ${OBJECT_PERMISSION_FRAGMENT} ${FIELD_PERMISSION_FRAGMENT} ${ROW_LEVEL_PERMISSION_PREDICATE_FRAGMENT} @@ -32,7 +32,7 @@ export const GET_ROLES = gql` ...ApiKeyForRoleFragment } permissionFlags { - ...PermissionFlagFragment + ...RolePermissionFlagFragment } objectPermissions { ...ObjectPermissionFragment diff --git a/packages/twenty-front/src/modules/settings/roles/hooks/useHasPermissionFlag.ts b/packages/twenty-front/src/modules/settings/roles/hooks/useHasPermissionFlag.ts index 1debfaec71..a46fd877f5 100644 --- a/packages/twenty-front/src/modules/settings/roles/hooks/useHasPermissionFlag.ts +++ b/packages/twenty-front/src/modules/settings/roles/hooks/useHasPermissionFlag.ts @@ -6,16 +6,18 @@ import { import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -export const useHasPermissionFlag = (permissionFlag?: PermissionFlagType) => { +export const useHasPermissionFlag = ( + permissionFlagKey?: PermissionFlagType, +) => { const currentWorkspace = useAtomStateValue(currentWorkspaceState); const currentUserWorkspace = useAtomStateValue(currentUserWorkspaceState); - if (!permissionFlag) { + if (!permissionFlagKey) { return true; } if ( - permissionFlag === PermissionFlagType.WORKSPACE && + permissionFlagKey === PermissionFlagType.WORKSPACE && currentWorkspace?.activationStatus === WorkspaceActivationStatus.PENDING_CREATION ) { @@ -23,5 +25,5 @@ export const useHasPermissionFlag = (permissionFlag?: PermissionFlagType) => { } const userFlags = currentUserWorkspace?.permissionFlags ?? []; - return userFlags.includes(permissionFlag); + return userFlags.includes(permissionFlagKey); }; diff --git a/packages/twenty-front/src/pages/settings/applications/SettingsApplicationDetails.tsx b/packages/twenty-front/src/pages/settings/applications/SettingsApplicationDetails.tsx index 09991c6e44..8d4ce446ee 100644 --- a/packages/twenty-front/src/pages/settings/applications/SettingsApplicationDetails.tsx +++ b/packages/twenty-front/src/pages/settings/applications/SettingsApplicationDetails.tsx @@ -92,11 +92,6 @@ export const SettingsApplicationDetails = () => { const displayName = app?.displayName ?? application?.name ?? t`Application details`; const description = app?.description ?? resolvedDescription; - const logoUrl = - app?.logoUrl ?? - application?.logo ?? - application?.applicationRegistration?.logoUrl ?? - undefined; const getScreenshots = () => { if (app?.screenshots?.length) return app.screenshots; diff --git a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationPermissionsTab.tsx b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationPermissionsTab.tsx index 0338802bcc..048bbfa139 100644 --- a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationPermissionsTab.tsx +++ b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationPermissionsTab.tsx @@ -123,7 +123,7 @@ const buildSyntheticRole = ( })), permissionFlags: (defaultRole.permissionFlags ?? []).map( (permissionFlag) => ({ - __typename: 'PermissionFlag' as const, + __typename: 'RolePermissionFlag' as const, id: uuidv4(), roleId: defaultRole.universalIdentifier, flag: permissionFlag.flag, diff --git a/packages/twenty-server/src/database/commands/list-and-delete-orphaned-workspace-entities.command.ts b/packages/twenty-server/src/database/commands/list-and-delete-orphaned-workspace-entities.command.ts index 71260b481e..f37a7416c1 100644 --- a/packages/twenty-server/src/database/commands/list-and-delete-orphaned-workspace-entities.command.ts +++ b/packages/twenty-server/src/database/commands/list-and-delete-orphaned-workspace-entities.command.ts @@ -32,6 +32,7 @@ import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permi 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 { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity'; import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity'; import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; @@ -66,7 +67,7 @@ const WORKSPACE_RELATED_ENTITIES: EntityTarget[] = [ ViewFilterGroupEntity, FieldPermissionEntity, ObjectPermissionEntity, - PermissionFlagEntity, + RolePermissionFlagEntity, RoleTargetEntity, SearchFieldMetadataEntity, RowLevelPermissionPredicateEntity, @@ -81,6 +82,7 @@ const WORKSPACE_RELATED_ENTITIES: EntityTarget[] = [ // Level 2: Children that depend on core entities FieldMetadataEntity, PageLayoutEntity, + PermissionFlagEntity, SkillEntity, LogicFunctionEntity, diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1778235340020-rename-permission-flag-to-role-permission-flag.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1778235340020-rename-permission-flag-to-role-permission-flag.ts new file mode 100644 index 0000000000..132a07f910 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1778235340020-rename-permission-flag-to-role-permission-flag.ts @@ -0,0 +1,102 @@ +import { QueryRunner } from 'typeorm'; + +import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator'; +import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface'; + +@RegisteredInstanceCommand('2.6.0', 1778235340020) +export class RenamePermissionFlagToRolePermissionFlagFastInstanceCommand + implements FastInstanceCommand +{ + public async up(queryRunner: QueryRunner): Promise { + // The legacy permissionFlag table stores per-role grants (`roleId` + `flag`). + // Rename it to preserve those rows before creating the new permissionFlag catalog table. + await queryRunner.query( + `ALTER TABLE "core"."permissionFlag" RENAME TO "rolePermissionFlag"`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" RENAME CONSTRAINT "PK_a02789db60620a1e9f90147b50f" TO "PK_76591adc8035c2e7b0cd6115136"`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" RENAME CONSTRAINT "IDX_PERMISSION_FLAG_FLAG_ROLE_ID_UNIQUE" TO "IDX_ROLE_PERMISSION_FLAG_FLAG_ROLE_ID_UNIQUE"`, + ); + + await queryRunner.query( + `ALTER INDEX "core"."IDX_PERMISSION_FLAG_ROLE_ID" RENAME TO "IDX_ROLE_PERMISSION_FLAG_ROLE_ID"`, + ); + + // Re-hash inherited constraints/indexes so TypeORM's schema diff matches + // the renamed table. Original names were derived from "permissionFlag" + // and stay free for the new catalog table created by the next migration. + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" DROP CONSTRAINT "FK_13f8ca9c517976733a1ce4c10eb"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" DROP CONSTRAINT "FK_835bc9f7ef959debfc5cd268049"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" DROP CONSTRAINT "FK_b26a9d39a88d0e72373c677c6c5"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_da8ffd3c24b4a819430a861067"`, + ); + + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_e4559ae0dba56e53714137c704" ON "core"."rolePermissionFlag" ("workspaceId", "universalIdentifier")`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" ADD CONSTRAINT "FK_d47b1ebee75d98daa0c870c26e3" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" ADD CONSTRAINT "FK_3835ecc1019327566d35728c8ba" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" ADD CONSTRAINT "FK_4c6ea38698de230b0ec18fa2110" FOREIGN KEY ("roleId") REFERENCES "core"."role"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" DROP CONSTRAINT "FK_4c6ea38698de230b0ec18fa2110"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" DROP CONSTRAINT "FK_3835ecc1019327566d35728c8ba"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" DROP CONSTRAINT "FK_d47b1ebee75d98daa0c870c26e3"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_e4559ae0dba56e53714137c704"`, + ); + + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_da8ffd3c24b4a819430a861067" ON "core"."rolePermissionFlag" ("workspaceId", "universalIdentifier")`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" ADD CONSTRAINT "FK_b26a9d39a88d0e72373c677c6c5" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" ADD CONSTRAINT "FK_835bc9f7ef959debfc5cd268049" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" ADD CONSTRAINT "FK_13f8ca9c517976733a1ce4c10eb" FOREIGN KEY ("roleId") REFERENCES "core"."role"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + + await queryRunner.query( + `ALTER INDEX "core"."IDX_ROLE_PERMISSION_FLAG_ROLE_ID" RENAME TO "IDX_PERMISSION_FLAG_ROLE_ID"`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" RENAME CONSTRAINT "IDX_ROLE_PERMISSION_FLAG_FLAG_ROLE_ID_UNIQUE" TO "IDX_PERMISSION_FLAG_FLAG_ROLE_ID_UNIQUE"`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" RENAME CONSTRAINT "PK_76591adc8035c2e7b0cd6115136" TO "PK_a02789db60620a1e9f90147b50f"`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" RENAME TO "permissionFlag"`, + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1778235340021-permission-flag-syncable-entity.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1778235340021-permission-flag-syncable-entity.ts new file mode 100644 index 0000000000..9ea537b2ec --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1778235340021-permission-flag-syncable-entity.ts @@ -0,0 +1,69 @@ +import { QueryRunner } from 'typeorm'; + +import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator'; +import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface'; + +@RegisteredInstanceCommand('2.6.0', 1778235340021) +export class PermissionFlagSyncableEntityFastInstanceCommand + implements FastInstanceCommand +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "core"."permissionFlag" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "workspaceId" uuid NOT NULL, + "applicationId" uuid NOT NULL, + "universalIdentifier" uuid NOT NULL, + "key" varchar NOT NULL, + "label" varchar NOT NULL, + "description" text, + "icon" varchar, + "permissionType" varchar NOT NULL, + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + CONSTRAINT "IDX_PERMISSION_FLAG_KEY_WORKSPACE_ID_UNIQUE" UNIQUE ("key", "workspaceId"), + CONSTRAINT "PK_a02789db60620a1e9f90147b50f" PRIMARY KEY ("id") + )`, + ); + + await queryRunner.query( + `CREATE INDEX "IDX_PERMISSION_FLAG_APPLICATION_ID" ON "core"."permissionFlag" ("applicationId")`, + ); + + // Reuses canonical hash names freed by the rename migration so TypeORM's + // schema diff matches without further intervention. + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_da8ffd3c24b4a819430a861067" ON "core"."permissionFlag" ("workspaceId", "universalIdentifier")`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."permissionFlag" + ADD CONSTRAINT "FK_835bc9f7ef959debfc5cd268049" + FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") + ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."permissionFlag" + ADD CONSTRAINT "FK_b26a9d39a88d0e72373c677c6c5" + FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") + ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."permissionFlag" DROP CONSTRAINT IF EXISTS "FK_b26a9d39a88d0e72373c677c6c5"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."permissionFlag" DROP CONSTRAINT IF EXISTS "FK_835bc9f7ef959debfc5cd268049"`, + ); + await queryRunner.query( + `DROP INDEX IF EXISTS "core"."IDX_da8ffd3c24b4a819430a861067"`, + ); + await queryRunner.query( + `DROP INDEX IF EXISTS "core"."IDX_PERMISSION_FLAG_APPLICATION_ID"`, + ); + await queryRunner.query(`DROP TABLE IF EXISTS "core"."permissionFlag"`); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1778235340022-link-role-permission-flag-to-permission-flag.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1778235340022-link-role-permission-flag-to-permission-flag.ts new file mode 100644 index 0000000000..c71227e4f4 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1778235340022-link-role-permission-flag-to-permission-flag.ts @@ -0,0 +1,55 @@ +import { QueryRunner } from 'typeorm'; + +import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator'; +import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface'; + +@RegisteredInstanceCommand('2.6.0', 1778235340022) +export class LinkRolePermissionFlagToPermissionFlagFastInstanceCommand + implements FastInstanceCommand +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" + ADD COLUMN IF NOT EXISTS "permissionFlagId" uuid`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" + ADD CONSTRAINT "IDX_ROLE_PERMISSION_FLAG_PERMISSION_FLAG_ID_ROLE_ID_UNIQUE" + UNIQUE ("permissionFlagId", "roleId")`, + ); + + await queryRunner.query( + `CREATE INDEX "IDX_ROLE_PERMISSION_FLAG_PERMISSION_FLAG_ID" + ON "core"."rolePermissionFlag" ("permissionFlagId")`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" + ADD CONSTRAINT "FK_8724e63323f1331591a3e91b0b3" + FOREIGN KEY ("permissionFlagId") REFERENCES "core"."permissionFlag"("id") + ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" + DROP CONSTRAINT IF EXISTS "FK_8724e63323f1331591a3e91b0b3"`, + ); + + await queryRunner.query( + `DROP INDEX IF EXISTS "core"."IDX_ROLE_PERMISSION_FLAG_PERMISSION_FLAG_ID"`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" + DROP CONSTRAINT IF EXISTS "IDX_ROLE_PERMISSION_FLAG_PERMISSION_FLAG_ID_ROLE_ID_UNIQUE"`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."rolePermissionFlag" + DROP COLUMN IF EXISTS "permissionFlagId"`, + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-6/2-6-instance-command-slow-1778235340023-backfill-role-permission-flag-permission-flag-id.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-6/2-6-instance-command-slow-1778235340023-backfill-role-permission-flag-permission-flag-id.ts new file mode 100644 index 0000000000..e68e1728a9 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-6/2-6-instance-command-slow-1778235340023-backfill-role-permission-flag-permission-flag-id.ts @@ -0,0 +1,88 @@ +import { TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER } from 'twenty-shared/application'; +import { PermissionFlagType } from 'twenty-shared/constants'; +import { DataSource, QueryRunner } from 'typeorm'; + +import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator'; +import { SlowInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/slow-instance-command.interface'; +import { STANDARD_PERMISSION_FLAG_DEFINITIONS } from 'src/engine/metadata-modules/permission-flag/constants/standard-permission-flag-definitions.constant'; + +const PERMISSION_FLAG_TYPES = Object.values(PermissionFlagType) as string[]; + +@RegisteredInstanceCommand('2.6.0', 1778235340023, { type: 'slow' }) +export class BackfillRolePermissionFlagPermissionFlagIdSlowInstanceCommand + implements SlowInstanceCommand +{ + async runDataMigration(dataSource: DataSource): Promise { + const unknownFlagRows: { flag: string }[] = await dataSource.query( + `SELECT DISTINCT "flag" FROM "core"."rolePermissionFlag" + WHERE "flag" <> ALL($1::varchar[])`, + [PERMISSION_FLAG_TYPES], + ); + + if (unknownFlagRows.length > 0) { + const unknownFlags = unknownFlagRows.map((row) => row.flag).join(', '); + + throw new Error( + `Cannot migrate: rolePermissionFlag rows reference unknown flag value(s): ${unknownFlags}`, + ); + } + + for (const definition of STANDARD_PERMISSION_FLAG_DEFINITIONS) { + await dataSource.query( + `INSERT INTO "core"."permissionFlag" ( + "id", + "workspaceId", + "applicationId", + "universalIdentifier", + "key", + "label", + "description", + "icon", + "permissionType", + "createdAt", + "updatedAt" + ) + SELECT + uuid_generate_v4(), + workspace."id", + standardApplication."id", + $1::uuid, + $2, + $3, + $4, + $5, + $6, + now(), + now() + FROM "core"."workspace" workspace + INNER JOIN "core"."application" standardApplication + ON standardApplication."workspaceId" = workspace."id" + AND standardApplication."universalIdentifier" = $7 + AND standardApplication."deletedAt" IS NULL + ON CONFLICT ("key", "workspaceId") DO NOTHING`, + [ + definition.universalIdentifier, + definition.key, + definition.label, + definition.description, + definition.icon, + definition.permissionType, + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + ], + ); + } + + await dataSource.query( + `UPDATE "core"."rolePermissionFlag" rolePermissionFlag + SET "permissionFlagId" = permissionFlag."id" + FROM "core"."permissionFlag" permissionFlag + WHERE permissionFlag."workspaceId" = rolePermissionFlag."workspaceId" + AND permissionFlag."key" = rolePermissionFlag."flag" + AND rolePermissionFlag."permissionFlagId" IS NULL`, + ); + } + + public async up(_queryRunner: QueryRunner): Promise {} + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts index 2d8d0aa81c..86823ad489 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts @@ -31,6 +31,10 @@ import { AddToolAndWorkflowActionTriggerSettingsFastInstanceCommand } from 'src/ import { BackfillApplicationVariableUniversalIdentifierSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-slow-1777966965588-backfill-application-variable-universal-identifier'; import { MigrateToolTriggerSettingsSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-slow-1797000002000-migrate-tool-trigger-settings'; import { AddMetadataToBillingPriceFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1777100000000-add-metadata-to-billing-price'; +import { RenamePermissionFlagToRolePermissionFlagFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1778235340020-rename-permission-flag-to-role-permission-flag'; +import { PermissionFlagSyncableEntityFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1778235340021-permission-flag-syncable-entity'; +import { LinkRolePermissionFlagToPermissionFlagFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1778235340022-link-role-permission-flag-to-permission-flag'; +import { BackfillRolePermissionFlagPermissionFlagIdSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-slow-1778235340023-backfill-role-permission-flag-permission-flag-id'; import { AddEmailGroupChannelTypeFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1778256809018-add-email-group-channel-type'; import { AddApplicationIdToPublicDomainFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1798000003000-add-application-id-to-public-domain'; import { AddIsInternalMessagesImportEnabledFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1778525104406-add-is-internal-messages-import-enabled'; @@ -77,6 +81,10 @@ export const INSTANCE_COMMANDS = [ RemoveUserDefaultAvatarUrlFastInstanceCommand, TransformApplicationVariableToSyncableEntityFastInstanceCommand, BackfillApplicationVariableUniversalIdentifierSlowInstanceCommand, + RenamePermissionFlagToRolePermissionFlagFastInstanceCommand, + PermissionFlagSyncableEntityFastInstanceCommand, + LinkRolePermissionFlagToPermissionFlagFastInstanceCommand, + BackfillRolePermissionFlagPermissionFlagIdSlowInstanceCommand, AddEmailGroupChannelTypeFastInstanceCommand, AddApplicationIdToPublicDomainFastInstanceCommand, AddIsInternalMessagesImportEnabledFastInstanceCommand, diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-permission-flag-to-universal-flat-permission-flag.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-permission-flag-to-universal-flat-permission-flag.util.ts deleted file mode 100644 index e8a8e65c70..0000000000 --- a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-permission-flag-to-universal-flat-permission-flag.util.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { type UniversalFlatPermissionFlag } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-permission-flag.type'; -import { PermissionFlagManifest } from 'twenty-shared/application'; - -export const fromPermissionFlagToUniversalFlatPermissionFlag = ({ - permissionFlag, - roleUniversalIdentifier, - applicationUniversalIdentifier, - now, -}: { - permissionFlag: PermissionFlagManifest; - roleUniversalIdentifier: string; - applicationUniversalIdentifier: string; - now: string; -}): UniversalFlatPermissionFlag => { - return { - universalIdentifier: permissionFlag.universalIdentifier, - applicationUniversalIdentifier, - roleUniversalIdentifier, - flag: permissionFlag.flag, - createdAt: now, - updatedAt: now, - }; -}; diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-permission-flag-to-universal-flat-role-permission-flag.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-permission-flag-to-universal-flat-role-permission-flag.util.ts new file mode 100644 index 0000000000..82bb8ecc55 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-permission-flag-to-universal-flat-role-permission-flag.util.ts @@ -0,0 +1,27 @@ +import { type PermissionFlagManifest } from 'twenty-shared/application'; +import { SystemPermissionFlag } from 'twenty-shared/constants'; + +import { type UniversalFlatRolePermissionFlag } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-role-permission-flag.type'; + +export const fromPermissionFlagToUniversalFlatRolePermissionFlag = ({ + permissionFlag, + roleUniversalIdentifier, + applicationUniversalIdentifier, + now, +}: { + permissionFlag: PermissionFlagManifest; + roleUniversalIdentifier: string; + applicationUniversalIdentifier: string; + now: string; +}): UniversalFlatRolePermissionFlag => { + return { + universalIdentifier: permissionFlag.universalIdentifier, + applicationUniversalIdentifier, + roleUniversalIdentifier, + permissionFlagUniversalIdentifier: + SystemPermissionFlag[permissionFlag.flag], + flag: permissionFlag.flag, + createdAt: now, + updatedAt: now, + }; +}; diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-role-manifest-to-universal-flat-role.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-role-manifest-to-universal-flat-role.util.ts index acffe6b146..1cded19c3a 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-role-manifest-to-universal-flat-role.util.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-role-manifest-to-universal-flat-role.util.ts @@ -32,7 +32,7 @@ export const fromRoleManifestToUniversalFlatRole = ({ roleTargetUniversalIdentifiers: [], objectPermissionUniversalIdentifiers: [], fieldPermissionUniversalIdentifiers: [], - permissionFlagUniversalIdentifiers: [], + rolePermissionFlagUniversalIdentifiers: [], rowLevelPermissionPredicateUniversalIdentifiers: [], rowLevelPermissionPredicateGroupUniversalIdentifiers: [], createdAt: now, diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/utils/compute-application-manifest-all-universal-flat-entity-maps.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/utils/compute-application-manifest-all-universal-flat-entity-maps.util.ts index 0605c5de27..3251deba80 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-manifest/utils/compute-application-manifest-all-universal-flat-entity-maps.util.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/utils/compute-application-manifest-all-universal-flat-entity-maps.util.ts @@ -17,7 +17,7 @@ import { fromObjectPermissionManifestToUniversalFlatObjectPermission } from 'src import { fromPageLayoutManifestToUniversalFlatPageLayout } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-manifest-to-universal-flat-page-layout.util'; import { fromPageLayoutTabManifestToUniversalFlatPageLayoutTab } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-tab-manifest-to-universal-flat-page-layout-tab.util'; import { fromPageLayoutWidgetManifestToUniversalFlatPageLayoutWidget } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-widget-manifest-to-universal-flat-page-layout-widget.util'; -import { fromPermissionFlagToUniversalFlatPermissionFlag } from 'src/engine/core-modules/application/application-manifest/converters/from-permission-flag-to-universal-flat-permission-flag.util'; +import { fromPermissionFlagToUniversalFlatRolePermissionFlag } from 'src/engine/core-modules/application/application-manifest/converters/from-permission-flag-to-universal-flat-role-permission-flag.util'; import { fromRoleManifestToUniversalFlatRole } from 'src/engine/core-modules/application/application-manifest/converters/from-role-manifest-to-universal-flat-role.util'; import { fromSkillManifestToUniversalFlatSkill } from 'src/engine/core-modules/application/application-manifest/converters/from-skill-manifest-to-universal-flat-skill.util'; import { computeSearchVectorUniversalSettingsFromObjectManifest } from 'src/engine/core-modules/application/application-manifest/utils/compute-search-vector-universal-settings-from-object-manifest.util'; @@ -214,14 +214,15 @@ export const computeApplicationManifestAllUniversalFlatEntityMaps = ({ for (const permissionFlag of roleManifest.permissionFlags ?? []) { addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({ - universalFlatEntity: fromPermissionFlagToUniversalFlatPermissionFlag({ - permissionFlag, - roleUniversalIdentifier: roleManifest.universalIdentifier, - applicationUniversalIdentifier, - now, - }), + universalFlatEntity: + fromPermissionFlagToUniversalFlatRolePermissionFlag({ + permissionFlag, + roleUniversalIdentifier: roleManifest.universalIdentifier, + applicationUniversalIdentifier, + now, + }), universalFlatEntityMapsToMutate: - allUniversalFlatEntityMaps.flatPermissionFlagMaps, + allUniversalFlatEntityMaps.flatRolePermissionFlagMaps, }); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap index 86838b1719..97c7ac2993 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap @@ -229,8 +229,11 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma }, "permissionFlag": { "propertiesToCompare": [ - "flag", - "roleUniversalIdentifier", + "key", + "label", + "description", + "icon", + "permissionType", ], "propertiesToStringify": [], }, @@ -251,6 +254,14 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma ], "propertiesToStringify": [], }, + "rolePermissionFlag": { + "propertiesToCompare": [ + "permissionFlagUniversalIdentifier", + "roleUniversalIdentifier", + "flag", + ], + "propertiesToStringify": [], + }, "roleTarget": { "propertiesToCompare": [ "roleUniversalIdentifier", diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts index 2b9453dabb..c960f5fb20 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts @@ -1186,17 +1186,59 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = { universalProperty: 'pageLayoutUniversalIdentifier', }, }, - permissionFlag: { - flag: { + rolePermissionFlag: { + permissionFlagId: { toCompare: true, toStringify: false, - universalProperty: undefined, + universalProperty: 'permissionFlagUniversalIdentifier', }, roleId: { toCompare: true, toStringify: false, universalProperty: 'roleUniversalIdentifier', }, + flag: { + toCompare: true, + toStringify: false, + universalProperty: undefined, + }, + createdAt: { + toCompare: false, + toStringify: false, + universalProperty: undefined, + }, + updatedAt: { + toCompare: false, + toStringify: false, + universalProperty: undefined, + }, + }, + permissionFlag: { + key: { + toCompare: true, + toStringify: false, + universalProperty: undefined, + }, + label: { + toCompare: true, + toStringify: false, + universalProperty: undefined, + }, + description: { + toCompare: true, + toStringify: false, + universalProperty: undefined, + }, + icon: { + toCompare: true, + toStringify: false, + universalProperty: undefined, + }, + permissionType: { + toCompare: true, + toStringify: false, + universalProperty: undefined, + }, createdAt: { toCompare: false, toStringify: false, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-foreign-key.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-foreign-key.constant.ts index a36dd4ccb5..8ab905a195 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-foreign-key.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-foreign-key.constant.ts @@ -171,12 +171,19 @@ export const ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY = { workspace: null, application: null, }, - permissionFlag: { + rolePermissionFlag: { workspace: null, application: null, role: { foreignKey: 'roleId', }, + permissionFlag: { + foreignKey: 'permissionFlagId', + }, + }, + permissionFlag: { + workspace: null, + application: null, }, objectPermission: { workspace: null, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-relations.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-relations.constant.ts index 2603b0f0cb..96d02a00c4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-relations.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-relations.constant.ts @@ -300,16 +300,27 @@ export const ALL_MANY_TO_ONE_METADATA_RELATIONS = { workspace: null, application: null, }, - permissionFlag: { + rolePermissionFlag: { workspace: null, application: null, role: { metadataName: 'role', foreignKey: 'roleId', - inverseOneToManyProperty: 'permissionFlags', + inverseOneToManyProperty: 'rolePermissionFlags', isNullable: false, universalForeignKey: 'roleUniversalIdentifier', }, + permissionFlag: { + metadataName: 'permissionFlag', + foreignKey: 'permissionFlagId', + inverseOneToManyProperty: 'rolePermissionFlags', + isNullable: true, + universalForeignKey: 'permissionFlagUniversalIdentifier', + }, + }, + permissionFlag: { + workspace: null, + application: null, }, objectPermission: { workspace: null, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-entity-by-metadata-name.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-entity-by-metadata-name.constant.ts index 7835a1cc46..c2d923418e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-entity-by-metadata-name.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-entity-by-metadata-name.constant.ts @@ -17,6 +17,7 @@ import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab 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 { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity'; +import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity'; import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.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'; @@ -56,6 +57,7 @@ export const ALL_METADATA_ENTITY_BY_METADATA_NAME = { agent: AgentEntity, commandMenuItem: CommandMenuItemEntity, navigationMenuItem: NavigationMenuItemEntity, + rolePermissionFlag: RolePermissionFlagEntity, permissionFlag: PermissionFlagEntity, webhook: WebhookEntity, applicationVariable: ApplicationVariableEntity, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant.ts index da2f8ca4f8..91b157d034 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant.ts @@ -76,9 +76,13 @@ export const ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION = { view: true, pageLayout: true, }, - permissionFlag: { + rolePermissionFlag: { + permissionFlag: true, role: true, }, + permissionFlag: { + rolePermissionFlag: true, + }, objectPermission: { role: true, objectMetadata: true, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-serialized-relation.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-serialized-relation.constant.ts index 2e3a6cf01e..a73920283a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-serialized-relation.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-serialized-relation.constant.ts @@ -33,6 +33,7 @@ export const ALL_METADATA_SERIALIZED_RELATION = { logicFunction: {}, role: {}, roleTarget: {}, + rolePermissionFlag: {}, permissionFlag: {}, objectPermission: {}, fieldPermission: {}, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-one-to-many-metadata-relations.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-one-to-many-metadata-relations.constant.ts index 624643f5d2..feae37154c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-one-to-many-metadata-relations.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-one-to-many-metadata-relations.constant.ts @@ -168,11 +168,11 @@ export const ALL_ONE_TO_MANY_METADATA_RELATIONS = { universalFlatEntityForeignKeyAggregator: 'objectPermissionUniversalIdentifiers', }, - permissionFlags: { - metadataName: 'permissionFlag', - flatEntityForeignKeyAggregator: 'permissionFlagIds', + rolePermissionFlags: { + metadataName: 'rolePermissionFlag', + flatEntityForeignKeyAggregator: 'rolePermissionFlagIds', universalFlatEntityForeignKeyAggregator: - 'permissionFlagUniversalIdentifiers', + 'rolePermissionFlagUniversalIdentifiers', }, fieldPermissions: { metadataName: 'fieldPermission', @@ -194,7 +194,15 @@ export const ALL_ONE_TO_MANY_METADATA_RELATIONS = { }, }, roleTarget: {}, - permissionFlag: {}, + rolePermissionFlag: {}, + permissionFlag: { + rolePermissionFlags: { + metadataName: 'rolePermissionFlag', + flatEntityForeignKeyAggregator: 'rolePermissionFlagIds', + universalFlatEntityForeignKeyAggregator: + 'rolePermissionFlagUniversalIdentifiers', + }, + }, objectPermission: {}, fieldPermission: {}, pageLayout: { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module.ts index 022f427a3c..312c141009 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module.ts @@ -13,6 +13,7 @@ import { WorkspaceFlatPageLayoutTabMapCacheService } from 'src/engine/metadata-m 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 { WorkspaceFlatPermissionFlagMapCacheService } from 'src/engine/metadata-modules/flat-permission-flag/services/workspace-flat-permission-flag-map-cache.service'; +import { WorkspaceFlatRolePermissionFlagMapCacheService } from 'src/engine/metadata-modules/flat-role-permission-flag/services/workspace-flat-role-permission-flag-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 { WorkspaceFlatViewFieldGroupMapCacheService } from 'src/engine/metadata-modules/flat-view-field-group/services/workspace-flat-view-field-group-map-cache.service'; @@ -32,6 +33,7 @@ import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab 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 { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity'; +import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.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 { RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity'; @@ -64,6 +66,7 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache PageLayoutEntity, PageLayoutTabEntity, PageLayoutWidgetEntity, + RolePermissionFlagEntity, PermissionFlagEntity, RowLevelPermissionPredicateEntity, RowLevelPermissionPredicateGroupEntity, @@ -85,6 +88,7 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache WorkspaceFlatViewGroupMapCacheService, WorkspaceFlatObjectPermissionMapCacheService, WorkspaceFlatFieldPermissionMapCacheService, + WorkspaceFlatRolePermissionFlagMapCacheService, WorkspaceFlatPermissionFlagMapCacheService, WorkspaceFlatViewSortMapCacheService, WorkspaceFlatPageLayoutMapCacheService, @@ -106,6 +110,7 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache WorkspaceFlatViewGroupMapCacheService, WorkspaceFlatObjectPermissionMapCacheService, WorkspaceFlatFieldPermissionMapCacheService, + WorkspaceFlatRolePermissionFlagMapCacheService, WorkspaceFlatPermissionFlagMapCacheService, WorkspaceFlatViewSortMapCacheService, WorkspaceFlatPageLayoutMapCacheService, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name.ts index 28a598f3f7..215f6bebf2 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name.ts @@ -26,6 +26,8 @@ import { type FlatPageLayoutMaps } from 'src/engine/metadata-modules/flat-page-l import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type'; import { type FlatPermissionFlagMaps } from 'src/engine/metadata-modules/flat-permission-flag/types/flat-permission-flag-maps.type'; import { type FlatPermissionFlag } from 'src/engine/metadata-modules/flat-permission-flag/types/flat-permission-flag.type'; +import { type FlatRolePermissionFlagMaps } from 'src/engine/metadata-modules/flat-role-permission-flag/types/flat-role-permission-flag-maps.type'; +import { type FlatRolePermissionFlag } from 'src/engine/metadata-modules/flat-role-permission-flag/types/flat-role-permission-flag.type'; import { type FlatRoleTargetMaps } from 'src/engine/metadata-modules/flat-role-target/types/flat-role-target-maps.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'; @@ -70,6 +72,7 @@ import { type UniversalFlatPageLayoutTab } from 'src/engine/workspace-manager/wo import { type UniversalFlatPageLayoutWidget } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-page-layout-widget.type'; import { type UniversalFlatPageLayout } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-page-layout.type'; import { type UniversalFlatPermissionFlag } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-permission-flag.type'; +import { type UniversalFlatRolePermissionFlag } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-role-permission-flag.type'; import { type UniversalFlatRoleTarget } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-role-target.type'; import { type UniversalFlatRole } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-role.type'; import { type UniversalFlatRowLevelPermissionPredicateGroup } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-row-level-permission-predicate-group.type'; @@ -204,6 +207,14 @@ import { type UniversalDeletePermissionFlagAction, type UniversalUpdatePermissionFlagAction, } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/permission-flag/types/workspace-migration-permission-flag-action.type'; +import { + type FlatCreateRolePermissionFlagAction, + type FlatDeleteRolePermissionFlagAction, + type FlatUpdateRolePermissionFlagAction, + type UniversalCreateRolePermissionFlagAction, + type UniversalDeleteRolePermissionFlagAction, + type UniversalUpdateRolePermissionFlagAction, +} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-permission-flag/types/workspace-migration-role-permission-flag-action.type'; import { type FlatCreateRoleTargetAction, type FlatDeleteRoleTargetAction, @@ -625,6 +636,22 @@ export type AllFlatEntityTypesByMetadataName = { universalFlatEntity: UniversalFlatNavigationMenuItem; entity: NavigationMenuItemEntity; }; + rolePermissionFlag: { + flatEntityMaps: FlatRolePermissionFlagMaps; + universalActions: { + create: UniversalCreateRolePermissionFlagAction; + update: UniversalUpdateRolePermissionFlagAction; + delete: UniversalDeleteRolePermissionFlagAction; + }; + flatActions: { + create: FlatCreateRolePermissionFlagAction; + update: FlatUpdateRolePermissionFlagAction; + delete: FlatDeleteRolePermissionFlagAction; + }; + flatEntity: FlatRolePermissionFlag; + universalFlatEntity: UniversalFlatRolePermissionFlag; + entity: MetadataEntity<'rolePermissionFlag'>; + }; permissionFlag: { flatEntityMaps: FlatPermissionFlagMaps; universalActions: { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/__snapshots__/get-metadata-related-metadata-names.util.spec.ts.snap b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/__snapshots__/get-metadata-related-metadata-names.util.spec.ts.snap index 8e333567e6..5e23a00291 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/__snapshots__/get-metadata-related-metadata-names.util.spec.ts.snap +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/__snapshots__/get-metadata-related-metadata-names.util.spec.ts.snap @@ -93,7 +93,7 @@ exports[`getMetadataRelatedMetadataNames should return related metadata names fo exports[`getMetadataRelatedMetadataNames should return related metadata names for permissionFlag 1`] = ` [ - "role", + "rolePermissionFlag", ] `; @@ -101,13 +101,20 @@ exports[`getMetadataRelatedMetadataNames should return related metadata names fo [ "roleTarget", "objectPermission", - "permissionFlag", + "rolePermissionFlag", "fieldPermission", "rowLevelPermissionPredicate", "rowLevelPermissionPredicateGroup", ] `; +exports[`getMetadataRelatedMetadataNames should return related metadata names for rolePermissionFlag 1`] = ` +[ + "role", + "permissionFlag", +] +`; + exports[`getMetadataRelatedMetadataNames should return related metadata names for roleTarget 1`] = ` [ "role", diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/__snapshots__/sort-metadata-names-children-first.util.spec.ts.snap b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/__snapshots__/sort-metadata-names-children-first.util.spec.ts.snap index 180635b0d9..b767c8a3f3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/__snapshots__/sort-metadata-names-children-first.util.spec.ts.snap +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/__snapshots__/sort-metadata-names-children-first.util.spec.ts.snap @@ -10,10 +10,10 @@ exports[`sortMetadataNamesChildrenFirst should return metadata names sorted with "viewField", "objectPermission", "pageLayoutWidget", + "rolePermissionFlag", "viewSort", "index", "pageLayout", - "permissionFlag", "roleTarget", "rowLevelPermissionPredicateGroup", "viewGroup", @@ -27,6 +27,7 @@ exports[`sortMetadataNamesChildrenFirst should return metadata names sorted with "viewFieldGroup", "viewFilterGroup", "webhook", + "permissionFlag", "view", "fieldMetadata", "objectMetadata", diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/constants/flat-permission-flag-editable-properties.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/constants/flat-permission-flag-editable-properties.constant.ts index 35775f4b21..21f887b965 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/constants/flat-permission-flag-editable-properties.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/constants/flat-permission-flag-editable-properties.constant.ts @@ -1,6 +1,8 @@ import { type MetadataEntityPropertyName } from 'src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant'; export const FLAT_PERMISSION_FLAG_EDITABLE_PROPERTIES = [ - 'flag', - 'roleId', + 'label', + 'description', + 'icon', + 'permissionType', ] as const satisfies MetadataEntityPropertyName<'permissionFlag'>[]; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/flat-permission-flag.module.ts b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/flat-permission-flag.module.ts new file mode 100644 index 0000000000..ae24c38f0b --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/flat-permission-flag.module.ts @@ -0,0 +1,22 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; +import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; +import { WorkspaceFlatPermissionFlagMapCacheService } from 'src/engine/metadata-modules/flat-permission-flag/services/workspace-flat-permission-flag-map-cache.service'; +import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity'; +import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ + ApplicationEntity, + PermissionFlagEntity, + RolePermissionFlagEntity, + ]), + WorkspaceManyOrAllFlatEntityMapsCacheModule, + ], + providers: [WorkspaceFlatPermissionFlagMapCacheService], + exports: [WorkspaceFlatPermissionFlagMapCacheService], +}) +export class FlatPermissionFlagModule {} diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/services/workspace-flat-permission-flag-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/services/workspace-flat-permission-flag-map-cache.service.ts index 8b890cf336..e7fcbf9bdf 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/services/workspace-flat-permission-flag-map-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/services/workspace-flat-permission-flag-map-cache.service.ts @@ -8,10 +8,11 @@ import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-enti import { type FlatPermissionFlagMaps } from 'src/engine/metadata-modules/flat-permission-flag/types/flat-permission-flag-maps.type'; import { fromPermissionFlagEntityToFlatPermissionFlag } from 'src/engine/metadata-modules/flat-permission-flag/utils/from-permission-flag-entity-to-flat-permission-flag.util'; import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity'; -import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; -import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service'; +import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity'; import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator'; +import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service'; import { createIdToUniversalIdentifierMap } from 'src/engine/workspace-cache/utils/create-id-to-universal-identifier-map.util'; +import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id'; import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util'; @Injectable() @@ -22,46 +23,52 @@ export class WorkspaceFlatPermissionFlagMapCacheService extends WorkspaceCachePr private readonly permissionFlagRepository: Repository, @InjectRepository(ApplicationEntity) private readonly applicationRepository: Repository, - @InjectRepository(RoleEntity) - private readonly roleRepository: Repository, + @InjectRepository(RolePermissionFlagEntity) + private readonly rolePermissionFlagRepository: Repository, ) { super(); } async computeForCache(workspaceId: string): Promise { - const [permissionFlags, applications, roles] = await Promise.all([ - this.permissionFlagRepository.find({ - where: { workspaceId }, - withDeleted: true, - }), - this.applicationRepository.find({ - where: { workspaceId }, - select: ['id', 'universalIdentifier'], - withDeleted: true, - }), - this.roleRepository.find({ - where: { workspaceId }, - select: ['id', 'universalIdentifier'], - withDeleted: true, - }), - ]); + const [permissionFlags, applications, rolePermissionFlags] = + await Promise.all([ + this.permissionFlagRepository.find({ + where: { workspaceId }, + withDeleted: true, + }), + this.applicationRepository.find({ + where: { workspaceId }, + select: ['id', 'universalIdentifier'], + withDeleted: true, + }), + this.rolePermissionFlagRepository.find({ + where: { workspaceId }, + withDeleted: true, + }), + ]); const applicationIdToUniversalIdentifierMap = createIdToUniversalIdentifierMap(applications); - const roleIdToUniversalIdentifierMap = - createIdToUniversalIdentifierMap(roles); + const rolePermissionFlagsByPermissionFlagId = + regroupEntitiesByRelatedEntityId<'rolePermissionFlag'>({ + entities: rolePermissionFlags, + foreignKey: 'permissionFlagId', + }); const flatPermissionFlagMaps = createEmptyFlatEntityMaps(); - for (const permissionFlagEntity of permissionFlags) { - const flatPermissionFlag = fromPermissionFlagEntityToFlatPermissionFlag({ - entity: permissionFlagEntity, + for (const definition of permissionFlags) { + const flatDefinition = fromPermissionFlagEntityToFlatPermissionFlag({ + entity: { + ...definition, + rolePermissionFlags: + rolePermissionFlagsByPermissionFlagId.get(definition.id) ?? [], + }, applicationIdToUniversalIdentifierMap, - roleIdToUniversalIdentifierMap, }); addFlatEntityToFlatEntityMapsThroughMutationOrThrow({ - flatEntity: flatPermissionFlag, + flatEntity: flatDefinition, flatEntityMapsToMutate: flatPermissionFlagMaps, }); } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/from-create-permission-flag-input-to-flat-permission-flag-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/from-create-permission-flag-input-to-flat-permission-flag-to-create.util.ts index 8e423abbf5..af89e6a74d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/from-create-permission-flag-input-to-flat-permission-flag-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/from-create-permission-flag-input-to-flat-permission-flag-to-create.util.ts @@ -1,38 +1,44 @@ +import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils'; import { v4 } from 'uuid'; import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; -import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; -import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; +import { type FlatPermissionFlag } from 'src/engine/metadata-modules/flat-permission-flag/types/flat-permission-flag.type'; import { type CreatePermissionFlagInput } from 'src/engine/metadata-modules/permission-flag/dtos/create-permission-flag.input'; -import { type UniversalFlatPermissionFlag } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-permission-flag.type'; export const fromCreatePermissionFlagInputToFlatPermissionFlagToCreate = ({ createPermissionFlagInput, + workspaceId, flatApplication, - flatRoleMaps, }: { createPermissionFlagInput: CreatePermissionFlagInput; + workspaceId: string; flatApplication: FlatApplication; -} & Pick): UniversalFlatPermissionFlag & { - id: string; -} => { - const { roleId, flag, universalIdentifier } = createPermissionFlagInput; +}): FlatPermissionFlag => { const now = new Date().toISOString(); - const { roleUniversalIdentifier } = resolveEntityRelationUniversalIdentifiers( - { - metadataName: 'permissionFlag', - foreignKeyValues: { roleId }, - flatEntityMaps: { flatRoleMaps }, - }, - ); + const { key, label, description, icon } = + trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties( + createPermissionFlagInput, + ['key', 'label', 'description', 'icon'], + ); + + const id = createPermissionFlagInput.id ?? v4(); + const universalIdentifier = + createPermissionFlagInput.universalIdentifier ?? id; return { - id: v4(), - flag, - universalIdentifier: universalIdentifier ?? v4(), + id, + universalIdentifier, + key, + label, + description: description ?? null, + icon: icon ?? null, + permissionType: createPermissionFlagInput.permissionType, + workspaceId, + applicationId: flatApplication.id, applicationUniversalIdentifier: flatApplication.universalIdentifier, - roleUniversalIdentifier, + rolePermissionFlagIds: [], + rolePermissionFlagUniversalIdentifiers: [], createdAt: now, updatedAt: now, }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/from-delete-permission-flag-input-to-flat-permission-flag-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/from-delete-permission-flag-input-to-flat-permission-flag-or-throw.util.ts new file mode 100644 index 0000000000..5e26ba83f0 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/from-delete-permission-flag-input-to-flat-permission-flag-or-throw.util.ts @@ -0,0 +1,31 @@ +import { isDefined } from 'twenty-shared/utils'; + +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; +import { type FlatPermissionFlag } from 'src/engine/metadata-modules/flat-permission-flag/types/flat-permission-flag.type'; +import { + PermissionFlagException, + PermissionFlagExceptionCode, +} from 'src/engine/metadata-modules/permission-flag/permission-flag.exception'; + +export const fromDeletePermissionFlagInputToFlatPermissionFlagOrThrow = ({ + flatPermissionFlagMaps, + permissionFlagId, +}: { + flatPermissionFlagMaps: FlatEntityMaps; + permissionFlagId: string; +}): FlatPermissionFlag => { + const existing = findFlatEntityByIdInFlatEntityMaps({ + flatEntityId: permissionFlagId, + flatEntityMaps: flatPermissionFlagMaps, + }); + + if (!isDefined(existing)) { + throw new PermissionFlagException( + 'Permission flag not found', + PermissionFlagExceptionCode.PERMISSION_FLAG_NOT_FOUND, + ); + } + + return existing; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/from-flat-permission-flag-to-permission-flag-dto.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/from-flat-permission-flag-to-permission-flag-dto.util.ts new file mode 100644 index 0000000000..bd9e678448 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/from-flat-permission-flag-to-permission-flag-dto.util.ts @@ -0,0 +1,18 @@ +import { type FlatPermissionFlag } from 'src/engine/metadata-modules/flat-permission-flag/types/flat-permission-flag.type'; +import { type PermissionFlagDTO } from 'src/engine/metadata-modules/permission-flag/dtos/permission-flag.dto'; + +export const fromFlatPermissionFlagToPermissionFlagDto = ( + flatDefinition: FlatPermissionFlag, +): PermissionFlagDTO => ({ + id: flatDefinition.id, + universalIdentifier: flatDefinition.universalIdentifier, + key: flatDefinition.key, + label: flatDefinition.label, + description: flatDefinition.description, + icon: flatDefinition.icon, + permissionType: flatDefinition.permissionType, + workspaceId: flatDefinition.workspaceId, + applicationId: flatDefinition.applicationId, + createdAt: new Date(flatDefinition.createdAt), + updatedAt: new Date(flatDefinition.updatedAt), +}); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/from-permission-flag-entity-to-flat-permission-flag.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/from-permission-flag-entity-to-flat-permission-flag.util.ts index 8c7a0d079c..6df8ffe6d6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/from-permission-flag-entity-to-flat-permission-flag.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/from-permission-flag-entity-to-flat-permission-flag.util.ts @@ -11,9 +11,8 @@ import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/type export const fromPermissionFlagEntityToFlatPermissionFlag = ({ entity: permissionFlagEntity, applicationIdToUniversalIdentifierMap, - roleIdToUniversalIdentifierMap, }: FromEntityToFlatEntityArgs<'permissionFlag'>): FlatPermissionFlag => { - const permissionFlagEntityWithoutRelations = removePropertiesFromRecord( + const entityWithoutRelations = removePropertiesFromRecord( permissionFlagEntity, getMetadataEntityRelationProperties('permissionFlag'), ); @@ -30,24 +29,18 @@ export const fromPermissionFlagEntityToFlatPermissionFlag = ({ ); } - const roleUniversalIdentifier = roleIdToUniversalIdentifierMap.get( - permissionFlagEntity.roleId, - ); - - if (!isDefined(roleUniversalIdentifier)) { - throw new FlatEntityMapsException( - `Role with id ${permissionFlagEntity.roleId} not found for permissionFlag ${permissionFlagEntity.id}`, - FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND, - ); - } - return { - ...permissionFlagEntityWithoutRelations, + ...entityWithoutRelations, createdAt: permissionFlagEntity.createdAt.toISOString(), updatedAt: permissionFlagEntity.updatedAt.toISOString(), - universalIdentifier: - permissionFlagEntityWithoutRelations.universalIdentifier, + universalIdentifier: entityWithoutRelations.universalIdentifier, applicationUniversalIdentifier, - roleUniversalIdentifier, + rolePermissionFlagIds: permissionFlagEntity.rolePermissionFlags.map( + ({ id }) => id, + ), + rolePermissionFlagUniversalIdentifiers: + permissionFlagEntity.rolePermissionFlags.map( + ({ universalIdentifier }) => universalIdentifier, + ), }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/from-update-permission-flag-input-to-flat-permission-flag-to-update-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/from-update-permission-flag-input-to-flat-permission-flag-to-update-or-throw.util.ts new file mode 100644 index 0000000000..5fd1cd098a --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/from-update-permission-flag-input-to-flat-permission-flag-to-update-or-throw.util.ts @@ -0,0 +1,42 @@ +import { isDefined } from 'twenty-shared/utils'; + +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; +import { FLAT_PERMISSION_FLAG_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-permission-flag/constants/flat-permission-flag-editable-properties.constant'; +import { type FlatPermissionFlag } from 'src/engine/metadata-modules/flat-permission-flag/types/flat-permission-flag.type'; +import { + PermissionFlagException, + PermissionFlagExceptionCode, +} from 'src/engine/metadata-modules/permission-flag/permission-flag.exception'; +import { type UpdatePermissionFlagInput } from 'src/engine/metadata-modules/permission-flag/dtos/update-permission-flag.input'; +import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util'; + +export const fromUpdatePermissionFlagInputToFlatPermissionFlagToUpdateOrThrow = + ({ + flatPermissionFlagMaps, + updatePermissionFlagInput, + }: { + flatPermissionFlagMaps: FlatEntityMaps; + updatePermissionFlagInput: UpdatePermissionFlagInput; + }): FlatPermissionFlag => { + const existing = findFlatEntityByIdInFlatEntityMaps({ + flatEntityId: updatePermissionFlagInput.id, + flatEntityMaps: flatPermissionFlagMaps, + }); + + if (!isDefined(existing)) { + throw new PermissionFlagException( + 'Permission flag not found', + PermissionFlagExceptionCode.PERMISSION_FLAG_NOT_FOUND, + ); + } + + return { + ...mergeUpdateInExistingRecord({ + existing, + properties: [...FLAT_PERMISSION_FLAG_EDITABLE_PROPERTIES], + update: updatePermissionFlagInput.update, + }), + updatedAt: new Date().toISOString(), + }; + }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/synthesize-flat-permission-flag-from-flag.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/synthesize-flat-permission-flag-from-flag.util.ts new file mode 100644 index 0000000000..df2266e2c0 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag/utils/synthesize-flat-permission-flag-from-flag.util.ts @@ -0,0 +1,57 @@ +import { + PermissionFlagType, + SystemPermissionFlag, +} from 'twenty-shared/constants'; + +import { type FlatPermissionFlag } from 'src/engine/metadata-modules/flat-permission-flag/types/flat-permission-flag.type'; +import { + STANDARD_PERMISSION_FLAG_DEFINITIONS, + type StandardPermissionFlagDefinition, +} from 'src/engine/metadata-modules/permission-flag/constants/standard-permission-flag-definitions.constant'; + +const STANDARD_PERMISSION_FLAG_DEFINITION_BY_KEY: Record< + PermissionFlagType, + StandardPermissionFlagDefinition +> = STANDARD_PERMISSION_FLAG_DEFINITIONS.reduce( + (accumulator, definition) => { + accumulator[definition.key] = definition; + + return accumulator; + }, + {} as Record, +); + +const SYNTHESIZED_TIMESTAMP = new Date(0).toISOString(); + +// TODO: Remove this once we have a proper permission flag catalog backfilled +export const synthesizeFlatPermissionFlagFromFlag = ({ + flag, + workspaceId, + applicationId, + applicationUniversalIdentifier, +}: { + flag: PermissionFlagType; + workspaceId: string; + applicationId: string; + applicationUniversalIdentifier: string; +}): FlatPermissionFlag => { + const definition = STANDARD_PERMISSION_FLAG_DEFINITION_BY_KEY[flag]; + const universalIdentifier = SystemPermissionFlag[flag]; + + return { + id: universalIdentifier, + universalIdentifier, + workspaceId, + applicationId, + applicationUniversalIdentifier, + key: flag, + label: definition.label, + description: definition.description, + icon: definition.icon, + permissionType: definition.permissionType, + rolePermissionFlagIds: [], + rolePermissionFlagUniversalIdentifiers: [], + createdAt: SYNTHESIZED_TIMESTAMP, + updatedAt: SYNTHESIZED_TIMESTAMP, + }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-role-permission-flag/services/workspace-flat-role-permission-flag-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/flat-role-permission-flag/services/workspace-flat-role-permission-flag-map-cache.service.ts new file mode 100644 index 0000000000..979f11f796 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-role-permission-flag/services/workspace-flat-role-permission-flag-map-cache.service.ts @@ -0,0 +1,86 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { Repository } from 'typeorm'; + +import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; +import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant'; +import { type FlatRolePermissionFlagMaps } from 'src/engine/metadata-modules/flat-role-permission-flag/types/flat-role-permission-flag-maps.type'; +import { fromRolePermissionFlagEntityToFlatRolePermissionFlag } from 'src/engine/metadata-modules/flat-role-permission-flag/utils/from-role-permission-flag-entity-to-flat-role-permission-flag.util'; +import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity'; +import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity'; +import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; +import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service'; +import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator'; +import { createIdToUniversalIdentifierMap } from 'src/engine/workspace-cache/utils/create-id-to-universal-identifier-map.util'; +import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util'; + +@Injectable() +@WorkspaceCache('flatRolePermissionFlagMaps') +export class WorkspaceFlatRolePermissionFlagMapCacheService extends WorkspaceCacheProvider { + constructor( + @InjectRepository(RolePermissionFlagEntity) + private readonly rolePermissionFlagRepository: Repository, + @InjectRepository(ApplicationEntity) + private readonly applicationRepository: Repository, + @InjectRepository(RoleEntity) + private readonly roleRepository: Repository, + @InjectRepository(PermissionFlagEntity) + private readonly permissionFlagRepository: Repository, + ) { + super(); + } + + async computeForCache( + workspaceId: string, + ): Promise { + const [rolePermissionFlags, applications, roles, permissionFlags] = + await Promise.all([ + this.rolePermissionFlagRepository.find({ + where: { workspaceId }, + withDeleted: true, + }), + this.applicationRepository.find({ + where: { workspaceId }, + select: ['id', 'universalIdentifier'], + withDeleted: true, + }), + this.roleRepository.find({ + where: { workspaceId }, + select: ['id', 'universalIdentifier'], + withDeleted: true, + }), + this.permissionFlagRepository.find({ + where: { workspaceId }, + select: ['id', 'universalIdentifier'], + withDeleted: true, + }), + ]); + + const applicationIdToUniversalIdentifierMap = + createIdToUniversalIdentifierMap(applications); + const roleIdToUniversalIdentifierMap = + createIdToUniversalIdentifierMap(roles); + const permissionFlagIdToUniversalIdentifierMap = + createIdToUniversalIdentifierMap(permissionFlags); + + const flatRolePermissionFlagMaps = createEmptyFlatEntityMaps(); + + for (const rolePermissionFlagEntity of rolePermissionFlags) { + const flatRolePermissionFlag = + fromRolePermissionFlagEntityToFlatRolePermissionFlag({ + entity: rolePermissionFlagEntity, + applicationIdToUniversalIdentifierMap, + permissionFlagIdToUniversalIdentifierMap, + roleIdToUniversalIdentifierMap, + }); + + addFlatEntityToFlatEntityMapsThroughMutationOrThrow({ + flatEntity: flatRolePermissionFlag, + flatEntityMapsToMutate: flatRolePermissionFlagMaps, + }); + } + + return flatRolePermissionFlagMaps; + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-role-permission-flag/types/flat-role-permission-flag-maps.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-role-permission-flag/types/flat-role-permission-flag-maps.type.ts new file mode 100644 index 0000000000..5bb2f9a9c3 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-role-permission-flag/types/flat-role-permission-flag-maps.type.ts @@ -0,0 +1,4 @@ +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { type FlatRolePermissionFlag } from 'src/engine/metadata-modules/flat-role-permission-flag/types/flat-role-permission-flag.type'; + +export type FlatRolePermissionFlagMaps = FlatEntityMaps; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-role-permission-flag/types/flat-role-permission-flag.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-role-permission-flag/types/flat-role-permission-flag.type.ts new file mode 100644 index 0000000000..3b44b64d29 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-role-permission-flag/types/flat-role-permission-flag.type.ts @@ -0,0 +1,4 @@ +import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type'; +import { type RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity'; + +export type FlatRolePermissionFlag = FlatEntityFrom; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-role-permission-flag/utils/from-create-role-permission-flag-input-to-flat-role-permission-flag-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-role-permission-flag/utils/from-create-role-permission-flag-input-to-flat-role-permission-flag-to-create.util.ts new file mode 100644 index 0000000000..e28ba00459 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-role-permission-flag/utils/from-create-role-permission-flag-input-to-flat-role-permission-flag-to-create.util.ts @@ -0,0 +1,45 @@ +import { v4 } from 'uuid'; + +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; +import { type CreateRolePermissionFlagInput } from 'src/engine/metadata-modules/role-permission-flag/dtos/create-role-permission-flag.input'; +import { type UniversalFlatRolePermissionFlag } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-role-permission-flag.type'; + +export const fromCreateRolePermissionFlagInputToFlatRolePermissionFlagToCreate = + ({ + createRolePermissionFlagInput, + flatApplication, + flatPermissionFlagMaps, + flatRoleMaps, + }: { + createRolePermissionFlagInput: CreateRolePermissionFlagInput; + flatApplication: FlatApplication; + } & Pick< + AllFlatEntityMaps, + 'flatPermissionFlagMaps' | 'flatRoleMaps' + >): UniversalFlatRolePermissionFlag & { + id: string; + } => { + const { roleId, permissionFlagId, flag, universalIdentifier } = + createRolePermissionFlagInput; + const now = new Date().toISOString(); + + const { permissionFlagUniversalIdentifier, roleUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'rolePermissionFlag', + foreignKeyValues: { permissionFlagId, roleId }, + flatEntityMaps: { flatPermissionFlagMaps, flatRoleMaps }, + }); + + return { + id: v4(), + universalIdentifier: universalIdentifier ?? v4(), + applicationUniversalIdentifier: flatApplication.universalIdentifier, + permissionFlagUniversalIdentifier, + roleUniversalIdentifier, + flag, + createdAt: now, + updatedAt: now, + }; + }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-role-permission-flag/utils/from-role-permission-flag-entity-to-flat-role-permission-flag.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-role-permission-flag/utils/from-role-permission-flag-entity-to-flat-role-permission-flag.util.ts new file mode 100644 index 0000000000..4201636c2e --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-role-permission-flag/utils/from-role-permission-flag-entity-to-flat-role-permission-flag.util.ts @@ -0,0 +1,71 @@ +import { SystemPermissionFlag } from 'twenty-shared/constants'; +import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils'; + +import { + FlatEntityMapsException, + FlatEntityMapsExceptionCode, +} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception'; +import { getMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util'; +import { type FlatRolePermissionFlag } from 'src/engine/metadata-modules/flat-role-permission-flag/types/flat-role-permission-flag.type'; +import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type'; + +export const fromRolePermissionFlagEntityToFlatRolePermissionFlag = ({ + entity: rolePermissionFlagEntity, + applicationIdToUniversalIdentifierMap, + permissionFlagIdToUniversalIdentifierMap, + roleIdToUniversalIdentifierMap, +}: FromEntityToFlatEntityArgs<'rolePermissionFlag'>): FlatRolePermissionFlag => { + const rolePermissionFlagEntityWithoutRelations = removePropertiesFromRecord( + rolePermissionFlagEntity, + getMetadataEntityRelationProperties('rolePermissionFlag'), + ); + + const applicationUniversalIdentifier = + applicationIdToUniversalIdentifierMap.get( + rolePermissionFlagEntity.applicationId, + ); + + if (!isDefined(applicationUniversalIdentifier)) { + throw new FlatEntityMapsException( + `Application with id ${rolePermissionFlagEntity.applicationId} not found for rolePermissionFlag ${rolePermissionFlagEntity.id}`, + FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND, + ); + } + + const roleUniversalIdentifier = roleIdToUniversalIdentifierMap.get( + rolePermissionFlagEntity.roleId, + ); + + if (!isDefined(roleUniversalIdentifier)) { + throw new FlatEntityMapsException( + `Role with id ${rolePermissionFlagEntity.roleId} not found for rolePermissionFlag ${rolePermissionFlagEntity.id}`, + FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND, + ); + } + + const permissionFlagUniversalIdentifier = isDefined( + rolePermissionFlagEntity.permissionFlagId, + ) + ? permissionFlagIdToUniversalIdentifierMap.get( + rolePermissionFlagEntity.permissionFlagId, + ) + : SystemPermissionFlag[rolePermissionFlagEntity.flag]; + + if (!isDefined(permissionFlagUniversalIdentifier)) { + throw new FlatEntityMapsException( + `PermissionFlag with id ${rolePermissionFlagEntity.permissionFlagId} not found for rolePermissionFlag ${rolePermissionFlagEntity.id}`, + FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND, + ); + } + + return { + ...rolePermissionFlagEntityWithoutRelations, + createdAt: rolePermissionFlagEntity.createdAt.toISOString(), + updatedAt: rolePermissionFlagEntity.updatedAt.toISOString(), + universalIdentifier: + rolePermissionFlagEntityWithoutRelations.universalIdentifier, + applicationUniversalIdentifier, + permissionFlagUniversalIdentifier, + roleUniversalIdentifier, + }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-role/types/flat-role.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-role/types/flat-role.type.ts index 4e04c099ec..2221704ad0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-role/types/flat-role.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-role/types/flat-role.type.ts @@ -2,8 +2,8 @@ import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/typ import { type RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; export type FlatRole = FlatEntityFrom & { - // TODO remove once objectPermission permissionFlag fieldPermission have been migrated to v2 + // TODO remove once objectPermission rolePermissionFlag fieldPermission have been migrated to v2 objectPermissionIds: string[]; - permissionFlagIds: string[]; + rolePermissionFlagIds: string[]; fieldPermissionIds: string[]; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-create-role-input-to-flat-role-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-create-role-input-to-flat-role-to-create.util.ts index 5b6a081cd6..20be70e7cd 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-create-role-input-to-flat-role-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-create-role-input-to-flat-role-to-create.util.ts @@ -54,9 +54,9 @@ export const fromCreateRoleInputToFlatRoleToCreate = ({ roleTargetIds: [], roleTargetUniversalIdentifiers: [], objectPermissionIds: [], - permissionFlagIds: [], + rolePermissionFlagIds: [], objectPermissionUniversalIdentifiers: [], - permissionFlagUniversalIdentifiers: [], + rolePermissionFlagUniversalIdentifiers: [], fieldPermissionIds: [], fieldPermissionUniversalIdentifiers: [], rowLevelPermissionPredicateIds: [], diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-role-entity-to-flat-role.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-role-entity-to-flat-role.util.ts index 7ff3d8a385..9b24bab625 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-role-entity-to-flat-role.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-role-entity-to-flat-role.util.ts @@ -13,10 +13,10 @@ import { type RegroupedEntity } from 'src/engine/workspace-cache/utils/regroup-e type FromRoleEntityToFlatRoleArgs = { entity: Omit< EntityWithRegroupedOneToManyRelations>, - 'objectPermissions' | 'permissionFlags' | 'fieldPermissions' + 'objectPermissions' | 'rolePermissionFlags' | 'fieldPermissions' > & { objectPermissions: RegroupedEntity[]; - permissionFlags: RegroupedEntity[]; + rolePermissionFlags: RegroupedEntity[]; fieldPermissions: RegroupedEntity[]; }; } & EntityManyToOneIdByUniversalIdentifierMaps<'role'>; @@ -57,7 +57,7 @@ export const fromRoleEntityToFlatRole = ({ applicationId: roleEntity.applicationId, roleTargetIds: roleEntity.roleTargets.map(({ id }) => id), objectPermissionIds: roleEntity.objectPermissions.map(({ id }) => id), - permissionFlagIds: roleEntity.permissionFlags.map(({ id }) => id), + rolePermissionFlagIds: roleEntity.rolePermissionFlags.map(({ id }) => id), fieldPermissionIds: roleEntity.fieldPermissions.map(({ id }) => id), rowLevelPermissionPredicateIds: roleEntity.rowLevelPermissionPredicates.map( ({ id }) => id, @@ -71,7 +71,7 @@ export const fromRoleEntityToFlatRole = ({ objectPermissionUniversalIdentifiers: roleEntity.objectPermissions.map( ({ universalIdentifier }) => universalIdentifier, ), - permissionFlagUniversalIdentifiers: roleEntity.permissionFlags.map( + rolePermissionFlagUniversalIdentifiers: roleEntity.rolePermissionFlags.map( ({ universalIdentifier }) => universalIdentifier, ), fieldPermissionUniversalIdentifiers: roleEntity.fieldPermissions.map( diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-engine.module.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-engine.module.ts index 2cf191f506..838071bcdb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/metadata-engine.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/metadata-engine.module.ts @@ -17,6 +17,7 @@ import { MessageChannelMetadataModule } from 'src/engine/metadata-modules/messag import { MessageFolderMetadataModule } from 'src/engine/metadata-modules/message-folder/message-folder-metadata.module'; import { NavigationMenuItemModule } from 'src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.module'; import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; +import { PermissionFlagModule } from 'src/engine/metadata-modules/permission-flag/permission-flag.module'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { MinimalMetadataModule } from 'src/engine/metadata-modules/minimal-metadata/minimal-metadata.module'; import { RoleModule } from 'src/engine/metadata-modules/role/role.module'; @@ -47,6 +48,7 @@ import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/work WorkspaceMetadataVersionModule, RoleModule, PermissionsModule, + PermissionFlagModule, RouteTriggerModule, WebhookModule, ConnectedAccountMetadataModule, @@ -75,6 +77,7 @@ import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/work ViewModule, RoleModule, PermissionsModule, + PermissionFlagModule, WebhookModule, ConnectedAccountMetadataModule, MessageChannelMetadataModule, diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag/constants/permission-flag-permission-type.constant.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag/constants/permission-flag-permission-type.constant.ts new file mode 100644 index 0000000000..dcbe3bf5b8 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag/constants/permission-flag-permission-type.constant.ts @@ -0,0 +1,4 @@ +export const PERMISSION_FLAG_PERMISSION_TYPES = ['settings', 'tool'] as const; + +export type PermissionFlagPermissionType = + (typeof PERMISSION_FLAG_PERMISSION_TYPES)[number]; diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag/constants/standard-permission-flag-definitions.constant.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag/constants/standard-permission-flag-definitions.constant.ts new file mode 100644 index 0000000000..b1d296b371 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag/constants/standard-permission-flag-definitions.constant.ts @@ -0,0 +1,160 @@ +import { + PermissionFlagType, + SystemPermissionFlag, +} from 'twenty-shared/constants'; + +import { type PermissionFlagPermissionType } from 'src/engine/metadata-modules/permission-flag/constants/permission-flag-permission-type.constant'; +import { TOOL_PERMISSION_FLAGS } from 'src/engine/metadata-modules/permissions/constants/tool-permission-flags'; + +export type StandardPermissionFlagDefinition = { + key: PermissionFlagType; + universalIdentifier: string; + label: string; + description: string | null; + icon: string | null; + permissionType: PermissionFlagPermissionType; +}; + +type StandardPermissionFlagMetadata = Pick< + StandardPermissionFlagDefinition, + 'label' | 'description' | 'icon' +>; + +const STANDARD_PERMISSION_FLAG_METADATA: Record< + PermissionFlagType, + StandardPermissionFlagMetadata +> = { + [PermissionFlagType.API_KEYS_AND_WEBHOOKS]: { + label: 'API Keys & Webhooks', + description: 'Manage API keys and webhooks', + icon: 'IconCode', + }, + [PermissionFlagType.WORKSPACE]: { + label: 'Workspace', + description: 'Set global workspace preferences', + icon: 'IconSettings', + }, + [PermissionFlagType.WORKSPACE_MEMBERS]: { + label: 'Users', + description: 'Add or remove users', + icon: 'IconUsers', + }, + [PermissionFlagType.ROLES]: { + label: 'Roles', + description: 'Define user roles and access levels', + icon: 'IconLockOpen', + }, + [PermissionFlagType.DATA_MODEL]: { + label: 'Data Model', + description: 'Edit data structure and fields', + icon: 'IconHierarchy', + }, + [PermissionFlagType.SECURITY]: { + label: 'Security', + description: 'Manage security policies', + icon: 'IconKey', + }, + [PermissionFlagType.WORKFLOWS]: { + label: 'Workflows', + description: 'Manage workflows', + icon: 'IconSettingsAutomation', + }, + [PermissionFlagType.IMPERSONATE]: { + label: 'Impersonate', + description: 'Impersonate workspace users', + icon: 'IconSpy', + }, + [PermissionFlagType.SSO_BYPASS]: { + label: 'SSO Bypass', + description: 'Enable bypass options', + icon: 'IconShield', + }, + [PermissionFlagType.APPLICATIONS]: { + label: 'Applications', + description: 'Install and manage applications', + icon: 'IconApps', + }, + [PermissionFlagType.MARKETPLACE_APPS]: { + label: 'Marketplace Apps', + description: 'Browse and install marketplace apps', + icon: 'IconShoppingBag', + }, + [PermissionFlagType.LAYOUTS]: { + label: 'Layouts', + description: 'Customize page layouts and UI structure', + icon: 'IconLayoutSidebarRightCollapse', + }, + [PermissionFlagType.BILLING]: { + label: 'Billing', + description: 'Manage billing and subscriptions', + icon: 'IconCreditCard', + }, + [PermissionFlagType.AI_SETTINGS]: { + label: 'AI', + description: 'Create and configure AI agents', + icon: 'IconSparkles', + }, + [PermissionFlagType.AI]: { + label: 'Ask AI', + description: 'Chat with AI agents and use AI features', + icon: 'IconSparkles', + }, + [PermissionFlagType.VIEWS]: { + label: 'Manage Views', + description: 'Create, edit, and delete workspace views', + icon: 'IconTable', + }, + [PermissionFlagType.UPLOAD_FILE]: { + label: 'Upload Files', + description: 'Allow uploading files and attachments', + icon: 'IconFileUpload', + }, + [PermissionFlagType.DOWNLOAD_FILE]: { + label: 'Download Files', + description: 'Allow downloading files and attachments', + icon: 'IconDownload', + }, + [PermissionFlagType.SEND_EMAIL_TOOL]: { + label: 'Send Email', + description: 'Send emails via connected accounts', + icon: 'IconMail', + }, + [PermissionFlagType.HTTP_REQUEST_TOOL]: { + label: 'HTTP Request', + description: 'Make HTTP requests to external APIs', + icon: 'IconApi', + }, + [PermissionFlagType.CODE_INTERPRETER_TOOL]: { + label: 'Code Interpreter', + description: 'Run code to analyze files and data', + icon: 'IconCode', + }, + [PermissionFlagType.IMPORT_CSV]: { + label: 'Import CSV', + description: 'Allow importing data from CSV files', + icon: 'IconFileImport', + }, + [PermissionFlagType.EXPORT_CSV]: { + label: 'Export CSV', + description: 'Allow exporting data to CSV files', + icon: 'IconFileExport', + }, + [PermissionFlagType.CONNECTED_ACCOUNTS]: { + label: 'Sync Account', + description: 'Sync email and calendar accounts', + icon: 'IconAt', + }, + [PermissionFlagType.PROFILE_INFORMATION]: { + label: 'Edit Profile', + description: 'Edit own profile information', + icon: 'IconUser', + }, +}; + +export const STANDARD_PERMISSION_FLAG_DEFINITIONS: StandardPermissionFlagDefinition[] = + Object.values(PermissionFlagType).map((key) => ({ + key, + universalIdentifier: SystemPermissionFlag[key], + ...STANDARD_PERMISSION_FLAG_METADATA[key], + permissionType: TOOL_PERMISSION_FLAGS.includes(key) ? 'tool' : 'settings', + })); diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag/dtos/create-permission-flag.input.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag/dtos/create-permission-flag.input.ts index e5d463866d..05773e1409 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permission-flag/dtos/create-permission-flag.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag/dtos/create-permission-flag.input.ts @@ -1,7 +1,52 @@ -import { PermissionFlagType } from 'twenty-shared/constants'; +import { Field, InputType } from '@nestjs/graphql'; -export type CreatePermissionFlagInput = { - roleId: string; - flag: PermissionFlagType; +import { + IsIn, + IsNotEmpty, + IsOptional, + IsString, + IsUUID, +} from 'class-validator'; + +import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; +import { + PERMISSION_FLAG_PERMISSION_TYPES, + type PermissionFlagPermissionType, +} from 'src/engine/metadata-modules/permission-flag/constants/permission-flag-permission-type.constant'; + +@InputType() +export class CreatePermissionFlagInput { + @IsUUID() + @IsOptional() + @Field(() => UUIDScalarType, { nullable: true }) + id?: string; + + @IsUUID() + @IsOptional() + @Field(() => UUIDScalarType, { nullable: true }) universalIdentifier?: string; -}; + + @IsString() + @IsNotEmpty() + @Field() + key: string; + + @IsString() + @IsNotEmpty() + @Field() + label: string; + + @IsString() + @IsOptional() + @Field({ nullable: true }) + description?: string; + + @IsString() + @IsOptional() + @Field({ nullable: true }) + icon?: string; + + @IsIn(PERMISSION_FLAG_PERMISSION_TYPES) + @Field(() => String) + permissionType: PermissionFlagPermissionType; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag/dtos/permission-flag.dto.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag/dtos/permission-flag.dto.ts index 2e5c16d5a2..4cd4ccd8b8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permission-flag/dtos/permission-flag.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag/dtos/permission-flag.dto.ts @@ -1,17 +1,67 @@ -import { Field, ObjectType } from '@nestjs/graphql'; +import { Field, HideField, ObjectType } from '@nestjs/graphql'; -import { PermissionFlagType } from 'twenty-shared/constants'; +import { + IsDateString, + IsIn, + IsNotEmpty, + IsOptional, + IsString, + IsUUID, +} from 'class-validator'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; +import { + PERMISSION_FLAG_PERMISSION_TYPES, + type PermissionFlagPermissionType, +} from 'src/engine/metadata-modules/permission-flag/constants/permission-flag-permission-type.constant'; @ObjectType('PermissionFlag') export class PermissionFlagDTO { - @Field(() => UUIDScalarType, { nullable: false }) + @IsUUID() + @IsNotEmpty() + @Field(() => UUIDScalarType) id: string; - @Field(() => UUIDScalarType, { nullable: false }) - roleId: string; + @IsUUID() + @IsNotEmpty() + @Field(() => UUIDScalarType) + universalIdentifier: string; - @Field({ nullable: false }) - flag: PermissionFlagType; + @IsString() + @IsNotEmpty() + @Field() + key: string; + + @IsString() + @IsNotEmpty() + @Field() + label: string; + + @IsString() + @IsOptional() + @Field(() => String, { nullable: true }) + description: string | null; + + @IsString() + @IsOptional() + @Field(() => String, { nullable: true }) + icon: string | null; + + @IsIn(PERMISSION_FLAG_PERMISSION_TYPES) + @Field(() => String) + permissionType: PermissionFlagPermissionType; + + @HideField() + workspaceId: string; + + @Field(() => UUIDScalarType) + applicationId: string; + + @IsDateString() + @Field() + createdAt: Date; + + @IsDateString() + @Field() + updatedAt: Date; } diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag/dtos/update-permission-flag.input.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag/dtos/update-permission-flag.input.ts new file mode 100644 index 0000000000..68f654b25e --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag/dtos/update-permission-flag.input.ts @@ -0,0 +1,57 @@ +import { Field, InputType } from '@nestjs/graphql'; + +import { Type } from 'class-transformer'; +import { + IsIn, + IsNotEmpty, + IsOptional, + IsString, + IsUUID, + ValidateNested, +} from 'class-validator'; + +import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; +import { + PERMISSION_FLAG_PERMISSION_TYPES, + type PermissionFlagPermissionType, +} from 'src/engine/metadata-modules/permission-flag/constants/permission-flag-permission-type.constant'; + +@InputType() +export class UpdatePermissionFlagInputUpdates { + @IsOptional() + @IsString() + @Field({ nullable: true }) + label?: string; + + @IsOptional() + @IsString() + @Field({ nullable: true }) + description?: string; + + @IsOptional() + @IsString() + @Field({ nullable: true }) + icon?: string; + + @IsOptional() + @IsIn(PERMISSION_FLAG_PERMISSION_TYPES) + @Field(() => String, { nullable: true }) + permissionType?: PermissionFlagPermissionType; +} + +@InputType() +export class UpdatePermissionFlagInput { + @IsUUID() + @IsNotEmpty() + @Field(() => UUIDScalarType, { + description: 'The id of the permission flag definition to update', + }) + id: string; + + @Type(() => UpdatePermissionFlagInputUpdates) + @ValidateNested() + @Field(() => UpdatePermissionFlagInputUpdates, { + description: 'The fields to update', + }) + update: UpdatePermissionFlagInputUpdates; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.entity.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.entity.ts index b77fce9c1a..984b39bf3e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.entity.ts @@ -1,38 +1,46 @@ -import { PermissionFlagType } from 'twenty-shared/constants'; import { Column, CreateDateColumn, Entity, Index, - JoinColumn, - ManyToOne, + OneToMany, PrimaryGeneratedColumn, Relation, Unique, UpdateDateColumn, } from 'typeorm'; -import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; +import { type PermissionFlagPermissionType } from 'src/engine/metadata-modules/permission-flag/constants/permission-flag-permission-type.constant'; +import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity'; import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; @Entity('permissionFlag') -@Unique('IDX_PERMISSION_FLAG_FLAG_ROLE_ID_UNIQUE', ['flag', 'roleId']) -@Index('IDX_PERMISSION_FLAG_ROLE_ID', ['roleId']) +@Unique('IDX_PERMISSION_FLAG_KEY_WORKSPACE_ID_UNIQUE', ['key', 'workspaceId']) +@Index('IDX_PERMISSION_FLAG_APPLICATION_ID', ['applicationId']) export class PermissionFlagEntity extends SyncableEntity { @PrimaryGeneratedColumn('uuid') id: string; - @Column({ nullable: false, type: 'uuid' }) - roleId: string; - - @ManyToOne(() => RoleEntity, (role) => role.permissionFlags, { - onDelete: 'CASCADE', - }) - @JoinColumn({ name: 'roleId' }) - role: Relation; + @Column({ nullable: false, type: 'varchar' }) + key: string; @Column({ nullable: false, type: 'varchar' }) - flag: PermissionFlagType; + label: string; + + @Column({ nullable: true, type: 'text' }) + description: string | null; + + @Column({ nullable: true, type: 'varchar' }) + icon: string | null; + + @Column({ nullable: false, type: 'varchar' }) + permissionType: PermissionFlagPermissionType; + + @OneToMany( + () => RolePermissionFlagEntity, + (rolePermissionFlag) => rolePermissionFlag.permissionFlag, + ) + rolePermissionFlags: Relation; @CreateDateColumn({ type: 'timestamptz' }) createdAt: Date; diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.exception.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.exception.ts new file mode 100644 index 0000000000..1ebf6aa5cc --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.exception.ts @@ -0,0 +1,52 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; +import { assertUnreachable } from 'twenty-shared/utils'; + +import { CustomException } from 'src/utils/custom-exception'; + +export enum PermissionFlagExceptionCode { + PERMISSION_FLAG_NOT_FOUND = 'PERMISSION_FLAG_NOT_FOUND', + PERMISSION_FLAG_ALREADY_EXISTS = 'PERMISSION_FLAG_ALREADY_EXISTS', + INVALID_PERMISSION_FLAG_KEY = 'INVALID_PERMISSION_FLAG_KEY', + INVALID_PERMISSION_FLAG_PERMISSION_TYPE = 'INVALID_PERMISSION_FLAG_PERMISSION_TYPE', + PERMISSION_FLAG_KEY_IMMUTABLE = 'PERMISSION_FLAG_KEY_IMMUTABLE', + PERMISSION_FLAG_IS_STANDARD = 'PERMISSION_FLAG_IS_STANDARD', + PERMISSION_FLAG_IN_USE = 'PERMISSION_FLAG_IN_USE', +} + +const getPermissionFlagExceptionUserFriendlyMessage = ( + code: PermissionFlagExceptionCode, +) => { + switch (code) { + case PermissionFlagExceptionCode.PERMISSION_FLAG_NOT_FOUND: + return msg`Permission flag not found.`; + case PermissionFlagExceptionCode.PERMISSION_FLAG_ALREADY_EXISTS: + return msg`Permission flag already exists.`; + case PermissionFlagExceptionCode.INVALID_PERMISSION_FLAG_KEY: + return msg`Invalid permission flag key.`; + case PermissionFlagExceptionCode.INVALID_PERMISSION_FLAG_PERMISSION_TYPE: + return msg`Invalid permission flag permission type.`; + case PermissionFlagExceptionCode.PERMISSION_FLAG_KEY_IMMUTABLE: + return msg`Permission flag key cannot be changed.`; + case PermissionFlagExceptionCode.PERMISSION_FLAG_IS_STANDARD: + return msg`Standard permission flags cannot be modified.`; + case PermissionFlagExceptionCode.PERMISSION_FLAG_IN_USE: + return msg`Permission flag is still assigned to a role.`; + default: + assertUnreachable(code); + } +}; + +export class PermissionFlagException extends CustomException { + constructor( + message: string, + code: PermissionFlagExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + getPermissionFlagExceptionUserFriendlyMessage(code), + }); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.module.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.module.ts index f77d76940d..4c8bb1151f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.module.ts @@ -2,16 +2,20 @@ import { Module } from '@nestjs/common'; import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; +import { FlatPermissionFlagModule } from 'src/engine/metadata-modules/flat-permission-flag/flat-permission-flag.module'; import { PermissionFlagService } from 'src/engine/metadata-modules/permission-flag/permission-flag.service'; -import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; +import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; +import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module'; @Module({ imports: [ - WorkspaceCacheModule, - ApplicationModule, - WorkspaceMigrationModule, WorkspaceManyOrAllFlatEntityMapsCacheModule, + WorkspaceMigrationModule, + WorkspaceCacheStorageModule, + ApplicationModule, + FlatPermissionFlagModule, + PermissionsModule, ], providers: [PermissionFlagService], exports: [PermissionFlagService], diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.service.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.service.ts index 192393ab09..d7e0e6d2f3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.service.ts @@ -1,19 +1,18 @@ import { Injectable } from '@nestjs/common'; -import { msg } from '@lingui/core/macro'; -import { PermissionFlagType } from 'twenty-shared/constants'; import { isDefined } from 'twenty-shared/utils'; import { ApplicationService } from 'src/engine/core-modules/application/application.service'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; -import { type FlatPermissionFlag } from 'src/engine/metadata-modules/flat-permission-flag/types/flat-permission-flag.type'; +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 { fromCreatePermissionFlagInputToFlatPermissionFlagToCreate } from 'src/engine/metadata-modules/flat-permission-flag/utils/from-create-permission-flag-input-to-flat-permission-flag-to-create.util'; -import { type UpsertPermissionFlagsInput } from 'src/engine/metadata-modules/permission-flag/dtos/upsert-permission-flag-input'; -import { - PermissionsException, - PermissionsExceptionCode, - PermissionsExceptionMessage, -} from 'src/engine/metadata-modules/permissions/permissions.exception'; +import { fromDeletePermissionFlagInputToFlatPermissionFlagOrThrow } from 'src/engine/metadata-modules/flat-permission-flag/utils/from-delete-permission-flag-input-to-flat-permission-flag-or-throw.util'; +import { fromFlatPermissionFlagToPermissionFlagDto } from 'src/engine/metadata-modules/flat-permission-flag/utils/from-flat-permission-flag-to-permission-flag-dto.util'; +import { fromUpdatePermissionFlagInputToFlatPermissionFlagToUpdateOrThrow } from 'src/engine/metadata-modules/flat-permission-flag/utils/from-update-permission-flag-input-to-flat-permission-flag-to-update-or-throw.util'; +import { type CreatePermissionFlagInput } from 'src/engine/metadata-modules/permission-flag/dtos/create-permission-flag.input'; +import { type PermissionFlagDTO } from 'src/engine/metadata-modules/permission-flag/dtos/permission-flag.dto'; +import { type UpdatePermissionFlagInput } from 'src/engine/metadata-modules/permission-flag/dtos/update-permission-flag.input'; import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception'; import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service'; @@ -25,115 +24,8 @@ export class PermissionFlagService { private readonly applicationService: ApplicationService, ) {} - public async upsertPermissionFlags({ - workspaceId, - input, - }: { - workspaceId: string; - input: UpsertPermissionFlagsInput; - }): Promise { - const { flatPermissionFlagMaps, flatRoleMaps } = - await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatPermissionFlagMaps', 'flatRoleMaps'], - }, - ); - - const roleUniversalId = flatRoleMaps.universalIdentifierById[input.roleId]; - const role = isDefined(roleUniversalId) - ? flatRoleMaps.byUniversalIdentifier[roleUniversalId] - : undefined; - - if (!isDefined(role)) { - throw new PermissionsException( - PermissionsExceptionMessage.ROLE_NOT_FOUND, - PermissionsExceptionCode.ROLE_NOT_FOUND, - { - userFriendlyMessage: msg`The role you are trying to modify could not be found.`, - }, - ); - } - - const invalidFlags = input.permissionFlagKeys.filter( - (flag) => !Object.values(PermissionFlagType).includes(flag), - ); - - if (invalidFlags.length > 0) { - throw new PermissionsException( - `${PermissionsExceptionMessage.INVALID_SETTING}: ${invalidFlags.join(', ')}`, - PermissionsExceptionCode.INVALID_SETTING, - { - userFriendlyMessage: msg`Some of the permissions you selected are not valid. Please try again with valid permission settings.`, - }, - ); - } - - const roleUniversalIdentifier = role.universalIdentifier; - - const currentPermissionFlagsForRole = Object.values( - flatPermissionFlagMaps.byUniversalIdentifier, - ).filter( - (pf): pf is FlatPermissionFlag => - isDefined(pf) && pf.roleUniversalIdentifier === roleUniversalIdentifier, - ); - - const inputSet = new Set(input.permissionFlagKeys); - const existingSet = new Set( - currentPermissionFlagsForRole.map((pf) => pf.flag), - ); - - const { workspaceCustomFlatApplication } = - await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( - { workspaceId }, - ); - - const flatEntityToCreate = input.permissionFlagKeys - .filter((flag) => !existingSet.has(flag)) - .map((flag) => - fromCreatePermissionFlagInputToFlatPermissionFlagToCreate({ - createPermissionFlagInput: { roleId: input.roleId, flag }, - flatApplication: workspaceCustomFlatApplication, - flatRoleMaps, - }), - ); - - const flatEntityToDelete = currentPermissionFlagsForRole.filter( - (pf) => !inputSet.has(pf.flag), - ); - - if (flatEntityToCreate.length === 0 && flatEntityToDelete.length === 0) { - const unchanged = currentPermissionFlagsForRole.filter((pf) => - inputSet.has(pf.flag), - ); - return unchanged; - } - - const buildAndRunResult = - await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( - { - allFlatEntityOperationByMetadataName: { - permissionFlag: { - flatEntityToCreate, - flatEntityToDelete, - flatEntityToUpdate: [], - }, - }, - workspaceId, - isSystemBuild: false, - applicationUniversalIdentifier: - workspaceCustomFlatApplication.universalIdentifier, - }, - ); - - if (buildAndRunResult.status === 'fail') { - throw new WorkspaceMigrationBuilderException( - buildAndRunResult, - 'Validation errors occurred while upserting permission flags', - ); - } - - const { flatPermissionFlagMaps: freshFlatPermissionFlagMaps } = + async findAll(workspaceId: string): Promise { + const { flatPermissionFlagMaps } = await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { workspaceId, @@ -141,13 +33,196 @@ export class PermissionFlagService { }, ); - const resultFlags = Object.values( - freshFlatPermissionFlagMaps.byUniversalIdentifier, - ).filter( - (pf): pf is FlatPermissionFlag => - isDefined(pf) && pf.roleUniversalIdentifier === roleUniversalIdentifier, - ); + return Object.values(flatPermissionFlagMaps.byUniversalIdentifier) + .filter(isDefined) + .sort((a, b) => a.createdAt.localeCompare(b.createdAt)) + .map(fromFlatPermissionFlagToPermissionFlagDto); + } - return resultFlags; + async findById( + id: string, + workspaceId: string, + ): Promise { + const { flatPermissionFlagMaps } = + await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatPermissionFlagMaps'], + }, + ); + + const flatPermissionFlag = findFlatEntityByIdInFlatEntityMaps({ + flatEntityId: id, + flatEntityMaps: flatPermissionFlagMaps, + }); + + if (!isDefined(flatPermissionFlag)) { + return null; + } + + return fromFlatPermissionFlagToPermissionFlagDto(flatPermissionFlag); + } + + async create( + input: CreatePermissionFlagInput, + workspaceId: string, + ): Promise { + const { workspaceCustomFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { workspaceId }, + ); + + const flatToCreate = + fromCreatePermissionFlagInputToFlatPermissionFlagToCreate({ + createPermissionFlagInput: input, + workspaceId, + flatApplication: workspaceCustomFlatApplication, + }); + + const validateAndBuildResult = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + allFlatEntityOperationByMetadataName: { + permissionFlag: { + flatEntityToCreate: [flatToCreate], + flatEntityToDelete: [], + flatEntityToUpdate: [], + }, + }, + workspaceId, + applicationUniversalIdentifier: + workspaceCustomFlatApplication.universalIdentifier, + }, + ); + + if (validateAndBuildResult.status === 'fail') { + throw new WorkspaceMigrationBuilderException( + validateAndBuildResult, + 'Validation errors occurred while creating permission flag', + ); + } + + const { flatPermissionFlagMaps: recomputedMaps } = + await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatPermissionFlagMaps'], + }, + ); + + return fromFlatPermissionFlagToPermissionFlagDto( + findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityId: flatToCreate.id, + flatEntityMaps: recomputedMaps, + }), + ); + } + + async update( + input: UpdatePermissionFlagInput, + workspaceId: string, + ): Promise { + const { workspaceCustomFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { workspaceId }, + ); + + const { flatPermissionFlagMaps: existingMaps } = + await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatPermissionFlagMaps'], + }, + ); + + const flatToUpdate = + fromUpdatePermissionFlagInputToFlatPermissionFlagToUpdateOrThrow({ + flatPermissionFlagMaps: existingMaps, + updatePermissionFlagInput: input, + }); + + const validateAndBuildResult = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + allFlatEntityOperationByMetadataName: { + permissionFlag: { + flatEntityToCreate: [], + flatEntityToDelete: [], + flatEntityToUpdate: [flatToUpdate], + }, + }, + workspaceId, + applicationUniversalIdentifier: + workspaceCustomFlatApplication.universalIdentifier, + }, + ); + + if (validateAndBuildResult.status === 'fail') { + throw new WorkspaceMigrationBuilderException( + validateAndBuildResult, + 'Validation errors occurred while updating permission flag', + ); + } + + const { flatPermissionFlagMaps: recomputedMaps } = + await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatPermissionFlagMaps'], + }, + ); + + return fromFlatPermissionFlagToPermissionFlagDto( + findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityId: input.id, + flatEntityMaps: recomputedMaps, + }), + ); + } + + async delete(id: string, workspaceId: string): Promise { + const { workspaceCustomFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { workspaceId }, + ); + + const { flatPermissionFlagMaps: existingMaps } = + await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatPermissionFlagMaps'], + }, + ); + + const flatToDelete = + fromDeletePermissionFlagInputToFlatPermissionFlagOrThrow({ + flatPermissionFlagMaps: existingMaps, + permissionFlagId: id, + }); + + const validateAndBuildResult = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + allFlatEntityOperationByMetadataName: { + permissionFlag: { + flatEntityToCreate: [], + flatEntityToDelete: [flatToDelete], + flatEntityToUpdate: [], + }, + }, + workspaceId, + applicationUniversalIdentifier: + workspaceCustomFlatApplication.universalIdentifier, + }, + ); + + if (validateAndBuildResult.status === 'fail') { + throw new WorkspaceMigrationBuilderException( + validateAndBuildResult, + 'Validation errors occurred while deleting permission flag', + ); + } + + return fromFlatPermissionFlagToPermissionFlagDto(flatToDelete); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag/utils/from-flat-permission-flag-to-permission-flag-dto.util.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag/utils/from-flat-permission-flag-to-permission-flag-dto.util.ts deleted file mode 100644 index 97e3bb8cb3..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/permission-flag/utils/from-flat-permission-flag-to-permission-flag-dto.util.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { type FlatPermissionFlag } from 'src/engine/metadata-modules/flat-permission-flag/types/flat-permission-flag.type'; -import { type PermissionFlagDTO } from 'src/engine/metadata-modules/permission-flag/dtos/permission-flag.dto'; - -export const fromFlatPermissionFlagToPermissionFlagDto = ( - flatPermissionFlag: FlatPermissionFlag, -): PermissionFlagDTO => ({ - id: flatPermissionFlag.id, - roleId: flatPermissionFlag.roleId, - flag: flatPermissionFlag.flag, -}); diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/__tests__/permissions.service.spec.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/__tests__/permissions.service.spec.ts index 23e058bf75..2b5e91f76e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/__tests__/permissions.service.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/__tests__/permissions.service.spec.ts @@ -1,7 +1,10 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { PermissionFlagType } from 'twenty-shared/constants'; +import { + PermissionFlagType, + SystemPermissionFlag, +} from 'twenty-shared/constants'; import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api-key-role.service'; import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; @@ -60,7 +63,7 @@ describe('PermissionsService', () => { canBeAssignedToUsers: true, canBeAssignedToAgents: true, canBeAssignedToApiKeys: true, - permissionFlags: [], + rolePermissionFlags: [], workspaceId: 'test-workspace-id', createdAt: new Date(), updatedAt: new Date(), @@ -133,7 +136,7 @@ describe('PermissionsService', () => { canBeAssignedToUsers: true, canBeAssignedToAgents: true, canBeAssignedToApiKeys: true, - permissionFlags: [], + rolePermissionFlags: [], workspaceId: 'test-workspace-id', createdAt: new Date(), updatedAt: new Date(), @@ -184,7 +187,7 @@ describe('PermissionsService', () => { canBeAssignedToUsers: true, canBeAssignedToAgents: true, canBeAssignedToApiKeys: true, - permissionFlags: [], + rolePermissionFlags: [], roleTargets: [], objectPermissions: [], fieldPermissions: [], @@ -254,7 +257,7 @@ describe('PermissionsService', () => { canBeAssignedToUsers: true, canBeAssignedToAgents: true, canBeAssignedToApiKeys: true, - permissionFlags: [], + rolePermissionFlags: [], roleTargets: [], objectPermissions: [], fieldPermissions: [], @@ -292,8 +295,8 @@ describe('PermissionsService', () => { }); }); - describe('Granular permissions with permissionFlags', () => { - it('should grant specific tool permission when included in permissionFlags even if canAccessAllTools is false', () => { + describe('Granular permissions with rolePermissionFlags', () => { + it('should grant specific tool permission when included in rolePermissionFlags even if canAccessAllTools is false', () => { const roleWithSpecificPermission: Partial = { id: 'test-role-id', label: 'Test Role', @@ -308,10 +311,13 @@ describe('PermissionsService', () => { canBeAssignedToUsers: true, canBeAssignedToAgents: true, canBeAssignedToApiKeys: true, - permissionFlags: [ + rolePermissionFlags: [ { id: 'permission-1', - flag: PermissionFlagType.UPLOAD_FILE, + permissionFlag: { + key: PermissionFlagType.UPLOAD_FILE, + universalIdentifier: SystemPermissionFlag.UPLOAD_FILE, + }, roleId: 'test-role-id', workspaceId: 'test-workspace-id', createdAt: new Date(), @@ -338,7 +344,7 @@ describe('PermissionsService', () => { ).toBe(false); }); - it('should grant specific settings permission when included in permissionFlags even if canUpdateAllSettings is false', () => { + it('should grant specific settings permission when included in rolePermissionFlags even if canUpdateAllSettings is false', () => { const roleWithSpecificPermission: Partial = { id: 'test-role-id', label: 'Test Role', @@ -353,10 +359,13 @@ describe('PermissionsService', () => { canBeAssignedToUsers: true, canBeAssignedToAgents: true, canBeAssignedToApiKeys: true, - permissionFlags: [ + rolePermissionFlags: [ { id: 'permission-1', - flag: PermissionFlagType.ROLES, + permissionFlag: { + key: PermissionFlagType.ROLES, + universalIdentifier: SystemPermissionFlag.ROLES, + }, roleId: 'test-role-id', workspaceId: 'test-workspace-id', createdAt: new Date(), @@ -400,7 +409,7 @@ describe('PermissionsService', () => { canBeAssignedToUsers: true, canBeAssignedToAgents: true, canBeAssignedToApiKeys: true, - permissionFlags: [], + rolePermissionFlags: [], roleTargets: [], objectPermissions: [], fieldPermissions: [], diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts index 9fb0566c86..3ef8fd66e8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts @@ -2,7 +2,10 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { msg } from '@lingui/core/macro'; -import { PermissionFlagType } from 'twenty-shared/constants'; +import { + PermissionFlagType, + SystemPermissionFlag, +} from 'twenty-shared/constants'; import { isDefined } from 'twenty-shared/utils'; import { In, Repository } from 'typeorm'; @@ -66,7 +69,7 @@ export class PermissionsService { const defaultSettingsPermissions = this.getDefaultUserWorkspacePermissions().permissionFlags; - const permissionFlags = Object.keys(PermissionFlagType).reduce( + const permissionFlags = Object.values(PermissionFlagType).reduce( (acc, feature) => { const hasBasePermission = this.isToolPermission(feature) ? roleOfUserWorkspace.canAccessAllTools @@ -76,9 +79,7 @@ export class PermissionsService { ...acc, [feature]: hasBasePermission || - roleOfUserWorkspace.permissionFlags.some( - (permissionFlag) => permissionFlag.flag === feature, - ), + this.roleHasPermissionFlag(roleOfUserWorkspace, feature), }; }, defaultSettingsPermissions, @@ -150,7 +151,10 @@ export class PermissionsService { const role = await this.roleRepository.findOne({ where: { id: roleId, workspaceId }, - relations: ['permissionFlags'], + relations: [ + 'rolePermissionFlags', + 'rolePermissionFlags.permissionFlag', + ], }); if (!isDefined(role)) { @@ -203,7 +207,10 @@ export class PermissionsService { const role = await this.roleRepository.findOne({ where: { id: applicationRoleId, workspaceId }, - relations: ['permissionFlags'], + relations: [ + 'rolePermissionFlags', + 'rolePermissionFlags.permissionFlag', + ], }); if (!isDefined(role)) { @@ -240,10 +247,22 @@ export class PermissionsService { return true; } - const permissionFlags = role.permissionFlags ?? []; + return this.roleHasPermissionFlag(role, setting); + } - return permissionFlags.some( - (permissionFlag) => permissionFlag.flag === setting, + private roleHasPermissionFlag( + role: RoleEntity, + flag: PermissionFlagType, + ): boolean { + const rolePermissionFlags = role.rolePermissionFlags ?? []; + + const permissionFlagUniversalIdentifier = SystemPermissionFlag[flag]; + + return rolePermissionFlags.some( + (rolePermissionFlag) => + (rolePermissionFlag.permissionFlag?.universalIdentifier ?? + SystemPermissionFlag[rolePermissionFlag.flag]) === + permissionFlagUniversalIdentifier, ); } @@ -292,7 +311,7 @@ export class PermissionsService { const result = await this.getRolesFromPermissionConfig( rolePermissionConfig, workspaceId, - ['permissionFlags'], + ['rolePermissionFlags', 'rolePermissionFlags.permissionFlag'], ); if (result === null) { @@ -318,7 +337,7 @@ export class PermissionsService { const result = await this.getRolesFromPermissionConfig( rolePermissionConfig, workspaceId, - ['permissionFlags'], + ['rolePermissionFlags', 'rolePermissionFlags.permissionFlag'], ); if (result === null) { @@ -332,11 +351,7 @@ export class PermissionsService { return true; } - const permissionFlags = role.permissionFlags ?? []; - - return permissionFlags.some( - (permissionFlag) => permissionFlag.flag === flag, - ); + return this.roleHasPermissionFlag(role, flag); }; return useIntersection diff --git a/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/dtos/create-role-permission-flag.input.ts b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/dtos/create-role-permission-flag.input.ts new file mode 100644 index 0000000000..d8a8e0f249 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/dtos/create-role-permission-flag.input.ts @@ -0,0 +1,8 @@ +import { type PermissionFlagType } from 'twenty-shared/constants'; + +export type CreateRolePermissionFlagInput = { + roleId: string; + permissionFlagId: string; + flag: PermissionFlagType; + universalIdentifier?: string; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/dtos/role-permission-flag.dto.ts b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/dtos/role-permission-flag.dto.ts new file mode 100644 index 0000000000..5ce8ff3d60 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/dtos/role-permission-flag.dto.ts @@ -0,0 +1,17 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import { PermissionFlagType } from 'twenty-shared/constants'; + +import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; + +@ObjectType('RolePermissionFlag') +export class RolePermissionFlagDTO { + @Field(() => UUIDScalarType, { nullable: false }) + id: string; + + @Field(() => UUIDScalarType, { nullable: false }) + roleId: string; + + @Field({ nullable: false }) + flag: PermissionFlagType; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag/dtos/upsert-permission-flag-input.ts b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/dtos/upsert-permission-flags.input.ts similarity index 100% rename from packages/twenty-server/src/engine/metadata-modules/permission-flag/dtos/upsert-permission-flag-input.ts rename to packages/twenty-server/src/engine/metadata-modules/role-permission-flag/dtos/upsert-permission-flags.input.ts diff --git a/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity.ts b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity.ts new file mode 100644 index 0000000000..5a30dd022f --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity.ts @@ -0,0 +1,61 @@ +import { PermissionFlagType } from 'twenty-shared/constants'; +import { + Column, + CreateDateColumn, + Entity, + Index, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + Relation, + Unique, + UpdateDateColumn, +} from 'typeorm'; + +import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity'; +import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; +import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; + +@Entity('rolePermissionFlag') +@Unique('IDX_ROLE_PERMISSION_FLAG_FLAG_ROLE_ID_UNIQUE', ['flag', 'roleId']) +@Unique('IDX_ROLE_PERMISSION_FLAG_PERMISSION_FLAG_ID_ROLE_ID_UNIQUE', [ + 'permissionFlagId', + 'roleId', +]) +@Index('IDX_ROLE_PERMISSION_FLAG_ROLE_ID', ['roleId']) +@Index('IDX_ROLE_PERMISSION_FLAG_PERMISSION_FLAG_ID', ['permissionFlagId']) +export class RolePermissionFlagEntity extends SyncableEntity { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ nullable: false, type: 'uuid' }) + roleId: string; + + @ManyToOne(() => RoleEntity, (role) => role.rolePermissionFlags, { + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'roleId' }) + role: Relation; + + @Column({ nullable: false, type: 'varchar' }) + flag: PermissionFlagType; + + @Column({ nullable: true, type: 'uuid' }) + permissionFlagId: string | null; + + @ManyToOne( + () => PermissionFlagEntity, + (permissionFlag) => permissionFlag.rolePermissionFlags, + { + onDelete: 'CASCADE', + }, + ) + @JoinColumn({ name: 'permissionFlagId' }) + permissionFlag: Relation | null; + + @CreateDateColumn({ type: 'timestamptz' }) + createdAt: Date; + + @UpdateDateColumn({ type: 'timestamptz' }) + updatedAt: Date; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/role-permission-flag.module.ts b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/role-permission-flag.module.ts new file mode 100644 index 0000000000..5fe590cb9b --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/role-permission-flag.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; + +import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; +import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; +import { RolePermissionFlagService } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.service'; +import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; +import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module'; + +@Module({ + imports: [ + WorkspaceCacheModule, + ApplicationModule, + WorkspaceMigrationModule, + WorkspaceManyOrAllFlatEntityMapsCacheModule, + ], + providers: [RolePermissionFlagService], + exports: [RolePermissionFlagService], +}) +export class RolePermissionFlagModule {} diff --git a/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/role-permission-flag.service.ts b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/role-permission-flag.service.ts new file mode 100644 index 0000000000..da8cfad398 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/role-permission-flag.service.ts @@ -0,0 +1,185 @@ +import { Injectable } from '@nestjs/common'; + +import { msg } from '@lingui/core/macro'; +import { + type PermissionFlagType, + SystemPermissionFlag, +} from 'twenty-shared/constants'; +import { isDefined } from 'twenty-shared/utils'; + +import { ApplicationService } from 'src/engine/core-modules/application/application.service'; +import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; +import { type FlatRolePermissionFlag } from 'src/engine/metadata-modules/flat-role-permission-flag/types/flat-role-permission-flag.type'; +import { fromCreateRolePermissionFlagInputToFlatRolePermissionFlagToCreate } from 'src/engine/metadata-modules/flat-role-permission-flag/utils/from-create-role-permission-flag-input-to-flat-role-permission-flag-to-create.util'; +import { type UpsertPermissionFlagsInput } from 'src/engine/metadata-modules/role-permission-flag/dtos/upsert-permission-flags.input'; +import { + PermissionsException, + PermissionsExceptionCode, + PermissionsExceptionMessage, +} from 'src/engine/metadata-modules/permissions/permissions.exception'; +import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception'; +import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service'; + +@Injectable() +export class RolePermissionFlagService { + constructor( + private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, + private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, + private readonly applicationService: ApplicationService, + ) {} + + public async upsertPermissionFlags({ + workspaceId, + input, + }: { + workspaceId: string; + input: UpsertPermissionFlagsInput; + }): Promise { + const { flatPermissionFlagMaps, flatRolePermissionFlagMaps, flatRoleMaps } = + await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: [ + 'flatPermissionFlagMaps', + 'flatRolePermissionFlagMaps', + 'flatRoleMaps', + ], + }, + ); + + const roleUniversalId = flatRoleMaps.universalIdentifierById[input.roleId]; + const role = isDefined(roleUniversalId) + ? flatRoleMaps.byUniversalIdentifier[roleUniversalId] + : undefined; + + if (!isDefined(role)) { + throw new PermissionsException( + PermissionsExceptionMessage.ROLE_NOT_FOUND, + PermissionsExceptionCode.ROLE_NOT_FOUND, + { + userFriendlyMessage: msg`The role you are trying to modify could not be found.`, + }, + ); + } + + const roleUniversalIdentifier = role.universalIdentifier; + const permissionFlagByKey = new Map( + Object.values(flatPermissionFlagMaps.byUniversalIdentifier) + .filter(isDefined) + .map((permissionFlag) => [permissionFlag.key, permissionFlag]), + ); + const permissionFlagsByInputKey = input.permissionFlagKeys.map((flag) => ({ + flag, + permissionFlag: permissionFlagByKey.get(flag), + })); + const missingPermissionFlags = permissionFlagsByInputKey + .filter(({ permissionFlag }) => !isDefined(permissionFlag)) + .map(({ flag }) => flag); + + if (missingPermissionFlags.length > 0) { + throw new PermissionsException( + `${PermissionsExceptionMessage.INVALID_SETTING}: ${missingPermissionFlags.join(', ')}`, + PermissionsExceptionCode.INVALID_SETTING, + { + userFriendlyMessage: msg`Some of the permissions you selected are not valid. Please try again with valid permission settings.`, + }, + ); + } + + const currentRolePermissionFlagsForRole = Object.values( + flatRolePermissionFlagMaps.byUniversalIdentifier, + ).filter( + (pf): pf is FlatRolePermissionFlag => + isDefined(pf) && pf.roleUniversalIdentifier === roleUniversalIdentifier, + ); + + const inputSet = new Set( + permissionFlagsByInputKey + .map(({ permissionFlag }) => permissionFlag?.universalIdentifier) + .filter(isDefined), + ); + const getEffectiveUniversalIdentifier = ( + pf: FlatRolePermissionFlag, + ): string => + pf.permissionFlagUniversalIdentifier ?? SystemPermissionFlag[pf.flag]; + const existingSet = new Set( + currentRolePermissionFlagsForRole.map(getEffectiveUniversalIdentifier), + ); + + const { workspaceCustomFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { workspaceId }, + ); + + const flatEntityToCreate = permissionFlagsByInputKey + .map(({ permissionFlag }) => permissionFlag) + .filter(isDefined) + .filter( + (permissionFlag) => + !existingSet.has(permissionFlag.universalIdentifier), + ) + .map((permissionFlag) => + fromCreateRolePermissionFlagInputToFlatRolePermissionFlagToCreate({ + createRolePermissionFlagInput: { + roleId: input.roleId, + permissionFlagId: permissionFlag.id, + flag: permissionFlag.key as PermissionFlagType, + }, + flatApplication: workspaceCustomFlatApplication, + flatPermissionFlagMaps, + flatRoleMaps, + }), + ); + + const flatEntityToDelete = currentRolePermissionFlagsForRole.filter( + (pf) => !inputSet.has(getEffectiveUniversalIdentifier(pf)), + ); + + if (flatEntityToCreate.length === 0 && flatEntityToDelete.length === 0) { + return currentRolePermissionFlagsForRole.filter((pf) => + inputSet.has(getEffectiveUniversalIdentifier(pf)), + ); + } + + const buildAndRunResult = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + allFlatEntityOperationByMetadataName: { + rolePermissionFlag: { + flatEntityToCreate, + flatEntityToDelete, + flatEntityToUpdate: [], + }, + }, + workspaceId, + isSystemBuild: false, + applicationUniversalIdentifier: + workspaceCustomFlatApplication.universalIdentifier, + }, + ); + + if (buildAndRunResult.status === 'fail') { + throw new WorkspaceMigrationBuilderException( + buildAndRunResult, + 'Validation errors occurred while upserting permission flags', + ); + } + + const { flatRolePermissionFlagMaps: freshFlatRolePermissionFlagMaps } = + await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatRolePermissionFlagMaps'], + }, + ); + + const resultFlags = Object.values( + freshFlatRolePermissionFlagMaps.byUniversalIdentifier, + ).filter( + (pf): pf is FlatRolePermissionFlag => + isDefined(pf) && pf.roleUniversalIdentifier === roleUniversalIdentifier, + ); + + return resultFlags; + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/utils/__tests__/from-flat-role-permission-flag-to-role-permission-flag-dto.util.spec.ts b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/utils/__tests__/from-flat-role-permission-flag-to-role-permission-flag-dto.util.spec.ts new file mode 100644 index 0000000000..99891689e4 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/utils/__tests__/from-flat-role-permission-flag-to-role-permission-flag-dto.util.spec.ts @@ -0,0 +1,79 @@ +import { PermissionFlagType } from 'twenty-shared/constants'; + +import { type FlatPermissionFlagMaps } from 'src/engine/metadata-modules/flat-permission-flag/types/flat-permission-flag-maps.type'; +import { type FlatPermissionFlag } from 'src/engine/metadata-modules/flat-permission-flag/types/flat-permission-flag.type'; +import { type FlatRolePermissionFlag } from 'src/engine/metadata-modules/flat-role-permission-flag/types/flat-role-permission-flag.type'; +import { fromFlatRolePermissionFlagToRolePermissionFlagDto } from 'src/engine/metadata-modules/role-permission-flag/utils/from-flat-role-permission-flag-to-role-permission-flag-dto.util'; + +const buildFlatRolePermissionFlag = ( + overrides: Partial = {}, +): FlatRolePermissionFlag => + ({ + id: 'role-permission-flag-id', + roleId: 'role-id', + workspaceId: 'workspace-id', + applicationId: 'application-id', + applicationUniversalIdentifier: 'application-universal-id', + permissionFlagUniversalIdentifier: 'permission-flag-universal-id', + flag: PermissionFlagType.WORKSPACE, + ...overrides, + }) as FlatRolePermissionFlag; + +const buildFlatPermissionFlag = (): FlatPermissionFlag => ({ + id: 'permission-flag-id', + workspaceId: 'workspace-id', + applicationId: 'application-id', + universalIdentifier: 'permission-flag-universal-id', + applicationUniversalIdentifier: 'application-universal-id', + key: PermissionFlagType.WORKSPACE, + label: 'Workspace', + description: null, + icon: null, + permissionType: 'settings', + rolePermissionFlagIds: [], + rolePermissionFlagUniversalIdentifiers: [], + createdAt: '2026-05-13T00:00:00.000Z', + updatedAt: '2026-05-13T00:00:00.000Z', +}); + +const buildFlatPermissionFlagMaps = (): FlatPermissionFlagMaps => ({ + byUniversalIdentifier: { + 'permission-flag-universal-id': buildFlatPermissionFlag(), + }, + universalIdentifierById: { + 'permission-flag-id': 'permission-flag-universal-id', + }, + universalIdentifiersByApplicationId: {}, +}); + +describe('fromFlatRolePermissionFlagToRolePermissionFlagDto', () => { + it('maps role permission flags to the public permission flag DTO shape', () => { + expect( + fromFlatRolePermissionFlagToRolePermissionFlagDto( + buildFlatRolePermissionFlag(), + buildFlatPermissionFlagMaps(), + ), + ).toEqual({ + id: 'role-permission-flag-id', + roleId: 'role-id', + flag: PermissionFlagType.WORKSPACE, + }); + }); + + it('falls back to the legacy flag column when the catalog is missing the entry', () => { + expect( + fromFlatRolePermissionFlagToRolePermissionFlagDto( + buildFlatRolePermissionFlag(), + { + byUniversalIdentifier: {}, + universalIdentifierById: {}, + universalIdentifiersByApplicationId: {}, + }, + ), + ).toEqual({ + id: 'role-permission-flag-id', + roleId: 'role-id', + flag: PermissionFlagType.WORKSPACE, + }); + }); +}); diff --git a/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/utils/from-flat-role-permission-flag-to-role-permission-flag-dto.util.ts b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/utils/from-flat-role-permission-flag-to-role-permission-flag-dto.util.ts new file mode 100644 index 0000000000..874ed0f554 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/role-permission-flag/utils/from-flat-role-permission-flag-to-role-permission-flag-dto.util.ts @@ -0,0 +1,36 @@ +import { PermissionFlagType } from 'twenty-shared/constants'; +import { isDefined } from 'twenty-shared/utils'; + +import { type FlatPermissionFlagMaps } from 'src/engine/metadata-modules/flat-permission-flag/types/flat-permission-flag-maps.type'; +import { synthesizeFlatPermissionFlagFromFlag } from 'src/engine/metadata-modules/flat-permission-flag/utils/synthesize-flat-permission-flag-from-flag.util'; +import { type FlatRolePermissionFlag } from 'src/engine/metadata-modules/flat-role-permission-flag/types/flat-role-permission-flag.type'; +import { type RolePermissionFlagDTO } from 'src/engine/metadata-modules/role-permission-flag/dtos/role-permission-flag.dto'; + +export const fromFlatRolePermissionFlagToRolePermissionFlagDto = ( + flatRolePermissionFlag: FlatRolePermissionFlag, + flatPermissionFlagMaps: FlatPermissionFlagMaps, +): RolePermissionFlagDTO => { + const catalogPermissionFlag = isDefined( + flatRolePermissionFlag.permissionFlagUniversalIdentifier, + ) + ? flatPermissionFlagMaps.byUniversalIdentifier[ + flatRolePermissionFlag.permissionFlagUniversalIdentifier + ] + : undefined; + + const permissionFlag = isDefined(catalogPermissionFlag) + ? catalogPermissionFlag + : synthesizeFlatPermissionFlagFromFlag({ + flag: flatRolePermissionFlag.flag, + workspaceId: flatRolePermissionFlag.workspaceId, + applicationId: flatRolePermissionFlag.applicationId, + applicationUniversalIdentifier: + flatRolePermissionFlag.applicationUniversalIdentifier, + }); + + return { + id: flatRolePermissionFlag.id, + roleId: flatRolePermissionFlag.roleId, + flag: permissionFlag.key as PermissionFlagType, + }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/role/dtos/role.dto.ts b/packages/twenty-server/src/engine/metadata-modules/role/dtos/role.dto.ts index d3a39b4c6c..9380ec618d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/dtos/role.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/dtos/role.dto.ts @@ -7,7 +7,7 @@ import { WorkspaceMemberDTO } from 'src/engine/core-modules/user/dtos/workspace- import { AgentDTO } from 'src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto'; import { FieldPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/field-permission.dto'; import { ObjectPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/object-permission.dto'; -import { PermissionFlagDTO } from 'src/engine/metadata-modules/permission-flag/dtos/permission-flag.dto'; +import { RolePermissionFlagDTO } from 'src/engine/metadata-modules/role-permission-flag/dtos/role-permission-flag.dto'; import { type RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity'; import { RowLevelPermissionPredicateGroupDTO } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/row-level-permission-predicate-group.dto'; import { RowLevelPermissionPredicateDTO } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/row-level-permission-predicate.dto'; @@ -86,8 +86,8 @@ export class RoleDTO { @Field({ nullable: false }) canDestroyAllObjectRecords: boolean; - @Field(() => [PermissionFlagDTO], { nullable: true }) - permissionFlags?: PermissionFlagDTO[]; + @Field(() => [RolePermissionFlagDTO], { nullable: true }) + permissionFlags?: RolePermissionFlagDTO[]; @Field(() => [ObjectPermissionDTO], { nullable: true }) objectPermissions?: ObjectPermissionDTO[]; diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.entity.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.entity.ts index 932d118102..86c8699736 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.entity.ts @@ -11,7 +11,7 @@ import { import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity'; import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity'; -import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity'; +import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity'; import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.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'; @@ -81,10 +81,10 @@ export class RoleEntity extends SyncableEntity implements Required { objectPermissions: Relation; @OneToMany( - () => PermissionFlagEntity, - (permissionFlag: PermissionFlagEntity) => permissionFlag.role, + () => RolePermissionFlagEntity, + (rolePermissionFlag: RolePermissionFlagEntity) => rolePermissionFlag.role, ) - permissionFlags: Relation; + rolePermissionFlags: Relation; @OneToMany( () => FieldPermissionEntity, diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts index 5e9d9a5da5..eaed1e34a8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts @@ -15,8 +15,8 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity'; import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity'; import { ObjectPermissionModule } from 'src/engine/metadata-modules/object-permission/object-permission.module'; -import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity'; -import { PermissionFlagModule } from 'src/engine/metadata-modules/permission-flag/permission-flag.module'; +import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity'; +import { RolePermissionFlagModule } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.module'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; @@ -39,7 +39,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace RoleEntity, RoleTargetEntity, ObjectPermissionEntity, - PermissionFlagEntity, + RolePermissionFlagEntity, FieldPermissionEntity, UserWorkspaceEntity, ObjectMetadataEntity, @@ -52,7 +52,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace ApiKeyModule, PermissionsModule, ObjectPermissionModule, - PermissionFlagModule, + RolePermissionFlagModule, RowLevelPermissionModule, WorkspaceManyOrAllFlatEntityMapsCacheModule, WorkspaceMigrationModule, diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts index 0b4f756c8d..3b441c2499 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts @@ -33,16 +33,17 @@ import { AgentDTO } from 'src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto import { fromFlatAgentWithRoleIdToAgentDto } from 'src/engine/metadata-modules/flat-agent/utils/from-agent-entity-to-agent-dto.util'; import { FieldPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/field-permission.dto'; import { ObjectPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/object-permission.dto'; +import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; import { UpsertFieldPermissionsInput } from 'src/engine/metadata-modules/object-permission/dtos/upsert-field-permissions.input'; import { UpsertObjectPermissionsInput } from 'src/engine/metadata-modules/object-permission/dtos/upsert-object-permissions.input'; import { FieldPermissionService } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.service'; import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service'; import { fromFlatFieldPermissionToFieldPermissionDto } from 'src/engine/metadata-modules/object-permission/utils/from-flat-field-permission-to-field-permission-dto.util'; import { fromFlatObjectPermissionToObjectPermissionDto } from 'src/engine/metadata-modules/object-permission/utils/from-flat-object-permission-to-object-permission-dto.util'; -import { PermissionFlagDTO } from 'src/engine/metadata-modules/permission-flag/dtos/permission-flag.dto'; -import { UpsertPermissionFlagsInput } from 'src/engine/metadata-modules/permission-flag/dtos/upsert-permission-flag-input'; -import { PermissionFlagService } from 'src/engine/metadata-modules/permission-flag/permission-flag.service'; -import { fromFlatPermissionFlagToPermissionFlagDto } from 'src/engine/metadata-modules/permission-flag/utils/from-flat-permission-flag-to-permission-flag-dto.util'; +import { RolePermissionFlagDTO } from 'src/engine/metadata-modules/role-permission-flag/dtos/role-permission-flag.dto'; +import { UpsertPermissionFlagsInput } from 'src/engine/metadata-modules/role-permission-flag/dtos/upsert-permission-flags.input'; +import { RolePermissionFlagService } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.service'; +import { fromFlatRolePermissionFlagToRolePermissionFlagDto } from 'src/engine/metadata-modules/role-permission-flag/utils/from-flat-role-permission-flag-to-role-permission-flag-dto.util'; import { PermissionsException, PermissionsExceptionCode, @@ -87,7 +88,7 @@ export class RoleResolver { private readonly roleService: RoleService, private readonly userWorkspaceService: UserWorkspaceService, private readonly objectPermissionService: ObjectPermissionService, - private readonly settingPermissionService: PermissionFlagService, + private readonly rolePermissionFlagService: RolePermissionFlagService, private readonly agentRoleService: AiAgentRoleService, private readonly apiKeyRoleService: ApiKeyRoleService, private readonly fieldPermissionService: FieldPermissionService, @@ -95,6 +96,7 @@ export class RoleResolver { private readonly rowLevelPermissionPredicateService: RowLevelPermissionPredicateService, private readonly rowLevelPermissionPredicateGroupService: RowLevelPermissionPredicateGroupService, private readonly workspaceCacheService: WorkspaceCacheService, + private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, ) {} @Query(() => [RoleDTO]) @@ -223,18 +225,31 @@ export class RoleResolver { ); } - @Mutation(() => [PermissionFlagDTO]) + @Mutation(() => [RolePermissionFlagDTO]) async upsertPermissionFlags( @AuthWorkspace() workspace: WorkspaceEntity, @Args('upsertPermissionFlagsInput') upsertPermissionFlagsInput: UpsertPermissionFlagsInput, - ): Promise { - const flatPermissionFlags = - await this.settingPermissionService.upsertPermissionFlags({ + ): Promise { + const flatRolePermissionFlags = + await this.rolePermissionFlagService.upsertPermissionFlags({ workspaceId: workspace.id, input: upsertPermissionFlagsInput, }); - return flatPermissionFlags.map(fromFlatPermissionFlagToPermissionFlagDto); + const { flatPermissionFlagMaps } = + await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId: workspace.id, + flatMapsKeys: ['flatPermissionFlagMaps'], + }, + ); + + return flatRolePermissionFlags.map((flatRolePermissionFlag) => + fromFlatRolePermissionFlagToRolePermissionFlagDto( + flatRolePermissionFlag, + flatPermissionFlagMaps, + ), + ); } @Mutation(() => [FieldPermissionDTO]) diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts index 8f57ecebb9..53f045ab21 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts @@ -49,7 +49,9 @@ export class RoleService { }, relations: { roleTargets: true, - permissionFlags: true, + rolePermissionFlags: { + permissionFlag: true, + }, objectPermissions: true, fieldPermissions: true, }, @@ -67,7 +69,9 @@ export class RoleService { }, relations: { roleTargets: true, - permissionFlags: true, + rolePermissionFlags: { + permissionFlag: true, + }, objectPermissions: true, fieldPermissions: true, }, diff --git a/packages/twenty-server/src/engine/metadata-modules/role/services/__tests__/workspace-roles-permissions-cache.service.spec.ts b/packages/twenty-server/src/engine/metadata-modules/role/services/__tests__/workspace-roles-permissions-cache.service.spec.ts index e2b4610137..08a6a04936 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/services/__tests__/workspace-roles-permissions-cache.service.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/services/__tests__/workspace-roles-permissions-cache.service.spec.ts @@ -1,14 +1,17 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { PermissionFlagType } from 'twenty-shared/constants'; +import { + PermissionFlagType, + SystemPermissionFlag, +} from 'twenty-shared/constants'; import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; import { type Repository } from 'typeorm'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity'; import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity'; -import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity'; +import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; import { WorkspaceRolesPermissionsCacheService } from 'src/engine/metadata-modules/role/services/workspace-roles-permissions-cache.service'; import { RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity'; @@ -23,7 +26,7 @@ const PERSON_OBJECT_METADATA_ID = '44444444-4444-4444-8444-444444444444'; const createBaseRole = ( overrides: Partial & - Pick, + Pick, ): RoleEntity => ({ id: ROLE_ID, @@ -56,8 +59,8 @@ describe('WorkspaceRolesPermissionsCacheService', () => { let objectPermissionRepository: jest.Mocked< Pick, 'find'> >; - let permissionFlagRepository: jest.Mocked< - Pick, 'find'> + let rolePermissionFlagRepository: jest.Mocked< + Pick, 'find'> >; const workspaceObjectMetadataFixture: ObjectMetadataEntity[] = [ @@ -94,7 +97,7 @@ describe('WorkspaceRolesPermissionsCacheService', () => { find: jest.fn().mockResolvedValue([]), }; - permissionFlagRepository = { + rolePermissionFlagRepository = { find: jest.fn().mockResolvedValue([]), }; const fieldPermissionRepository = { @@ -123,8 +126,8 @@ describe('WorkspaceRolesPermissionsCacheService', () => { useValue: objectPermissionRepository, }, { - provide: getRepositoryToken(PermissionFlagEntity), - useValue: permissionFlagRepository, + provide: getRepositoryToken(RolePermissionFlagEntity), + useValue: rolePermissionFlagRepository, }, { provide: getRepositoryToken(FieldPermissionEntity), @@ -148,7 +151,7 @@ describe('WorkspaceRolesPermissionsCacheService', () => { it('should deny all record permissions when role has neither workspace members access nor update-all-settings', async () => { roleRepository.find.mockResolvedValue([ createBaseRole({ - permissionFlags: [], + rolePermissionFlags: [], objectPermissions: [], }), ]); @@ -164,16 +167,19 @@ describe('WorkspaceRolesPermissionsCacheService', () => { }); it('should grant all record permissions when role has WORKSPACE_MEMBERS permission flag', async () => { - permissionFlagRepository.find.mockResolvedValue([ + rolePermissionFlagRepository.find.mockResolvedValue([ { roleId: ROLE_ID, - flag: PermissionFlagType.WORKSPACE_MEMBERS, - } as PermissionFlagEntity, + permissionFlag: { + key: PermissionFlagType.WORKSPACE_MEMBERS, + universalIdentifier: SystemPermissionFlag.WORKSPACE_MEMBERS, + }, + } as RolePermissionFlagEntity, ]); roleRepository.find.mockResolvedValue([ createBaseRole({ - permissionFlags: [], + rolePermissionFlags: [], objectPermissions: [], }), ]); @@ -192,7 +198,7 @@ describe('WorkspaceRolesPermissionsCacheService', () => { roleRepository.find.mockResolvedValue([ createBaseRole({ canUpdateAllSettings: true, - permissionFlags: [], + rolePermissionFlags: [], objectPermissions: [], }), ]); @@ -210,7 +216,7 @@ describe('WorkspaceRolesPermissionsCacheService', () => { it('should deny all record permissions when role has neither workflows access nor update-all-settings', async () => { roleRepository.find.mockResolvedValue([ createBaseRole({ - permissionFlags: [], + rolePermissionFlags: [], objectPermissions: [], }), ]); @@ -225,16 +231,19 @@ describe('WorkspaceRolesPermissionsCacheService', () => { }); it('should grant all record permissions when role has WORKFLOWS permission flag', async () => { - permissionFlagRepository.find.mockResolvedValue([ + rolePermissionFlagRepository.find.mockResolvedValue([ { roleId: ROLE_ID, - flag: PermissionFlagType.WORKFLOWS, - } as PermissionFlagEntity, + permissionFlag: { + key: PermissionFlagType.WORKFLOWS, + universalIdentifier: SystemPermissionFlag.WORKFLOWS, + }, + } as RolePermissionFlagEntity, ]); roleRepository.find.mockResolvedValue([ createBaseRole({ - permissionFlags: [], + rolePermissionFlags: [], objectPermissions: [], }), ]); @@ -264,7 +273,7 @@ describe('WorkspaceRolesPermissionsCacheService', () => { roleRepository.find.mockResolvedValue([ createBaseRole({ - permissionFlags: [], + rolePermissionFlags: [], objectPermissions: [], }), ]); @@ -285,7 +294,7 @@ describe('WorkspaceRolesPermissionsCacheService', () => { canUpdateAllObjectRecords: true, canSoftDeleteAllObjectRecords: true, canDestroyAllObjectRecords: true, - permissionFlags: [], + rolePermissionFlags: [], objectPermissions: [], }), ]); diff --git a/packages/twenty-server/src/engine/metadata-modules/role/services/workspace-flat-role-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/role/services/workspace-flat-role-map-cache.service.ts index 92a3e5690f..054190ffb6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/services/workspace-flat-role-map-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/services/workspace-flat-role-map-cache.service.ts @@ -12,7 +12,7 @@ import { type FlatRole } from 'src/engine/metadata-modules/flat-role/types/flat- import { fromRoleEntityToFlatRole } from 'src/engine/metadata-modules/flat-role/utils/from-role-entity-to-flat-role.util'; import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity'; import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity'; -import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity'; +import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity'; import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.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'; @@ -36,8 +36,8 @@ export class WorkspaceFlatRoleMapCacheService extends WorkspaceCacheProvider< private readonly roleTargetRepository: Repository, @InjectRepository(ObjectPermissionEntity) private readonly objectPermissionRepository: Repository, - @InjectRepository(PermissionFlagEntity) - private readonly permissionFlagRepository: Repository, + @InjectRepository(RolePermissionFlagEntity) + private readonly rolePermissionFlagRepository: Repository, @InjectRepository(FieldPermissionEntity) private readonly fieldPermissionRepository: Repository, @InjectRepository(RowLevelPermissionPredicateEntity) @@ -56,7 +56,7 @@ export class WorkspaceFlatRoleMapCacheService extends WorkspaceCacheProvider< applications, roleTargets, objectPermissions, - permissionFlags, + rolePermissionFlags, fieldPermissions, rowLevelPermissionPredicates, rowLevelPermissionPredicateGroups, @@ -80,7 +80,7 @@ export class WorkspaceFlatRoleMapCacheService extends WorkspaceCacheProvider< select: ['id', 'universalIdentifier', 'roleId'], withDeleted: true, }), - this.permissionFlagRepository.find({ + this.rolePermissionFlagRepository.find({ where: { workspaceId }, select: ['id', 'universalIdentifier', 'roleId'], withDeleted: true, @@ -105,7 +105,7 @@ export class WorkspaceFlatRoleMapCacheService extends WorkspaceCacheProvider< const [ roleTargetsByRoleId, objectPermissionsByRoleId, - permissionFlagsByRoleId, + rolePermissionFlagsByRoleId, fieldPermissionsByRoleId, rowLevelPermissionPredicatesByRoleId, rowLevelPermissionPredicateGroupsByRoleId, @@ -120,7 +120,7 @@ export class WorkspaceFlatRoleMapCacheService extends WorkspaceCacheProvider< foreignKey: 'roleId', }, { - entities: permissionFlags, + entities: rolePermissionFlags, foreignKey: 'roleId', }, { @@ -149,7 +149,8 @@ export class WorkspaceFlatRoleMapCacheService extends WorkspaceCacheProvider< ...roleEntity, roleTargets: roleTargetsByRoleId.get(roleEntity.id) || [], objectPermissions: objectPermissionsByRoleId.get(roleEntity.id) || [], - permissionFlags: permissionFlagsByRoleId.get(roleEntity.id) || [], + rolePermissionFlags: + rolePermissionFlagsByRoleId.get(roleEntity.id) || [], fieldPermissions: fieldPermissionsByRoleId.get(roleEntity.id) || [], rowLevelPermissionPredicates: rowLevelPermissionPredicatesByRoleId.get(roleEntity.id) || [], diff --git a/packages/twenty-server/src/engine/metadata-modules/role/services/workspace-roles-permissions-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/role/services/workspace-roles-permissions-cache.service.ts index bdbec04685..3dec732c91 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/services/workspace-roles-permissions-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/services/workspace-roles-permissions-cache.service.ts @@ -1,7 +1,10 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { PermissionFlagType } from 'twenty-shared/constants'; +import { + PermissionFlagType, + SystemPermissionFlag, +} from 'twenty-shared/constants'; import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; import { type ObjectsPermissions, @@ -16,7 +19,7 @@ import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/wo import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity'; import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity'; -import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity'; +import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.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 { RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity'; @@ -41,8 +44,8 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide private readonly roleRepository: Repository, @InjectRepository(ObjectPermissionEntity) private readonly objectPermissionRepository: Repository, - @InjectRepository(PermissionFlagEntity) - private readonly permissionFlagRepository: Repository, + @InjectRepository(RolePermissionFlagEntity) + private readonly rolePermissionFlagRepository: Repository, @InjectRepository(FieldPermissionEntity) private readonly fieldPermissionRepository: Repository, @InjectRepository(RowLevelPermissionPredicateEntity) @@ -59,7 +62,7 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide const [ roles, objectPermissions, - permissionFlags, + rolePermissionFlags, fieldPermissions, rowLevelPermissionPredicates, rowLevelPermissionPredicateGroups, @@ -71,8 +74,9 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide this.objectPermissionRepository.find({ where: { workspaceId }, }), - this.permissionFlagRepository.find({ + this.rolePermissionFlagRepository.find({ where: { workspaceId }, + relations: ['permissionFlag'], }), this.fieldPermissionRepository.find({ where: { workspaceId }, @@ -91,9 +95,9 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide entities: objectPermissions, foreignKey: 'roleId', }); - const permissionFlagsByRoleId = - regroupEntitiesByRelatedEntityId<'permissionFlag'>({ - entities: permissionFlags, + const rolePermissionFlagsByRoleId = + regroupEntitiesByRelatedEntityId<'rolePermissionFlag'>({ + entities: rolePermissionFlags, foreignKey: 'roleId', }); const fieldPermissionsByRoleId = @@ -117,7 +121,8 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide for (const role of roles) { const roleObjectPermissions = objectPermissionsByRoleId.get(role.id) ?? []; - const rolePermissionFlags = permissionFlagsByRoleId.get(role.id) ?? []; + const roleRolePermissionFlags = + rolePermissionFlagsByRoleId.get(role.id) ?? []; const roleFieldPermissions = fieldPermissionsByRoleId.get(role.id) ?? []; const roleRowLevelPermissionPredicates = rowLevelPermissionPredicatesByRoleId.get(role.id) ?? []; @@ -150,7 +155,7 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide const hasWorkflowsPermissions = this.hasSettingsGatedObjectPermissions( role, - rolePermissionFlags, + roleRolePermissionFlags, PermissionFlagType.WORKFLOWS, ); @@ -163,7 +168,7 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide const hasWorkspaceMembersPermissions = this.hasSettingsGatedObjectPermissions( role, - rolePermissionFlags, + roleRolePermissionFlags, PermissionFlagType.WORKSPACE_MEMBERS, ); @@ -269,13 +274,18 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide private hasSettingsGatedObjectPermissions( role: RoleEntity, - permissionFlags: PermissionFlagEntity[], + rolePermissionFlags: RolePermissionFlagEntity[], permissionFlagType: PermissionFlagType, ): boolean { const hasPermissionFromRole = role.canUpdateAllSettings; + const permissionFlagUniversalIdentifier = + SystemPermissionFlag[permissionFlagType]; const hasPermissionFromSettingPermissions = isDefined( - permissionFlags.find( - (permissionFlag) => permissionFlag.flag === permissionFlagType, + rolePermissionFlags.find( + (rolePermissionFlag) => + (rolePermissionFlag.permissionFlag?.universalIdentifier ?? + SystemPermissionFlag[rolePermissionFlag.flag]) === + permissionFlagUniversalIdentifier, ), ); diff --git a/packages/twenty-server/src/engine/metadata-modules/role/utils/__tests__/fromRoleEntityToRoleDto.util.spec.ts b/packages/twenty-server/src/engine/metadata-modules/role/utils/__tests__/fromRoleEntityToRoleDto.util.spec.ts new file mode 100644 index 0000000000..dad31805ed --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/role/utils/__tests__/fromRoleEntityToRoleDto.util.spec.ts @@ -0,0 +1,65 @@ +import { PermissionFlagType } from 'twenty-shared/constants'; + +import { type RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; +import { fromRoleEntityToRoleDto } from 'src/engine/metadata-modules/role/utils/fromRoleEntityToRoleDto.util'; + +const buildRoleEntity = (overrides: Partial = {}): RoleEntity => + ({ + id: 'role-id', + label: 'Role', + universalIdentifier: 'role-universal-identifier', + canUpdateAllSettings: false, + canAccessAllTools: false, + description: null, + icon: null, + isEditable: true, + canReadAllObjectRecords: false, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canBeAssignedToUsers: true, + canBeAssignedToAgents: true, + canBeAssignedToApiKeys: true, + roleTargets: [], + objectPermissions: [], + fieldPermissions: [], + rolePermissionFlags: [], + ...overrides, + }) as RoleEntity; + +describe('fromRoleEntityToRoleDto', () => { + it('returns undefined permission flags when role permission flags are not loaded', () => { + const roleDto = fromRoleEntityToRoleDto( + buildRoleEntity({ + rolePermissionFlags: + undefined as unknown as RoleEntity['rolePermissionFlags'], + }), + ); + + expect(roleDto.permissionFlags).toBeUndefined(); + }); + + it('maps loaded role permission flags to the public permission flag DTO shape', () => { + const roleDto = fromRoleEntityToRoleDto( + buildRoleEntity({ + rolePermissionFlags: [ + { + id: 'role-permission-flag-id', + roleId: 'role-id', + permissionFlag: { + key: PermissionFlagType.WORKSPACE, + }, + }, + ] as RoleEntity['rolePermissionFlags'], + }), + ); + + expect(roleDto.permissionFlags).toEqual([ + { + id: 'role-permission-flag-id', + roleId: 'role-id', + flag: PermissionFlagType.WORKSPACE, + }, + ]); + }); +}); diff --git a/packages/twenty-server/src/engine/metadata-modules/role/utils/fromRoleEntityToRoleDto.util.ts b/packages/twenty-server/src/engine/metadata-modules/role/utils/fromRoleEntityToRoleDto.util.ts index a40b266b48..ade62e9c74 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/utils/fromRoleEntityToRoleDto.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/utils/fromRoleEntityToRoleDto.util.ts @@ -1,3 +1,5 @@ +import { PermissionFlagType } from 'twenty-shared/constants'; + import { type RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto'; import { type RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; @@ -19,7 +21,12 @@ export const fromRoleEntityToRoleDto = (role: RoleEntity): RoleDTO => { canBeAssignedToAgents: role.canBeAssignedToAgents, canBeAssignedToApiKeys: role.canBeAssignedToApiKeys, roleTargets: role.roleTargets, - permissionFlags: role.permissionFlags, + permissionFlags: role.rolePermissionFlags?.map((rolePermissionFlag) => ({ + id: rolePermissionFlag.id, + roleId: rolePermissionFlag.roleId, + flag: (rolePermissionFlag.permissionFlag?.key ?? + rolePermissionFlag.flag) as PermissionFlagType, + })), objectPermissions: role.objectPermissions, fieldPermissions: role.fieldPermissions, }; diff --git a/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts b/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts index 4f9c683ec7..319b9808d0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts @@ -108,7 +108,9 @@ export class UserRoleService { }, relations: { role: { - permissionFlags: true, + rolePermissionFlags: { + permissionFlag: true, + }, }, }, }); diff --git a/packages/twenty-server/src/engine/workspace-cache/types/workspace-cache-key.type.ts b/packages/twenty-server/src/engine/workspace-cache/types/workspace-cache-key.type.ts index 7a42b17f56..7171865187 100644 --- a/packages/twenty-server/src/engine/workspace-cache/types/workspace-cache-key.type.ts +++ b/packages/twenty-server/src/engine/workspace-cache/types/workspace-cache-key.type.ts @@ -35,7 +35,8 @@ export const WORKSPACE_CACHE_KEYS_V2 = { flatApplicationMaps: 'flat-maps:flatApplicationMaps', flatRoleMaps: 'flat-maps:role', flatRoleTargetMaps: 'flat-maps:role-target', - flatPermissionFlagMaps: 'flat-maps:permission-flag', + flatRolePermissionFlagMaps: 'flat-maps:role-permission-flag', + flatPermissionFlagMaps: 'flat-maps:permission-flag-catalog', flatObjectPermissionMaps: 'flat-maps:object-permission', flatFieldPermissionMaps: 'flat-maps:field-permission', ORMEntityMetadatas: 'orm:entity-metadatas', diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-all-metadata-name.constant.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-all-metadata-name.constant.ts index 927ef8e90b..3b700d3121 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-all-metadata-name.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-all-metadata-name.constant.ts @@ -10,6 +10,7 @@ export const TWENTY_STANDARD_ALL_METADATA_NAME = [ 'viewGroup', 'view', 'navigationMenuItem', + 'permissionFlag', 'role', 'agent', 'skill', diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/permission-flag/build-standard-flat-permission-flag-metadata-maps.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/permission-flag/build-standard-flat-permission-flag-metadata-maps.util.ts new file mode 100644 index 0000000000..90739077d6 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/permission-flag/build-standard-flat-permission-flag-metadata-maps.util.ts @@ -0,0 +1,41 @@ +import { v4 } from 'uuid'; + +import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant'; +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/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 { type FlatPermissionFlag } from 'src/engine/metadata-modules/flat-permission-flag/types/flat-permission-flag.type'; +import { STANDARD_PERMISSION_FLAG_DEFINITIONS } from 'src/engine/metadata-modules/permission-flag/constants/standard-permission-flag-definitions.constant'; +import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; + +export const buildStandardFlatPermissionFlagMetadataMaps = ({ + now, + workspaceId, + twentyStandardApplicationId, +}: { + now: string; + workspaceId: string; + twentyStandardApplicationId: string; +}): FlatEntityMaps => { + let flatPermissionFlagMaps = createEmptyFlatEntityMaps(); + + for (const permissionFlagDefinition of STANDARD_PERMISSION_FLAG_DEFINITIONS) { + flatPermissionFlagMaps = addFlatEntityToFlatEntityMapsOrThrow({ + flatEntity: { + id: v4(), + ...permissionFlagDefinition, + workspaceId, + applicationId: twentyStandardApplicationId, + universalIdentifier: permissionFlagDefinition.universalIdentifier, + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION.universalIdentifier, + rolePermissionFlagIds: [], + rolePermissionFlagUniversalIdentifiers: [], + createdAt: now, + updatedAt: now, + }, + flatEntityMaps: flatPermissionFlagMaps, + }); + } + + return flatPermissionFlagMaps; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/role-metadata/create-standard-role-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/role-metadata/create-standard-role-flat-metadata.util.ts index 26d3c04405..f7553dd05a 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/role-metadata/create-standard-role-flat-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/role-metadata/create-standard-role-flat-metadata.util.ts @@ -72,8 +72,8 @@ export const createStandardRoleFlatMetadata = ({ TWENTY_STANDARD_APPLICATION.universalIdentifier, createdAt: now, updatedAt: now, - permissionFlagIds: [], - permissionFlagUniversalIdentifiers: [], + rolePermissionFlagIds: [], + rolePermissionFlagUniversalIdentifiers: [], objectPermissionUniversalIdentifiers: [], fieldPermissionIds: [], fieldPermissionUniversalIdentifiers: [], diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant.ts index d5769b7e5f..89134ed89c 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant.ts @@ -15,6 +15,7 @@ import { buildStandardFlatObjectMetadataMaps } from 'src/engine/workspace-manage import { buildStandardFlatPageLayoutTabMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/build-standard-flat-page-layout-tab-metadata-maps.util'; import { buildStandardFlatPageLayoutWidgetMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/build-standard-flat-page-layout-widget-metadata-maps.util'; import { buildStandardFlatPageLayoutMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout/build-standard-flat-page-layout-metadata-maps.util'; +import { buildStandardFlatPermissionFlagMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/permission-flag/build-standard-flat-permission-flag-metadata-maps.util'; import { buildStandardFlatRoleMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/role-metadata/build-standard-flat-role-metadata-maps.util'; import { buildStandardFlatSkillMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/build-standard-flat-skill-metadata-maps.util'; import { buildStandardFlatViewFieldMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/view-field/build-standard-flat-view-field-metadata-maps.util'; @@ -140,6 +141,12 @@ export const computeTwentyStandardApplicationAllFlatEntityMaps = ({ dependencyFlatEntityMaps: undefined, }); + const flatPermissionFlagMaps = buildStandardFlatPermissionFlagMetadataMaps({ + now, + workspaceId, + twentyStandardApplicationId, + }); + const flatAgentMaps = buildStandardFlatAgentMetadataMaps({ now, workspaceId, @@ -213,6 +220,7 @@ export const computeTwentyStandardApplicationAllFlatEntityMaps = ({ flatFieldMetadataMaps, flatObjectMetadataMaps, flatNavigationMenuItemMaps, + flatPermissionFlagMaps, flatRoleMaps, flatAgentMaps, flatSkillMaps, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/utils/enrich-create-workspace-migration-action-with-ids.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/utils/enrich-create-workspace-migration-action-with-ids.util.ts index e90bcc6f69..591038de9e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/utils/enrich-create-workspace-migration-action-with-ids.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/utils/enrich-create-workspace-migration-action-with-ids.util.ts @@ -167,6 +167,7 @@ export const enrichCreateWorkspaceMigrationActionsWithIds = ({ case 'navigationMenuItem': case 'frontComponent': case 'viewSort': + case 'rolePermissionFlag': case 'permissionFlag': case 'objectPermission': case 'fieldPermission': diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-build-orchestrator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-build-orchestrator.service.ts index 9dcf7c0543..b7e45136bb 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-build-orchestrator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-build-orchestrator.service.ts @@ -29,6 +29,7 @@ import { WorkspaceMigrationPageLayoutTabActionsBuilderService } from 'src/engine import { WorkspaceMigrationPageLayoutWidgetActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/page-layout-widget/workspace-migration-page-layout-widget-actions-builder.service'; import { WorkspaceMigrationPageLayoutActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/page-layout/workspace-migration-page-layout-actions-builder.service'; import { WorkspaceMigrationPermissionFlagActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/permission-flag/workspace-migration-permission-flag-actions-builder.service'; +import { WorkspaceMigrationRolePermissionFlagActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-permission-flag/workspace-migration-role-permission-flag-actions-builder.service'; import { WorkspaceMigrationRoleTargetActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-target/workspace-migration-role-target-actions-builder.service'; import { WorkspaceMigrationRoleActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role/workspace-migration-role-actions-builder.service'; import { WorkspaceMigrationRowLevelPermissionPredicateGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/row-level-permission-predicate-group/workspace-migration-row-level-permission-predicate-group-actions-builder.service'; @@ -58,6 +59,7 @@ export class WorkspaceMigrationBuildOrchestratorService { private readonly workspaceMigrationViewSortActionsBuilderService: WorkspaceMigrationViewSortActionsBuilderService, private readonly workspaceMigrationFieldPermissionActionsBuilderService: WorkspaceMigrationFieldPermissionActionsBuilderService, private readonly workspaceMigrationObjectPermissionActionsBuilderService: WorkspaceMigrationObjectPermissionActionsBuilderService, + private readonly workspaceMigrationRolePermissionFlagActionsBuilderService: WorkspaceMigrationRolePermissionFlagActionsBuilderService, private readonly workspaceMigrationPermissionFlagActionsBuilderService: WorkspaceMigrationPermissionFlagActionsBuilderService, private readonly workspaceMigrationLogicFunctionActionsBuilderService: WorkspaceMigrationLogicFunctionActionsBuilderService, private readonly workspaceMigrationRoleTargetActionsBuilderService: WorkspaceMigrationRoleTargetActionsBuilderService, @@ -158,6 +160,7 @@ export class WorkspaceMigrationBuildOrchestratorService { flatRoleMaps, flatObjectPermissionMaps, flatFieldPermissionMaps, + flatRolePermissionFlagMaps, flatPermissionFlagMaps, flatRoleTargetMaps, flatAgentMaps, @@ -558,6 +561,34 @@ export class WorkspaceMigrationBuildOrchestratorService { } } + if (isDefined(flatRolePermissionFlagMaps)) { + const { + from: fromFlatRolePermissionFlagMaps, + to: toFlatRolePermissionFlagMaps, + } = flatRolePermissionFlagMaps; + + const rolePermissionFlagResult = + await this.workspaceMigrationRolePermissionFlagActionsBuilderService.validateAndBuild( + { + additionalCacheDataMaps, + from: fromFlatRolePermissionFlagMaps, + to: toFlatRolePermissionFlagMaps, + buildOptions, + dependencyOptimisticFlatEntityMaps: optimisticAllFlatEntityMaps, + workspaceId, + }, + ); + + if (rolePermissionFlagResult.status === 'fail') { + orchestratorFailureReport.rolePermissionFlag.push( + ...rolePermissionFlagResult.errors, + ); + } else { + orchestratorActionsReport.rolePermissionFlag = + rolePermissionFlagResult.actions; + } + } + if (isDefined(flatPermissionFlagMaps)) { const { from: fromFlatPermissionFlagMaps, to: toFlatPermissionFlagMaps } = flatPermissionFlagMaps; @@ -986,6 +1017,12 @@ export class WorkspaceMigrationBuildOrchestratorService { /// // Permission flags + ...aggregatedOrchestratorActionsReport.rolePermissionFlag.delete, + ...aggregatedOrchestratorActionsReport.rolePermissionFlag.create, + ...aggregatedOrchestratorActionsReport.rolePermissionFlag.update, + /// + + // Permission flag definitions ...aggregatedOrchestratorActionsReport.permissionFlag.delete, ...aggregatedOrchestratorActionsReport.permissionFlag.create, ...aggregatedOrchestratorActionsReport.permissionFlag.update, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/__tests__/__snapshots__/all-universal-flat-entity-foreign-key-aggregator-properties.constant.spec.ts.snap b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/__tests__/__snapshots__/all-universal-flat-entity-foreign-key-aggregator-properties.constant.spec.ts.snap index b4770070c2..3ef9799ef5 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/__tests__/__snapshots__/all-universal-flat-entity-foreign-key-aggregator-properties.constant.spec.ts.snap +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/__tests__/__snapshots__/all-universal-flat-entity-foreign-key-aggregator-properties.constant.spec.ts.snap @@ -35,15 +35,18 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES should matc "widgetUniversalIdentifiers", ], "pageLayoutWidget": [], - "permissionFlag": [], + "permissionFlag": [ + "rolePermissionFlagUniversalIdentifiers", + ], "role": [ "roleTargetUniversalIdentifiers", "objectPermissionUniversalIdentifiers", - "permissionFlagUniversalIdentifiers", + "rolePermissionFlagUniversalIdentifiers", "fieldPermissionUniversalIdentifiers", "rowLevelPermissionPredicateUniversalIdentifiers", "rowLevelPermissionPredicateGroupUniversalIdentifiers", ], + "rolePermissionFlag": [], "roleTarget": [], "rowLevelPermissionPredicate": [], "rowLevelPermissionPredicateGroup": [ diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-jsonb-properties-with-serialized-relation-by-metadata-name.constant.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-jsonb-properties-with-serialized-relation-by-metadata-name.constant.ts index e8c1635a4c..01b8d0e4b5 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-jsonb-properties-with-serialized-relation-by-metadata-name.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-jsonb-properties-with-serialized-relation-by-metadata-name.constant.ts @@ -38,6 +38,7 @@ export const ALL_JSONB_PROPERTIES_WITH_SERIALIZED_RELATION_BY_METADATA_NAME = { }, commandMenuItem: {}, navigationMenuItem: {}, + rolePermissionFlag: {}, permissionFlag: {}, objectPermission: {}, fieldPermission: {}, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-role-permission-flag.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-role-permission-flag.type.ts new file mode 100644 index 0000000000..479db0a96f --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-role-permission-flag.type.ts @@ -0,0 +1,7 @@ +import { type RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity'; +import { type UniversalFlatEntityFrom } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type'; + +export type UniversalFlatRolePermissionFlag = UniversalFlatEntityFrom< + RolePermissionFlagEntity, + 'rolePermissionFlag' +>; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/permission-flag/workspace-migration-permission-flag-actions-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/permission-flag/workspace-migration-permission-flag-actions-builder.service.ts index d7d19c4419..4acaa6e587 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/permission-flag/workspace-migration-permission-flag-actions-builder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/permission-flag/workspace-migration-permission-flag-actions-builder.service.ts @@ -2,12 +2,12 @@ import { Injectable } from '@nestjs/common'; import { ALL_METADATA_NAME } from 'twenty-shared/metadata'; +import { type UniversalUpdatePermissionFlagAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/permission-flag/types/workspace-migration-permission-flag-action.type'; +import { type FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type'; +import { type UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type'; +import { type UniversalFlatEntityValidationReturnType } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-result.type'; import { WorkspaceEntityMigrationBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/services/workspace-entity-migration-builder.service'; -import { UniversalUpdatePermissionFlagAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/permission-flag/types/workspace-migration-permission-flag-action.type'; import { FlatPermissionFlagValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-permission-flag-validator.service'; -import { UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type'; -import { UniversalFlatEntityValidationReturnType } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-result.type'; -import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type'; @Injectable() export class WorkspaceMigrationPermissionFlagActionsBuilderService extends WorkspaceEntityMigrationBuilderService< @@ -105,7 +105,7 @@ export class WorkspaceMigrationPermissionFlagActionsBuilderService extends Works const { universalIdentifier, flatEntityUpdate } = args; - const updatePermissionFlagAction: UniversalUpdatePermissionFlagAction = { + const updateAction: UniversalUpdatePermissionFlagAction = { type: 'update', metadataName: 'permissionFlag', universalIdentifier, @@ -114,7 +114,7 @@ export class WorkspaceMigrationPermissionFlagActionsBuilderService extends Works return { status: 'success', - action: updatePermissionFlagAction, + action: updateAction, }; } } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-permission-flag/types/workspace-migration-role-permission-flag-action.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-permission-flag/types/workspace-migration-role-permission-flag-action.type.ts new file mode 100644 index 0000000000..ede609a2b9 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-permission-flag/types/workspace-migration-role-permission-flag-action.type.ts @@ -0,0 +1,24 @@ +import { type BaseFlatCreateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-create-workspace-migration-action.type'; +import { type BaseFlatDeleteWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-delete-workspace-migration-action.type'; +import { type BaseFlatUpdateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-update-workspace-migration-action.type'; +import { type BaseUniversalCreateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-create-workspace-migration-action.type'; +import { type BaseUniversalDeleteWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-delete-workspace-migration-action.type'; +import { type BaseUniversalUpdateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-update-workspace-migration-action.type'; + +export type FlatCreateRolePermissionFlagAction = + BaseFlatCreateWorkspaceMigrationAction<'rolePermissionFlag'>; + +export type FlatUpdateRolePermissionFlagAction = + BaseFlatUpdateWorkspaceMigrationAction<'rolePermissionFlag'>; + +export type FlatDeleteRolePermissionFlagAction = + BaseFlatDeleteWorkspaceMigrationAction<'rolePermissionFlag'>; + +export type UniversalCreateRolePermissionFlagAction = + BaseUniversalCreateWorkspaceMigrationAction<'rolePermissionFlag'>; + +export type UniversalUpdateRolePermissionFlagAction = + BaseUniversalUpdateWorkspaceMigrationAction<'rolePermissionFlag'>; + +export type UniversalDeleteRolePermissionFlagAction = + BaseUniversalDeleteWorkspaceMigrationAction<'rolePermissionFlag'>; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-permission-flag/workspace-migration-role-permission-flag-actions-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-permission-flag/workspace-migration-role-permission-flag-actions-builder.service.ts new file mode 100644 index 0000000000..1ecc33c0a3 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-permission-flag/workspace-migration-role-permission-flag-actions-builder.service.ts @@ -0,0 +1,122 @@ +import { Injectable } from '@nestjs/common'; + +import { ALL_METADATA_NAME } from 'twenty-shared/metadata'; + +import { WorkspaceEntityMigrationBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/services/workspace-entity-migration-builder.service'; +import { UniversalUpdateRolePermissionFlagAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-permission-flag/types/workspace-migration-role-permission-flag-action.type'; +import { FlatRolePermissionFlagValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-role-permission-flag-validator.service'; +import { UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type'; +import { UniversalFlatEntityValidationReturnType } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-result.type'; +import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type'; + +@Injectable() +export class WorkspaceMigrationRolePermissionFlagActionsBuilderService extends WorkspaceEntityMigrationBuilderService< + typeof ALL_METADATA_NAME.rolePermissionFlag +> { + constructor( + private readonly flatRolePermissionFlagValidatorService: FlatRolePermissionFlagValidatorService, + ) { + super(ALL_METADATA_NAME.rolePermissionFlag); + } + + protected validateFlatEntityCreation( + args: UniversalFlatEntityValidationArgs< + typeof ALL_METADATA_NAME.rolePermissionFlag + >, + ): UniversalFlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.rolePermissionFlag, + 'create' + > { + const validationResult = + this.flatRolePermissionFlagValidatorService.validateFlatRolePermissionFlagCreation( + args, + ); + + if (validationResult.errors.length > 0) { + return { + status: 'fail', + ...validationResult, + }; + } + + const { flatEntityToValidate: flatRolePermissionFlagToValidate } = args; + + return { + status: 'success', + action: { + type: 'create', + metadataName: 'rolePermissionFlag', + flatEntity: flatRolePermissionFlagToValidate, + }, + }; + } + + protected validateFlatEntityDeletion( + args: UniversalFlatEntityValidationArgs< + typeof ALL_METADATA_NAME.rolePermissionFlag + >, + ): UniversalFlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.rolePermissionFlag, + 'delete' + > { + const validationResult = + this.flatRolePermissionFlagValidatorService.validateFlatRolePermissionFlagDeletion( + args, + ); + + if (validationResult.errors.length > 0) { + return { + status: 'fail', + ...validationResult, + }; + } + + const { flatEntityToValidate: flatRolePermissionFlagToValidate } = args; + + return { + status: 'success', + action: { + type: 'delete', + metadataName: 'rolePermissionFlag', + universalIdentifier: + flatRolePermissionFlagToValidate.universalIdentifier, + }, + }; + } + + protected validateFlatEntityUpdate( + args: FlatEntityUpdateValidationArgs< + typeof ALL_METADATA_NAME.rolePermissionFlag + >, + ): UniversalFlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.rolePermissionFlag, + 'update' + > { + const validationResult = + this.flatRolePermissionFlagValidatorService.validateFlatRolePermissionFlagUpdate( + args, + ); + + if (validationResult.errors.length > 0) { + return { + status: 'fail', + ...validationResult, + }; + } + + const { universalIdentifier, flatEntityUpdate } = args; + + const updateRolePermissionFlagAction: UniversalUpdateRolePermissionFlagAction = + { + type: 'update', + metadataName: 'rolePermissionFlag', + universalIdentifier, + update: flatEntityUpdate, + }; + + return { + status: 'success', + action: updateRolePermissionFlagAction, + }; + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/__tests__/flat-permission-flag-validator.service.spec.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/__tests__/flat-permission-flag-validator.service.spec.ts new file mode 100644 index 0000000000..8fa457234a --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/__tests__/flat-permission-flag-validator.service.spec.ts @@ -0,0 +1,381 @@ +import { Test, type TestingModule } from '@nestjs/testing'; + +import { PermissionFlagType } from 'twenty-shared/constants'; +import { ALL_METADATA_NAME } from 'twenty-shared/metadata'; + +import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant'; +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { type FlatRolePermissionFlag } from 'src/engine/metadata-modules/flat-role-permission-flag/types/flat-role-permission-flag.type'; +import { type FlatPermissionFlag } from 'src/engine/metadata-modules/flat-permission-flag/types/flat-permission-flag.type'; +import { type PermissionFlagPermissionType } from 'src/engine/metadata-modules/permission-flag/constants/permission-flag-permission-type.constant'; +import { PermissionFlagExceptionCode } from 'src/engine/metadata-modules/permission-flag/permission-flag.exception'; +import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; +import { FlatPermissionFlagValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-permission-flag-validator.service'; + +const buildFlatDefinition = ( + overrides: Partial = {}, +): FlatPermissionFlag => + ({ + id: '00000000-0000-0000-0000-000000000001', + universalIdentifier: '00000000-0000-0000-0000-000000000001', + key: 'TEST_FLAG', + label: 'Test Flag', + description: 'A flag for tests', + icon: 'IconTest', + permissionType: 'tool', + workspaceId: 'workspace-id', + applicationId: '00000000-0000-0000-0000-000000000aaa', + applicationUniversalIdentifier: '00000000-0000-0000-0000-000000000aaa', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + ...overrides, + }) as unknown as FlatPermissionFlag; + +const buildEmptyMaps = (): FlatEntityMaps => + createEmptyFlatEntityMaps() as unknown as FlatEntityMaps; + +const buildEmptyRolePermissionFlagMaps = + (): FlatEntityMaps => + createEmptyFlatEntityMaps() as unknown as FlatEntityMaps; + +const buildFlatRolePermissionFlag = ( + overrides: Partial = {}, +): FlatRolePermissionFlag => + ({ + id: '00000000-0000-0000-0000-000000000101', + universalIdentifier: '00000000-0000-0000-0000-000000000101', + permissionFlagId: '00000000-0000-0000-0000-000000000001', + permissionFlagUniversalIdentifier: '00000000-0000-0000-0000-000000000001', + roleUniversalIdentifier: '00000000-0000-0000-0000-000000000201', + workspaceId: 'workspace-id', + applicationId: '00000000-0000-0000-0000-000000000aaa', + applicationUniversalIdentifier: '00000000-0000-0000-0000-000000000aaa', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + ...overrides, + }) as unknown as FlatRolePermissionFlag; + +const buildArgs = ( + flatEntityToValidate: FlatPermissionFlag, + optimisticMaps: FlatEntityMaps = buildEmptyMaps(), +) => + ({ + flatEntityToValidate, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatPermissionFlagMaps: optimisticMaps, + }, + buildOptions: {} as never, + }) as unknown as Parameters< + FlatPermissionFlagValidatorService['validateFlatPermissionFlagCreation'] + >[0]; + +describe('FlatPermissionFlagValidatorService', () => { + let service: FlatPermissionFlagValidatorService; + + beforeEach(async () => { + const moduleRef: TestingModule = await Test.createTestingModule({ + providers: [FlatPermissionFlagValidatorService], + }).compile(); + + service = moduleRef.get(FlatPermissionFlagValidatorService); + }); + + describe('validateFlatPermissionFlagCreation', () => { + it('passes for a valid definition', () => { + const result = service.validateFlatPermissionFlagCreation( + buildArgs(buildFlatDefinition()), + ); + + expect(result.errors).toHaveLength(0); + }); + + it('rejects an empty key', () => { + const result = service.validateFlatPermissionFlagCreation( + buildArgs(buildFlatDefinition({ key: '' })), + ); + + expect(result.errors.map((error) => error.code)).toEqual([ + PermissionFlagExceptionCode.INVALID_PERMISSION_FLAG_KEY, + ]); + }); + + it('rejects an unknown permission type', () => { + const result = service.validateFlatPermissionFlagCreation( + buildArgs( + buildFlatDefinition({ + permissionType: 'invalid' as PermissionFlagPermissionType, + }), + ), + ); + + expect(result.errors.map((error) => error.code)).toEqual([ + PermissionFlagExceptionCode.INVALID_PERMISSION_FLAG_PERMISSION_TYPE, + ]); + }); + + it('rejects a duplicate key in the same workspace', () => { + const existing = buildFlatDefinition({ + universalIdentifier: '00000000-0000-0000-0000-000000000999', + key: 'EXISTING_KEY', + }); + const optimisticMaps = buildEmptyMaps(); + optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] = + existing; + + const result = service.validateFlatPermissionFlagCreation( + buildArgs(buildFlatDefinition({ key: 'EXISTING_KEY' }), optimisticMaps), + ); + + expect(result.errors.map((error) => error.code)).toEqual([ + PermissionFlagExceptionCode.PERMISSION_FLAG_ALREADY_EXISTS, + ]); + }); + + it('rejects a duplicate universal identifier', () => { + const existing = buildFlatDefinition(); + const optimisticMaps = buildEmptyMaps(); + optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] = + existing; + + const result = service.validateFlatPermissionFlagCreation( + buildArgs( + buildFlatDefinition({ + id: '00000000-0000-0000-0000-000000000002', + key: 'ANOTHER_TEST_FLAG', + }), + optimisticMaps, + ), + ); + + expect(result.errors.map((error) => error.code)).toEqual([ + PermissionFlagExceptionCode.PERMISSION_FLAG_ALREADY_EXISTS, + ]); + }); + }); + + describe('validateFlatPermissionFlagUpdate', () => { + it('passes for a valid update', () => { + const existing = buildFlatDefinition(); + const optimisticMaps = buildEmptyMaps(); + optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] = + existing; + + const result = service.validateFlatPermissionFlagUpdate({ + universalIdentifier: existing.universalIdentifier, + flatEntityUpdate: { label: 'Updated Label' }, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatPermissionFlagMaps: optimisticMaps, + }, + buildOptions: {} as never, + } as unknown as Parameters< + FlatPermissionFlagValidatorService['validateFlatPermissionFlagUpdate'] + >[0]); + + expect(result.errors).toHaveLength(0); + }); + + it('rejects updating the immutable key field', () => { + const existing = buildFlatDefinition({ key: 'ORIGINAL_KEY' }); + const optimisticMaps = buildEmptyMaps(); + optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] = + existing; + + const result = service.validateFlatPermissionFlagUpdate({ + universalIdentifier: existing.universalIdentifier, + flatEntityUpdate: { key: 'NEW_KEY' }, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatPermissionFlagMaps: optimisticMaps, + }, + buildOptions: {} as never, + } as unknown as Parameters< + FlatPermissionFlagValidatorService['validateFlatPermissionFlagUpdate'] + >[0]); + + expect(result.errors.map((error) => error.code)).toEqual([ + PermissionFlagExceptionCode.PERMISSION_FLAG_KEY_IMMUTABLE, + ]); + }); + + it('returns not-found if no existing definition matches the universalIdentifier', () => { + const result = service.validateFlatPermissionFlagUpdate({ + universalIdentifier: '00000000-0000-0000-0000-deadbeefdead', + flatEntityUpdate: { label: 'New Label' }, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatPermissionFlagMaps: buildEmptyMaps(), + }, + buildOptions: {} as never, + } as unknown as Parameters< + FlatPermissionFlagValidatorService['validateFlatPermissionFlagUpdate'] + >[0]); + + expect(result.errors.map((error) => error.code)).toEqual([ + PermissionFlagExceptionCode.PERMISSION_FLAG_NOT_FOUND, + ]); + }); + + it('rejects updating a standard definition from a custom application', () => { + const existing = buildFlatDefinition({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION.universalIdentifier, + }); + const optimisticMaps = buildEmptyMaps(); + optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] = + existing; + + const result = service.validateFlatPermissionFlagUpdate({ + universalIdentifier: existing.universalIdentifier, + flatEntityUpdate: { label: 'Updated Label' }, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatPermissionFlagMaps: optimisticMaps, + }, + buildOptions: { + isSystemBuild: false, + applicationUniversalIdentifier: + '00000000-0000-0000-0000-000000000aaa', + }, + } as unknown as Parameters< + FlatPermissionFlagValidatorService['validateFlatPermissionFlagUpdate'] + >[0]); + + expect(result.errors.map((error) => error.code)).toEqual([ + PermissionFlagExceptionCode.PERMISSION_FLAG_IS_STANDARD, + ]); + }); + + it('rejects updating to an unknown permission type', () => { + const existing = buildFlatDefinition(); + const optimisticMaps = buildEmptyMaps(); + optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] = + existing; + + const result = service.validateFlatPermissionFlagUpdate({ + universalIdentifier: existing.universalIdentifier, + flatEntityUpdate: { + permissionType: 'invalid' as PermissionFlagPermissionType, + }, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatPermissionFlagMaps: optimisticMaps, + }, + buildOptions: {} as never, + } as unknown as Parameters< + FlatPermissionFlagValidatorService['validateFlatPermissionFlagUpdate'] + >[0]); + + expect(result.errors.map((error) => error.code)).toEqual([ + PermissionFlagExceptionCode.INVALID_PERMISSION_FLAG_PERMISSION_TYPE, + ]); + }); + }); + + describe('validateFlatPermissionFlagDeletion', () => { + it('passes for a valid deletion', () => { + const existing = buildFlatDefinition(); + const optimisticMaps = buildEmptyMaps(); + optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] = + existing; + + const result = service.validateFlatPermissionFlagDeletion({ + flatEntityToValidate: existing, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatPermissionFlagMaps: optimisticMaps, + flatRolePermissionFlagMaps: buildEmptyRolePermissionFlagMaps(), + }, + buildOptions: {} as never, + } as unknown as Parameters< + FlatPermissionFlagValidatorService['validateFlatPermissionFlagDeletion'] + >[0]); + + expect(result.errors).toHaveLength(0); + }); + + it('returns not-found if no existing definition matches the universalIdentifier', () => { + const result = service.validateFlatPermissionFlagDeletion({ + flatEntityToValidate: buildFlatDefinition({ + universalIdentifier: '00000000-0000-0000-0000-deadbeefdead', + }), + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatPermissionFlagMaps: buildEmptyMaps(), + flatRolePermissionFlagMaps: buildEmptyRolePermissionFlagMaps(), + }, + buildOptions: {} as never, + } as unknown as Parameters< + FlatPermissionFlagValidatorService['validateFlatPermissionFlagDeletion'] + >[0]); + + expect(result.errors.map((error) => error.code)).toEqual([ + PermissionFlagExceptionCode.PERMISSION_FLAG_NOT_FOUND, + ]); + }); + + it('rejects deleting a standard definition from a custom application', () => { + const existing = buildFlatDefinition({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION.universalIdentifier, + }); + const optimisticMaps = buildEmptyMaps(); + optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] = + existing; + + const result = service.validateFlatPermissionFlagDeletion({ + flatEntityToValidate: existing, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatPermissionFlagMaps: optimisticMaps, + flatRolePermissionFlagMaps: buildEmptyRolePermissionFlagMaps(), + }, + buildOptions: { + isSystemBuild: false, + applicationUniversalIdentifier: + '00000000-0000-0000-0000-000000000aaa', + }, + } as unknown as Parameters< + FlatPermissionFlagValidatorService['validateFlatPermissionFlagDeletion'] + >[0]); + + expect(result.errors.map((error) => error.code)).toEqual([ + PermissionFlagExceptionCode.PERMISSION_FLAG_IS_STANDARD, + ]); + }); + + it('rejects deleting a definition while roles still grant its key', () => { + const existing = buildFlatDefinition({ + key: PermissionFlagType.WORKSPACE, + }); + const optimisticMaps = buildEmptyMaps(); + optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] = + existing; + + const rolePermissionFlagMaps = buildEmptyRolePermissionFlagMaps(); + const rolePermissionFlag = buildFlatRolePermissionFlag({ + permissionFlagId: existing.id, + permissionFlagUniversalIdentifier: existing.universalIdentifier, + }); + rolePermissionFlagMaps.byUniversalIdentifier[ + rolePermissionFlag.universalIdentifier + ] = rolePermissionFlag; + + const result = service.validateFlatPermissionFlagDeletion({ + flatEntityToValidate: existing, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatPermissionFlagMaps: optimisticMaps, + flatRolePermissionFlagMaps: rolePermissionFlagMaps, + }, + buildOptions: { + isSystemBuild: false, + applicationUniversalIdentifier: + '00000000-0000-0000-0000-000000000aaa', + }, + } as unknown as Parameters< + FlatPermissionFlagValidatorService['validateFlatPermissionFlagDeletion'] + >[0]); + + expect(result.errors.map((error) => error.code)).toEqual([ + PermissionFlagExceptionCode.PERMISSION_FLAG_IN_USE, + ]); + }); + }); + + // ALL_METADATA_NAME is referenced as a sanity check the metadata name exists + it('uses the registered ALL_METADATA_NAME entry', () => { + expect(ALL_METADATA_NAME.permissionFlag).toBe('permissionFlag'); + }); +}); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-permission-flag-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-permission-flag-validator.service.ts index 1bcf3a4290..3ae08cf083 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-permission-flag-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-permission-flag-validator.service.ts @@ -1,17 +1,19 @@ import { Injectable } from '@nestjs/common'; import { msg, t } from '@lingui/core/macro'; -import { PermissionFlagType } from 'twenty-shared/constants'; +import { isNonEmptyString } from '@sniptt/guards'; import { ALL_METADATA_NAME } from 'twenty-shared/metadata'; import { isDefined } from 'twenty-shared/utils'; import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util'; -import { PermissionsExceptionCode } from 'src/engine/metadata-modules/permissions/permissions.exception'; -import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type'; +import { PERMISSION_FLAG_PERMISSION_TYPES } from 'src/engine/metadata-modules/permission-flag/constants/permission-flag-permission-type.constant'; +import { PermissionFlagExceptionCode } from 'src/engine/metadata-modules/permission-flag/permission-flag.exception'; +import { belongsToTwentyStandardApp } from 'src/engine/metadata-modules/utils/belongs-to-twenty-standard-app.util'; +import { isCallerTwentyStandardApp } from 'src/engine/metadata-modules/utils/is-caller-twenty-standard-app.util'; +import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type'; import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/utils/get-flat-entity-validation-error.util'; -import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type'; -import { UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type'; -import { validateRoleBelongsToCallerApplication } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/utils/validate-role-belongs-to-caller-application.util'; +import { type FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type'; +import { type UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type'; @Injectable() export class FlatPermissionFlagValidatorService { @@ -19,17 +21,14 @@ export class FlatPermissionFlagValidatorService { flatEntityToValidate: flatPermissionFlagToValidate, optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { flatPermissionFlagMaps: optimisticFlatPermissionFlagMaps, - flatRoleMaps, }, - buildOptions, }: UniversalFlatEntityValidationArgs< typeof ALL_METADATA_NAME.permissionFlag >): FailedFlatEntityValidation<'permissionFlag', 'create'> { const validationResult = getEmptyFlatEntityValidationError({ flatEntityMinimalInformation: { universalIdentifier: flatPermissionFlagToValidate.universalIdentifier, - roleUniversalIdentifier: - flatPermissionFlagToValidate.roleUniversalIdentifier, + key: flatPermissionFlagToValidate.key, }, metadataName: 'permissionFlag', type: 'create', @@ -42,71 +41,47 @@ export class FlatPermissionFlagValidatorService { if (isDefined(existingByUniversalId)) { validationResult.errors.push({ - code: PermissionsExceptionCode.INVALID_SETTING, - message: t`Permission flag with universal identifier ${flatPermissionFlagToValidate.universalIdentifier} already exists`, - userFriendlyMessage: msg`Permission flag already exists`, + code: PermissionFlagExceptionCode.PERMISSION_FLAG_ALREADY_EXISTS, + message: t`Permission flag definition with universal identifier ${flatPermissionFlagToValidate.universalIdentifier} already exists`, + userFriendlyMessage: msg`Permission flag definition already exists`, }); } - const referencedRole = findFlatEntityByUniversalIdentifier({ - universalIdentifier: flatPermissionFlagToValidate.roleUniversalIdentifier, - flatEntityMaps: flatRoleMaps, - }); - - if (!isDefined(referencedRole)) { + if (!isNonEmptyString(flatPermissionFlagToValidate.key)) { validationResult.errors.push({ - code: PermissionsExceptionCode.ROLE_NOT_FOUND, - message: t`Role not found`, - userFriendlyMessage: msg`Role not found`, - }); - } else { - validationResult.errors.push( - ...validateRoleBelongsToCallerApplication({ - referencedRole, - buildOptions, - }), - ); - - if (!referencedRole.isEditable) { - validationResult.errors.push({ - code: PermissionsExceptionCode.ROLE_NOT_EDITABLE, - message: t`Role is not editable`, - userFriendlyMessage: msg`This role cannot be modified because it is a system role. Only custom roles can be edited.`, - }); - } - } - - const isValidFlag = - isDefined(flatPermissionFlagToValidate.flag) && - Object.values(PermissionFlagType).includes( - flatPermissionFlagToValidate.flag, - ); - - if (!isValidFlag) { - validationResult.errors.push({ - code: PermissionsExceptionCode.INVALID_SETTING, - message: t`Invalid permission flag value`, - userFriendlyMessage: msg`Invalid permission setting`, + code: PermissionFlagExceptionCode.INVALID_PERMISSION_FLAG_KEY, + message: t`Permission flag definition key is required`, + userFriendlyMessage: msg`Key is required`, }); } - const duplicateForSameRole = Object.values( + const duplicateKey = Object.values( optimisticFlatPermissionFlagMaps.byUniversalIdentifier, ).filter( - (pf) => - isDefined(pf) && - pf.roleUniversalIdentifier === - flatPermissionFlagToValidate.roleUniversalIdentifier && - pf.flag === flatPermissionFlagToValidate.flag && - pf.universalIdentifier !== + (definition) => + isDefined(definition) && + definition.key === flatPermissionFlagToValidate.key && + definition.universalIdentifier !== flatPermissionFlagToValidate.universalIdentifier, ); - if (duplicateForSameRole.length > 0) { + if (duplicateKey.length > 0) { validationResult.errors.push({ - code: PermissionsExceptionCode.INVALID_SETTING, - message: t`Permission flag for this role and setting already exists`, - userFriendlyMessage: msg`This permission is already set for the role`, + code: PermissionFlagExceptionCode.PERMISSION_FLAG_ALREADY_EXISTS, + message: t`Permission flag definition with key ${flatPermissionFlagToValidate.key} already exists in this workspace`, + userFriendlyMessage: msg`A permission flag with this key already exists`, + }); + } + + if ( + !PERMISSION_FLAG_PERMISSION_TYPES.includes( + flatPermissionFlagToValidate.permissionType, + ) + ) { + validationResult.errors.push({ + code: PermissionFlagExceptionCode.INVALID_PERMISSION_FLAG_PERMISSION_TYPE, + message: t`Permission flag definition permission type must be 'settings' or 'tool'`, + userFriendlyMessage: msg`Invalid permission type`, }); } @@ -118,17 +93,11 @@ export class FlatPermissionFlagValidatorService { flatEntityUpdate, optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { flatPermissionFlagMaps: optimisticFlatPermissionFlagMaps, - flatRoleMaps, }, buildOptions, }: FlatEntityUpdateValidationArgs< typeof ALL_METADATA_NAME.permissionFlag >): FailedFlatEntityValidation<'permissionFlag', 'update'> { - const existingFlatPermissionFlag = findFlatEntityByUniversalIdentifier({ - universalIdentifier, - flatEntityMaps: optimisticFlatPermissionFlagMaps, - }); - const validationResult = getEmptyFlatEntityValidationError({ flatEntityMinimalInformation: { universalIdentifier, @@ -137,79 +106,56 @@ export class FlatPermissionFlagValidatorService { type: 'update', }); - if (!isDefined(existingFlatPermissionFlag)) { + const existing = findFlatEntityByUniversalIdentifier({ + universalIdentifier, + flatEntityMaps: optimisticFlatPermissionFlagMaps, + }); + + if (!isDefined(existing)) { validationResult.errors.push({ - code: PermissionsExceptionCode.PERMISSION_NOT_FOUND, - message: t`Permission flag to update not found`, - userFriendlyMessage: msg`Permission flag not found`, + code: PermissionFlagExceptionCode.PERMISSION_FLAG_NOT_FOUND, + message: t`Permission flag definition to update not found`, + userFriendlyMessage: msg`Permission flag definition not found`, }); return validationResult; } - const updatedFlatPermissionFlag = { - ...existingFlatPermissionFlag, - ...flatEntityUpdate, - }; - - const referencedRole = findFlatEntityByUniversalIdentifier({ - universalIdentifier: updatedFlatPermissionFlag.roleUniversalIdentifier, - flatEntityMaps: flatRoleMaps, - }); - - if (!isDefined(referencedRole)) { + if ( + !isCallerTwentyStandardApp(buildOptions) && + belongsToTwentyStandardApp({ + universalIdentifier: existing.universalIdentifier, + applicationUniversalIdentifier: existing.applicationUniversalIdentifier, + }) + ) { validationResult.errors.push({ - code: PermissionsExceptionCode.ROLE_NOT_FOUND, - message: t`Role not found`, - userFriendlyMessage: msg`Role not found`, + code: PermissionFlagExceptionCode.PERMISSION_FLAG_IS_STANDARD, + message: t`Cannot update standard permission flag definition`, + userFriendlyMessage: msg`Cannot update standard permission flag definition`, }); - } else { - validationResult.errors.push( - ...validateRoleBelongsToCallerApplication({ - referencedRole, - buildOptions, - }), - ); - - if (!referencedRole.isEditable) { - validationResult.errors.push({ - code: PermissionsExceptionCode.ROLE_NOT_EDITABLE, - message: t`Role is not editable`, - userFriendlyMessage: msg`This role cannot be modified because it is a system role. Only custom roles can be edited.`, - }); - } } - if (isDefined(flatEntityUpdate.flag)) { - const isValidFlag = Object.values(PermissionFlagType).includes( - flatEntityUpdate.flag as PermissionFlagType, - ); - - if (!isValidFlag) { - validationResult.errors.push({ - code: PermissionsExceptionCode.INVALID_SETTING, - message: t`Invalid permission flag value`, - userFriendlyMessage: msg`Invalid permission setting`, - }); - } - } - - const duplicateForSameRole = Object.values( - optimisticFlatPermissionFlagMaps.byUniversalIdentifier, - ).filter( - (pf) => - isDefined(pf) && - pf.roleUniversalIdentifier === - updatedFlatPermissionFlag.roleUniversalIdentifier && - pf.flag === updatedFlatPermissionFlag.flag && - pf.universalIdentifier !== universalIdentifier, - ); - - if (duplicateForSameRole.length > 0) { + if ( + isDefined(flatEntityUpdate.key) && + flatEntityUpdate.key !== existing.key + ) { validationResult.errors.push({ - code: PermissionsExceptionCode.INVALID_SETTING, - message: t`Permission flag for this role and setting already exists`, - userFriendlyMessage: msg`This permission is already set for the role`, + code: PermissionFlagExceptionCode.PERMISSION_FLAG_KEY_IMMUTABLE, + message: t`Permission flag definition key cannot be changed after creation`, + userFriendlyMessage: msg`Key cannot be changed`, + }); + } + + if ( + isDefined(flatEntityUpdate.permissionType) && + !PERMISSION_FLAG_PERMISSION_TYPES.includes( + flatEntityUpdate.permissionType, + ) + ) { + validationResult.errors.push({ + code: PermissionFlagExceptionCode.INVALID_PERMISSION_FLAG_PERMISSION_TYPE, + message: t`Permission flag definition permission type must be 'settings' or 'tool'`, + userFriendlyMessage: msg`Invalid permission type`, }); } @@ -220,8 +166,9 @@ export class FlatPermissionFlagValidatorService { flatEntityToValidate: { universalIdentifier }, optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { flatPermissionFlagMaps: optimisticFlatPermissionFlagMaps, - flatRoleMaps, + flatRolePermissionFlagMaps: optimisticFlatRolePermissionFlagMaps, }, + buildOptions, }: UniversalFlatEntityValidationArgs< typeof ALL_METADATA_NAME.permissionFlag >): FailedFlatEntityValidation<'permissionFlag', 'delete'> { @@ -233,30 +180,50 @@ export class FlatPermissionFlagValidatorService { type: 'delete', }); - const existingFlatPermissionFlag = findFlatEntityByUniversalIdentifier({ + const existing = findFlatEntityByUniversalIdentifier({ universalIdentifier, flatEntityMaps: optimisticFlatPermissionFlagMaps, }); - if (!isDefined(existingFlatPermissionFlag)) { + if (!isDefined(existing)) { validationResult.errors.push({ - code: PermissionsExceptionCode.PERMISSION_NOT_FOUND, - message: t`Permission flag to delete not found`, - userFriendlyMessage: msg`Permission flag not found`, - }); - } else { - const referencedRole = findFlatEntityByUniversalIdentifier({ - universalIdentifier: existingFlatPermissionFlag.roleUniversalIdentifier, - flatEntityMaps: flatRoleMaps, + code: PermissionFlagExceptionCode.PERMISSION_FLAG_NOT_FOUND, + message: t`Permission flag definition to delete not found`, + userFriendlyMessage: msg`Permission flag definition not found`, }); - if (isDefined(referencedRole) && !referencedRole.isEditable) { - validationResult.errors.push({ - code: PermissionsExceptionCode.ROLE_NOT_EDITABLE, - message: t`Role is not editable`, - userFriendlyMessage: msg`This role cannot be modified because it is a system role. Only custom roles can be edited.`, - }); - } + return validationResult; + } + + if ( + !isCallerTwentyStandardApp(buildOptions) && + belongsToTwentyStandardApp({ + universalIdentifier: existing.universalIdentifier, + applicationUniversalIdentifier: existing.applicationUniversalIdentifier, + }) + ) { + validationResult.errors.push({ + code: PermissionFlagExceptionCode.PERMISSION_FLAG_IS_STANDARD, + message: t`Cannot delete standard permission flag definition`, + userFriendlyMessage: msg`Cannot delete standard permission flag definition`, + }); + } + + const isPermissionFlagInUse = Object.values( + optimisticFlatRolePermissionFlagMaps.byUniversalIdentifier, + ).some( + (rolePermissionFlag) => + isDefined(rolePermissionFlag) && + rolePermissionFlag.permissionFlagUniversalIdentifier === + existing.universalIdentifier, + ); + + if (isPermissionFlagInUse) { + validationResult.errors.push({ + code: PermissionFlagExceptionCode.PERMISSION_FLAG_IN_USE, + message: t`Permission flag definition with key ${existing.key} is still assigned to a role`, + userFriendlyMessage: msg`Remove this permission from all roles before deleting it`, + }); } return validationResult; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-role-permission-flag-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-role-permission-flag-validator.service.ts new file mode 100644 index 0000000000..087c31f418 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-role-permission-flag-validator.service.ts @@ -0,0 +1,281 @@ +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 { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util'; +import { PermissionsExceptionCode } from 'src/engine/metadata-modules/permissions/permissions.exception'; +import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type'; +import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/utils/get-flat-entity-validation-error.util'; +import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type'; +import { UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type'; +import { validateRoleBelongsToCallerApplication } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/utils/validate-role-belongs-to-caller-application.util'; + +@Injectable() +export class FlatRolePermissionFlagValidatorService { + validateFlatRolePermissionFlagCreation({ + flatEntityToValidate: flatRolePermissionFlagToValidate, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatPermissionFlagMaps, + flatRolePermissionFlagMaps: optimisticFlatRolePermissionFlagMaps, + flatRoleMaps, + }, + buildOptions, + }: UniversalFlatEntityValidationArgs< + typeof ALL_METADATA_NAME.rolePermissionFlag + >): FailedFlatEntityValidation<'rolePermissionFlag', 'create'> { + const validationResult = getEmptyFlatEntityValidationError({ + flatEntityMinimalInformation: { + universalIdentifier: + flatRolePermissionFlagToValidate.universalIdentifier, + permissionFlagUniversalIdentifier: + flatRolePermissionFlagToValidate.permissionFlagUniversalIdentifier, + roleUniversalIdentifier: + flatRolePermissionFlagToValidate.roleUniversalIdentifier, + }, + metadataName: 'rolePermissionFlag', + type: 'create', + }); + + const existingByUniversalId = findFlatEntityByUniversalIdentifier({ + universalIdentifier: flatRolePermissionFlagToValidate.universalIdentifier, + flatEntityMaps: optimisticFlatRolePermissionFlagMaps, + }); + + if (isDefined(existingByUniversalId)) { + validationResult.errors.push({ + code: PermissionsExceptionCode.INVALID_SETTING, + message: t`Role permission flag with universal identifier ${flatRolePermissionFlagToValidate.universalIdentifier} already exists`, + userFriendlyMessage: msg`Role permission flag already exists`, + }); + } + + const referencedRole = findFlatEntityByUniversalIdentifier({ + universalIdentifier: + flatRolePermissionFlagToValidate.roleUniversalIdentifier, + flatEntityMaps: flatRoleMaps, + }); + + if (!isDefined(referencedRole)) { + validationResult.errors.push({ + code: PermissionsExceptionCode.ROLE_NOT_FOUND, + message: t`Role not found`, + userFriendlyMessage: msg`Role not found`, + }); + } else { + validationResult.errors.push( + ...validateRoleBelongsToCallerApplication({ + referencedRole, + buildOptions, + }), + ); + + if (!referencedRole.isEditable) { + validationResult.errors.push({ + code: PermissionsExceptionCode.ROLE_NOT_EDITABLE, + message: t`Role is not editable`, + userFriendlyMessage: msg`This role cannot be modified because it is a system role. Only custom roles can be edited.`, + }); + } + } + + if ( + isDefined( + flatRolePermissionFlagToValidate.permissionFlagUniversalIdentifier, + ) + ) { + const referencedPermissionFlag = findFlatEntityByUniversalIdentifier({ + universalIdentifier: + flatRolePermissionFlagToValidate.permissionFlagUniversalIdentifier, + flatEntityMaps: flatPermissionFlagMaps, + }); + + if (!isDefined(referencedPermissionFlag)) { + validationResult.errors.push({ + code: PermissionsExceptionCode.INVALID_SETTING, + message: t`Permission flag not found`, + userFriendlyMessage: msg`Invalid permission setting`, + }); + } + } + + const duplicateForSameRole = Object.values( + optimisticFlatRolePermissionFlagMaps.byUniversalIdentifier, + ).filter( + (pf) => + isDefined(pf) && + pf.roleUniversalIdentifier === + flatRolePermissionFlagToValidate.roleUniversalIdentifier && + pf.permissionFlagUniversalIdentifier === + flatRolePermissionFlagToValidate.permissionFlagUniversalIdentifier && + pf.universalIdentifier !== + flatRolePermissionFlagToValidate.universalIdentifier, + ); + + if (duplicateForSameRole.length > 0) { + validationResult.errors.push({ + code: PermissionsExceptionCode.INVALID_SETTING, + message: t`Permission flag for this role and setting already exists`, + userFriendlyMessage: msg`This permission is already set for the role`, + }); + } + + return validationResult; + } + + validateFlatRolePermissionFlagUpdate({ + universalIdentifier, + flatEntityUpdate, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatPermissionFlagMaps, + flatRolePermissionFlagMaps: optimisticFlatRolePermissionFlagMaps, + flatRoleMaps, + }, + buildOptions, + }: FlatEntityUpdateValidationArgs< + typeof ALL_METADATA_NAME.rolePermissionFlag + >): FailedFlatEntityValidation<'rolePermissionFlag', 'update'> { + const existingFlatRolePermissionFlag = findFlatEntityByUniversalIdentifier({ + universalIdentifier, + flatEntityMaps: optimisticFlatRolePermissionFlagMaps, + }); + + const validationResult = getEmptyFlatEntityValidationError({ + flatEntityMinimalInformation: { + universalIdentifier, + }, + metadataName: 'rolePermissionFlag', + type: 'update', + }); + + if (!isDefined(existingFlatRolePermissionFlag)) { + validationResult.errors.push({ + code: PermissionsExceptionCode.PERMISSION_NOT_FOUND, + message: t`Permission flag to update not found`, + userFriendlyMessage: msg`Permission flag not found`, + }); + + return validationResult; + } + + const updatedFlatRolePermissionFlag = { + ...existingFlatRolePermissionFlag, + ...flatEntityUpdate, + }; + + const referencedRole = findFlatEntityByUniversalIdentifier({ + universalIdentifier: + updatedFlatRolePermissionFlag.roleUniversalIdentifier, + flatEntityMaps: flatRoleMaps, + }); + + if (!isDefined(referencedRole)) { + validationResult.errors.push({ + code: PermissionsExceptionCode.ROLE_NOT_FOUND, + message: t`Role not found`, + userFriendlyMessage: msg`Role not found`, + }); + } else { + validationResult.errors.push( + ...validateRoleBelongsToCallerApplication({ + referencedRole, + buildOptions, + }), + ); + + if (!referencedRole.isEditable) { + validationResult.errors.push({ + code: PermissionsExceptionCode.ROLE_NOT_EDITABLE, + message: t`Role is not editable`, + userFriendlyMessage: msg`This role cannot be modified because it is a system role. Only custom roles can be edited.`, + }); + } + } + + if (isDefined(flatEntityUpdate.permissionFlagUniversalIdentifier)) { + const referencedPermissionFlag = findFlatEntityByUniversalIdentifier({ + universalIdentifier: + flatEntityUpdate.permissionFlagUniversalIdentifier as string, + flatEntityMaps: flatPermissionFlagMaps, + }); + + if (!isDefined(referencedPermissionFlag)) { + validationResult.errors.push({ + code: PermissionsExceptionCode.INVALID_SETTING, + message: t`Permission flag not found`, + userFriendlyMessage: msg`Invalid permission setting`, + }); + } + } + + const duplicateForSameRole = Object.values( + optimisticFlatRolePermissionFlagMaps.byUniversalIdentifier, + ).filter( + (pf) => + isDefined(pf) && + pf.roleUniversalIdentifier === + updatedFlatRolePermissionFlag.roleUniversalIdentifier && + pf.permissionFlagUniversalIdentifier === + updatedFlatRolePermissionFlag.permissionFlagUniversalIdentifier && + pf.universalIdentifier !== universalIdentifier, + ); + + if (duplicateForSameRole.length > 0) { + validationResult.errors.push({ + code: PermissionsExceptionCode.INVALID_SETTING, + message: t`Permission flag for this role and setting already exists`, + userFriendlyMessage: msg`This permission is already set for the role`, + }); + } + + return validationResult; + } + + validateFlatRolePermissionFlagDeletion({ + flatEntityToValidate: { universalIdentifier }, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatRolePermissionFlagMaps: optimisticFlatRolePermissionFlagMaps, + flatRoleMaps, + }, + }: UniversalFlatEntityValidationArgs< + typeof ALL_METADATA_NAME.rolePermissionFlag + >): FailedFlatEntityValidation<'rolePermissionFlag', 'delete'> { + const validationResult = getEmptyFlatEntityValidationError({ + flatEntityMinimalInformation: { + universalIdentifier, + }, + metadataName: 'rolePermissionFlag', + type: 'delete', + }); + + const existingFlatRolePermissionFlag = findFlatEntityByUniversalIdentifier({ + universalIdentifier, + flatEntityMaps: optimisticFlatRolePermissionFlagMaps, + }); + + if (!isDefined(existingFlatRolePermissionFlag)) { + validationResult.errors.push({ + code: PermissionsExceptionCode.PERMISSION_NOT_FOUND, + message: t`Permission flag to delete not found`, + userFriendlyMessage: msg`Permission flag not found`, + }); + } else { + const referencedRole = findFlatEntityByUniversalIdentifier({ + universalIdentifier: + existingFlatRolePermissionFlag.roleUniversalIdentifier, + flatEntityMaps: flatRoleMaps, + }); + + if (isDefined(referencedRole) && !referencedRole.isEditable) { + validationResult.errors.push({ + code: PermissionsExceptionCode.ROLE_NOT_EDITABLE, + message: t`Role is not editable`, + userFriendlyMessage: msg`This role cannot be modified because it is a system role. Only custom roles can be edited.`, + }); + } + } + + return validationResult; + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/workspace-migration-builder-validators.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/workspace-migration-builder-validators.module.ts index d32733d856..f5b0939fb3 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/workspace-migration-builder-validators.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/workspace-migration-builder-validators.module.ts @@ -17,6 +17,7 @@ import { FlatPageLayoutTabValidatorService } from 'src/engine/workspace-manager/ import { FlatPageLayoutValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-validator.service'; import { FlatPageLayoutWidgetValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service'; import { FlatPermissionFlagValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-permission-flag-validator.service'; +import { FlatRolePermissionFlagValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-role-permission-flag-validator.service'; import { FlatRoleTargetValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-role-target-validator.service'; import { FlatRoleValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-role-validator.service'; import { FlatRowLevelPermissionPredicateGroupValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-row-level-permission-predicate-group-validator.service'; @@ -45,6 +46,7 @@ import { FlatConnectionProviderValidatorService } from 'src/engine/workspace-man FlatViewSortValidatorService, FlatFieldPermissionValidatorService, FlatObjectPermissionValidatorService, + FlatRolePermissionFlagValidatorService, FlatPermissionFlagValidatorService, FlatIndexValidatorService, FlatFieldMetadataValidatorService, @@ -78,6 +80,7 @@ import { FlatConnectionProviderValidatorService } from 'src/engine/workspace-man FlatViewSortValidatorService, FlatFieldPermissionValidatorService, FlatObjectPermissionValidatorService, + FlatRolePermissionFlagValidatorService, FlatPermissionFlagValidatorService, FlatIndexValidatorService, FlatFieldMetadataValidatorService, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/workspace-migration-builder.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/workspace-migration-builder.module.ts index e5c52274ff..b4b69ec6e9 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/workspace-migration-builder.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/workspace-migration-builder.module.ts @@ -16,6 +16,7 @@ import { WorkspaceMigrationPageLayoutTabActionsBuilderService } from 'src/engine import { WorkspaceMigrationPageLayoutWidgetActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/page-layout-widget/workspace-migration-page-layout-widget-actions-builder.service'; import { WorkspaceMigrationPageLayoutActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/page-layout/workspace-migration-page-layout-actions-builder.service'; import { WorkspaceMigrationPermissionFlagActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/permission-flag/workspace-migration-permission-flag-actions-builder.service'; +import { WorkspaceMigrationRolePermissionFlagActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-permission-flag/workspace-migration-role-permission-flag-actions-builder.service'; import { WorkspaceMigrationRoleTargetActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-target/workspace-migration-role-target-actions-builder.service'; import { WorkspaceMigrationRoleActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role/workspace-migration-role-actions-builder.service'; import { WorkspaceMigrationRowLevelPermissionPredicateGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/row-level-permission-predicate-group/workspace-migration-row-level-permission-predicate-group-actions-builder.service'; @@ -47,6 +48,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace- WorkspaceMigrationViewSortActionsBuilderService, WorkspaceMigrationFieldPermissionActionsBuilderService, WorkspaceMigrationObjectPermissionActionsBuilderService, + WorkspaceMigrationRolePermissionFlagActionsBuilderService, WorkspaceMigrationPermissionFlagActionsBuilderService, WorkspaceMigrationIndexActionsBuilderService, WorkspaceMigrationLogicFunctionActionsBuilderService, @@ -79,6 +81,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace- WorkspaceMigrationViewSortActionsBuilderService, WorkspaceMigrationFieldPermissionActionsBuilderService, WorkspaceMigrationObjectPermissionActionsBuilderService, + WorkspaceMigrationRolePermissionFlagActionsBuilderService, WorkspaceMigrationPermissionFlagActionsBuilderService, WorkspaceMigrationFieldActionsBuilderService, WorkspaceMigrationLogicFunctionActionsBuilderService, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/permission-flag/services/create-permission-flag-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/permission-flag/services/create-permission-flag-action-handler.service.ts index 515d557d24..e68b01a0c5 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/permission-flag/services/create-permission-flag-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/permission-flag/services/create-permission-flag-action-handler.service.ts @@ -2,15 +2,15 @@ import { Injectable } from '@nestjs/common'; import { v4 } from 'uuid'; -import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; -import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { - FlatCreatePermissionFlagAction, - UniversalCreatePermissionFlagAction, + type FlatCreatePermissionFlagAction, + type UniversalCreatePermissionFlagAction, } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/permission-flag/types/workspace-migration-permission-flag-action.type'; +import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; import { - WorkspaceMigrationActionRunnerArgs, - WorkspaceMigrationActionRunnerContext, + type WorkspaceMigrationActionRunnerArgs, + type WorkspaceMigrationActionRunnerContext, } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type'; @Injectable() @@ -18,26 +18,29 @@ export class CreatePermissionFlagActionHandlerService extends WorkspaceMigration 'create', 'permissionFlag', ) { + constructor() { + super(); + } + override async transpileUniversalActionToFlatAction({ action, - allFlatEntityMaps, flatApplication, workspaceId, }: WorkspaceMigrationActionRunnerArgs): Promise { - const { roleId } = resolveUniversalRelationIdentifiersToIds({ - flatEntityMaps: allFlatEntityMaps, - metadataName: action.metadataName, - universalForeignKeyValues: action.flatEntity, - }); + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'permissionFlag', + }); return { ...action, flatEntity: { ...action.flatEntity, - roleId, applicationId: flatApplication.id, id: action.id ?? v4(), workspaceId, + rolePermissionFlagIds: [], + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/permission-flag/services/delete-permission-flag-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/permission-flag/services/delete-permission-flag-action-handler.service.ts index 21aebd6b4f..ca7cf43d91 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/permission-flag/services/delete-permission-flag-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/permission-flag/services/delete-permission-flag-action-handler.service.ts @@ -1,14 +1,14 @@ import { Injectable } from '@nestjs/common'; -import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity'; import { - FlatDeletePermissionFlagAction, - UniversalDeletePermissionFlagAction, + type FlatDeletePermissionFlagAction, + type UniversalDeletePermissionFlagAction, } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/permission-flag/types/workspace-migration-permission-flag-action.type'; +import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; import { - WorkspaceMigrationActionRunnerArgs, - WorkspaceMigrationActionRunnerContext, + type WorkspaceMigrationActionRunnerArgs, + type WorkspaceMigrationActionRunnerContext, } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type'; @Injectable() @@ -31,12 +31,12 @@ export class DeletePermissionFlagActionHandlerService extends WorkspaceMigration ): Promise { const { flatAction, queryRunner, workspaceId } = context; - const permissionFlagRepository = + const repository = queryRunner.manager.getRepository( PermissionFlagEntity, ); - await permissionFlagRepository.delete({ + await repository.delete({ id: flatAction.entityId, workspaceId, }); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/permission-flag/services/update-permission-flag-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/permission-flag/services/update-permission-flag-action-handler.service.ts index 0c260882e2..70269366c6 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/permission-flag/services/update-permission-flag-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/permission-flag/services/update-permission-flag-action-handler.service.ts @@ -1,16 +1,16 @@ import { Injectable } from '@nestjs/common'; -import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util'; import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity'; import { resolveUniversalUpdateRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-update-relation-identifiers-to-ids.util'; import { - FlatUpdatePermissionFlagAction, - UniversalUpdatePermissionFlagAction, + type FlatUpdatePermissionFlagAction, + type UniversalUpdatePermissionFlagAction, } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/permission-flag/types/workspace-migration-permission-flag-action.type'; +import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; import { - WorkspaceMigrationActionRunnerArgs, - WorkspaceMigrationActionRunnerContext, + type WorkspaceMigrationActionRunnerArgs, + type WorkspaceMigrationActionRunnerContext, } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type'; @Injectable() @@ -18,16 +18,12 @@ export class UpdatePermissionFlagActionHandlerService extends WorkspaceMigration 'update', 'permissionFlag', ) { - constructor() { - super(); - } - override async transpileUniversalActionToFlatAction( context: WorkspaceMigrationActionRunnerArgs, ): Promise { const { action, allFlatEntityMaps } = context; - const flatPermissionFlag = findFlatEntityByUniversalIdentifierOrThrow({ + const flatDefinition = findFlatEntityByUniversalIdentifierOrThrow({ flatEntityMaps: allFlatEntityMaps.flatPermissionFlagMaps, universalIdentifier: action.universalIdentifier, }); @@ -41,7 +37,7 @@ export class UpdatePermissionFlagActionHandlerService extends WorkspaceMigration return { type: 'update', metadataName: 'permissionFlag', - entityId: flatPermissionFlag.id, + entityId: flatDefinition.id, update, }; } @@ -52,15 +48,12 @@ export class UpdatePermissionFlagActionHandlerService extends WorkspaceMigration const { flatAction, queryRunner, workspaceId } = context; const { entityId, update } = flatAction; - const permissionFlagRepository = + const repository = queryRunner.manager.getRepository( PermissionFlagEntity, ); - await permissionFlagRepository.update( - { id: entityId, workspaceId }, - update, - ); + await repository.update({ id: entityId, workspaceId }, update); } async executeForWorkspaceSchema( diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-permission-flag/services/create-role-permission-flag-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-permission-flag/services/create-role-permission-flag-action-handler.service.ts new file mode 100644 index 0000000000..ba32b5827c --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-permission-flag/services/create-role-permission-flag-action-handler.service.ts @@ -0,0 +1,62 @@ +import { Injectable } from '@nestjs/common'; + +import { v4 } from 'uuid'; + +import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util'; +import { + FlatCreateRolePermissionFlagAction, + UniversalCreateRolePermissionFlagAction, +} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-permission-flag/types/workspace-migration-role-permission-flag-action.type'; +import { + WorkspaceMigrationActionRunnerArgs, + WorkspaceMigrationActionRunnerContext, +} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type'; + +@Injectable() +export class CreateRolePermissionFlagActionHandlerService extends WorkspaceMigrationRunnerActionHandler( + 'create', + 'rolePermissionFlag', +) { + override async transpileUniversalActionToFlatAction({ + action, + allFlatEntityMaps, + flatApplication, + workspaceId, + }: WorkspaceMigrationActionRunnerArgs): Promise { + const relationIds = resolveUniversalRelationIdentifiersToIds({ + flatEntityMaps: allFlatEntityMaps, + metadataName: action.metadataName, + universalForeignKeyValues: action.flatEntity, + }); + + return { + ...action, + flatEntity: { + ...action.flatEntity, + ...relationIds, + applicationId: flatApplication.id, + id: action.id ?? v4(), + workspaceId, + }, + }; + } + + async executeForMetadata( + context: WorkspaceMigrationActionRunnerContext, + ): Promise { + const { flatAction, queryRunner } = context; + const { flatEntity } = flatAction; + + await this.insertFlatEntitiesInRepository({ + queryRunner, + flatEntities: [flatEntity], + }); + } + + async executeForWorkspaceSchema( + _context: WorkspaceMigrationActionRunnerContext, + ): Promise { + return; + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-permission-flag/services/delete-role-permission-flag-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-permission-flag/services/delete-role-permission-flag-action-handler.service.ts new file mode 100644 index 0000000000..afbe93c3a9 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-permission-flag/services/delete-role-permission-flag-action-handler.service.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@nestjs/common'; + +import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity'; +import { + FlatDeleteRolePermissionFlagAction, + UniversalDeleteRolePermissionFlagAction, +} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-permission-flag/types/workspace-migration-role-permission-flag-action.type'; +import { + WorkspaceMigrationActionRunnerArgs, + WorkspaceMigrationActionRunnerContext, +} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type'; + +@Injectable() +export class DeleteRolePermissionFlagActionHandlerService extends WorkspaceMigrationRunnerActionHandler( + 'delete', + 'rolePermissionFlag', +) { + constructor() { + super(); + } + + override async transpileUniversalActionToFlatAction( + context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + return this.transpileUniversalDeleteActionToFlatDeleteAction(context); + } + + async executeForMetadata( + context: WorkspaceMigrationActionRunnerContext, + ): Promise { + const { flatAction, queryRunner, workspaceId } = context; + + const rolePermissionFlagRepository = + queryRunner.manager.getRepository( + RolePermissionFlagEntity, + ); + + await rolePermissionFlagRepository.delete({ + id: flatAction.entityId, + workspaceId, + }); + } + + async executeForWorkspaceSchema( + _context: WorkspaceMigrationActionRunnerContext, + ): Promise { + return; + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-permission-flag/services/update-role-permission-flag-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-permission-flag/services/update-role-permission-flag-action-handler.service.ts new file mode 100644 index 0000000000..b1e1e40c79 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-permission-flag/services/update-role-permission-flag-action-handler.service.ts @@ -0,0 +1,71 @@ +import { Injectable } from '@nestjs/common'; + +import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util'; +import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity'; +import { resolveUniversalUpdateRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-update-relation-identifiers-to-ids.util'; +import { + FlatUpdateRolePermissionFlagAction, + UniversalUpdateRolePermissionFlagAction, +} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-permission-flag/types/workspace-migration-role-permission-flag-action.type'; +import { + WorkspaceMigrationActionRunnerArgs, + WorkspaceMigrationActionRunnerContext, +} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type'; + +@Injectable() +export class UpdateRolePermissionFlagActionHandlerService extends WorkspaceMigrationRunnerActionHandler( + 'update', + 'rolePermissionFlag', +) { + constructor() { + super(); + } + + override async transpileUniversalActionToFlatAction( + context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + const { action, allFlatEntityMaps } = context; + + const flatRolePermissionFlag = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: allFlatEntityMaps.flatRolePermissionFlagMaps, + universalIdentifier: action.universalIdentifier, + }); + + const update = resolveUniversalUpdateRelationIdentifiersToIds({ + metadataName: 'rolePermissionFlag', + universalUpdate: action.update, + allFlatEntityMaps, + }); + + return { + type: 'update', + metadataName: 'rolePermissionFlag', + entityId: flatRolePermissionFlag.id, + update, + }; + } + + async executeForMetadata( + context: WorkspaceMigrationActionRunnerContext, + ): Promise { + const { flatAction, queryRunner, workspaceId } = context; + const { entityId, update } = flatAction; + + const rolePermissionFlagRepository = + queryRunner.manager.getRepository( + RolePermissionFlagEntity, + ); + + await rolePermissionFlagRepository.update( + { id: entityId, workspaceId }, + update, + ); + } + + async executeForWorkspaceSchema( + _context: WorkspaceMigrationActionRunnerContext, + ): Promise { + return; + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role/services/create-role-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role/services/create-role-action-handler.service.ts index 963780d797..c103c6ea97 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role/services/create-role-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role/services/create-role-action-handler.service.ts @@ -44,7 +44,7 @@ export class CreateRoleActionHandlerService extends WorkspaceMigrationRunnerActi rowLevelPermissionPredicateIds: [], rowLevelPermissionPredicateGroupIds: [], objectPermissionIds: [], - permissionFlagIds: [], + rolePermissionFlagIds: [], fieldPermissionIds: [], ...emptyUniversalForeignKeyAggregators, }, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/workspace-schema-migration-runner-action-handlers.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/workspace-schema-migration-runner-action-handlers.module.ts index e5445069a5..134276e311 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/workspace-schema-migration-runner-action-handlers.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/workspace-schema-migration-runner-action-handlers.module.ts @@ -46,6 +46,9 @@ import { UpdatePageLayoutActionHandlerService } from 'src/engine/workspace-manag import { CreatePermissionFlagActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/permission-flag/services/create-permission-flag-action-handler.service'; import { DeletePermissionFlagActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/permission-flag/services/delete-permission-flag-action-handler.service'; import { UpdatePermissionFlagActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/permission-flag/services/update-permission-flag-action-handler.service'; +import { CreateRolePermissionFlagActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-permission-flag/services/create-role-permission-flag-action-handler.service'; +import { DeleteRolePermissionFlagActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-permission-flag/services/delete-role-permission-flag-action-handler.service'; +import { UpdateRolePermissionFlagActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-permission-flag/services/update-role-permission-flag-action-handler.service'; import { CreateRoleTargetActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-target/services/create-role-target-action-handler.service'; import { DeleteRoleTargetActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-target/services/delete-role-target-action-handler.service'; import { UpdateRoleTargetActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-target/services/update-role-target-action-handler.service'; @@ -151,6 +154,10 @@ import { UpdateConnectionProviderActionHandlerService } from 'src/engine/workspa DeleteRoleTargetActionHandlerService, UpdateRoleTargetActionHandlerService, + CreateRolePermissionFlagActionHandlerService, + UpdateRolePermissionFlagActionHandlerService, + DeleteRolePermissionFlagActionHandlerService, + CreatePermissionFlagActionHandlerService, UpdatePermissionFlagActionHandlerService, DeletePermissionFlagActionHandlerService, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/constants/metadata-event-to-emit.constant.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/constants/metadata-event-to-emit.constant.ts index 2f6a456cf9..24597d7984 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/constants/metadata-event-to-emit.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/constants/metadata-event-to-emit.constant.ts @@ -19,6 +19,7 @@ export const METADATA_EVENTS_TO_EMIT = { pageLayoutTab: true, commandMenuItem: true, navigationMenuItem: true, + rolePermissionFlag: true, permissionFlag: true, objectPermission: true, fieldPermission: true, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service.ts index 579e0b0376..4041c2f46e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service.ts @@ -90,7 +90,7 @@ export class WorkspaceMigrationRunnerService { const shouldInvalidateRolesPermissionsCache = flatMapsKeysSet.has('flatObjectPermissionMaps') || flatMapsKeysSet.has('flatFieldPermissionMaps') || - flatMapsKeysSet.has('flatPermissionFlagMaps'); + flatMapsKeysSet.has('flatRolePermissionFlagMaps'); if ( shouldIncrementMetadataGraphqlSchemaVersion || diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/derive-metadata-events-from-create-action.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/derive-metadata-events-from-create-action.util.ts index 013bba6b7e..cfeb6849bb 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/derive-metadata-events-from-create-action.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/derive-metadata-events-from-create-action.util.ts @@ -92,6 +92,7 @@ const deriveAllMetadataEventsFromCreateAction = ( case 'commandMenuItem': case 'frontComponent': case 'navigationMenuItem': + case 'rolePermissionFlag': case 'permissionFlag': case 'objectPermission': case 'fieldPermission': diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/derive-metadata-events-from-delete-action.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/derive-metadata-events-from-delete-action.util.ts index 7de10f3907..2d9d59c41f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/derive-metadata-events-from-delete-action.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/derive-metadata-events-from-delete-action.util.ts @@ -56,6 +56,7 @@ const deriveAllMetadataEventsFromDeleteAction = ({ case 'commandMenuItem': case 'frontComponent': case 'navigationMenuItem': + case 'rolePermissionFlag': case 'permissionFlag': case 'objectPermission': case 'fieldPermission': diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/derive-metadata-events-from-update-action.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/derive-metadata-events-from-update-action.util.ts index 5faaa51112..5eb2f5fff2 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/derive-metadata-events-from-update-action.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/derive-metadata-events-from-update-action.util.ts @@ -134,6 +134,7 @@ const deriveAllMetadataEventsFromUpdateAction = ({ case 'commandMenuItem': case 'frontComponent': case 'navigationMenuItem': + case 'rolePermissionFlag': case 'permissionFlag': case 'objectPermission': case 'fieldPermission': diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/optimistically-apply-create-action-on-all-flat-entity-maps.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/optimistically-apply-create-action-on-all-flat-entity-maps.util.ts index 8d313ce09c..d583d27b05 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/optimistically-apply-create-action-on-all-flat-entity-maps.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/optimistically-apply-create-action-on-all-flat-entity-maps.util.ts @@ -68,6 +68,7 @@ export const optimisticallyApplyCreateActionOnAllFlatEntityMaps = ({ case 'commandMenuItem': case 'frontComponent': case 'navigationMenuItem': + case 'rolePermissionFlag': case 'permissionFlag': case 'objectPermission': case 'fieldPermission': diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/optimistically-apply-delete-action-on-all-flat-entity-maps.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/optimistically-apply-delete-action-on-all-flat-entity-maps.util.ts index d262a44c7a..a479fe30de 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/optimistically-apply-delete-action-on-all-flat-entity-maps.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/optimistically-apply-delete-action-on-all-flat-entity-maps.util.ts @@ -40,6 +40,7 @@ export const optimisticallyApplyDeleteActionOnAllFlatEntityMaps = ({ case 'commandMenuItem': case 'frontComponent': case 'navigationMenuItem': + case 'rolePermissionFlag': case 'permissionFlag': case 'objectPermission': case 'fieldPermission': diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/optimistically-apply-update-action-on-all-flat-entity-maps.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/optimistically-apply-update-action-on-all-flat-entity-maps.util.ts index e5a94ff070..a094581e79 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/optimistically-apply-update-action-on-all-flat-entity-maps.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/optimistically-apply-update-action-on-all-flat-entity-maps.util.ts @@ -61,6 +61,7 @@ export const optimisticallyApplyUpdateActionOnAllFlatEntityMaps = ({ case 'commandMenuItem': case 'frontComponent': case 'navigationMenuItem': + case 'rolePermissionFlag': case 'permissionFlag': case 'objectPermission': case 'fieldPermission': diff --git a/packages/twenty-server/test/integration/graphql/suites/settings-permissions/roles.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/settings-permissions/roles.integration-spec.ts index eee296ecf9..e15678fc70 100644 --- a/packages/twenty-server/test/integration/graphql/suites/settings-permissions/roles.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/settings-permissions/roles.integration-spec.ts @@ -601,9 +601,9 @@ describe('roles permissions', () => { expect(res.body.errors[0].extensions.code).toBe( ErrorCode.METADATA_VALIDATION_FAILED, ); - const permissionFlagErrors = - res.body.errors[0].extensions.errors?.permissionFlag ?? []; - const hasRoleNotEditable = permissionFlagErrors.some( + const rolePermissionFlagErrors = + res.body.errors[0].extensions.errors?.rolePermissionFlag ?? []; + const hasRoleNotEditable = rolePermissionFlagErrors.some( (failure: { errors?: Array<{ code?: string }> }) => failure.errors?.some( (err) => diff --git a/packages/twenty-server/test/integration/graphql/suites/workspace/successful-user-and-workspace-creation.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/workspace/successful-user-and-workspace-creation.integration-spec.ts index 241b6e195c..9eff76e620 100644 --- a/packages/twenty-server/test/integration/graphql/suites/workspace/successful-user-and-workspace-creation.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/workspace/successful-user-and-workspace-creation.integration-spec.ts @@ -217,6 +217,7 @@ describe('Successful user and workspace creation', () => { 'objectPermission', 'fieldPermission', 'permissionFlag', + 'rolePermissionFlag', 'logicFunction', 'agent', 'view', diff --git a/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/failing-sync-application-cross-app-permission-create-on-standard-role.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/failing-sync-application-cross-app-permission-create-on-standard-role.integration-spec.ts.snap index cc17ce95f8..02163e3e3f 100644 --- a/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/failing-sync-application-cross-app-permission-create-on-standard-role.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/failing-sync-application-cross-app-permission-create-on-standard-role.integration-spec.ts.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Sync application should fail when creating permissions on a standard role from another app should fail when adding a field permission under the standard admin role 1`] = ` { @@ -75,29 +75,6 @@ exports[`Sync application should fail when creating permissions on a standard ro "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { - "permissionFlag": [ - { - "errors": [ - { - "code": "ROLE_BELONGS_TO_ANOTHER_APPLICATION", - "message": "Cannot target a role owned by another application", - "userFriendlyMessage": "Cannot target a role owned by another application.", - }, - { - "code": "ROLE_NOT_EDITABLE", - "message": "Role is not editable", - "userFriendlyMessage": "This role cannot be modified because it is a system role. Only custom roles can be edited.", - }, - ], - "flatEntityMinimalInformation": { - "roleUniversalIdentifier": Any, - "universalIdentifier": Any, - }, - "metadataName": "permissionFlag", - "status": "fail", - "type": "create", - }, - ], "role": [ { "errors": [ @@ -114,11 +91,35 @@ exports[`Sync application should fail when creating permissions on a standard ro "type": "create", }, ], + "rolePermissionFlag": [ + { + "errors": [ + { + "code": "ROLE_BELONGS_TO_ANOTHER_APPLICATION", + "message": "Cannot target a role owned by another application", + "userFriendlyMessage": "Cannot target a role owned by another application.", + }, + { + "code": "ROLE_NOT_EDITABLE", + "message": "Role is not editable", + "userFriendlyMessage": "This role cannot be modified because it is a system role. Only custom roles can be edited.", + }, + ], + "flatEntityMinimalInformation": { + "permissionFlagUniversalIdentifier": Any, + "roleUniversalIdentifier": Any, + "universalIdentifier": Any, + }, + "metadataName": "rolePermissionFlag", + "status": "fail", + "type": "create", + }, + ], }, - "message": "Validation failed for 1 role, 1 permissionFlag", + "message": "Validation failed for 1 role, 1 rolePermissionFlag", "summary": { - "permissionFlag": 1, "role": 1, + "rolePermissionFlag": 1, "totalErrors": 2, }, "userFriendlyMessage": "Many validation errors", diff --git a/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/failing-sync-application-cross-app-permission-retarget-on-update.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/failing-sync-application-cross-app-permission-retarget-on-update.integration-spec.ts.snap index e59cdb3bfb..ad22081ff1 100644 --- a/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/failing-sync-application-cross-app-permission-retarget-on-update.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/failing-sync-application-cross-app-permission-retarget-on-update.integration-spec.ts.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Sync application should fail when retargeting existing permissions to a standard role on update should fail when retargeting a field permission from own role to the standard admin role 1`] = ` { @@ -68,28 +68,6 @@ exports[`Sync application should fail when retargeting existing permissions to a "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { - "permissionFlag": [ - { - "errors": [ - { - "code": "ROLE_BELONGS_TO_ANOTHER_APPLICATION", - "message": "Cannot target a role owned by another application", - "userFriendlyMessage": "Cannot target a role owned by another application.", - }, - { - "code": "ROLE_NOT_EDITABLE", - "message": "Role is not editable", - "userFriendlyMessage": "This role cannot be modified because it is a system role. Only custom roles can be edited.", - }, - ], - "flatEntityMinimalInformation": { - "universalIdentifier": Any, - }, - "metadataName": "permissionFlag", - "status": "fail", - "type": "update", - }, - ], "role": [ { "errors": [ @@ -112,11 +90,33 @@ exports[`Sync application should fail when retargeting existing permissions to a "type": "create", }, ], + "rolePermissionFlag": [ + { + "errors": [ + { + "code": "ROLE_BELONGS_TO_ANOTHER_APPLICATION", + "message": "Cannot target a role owned by another application", + "userFriendlyMessage": "Cannot target a role owned by another application.", + }, + { + "code": "ROLE_NOT_EDITABLE", + "message": "Role is not editable", + "userFriendlyMessage": "This role cannot be modified because it is a system role. Only custom roles can be edited.", + }, + ], + "flatEntityMinimalInformation": { + "universalIdentifier": Any, + }, + "metadataName": "rolePermissionFlag", + "status": "fail", + "type": "update", + }, + ], }, - "message": "Validation failed for 1 role, 1 permissionFlag", + "message": "Validation failed for 1 role, 1 rolePermissionFlag", "summary": { - "permissionFlag": 1, "role": 1, + "rolePermissionFlag": 1, "totalErrors": 2, }, "userFriendlyMessage": "Many validation errors", diff --git a/packages/twenty-server/test/integration/metadata/suites/application/failing-sync-application-cross-app-permission-create-on-standard-role.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/application/failing-sync-application-cross-app-permission-create-on-standard-role.integration-spec.ts index 0bed38c19a..eddafe1355 100644 --- a/packages/twenty-server/test/integration/metadata/suites/application/failing-sync-application-cross-app-permission-create-on-standard-role.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/application/failing-sync-application-cross-app-permission-create-on-standard-role.integration-spec.ts @@ -10,7 +10,7 @@ import { STANDARD_ROLE } from 'src/engine/workspace-manager/twenty-standard-appl const TEST_APP_ID = 'a1b2c3d4-0010-4000-a000-000000000010'; const TEST_ROLE_ID = 'a1b2c3d4-0010-4000-a000-000000000011'; -const TEST_PERMISSION_FLAG_ID = 'a1b2c3d4-0010-4000-a000-000000000012'; +const TEST_ROLE_PERMISSION_FLAG_ID = 'a1b2c3d4-0010-4000-a000-000000000012'; const TEST_OBJECT_PERMISSION_ID = 'a1b2c3d4-0010-4000-a000-000000000013'; const TEST_FIELD_PERMISSION_ID = 'a1b2c3d4-0010-4000-a000-000000000014'; const FAKE_OBJECT_ID = 'a1b2c3d4-0010-4000-a000-000000000020'; @@ -50,7 +50,7 @@ describe('Sync application should fail when creating permissions on a standard r 'Attempts to add permissions to the standard admin role', permissionFlags: [ { - universalIdentifier: TEST_PERMISSION_FLAG_ID, + universalIdentifier: TEST_ROLE_PERMISSION_FLAG_ID, flag: PermissionFlagType.WORKSPACE, }, ], diff --git a/packages/twenty-server/test/integration/metadata/suites/application/failing-sync-application-cross-app-permission-retarget-on-update.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/application/failing-sync-application-cross-app-permission-retarget-on-update.integration-spec.ts index cc6e6108fc..707fb79bfb 100644 --- a/packages/twenty-server/test/integration/metadata/suites/application/failing-sync-application-cross-app-permission-retarget-on-update.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/application/failing-sync-application-cross-app-permission-retarget-on-update.integration-spec.ts @@ -11,7 +11,7 @@ import { STANDARD_ROLE } from 'src/engine/workspace-manager/twenty-standard-appl const TEST_APP_ID = 'a1b2c3d4-0020-4000-a000-000000000010'; const TEST_ROLE_ID = 'a1b2c3d4-0020-4000-a000-000000000011'; -const TEST_PERMISSION_FLAG_ID = 'a1b2c3d4-0020-4000-a000-000000000012'; +const TEST_ROLE_PERMISSION_FLAG_ID = 'a1b2c3d4-0020-4000-a000-000000000012'; const TEST_OBJECT_PERMISSION_ID = 'a1b2c3d4-0020-4000-a000-000000000013'; const TEST_FIELD_PERMISSION_ID = 'a1b2c3d4-0020-4000-a000-000000000014'; @@ -47,7 +47,7 @@ describe('Sync application should fail when retargeting existing permissions to description: 'Default role for the test app', permissionFlags: [ { - universalIdentifier: TEST_PERMISSION_FLAG_ID, + universalIdentifier: TEST_ROLE_PERMISSION_FLAG_ID, flag: PermissionFlagType.WORKSPACE, }, ], @@ -77,7 +77,7 @@ describe('Sync application should fail when retargeting existing permissions to description: 'Attempts to retarget permission flag to admin role', permissionFlags: [ { - universalIdentifier: TEST_PERMISSION_FLAG_ID, + universalIdentifier: TEST_ROLE_PERMISSION_FLAG_ID, flag: PermissionFlagType.WORKSPACE, }, ], diff --git a/packages/twenty-server/test/integration/metadata/suites/permission-flag/__snapshots__/failing-permission-flag-upsert.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/role-permission-flag/__snapshots__/failing-permission-flag-upsert.integration-spec.ts.snap similarity index 89% rename from packages/twenty-server/test/integration/metadata/suites/permission-flag/__snapshots__/failing-permission-flag-upsert.integration-spec.ts.snap rename to packages/twenty-server/test/integration/metadata/suites/role-permission-flag/__snapshots__/failing-permission-flag-upsert.integration-spec.ts.snap index 043f2e4015..41961024b0 100644 --- a/packages/twenty-server/test/integration/metadata/suites/permission-flag/__snapshots__/failing-permission-flag-upsert.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/role-permission-flag/__snapshots__/failing-permission-flag-upsert.integration-spec.ts.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Permission flag upsert should fail when permissionFlagKeys contains invalid enum value 1`] = ` { @@ -19,7 +19,7 @@ exports[`Permission flag upsert should fail when role is not editable (system ro "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { - "permissionFlag": [ + "rolePermissionFlag": [ { "errors": [ { @@ -34,18 +34,19 @@ exports[`Permission flag upsert should fail when role is not editable (system ro }, ], "flatEntityMinimalInformation": { + "permissionFlagUniversalIdentifier": Any, "roleUniversalIdentifier": Any, "universalIdentifier": Any, }, - "metadataName": "permissionFlag", + "metadataName": "rolePermissionFlag", "status": "fail", "type": "create", }, ], }, - "message": "Validation failed for 1 permissionFlag", + "message": "Validation failed for 1 rolePermissionFlag", "summary": { - "permissionFlag": 1, + "rolePermissionFlag": 1, "totalErrors": 1, }, "userFriendlyMessage": "Cannot target a role owned by another application.", diff --git a/packages/twenty-server/test/integration/metadata/suites/permission-flag/failing-permission-flag-upsert.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/role-permission-flag/failing-permission-flag-upsert.integration-spec.ts similarity index 92% rename from packages/twenty-server/test/integration/metadata/suites/permission-flag/failing-permission-flag-upsert.integration-spec.ts rename to packages/twenty-server/test/integration/metadata/suites/role-permission-flag/failing-permission-flag-upsert.integration-spec.ts index 0dc275ff9e..96f8e2e7e5 100644 --- a/packages/twenty-server/test/integration/metadata/suites/permission-flag/failing-permission-flag-upsert.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/role-permission-flag/failing-permission-flag-upsert.integration-spec.ts @@ -1,5 +1,5 @@ import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util'; -import { upsertPermissionFlags } from 'test/integration/metadata/suites/permission-flag/utils/upsert-permission-flags.util'; +import { upsertPermissionFlags } from 'test/integration/metadata/suites/role-permission-flag/utils/upsert-permission-flags.util'; import { createOneRole } from 'test/integration/metadata/suites/role/utils/create-one-role.util'; import { deleteOneRole } from 'test/integration/metadata/suites/role/utils/delete-one-role.util'; import { findRoles } from 'test/integration/metadata/suites/role/utils/find-roles.util'; @@ -12,7 +12,7 @@ import { import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; -import { type UpsertPermissionFlagsInput } from 'src/engine/metadata-modules/permission-flag/dtos/upsert-permission-flag-input'; +import { type UpsertPermissionFlagsInput } from 'src/engine/metadata-modules/role-permission-flag/dtos/upsert-permission-flags.input'; type GlobalTestContext = { editableRoleId: string; @@ -23,7 +23,7 @@ type TestContext = { input: (globalContext: GlobalTestContext) => UpsertPermissionFlagsInput; }; -const failingPermissionFlagUpsertTestCases: EachTestingContext[] = +const failingRolePermissionFlagUpsertTestCases: EachTestingContext[] = [ { title: 'when roleId is not a valid UUID', @@ -116,7 +116,7 @@ describe('Permission flag upsert should fail', () => { } }); - it.each(eachTestingContextFilter(failingPermissionFlagUpsertTestCases))( + it.each(eachTestingContextFilter(failingRolePermissionFlagUpsertTestCases))( '$title', async ({ context }) => { const globalContext: GlobalTestContext = { diff --git a/packages/twenty-server/test/integration/metadata/suites/permission-flag/successful-permission-flag-upsert.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/role-permission-flag/successful-permission-flag-upsert.integration-spec.ts similarity index 98% rename from packages/twenty-server/test/integration/metadata/suites/permission-flag/successful-permission-flag-upsert.integration-spec.ts rename to packages/twenty-server/test/integration/metadata/suites/role-permission-flag/successful-permission-flag-upsert.integration-spec.ts index 9db61491e1..1ed625568b 100644 --- a/packages/twenty-server/test/integration/metadata/suites/permission-flag/successful-permission-flag-upsert.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/role-permission-flag/successful-permission-flag-upsert.integration-spec.ts @@ -1,4 +1,4 @@ -import { upsertPermissionFlags } from 'test/integration/metadata/suites/permission-flag/utils/upsert-permission-flags.util'; +import { upsertPermissionFlags } from 'test/integration/metadata/suites/role-permission-flag/utils/upsert-permission-flags.util'; import { createOneRole } from 'test/integration/metadata/suites/role/utils/create-one-role.util'; import { deleteOneRole } from 'test/integration/metadata/suites/role/utils/delete-one-role.util'; import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test'; diff --git a/packages/twenty-server/test/integration/metadata/suites/permission-flag/utils/upsert-permission-flags-query-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/role-permission-flag/utils/upsert-permission-flags-query-factory.util.ts similarity index 80% rename from packages/twenty-server/test/integration/metadata/suites/permission-flag/utils/upsert-permission-flags-query-factory.util.ts rename to packages/twenty-server/test/integration/metadata/suites/role-permission-flag/utils/upsert-permission-flags-query-factory.util.ts index 3ddb215914..348635a349 100644 --- a/packages/twenty-server/test/integration/metadata/suites/permission-flag/utils/upsert-permission-flags-query-factory.util.ts +++ b/packages/twenty-server/test/integration/metadata/suites/role-permission-flag/utils/upsert-permission-flags-query-factory.util.ts @@ -1,11 +1,11 @@ import gql from 'graphql-tag'; import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; -import { type UpsertPermissionFlagsInput } from 'src/engine/metadata-modules/permission-flag/dtos/upsert-permission-flag-input'; +import { type UpsertPermissionFlagsInput } from 'src/engine/metadata-modules/role-permission-flag/dtos/upsert-permission-flags.input'; export type UpsertPermissionFlagsFactoryInput = UpsertPermissionFlagsInput; -const DEFAULT_PERMISSION_FLAG_GQL_FIELDS = ` +const DEFAULT_ROLE_PERMISSION_FLAG_GQL_FIELDS = ` id roleId flag @@ -13,7 +13,7 @@ const DEFAULT_PERMISSION_FLAG_GQL_FIELDS = ` export const upsertPermissionFlagsQueryFactory = ({ input, - gqlFields = DEFAULT_PERMISSION_FLAG_GQL_FIELDS, + gqlFields = DEFAULT_ROLE_PERMISSION_FLAG_GQL_FIELDS, }: PerformMetadataQueryParams) => ({ query: gql` mutation UpsertPermissionFlags($upsertPermissionFlagsInput: UpsertPermissionFlagsInput!) { diff --git a/packages/twenty-server/test/integration/metadata/suites/permission-flag/utils/upsert-permission-flags.util.ts b/packages/twenty-server/test/integration/metadata/suites/role-permission-flag/utils/upsert-permission-flags.util.ts similarity index 83% rename from packages/twenty-server/test/integration/metadata/suites/permission-flag/utils/upsert-permission-flags.util.ts rename to packages/twenty-server/test/integration/metadata/suites/role-permission-flag/utils/upsert-permission-flags.util.ts index c9aa8e2673..bf2cc8115c 100644 --- a/packages/twenty-server/test/integration/metadata/suites/permission-flag/utils/upsert-permission-flags.util.ts +++ b/packages/twenty-server/test/integration/metadata/suites/role-permission-flag/utils/upsert-permission-flags.util.ts @@ -1,14 +1,14 @@ import { type UpsertPermissionFlagsFactoryInput, upsertPermissionFlagsQueryFactory, -} from 'test/integration/metadata/suites/permission-flag/utils/upsert-permission-flags-query-factory.util'; +} from 'test/integration/metadata/suites/role-permission-flag/utils/upsert-permission-flags-query-factory.util'; import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type'; import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util'; import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util'; -import { type PermissionFlagDTO } from 'src/engine/metadata-modules/permission-flag/dtos/permission-flag.dto'; +import { type RolePermissionFlagDTO } from 'src/engine/metadata-modules/role-permission-flag/dtos/role-permission-flag.dto'; export const upsertPermissionFlags = async ({ input, @@ -16,7 +16,7 @@ export const upsertPermissionFlags = async ({ expectToFail = false, token, }: PerformMetadataQueryParams): CommonResponseBody<{ - upsertPermissionFlags: PermissionFlagDTO[]; + upsertPermissionFlags: RolePermissionFlagDTO[]; }> => { const graphqlOperation = upsertPermissionFlagsQueryFactory({ input, diff --git a/packages/twenty-shared/src/constants/SystemPermissionFlag.ts b/packages/twenty-shared/src/constants/SystemPermissionFlag.ts new file mode 100644 index 0000000000..3ecec52dd5 --- /dev/null +++ b/packages/twenty-shared/src/constants/SystemPermissionFlag.ts @@ -0,0 +1,29 @@ +import type { PermissionFlagType } from './PermissionFlagType'; + +export const SystemPermissionFlag = { + API_KEYS_AND_WEBHOOKS: 'beb99803-9a0e-588d-9c4b-3dd14d9cdf21', + WORKSPACE: '86a58859-a539-5fa8-ba18-a6aaddc811b6', + WORKSPACE_MEMBERS: 'ec342166-2df7-53e7-aa9b-64992d6cb35f', + ROLES: '80f1e5ab-484c-5660-a5a1-0ee7413d8190', + DATA_MODEL: 'f0c08013-3c9a-5489-bd45-a641fe5f085d', + SECURITY: '4a8b6016-1d3d-5c94-bbe8-a74c5f32335a', + WORKFLOWS: '6189e7bd-4051-5752-b6b1-5f31358fbaf1', + IMPERSONATE: 'dd2274d1-bbd5-551d-bcf2-3147539cef11', + SSO_BYPASS: '2e9cfdc0-4669-58ab-b241-b6c3c14d96df', + APPLICATIONS: '9faa55f7-2606-566b-9e1d-aefbedbd0136', + MARKETPLACE_APPS: '3a37e2f5-d3e4-56d6-b541-adf0237468ff', + LAYOUTS: '458d1ee5-ba12-5f4e-9d17-6f8c11fcd7fd', + BILLING: '24b66701-ed97-51e4-aa63-794e1b31414c', + AI_SETTINGS: 'e8cc4a30-c120-5925-8f5d-73d6a2247fee', + AI: '1c7f09a7-367d-568b-a0e8-c274eccd6ee3', + VIEWS: 'c68354c4-535d-53a9-848a-e3a6774b5fb8', + UPLOAD_FILE: 'dc6931a9-44bd-5164-a983-3db5db837f54', + DOWNLOAD_FILE: '6edf252f-228e-5e2f-9706-7bab7a796fd3', + SEND_EMAIL_TOOL: '19f382a8-689d-5792-a18c-737f0ce9025f', + HTTP_REQUEST_TOOL: '1bf49cf1-bb8f-5eb9-809a-5f34ea79fc6a', + CODE_INTERPRETER_TOOL: '11f31c3a-7042-54b7-a2dd-ae31dd9e9fa2', + IMPORT_CSV: 'c4912536-a4db-5af9-85ac-be2919c24c5e', + EXPORT_CSV: '88efced9-ca6d-5316-a695-f1b9158e7370', + CONNECTED_ACCOUNTS: 'e5f63b2d-5369-5df6-8f32-a8bd9e79e653', + PROFILE_INFORMATION: '7c13f23f-78d8-5e7d-8963-a7c8772fb4e8', +} as const satisfies Record; diff --git a/packages/twenty-shared/src/constants/index.ts b/packages/twenty-shared/src/constants/index.ts index 2317dfee7b..d0920d2d84 100644 --- a/packages/twenty-shared/src/constants/index.ts +++ b/packages/twenty-shared/src/constants/index.ts @@ -49,6 +49,7 @@ export { RELATION_NESTED_QUERY_KEYWORDS } from './RelationNestedQueriesKeyword'; export { RESERVED_SUBDOMAINS } from './ReservedSubdomains'; export { STANDARD_OBJECT_RECORDS_UNDER_OBJECT_RECORDS_PERMISSIONS } from './StandardObjectRecordsUnderObjectRecordsPermissions'; export { SUBDOMAIN_PATTERN } from './SubdomainPattern'; +export { SystemPermissionFlag } from './SystemPermissionFlag'; export { TWENTY_COMPANIES_BASE_URL } from './TwentyCompaniesBaseUrl'; export { TWENTY_ICONS_BASE_URL } from './TwentyIconsBaseUrl'; export { VIEW_GROUP_VISIBLE_OPTIONS_MAX } from './ViewGroupVisibleOptionsMax'; diff --git a/packages/twenty-shared/src/metadata/constants/all-metadata-name.constant.ts b/packages/twenty-shared/src/metadata/constants/all-metadata-name.constant.ts index feed9014cc..f59df21572 100644 --- a/packages/twenty-shared/src/metadata/constants/all-metadata-name.constant.ts +++ b/packages/twenty-shared/src/metadata/constants/all-metadata-name.constant.ts @@ -21,6 +21,7 @@ export const ALL_METADATA_NAME = { pageLayoutTab: 'pageLayoutTab', commandMenuItem: 'commandMenuItem', navigationMenuItem: 'navigationMenuItem', + rolePermissionFlag: 'rolePermissionFlag', permissionFlag: 'permissionFlag', objectPermission: 'objectPermission', fieldPermission: 'fieldPermission',