From 4996f3dd2860bebc6e55f3269f2e39bc3ba6eedb Mon Sep 17 00:00:00 2001
From: Paul Rastoin <45004772+prastoin@users.noreply.github.com>
Date: Tue, 9 Dec 2025 14:09:48 +0100
Subject: [PATCH] Finalize twenty standard app as workspace migration object
and fields (#16353)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
# Introduction
Related to https://github.com/twentyhq/core-team-issues/issues/1995
In this PR we're fixing the remaining object/fields validation errors
resulting from standard objects and fields now passing a validation that
wasn't when using the sync metadata
## Key Changes
- **Field naming**: Renamed `iCalUID` to `iCalUid` for consistent
camelCase convention across calendar events
- **Enum standardization**: Uppercased enum values for message channels
(email→EMAIL), message participants (from→FROM, to→TO, cc→CC, bcc→BCC),
and message direction (incoming→INCOMING, outgoing→OUTGOING)
- **Label simplification**: Removed example values from workspace member
number format labels for cleaner UI
- **Migration infrastructure**: Added `isSystemBuild` flag throughout
field metadata service pipeline to allow system-level updates of
standard fields that bypass normal restrictions
## Migrating the existing data
We've created an upgrade command that will identify using the existing
object and field standard id field that needs to be updated, even though
the sync metadata still in usage could have fix them ( and the goal is
to deprecate it by the end of the sprint )
We will call the updateOneField for each of them, we're passing by the
field service in order to battle test what are going to be the temporary
way to handle standard migrations when we will start deprecating the
sync metadata but haven't still refactored the v2 workspace migration to
be workspace agnostic
## Twenty eng migration
Tested the whole migration + upgrade on twenty eng
Here are generated workspace migration
Records are handled natively gracefully too
### ICalUid
```json
{
"status": "success",
"workspaceMigration": {
"relatedFlatEntityMapsKeys": [
"flatFieldMetadataMaps",
"flatIndexMaps",
"flatViewFilterMaps",
"flatViewGroupMaps",
"flatViewMaps",
"flatViewFieldMaps",
"flatObjectMetadataMaps"
],
"actions": [
{
"type": "update_field",
"fieldMetadataId": "",
"objectMetadataId": "",
"updates": [
{
"from": "iCalUID",
"to": "iCalUid",
"property": "name"
}
]
}
],
"workspaceId": ""
}
}
```
### Incoming Outgoing
None as already caps in database somehow
```json
{
"status": "success",
"workspaceMigration": {
"relatedFlatEntityMapsKeys": [
"flatFieldMetadataMaps",
"flatIndexMaps",
"flatViewFilterMaps",
"flatViewGroupMaps",
"flatViewMaps",
"flatViewFieldMaps",
"flatObjectMetadataMaps"
],
"actions": [],
"workspaceId": ""
}
}
```
### EMAIL
```json
{
"status": "success",
"workspaceMigration": {
"relatedFlatEntityMapsKeys": [
"flatFieldMetadataMaps",
"flatIndexMaps",
"flatViewFilterMaps",
"flatViewGroupMaps",
"flatViewMaps",
"flatViewFieldMaps",
"flatObjectMetadataMaps"
],
"actions": [
{
"type": "update_field",
"fieldMetadataId": "",
"objectMetadataId": "",
"updates": [
{
"from": "'email'",
"to": "'EMAIL'",
"property": "defaultValue"
},
{
"from": [
{
"color": "green",
"id": "",
"label": "Email",
"position": 0,
"value": "email"
},
{
"color": "blue",
"id": "",
"label": "SMS",
"position": 1,
"value": "sms"
}
],
"to": [
{
"color": "green",
"id": "",
"label": "Email",
"position": 0,
"value": "EMAIL"
},
{
"color": "blue",
"id": "",
"label": "SMS",
"position": 1,
"value": "SMS"
}
],
"property": "options"
}
]
}
],
"workspaceId": "e"
}
}
```
### MessageParticipantRole
```json
{
"status": "success",
"workspaceMigration": {
"relatedFlatEntityMapsKeys": [
"flatFieldMetadataMaps",
"flatIndexMaps",
"flatViewFilterMaps",
"flatViewGroupMaps",
"flatViewMaps",
"flatViewFieldMaps",
"flatObjectMetadataMaps"
],
"actions": [
{
"type": "update_field",
"fieldMetadataId": "",
"objectMetadataId": "",
"updates": [
{
"from": "'from'",
"to": "'FROM'",
"property": "defaultValue"
},
{
"from": [
{
"color": "green",
"id": "",
"label": "From",
"position": 0,
"value": "from"
},
{
"color": "blue",
"id": "",
"label": "To",
"position": 1,
"value": "to"
},
{
"color": "orange",
"id": "",
"label": "Cc",
"position": 2,
"value": "cc"
},
{
"color": "red",
"id": "",
"label": "Bcc",
"position": 3,
"value": "bcc"
}
],
"to": [
{
"color": "green",
"id": "",
"label": "From",
"position": 0,
"value": "FROM"
},
{
"color": "blue",
"id": "",
"label": "To",
"position": 1,
"value": "TO"
},
{
"color": "orange",
"id": "",
"label": "Cc",
"position": 2,
"value": "CC"
},
{
"color": "red",
"id": "",
"label": "Bcc",
"position": 3,
"value": "BCC"
}
],
"property": "options"
}
]
}
],
"workspaceId": ""
}
}
```
### Workspace member number format labels
```json
{
"status": "success",
"workspaceMigration": {
"relatedFlatEntityMapsKeys": [
"flatFieldMetadataMaps",
"flatIndexMaps",
"flatViewFilterMaps",
"flatViewGroupMaps",
"flatViewMaps",
"flatViewFieldMaps",
"flatObjectMetadataMaps"
],
"actions": [
{
"type": "update_field",
"fieldMetadataId": "",
"objectMetadataId": "",
"updates": [
{
"from": [
{
"color": "turquoise",
"id": "",
"label": "System",
"position": 0,
"value": "SYSTEM"
},
{
"color": "blue",
"id": "",
"label": "Commas and dot (1,234.56)",
"position": 1,
"value": "COMMAS_AND_DOT"
},
{
"color": "green",
"id": "",
"label": "Spaces and comma (1 234,56)",
"position": 2,
"value": "SPACES_AND_COMMA"
},
{
"color": "orange",
"id": "",
"label": "Dots and comma (1.234,56)",
"position": 3,
"value": "DOTS_AND_COMMA"
},
{
"color": "purple",
"id": "",
"label": "Apostrophe and dot (1'234.56)",
"position": 4,
"value": "APOSTROPHE_AND_DOT"
}
],
"to": [
{
"color": "turquoise",
"id": "",
"label": "System",
"position": 0,
"value": "SYSTEM"
},
{
"color": "blue",
"id": "",
"label": "Commas and dot",
"position": 1,
"value": "COMMAS_AND_DOT"
},
{
"color": "green",
"id": "",
"label": "Spaces and comma",
"position": 2,
"value": "SPACES_AND_COMMA"
},
{
"color": "orange",
"id": "",
"label": "Dots and comma",
"position": 3,
"value": "DOTS_AND_COMMA"
},
{
"color": "purple",
"id": "",
"label": "Apostrophe and dot",
"position": 4,
"value": "APOSTROPHE_AND_DOT"
}
],
"property": "options"
}
]
}
],
"workspaceId": ""
}
}
```
---
.../emails/components/EmailThreadMessage.tsx | 3 +-
.../emails/types/EmailParticipantRole.ts | 1 -
.../types/EmailThreadMessageParticipant.ts | 4 +-
.../getDisplayNameFromParticipant.test.ts | 21 +-
.../useEmailThreadInCommandMenu.test.tsx | 14 +-
.../hooks/useEmailThreadInCommandMenu.ts | 3 +-
.../utils/computeMetadataNameFromLabel.ts | 2 +-
.../generated/mock-metadata-query-result.ts | 4 +-
...grate-standard-invalid-entities.command.ts | 380 ++++++++++++++++++
.../1-13-upgrade-version-command.module.ts | 5 +
.../upgrade.command.ts | 3 +
.../services/timeline-messaging.service.ts | 5 +-
.../utils/filter-active-participants.util.ts | 6 +-
...m-create-agent-input-to-flat-agent.util.ts | 4 +-
.../services/field-metadata.service.ts | 1 +
...e-and-related-flat-field-to-update.util.ts | 7 +-
...field-input-to-flat-field-metadata.util.ts | 3 +
...-relation-flat-field-metadata-pair.util.ts | 6 +-
...ld-metadata-name-synced-with-label.util.ts | 15 +-
.../utils/sanitize-raw-update-field-input.ts | 4 +-
...-metadata-names-synced-with-labels.util.ts | 17 +-
...at-object-metadata-name-and-labels.util.ts | 8 +-
...-metadata-name-from-label-or-throw.util.ts | 2 +-
.../decorators/workspace-field.decorator.ts | 3 +-
.../workspace-relation.decorator.ts | 7 +-
.../calendar-event-data-seeds.constant.ts | 6 +-
.../message-channel-data-seeds.constant.ts | 13 +-
...message-participant-data-seeds.constant.ts | 8 +-
.../constants/standard-object.constant.ts | 4 +-
...event-standard-flat-field-metadata.util.ts | 4 +-
...ation-standard-flat-field-metadata.util.ts | 17 +-
...annel-standard-flat-field-metadata.util.ts | 17 +-
...ipant-standard-flat-field-metadata.util.ts | 31 +-
...ember-standard-flat-field-metadata.util.ts | 26 +-
.../flat-field-metadata-validator.service.ts | 10 +-
.../flat-object-metadata-validator.service.ts | 2 +
.../constants/standard-field-ids.ts | 2 +-
.../drivers/caldav/lib/caldav.client.ts | 2 +-
.../format-google-calendar-event.util.spec.ts | 2 +-
.../format-google-calendar-event.util.ts | 4 +-
...rmat-microsoft-calendar-event.util.spec.ts | 4 +-
.../format-microsoft-calendar-event.util.ts | 4 +-
.../services/calendar-save-events.service.ts | 24 +-
.../utils/calendar-event-mapper.util.ts | 2 +-
...ts-visibility-restrictions.service.spec.ts | 2 +-
.../calendar-event.workspace-entity.ts | 4 +-
.../common/types/fetched-calendar-event.ts | 2 +-
...ging-blocklist-item-delete-messages.job.ts | 6 +-
.../message-channel.workspace-entity.ts | 4 +-
.../message-participant.workspace-entity.ts | 38 +-
.../drivers/gmail/types/gmail-message.type.ts | 4 +-
.../parse-and-format-gmail-message.util.ts | 20 +-
.../services/imap-get-messages.service.ts | 11 +-
.../microsoft-get-messages.service.spec.ts | 19 +-
.../microsoft-get-messages.service.ts | 21 +-
...ing-delete-group-email-messages.service.ts | 3 +-
...d-enqueue-contact-creation.service.spec.ts | 42 +-
...es-and-enqueue-contact-creation.service.ts | 7 +-
.../utils/__mocks__/messages.ts | 23 +-
.../__tests__/filter-emails.util.spec.ts | 12 +-
...ddress-object-as-participants.util.spec.ts | 28 +-
.../utils/filter-out-group-emails.util.ts | 4 +-
...mat-address-object-as-participants.util.ts | 3 +-
.../workspace-member.workspace-entity.ts | 8 +-
... => add-custom-suffix-if-reserved.util.ts} | 5 +-
.../compute-metadata-name-from-label.util.ts | 15 +-
packages/twenty-shared/src/metadata/index.ts | 2 +-
.../src/types/MessageParticipantRole.ts | 6 +
packages/twenty-shared/src/types/index.ts | 1 +
69 files changed, 807 insertions(+), 193 deletions(-)
delete mode 100644 packages/twenty-front/src/modules/activities/emails/types/EmailParticipantRole.ts
create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-13/1-13-migrate-standard-invalid-entities.command.ts
rename packages/twenty-shared/src/metadata/{sanitize-reserved-keyword.util.ts => add-custom-suffix-if-reserved.util.ts} (57%)
create mode 100644 packages/twenty-shared/src/types/MessageParticipantRole.ts
diff --git a/packages/twenty-front/src/modules/activities/emails/components/EmailThreadMessage.tsx b/packages/twenty-front/src/modules/activities/emails/components/EmailThreadMessage.tsx
index e5cd3507d9..ccd1150722 100644
--- a/packages/twenty-front/src/modules/activities/emails/components/EmailThreadMessage.tsx
+++ b/packages/twenty-front/src/modules/activities/emails/components/EmailThreadMessage.tsx
@@ -6,6 +6,7 @@ import { EmailThreadMessageBodyPreview } from '@/activities/emails/components/Em
import { EmailThreadMessageReceivers } from '@/activities/emails/components/EmailThreadMessageReceivers';
import { EmailThreadMessageSender } from '@/activities/emails/components/EmailThreadMessageSender';
import { type EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant';
+import { MessageParticipantRole } from 'twenty-shared/types';
const StyledThreadMessage = styled.div`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
@@ -45,7 +46,7 @@ export const EmailThreadMessage = ({
const [isOpen, setIsOpen] = useState(isExpanded);
const receivers = participants.filter(
- (participant) => participant.role !== 'from',
+ (participant) => participant.role !== MessageParticipantRole.FROM,
);
if (!sender || receivers.length === 0) {
diff --git a/packages/twenty-front/src/modules/activities/emails/types/EmailParticipantRole.ts b/packages/twenty-front/src/modules/activities/emails/types/EmailParticipantRole.ts
deleted file mode 100644
index f852b3ac07..0000000000
--- a/packages/twenty-front/src/modules/activities/emails/types/EmailParticipantRole.ts
+++ /dev/null
@@ -1 +0,0 @@
-export type EmailParticipantRole = 'from' | 'to' | 'cc' | 'bcc';
diff --git a/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessageParticipant.ts b/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessageParticipant.ts
index 937e988aab..f531b38672 100644
--- a/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessageParticipant.ts
+++ b/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessageParticipant.ts
@@ -1,12 +1,12 @@
-import { type EmailParticipantRole } from '@/activities/emails/types/EmailParticipantRole';
import { type Person } from '@/people/types/Person';
import { type WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
+import { type MessageParticipantRole } from 'twenty-shared/types';
export type EmailThreadMessageParticipant = {
id: string;
displayName: string;
handle: string;
- role: EmailParticipantRole;
+ role: MessageParticipantRole;
messageId: string;
person: Person;
workspaceMember: WorkspaceMember;
diff --git a/packages/twenty-front/src/modules/activities/emails/utils/__tests__/getDisplayNameFromParticipant.test.ts b/packages/twenty-front/src/modules/activities/emails/utils/__tests__/getDisplayNameFromParticipant.test.ts
index ae0e2455c5..a343a658c3 100644
--- a/packages/twenty-front/src/modules/activities/emails/utils/__tests__/getDisplayNameFromParticipant.test.ts
+++ b/packages/twenty-front/src/modules/activities/emails/utils/__tests__/getDisplayNameFromParticipant.test.ts
@@ -1,5 +1,6 @@
import { type EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant';
+import { MessageParticipantRole } from 'twenty-shared/types';
import { getDisplayNameFromParticipant } from '../getDisplayNameFromParticipant';
describe('getDisplayNameFromParticipant', () => {
@@ -8,7 +9,7 @@ describe('getDisplayNameFromParticipant', () => {
__typename: 'EmailThreadMessageParticipant',
displayName: '',
handle: '',
- role: 'from',
+ role: MessageParticipantRole.FROM,
messageId: '638f52d1-fd55-4a2b-b0f3-9858ea3b2e91',
person: {
__typename: 'Person',
@@ -51,23 +52,23 @@ describe('getDisplayNameFromParticipant', () => {
},
};
- const participantWithHandle: any = {
+ const participantWithHandle = {
displayName: '',
handle: 'user_handle',
- role: 'from',
- };
+ role: MessageParticipantRole.FROM,
+ } as EmailThreadMessageParticipant;
- const participantWithDisplayName: any = {
+ const participantWithDisplayName = {
displayName: 'User123',
handle: '',
- role: 'from',
- };
+ role: MessageParticipantRole.FROM,
+ } as EmailThreadMessageParticipant;
- const participantWithoutInfo: any = {
+ const participantWithoutInfo = {
displayName: '',
handle: '',
- role: 'from',
- };
+ role: MessageParticipantRole.FROM,
+ } as EmailThreadMessageParticipant;
it('should return full name when shouldUseFullName is true', () => {
expect(
diff --git a/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/__tests__/useEmailThreadInCommandMenu.test.tsx b/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/__tests__/useEmailThreadInCommandMenu.test.tsx
index bcc2d2ad34..5fded4ab5c 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/__tests__/useEmailThreadInCommandMenu.test.tsx
+++ b/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/__tests__/useEmailThreadInCommandMenu.test.tsx
@@ -7,6 +7,7 @@ import {
QUERY_DEFAULT_LIMIT_RECORDS,
QUERY_MAX_RECORDS,
} from 'twenty-shared/constants';
+import { MessageParticipantRole } from 'twenty-shared/types';
import { generateEmptyJestRecordNode } from '~/testing/jest/generateEmptyJestRecordNode';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { useEmailThreadInCommandMenu } from '../useEmailThreadInCommandMenu';
@@ -313,7 +314,10 @@ const mocks = [
}
`,
variables: {
- filter: { messageId: { in: ['1', '2'] }, role: { eq: 'from' } },
+ filter: {
+ messageId: { in: ['1', '2'] },
+ role: { eq: MessageParticipantRole.FROM },
+ },
orderBy: undefined,
lastCursor: undefined,
limit: QUERY_DEFAULT_LIMIT_RECORDS,
@@ -328,7 +332,7 @@ const mocks = [
objectNameSingular: 'messageParticipant',
input: {
id: 'messageParticipant-1',
- role: 'from',
+ role: MessageParticipantRole.FROM,
messageId: '1',
},
}),
@@ -339,7 +343,7 @@ const mocks = [
objectNameSingular: 'messageParticipant',
input: {
id: 'messageParticipant-2',
- role: 'from',
+ role: MessageParticipantRole.FROM,
messageId: '2',
},
}),
@@ -401,7 +405,7 @@ describe('useEmailThreadInCommandMenu', () => {
id: 'messageParticipant-1',
messageId: '1',
person: null,
- role: 'from',
+ role: MessageParticipantRole.FROM,
workspaceMember: null,
},
subject: '',
@@ -422,7 +426,7 @@ describe('useEmailThreadInCommandMenu', () => {
id: 'messageParticipant-2',
messageId: '2',
person: null,
- role: 'from',
+ role: MessageParticipantRole.FROM,
workspaceMember: null,
},
subject: '',
diff --git a/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/useEmailThreadInCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/useEmailThreadInCommandMenu.ts
index 09c2d40df4..806255016d 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/useEmailThreadInCommandMenu.ts
+++ b/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/useEmailThreadInCommandMenu.ts
@@ -14,6 +14,7 @@ import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
+import { MessageParticipantRole } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
// to improve - https://github.com/twentyhq/twenty/issues/12190
@@ -82,7 +83,7 @@ export const useEmailThreadInCommandMenu = () => {
in: messages.map(({ id }) => id),
},
role: {
- eq: 'from',
+ eq: MessageParticipantRole.FROM,
},
},
objectNameSingular: CoreObjectNameSingular.MessageParticipant,
diff --git a/packages/twenty-front/src/pages/settings/data-model/utils/computeMetadataNameFromLabel.ts b/packages/twenty-front/src/pages/settings/data-model/utils/computeMetadataNameFromLabel.ts
index aaad22006b..f0d5f276c9 100644
--- a/packages/twenty-front/src/pages/settings/data-model/utils/computeMetadataNameFromLabel.ts
+++ b/packages/twenty-front/src/pages/settings/data-model/utils/computeMetadataNameFromLabel.ts
@@ -4,7 +4,7 @@ import { computeMetadataNameFromLabel as computeMetadataNameFromLabelCore } from
// This is needed for form validation and UI components that prefer graceful degradation
export const computeMetadataNameFromLabel = (label: string): string => {
try {
- return computeMetadataNameFromLabelCore(label);
+ return computeMetadataNameFromLabelCore({ label });
} catch {
return '';
}
diff --git a/packages/twenty-front/src/testing/mock-data/generated/mock-metadata-query-result.ts b/packages/twenty-front/src/testing/mock-data/generated/mock-metadata-query-result.ts
index 196b199812..6168eb358d 100644
--- a/packages/twenty-front/src/testing/mock-data/generated/mock-metadata-query-result.ts
+++ b/packages/twenty-front/src/testing/mock-data/generated/mock-metadata-query-result.ts
@@ -3877,7 +3877,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
"isUnique": false,
"createdAt": "2025-06-09T18:53:47.000Z",
"updatedAt": "2025-06-09T18:53:47.000Z",
- "defaultValue": "'from'",
+ "defaultValue": "'FROM'",
"options": [
{
"id": "edc6f452-4ecc-4ff7-b30c-18575dbe0bfd",
@@ -8594,7 +8594,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
"__typename": "Field",
"id": "21f219f9-c531-4ab5-9640-69230baa83d0",
"type": "TEXT",
- "name": "iCalUID",
+ "name": "iCalUid",
"isCustom": false,
"isActive": true,
"isSystem": false,
diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-13/1-13-migrate-standard-invalid-entities.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-13/1-13-migrate-standard-invalid-entities.command.ts
new file mode 100644
index 0000000000..cf0d10b1c6
--- /dev/null
+++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-13/1-13-migrate-standard-invalid-entities.command.ts
@@ -0,0 +1,380 @@
+import { InjectRepository } from '@nestjs/typeorm';
+
+import { Command } from 'nest-commander';
+import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
+import {
+ FieldMetadataComplexOption,
+ FieldMetadataDefaultValue,
+ FieldMetadataType,
+ FromTo,
+} from 'twenty-shared/types';
+import { isDefined } from 'twenty-shared/utils';
+import { type Repository } from 'typeorm';
+
+import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
+import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
+import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
+import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
+import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service';
+import { MetadataFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity-maps.type';
+import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
+import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
+import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
+import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
+import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
+import { AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type';
+import { AllStandardObjectName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-name.type';
+import {
+ CALENDAR_EVENT_STANDARD_FIELD_IDS,
+ MESSAGE_CHANNEL_STANDARD_FIELD_IDS,
+ MESSAGE_PARTICIPANT_STANDARD_FIELD_IDS,
+ MESSAGE_STANDARD_FIELD_IDS,
+ WORKSPACE_MEMBER_STANDARD_FIELD_IDS,
+} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
+
+const findStandardFlatObjectMetadataOrThrow = ({
+ flatObjectMetadataMaps,
+ standardId,
+}: {
+ flatObjectMetadataMaps: MetadataFlatEntityMaps<'objectMetadata'>;
+ standardId: string;
+}): FlatObjectMetadata => {
+ const flatObjectMetadata = Object.values(flatObjectMetadataMaps.byId).find(
+ (flatObjectMetadata) => flatObjectMetadata?.standardId === standardId,
+ );
+
+ if (!isDefined(flatObjectMetadata)) {
+ throw new Error(`Could not find standard object ${standardId}`);
+ }
+
+ return flatObjectMetadata;
+};
+
+const findStandardFlatFieldMetadatawOrThrow = ({
+ flatFieldMetadataMaps,
+ flatObjectMetadata,
+ standardId,
+}: {
+ standardId: string;
+ flatObjectMetadata: FlatObjectMetadata;
+ flatFieldMetadataMaps: MetadataFlatEntityMaps<'fieldMetadata'>;
+}) => {
+ const objectFlatFieldMetadata = findManyFlatEntityByIdInFlatEntityMapsOrThrow(
+ {
+ flatEntityIds: flatObjectMetadata.fieldMetadataIds,
+ flatEntityMaps: flatFieldMetadataMaps,
+ },
+ );
+
+ const flatFieldMetadata = objectFlatFieldMetadata.find(
+ (flatFieldMetadata) => flatFieldMetadata.standardId === standardId,
+ );
+
+ if (!isDefined(flatFieldMetadata)) {
+ throw new Error(`Could not find standard field ${standardId}`);
+ }
+
+ return flatFieldMetadata;
+};
+
+const ENUM_FIELD_OPTIONS_TO_MUTATE = {
+ messageChannelMessageAssociation: {
+ fieldStandardId: MESSAGE_STANDARD_FIELD_IDS.direction,
+ field: 'direction',
+ defaultValue: {
+ fromDefaultValue: "'incoming'",
+ toDefaultValue: "'INCOMING'",
+ },
+ options: [
+ {
+ fromOption: {
+ value: 'incoming',
+ label: 'Incoming',
+ position: 0,
+ color: 'green',
+ },
+ toOption: {
+ value: 'INCOMING',
+ label: 'Incoming',
+ position: 0,
+ color: 'green',
+ },
+ },
+ {
+ fromOption: {
+ value: 'outgoing',
+ label: 'Outgoing',
+ position: 1,
+ color: 'blue',
+ },
+ toOption: {
+ value: 'OUTGOING',
+ label: 'Outgoing',
+ position: 1,
+ color: 'blue',
+ },
+ },
+ ],
+ },
+ messageChannel: {
+ fieldStandardId: MESSAGE_CHANNEL_STANDARD_FIELD_IDS.type,
+ field: 'type',
+ defaultValue: { fromDefaultValue: "'email'", toDefaultValue: "'EMAIL'" },
+ options: [
+ {
+ fromOption: {
+ value: 'email',
+ label: 'Email',
+ position: 0,
+ color: 'green',
+ },
+ toOption: {
+ value: 'EMAIL',
+ label: 'Email',
+ position: 0,
+ color: 'green',
+ },
+ },
+ {
+ fromOption: { value: 'sms', label: 'SMS', position: 1, color: 'blue' },
+ toOption: { value: 'SMS', label: 'SMS', position: 1, color: 'blue' },
+ },
+ ],
+ },
+ messageParticipant: {
+ fieldStandardId: MESSAGE_PARTICIPANT_STANDARD_FIELD_IDS.role,
+ field: 'role',
+ defaultValue: { fromDefaultValue: "'from'", toDefaultValue: "'FROM'" },
+ options: [
+ {
+ fromOption: {
+ value: 'from',
+ label: 'From',
+ position: 0,
+ color: 'green',
+ },
+ toOption: { value: 'FROM', label: 'From', position: 0, color: 'green' },
+ },
+ {
+ fromOption: { value: 'to', label: 'To', position: 1, color: 'blue' },
+ toOption: { value: 'TO', label: 'To', position: 1, color: 'blue' },
+ },
+ {
+ fromOption: { value: 'cc', label: 'Cc', position: 2, color: 'orange' },
+ toOption: { value: 'CC', label: 'Cc', position: 2, color: 'orange' },
+ },
+ {
+ fromOption: { value: 'bcc', label: 'Bcc', position: 3, color: 'red' },
+ toOption: { value: 'BCC', label: 'Bcc', position: 3, color: 'red' },
+ },
+ ],
+ },
+ workspaceMember: {
+ fieldStandardId: WORKSPACE_MEMBER_STANDARD_FIELD_IDS.numberFormat,
+ field: 'numberFormat',
+ defaultValue: { fromDefaultValue: "'system'", toDefaultValue: "'SYSTEM'" },
+ options: [
+ {
+ fromOption: {
+ value: 'SYSTEM',
+ label: 'System',
+ position: 0,
+ color: 'turquoise',
+ },
+ toOption: {
+ value: 'SYSTEM',
+ label: 'System',
+ position: 0,
+ color: 'turquoise',
+ },
+ },
+ {
+ fromOption: {
+ value: 'COMMAS_AND_DOT',
+ label: 'Commas and dot (1,234.56)',
+ position: 1,
+ color: 'blue',
+ },
+ toOption: {
+ value: 'COMMAS_AND_DOT',
+ label: 'Commas and dot',
+ position: 1,
+ color: 'blue',
+ },
+ },
+ {
+ fromOption: {
+ value: 'SPACES_AND_COMMA',
+ label: 'Spaces and comma (1 234,56)',
+ position: 2,
+ color: 'green',
+ },
+ toOption: {
+ value: 'SPACES_AND_COMMA',
+ label: 'Spaces and comma',
+ position: 2,
+ color: 'green',
+ },
+ },
+ {
+ fromOption: {
+ value: 'DOTS_AND_COMMA',
+ label: 'Dots and comma (1.234,56)',
+ position: 3,
+ color: 'orange',
+ },
+ toOption: {
+ value: 'DOTS_AND_COMMA',
+ label: 'Dots and comma',
+ position: 3,
+ color: 'orange',
+ },
+ },
+ {
+ fromOption: {
+ value: 'APOSTROPHE_AND_DOT',
+ label: "Apostrophe and dot (1'234.56)",
+ position: 4,
+ color: 'purple',
+ },
+ toOption: {
+ value: 'APOSTROPHE_AND_DOT',
+ label: 'Apostrophe and dot',
+ position: 4,
+ color: 'purple',
+ },
+ },
+ ],
+ },
+} as const satisfies {
+ [P in AllStandardObjectName]?: {
+ fieldStandardId: string;
+ field: AllStandardObjectFieldName
;
+ options: FromTo[];
+ defaultValue: FromTo;
+ };
+};
+
+@Command({
+ name: 'upgrade:1-13:migrate-standard-invalid-entities',
+ description: 'Migrate standard invalid entities',
+})
+export class MigrateStandardInvalidEntitiesCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
+ constructor(
+ @InjectRepository(WorkspaceEntity)
+ protected readonly workspaceRepository: Repository,
+ protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
+ protected readonly dataSourceService: DataSourceService,
+ private readonly workspaceCacheService: WorkspaceCacheService,
+ private readonly fieldMetadataService: FieldMetadataService,
+ ) {
+ super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
+ }
+
+ override async runOnWorkspace({
+ options,
+ workspaceId,
+ }: RunOnWorkspaceArgs): Promise {
+ const isDryRun = options.dryRun || false;
+
+ this.logger.log(
+ `MigrateStandardInvalidEntitiesCommand starting for workspace ${workspaceId}`,
+ );
+ const { flatFieldMetadataMaps, flatObjectMetadataMaps } =
+ await this.workspaceCacheService.getOrRecompute(workspaceId, [
+ 'flatFieldMetadataMaps',
+ 'flatObjectMetadataMaps',
+ ]);
+
+ const flatCalendarEventObject = findStandardFlatObjectMetadataOrThrow({
+ standardId: STANDARD_OBJECT_IDS.calendarEvent,
+ flatObjectMetadataMaps,
+ });
+
+ const iCalUidFieldId = findStandardFlatFieldMetadatawOrThrow({
+ flatFieldMetadataMaps,
+ flatObjectMetadata: flatCalendarEventObject,
+ standardId: CALENDAR_EVENT_STANDARD_FIELD_IDS.iCalUid,
+ }).id;
+
+ const allAffectedObjectNames = Object.keys(
+ ENUM_FIELD_OPTIONS_TO_MUTATE,
+ ).filter(isDefined) as (keyof typeof ENUM_FIELD_OPTIONS_TO_MUTATE)[];
+
+ const flatEnumFieldMetadatasToUpdate = allAffectedObjectNames.flatMap<{
+ id: string;
+ options: FieldMetadataComplexOption[];
+ defaultValue: FieldMetadataDefaultValue;
+ }>((objectName) => {
+ const { defaultValue, field, options, fieldStandardId } =
+ ENUM_FIELD_OPTIONS_TO_MUTATE[objectName];
+
+ const objectStandardId = STANDARD_OBJECT_IDS[objectName];
+
+ const flatObjectMetadata = findStandardFlatObjectMetadataOrThrow({
+ flatObjectMetadataMaps,
+ standardId: objectStandardId,
+ });
+
+ const flatFieldMetadata = findStandardFlatFieldMetadatawOrThrow({
+ flatFieldMetadataMaps,
+ flatObjectMetadata,
+ standardId: fieldStandardId,
+ });
+
+ if (
+ flatFieldMetadata.name !== field ||
+ !isFlatFieldMetadataOfType(flatFieldMetadata, FieldMetadataType.SELECT)
+ ) {
+ throw new Error(
+ `Retrieved flat field is not the one expected expected ${field} got ${flatFieldMetadata.name} in workspace ${workspaceId}`,
+ );
+ }
+
+ const updatedOptions = flatFieldMetadata.options.map((option) => {
+ const matchingNewOption = options.find(
+ ({ fromOption }) => fromOption.value === option.value,
+ );
+
+ if (!isDefined(matchingNewOption)) {
+ return option;
+ }
+
+ return {
+ ...matchingNewOption.toOption,
+ id: option.id,
+ };
+ });
+
+ const updatedDefaultValue: FieldMetadataDefaultValue =
+ flatFieldMetadata.defaultValue === defaultValue.fromDefaultValue
+ ? defaultValue.toDefaultValue
+ : flatFieldMetadata.defaultValue;
+
+ return {
+ id: flatFieldMetadata.id,
+ options: updatedOptions,
+ defaultValue: updatedDefaultValue,
+ };
+ });
+
+ const allUpdates = [
+ {
+ id: iCalUidFieldId,
+ name: 'iCalUid',
+ },
+ ...flatEnumFieldMetadatasToUpdate,
+ ];
+
+ if (!isDryRun) {
+ for (const updateFieldInput of allUpdates) {
+ await this.fieldMetadataService.updateOneField({
+ updateFieldInput,
+ workspaceId,
+ isSystemBuild: true,
+ });
+ }
+ }
+ this.logger.log('Migrated standard invalid entities');
+ }
+}
diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-13/1-13-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-13/1-13-upgrade-version-command.module.ts
index b05f4db5f0..c3b4b954bf 100644
--- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-13/1-13-upgrade-version-command.module.ts
+++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-13/1-13-upgrade-version-command.module.ts
@@ -5,6 +5,7 @@ import { BackfillPageLayoutUniversalIdentifiersCommand } from 'src/database/comm
import { BackfillViewMainGroupByFieldMetadataIdCommand } from 'src/database/commands/upgrade-version-command/1-13/1-13-backfill-view-main-group-by-field-metadata-id.command';
import { CleanEmptyStringNullInTextFieldsCommand } from 'src/database/commands/upgrade-version-command/1-13/1-13-clean-empty-string-null-in-text-fields.command';
import { DeduplicateRoleTargetsCommand } from 'src/database/commands/upgrade-version-command/1-13/1-13-deduplicate-role-targets.command';
+import { MigrateStandardInvalidEntitiesCommand } from 'src/database/commands/upgrade-version-command/1-13/1-13-migrate-standard-invalid-entities.command';
import { RenameIndexNameCommand } from 'src/database/commands/upgrade-version-command/1-13/1-13-rename-unique-index.command';
import { UpdateRoleTargetsUniqueConstraintMigrationCommand } from 'src/database/commands/upgrade-version-command/1-13/1-13-update-role-targets-unique-constraint-migration.command';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
@@ -12,6 +13,7 @@ import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
+import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout-tab.entity';
@@ -40,6 +42,7 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
DataSourceModule,
ApplicationModule,
WorkspaceCacheModule,
+ FieldMetadataModule,
],
providers: [
CleanEmptyStringNullInTextFieldsCommand,
@@ -47,6 +50,7 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
BackfillPageLayoutUniversalIdentifiersCommand,
DeduplicateRoleTargetsCommand,
RenameIndexNameCommand,
+ MigrateStandardInvalidEntitiesCommand,
UpdateRoleTargetsUniqueConstraintMigrationCommand,
],
exports: [
@@ -55,6 +59,7 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
BackfillPageLayoutUniversalIdentifiersCommand,
DeduplicateRoleTargetsCommand,
RenameIndexNameCommand,
+ MigrateStandardInvalidEntitiesCommand,
UpdateRoleTargetsUniqueConstraintMigrationCommand,
],
})
diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts
index 84cd350622..b8d4f1a85f 100644
--- a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts
+++ b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts
@@ -29,6 +29,7 @@ import { SetStandardApplicationNotUninstallableCommand } from 'src/database/comm
import { WorkspaceCustomApplicationIdNonNullableCommand } from 'src/database/commands/upgrade-version-command/1-12/1-12-workspace-custom-application-id-non-nullable-migration.command';
import { BackfillPageLayoutUniversalIdentifiersCommand } from 'src/database/commands/upgrade-version-command/1-13/1-13-backfill-page-layout-universal-identifiers.command';
import { DeduplicateRoleTargetsCommand } from 'src/database/commands/upgrade-version-command/1-13/1-13-deduplicate-role-targets.command';
+import { MigrateStandardInvalidEntitiesCommand } from 'src/database/commands/upgrade-version-command/1-13/1-13-migrate-standard-invalid-entities.command';
import { UpdateRoleTargetsUniqueConstraintMigrationCommand } from 'src/database/commands/upgrade-version-command/1-13/1-13-update-role-targets-unique-constraint-migration.command';
import { FixLabelIdentifierPositionAndVisibilityCommand } from 'src/database/commands/upgrade-version-command/1-6/1-6-fix-label-identifier-position-and-visibility.command';
import { BackfillWorkflowManualTriggerAvailabilityCommand } from 'src/database/commands/upgrade-version-command/1-7/1-7-backfill-workflow-manual-trigger-availability.command';
@@ -100,6 +101,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
protected readonly deduplicateRoleTargetsCommand: DeduplicateRoleTargetsCommand,
protected readonly updateRoleTargetsUniqueConstraintMigrationCommand: UpdateRoleTargetsUniqueConstraintMigrationCommand,
protected readonly backfillPageLayoutUniversalIdentifiersCommand: BackfillPageLayoutUniversalIdentifiersCommand,
+ protected readonly migrateStandardInvalidEntitiesCommand: MigrateStandardInvalidEntitiesCommand,
) {
super(
workspaceRepository,
@@ -172,6 +174,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
this.deduplicateRoleTargetsCommand,
this.updateRoleTargetsUniqueConstraintMigrationCommand,
this.backfillPageLayoutUniversalIdentifiersCommand,
+ this.migrateStandardInvalidEntitiesCommand,
],
afterSyncMetadata: [],
};
diff --git a/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts b/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts
index a6c86d149d..adbd2bfa2b 100644
--- a/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts
@@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common';
import { In } from 'typeorm';
+import { MessageParticipantRole } from 'twenty-shared/types';
import { type TimelineThreadDTO } from 'src/engine/core-modules/messaging/dtos/timeline-thread.dto';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@@ -115,7 +116,9 @@ export class TimelineMessagingService {
.where('message.messageThreadId = ANY(:messageThreadIds)', {
messageThreadIds,
})
- .andWhere('messageParticipant.role = :role', { role: 'from' })
+ .andWhere('messageParticipant.role = :role', {
+ role: MessageParticipantRole.FROM,
+ })
.orderBy('message.messageThreadId')
.distinctOn(['message.messageThreadId', 'messageParticipant.handle'])
.getMany();
diff --git a/packages/twenty-server/src/engine/core-modules/messaging/utils/filter-active-participants.util.ts b/packages/twenty-server/src/engine/core-modules/messaging/utils/filter-active-participants.util.ts
index 2bd414f838..0d624a0a21 100644
--- a/packages/twenty-server/src/engine/core-modules/messaging/utils/filter-active-participants.util.ts
+++ b/packages/twenty-server/src/engine/core-modules/messaging/utils/filter-active-participants.util.ts
@@ -1,7 +1,11 @@
+import { MessageParticipantRole } from 'twenty-shared/types';
+
import { type MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
export const filterActiveParticipants = (
participants: MessageParticipantWorkspaceEntity[],
): MessageParticipantWorkspaceEntity[] => {
- return participants.filter((participant) => participant.role === 'from');
+ return participants.filter(
+ (participant) => participant.role === MessageParticipantRole.FROM,
+ );
};
diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/from-create-agent-input-to-flat-agent.util.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/from-create-agent-input-to-flat-agent.util.ts
index 5a38cbd6c8..7a6fcadc50 100644
--- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/from-create-agent-input-to-flat-agent.util.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/from-create-agent-input-to-flat-agent.util.ts
@@ -1,10 +1,10 @@
import { isNonEmptyString } from '@sniptt/guards';
+import { computeMetadataNameFromLabel } from 'twenty-shared/metadata';
import {
isDefined,
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import { v4 } from 'uuid';
-import { computeMetadataNameFromLabel } from 'twenty-shared/metadata';
import { type CreateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/create-agent.input';
import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
@@ -48,7 +48,7 @@ export const fromCreateAgentInputToFlatAgent = ({
standardId,
name: isNonEmptyString(createAgentInput.name)
? createAgentInput.name
- : computeMetadataNameFromLabel(createAgentInput.label),
+ : computeMetadataNameFromLabel({ label: createAgentInput.label }),
label: createAgentInput.label,
icon: createAgentInput.icon ?? null,
description: createAgentInput.description ?? null,
diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts
index feb781313c..1e626ea5f2 100644
--- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts
@@ -181,6 +181,7 @@ export class FieldMetadataService extends TypeOrmQueryService;
// Note: Standard override is way too complex we should land a smoother implemenentation once we standardize
// them across every flat entities
@@ -34,11 +35,13 @@ export const computeFlatFieldToUpdateAndRelatedFlatFieldToUpdate = ({
rawUpdateFieldInput,
flatFieldMetadataMaps,
flatObjectMetadata,
+ isSystemBuild,
}: ComputeFlatFieldToUpdateAndRelatedFlatFieldToUpdateArgs): ComputeFlatFieldToUpdateAndRelatedFlatFieldToUpdateReturnType => {
const { standardOverrides, updatedEditableFieldProperties } =
sanitizeRawUpdateFieldInput({
existingFlatFieldMetadata: fromFlatFieldMetadata,
rawUpdateFieldInput,
+ isSystemBuild,
});
const isStandardField = isStandardMetadata(fromFlatFieldMetadata);
@@ -48,7 +51,7 @@ export const computeFlatFieldToUpdateAndRelatedFlatFieldToUpdate = ({
existing: fromFlatFieldMetadata,
properties:
FLAT_FIELD_METADATA_EDITABLE_PROPERTIES[
- isStandardField ? 'standard' : 'custom'
+ isStandardField && !isSystemBuild ? 'standard' : 'custom'
],
update: updatedEditableFieldProperties,
}),
@@ -104,7 +107,7 @@ export const computeFlatFieldToUpdateAndRelatedFlatFieldToUpdate = ({
>((relatedFlatFieldMetadataFrom) => {
const relatedMorphPropertiesToUpdateTo =
FLAT_FIELD_METADATA_MORPH_RELATION_EDITABLE_PROPERTIES_ON_SIBLING_MORPH_RELATION_UPDATE_CONSTANT[
- isStandardField ? 'standard' : 'custom'
+ isStandardField && !isSystemBuild ? 'standard' : 'custom'
];
const relatedFlatFieldMetadataTo = {
...mergeUpdateInExistingRecord({
diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata.util.ts
index 388ada54cf..a8ff395b02 100644
--- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata.util.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata.util.ts
@@ -24,6 +24,7 @@ import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-fiel
type FromUpdateFieldInputToFlatFieldMetadataArgs = {
updateFieldInput: UpdateFieldInput;
workspaceCustomApplicationId: string;
+ isSystemBuild: boolean;
} & Pick<
AllFlatEntityMaps,
| 'flatObjectMetadataMaps'
@@ -49,6 +50,7 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
flatViewGroupMaps,
flatViewMaps,
flatViewFieldMaps,
+ isSystemBuild,
}: FromUpdateFieldInputToFlatFieldMetadataArgs): FieldInputTranspilationResult => {
const updateFieldInputInformalProperties =
extractAndSanitizeObjectStringFields(rawUpdateFieldInput, [
@@ -98,6 +100,7 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
flatObjectMetadata,
fromFlatFieldMetadata: existingFlatFieldMetadataToUpdate,
rawUpdateFieldInput,
+ isSystemBuild,
});
const { flatFieldMetadatasToCreate, flatIndexMetadatasToCreate } =
diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util.ts
index 7b9b085811..fbec150898 100644
--- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util.ts
@@ -1,10 +1,10 @@
+import { computeMetadataNameFromLabel } from 'twenty-shared/metadata';
import {
FieldMetadataType,
RelationOnDeleteAction,
RelationType,
} from 'twenty-shared/types';
import { v4 } from 'uuid';
-import { computeMetadataNameFromLabel } from 'twenty-shared/metadata';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { type MorphOrRelationFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/morph-or-relation-field-metadata-type.type';
@@ -100,7 +100,9 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({
label: relationCreationPayload.targetFieldLabel,
name:
targetFieldName ??
- computeMetadataNameFromLabel(relationCreationPayload.targetFieldLabel),
+ computeMetadataNameFromLabel({
+ label: relationCreationPayload.targetFieldLabel,
+ }),
objectMetadataId: targetFlatObjectMetadata.id,
type: FieldMetadataType.RELATION,
workspaceId,
diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-name-synced-with-label.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-name-synced-with-label.util.ts
index 884006091d..4279f0855b 100644
--- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-name-synced-with-label.util.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-name-synced-with-label.util.ts
@@ -2,13 +2,20 @@ import { computeMetadataNameFromLabel } from 'twenty-shared/metadata';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
-export const isFlatFieldMetadataNameSyncedWithLabel = (
+export const isFlatFieldMetadataNameSyncedWithLabel = ({
+ flatFieldMetadata,
+ isSystemBuild,
+}: {
flatFieldMetadata: Pick<
FlatFieldMetadata,
'name' | 'isLabelSyncedWithName' | 'label'
- >,
-) => {
- const computedName = computeMetadataNameFromLabel(flatFieldMetadata.label);
+ >;
+ isSystemBuild: boolean;
+}) => {
+ const computedName = computeMetadataNameFromLabel({
+ label: flatFieldMetadata.label,
+ applyCustomSuffix: !isSystemBuild,
+ });
return flatFieldMetadata.name === computedName;
};
diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/sanitize-raw-update-field-input.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/sanitize-raw-update-field-input.ts
index 1a5da3b5c8..a2cdd5a2a0 100644
--- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/sanitize-raw-update-field-input.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/sanitize-raw-update-field-input.ts
@@ -18,10 +18,12 @@ import { isStandardMetadata } from 'src/engine/metadata-modules/utils/is-standar
type SanitizeRawUpdateFieldInputArgs = {
rawUpdateFieldInput: UpdateFieldInput;
existingFlatFieldMetadata: FlatFieldMetadata;
+ isSystemBuild: boolean;
};
export const sanitizeRawUpdateFieldInput = ({
existingFlatFieldMetadata,
rawUpdateFieldInput,
+ isSystemBuild,
}: SanitizeRawUpdateFieldInputArgs) => {
const isStandardField = isStandardMetadata(existingFlatFieldMetadata);
const updatedEditableFieldProperties = extractAndSanitizeObjectStringFields(
@@ -43,7 +45,7 @@ export const sanitizeRawUpdateFieldInput = ({
...option,
}));
- if (!isStandardField) {
+ if (!isStandardField || isSystemBuild) {
return {
updatedEditableFieldProperties,
standardOverrides: null,
diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/are-flat-object-metadata-names-synced-with-labels.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/are-flat-object-metadata-names-synced-with-labels.util.ts
index fd62de4f88..1785eeceef 100644
--- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/are-flat-object-metadata-names-synced-with-labels.util.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/are-flat-object-metadata-names-synced-with-labels.util.ts
@@ -2,16 +2,25 @@ import { computeMetadataNameFromLabel } from 'twenty-shared/metadata';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
-export const areFlatObjectMetadataNamesSyncedWithLabels = (
+export const areFlatObjectMetadataNamesSyncedWithLabels = ({
+ flatObjectdMetadata,
+ isSystemBuild,
+}: {
+ isSystemBuild: boolean;
flatObjectdMetadata: Pick<
FlatObjectMetadata,
'namePlural' | 'nameSingular' | 'labelPlural' | 'labelSingular'
- >,
-) => {
+ >;
+}) => {
const [computedSingularName, computedPluralName] = [
flatObjectdMetadata.labelSingular,
flatObjectdMetadata.labelPlural,
- ].map(computeMetadataNameFromLabel);
+ ].map((label) =>
+ computeMetadataNameFromLabel({
+ label,
+ applyCustomSuffix: !isSystemBuild,
+ }),
+ );
return (
flatObjectdMetadata.nameSingular === computedSingularName &&
diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-name-and-labels.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-name-and-labels.util.ts
index db087b92dd..ff6097a913 100644
--- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-name-and-labels.util.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-name-and-labels.util.ts
@@ -8,6 +8,7 @@ import { areFlatObjectMetadataNamesSyncedWithLabels } from 'src/engine/metadata-
import { validateFlatObjectMetadataLabel } from 'src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-label.util';
import { validateFlatObjectMetadataNames } from 'src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-name.util';
import { ObjectMetadataExceptionCode } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
+import { type WorkspaceMigrationBuilderOptions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-builder-options.type';
type ValidateNoOtherObjectWithSameNameExistsOrThrowsParams = {
objectMetadataNameSingular: string;
@@ -36,9 +37,11 @@ export const doesOtherObjectWithSameNameExists = ({
export const validateFlatObjectMetadataNameAndLabels = ({
optimisticFlatObjectMetadataMaps,
flatObjectMetadataToValidate,
+ buildOptions,
}: {
flatObjectMetadataToValidate: FlatObjectMetadata;
optimisticFlatObjectMetadataMaps: FlatEntityMaps;
+ buildOptions: WorkspaceMigrationBuilderOptions;
}): FlatObjectMetadataValidationError[] => {
const errors: FlatObjectMetadataValidationError[] = [];
@@ -58,7 +61,10 @@ export const validateFlatObjectMetadataNameAndLabels = ({
if (
flatObjectMetadataToValidate.isLabelSyncedWithName &&
- !areFlatObjectMetadataNamesSyncedWithLabels(flatObjectMetadataToValidate)
+ !areFlatObjectMetadataNamesSyncedWithLabels({
+ flatObjectdMetadata: flatObjectMetadataToValidate,
+ isSystemBuild: buildOptions.isSystemBuild,
+ })
) {
errors.push({
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/compute-metadata-name-from-label-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/utils/compute-metadata-name-from-label-or-throw.util.ts
index e4f5405e7d..14fccce18d 100644
--- a/packages/twenty-server/src/engine/metadata-modules/utils/compute-metadata-name-from-label-or-throw.util.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/utils/compute-metadata-name-from-label-or-throw.util.ts
@@ -17,7 +17,7 @@ export const computeMetadataNameFromLabelOrThrow = (label: string): string => {
}
try {
- return computeMetadataNameFromLabelCore(label);
+ return computeMetadataNameFromLabelCore({ label });
} catch (error) {
if (error instanceof Error) {
throw new InvalidMetadataException(
diff --git a/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-field.decorator.ts b/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-field.decorator.ts
index 410832b64d..2ec99939c6 100644
--- a/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-field.decorator.ts
+++ b/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-field.decorator.ts
@@ -82,7 +82,8 @@ export function WorkspaceField(
generateDefaultValue(options.type)) as FieldMetadataDefaultValue | null;
const name = propertyKey.toString();
const label = options.label.message ?? '';
- const isLabelSyncedWithName = computeMetadataNameFromLabel(label) === name;
+ const isLabelSyncedWithName =
+ computeMetadataNameFromLabel({ label }) === name;
metadataArgsStorage.addFields({
target: object.constructor,
diff --git a/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-relation.decorator.ts b/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-relation.decorator.ts
index f58e20abe7..a8f68ebcb4 100644
--- a/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-relation.decorator.ts
+++ b/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-relation.decorator.ts
@@ -1,9 +1,9 @@
import { type MessageDescriptor } from '@lingui/core';
import { isDefined, isUUID } from 'class-validator';
+import { computeMetadataNameFromLabel } from 'twenty-shared/metadata';
+import { type RelationOnDeleteAction } from 'twenty-shared/types';
import { CustomError } from 'twenty-shared/utils';
import { type ObjectType } from 'typeorm';
-import { type RelationOnDeleteAction } from 'twenty-shared/types';
-import { computeMetadataNameFromLabel } from 'twenty-shared/metadata';
import { type RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
@@ -88,7 +88,8 @@ export function WorkspaceRelation(
);
const name = propertyKey.toString();
const label = options.label.message ?? '';
- const isLabelSyncedWithName = computeMetadataNameFromLabel(label) === name;
+ const isLabelSyncedWithName =
+ computeMetadataNameFromLabel({ label }) === name;
if (options.isMorphRelation && !isDefined(options.morphId)) {
throw new CustomError(
diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/calendar-event-data-seeds.constant.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/calendar-event-data-seeds.constant.ts
index 2d1016380f..4681fe2180 100644
--- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/calendar-event-data-seeds.constant.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/calendar-event-data-seeds.constant.ts
@@ -9,7 +9,7 @@ type CalendarEventDataSeed = {
externalUpdatedAt: string;
description: string;
location: string;
- iCalUID: string;
+ iCalUid: string;
conferenceSolution: string;
conferenceLinkPrimaryLinkLabel: string;
conferenceLinkPrimaryLinkUrl: string;
@@ -27,7 +27,7 @@ export const CALENDAR_EVENT_DATA_SEED_COLUMNS: (keyof CalendarEventDataSeed)[] =
'externalUpdatedAt',
'description',
'location',
- 'iCalUID',
+ 'iCalUid',
'conferenceSolution',
'conferenceLinkPrimaryLinkLabel',
'conferenceLinkPrimaryLinkUrl',
@@ -217,7 +217,7 @@ const GENERATE_CALENDAR_EVENT_SEEDS = (): CalendarEventDataSeed[] => {
).toISOString(),
description: TEMPLATE.description,
location: LOCATION,
- iCalUID: `event${INDEX}@calendar.twentycrm.com`,
+ iCalUid: `event${INDEX}@calendar.twentycrm.com`,
conferenceSolution: CONFERENCE_SOLUTION,
conferenceLinkPrimaryLinkLabel: CONFERENCE_LINK,
conferenceLinkPrimaryLinkUrl: CONFERENCE_LINK,
diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/message-channel-data-seeds.constant.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/message-channel-data-seeds.constant.ts
index 1d1e1dd0b2..3b7dd728a1 100644
--- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/message-channel-data-seeds.constant.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/message-channel-data-seeds.constant.ts
@@ -1,6 +1,7 @@
import { CONNECTED_ACCOUNT_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/connected-account-data-seeds.constant';
import {
MessageChannelSyncStage,
+ MessageChannelType,
MessageChannelVisibility,
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
@@ -10,7 +11,7 @@ type MessageChannelDataSeed = {
updatedAt: Date;
deletedAt: Date | null;
isContactAutoCreationEnabled: boolean;
- type: string;
+ type: MessageChannelType;
connectedAccountId: string;
handle: string;
isSyncEnabled: boolean;
@@ -54,7 +55,7 @@ export const MESSAGE_CHANNEL_DATA_SEEDS: MessageChannelDataSeed[] = [
updatedAt: new Date(),
deletedAt: null,
isContactAutoCreationEnabled: true,
- type: 'email',
+ type: MessageChannelType.EMAIL,
connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.TIM,
handle: 'tim@apple.dev',
isSyncEnabled: true,
@@ -67,7 +68,7 @@ export const MESSAGE_CHANNEL_DATA_SEEDS: MessageChannelDataSeed[] = [
updatedAt: new Date(),
deletedAt: null,
isContactAutoCreationEnabled: true,
- type: 'email',
+ type: MessageChannelType.EMAIL,
connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.JONY,
handle: 'jony.ive@apple.dev',
isSyncEnabled: true,
@@ -80,7 +81,7 @@ export const MESSAGE_CHANNEL_DATA_SEEDS: MessageChannelDataSeed[] = [
updatedAt: new Date(),
deletedAt: null,
isContactAutoCreationEnabled: true,
- type: 'email',
+ type: MessageChannelType.EMAIL,
connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.PHIL,
handle: 'phil.schiler@apple.dev',
isSyncEnabled: true,
@@ -93,7 +94,7 @@ export const MESSAGE_CHANNEL_DATA_SEEDS: MessageChannelDataSeed[] = [
updatedAt: new Date(),
deletedAt: null,
isContactAutoCreationEnabled: true,
- type: 'email',
+ type: MessageChannelType.EMAIL,
connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.TIM, // Use TIM's connected account for shared inbox
handle: 'support@apple.dev',
isSyncEnabled: true,
@@ -106,7 +107,7 @@ export const MESSAGE_CHANNEL_DATA_SEEDS: MessageChannelDataSeed[] = [
updatedAt: new Date(),
deletedAt: null,
isContactAutoCreationEnabled: true,
- type: 'email',
+ type: MessageChannelType.EMAIL,
connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.TIM, // Use TIM's connected account for shared inbox
handle: 'sales@apple.dev',
isSyncEnabled: true,
diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/message-participant-data-seeds.constant.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/message-participant-data-seeds.constant.ts
index b6aea1e228..ce702d879c 100644
--- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/message-participant-data-seeds.constant.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/message-participant-data-seeds.constant.ts
@@ -1,3 +1,5 @@
+import { MessageParticipantRole } from 'twenty-shared/types';
+
import { MESSAGE_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/message-data-seeds.constant';
import { PERSON_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/person-data-seeds.constant';
import {
@@ -14,7 +16,7 @@ export type MessageParticipantDataSeed = {
personId: string;
displayName: string;
handle: string;
- role: string;
+ role: MessageParticipantRole;
messageId: string;
};
@@ -226,7 +228,9 @@ const CREATE_MESSAGE_PARTICIPANTS = (
for (let I = 0; I < TOTAL_PARTICIPANTS; I++) {
const IS_SENDER = I === 0;
- const ROLE = IS_SENDER ? 'from' : 'to';
+ const ROLE = IS_SENDER
+ ? MessageParticipantRole.FROM
+ : MessageParticipantRole.TO;
const HANDLE = IS_SENDER ? 'outgoing' : 'incoming';
// Random date within the last 3 months
diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant.ts
index 839aeab20c..d05a1e327a 100644
--- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant.ts
@@ -271,8 +271,8 @@ export const STANDARD_OBJECTS = {
location: {
universalIdentifier: CALENDAR_EVENT_STANDARD_FIELD_IDS.location,
},
- iCalUID: {
- universalIdentifier: CALENDAR_EVENT_STANDARD_FIELD_IDS.iCalUID,
+ iCalUid: {
+ universalIdentifier: CALENDAR_EVENT_STANDARD_FIELD_IDS.iCalUid,
},
conferenceSolution: {
universalIdentifier:
diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-calendar-event-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-calendar-event-standard-flat-field-metadata.util.ts
index 09b5e893d0..3f8d1a5761 100644
--- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-calendar-event-standard-flat-field-metadata.util.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-calendar-event-standard-flat-field-metadata.util.ts
@@ -210,11 +210,11 @@ export const buildCalendarEventStandardFlatFieldMetadatas = ({
},
standardFieldMetadataIdByObjectAndFieldName,
}),
- iCalUID: createStandardFieldFlatMetadata({
+ iCalUid: createStandardFieldFlatMetadata({
objectName: 'calendarEvent',
workspaceId,
options: {
- fieldName: 'iCalUID',
+ fieldName: 'iCalUid',
type: FieldMetadataType.TEXT,
label: 'iCal UID',
description: 'iCal UID',
diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-message-channel-message-association-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-message-channel-message-association-standard-flat-field-metadata.util.ts
index 065b7fc882..aa4e00617b 100644
--- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-message-channel-message-association-standard-flat-field-metadata.util.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-message-channel-message-association-standard-flat-field-metadata.util.ts
@@ -9,6 +9,7 @@ import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/tw
import { createStandardFieldFlatMetadata } from 'src/engine/workspace-manager/twenty-standard-application/utils/create-standard-field-flat-metadata.util';
import { createStandardRelationFieldFlatMetadata } from 'src/engine/workspace-manager/twenty-standard-application/utils/create-standard-relation-field-flat-metadata.util';
import { type StandardFieldMetadataIdByObjectAndFieldName } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-field-metadata-id-by-object-and-field-name.util';
+import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum';
export const buildMessageChannelMessageAssociationStandardFlatFieldMetadatas =
({
@@ -128,10 +129,20 @@ export const buildMessageChannelMessageAssociationStandardFlatFieldMetadatas =
description: 'Message Direction',
icon: 'IconDirection',
isNullable: false,
- defaultValue: "'incoming'",
+ defaultValue: `'${MessageDirection.INCOMING}'`,
options: [
- { value: 'incoming', label: 'Incoming', position: 0, color: 'green' },
- { value: 'outgoing', label: 'Outgoing', position: 1, color: 'blue' },
+ {
+ value: MessageDirection.INCOMING,
+ label: 'Incoming',
+ position: 0,
+ color: 'green',
+ },
+ {
+ value: MessageDirection.OUTGOING,
+ label: 'Outgoing',
+ position: 1,
+ color: 'blue',
+ },
],
createdAt,
},
diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-message-channel-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-message-channel-standard-flat-field-metadata.util.ts
index f9e638577b..f60c92641e 100644
--- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-message-channel-standard-flat-field-metadata.util.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-message-channel-standard-flat-field-metadata.util.ts
@@ -9,6 +9,7 @@ import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/tw
import { createStandardFieldFlatMetadata } from 'src/engine/workspace-manager/twenty-standard-application/utils/create-standard-field-flat-metadata.util';
import { createStandardRelationFieldFlatMetadata } from 'src/engine/workspace-manager/twenty-standard-application/utils/create-standard-relation-field-flat-metadata.util';
import { type StandardFieldMetadataIdByObjectAndFieldName } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-field-metadata-id-by-object-and-field-name.util';
+import { MessageChannelType } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
export const buildMessageChannelStandardFlatFieldMetadatas = ({
createdAt,
@@ -138,10 +139,20 @@ export const buildMessageChannelStandardFlatFieldMetadatas = ({
description: 'Channel Type',
icon: 'IconMessage',
isNullable: false,
- defaultValue: "'email'",
+ defaultValue: `'${MessageChannelType.EMAIL}'`,
options: [
- { value: 'email', label: 'Email', position: 0, color: 'green' },
- { value: 'sms', label: 'SMS', position: 1, color: 'blue' },
+ {
+ value: MessageChannelType.EMAIL,
+ label: 'Email',
+ position: 0,
+ color: 'green',
+ },
+ {
+ value: MessageChannelType.SMS,
+ label: 'SMS',
+ position: 1,
+ color: 'blue',
+ },
],
createdAt,
},
diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-message-participant-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-message-participant-standard-flat-field-metadata.util.ts
index 83ebc1671f..8a787aa646 100644
--- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-message-participant-standard-flat-field-metadata.util.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-message-participant-standard-flat-field-metadata.util.ts
@@ -1,5 +1,6 @@
import {
FieldMetadataType,
+ MessageParticipantRole,
RelationOnDeleteAction,
RelationType,
} from 'twenty-shared/types';
@@ -99,12 +100,32 @@ export const buildMessageParticipantStandardFlatFieldMetadatas = ({
description: 'Role',
icon: 'IconAt',
isNullable: false,
- defaultValue: "'from'",
+ defaultValue: `'${MessageParticipantRole.FROM}'`,
options: [
- { value: 'from', label: 'From', position: 0, color: 'green' },
- { value: 'to', label: 'To', position: 1, color: 'blue' },
- { value: 'cc', label: 'Cc', position: 2, color: 'orange' },
- { value: 'bcc', label: 'Bcc', position: 3, color: 'red' },
+ {
+ value: MessageParticipantRole.FROM,
+ label: 'From',
+ position: 0,
+ color: 'green',
+ },
+ {
+ value: MessageParticipantRole.TO,
+ label: 'To',
+ position: 1,
+ color: 'blue',
+ },
+ {
+ value: MessageParticipantRole.CC,
+ label: 'Cc',
+ position: 2,
+ color: 'orange',
+ },
+ {
+ value: MessageParticipantRole.BCC,
+ label: 'Bcc',
+ position: 3,
+ color: 'red',
+ },
],
createdAt,
},
diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-workspace-member-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-workspace-member-standard-flat-field-metadata.util.ts
index ae310b2d09..2a4d952e03 100644
--- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-workspace-member-standard-flat-field-metadata.util.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/compute-workspace-member-standard-flat-field-metadata.util.ts
@@ -9,6 +9,7 @@ import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/tw
import { createStandardFieldFlatMetadata } from 'src/engine/workspace-manager/twenty-standard-application/utils/create-standard-field-flat-metadata.util';
import { createStandardRelationFieldFlatMetadata } from 'src/engine/workspace-manager/twenty-standard-application/utils/create-standard-relation-field-flat-metadata.util';
import { type StandardFieldMetadataIdByObjectAndFieldName } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-field-metadata-id-by-object-and-field-name.util';
+import { WorkspaceMemberNumberFormatEnum } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
export const buildWorkspaceMemberStandardFlatFieldMetadatas = ({
createdAt,
@@ -296,30 +297,35 @@ export const buildWorkspaceMemberStandardFlatFieldMetadatas = ({
icon: 'IconNumbers',
isSystem: true,
isNullable: false,
- defaultValue: "'SYSTEM'",
+ defaultValue: `'${WorkspaceMemberNumberFormatEnum.SYSTEM}'`,
options: [
- { value: 'SYSTEM', label: 'System', position: 0, color: 'turquoise' },
{
- value: 'COMMAS_AND_DOT',
- label: 'Commas and dot (1,234.56)',
+ value: WorkspaceMemberNumberFormatEnum.SYSTEM,
+ label: 'System',
+ position: 0,
+ color: 'turquoise',
+ },
+ {
+ value: WorkspaceMemberNumberFormatEnum.COMMAS_AND_DOT,
+ label: 'Commas and dot',
position: 1,
color: 'blue',
},
{
- value: 'SPACES_AND_COMMA',
- label: 'Spaces and comma (1 234,56)',
+ value: WorkspaceMemberNumberFormatEnum.SPACES_AND_COMMA,
+ label: 'Spaces and comma',
position: 2,
color: 'green',
},
{
- value: 'DOTS_AND_COMMA',
- label: 'Dots and comma (1.234,56)',
+ value: WorkspaceMemberNumberFormatEnum.DOTS_AND_COMMA,
+ label: 'Dots and comma',
position: 3,
color: 'orange',
},
{
- value: 'APOSTROPHE_AND_DOT',
- label: "Apostrophe and dot (1'234.56)",
+ value: WorkspaceMemberNumberFormatEnum.APOSTROPHE_AND_DOT,
+ label: 'Apostrophe and dot',
position: 4,
color: 'purple',
},
diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service.ts
index 5a230abcb3..6023ca3d9b 100644
--- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service.ts
@@ -146,7 +146,10 @@ export class FlatFieldMetadataValidatorService {
if (
flatFieldMetadataToValidate.isLabelSyncedWithName &&
- !isFlatFieldMetadataNameSyncedWithLabel(flatFieldMetadataToValidate)
+ !isFlatFieldMetadataNameSyncedWithLabel({
+ flatFieldMetadata: flatFieldMetadataToValidate,
+ isSystemBuild: buildOptions.isSystemBuild,
+ })
) {
validationResult.errors.push({
code: FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED,
@@ -318,7 +321,10 @@ export class FlatFieldMetadataValidatorService {
if (
flatFieldMetadataToValidate.isLabelSyncedWithName &&
- !isFlatFieldMetadataNameSyncedWithLabel(flatFieldMetadataToValidate)
+ !isFlatFieldMetadataNameSyncedWithLabel({
+ flatFieldMetadata: flatFieldMetadataToValidate,
+ isSystemBuild: buildOptions.isSystemBuild,
+ })
) {
validationResult.errors.push({
code: FieldMetadataExceptionCode.NAME_NOT_SYNCED_WITH_LABEL,
diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-object-metadata-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-object-metadata-validator.service.ts
index f0d15a9efc..1a9cc78ae4 100644
--- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-object-metadata-validator.service.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-object-metadata-validator.service.ts
@@ -65,6 +65,7 @@ export class FlatObjectMetadataValidatorService {
...validateFlatObjectMetadataNameAndLabels({
optimisticFlatObjectMetadataMaps,
flatObjectMetadataToValidate: updatedFlatObjectMetadata,
+ buildOptions,
}),
);
@@ -202,6 +203,7 @@ export class FlatObjectMetadataValidatorService {
...validateFlatObjectMetadataNameAndLabels({
optimisticFlatObjectMetadataMaps,
flatObjectMetadataToValidate,
+ buildOptions,
}),
);
diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts
index 4fda5be8fc..12e5cf20c1 100644
--- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts
@@ -102,7 +102,7 @@ export const CALENDAR_EVENT_STANDARD_FIELD_IDS = {
externalUpdatedAt: '20202020-b355-4c18-8825-ef42c8a5a755',
description: '20202020-52c4-4266-a98f-e90af0b4d271',
location: '20202020-641a-4ffe-960d-c3c186d95b17',
- iCalUID: '20202020-f24b-45f4-b6a3-d2f9fcb98714',
+ iCalUid: '20202020-f24b-45f4-b6a3-d2f9fcb98714',
conferenceSolution: '20202020-1c3f-4b5a-b526-5411a82179eb',
conferenceLink: '20202020-35da-43ef-9ca0-e936e9dc237b',
calendarChannelEventAssociations: '20202020-bdf8-4572-a2cc-ecbb6bcc3a02',
diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/lib/caldav.client.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/lib/caldav.client.ts
index 6299f85743..3aff9b2197 100644
--- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/lib/caldav.client.ts
+++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/lib/caldav.client.ts
@@ -272,7 +272,7 @@ export class CalDAVClient {
return {
id: objectUrl,
title: event.summary || 'Untitled Event',
- iCalUID: event.uid || '',
+ iCalUid: event.uid || '',
description: event.description || '',
startsAt: event.start.toISOString(),
endsAt: event.end.toISOString(),
diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/utils/__tests__/format-google-calendar-event.util.spec.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/utils/__tests__/format-google-calendar-event.util.spec.ts
index e03542029d..ca06a8a365 100644
--- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/utils/__tests__/format-google-calendar-event.util.spec.ts
+++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/utils/__tests__/format-google-calendar-event.util.spec.ts
@@ -120,6 +120,6 @@ describe('formatGoogleCalendarEvents', () => {
mockGoogleEventWithImproperUcalid,
]);
- expect(result[0].iCalUID).toBe(context.expected);
+ expect(result[0].iCalUid).toBe(context.expected);
});
});
diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/utils/format-google-calendar-event.util.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/utils/format-google-calendar-event.util.ts
index 8a47e446b9..2fdd5ee715 100644
--- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/utils/format-google-calendar-event.util.ts
+++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/utils/format-google-calendar-event.util.ts
@@ -38,7 +38,7 @@ const formatGoogleCalendarEvent = (
externalUpdatedAt: event.updated ?? '',
description: event.description ?? '',
location: event.location ?? '',
- iCalUID: event.iCalUID ?? '',
+ iCalUid: event.iCalUID ?? '',
conferenceSolution:
event.conferenceData?.conferenceSolution?.key?.type ?? '',
conferenceLinkLabel: event.conferenceData?.entryPoints?.[0]?.uri ?? '',
@@ -63,7 +63,7 @@ const formatGoogleCalendarEvent = (
'externalUpdatedAt',
'description',
'location',
- 'iCalUID',
+ 'iCalUid',
'conferenceSolution',
'conferenceLinkLabel',
'conferenceLinkUrl',
diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/utils/__tests__/format-microsoft-calendar-event.util.spec.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/utils/__tests__/format-microsoft-calendar-event.util.spec.ts
index 516fa38f8d..fa917da63e 100644
--- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/utils/__tests__/format-microsoft-calendar-event.util.spec.ts
+++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/utils/__tests__/format-microsoft-calendar-event.util.spec.ts
@@ -100,7 +100,7 @@ describe('formatMicrosoftCalendarEvents', () => {
mockMicrosoftEventWithImproperData2,
]);
- expect(result[0].iCalUID).toBe('eventStrange@microsoft.com');
- expect(result[1].iCalUID).toBe('>\u0015-;_�^�W&�p\u001f�');
+ expect(result[0].iCalUid).toBe('eventStrange@microsoft.com');
+ expect(result[1].iCalUid).toBe('>\u0015-;_�^�W&�p\u001f�');
});
});
diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/utils/format-microsoft-calendar-event.util.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/utils/format-microsoft-calendar-event.util.ts
index 3a55f9f777..fb737b2fdf 100644
--- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/utils/format-microsoft-calendar-event.util.ts
+++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/utils/format-microsoft-calendar-event.util.ts
@@ -42,7 +42,7 @@ const formatMicrosoftCalendarEvent = (event: Event): FetchedCalendarEvent => {
externalUpdatedAt: event.lastModifiedDateTime ?? '',
description: event.body?.content ?? '',
location: event.location?.displayName ?? '',
- iCalUID: event.iCalUId ?? '',
+ iCalUid: event.iCalUId ?? '',
conferenceSolution: event.onlineMeetingProvider ?? '',
conferenceLinkLabel: event.onlineMeeting?.joinUrl ?? '',
conferenceLinkUrl: event.onlineMeeting?.joinUrl ?? '',
@@ -66,7 +66,7 @@ const formatMicrosoftCalendarEvent = (event: Event): FetchedCalendarEvent => {
'externalUpdatedAt',
'description',
'location',
- 'iCalUID',
+ 'iCalUid',
'conferenceSolution',
'conferenceLinkLabel',
'conferenceLinkUrl',
diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts
index 1f83c1e544..3cb8e2cf0b 100644
--- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts
+++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts
@@ -44,21 +44,21 @@ export class CalendarSaveEventsService {
const existingCalendarEvents = await calendarEventRepository.find({
where: {
- iCalUID: Any(
- fetchedCalendarEvents.map((event) => event.iCalUID as string),
+ iCalUid: Any(
+ fetchedCalendarEvents.map((event) => event.iCalUid as string),
),
},
});
const fetchedCalendarEventsWithDBEvents: FetchedCalendarEventWithDBEvent[] =
fetchedCalendarEvents.map((event): FetchedCalendarEventWithDBEvent => {
- const existingEventWithSameiCalUID = existingCalendarEvents.find(
- (existingEvent) => existingEvent.iCalUID === event.iCalUID,
+ const existingEventWithSameiCalUid = existingCalendarEvents.find(
+ (existingEvent) => existingEvent.iCalUid === event.iCalUid,
);
return {
fetchedCalendarEvent: event,
- existingCalendarEvent: existingEventWithSameiCalUID ?? null,
+ existingCalendarEvent: existingEventWithSameiCalUid ?? null,
newlyCreatedCalendarEvent: null,
};
});
@@ -78,7 +78,7 @@ export class CalendarSaveEventsService {
.map(
({ fetchedCalendarEvent }) =>
({
- iCalUID: fetchedCalendarEvent.iCalUID,
+ iCalUid: fetchedCalendarEvent.iCalUid,
title: fetchedCalendarEvent.title,
description: fetchedCalendarEvent.description,
startsAt: fetchedCalendarEvent.startsAt,
@@ -113,7 +113,7 @@ export class CalendarSaveEventsService {
({ fetchedCalendarEvent, existingCalendarEvent }) => {
const savedCalendarEvent = savedCalendarEvents.find(
(savedCalendarEvent) =>
- savedCalendarEvent.iCalUID === fetchedCalendarEvent.iCalUID,
+ savedCalendarEvent.iCalUid === fetchedCalendarEvent.iCalUid,
);
return {
@@ -132,13 +132,13 @@ export class CalendarSaveEventsService {
.map(({ fetchedCalendarEvent, existingCalendarEvent }) => {
if (!existingCalendarEvent) {
throw new Error(
- `Existing calendar event with iCalUID ${fetchedCalendarEvent.iCalUID} not found - should never happen`,
+ `Existing calendar event with iCalUid ${fetchedCalendarEvent.iCalUid} not found - should never happen`,
);
}
return {
id: existingCalendarEvent.id,
- iCalUID: fetchedCalendarEvent.iCalUID,
+ iCalUid: fetchedCalendarEvent.iCalUid,
title: fetchedCalendarEvent.title,
description: fetchedCalendarEvent.description,
startsAt: fetchedCalendarEvent.startsAt,
@@ -184,7 +184,7 @@ export class CalendarSaveEventsService {
if (!calendarEventId) {
throw new Error(
- `Calendar event id not found for event with iCalUID ${fetchedCalendarEvent.iCalUID} - should never happen`,
+ `Calendar event id not found for event with iCalUid ${fetchedCalendarEvent.iCalUid} - should never happen`,
);
}
@@ -213,7 +213,7 @@ export class CalendarSaveEventsService {
.flatMap(({ newlyCreatedCalendarEvent, fetchedCalendarEvent }) => {
if (!newlyCreatedCalendarEvent?.id) {
throw new Error(
- `Newly created calendar event with iCalUID ${fetchedCalendarEvent.iCalUID} not found - should never happen`,
+ `Newly created calendar event with iCalUid ${fetchedCalendarEvent.iCalUid} not found - should never happen`,
);
}
@@ -233,7 +233,7 @@ export class CalendarSaveEventsService {
.flatMap(({ fetchedCalendarEvent, existingCalendarEvent }) => {
if (!existingCalendarEvent?.id) {
throw new Error(
- `Existing calendar event with iCalUID ${fetchedCalendarEvent.iCalUID} not found - should never happen`,
+ `Existing calendar event with iCalUid ${fetchedCalendarEvent.iCalUid} not found - should never happen`,
);
}
diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/utils/calendar-event-mapper.util.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/utils/calendar-event-mapper.util.ts
index a098150532..6536cd61d2 100644
--- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/utils/calendar-event-mapper.util.ts
+++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/utils/calendar-event-mapper.util.ts
@@ -5,7 +5,7 @@ export const mapCalendarEventsByICalUID = (
): Map => {
return new Map(
existingCalendarEvents.map((calendarEvent) => [
- calendarEvent.iCalUID ?? '',
+ calendarEvent.iCalUid ?? '',
calendarEvent.id,
]),
);
diff --git a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.spec.ts b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.spec.ts
index 662aba6e42..485f44c40b 100644
--- a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.spec.ts
+++ b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.spec.ts
@@ -31,7 +31,7 @@ const createMockCalendarEvent = (
deletedAt: null,
createdAt: '2024-03-20T09:00:00Z',
updatedAt: '2024-03-20T09:00:00Z',
- iCalUID: '',
+ iCalUid: '',
conferenceSolution: '',
calendarChannelEventAssociations: [],
calendarEventParticipants: [],
diff --git a/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-event.workspace-entity.ts b/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-event.workspace-entity.ts
index 3ff9042db8..500e4ad968 100644
--- a/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-event.workspace-entity.ts
+++ b/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-event.workspace-entity.ts
@@ -127,14 +127,14 @@ export class CalendarEventWorkspaceEntity extends BaseWorkspaceEntity {
location: string | null;
@WorkspaceField({
- standardId: CALENDAR_EVENT_STANDARD_FIELD_IDS.iCalUID,
+ standardId: CALENDAR_EVENT_STANDARD_FIELD_IDS.iCalUid,
type: FieldMetadataType.TEXT,
label: msg`iCal UID`,
description: msg`iCal UID`,
icon: 'IconKey',
})
@WorkspaceIsNullable()
- iCalUID: string | null;
+ iCalUid: string | null;
@WorkspaceField({
standardId: CALENDAR_EVENT_STANDARD_FIELD_IDS.conferenceSolution,
diff --git a/packages/twenty-server/src/modules/calendar/common/types/fetched-calendar-event.ts b/packages/twenty-server/src/modules/calendar/common/types/fetched-calendar-event.ts
index 85239d585b..e183454139 100644
--- a/packages/twenty-server/src/modules/calendar/common/types/fetched-calendar-event.ts
+++ b/packages/twenty-server/src/modules/calendar/common/types/fetched-calendar-event.ts
@@ -8,7 +8,7 @@ export type FetchedCalendarEventParticipant = {
export type FetchedCalendarEvent = {
id: string;
title: string;
- iCalUID: string;
+ iCalUid: string;
description: string;
startsAt: string;
endsAt: string;
diff --git a/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-item-delete-messages.job.ts b/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-item-delete-messages.job.ts
index 12ad56e0af..aedc03c2d1 100644
--- a/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-item-delete-messages.job.ts
+++ b/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-item-delete-messages.job.ts
@@ -2,6 +2,7 @@ import { Scope } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { And, Any, ILike, In, Not, Or } from 'typeorm';
+import { MessageParticipantRole } from 'twenty-shared/types';
import { type ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
@@ -87,7 +88,10 @@ export class BlocklistItemDeleteMessagesJob {
continue;
}
- const rolesToDelete: ('from' | 'to')[] = ['from', 'to'];
+ const rolesToDelete = [
+ MessageParticipantRole.FROM,
+ MessageParticipantRole.TO,
+ ] as const;
const messageChannels = await messageChannelRepository.find({
select: {
diff --git a/packages/twenty-server/src/modules/messaging/common/standard-objects/message-channel.workspace-entity.ts b/packages/twenty-server/src/modules/messaging/common/standard-objects/message-channel.workspace-entity.ts
index 33f17e8538..d2f4ce6d0d 100644
--- a/packages/twenty-server/src/modules/messaging/common/standard-objects/message-channel.workspace-entity.ts
+++ b/packages/twenty-server/src/modules/messaging/common/standard-objects/message-channel.workspace-entity.ts
@@ -47,8 +47,8 @@ export enum MessageChannelVisibility {
}
export enum MessageChannelType {
- EMAIL = 'email',
- SMS = 'sms',
+ EMAIL = 'EMAIL',
+ SMS = 'SMS',
}
export enum MessageChannelContactAutoCreationPolicy {
diff --git a/packages/twenty-server/src/modules/messaging/common/standard-objects/message-participant.workspace-entity.ts b/packages/twenty-server/src/modules/messaging/common/standard-objects/message-participant.workspace-entity.ts
index 76e1701ff2..3b435fddc3 100644
--- a/packages/twenty-server/src/modules/messaging/common/standard-objects/message-participant.workspace-entity.ts
+++ b/packages/twenty-server/src/modules/messaging/common/standard-objects/message-participant.workspace-entity.ts
@@ -1,6 +1,10 @@
import { msg } from '@lingui/core/macro';
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
-import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
+import {
+ FieldMetadataType,
+ MessageParticipantRole,
+ RelationOnDeleteAction,
+} from 'twenty-shared/types';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
@@ -39,14 +43,34 @@ export class MessageParticipantWorkspaceEntity extends BaseWorkspaceEntity {
description: msg`Role`,
icon: 'IconAt',
options: [
- { value: 'from', label: 'From', position: 0, color: 'green' },
- { value: 'to', label: 'To', position: 1, color: 'blue' },
- { value: 'cc', label: 'Cc', position: 2, color: 'orange' },
- { value: 'bcc', label: 'Bcc', position: 3, color: 'red' },
+ {
+ value: MessageParticipantRole.FROM,
+ label: 'From',
+ position: 0,
+ color: 'green',
+ },
+ {
+ value: MessageParticipantRole.TO,
+ label: 'To',
+ position: 1,
+ color: 'blue',
+ },
+ {
+ value: MessageParticipantRole.CC,
+ label: 'Cc',
+ position: 2,
+ color: 'orange',
+ },
+ {
+ value: MessageParticipantRole.BCC,
+ label: 'Bcc',
+ position: 3,
+ color: 'red',
+ },
],
- defaultValue: "'from'",
+ defaultValue: `'${MessageParticipantRole.FROM}'`,
})
- role: string;
+ role: MessageParticipantRole;
@WorkspaceField({
standardId: MESSAGE_PARTICIPANT_STANDARD_FIELD_IDS.handle,
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-message.type.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-message.type.ts
index ee87048f71..9f8c04d9a8 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-message.type.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-message.type.ts
@@ -1,3 +1,5 @@
+import { type MessageParticipantRole } from 'twenty-shared/types';
+
export type GmailMessage = {
historyId: string;
externalId: string;
@@ -13,7 +15,7 @@ export type GmailMessage = {
};
export type Participant = {
- role: 'from' | 'to' | 'cc' | 'bcc';
+ role: MessageParticipantRole;
handle: string;
displayName: string;
};
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-and-format-gmail-message.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-and-format-gmail-message.util.ts
index 3e1d31debd..40f006569f 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-and-format-gmail-message.util.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-and-format-gmail-message.util.ts
@@ -1,5 +1,6 @@
import { type gmail_v1 as gmailV1 } from 'googleapis';
import planer from 'planer';
+import { MessageParticipantRole } from 'twenty-shared/types';
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { computeMessageDirection } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-message-direction.util';
@@ -43,17 +44,28 @@ export const parseAndFormatGmailMessage = (
const participants = [
...(from
- ? formatAddressObjectAsParticipants([{ address: from }], 'from')
+ ? formatAddressObjectAsParticipants(
+ [{ address: from }],
+ MessageParticipantRole.FROM,
+ )
: []),
...(toParticipants
? formatAddressObjectAsParticipants(
[{ address: toParticipants, name: '' }],
- 'to',
+ MessageParticipantRole.TO,
+ )
+ : []),
+ ...(cc
+ ? formatAddressObjectAsParticipants(
+ [{ address: cc }],
+ MessageParticipantRole.CC,
)
: []),
- ...(cc ? formatAddressObjectAsParticipants([{ address: cc }], 'cc') : []),
...(bcc
- ? formatAddressObjectAsParticipants([{ address: bcc }], 'bcc')
+ ? formatAddressObjectAsParticipants(
+ [{ address: bcc }],
+ MessageParticipantRole.BCC,
+ )
: []),
];
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts
index ed50476aa0..343ad54072 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts
@@ -2,6 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { type ImapFlow } from 'imapflow';
import { Address, type Email as ParsedMail } from 'postal-mime';
+import { MessageParticipantRole } from 'twenty-shared/types';
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { computeMessageDirection } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-message-direction.util';
@@ -203,11 +204,11 @@ export class ImapGetMessagesService {
private extractParticipants(parsed: ParsedMail) {
const addressFields = [
- { field: parsed.from, role: 'from' as const },
- { field: parsed.to, role: 'to' as const },
- { field: parsed.cc, role: 'cc' as const },
- { field: parsed.bcc, role: 'bcc' as const },
- ];
+ { field: parsed.from, role: MessageParticipantRole.FROM },
+ { field: parsed.to, role: MessageParticipantRole.TO },
+ { field: parsed.cc, role: MessageParticipantRole.CC },
+ { field: parsed.bcc, role: MessageParticipantRole.BCC },
+ ] as const;
return addressFields.flatMap(({ field, role }) =>
formatAddressObjectAsParticipants(this.extractAddresses(field), role),
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.spec.ts
index 1ec09e6f02..da35e1f39f 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.spec.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.spec.ts
@@ -2,7 +2,10 @@ import { Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Test, type TestingModule } from '@nestjs/testing';
-import { ConnectedAccountProvider } from 'twenty-shared/types';
+import {
+ ConnectedAccountProvider,
+ MessageParticipantRole,
+} from 'twenty-shared/types';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { GoogleOAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/drivers/google/google-oauth2-client-manager.service';
@@ -97,12 +100,12 @@ describe('Microsoft get messages service', () => {
{
displayName: 'John l',
handle: 'john.l@outlook.fr',
- role: 'from',
+ role: MessageParticipantRole.FROM,
},
{
displayName: 'Walker',
handle: 'walker@felixacme.onmicrosoft.com',
- role: 'to',
+ role: MessageParticipantRole.TO,
},
],
attachments: [],
@@ -130,22 +133,22 @@ describe('Microsoft get messages service', () => {
{
displayName: 'Microsoft',
handle: 'microsoft-noreply@microsoft.com',
- role: 'from',
+ role: MessageParticipantRole.FROM,
},
{
displayName: 'Walker',
handle: 'walker@felixacme.onmicrosoft.com',
- role: 'to',
+ role: MessageParticipantRole.TO,
},
{
displayName: 'Antoine',
handle: 'antoine@gmail.com',
- role: 'cc',
+ role: MessageParticipantRole.CC,
},
{
displayName: 'Cyril@acme2.com',
handle: 'cyril@acme2.com',
- role: 'cc',
+ role: MessageParticipantRole.CC,
},
],
attachments: [],
@@ -184,7 +187,7 @@ describe('Microsoft get messages service', () => {
displayName: responseExample.body.sender.emailAddress.name,
handle:
responseExample.body.sender.emailAddress.address.toLowerCase(),
- role: 'from',
+ role: MessageParticipantRole.FROM,
},
],
attachments: [],
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.ts
index cf9b4e016a..021957bee9 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.ts
@@ -2,6 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { type EmailAddress } from 'addressparser';
import { isDefined } from 'twenty-shared/utils';
+import { MessageParticipantRole } from 'twenty-shared/types';
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum';
@@ -109,16 +110,28 @@ export class MicrosoftGetMessagesService {
const participants = [
...(safeParseFrom
- ? formatAddressObjectAsParticipants(safeParseFrom, 'from')
+ ? formatAddressObjectAsParticipants(
+ safeParseFrom,
+ MessageParticipantRole.FROM,
+ )
: []),
...(safeParseTo
- ? formatAddressObjectAsParticipants(safeParseTo, 'to')
+ ? formatAddressObjectAsParticipants(
+ safeParseTo,
+ MessageParticipantRole.TO,
+ )
: []),
...(safeParseCc
- ? formatAddressObjectAsParticipants(safeParseCc, 'cc')
+ ? formatAddressObjectAsParticipants(
+ safeParseCc,
+ MessageParticipantRole.CC,
+ )
: []),
...(safeParseBcc
- ? formatAddressObjectAsParticipants(safeParseBcc, 'bcc')
+ ? formatAddressObjectAsParticipants(
+ safeParseBcc,
+ MessageParticipantRole.BCC,
+ )
: []),
];
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-delete-group-email-messages.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-delete-group-email-messages.service.ts
index a2a6209a1c..74d289b798 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-delete-group-email-messages.service.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-delete-group-email-messages.service.ts
@@ -2,6 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
import chunk from 'lodash.chunk';
import { isDefined } from 'twenty-shared/utils';
+import { MessageParticipantRole } from 'twenty-shared/types';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity';
@@ -55,7 +56,7 @@ export class MessagingDeleteGroupEmailMessagesService {
'message.messageParticipants',
'participant',
'participant.role = :role',
- { role: 'from' },
+ { role: MessageParticipantRole.FROM },
)
.where('mcma.messageChannelId = :messageChannelId', {
messageChannelId,
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts
index 71a74f9daf..ccc804670b 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts
@@ -1,7 +1,7 @@
import { Test, type TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
-import { FieldActorSource } from 'twenty-shared/types';
+import { FieldActorSource, MessageParticipantRole } from 'twenty-shared/types';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
@@ -56,8 +56,16 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => {
messageThreadExternalId: 'thread-1',
direction: MessageDirection.OUTGOING,
participants: [
- { role: 'from', handle: 'test@example.com', displayName: 'Test User' },
- { role: 'to', handle: 'contact@company.com', displayName: 'Contact' },
+ {
+ role: MessageParticipantRole.FROM,
+ handle: 'test@example.com',
+ displayName: 'Test User',
+ },
+ {
+ role: MessageParticipantRole.TO,
+ handle: 'contact@company.com',
+ displayName: 'Contact',
+ },
],
},
{
@@ -70,11 +78,23 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => {
messageThreadExternalId: 'thread-1',
direction: MessageDirection.INCOMING,
participants: [
- { role: 'from', handle: 'contact@company.com', displayName: 'Contact' },
- { role: 'to', handle: 'test@example.com', displayName: 'Test User' },
- { role: 'to', handle: 'personal@gmail.com', displayName: 'Personal' },
{
- role: 'to',
+ role: MessageParticipantRole.FROM,
+ handle: 'contact@company.com',
+ displayName: 'Contact',
+ },
+ {
+ role: MessageParticipantRole.TO,
+ handle: 'test@example.com',
+ displayName: 'Test User',
+ },
+ {
+ role: MessageParticipantRole.TO,
+ handle: 'personal@gmail.com',
+ displayName: 'Personal',
+ },
+ {
+ role: MessageParticipantRole.TO,
handle: 'team@lists.company.com',
displayName: 'Group email',
},
@@ -199,7 +219,7 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => {
...mockMessages[1],
participants: [
{
- role: 'from',
+ role: MessageParticipantRole.FROM,
handle: 'tim@apple.com',
displayName: 'participant email',
},
@@ -221,7 +241,7 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => {
{
handle: 'tim@apple.com',
displayName: 'participant email',
- role: 'from',
+ role: MessageParticipantRole.FROM,
shouldCreateContact: true,
messageId: 'db-message-id-2',
},
@@ -237,7 +257,7 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => {
...mockMessages[0],
participants: [
{
- role: 'from',
+ role: MessageParticipantRole.FROM,
handle: 'test@gmail.com',
displayName: 'participant personal email',
},
@@ -265,7 +285,7 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => {
...mockMessages[0],
participants: [
{
- role: 'from',
+ role: MessageParticipantRole.FROM,
handle: 'connected@account.com',
displayName: 'participant that is the Connected Account',
},
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts
index 1b71c9868c..bffa6656d4 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts
@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
-import { FieldActorSource } from 'twenty-shared/types';
+import { FieldActorSource, MessageParticipantRole } from 'twenty-shared/types';
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
@@ -66,8 +66,9 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
return messageId
? message.participants.map((participant: Participant) => {
const fromHandle =
- message.participants.find((p) => p.role === 'from')?.handle ||
- '';
+ message.participants.find(
+ (p) => p.role === MessageParticipantRole.FROM,
+ )?.handle || '';
const isMessageSentByConnectedAccount =
handleAliases.includes(fromHandle) ||
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__mocks__/messages.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__mocks__/messages.ts
index 4cc2ead8f9..761a876235 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__mocks__/messages.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__mocks__/messages.ts
@@ -1,3 +1,6 @@
+import { MessageParticipantRole } from 'twenty-shared/types';
+
+import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum';
import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message';
export const messagingGetMessagesServiceGetMessages = [
@@ -8,15 +11,15 @@ export const messagingGetMessagesServiceGetMessages = [
text: 'Just a test',
headerMessageId: '',
messageThreadExternalId: 'AAQkAGZlMDQ1NjU5Lk=',
- direction: 'OUTGOING',
+ direction: MessageDirection.OUTGOING,
participants: [
{
- role: 'from',
+ role: MessageParticipantRole.FROM,
handle: 'from@acme.com',
displayName: 'From',
},
{
- role: 'to',
+ role: MessageParticipantRole.TO,
handle: 'to@acme.com',
displayName: 'To',
},
@@ -30,15 +33,15 @@ export const messagingGetMessagesServiceGetMessages = [
text: 'Just a test',
headerMessageId: '',
messageThreadExternalId: 'AAQkAGZlMDQ1NjU5Lk=',
- direction: 'OUTGOING',
+ direction: MessageDirection.OUTGOING,
participants: [
{
- role: 'from',
+ role: MessageParticipantRole.FROM,
handle: 'from@acme.com',
displayName: 'From',
},
{
- role: 'to',
+ role: MessageParticipantRole.TO,
handle: 'to@external.com',
displayName: 'To',
},
@@ -52,19 +55,19 @@ export const messagingGetMessagesServiceGetMessages = [
text: 'Learn with interactive sandboxes, curated collections, and bookmarks',
headerMessageId: '',
messageThreadExternalId: 'AAQkAGZlMDQ1NNc=',
- direction: 'INCOMING',
+ direction: MessageDirection.INCOMING,
participants: [
{
- role: 'from',
+ role: MessageParticipantRole.FROM,
handle: 'learn@mails.microsoft.com',
displayName: 'Microsoft Learn',
},
{
- role: 'to',
+ role: MessageParticipantRole.TO,
handle: 'to@gmail.com',
displayName: 'To',
},
],
attachments: [],
},
-] as MessageWithParticipants[];
+] satisfies MessageWithParticipants[];
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__tests__/filter-emails.util.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__tests__/filter-emails.util.spec.ts
index e11678741c..8b3ab767bd 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__tests__/filter-emails.util.spec.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__tests__/filter-emails.util.spec.ts
@@ -1,3 +1,5 @@
+import { MessageParticipantRole } from 'twenty-shared/types';
+
import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum';
import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message';
import { messagingGetMessagesServiceGetMessages } from 'src/modules/messaging/message-import-manager/utils/__mocks__/messages';
@@ -64,7 +66,7 @@ describe('filterEmails', () => {
direction: MessageDirection.INCOMING,
participants: [
{
- role: 'from',
+ role: MessageParticipantRole.FROM,
handle: 'noreply@example.com',
displayName: 'No Reply',
},
@@ -81,7 +83,7 @@ describe('filterEmails', () => {
direction: MessageDirection.INCOMING,
participants: [
{
- role: 'from',
+ role: MessageParticipantRole.FROM,
handle: 'support@company.com',
displayName: 'Support Team',
},
@@ -98,7 +100,7 @@ describe('filterEmails', () => {
direction: MessageDirection.INCOMING,
participants: [
{
- role: 'from',
+ role: MessageParticipantRole.FROM,
handle: 'john@example.com',
displayName: 'John Doe',
},
@@ -126,7 +128,7 @@ describe('filterEmails', () => {
direction: MessageDirection.INCOMING,
participants: [
{
- role: 'from',
+ role: MessageParticipantRole.FROM,
handle: 'noreply@example.com',
displayName: 'No Reply',
},
@@ -174,7 +176,7 @@ describe('filterEmails', () => {
direction: MessageDirection.INCOMING,
participants: [
{
- role: 'from',
+ role: MessageParticipantRole.FROM,
handle: 'john.doe@example.com',
displayName: 'John Doe',
},
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__tests__/format-address-object-as-participants.util.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__tests__/format-address-object-as-participants.util.spec.ts
index cff04d2dae..79d72f3161 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__tests__/format-address-object-as-participants.util.spec.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__tests__/format-address-object-as-participants.util.spec.ts
@@ -1,3 +1,5 @@
+import { MessageParticipantRole } from 'twenty-shared/types';
+
import { formatAddressObjectAsParticipants } from 'src/modules/messaging/message-import-manager/utils/format-address-object-as-participants.util';
describe('formatAddressObjectAsParticipants', () => {
@@ -7,16 +9,19 @@ describe('formatAddressObjectAsParticipants', () => {
{ name: 'Jane Smith', address: 'jane.smith@example.com ' },
];
- const result = formatAddressObjectAsParticipants(addresses, 'from');
+ const result = formatAddressObjectAsParticipants(
+ addresses,
+ MessageParticipantRole.FROM,
+ );
expect(result).toEqual([
{
- role: 'from',
+ role: MessageParticipantRole.FROM,
handle: 'john.doe@example.com',
displayName: 'John Doe',
},
{
- role: 'from',
+ role: MessageParticipantRole.FROM,
handle: 'jane.smith@example.com',
displayName: 'Jane Smith',
},
@@ -29,7 +34,10 @@ describe('formatAddressObjectAsParticipants', () => {
address: 'john.doe',
};
- const result = formatAddressObjectAsParticipants([addressObject], 'to');
+ const result = formatAddressObjectAsParticipants(
+ [addressObject],
+ MessageParticipantRole.TO,
+ );
expect(result).toEqual([]);
});
@@ -40,7 +48,10 @@ describe('formatAddressObjectAsParticipants', () => {
address: '',
};
- const result = formatAddressObjectAsParticipants([addressObject], 'to');
+ const result = formatAddressObjectAsParticipants(
+ [addressObject],
+ MessageParticipantRole.TO,
+ );
expect(result).toEqual([]);
});
@@ -51,11 +62,14 @@ describe('formatAddressObjectAsParticipants', () => {
address: 'John.Doe@example.com',
};
- const result = formatAddressObjectAsParticipants([addressObject], 'to');
+ const result = formatAddressObjectAsParticipants(
+ [addressObject],
+ MessageParticipantRole.TO,
+ );
expect(result).toEqual([
{
- role: 'to',
+ role: MessageParticipantRole.TO,
handle: 'john.doe@example.com',
displayName: 'John Doe',
},
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-group-emails.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-group-emails.util.ts
index bd6b98c032..09f0190ef8 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-group-emails.util.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-group-emails.util.ts
@@ -1,3 +1,5 @@
+import { MessageParticipantRole } from 'twenty-shared/types';
+
import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message';
import { isGroupEmail } from 'src/modules/messaging/message-import-manager/utils/is-group-email';
@@ -8,7 +10,7 @@ export const filterOutGroupEmails = (messages: MessageWithParticipants[]) => {
}
const fromParticipant = message.participants.find(
- (participant) => participant.role === 'from',
+ (participant) => participant.role === MessageParticipantRole.FROM,
);
if (!fromParticipant || !fromParticipant.handle) {
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/format-address-object-as-participants.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/format-address-object-as-participants.util.ts
index 1745f9f917..38bf0af196 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/format-address-object-as-participants.util.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/format-address-object-as-participants.util.ts
@@ -1,4 +1,5 @@
import { isDefined } from 'twenty-shared/utils';
+import { type MessageParticipantRole } from 'twenty-shared/types';
import { type Participant } from 'src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-message.type';
import { type EmailAddress } from 'src/modules/messaging/message-import-manager/types/email-address';
@@ -9,7 +10,7 @@ const removeSpacesAndLowerCase = (email: string): string => {
export const formatAddressObjectAsParticipants = (
addressObjects: EmailAddress[],
- role: 'from' | 'to' | 'cc' | 'bcc',
+ role: MessageParticipantRole,
): Participant[] => {
const participants = addressObjects.map((addressObject) => {
const address = addressObject.address;
diff --git a/packages/twenty-server/src/modules/workspace-member/standard-objects/workspace-member.workspace-entity.ts b/packages/twenty-server/src/modules/workspace-member/standard-objects/workspace-member.workspace-entity.ts
index 294ec08943..50f5ee9ed8 100644
--- a/packages/twenty-server/src/modules/workspace-member/standard-objects/workspace-member.workspace-entity.ts
+++ b/packages/twenty-server/src/modules/workspace-member/standard-objects/workspace-member.workspace-entity.ts
@@ -410,25 +410,25 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
},
{
value: WorkspaceMemberNumberFormatEnum.COMMAS_AND_DOT,
- label: 'Commas and dot (1,234.56)',
+ label: 'Commas and dot',
position: 1,
color: 'blue',
},
{
value: WorkspaceMemberNumberFormatEnum.SPACES_AND_COMMA,
- label: 'Spaces and comma (1 234,56)',
+ label: 'Spaces and comma',
position: 2,
color: 'green',
},
{
value: WorkspaceMemberNumberFormatEnum.DOTS_AND_COMMA,
- label: 'Dots and comma (1.234,56)',
+ label: 'Dots and comma',
position: 3,
color: 'orange',
},
{
value: WorkspaceMemberNumberFormatEnum.APOSTROPHE_AND_DOT,
- label: "Apostrophe and dot (1'234.56)",
+ label: 'Apostrophe and dot',
position: 4,
color: 'purple',
},
diff --git a/packages/twenty-shared/src/metadata/sanitize-reserved-keyword.util.ts b/packages/twenty-shared/src/metadata/add-custom-suffix-if-reserved.util.ts
similarity index 57%
rename from packages/twenty-shared/src/metadata/sanitize-reserved-keyword.util.ts
rename to packages/twenty-shared/src/metadata/add-custom-suffix-if-reserved.util.ts
index 60b10a0083..c3e4b8534e 100644
--- a/packages/twenty-shared/src/metadata/sanitize-reserved-keyword.util.ts
+++ b/packages/twenty-shared/src/metadata/add-custom-suffix-if-reserved.util.ts
@@ -1,10 +1,9 @@
-import { capitalize } from '../utils';
import { RESERVED_METADATA_NAME_KEYWORDS } from './reserved-metadata-name-keywords.constant';
-export const sanitizeReservedKeyword = (name: string): string => {
+export const addCustomSuffixIfIsReserved = (name: string): string => {
if (!name) return name;
return RESERVED_METADATA_NAME_KEYWORDS.includes(name)
- ? `${name}${capitalize('custom')}`
+ ? `${name}Custom`
: name;
};
diff --git a/packages/twenty-shared/src/metadata/compute-metadata-name-from-label.util.ts b/packages/twenty-shared/src/metadata/compute-metadata-name-from-label.util.ts
index ba612410c2..0b935b5917 100644
--- a/packages/twenty-shared/src/metadata/compute-metadata-name-from-label.util.ts
+++ b/packages/twenty-shared/src/metadata/compute-metadata-name-from-label.util.ts
@@ -1,9 +1,14 @@
+import { addCustomSuffixIfIsReserved } from '@/metadata/add-custom-suffix-if-reserved.util';
import camelCase from 'lodash.camelcase';
import { slugify } from 'transliteration';
-import { sanitizeReservedKeyword } from './sanitize-reserved-keyword.util';
-
-export const computeMetadataNameFromLabel = (label: string): string => {
+export const computeMetadataNameFromLabel = ({
+ label,
+ applyCustomSuffix = true,
+}: {
+ label: string;
+ applyCustomSuffix?: boolean;
+}): string => {
if (!label) return '';
const prefixedLabel = /^\d/.test(label) ? `n${label}` : label;
@@ -22,5 +27,7 @@ export const computeMetadataNameFromLabel = (label: string): string => {
const computedName = camelCase(formattedString);
- return sanitizeReservedKeyword(computedName);
+ return applyCustomSuffix
+ ? addCustomSuffixIfIsReserved(computedName)
+ : computedName;
};
diff --git a/packages/twenty-shared/src/metadata/index.ts b/packages/twenty-shared/src/metadata/index.ts
index 9310bde475..7b83687eee 100644
--- a/packages/twenty-shared/src/metadata/index.ts
+++ b/packages/twenty-shared/src/metadata/index.ts
@@ -7,6 +7,7 @@
* |___/
*/
+export { addCustomSuffixIfIsReserved } from './add-custom-suffix-if-reserved.util';
export { ALL_METADATA_NAME } from './all-metadata-name.constant';
export type { AllMetadataName } from './all-metadata-name.type';
export { computeMetadataNameFromLabel } from './compute-metadata-name-from-label.util';
@@ -20,5 +21,4 @@ export {
CORE_OBJECT_NAMES,
RESERVED_METADATA_NAME_KEYWORDS,
} from './reserved-metadata-name-keywords.constant';
-export { sanitizeReservedKeyword } from './sanitize-reserved-keyword.util';
export { STANDARD_OBJECT_IDS } from './standard-object-ids';
diff --git a/packages/twenty-shared/src/types/MessageParticipantRole.ts b/packages/twenty-shared/src/types/MessageParticipantRole.ts
new file mode 100644
index 0000000000..6f0739d706
--- /dev/null
+++ b/packages/twenty-shared/src/types/MessageParticipantRole.ts
@@ -0,0 +1,6 @@
+export enum MessageParticipantRole {
+ 'FROM' = 'FROM',
+ 'TO' = 'TO',
+ 'CC' = 'CC',
+ 'BCC' = 'BCC',
+}
diff --git a/packages/twenty-shared/src/types/index.ts b/packages/twenty-shared/src/types/index.ts
index f1cbfddb6d..38d036572d 100644
--- a/packages/twenty-shared/src/types/index.ts
+++ b/packages/twenty-shared/src/types/index.ts
@@ -114,6 +114,7 @@ export type { FromTo } from './FromToType';
export { HTTPMethod } from './HttpMethod';
export type { IsEmptyRecord } from './IsEmptyRecord.type';
export type { IsExactly } from './IsExactly';
+export { MessageParticipantRole } from './MessageParticipantRole';
export type { ModifiedProperties } from './ModifiedProperties';
export type { NonNullableRequired } from './NonNullableRequired';
export type { Nullable } from './Nullable';