Compare commits

...

6 Commits

Author SHA1 Message Date
Charles Bochet 6bb8fa8559 Fix workspace relation sync (#11963)
While deploying the IS_NEW_RELATION_ENABLED (we don't compute relation
based on relationMetadata anymore) to existing workspace, I've tested to
run a sync-metadata post feature flag activation. This has raised two
issues:
- the workspaceMigration generator (which is over-complex and should be
refactored later) for fieldMetadata of type RELATION was not handling
settings update properly ;
- we need to delete existing fieldMetadata corresponding to the UUID
foreignKey as they are not needed anymore. This is handled as a 0.53
upgrade command as 0.53 will also come with the full removal of the old
relation system

---------

Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
Co-authored-by: prastoin <paul@twenty.com>
2025-05-10 11:54:59 +02:00
Paul Rastoin a771216f6e Fix field currency form settings (#11948)
# Introduction
Close #11947 

From my understanding this `singleQuote` manipulation is legacy due to
front preparing sql queries in some way.

We might wanna standardize this behavior for each `CURRENCIES` usage,
such as within the `ObjectFilterDropdownCurrencySelect` that does not
send nor expect singled quoted encapsulated currencies values.

Created core-team-issues tech issue
https://github.com/twentyhq/core-team-issues/issues/941


## Field creation variables
```json
{
  "input": {
    "field": {
      "defaultValue": {
        "amountMicros": null,
        "currencyCode": "'BYN'"
      },
      "description": "",
      "icon": "IconMoneybag",
      "label": "Maquito",
      "name": "maquito",
      "isLabelSyncedWithName": true,
      "objectMetadataId": "6ef5da20-cf03-49b5-81fe-73aa2c9856a9",
      "type": "CURRENCY"
    }
  }
}
```

## Filtering variables
```json
{
  "filter": {
    "test": {
      "currencyCode": {
        "in": [
          "ALL",
          "AED",
          "AFN",
          "ANG"
        ]
      }
    }
  },
  "orderBy": [
    {
      "position": "AscNullsFirst"
    }
  ]
}
```
2025-05-09 11:10:24 +02:00
Etienne fe371eb4c4 fix on field relation preview (#11926)
To reproduce : 
Modify object destination to an object with a 'simple' field as
labelIdentifier from data model settings, during creation of a relation
field from an object which has a composite field as labelIdentifier +
Relation type 'has many'

Fix : 
fieldValue and fieldDefinition does not update at the same time (when
object destination is modified) in RelationToManyFieldDisplay component.
Need to better define recordId to be sure fieldValue doesn't point on
previous record.

Tested : 
Relation field creation from Person with switch on relationType and
objectDestination (company/workspaceMember)
Relation field creation from Company with switch on relationType and
objectDestination (pet/person)
closes https://github.com/twentyhq/twenty/issues/11826
2025-05-07 17:10:41 +02:00
Guillim 1db92ce662 Critical email sync bug (#11885)
Removing the upsert to avoid the ON CONFLICT
2025-05-06 11:22:09 +02:00
prastoin 6bb0212a70 fix(server): upgrade command 2025-05-05 16:48:10 +02:00
Etienne 4a543a0e0a fix relation creation bug (#11848)
Context : In `RelationToOneFieldDisplay`, the `objectNameSingular` used
for the chip calculation was incorrect - it was using the value from the
opposite side of the relationship. Then, labelIdentifier based on
composite fields were causing errors (People and WorkspaceMembers with
name)


<img width="300" alt="Screenshot 2025-05-03 at 08 03 46"
src="https://github.com/user-attachments/assets/8d034700-5244-4b1b-978e-f77ae78b6ceb"
/>


Tested everywhere FieldDisplay is used. 
Tested for each both relation type. 

closes https://github.com/twentyhq/twenty/issues/11826
2025-05-03 08:57:53 +02:00
13 changed files with 232 additions and 96 deletions
@@ -13,7 +13,8 @@ import { isDefined } from 'twenty-shared/utils';
import { pascalCase } from '~/utils/string/pascalCase';
export const RelationFromManyFieldDisplay = () => {
const { fieldValue, fieldDefinition } = useRelationFromManyFieldDisplay();
const { fieldValue, fieldDefinition, generateRecordChipData } =
useRelationFromManyFieldDisplay();
const { isFocused } = useFieldFocus();
const { disableChipClick } = useContext(FieldContext);
@@ -99,14 +100,17 @@ export const RelationFromManyFieldDisplay = () => {
} else {
return (
<ExpandableList isChipCountDisplayed={isFocused}>
{fieldValue.filter(isDefined).map((record) => (
<RecordChip
key={record.id}
objectNameSingular={objectNameSingular}
record={record}
forceDisableClick={disableChipClick}
/>
))}
{fieldValue.filter(isDefined).map((record) => {
const recordChipData = generateRecordChipData(record);
return (
<RecordChip
key={recordChipData.recordId}
objectNameSingular={recordChipData.objectNameSingular}
record={record}
forceDisableClick={disableChipClick}
/>
);
})}
</ExpandableList>
);
}
@@ -60,9 +60,8 @@ export const useRelationFromManyFieldDisplay = () => {
: (record: ObjectRecord) =>
generateDefaultRecordChipData({
record,
// @ts-expect-error Above assertions does not infer that fieldDefinition.metadata.objectMetadataNameSingular always defined
objectNameSingular:
fieldDefinition.metadata.objectMetadataNameSingular,
fieldDefinition.metadata.relationObjectMetadataNameSingular,
});
return {
@@ -60,9 +60,8 @@ export const useRelationToOneFieldDisplay = () => {
: (record: ObjectRecord) =>
generateDefaultRecordChipData({
record,
// @ts-expect-error Above assertions does not infer that fieldDefinition.metadata.objectMetadataNameSingular always defined
objectNameSingular:
fieldDefinition.metadata.objectMetadataNameSingular,
fieldDefinition.metadata.relationObjectMetadataNameSingular,
});
return {
@@ -9,6 +9,7 @@ import { useCurrencySettingsFormInitialValues } from '@/settings/data-model/fiel
import { Select } from '@/ui/input/components/Select';
import { useLingui } from '@lingui/react/macro';
import { IconCurrencyDollar } from 'twenty-ui/display';
import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString';
export const settingsDataModelFieldCurrencyFormSchema = z.object({
defaultValue: currencyFieldDefaultValueSchema,
@@ -58,7 +59,10 @@ export const SettingsDataModelFieldCurrencyForm = ({
onChange={onChange}
disabled={disabled}
dropdownId="object-field-default-value-select-currency"
options={CURRENCIES}
options={CURRENCIES.map(({ value, ...rest }) => ({
...rest,
value: applySimpleQuotesToString(value),
}))}
selectSizeVariant="small"
withSearchInput={true}
/>
@@ -102,7 +102,7 @@ export const SettingsDataModelFieldPreview = ({
fieldMetadataItem.name || `${fieldMetadataItem.type}-new-field`;
const recordId =
previewRecord?.id ??
`${objectMetadataItem.nameSingular}-${fieldName}-${fieldMetadataItem.relationDefinition?.direction}-preview`;
`${objectMetadataItem.nameSingular}-${fieldName}-${fieldMetadataItem.relationDefinition?.direction}-${relationObjectMetadataItem?.nameSingular}-preview`;
return (
<>
@@ -0,0 +1,77 @@
import { InjectRepository } from '@nestjs/typeorm';
import chalk from 'chalk';
import { Command } from 'nest-commander';
import { FieldMetadataType } from 'twenty-shared/types';
import { In, Like, Repository } from 'typeorm';
import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
RunOnWorkspaceArgs,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@Command({
name: 'upgrade:0-53:remove-relation-foreign-key-field-metadata',
description: 'Remove relation foreign key from field metadata',
})
export class RemoveRelationForeignKeyFieldMetadataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
@InjectRepository(FieldMetadataEntity, 'metadata')
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {
super(workspaceRepository, twentyORMGlobalManager);
}
override async runOnWorkspace({
index,
total,
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
this.logger.log(
`Running command for workspace ${workspaceId} ${index + 1}/${total}`,
);
const fieldMetadataCollection = await this.fieldMetadataRepository.find({
where: {
workspaceId,
type: In([FieldMetadataType.UUID]),
label: Like('%(foreign key)%'),
},
});
if (!fieldMetadataCollection.length) {
this.logger.log(
chalk.yellow(
`No relation field metadata found for workspace ${workspaceId}.`,
),
);
return;
}
if (options.dryRun) {
this.logger.log(
chalk.yellow(
`Dry run, would delete ${fieldMetadataCollection.length} relation field metadata for workspace ${workspaceId}.`,
),
);
} else {
await this.fieldMetadataRepository.delete({
id: In(
fieldMetadataCollection.map((fieldMetadata) => fieldMetadata.id),
),
});
}
this.logger.log(
chalk.green(`Command completed for workspace ${workspaceId}.`),
);
}
}
@@ -1,16 +1,28 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { BackfillWorkflowNextStepIdsCommand } from 'src/database/commands/upgrade-version-command/0-52/0-52-backfill-workflow-next-step-ids.command';
import { MigrateWorkflowEventListenersToAutomatedTriggersCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-migrate-workflow-event-listeners-to-automated-triggers.command';
import { RemoveRelationForeignKeyFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-remove-relation-foreign-key-field-metadata.command';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
@Module({
imports: [
TypeOrmModule.forFeature([Workspace], 'core'),
TypeOrmModule.forFeature([FieldMetadataEntity], 'metadata'),
WorkspaceDataSourceModule,
],
providers: [MigrateWorkflowEventListenersToAutomatedTriggersCommand],
exports: [MigrateWorkflowEventListenersToAutomatedTriggersCommand],
providers: [
MigrateWorkflowEventListenersToAutomatedTriggersCommand,
BackfillWorkflowNextStepIdsCommand,
RemoveRelationForeignKeyFieldMetadataCommand,
],
exports: [
MigrateWorkflowEventListenersToAutomatedTriggersCommand,
RemoveRelationForeignKeyFieldMetadataCommand,
BackfillWorkflowNextStepIdsCommand,
],
})
export class V0_53_UpgradeVersionCommandModule {}
@@ -19,11 +19,11 @@ import { UpdateViewAggregateOperationsCommand } from 'src/database/commands/upgr
import { UpgradeCreatedByEnumCommand } from 'src/database/commands/upgrade-version-command/0-51/0-51-update-workflow-trigger-type-enum.command';
import { MigrateRelationsToFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/0-52/0-52-migrate-relations-to-field-metadata.command';
import { UpgradeDateAndDateTimeFieldsSettingsJsonCommand } from 'src/database/commands/upgrade-version-command/0-52/0-52-upgrade-settings-field';
import { MigrateWorkflowEventListenersToAutomatedTriggersCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-migrate-workflow-event-listeners-to-automated-triggers.command';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
import { MigrateWorkflowEventListenersToAutomatedTriggersCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-migrate-workflow-event-listeners-to-automated-triggers.command';
type VersionCommands = {
beforeSyncMetadata: ActiveOrSuspendedWorkspacesMigrationCommandRunner[];
@@ -34,7 +34,7 @@ type VersionCommands = {
description: 'Upgrade workspaces to the latest version',
})
export class UpgradeCommand extends UpgradeCommandRunner {
fromWorkspaceVersion = new SemVer('0.50.0');
fromWorkspaceVersion = new SemVer('0.51.0');
private commands: VersionCommands;
constructor(
@@ -117,7 +117,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
],
};
this.commands = commands_053;
this.commands = _commands_052;
}
override async runBeforeSyncMetadata(args: RunOnWorkspaceArgs) {
@@ -35,7 +35,7 @@ export async function determineSchemaRelationDetails(
objectMetadataMaps.byId[fieldMetadata.relationTargetObjectMetadataId];
if (!sourceObjectMetadata || !targetObjectMetadata) {
throw new Error('Object metadata not found');
throw new Error(`Object metadata not found for field ${fieldMetadata.id}`);
}
if (!fieldMetadata.relationTargetFieldMetadataId) {
@@ -18,7 +18,6 @@ import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
import { FieldMetadataUpdate } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-field.factory';
import { camelCase } from 'src/utils/camel-case';
@Injectable()
export class WorkspaceMigrationFieldRelationFactory {
constructor(
@@ -101,6 +100,24 @@ export class WorkspaceMigrationFieldRelationFactory {
);
}
if (!sourceFieldMetadata.settings) {
throw new Error(
`FieldMetadata for relation with id ${sourceFieldMetadata.id} has no settings`,
);
}
if (
sourceFieldMetadata.settings.relationType !== RelationType.MANY_TO_ONE
) {
continue;
}
if (!sourceFieldMetadata.settings.joinColumnName) {
throw new Error(
`FieldMetadata with id ${sourceFieldMetadata.id} has no join column name in settings`,
);
}
if (!targetObjectMetadata) {
throw new Error(
`ObjectMetadata with id ${sourceFieldMetadata.relationTargetObjectMetadataId} not found`,
@@ -137,29 +154,27 @@ export class WorkspaceMigrationFieldRelationFactory {
const migrations: WorkspaceMigrationTableAction[] = [
{
name: computeObjectTargetTable(targetObjectMetadata),
name: computeObjectTargetTable(sourceObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
{
action: WorkspaceMigrationColumnActionType.DROP_FOREIGN_KEY,
columnName: `${camelCase(targetFieldMetadata.name)}Id`,
columnName: sourceFieldMetadata.settings.joinColumnName,
},
],
},
{
name: computeObjectTargetTable(targetObjectMetadata),
name: computeObjectTargetTable(sourceObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY,
columnName: `${camelCase(targetFieldMetadata.name)}Id`,
columnName: sourceFieldMetadata.settings.joinColumnName,
referencedTableName:
computeObjectTargetTable(sourceObjectMetadata),
computeObjectTargetTable(targetObjectMetadata),
referencedTableColumnName: 'id',
isUnique:
targetFieldMetadata.settings.relationType ===
RelationType.ONE_TO_ONE,
onDelete: targetFieldMetadata.settings.onDelete,
isUnique: false,
onDelete: sourceFieldMetadata.settings.onDelete,
},
],
},
@@ -192,22 +207,27 @@ export class WorkspaceMigrationFieldRelationFactory {
sourceFieldMetadata.relationTargetObjectMetadataId
];
if (!sourceObjectMetadata) {
throw new Error(
`ObjectMetadata with id ${sourceFieldMetadata.objectMetadataId} not found`,
);
}
if (!sourceFieldMetadata.settings) {
throw new Error(
`FieldMetadata for relation with id ${sourceFieldMetadata.id} has no settings`,
);
}
// We're creating it from `ONE_TO_MANY` with the join column so we don't need to create a migration for `MANY_TO_ONE`
if (
sourceFieldMetadata.settings.relationType === RelationType.MANY_TO_ONE
sourceFieldMetadata.settings.relationType !== RelationType.MANY_TO_ONE
) {
continue;
}
if (!sourceObjectMetadata) {
if (!sourceFieldMetadata.settings.joinColumnName) {
throw new Error(
`ObjectMetadata with id ${sourceFieldMetadata.objectMetadataId} not found`,
`FieldMetadata with id ${sourceFieldMetadata.id} has no join column name in settings`,
);
}
@@ -239,43 +259,29 @@ export class WorkspaceMigrationFieldRelationFactory {
);
}
if (!targetFieldMetadata.settings) {
throw new Error(
`FieldMetadata for relation with id ${sourceFieldMetadata.id} has no settings`,
);
}
if (!targetFieldMetadata.settings.joinColumnName) {
continue;
}
const migrations: WorkspaceMigrationTableAction[] = [
{
name: computeObjectTargetTable(targetObjectMetadata),
name: computeObjectTargetTable(sourceObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
...this.workspaceMigrationFactory.createColumnActions(
WorkspaceMigrationColumnActionType.CREATE,
targetFieldMetadata,
sourceFieldMetadata,
),
],
},
{
name: computeObjectTargetTable(targetObjectMetadata),
name: computeObjectTargetTable(sourceObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY,
columnName:
targetFieldMetadata.settings.joinColumnName ??
`${camelCase(targetFieldMetadata.name)}Id`,
columnName: sourceFieldMetadata.settings.joinColumnName,
referencedTableName:
computeObjectTargetTable(sourceObjectMetadata),
computeObjectTargetTable(targetObjectMetadata),
referencedTableColumnName: 'id',
isUnique:
targetFieldMetadata.settings.relationType ===
RelationType.ONE_TO_ONE,
onDelete: targetFieldMetadata.settings.onDelete,
isUnique: false,
onDelete: sourceFieldMetadata.settings.onDelete,
},
],
},
@@ -314,6 +320,19 @@ export class WorkspaceMigrationFieldRelationFactory {
);
}
if (!sourceFieldMetadata.settings) {
throw new Error(
`FieldMetadata for relation with id ${sourceFieldMetadata.id} has no settings`,
);
}
if (
sourceFieldMetadata.settings.relationType !== RelationType.MANY_TO_ONE
) {
// Only MANY_TO_ONE relations deletion have consequences on the database schema
continue;
}
if (!targetObjectMetadata) {
throw new Error(
`ObjectMetadata with id ${sourceFieldMetadata.relationTargetObjectMetadataId} not found`,
@@ -342,32 +361,30 @@ export class WorkspaceMigrationFieldRelationFactory {
);
}
if (!targetFieldMetadata.settings) {
if (!sourceFieldMetadata.settings.joinColumnName) {
throw new Error(
`FieldMetadata for relation with id ${sourceFieldMetadata.id} has no settings`,
`FieldMetadata for relation with id ${sourceFieldMetadata.id} has no join column name, it should not generate a workspace migration`,
);
}
const migrations: WorkspaceMigrationTableAction[] = [
{
name: computeObjectTargetTable(targetObjectMetadata),
name: computeObjectTargetTable(sourceObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
{
action: WorkspaceMigrationColumnActionType.DROP_FOREIGN_KEY,
columnName: `${camelCase(targetFieldMetadata.name)}Id`,
columnName: sourceFieldMetadata.settings.joinColumnName,
},
],
},
{
name: computeObjectTargetTable(targetObjectMetadata),
name: computeObjectTargetTable(sourceObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
{
action: WorkspaceMigrationColumnActionType.DROP,
columnName:
targetFieldMetadata.settings.joinColumnName ??
`${camelCase(targetFieldMetadata.name)}Id`,
columnName: sourceFieldMetadata.settings.joinColumnName,
},
],
},
@@ -130,10 +130,6 @@ export class WorkspaceFieldRelationComparator {
throw new Error(`Field ${fieldId} not found in originalObjectMetadata`);
}
if (!standardFieldMetadata) {
throw new Error(`Field ${fieldId} not found in standardObjectMetadata`);
}
for (const difference of differences) {
const property = difference.path[difference.path.length - 1];
@@ -175,11 +171,11 @@ export class WorkspaceFieldRelationComparator {
(fieldPropertiesToStringify as readonly string[]).includes(property)
) {
const newValue = this.parseJSONOrString(difference.value);
const oldValue = this.parseJSONOrString(difference.oldValue);
if (property === 'settings' && difference.oldValue) {
const newSettings = newValue as FieldMetadataRelationSettings;
const oldSettings =
difference.oldValue as FieldMetadataRelationSettings;
const oldSettings = oldValue as FieldMetadataRelationSettings;
// Check if the relation type has changed
if (oldSettings.relationType !== newSettings.relationType) {
@@ -193,6 +189,10 @@ export class WorkspaceFieldRelationComparator {
}
}
if (!standardFieldMetadata) {
throw new Error(`Field ${fieldId} not found in standardObjectMetadata`);
}
if (relationTypeChange) {
result.push({
action: ComparatorAction.DELETE,
@@ -221,8 +221,6 @@ export class WorkspaceMetadataUpdaterService {
[];
let updatedFieldRelationMetadataCollection: FieldMetadataUpdate<FieldMetadataType.RELATION>[] =
[];
let deletedFieldRelationMetadataCollection: FieldMetadataUpdate<FieldMetadataType.RELATION>[] =
[];
/**
* Create field relation metadata
@@ -253,20 +251,26 @@ export class WorkspaceMetadataUpdaterService {
}
if (!options || options.actions.includes('delete')) {
deletedFieldRelationMetadataCollection = await this.updateEntities<
FieldMetadataEntity<FieldMetadataType.RELATION>
>(
manager,
FieldMetadataEntity,
storage.fieldRelationMetadataDeleteCollection,
['objectMetadataId', 'workspaceId'],
);
const fieldMetadataRepository =
manager.getRepository(FieldMetadataEntity);
if (storage.fieldRelationMetadataDeleteCollection.length > 0) {
await fieldMetadataRepository.delete(
storage.fieldRelationMetadataDeleteCollection.map(
(field) => field.id,
),
);
}
}
return {
createdFieldRelationMetadataCollection,
updatedFieldRelationMetadataCollection,
deletedFieldRelationMetadataCollection,
deletedFieldRelationMetadataCollection:
storage.fieldRelationMetadataDeleteCollection.map((field) => ({
current: field,
altered: field,
})),
};
}
@@ -62,19 +62,39 @@ export class MessagingMessageService {
});
if (existingMessage) {
await messageChannelMessageAssociationRepository.upsert(
{
messageChannelId,
messageId: existingMessage.id,
messageExternalId: message.externalId,
messageThreadExternalId: message.messageThreadExternalId,
},
{
conflictPaths: ['messageChannelId', 'messageId'],
indexPredicate: '"deletedAt" IS NULL',
},
transactionManager,
);
const existingAssociation =
await messageChannelMessageAssociationRepository.findOne(
{
where: {
messageChannelId,
messageId: existingMessage.id,
},
},
transactionManager,
);
if (existingAssociation) {
await messageChannelMessageAssociationRepository.update(
{
id: existingAssociation.id,
},
{
messageExternalId: message.externalId,
messageThreadExternalId: message.messageThreadExternalId,
},
transactionManager,
);
} else {
await messageChannelMessageAssociationRepository.insert(
{
messageChannelId,
messageId: existingMessage.id,
messageExternalId: message.externalId,
messageThreadExternalId: message.messageThreadExternalId,
},
transactionManager,
);
}
continue;
}