Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17a69abdd1 | ||
|
|
d03480472c | ||
|
|
8f3c336e62 | ||
|
|
e7a1448414 | ||
|
|
fc74938d7b | ||
|
|
d5e65c563e | ||
|
|
5c8ddb0c12 | ||
|
|
db0547f503 | ||
|
|
01535a3b3e | ||
|
|
45ac3e8218 | ||
|
|
cd09690d5d |
@@ -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!
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -500,7 +500,7 @@ export default {
|
||||
1
|
||||
]
|
||||
},
|
||||
"PermissionFlag": {
|
||||
"RolePermissionFlag": {
|
||||
"id": [
|
||||
3
|
||||
],
|
||||
|
||||
@@ -51,6 +51,8 @@ RUN npx nx run twenty-server:lingui:extract && \
|
||||
|
||||
RUN npx nx run twenty-server:build
|
||||
|
||||
RUN node -e "const fs = require('fs'); const manifests = ['/app/packages/twenty-shared/package.json', '/app/packages/twenty-server/package.json', '/app/packages/twenty-emails/package.json', '/app/packages/twenty-client-sdk/package.json']; for (const manifestPath of manifests) { if (!fs.existsSync(manifestPath)) { throw new Error(`Missing workspace manifest: ${manifestPath}`); } const content = fs.readFileSync(manifestPath, 'utf8'); if (content.trim().length === 0) { throw new Error(`Empty workspace manifest: ${manifestPath}`); } JSON.parse(content); } console.log('Workspace manifests are valid.');"
|
||||
|
||||
# Clean server build output (type declarations and compiled tests are not needed at runtime;
|
||||
# source maps are kept because twenty-infra extracts them from the image for Sentry uploads)
|
||||
RUN find /app/packages/twenty-server/dist -name '*.d.ts' -delete \
|
||||
|
||||
@@ -1,6 +1,27 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
validate_workspace_manifest() {
|
||||
manifest_path="$1"
|
||||
|
||||
if [ ! -s "${manifest_path}" ]; then
|
||||
echo "ERROR: Invalid runtime workspace manifest (missing or empty): ${manifest_path}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! node -e "JSON.parse(require('fs').readFileSync(process.argv[1], 'utf8'))" "${manifest_path}" >/dev/null 2>&1; then
|
||||
echo "ERROR: Invalid runtime workspace manifest (malformed JSON): ${manifest_path}" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
validate_workspace_manifests() {
|
||||
validate_workspace_manifest "/app/packages/twenty-shared/package.json" || return 1
|
||||
validate_workspace_manifest "/app/packages/twenty-server/package.json" || return 1
|
||||
validate_workspace_manifest "/app/packages/twenty-emails/package.json" || return 1
|
||||
validate_workspace_manifest "/app/packages/twenty-client-sdk/package.json" || return 1
|
||||
}
|
||||
|
||||
setup_and_migrate_db() {
|
||||
if [ "${DISABLE_DB_MIGRATIONS}" = "true" ]; then
|
||||
echo "Database setup and migrations are disabled, skipping..."
|
||||
@@ -45,6 +66,11 @@ register_background_jobs() {
|
||||
fi
|
||||
}
|
||||
|
||||
if ! validate_workspace_manifests; then
|
||||
echo "ERROR: Aborting startup because runtime workspace manifests are invalid." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
setup_and_migrate_db
|
||||
register_background_jobs
|
||||
|
||||
|
||||
@@ -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()`.
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -13178,6 +13178,11 @@ msgstr "Rolnaam"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "Rolnaam kan nie leeg wees nie"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "اسم الدور"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "لا يمكن أن يكون اسم الدور فارغًا"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "Nom del rol"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "El nom del rol no pot estar buit"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "Název role"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "Název role nesmí být prázdný"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "Rollenavn"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "Rollens navn kan ikke være tomt"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "Rollenname"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "Rollenname darf nicht leer sein"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "Όνομα ρόλου"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "Το όνομα του ρόλου δεν μπορεί να είναι κενό"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13196,6 +13196,11 @@ msgstr "Role name"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "Role name cannot be empty"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr "role permission flag"
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "Nombre del rol"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "El nombre del rol no puede estar vacío"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "Roolin nimi"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "Roolinimi ei saa olla tyhjä"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "Nom du rôle"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "Le nom du rôle ne peut pas être vide"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -13178,6 +13178,11 @@ msgstr "שם התפקיד"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "שם תפקיד לא יכול להיות ריק"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "Szerepkör neve"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "A szerep neve nem lehet üres"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "Nome del ruolo"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "Il nome del ruolo non può essere vuoto"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "役割名"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "役割名を空にすることはできません"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "역할 이름"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "역할 이름은 비워 둘 수 없습니다"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "Rolnaam"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "Rolnaam mag niet leeg zijn"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "Rollenavn"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "Rollens navn kan ikke være tomt"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "Nazwa roli"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "Nazwa roli nie może być pusta"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13173,6 +13173,11 @@ msgstr ""
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "Nome do cargo"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "O nome da função não pode estar vazio"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "Nome do papel"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "O nome do papel não pode estar vazio"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "Numele rolului"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "Numele rolului nu poate fi gol"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
Binary file not shown.
@@ -13178,6 +13178,11 @@ msgstr "Назив улоге"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "Назив улоге не може бити празан"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13180,6 +13180,11 @@ msgstr "Rollnamn"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "Rollnamn får inte vara tomt"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "Rol adı"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "Rol adı boş olamaz"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "Назва ролі"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "Назва ролі не може бути порожньою"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "Tên vai trò"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "Tên vai trò không thể để trống"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "角色名称"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "角色名称不能为空"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
@@ -13178,6 +13178,11 @@ msgstr "角色名稱"
|
||||
msgid "Role name cannot be empty"
|
||||
msgstr "角色名稱不能為空"
|
||||
|
||||
#. js-lingui-id: VMX1Oy
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role permission flag"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: jLNGne
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "role target"
|
||||
|
||||
+1
@@ -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`,
|
||||
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const PERMISSION_FLAG_FRAGMENT = gql`
|
||||
fragment PermissionFlagFragment on PermissionFlag {
|
||||
id
|
||||
flag
|
||||
roleId
|
||||
}
|
||||
`;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const ROLE_PERMISSION_FLAG_FRAGMENT = gql`
|
||||
fragment RolePermissionFlagFragment on RolePermissionFlag {
|
||||
id
|
||||
flag
|
||||
roleId
|
||||
}
|
||||
`;
|
||||
+3
-3
@@ -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
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { useContext, useId, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { styled } from '@linaria/react';
|
||||
import { AppTooltip, TooltipDelay, TooltipPosition } from 'twenty-ui/display';
|
||||
import { ThemeContext } from 'twenty-ui/theme-constants';
|
||||
import { isDefined, formatToShortNumber } from 'twenty-shared/utils';
|
||||
|
||||
import { useNumberFormat } from '@/localization/hooks/useNumberFormat';
|
||||
import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition';
|
||||
@@ -10,10 +13,13 @@ import {
|
||||
} from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { SETTINGS_FIELD_CURRENCY_CODES } from '@/settings/data-model/constants/SettingsFieldCurrencyCodes';
|
||||
import { EllipsisDisplay } from '@/ui/field/display/components/EllipsisDisplay';
|
||||
import { isDefined, formatToShortNumber } from 'twenty-shared/utils';
|
||||
import { DEFAULT_DECIMAL_VALUE } from '~/utils/format/formatNumber';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
import { ThemeContext } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledCurrencyIconContainer = styled.span`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
type CurrencyDisplayProps = {
|
||||
currencyValue: FieldCurrencyValue | null | undefined;
|
||||
@@ -57,7 +63,7 @@ export const CurrencyDisplay = ({
|
||||
<EllipsisDisplay>
|
||||
{shouldShowCurrencyTooltip && (
|
||||
<>
|
||||
<span
|
||||
<StyledCurrencyIconContainer
|
||||
id={tooltipAnchorId}
|
||||
onMouseEnter={() => setShouldRenderTooltip(true)}
|
||||
onMouseLeave={() => setShouldRenderTooltip(false)}
|
||||
@@ -67,7 +73,7 @@ export const CurrencyDisplay = ({
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
</span>{' '}
|
||||
</StyledCurrencyIconContainer>{' '}
|
||||
</>
|
||||
)}
|
||||
{amountToDisplay !== null
|
||||
|
||||
@@ -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;
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+3
-1
@@ -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<ObjectLiteral>[] = [
|
||||
ViewFilterGroupEntity,
|
||||
FieldPermissionEntity,
|
||||
ObjectPermissionEntity,
|
||||
PermissionFlagEntity,
|
||||
RolePermissionFlagEntity,
|
||||
RoleTargetEntity,
|
||||
SearchFieldMetadataEntity,
|
||||
RowLevelPermissionPredicateEntity,
|
||||
@@ -81,6 +82,7 @@ const WORKSPACE_RELATED_ENTITIES: EntityTarget<ObjectLiteral>[] = [
|
||||
// Level 2: Children that depend on core entities
|
||||
FieldMetadataEntity,
|
||||
PageLayoutEntity,
|
||||
PermissionFlagEntity,
|
||||
SkillEntity,
|
||||
LogicFunctionEntity,
|
||||
|
||||
|
||||
+102
@@ -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<void> {
|
||||
// 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<void> {
|
||||
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"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+69
@@ -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<void> {
|
||||
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<void> {
|
||||
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"`);
|
||||
}
|
||||
}
|
||||
+55
@@ -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<void> {
|
||||
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<void> {
|
||||
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"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
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';
|
||||
|
||||
const MESSAGE_CHANNEL_INDEX_NAME =
|
||||
'IDX_MESSAGE_CHANNEL_WORKSPACE_ID_SYNC_ENABLED_SYNC_STAGE';
|
||||
const CALENDAR_CHANNEL_INDEX_NAME =
|
||||
'IDX_CALENDAR_CHANNEL_WORKSPACE_ID_SYNC_ENABLED_SYNC_STAGE';
|
||||
|
||||
@RegisteredInstanceCommand('2.6.0', 1798000010000)
|
||||
export class AddChannelSyncStageIndexesFastInstanceCommand
|
||||
implements FastInstanceCommand
|
||||
{
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX IF NOT EXISTS "${MESSAGE_CHANNEL_INDEX_NAME}" ON "core"."messageChannel" ("workspaceId", "isSyncEnabled", "syncStage")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX IF NOT EXISTS "${CALENDAR_CHANNEL_INDEX_NAME}" ON "core"."calendarChannel" ("workspaceId", "isSyncEnabled", "syncStage")`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`DROP INDEX IF EXISTS "core"."${CALENDAR_CHANNEL_INDEX_NAME}"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX IF EXISTS "core"."${MESSAGE_CHANNEL_INDEX_NAME}"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+88
@@ -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<void> {
|
||||
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<void> {}
|
||||
|
||||
public async down(_queryRunner: QueryRunner): Promise<void> {}
|
||||
}
|
||||
+10
@@ -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';
|
||||
@@ -44,6 +48,7 @@ import { EncryptTotpSecretsSlowInstanceCommand } from 'src/database/commands/upg
|
||||
import { AddSubFieldNameToViewSortFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1778502963794-add-sub-field-name-to-view-sort';
|
||||
import { DropPostgresCredentialsTableFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1798500000000-drop-postgres-credentials-table';
|
||||
import { AddRelationTargetFieldMetadataIdToViewFilterFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1798000005000-add-relation-target-field-metadata-id-to-view-filter';
|
||||
import { AddChannelSyncStageIndexesFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1798000010000-add-channel-sync-stage-indexes';
|
||||
|
||||
export const INSTANCE_COMMANDS = [
|
||||
AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand,
|
||||
@@ -77,6 +82,10 @@ export const INSTANCE_COMMANDS = [
|
||||
RemoveUserDefaultAvatarUrlFastInstanceCommand,
|
||||
TransformApplicationVariableToSyncableEntityFastInstanceCommand,
|
||||
BackfillApplicationVariableUniversalIdentifierSlowInstanceCommand,
|
||||
RenamePermissionFlagToRolePermissionFlagFastInstanceCommand,
|
||||
PermissionFlagSyncableEntityFastInstanceCommand,
|
||||
LinkRolePermissionFlagToPermissionFlagFastInstanceCommand,
|
||||
BackfillRolePermissionFlagPermissionFlagIdSlowInstanceCommand,
|
||||
AddEmailGroupChannelTypeFastInstanceCommand,
|
||||
AddApplicationIdToPublicDomainFastInstanceCommand,
|
||||
AddIsInternalMessagesImportEnabledFastInstanceCommand,
|
||||
@@ -90,4 +99,5 @@ export const INSTANCE_COMMANDS = [
|
||||
AddSubFieldNameToViewSortFastInstanceCommand,
|
||||
DropPostgresCredentialsTableFastInstanceCommand,
|
||||
AddRelationTargetFieldMetadataIdToViewFilterFastInstanceCommand,
|
||||
AddChannelSyncStageIndexesFastInstanceCommand,
|
||||
];
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { type McpToolAnnotations } from 'src/engine/api/mcp/types/mcp-tool-annotations.type';
|
||||
|
||||
export const MCP_CLOSED_WORLD_READ_ONLY_TOOL_ANNOTATIONS: McpToolAnnotations = {
|
||||
readOnlyHint: true,
|
||||
openWorldHint: false,
|
||||
destructiveHint: false,
|
||||
};
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { type McpToolAnnotations } from 'src/engine/api/mcp/types/mcp-tool-annotations.type';
|
||||
|
||||
export const MCP_EXECUTE_TOOL_ANNOTATIONS: McpToolAnnotations = {
|
||||
readOnlyHint: false,
|
||||
openWorldHint: true,
|
||||
destructiveHint: true,
|
||||
};
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { type McpToolAnnotations } from 'src/engine/api/mcp/types/mcp-tool-annotations.type';
|
||||
|
||||
export const MCP_OPEN_WORLD_READ_ONLY_TOOL_ANNOTATIONS: McpToolAnnotations = {
|
||||
readOnlyHint: true,
|
||||
openWorldHint: true,
|
||||
destructiveHint: false,
|
||||
};
|
||||
+18
-1
@@ -2,12 +2,16 @@ import { HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { JSON_RPC_ERROR_CODE } from 'src/engine/api/mcp/constants/json-rpc-error-code.const';
|
||||
import { MCP_CLOSED_WORLD_READ_ONLY_TOOL_ANNOTATIONS } from 'src/engine/api/mcp/constants/mcp-closed-world-read-only-tool-annotations.const';
|
||||
import { MCP_EXECUTE_TOOL_ANNOTATIONS } from 'src/engine/api/mcp/constants/mcp-execute-tool-annotations.const';
|
||||
import { MCP_OPEN_WORLD_READ_ONLY_TOOL_ANNOTATIONS } from 'src/engine/api/mcp/constants/mcp-open-world-read-only-tool-annotations.const';
|
||||
import { MCP_PROTOCOL_VERSION } from 'src/engine/api/mcp/constants/mcp-protocol-version.const';
|
||||
import { MCP_SERVER_INFO } from 'src/engine/api/mcp/constants/mcp-server-info.const';
|
||||
import { MCP_SERVER_INSTRUCTIONS } from 'src/engine/api/mcp/constants/mcp-server-instructions.const';
|
||||
import { type JsonRpc } from 'src/engine/api/mcp/dtos/json-rpc';
|
||||
import { McpProtocolService } from 'src/engine/api/mcp/services/mcp-protocol.service';
|
||||
import { McpToolExecutorService } from 'src/engine/api/mcp/services/mcp-tool-executor.service';
|
||||
import { type McpToolAnnotations } from 'src/engine/api/mcp/types/mcp-tool-annotations.type';
|
||||
import { type FlatApiKey } from 'src/engine/core-modules/api-key/types/flat-api-key.type';
|
||||
import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api-key-role.service';
|
||||
import { EXECUTE_TOOL_TOOL_NAME } from 'src/engine/core-modules/tool-provider/tools/execute-tool.tool';
|
||||
@@ -41,7 +45,18 @@ describe('McpProtocolService', () => {
|
||||
EXECUTE_TOOL_TOOL_NAME,
|
||||
LOAD_SKILL_TOOL_NAME,
|
||||
'search_help_center',
|
||||
];
|
||||
] as const;
|
||||
|
||||
const EXPECTED_MCP_TOOL_ANNOTATIONS: Record<
|
||||
(typeof EXPECTED_MCP_TOOL_NAMES)[number],
|
||||
McpToolAnnotations
|
||||
> = {
|
||||
[GET_TOOL_CATALOG_TOOL_NAME]: MCP_CLOSED_WORLD_READ_ONLY_TOOL_ANNOTATIONS,
|
||||
[LEARN_TOOLS_TOOL_NAME]: MCP_CLOSED_WORLD_READ_ONLY_TOOL_ANNOTATIONS,
|
||||
[EXECUTE_TOOL_TOOL_NAME]: MCP_EXECUTE_TOOL_ANNOTATIONS,
|
||||
[LOAD_SKILL_TOOL_NAME]: MCP_CLOSED_WORLD_READ_ONLY_TOOL_ANNOTATIONS,
|
||||
search_help_center: MCP_OPEN_WORLD_READ_ONLY_TOOL_ANNOTATIONS,
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockSearchHelpCenterTool = {
|
||||
@@ -255,6 +270,7 @@ describe('McpProtocolService', () => {
|
||||
name,
|
||||
expect.objectContaining({
|
||||
description: expect.any(String),
|
||||
annotations: EXPECTED_MCP_TOOL_ANNOTATIONS[name],
|
||||
execute: expect.any(Function),
|
||||
}),
|
||||
]),
|
||||
@@ -294,6 +310,7 @@ describe('McpProtocolService', () => {
|
||||
name,
|
||||
expect.objectContaining({
|
||||
description: expect.any(String),
|
||||
annotations: EXPECTED_MCP_TOOL_ANNOTATIONS[name],
|
||||
}),
|
||||
]),
|
||||
),
|
||||
|
||||
+3
@@ -1,4 +1,5 @@
|
||||
import { JSON_RPC_ERROR_CODE } from 'src/engine/api/mcp/constants/json-rpc-error-code.const';
|
||||
import { MCP_CLOSED_WORLD_READ_ONLY_TOOL_ANNOTATIONS } from 'src/engine/api/mcp/constants/mcp-closed-world-read-only-tool-annotations.const';
|
||||
import {
|
||||
MCP_PROGRESS_NOTIFICATION_METHOD,
|
||||
TOOL_CALL_PROGRESS_TOKEN_PREFIX,
|
||||
@@ -24,6 +25,7 @@ describe('McpToolExecutorService', () => {
|
||||
required: ['query'],
|
||||
},
|
||||
},
|
||||
annotations: MCP_CLOSED_WORLD_READ_ONLY_TOOL_ANNOTATIONS,
|
||||
},
|
||||
} as any;
|
||||
|
||||
@@ -42,6 +44,7 @@ describe('McpToolExecutorService', () => {
|
||||
properties: { query: { type: 'string' } },
|
||||
required: ['query'],
|
||||
},
|
||||
annotations: MCP_CLOSED_WORLD_READ_ONLY_TOOL_ANNOTATIONS,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -4,12 +4,16 @@ import { type ToolSet, zodSchema } from 'ai';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { JSON_RPC_ERROR_CODE } from 'src/engine/api/mcp/constants/json-rpc-error-code.const';
|
||||
import { MCP_CLOSED_WORLD_READ_ONLY_TOOL_ANNOTATIONS } from 'src/engine/api/mcp/constants/mcp-closed-world-read-only-tool-annotations.const';
|
||||
import { MCP_EXCLUDED_TOOL_NAMES } from 'src/engine/api/mcp/constants/mcp-excluded-tool-names.const';
|
||||
import { MCP_EXECUTE_TOOL_ANNOTATIONS } from 'src/engine/api/mcp/constants/mcp-execute-tool-annotations.const';
|
||||
import { MCP_OPEN_WORLD_READ_ONLY_TOOL_ANNOTATIONS } from 'src/engine/api/mcp/constants/mcp-open-world-read-only-tool-annotations.const';
|
||||
import { MCP_PROTOCOL_VERSION } from 'src/engine/api/mcp/constants/mcp-protocol-version.const';
|
||||
import { MCP_SERVER_INFO } from 'src/engine/api/mcp/constants/mcp-server-info.const';
|
||||
import { MCP_SERVER_INSTRUCTIONS } from 'src/engine/api/mcp/constants/mcp-server-instructions.const';
|
||||
import { type JsonRpc } from 'src/engine/api/mcp/dtos/json-rpc';
|
||||
import { McpToolExecutorService } from 'src/engine/api/mcp/services/mcp-tool-executor.service';
|
||||
import { type McpToolAnnotations } from 'src/engine/api/mcp/types/mcp-tool-annotations.type';
|
||||
import { wrapJsonRpcResponse } from 'src/engine/api/mcp/utils/wrap-jsonrpc-response.util';
|
||||
import { type FlatApiKey } from 'src/engine/core-modules/api-key/types/flat-api-key.type';
|
||||
import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api-key-role.service';
|
||||
@@ -41,6 +45,33 @@ import { type FlatWorkspace } from 'src/engine/core-modules/workspace/types/flat
|
||||
import { SkillService } from 'src/engine/metadata-modules/skill/skill.service';
|
||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||
|
||||
type McpAnnotatedTool = ToolSet[string] & {
|
||||
annotations: McpToolAnnotations;
|
||||
};
|
||||
|
||||
const MCP_PRELOADED_TOOL_ANNOTATIONS: Record<string, McpToolAnnotations> = {
|
||||
search_help_center: MCP_OPEN_WORLD_READ_ONLY_TOOL_ANNOTATIONS,
|
||||
};
|
||||
|
||||
const annotatePreloadedMcpTools = (toolSet: ToolSet): ToolSet =>
|
||||
Object.fromEntries(
|
||||
Object.entries(toolSet).map(([name, toolDefinition]) => {
|
||||
const annotations = MCP_PRELOADED_TOOL_ANNOTATIONS[name];
|
||||
|
||||
if (!isDefined(annotations)) {
|
||||
throw new Error(`Missing MCP annotations for preloaded tool "${name}"`);
|
||||
}
|
||||
|
||||
return [
|
||||
name,
|
||||
{
|
||||
...toolDefinition,
|
||||
annotations,
|
||||
} as McpAnnotatedTool,
|
||||
];
|
||||
}),
|
||||
);
|
||||
|
||||
@Injectable()
|
||||
export class McpProtocolService {
|
||||
constructor(
|
||||
@@ -121,7 +152,7 @@ export class McpProtocolService {
|
||||
);
|
||||
|
||||
return {
|
||||
...preloadedTools,
|
||||
...annotatePreloadedMcpTools(preloadedTools),
|
||||
[GET_TOOL_CATALOG_TOOL_NAME]: {
|
||||
...createGetToolCatalogTool(this.toolRegistry, workspace.id, roleId, {
|
||||
userId: options?.userId,
|
||||
@@ -129,7 +160,8 @@ export class McpProtocolService {
|
||||
excludeTools: MCP_EXCLUDED_TOOL_NAMES,
|
||||
}),
|
||||
inputSchema: zodSchema(getToolCatalogInputSchema),
|
||||
},
|
||||
annotations: MCP_CLOSED_WORLD_READ_ONLY_TOOL_ANNOTATIONS,
|
||||
} as McpAnnotatedTool,
|
||||
[LEARN_TOOLS_TOOL_NAME]: {
|
||||
...createLearnToolsTool(
|
||||
this.toolRegistry,
|
||||
@@ -137,13 +169,15 @@ export class McpProtocolService {
|
||||
MCP_EXCLUDED_TOOL_NAMES,
|
||||
),
|
||||
inputSchema: zodSchema(learnToolsInputSchema),
|
||||
},
|
||||
annotations: MCP_CLOSED_WORLD_READ_ONLY_TOOL_ANNOTATIONS,
|
||||
} as McpAnnotatedTool,
|
||||
[EXECUTE_TOOL_TOOL_NAME]: {
|
||||
...createExecuteToolTool(this.toolRegistry, toolContext, {
|
||||
excludeTools: MCP_EXCLUDED_TOOL_NAMES,
|
||||
}),
|
||||
inputSchema: executeToolInputSchema,
|
||||
},
|
||||
annotations: MCP_EXECUTE_TOOL_ANNOTATIONS,
|
||||
} as McpAnnotatedTool,
|
||||
[LOAD_SKILL_TOOL_NAME]: {
|
||||
...createLoadSkillTool(
|
||||
(names) =>
|
||||
@@ -157,7 +191,8 @@ export class McpProtocolService {
|
||||
},
|
||||
),
|
||||
inputSchema: zodSchema(loadSkillInputSchema),
|
||||
},
|
||||
annotations: MCP_CLOSED_WORLD_READ_ONLY_TOOL_ANNOTATIONS,
|
||||
} as McpAnnotatedTool,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,18 @@ import {
|
||||
MCP_PROGRESS_NOTIFICATION_METHOD,
|
||||
TOOL_CALL_PROGRESS_TOKEN_PREFIX,
|
||||
} from 'src/engine/api/mcp/constants/mcp-progress-notification.const';
|
||||
import { type McpToolAnnotations } from 'src/engine/api/mcp/types/mcp-tool-annotations.type';
|
||||
import { wrapJsonRpcResponse } from 'src/engine/api/mcp/utils/wrap-jsonrpc-response.util';
|
||||
|
||||
type McpToolDefinition = ToolSet[string] & {
|
||||
annotations?: McpToolAnnotations;
|
||||
};
|
||||
|
||||
const unwrapJsonSchema = (schema: unknown) =>
|
||||
schema && typeof schema === 'object' && 'jsonSchema' in schema
|
||||
? schema.jsonSchema
|
||||
: schema;
|
||||
|
||||
@Injectable()
|
||||
export class McpToolExecutorService {
|
||||
async handleToolCall(
|
||||
@@ -76,20 +86,18 @@ export class McpToolExecutorService {
|
||||
const toolsArray = Object.entries(toolSet)
|
||||
.filter(([, def]) => !!def.inputSchema)
|
||||
.map(([name, def]) => {
|
||||
const toolDefinition = def as McpToolDefinition;
|
||||
// Unwrap the AI SDK's jsonSchema wrapper if present
|
||||
// The AI SDK serializes schemas as { jsonSchema: {...} } but MCP expects {...} directly
|
||||
const inputSchema = def.inputSchema;
|
||||
const unwrappedSchema =
|
||||
inputSchema &&
|
||||
typeof inputSchema === 'object' &&
|
||||
'jsonSchema' in inputSchema
|
||||
? inputSchema.jsonSchema
|
||||
: inputSchema;
|
||||
const inputSchema = unwrapJsonSchema(toolDefinition.inputSchema);
|
||||
|
||||
return {
|
||||
name,
|
||||
description: def.description,
|
||||
inputSchema: unwrappedSchema,
|
||||
description: toolDefinition.description,
|
||||
inputSchema,
|
||||
...(isDefined(toolDefinition.annotations) && {
|
||||
annotations: toolDefinition.annotations,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export type McpToolAnnotations = {
|
||||
readOnlyHint: boolean;
|
||||
openWorldHint: boolean;
|
||||
destructiveHint: boolean;
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import { type Query } from 'src/engine/api/rest/core/types/query.type';
|
||||
import { RestApiException } from 'src/engine/api/rest/errors/RestApiException';
|
||||
import { type RequestContext } from 'src/engine/api/rest/types/RequestContext';
|
||||
import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export enum GraphqlApiType {
|
||||
CORE = 'core',
|
||||
@@ -41,10 +42,14 @@ export class RestApiService {
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
throw new RestApiException(err.response.data.errors);
|
||||
if (isDefined(err.response?.data?.errors)) {
|
||||
throw new RestApiException(err.response.data.errors);
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (response.data.errors?.length) {
|
||||
if (isDefined(response.data.errors) && response.data.errors.length > 0) {
|
||||
throw new RestApiException(response.data.errors);
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user