Handle relations separately for remotes (#5538)
Remote object id columns are not removed anymore when a remote object is unsynced. This is because we do not use relations anymore. We only created the id field. So the current behavior that was implemented for custom objects, to retrieve the fields to deleted, does not work. Since remote object relations are really different, I extracted the logic from `objectMetadataService`. It now handles only the relations for custom objects creation and deletion (this part should be extracted as well). I create a new remote table relation service that will: - fetch objects metadata linked to remotes (favorites, activityTargets...) - look for columns based on remote object name - delete the fields and columns
This commit is contained in:
+20
@@ -0,0 +1,20 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { RemoteTableRelationsService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service';
|
||||
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature(
|
||||
[ObjectMetadataEntity, FieldMetadataEntity],
|
||||
'metadata',
|
||||
),
|
||||
WorkspaceMigrationModule,
|
||||
],
|
||||
providers: [RemoteTableRelationsService],
|
||||
exports: [RemoteTableRelationsService],
|
||||
})
|
||||
export class RemoteTableRelationsModule {}
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { In, Repository } from 'typeorm';
|
||||
|
||||
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
|
||||
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
|
||||
import {
|
||||
FieldMetadataEntity,
|
||||
FieldMetadataType,
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { createForeignKeyDeterministicUuid } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
|
||||
import {
|
||||
ACTIVITY_TARGET_STANDARD_FIELD_IDS,
|
||||
ATTACHMENT_STANDARD_FIELD_IDS,
|
||||
FAVORITE_STANDARD_FIELD_IDS,
|
||||
TIMELINE_ACTIVITY_STANDARD_FIELD_IDS,
|
||||
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { buildMigrationsToCreateRemoteTableRelations } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/utils/build-migrations-to-create-remote-table-relations.util';
|
||||
import { buildMigrationsToRemoveRemoteTableRelations } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/utils/build-migrations-to-remove-remote-table-relations.util';
|
||||
import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util';
|
||||
import { createRelationForeignKeyFieldMetadataName } from 'src/engine/metadata-modules/relation-metadata/utils/create-relation-foreign-key-field-metadata-name.util';
|
||||
|
||||
@Injectable()
|
||||
export class RemoteTableRelationsService {
|
||||
constructor(
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
|
||||
@InjectRepository(FieldMetadataEntity, 'metadata')
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
private readonly workspaceMigrationService: WorkspaceMigrationService,
|
||||
) {}
|
||||
|
||||
public async createForeignKeysMetadataAndMigrations(
|
||||
workspaceId: string,
|
||||
remoteObjectMetadata: ObjectMetadataEntity,
|
||||
objectPrimaryKeyFieldSettings:
|
||||
| FieldMetadataSettings<FieldMetadataType | 'default'>
|
||||
| undefined,
|
||||
objectPrimaryKeyColumnType?: string,
|
||||
) {
|
||||
const objectPrimaryKeyFieldType = mapUdtNameToFieldType(
|
||||
objectPrimaryKeyColumnType ?? 'uuid',
|
||||
);
|
||||
|
||||
const favoriteObjectMetadata = await this.createFavoriteRelation(
|
||||
workspaceId,
|
||||
remoteObjectMetadata,
|
||||
objectPrimaryKeyFieldType,
|
||||
objectPrimaryKeyFieldSettings,
|
||||
);
|
||||
|
||||
const activityTargetObjectMetadata =
|
||||
await this.createActivityTargetRelation(
|
||||
workspaceId,
|
||||
remoteObjectMetadata,
|
||||
objectPrimaryKeyFieldType,
|
||||
objectPrimaryKeyFieldSettings,
|
||||
);
|
||||
|
||||
const attachmentObjectMetadata = await this.createAttachmentRelation(
|
||||
workspaceId,
|
||||
remoteObjectMetadata,
|
||||
objectPrimaryKeyFieldType,
|
||||
objectPrimaryKeyFieldSettings,
|
||||
);
|
||||
|
||||
const timelineActivityObjectMetadata =
|
||||
await this.createTimelineActivityRelation(
|
||||
workspaceId,
|
||||
remoteObjectMetadata,
|
||||
objectPrimaryKeyFieldType,
|
||||
objectPrimaryKeyFieldSettings,
|
||||
);
|
||||
|
||||
// create migration to add foreign key columns
|
||||
await this.workspaceMigrationService.createCustomMigration(
|
||||
generateMigrationName(
|
||||
`add-foreign-keys-${remoteObjectMetadata.nameSingular}`,
|
||||
),
|
||||
workspaceId,
|
||||
buildMigrationsToCreateRemoteTableRelations(
|
||||
remoteObjectMetadata.nameSingular,
|
||||
[
|
||||
favoriteObjectMetadata,
|
||||
activityTargetObjectMetadata,
|
||||
attachmentObjectMetadata,
|
||||
timelineActivityObjectMetadata,
|
||||
],
|
||||
objectPrimaryKeyColumnType ?? 'uuid',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public async deleteForeignKeysMetadataAndCreateMigrations(
|
||||
workspaceId: string,
|
||||
remoteObjectMetadata: ObjectMetadataEntity,
|
||||
) {
|
||||
// find favorite, activityTarget, attachment, timelineActivity objects
|
||||
const favoriteObjectMetadata =
|
||||
await this.objectMetadataRepository.findOneByOrFail({
|
||||
nameSingular: 'favorite',
|
||||
workspaceId: workspaceId,
|
||||
});
|
||||
|
||||
const activityTargetObjectMetadata =
|
||||
await this.objectMetadataRepository.findOneByOrFail({
|
||||
nameSingular: 'activityTarget',
|
||||
workspaceId: workspaceId,
|
||||
});
|
||||
|
||||
const attachmentObjectMetadata =
|
||||
await this.objectMetadataRepository.findOneByOrFail({
|
||||
nameSingular: 'attachment',
|
||||
workspaceId: workspaceId,
|
||||
});
|
||||
|
||||
const timelineActivityObjectMetadata =
|
||||
await this.objectMetadataRepository.findOneByOrFail({
|
||||
nameSingular: 'timelineActivity',
|
||||
workspaceId: workspaceId,
|
||||
});
|
||||
|
||||
// compute the target column name
|
||||
const targetColumnName = createRelationForeignKeyFieldMetadataName(
|
||||
remoteObjectMetadata.nameSingular,
|
||||
);
|
||||
|
||||
// find the foreign key fields to delete
|
||||
const foreignKeyFieldsToDelete = await this.fieldMetadataRepository.find({
|
||||
where: {
|
||||
name: targetColumnName,
|
||||
objectMetadataId: In([
|
||||
favoriteObjectMetadata.id,
|
||||
activityTargetObjectMetadata.id,
|
||||
attachmentObjectMetadata.id,
|
||||
timelineActivityObjectMetadata.id,
|
||||
]),
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
const foreignKeyFieldsToDeleteIds = foreignKeyFieldsToDelete.map(
|
||||
(field) => field.id,
|
||||
);
|
||||
|
||||
await this.fieldMetadataRepository.delete(foreignKeyFieldsToDeleteIds);
|
||||
|
||||
// create migration to drop foreign key columns
|
||||
await this.workspaceMigrationService.createCustomMigration(
|
||||
generateMigrationName(
|
||||
`delete-foreign-keys-${remoteObjectMetadata.nameSingular}`,
|
||||
),
|
||||
workspaceId,
|
||||
buildMigrationsToRemoveRemoteTableRelations(targetColumnName, [
|
||||
favoriteObjectMetadata,
|
||||
activityTargetObjectMetadata,
|
||||
attachmentObjectMetadata,
|
||||
timelineActivityObjectMetadata,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
private async createActivityTargetRelation(
|
||||
workspaceId: string,
|
||||
createdObjectMetadata: ObjectMetadataEntity,
|
||||
objectPrimaryKeyType: FieldMetadataType,
|
||||
objectPrimaryKeyFieldSettings:
|
||||
| FieldMetadataSettings<FieldMetadataType | 'default'>
|
||||
| undefined,
|
||||
) {
|
||||
const activityTargetObjectMetadata =
|
||||
await this.objectMetadataRepository.findOneByOrFail({
|
||||
nameSingular: 'activityTarget',
|
||||
workspaceId: workspaceId,
|
||||
});
|
||||
|
||||
await this.fieldMetadataRepository.save(
|
||||
// Foreign key
|
||||
{
|
||||
standardId: createForeignKeyDeterministicUuid({
|
||||
objectId: createdObjectMetadata.id,
|
||||
standardId: ACTIVITY_TARGET_STANDARD_FIELD_IDS.custom,
|
||||
}),
|
||||
objectMetadataId: activityTargetObjectMetadata.id,
|
||||
workspaceId: workspaceId,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
type: objectPrimaryKeyType,
|
||||
name: `${createdObjectMetadata.nameSingular}Id`,
|
||||
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
|
||||
description: `ActivityTarget ${createdObjectMetadata.labelSingular} id foreign key`,
|
||||
icon: undefined,
|
||||
isNullable: true,
|
||||
isSystem: true,
|
||||
defaultValue: undefined,
|
||||
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
|
||||
},
|
||||
);
|
||||
|
||||
return activityTargetObjectMetadata;
|
||||
}
|
||||
|
||||
private async createAttachmentRelation(
|
||||
workspaceId: string,
|
||||
createdObjectMetadata: ObjectMetadataEntity,
|
||||
objectPrimaryKeyType: FieldMetadataType,
|
||||
objectPrimaryKeyFieldSettings:
|
||||
| FieldMetadataSettings<FieldMetadataType | 'default'>
|
||||
| undefined,
|
||||
) {
|
||||
const attachmentObjectMetadata =
|
||||
await this.objectMetadataRepository.findOneByOrFail({
|
||||
nameSingular: 'attachment',
|
||||
workspaceId: workspaceId,
|
||||
});
|
||||
|
||||
await this.fieldMetadataRepository.save(
|
||||
// Foreign key
|
||||
{
|
||||
standardId: createForeignKeyDeterministicUuid({
|
||||
objectId: createdObjectMetadata.id,
|
||||
standardId: ATTACHMENT_STANDARD_FIELD_IDS.custom,
|
||||
}),
|
||||
objectMetadataId: attachmentObjectMetadata.id,
|
||||
workspaceId: workspaceId,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
type: objectPrimaryKeyType,
|
||||
name: `${createdObjectMetadata.nameSingular}Id`,
|
||||
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
|
||||
description: `Attachment ${createdObjectMetadata.labelSingular} id foreign key`,
|
||||
icon: undefined,
|
||||
isNullable: true,
|
||||
isSystem: true,
|
||||
defaultValue: undefined,
|
||||
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
|
||||
},
|
||||
);
|
||||
|
||||
return attachmentObjectMetadata;
|
||||
}
|
||||
|
||||
private async createTimelineActivityRelation(
|
||||
workspaceId: string,
|
||||
createdObjectMetadata: ObjectMetadataEntity,
|
||||
objectPrimaryKeyType: FieldMetadataType,
|
||||
objectPrimaryKeyFieldSettings:
|
||||
| FieldMetadataSettings<FieldMetadataType | 'default'>
|
||||
| undefined,
|
||||
) {
|
||||
const timelineActivityObjectMetadata =
|
||||
await this.objectMetadataRepository.findOneByOrFail({
|
||||
nameSingular: 'timelineActivity',
|
||||
workspaceId: workspaceId,
|
||||
});
|
||||
|
||||
await this.fieldMetadataRepository.save(
|
||||
// Foreign key
|
||||
{
|
||||
standardId: createForeignKeyDeterministicUuid({
|
||||
objectId: createdObjectMetadata.id,
|
||||
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.custom,
|
||||
}),
|
||||
objectMetadataId: timelineActivityObjectMetadata.id,
|
||||
workspaceId: workspaceId,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
type: objectPrimaryKeyType,
|
||||
name: `${createdObjectMetadata.nameSingular}Id`,
|
||||
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
|
||||
description: `Timeline Activity ${createdObjectMetadata.labelSingular} id foreign key`,
|
||||
icon: undefined,
|
||||
isNullable: true,
|
||||
isSystem: true,
|
||||
defaultValue: undefined,
|
||||
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
|
||||
},
|
||||
);
|
||||
|
||||
return timelineActivityObjectMetadata;
|
||||
}
|
||||
|
||||
private async createFavoriteRelation(
|
||||
workspaceId: string,
|
||||
createdObjectMetadata: ObjectMetadataEntity,
|
||||
objectPrimaryKeyType: FieldMetadataType,
|
||||
objectPrimaryKeyFieldSettings:
|
||||
| FieldMetadataSettings<FieldMetadataType | 'default'>
|
||||
| undefined,
|
||||
) {
|
||||
const favoriteObjectMetadata =
|
||||
await this.objectMetadataRepository.findOneByOrFail({
|
||||
nameSingular: 'favorite',
|
||||
workspaceId: workspaceId,
|
||||
});
|
||||
|
||||
await this.fieldMetadataRepository.save(
|
||||
// Foreign key
|
||||
{
|
||||
standardId: createForeignKeyDeterministicUuid({
|
||||
objectId: createdObjectMetadata.id,
|
||||
standardId: FAVORITE_STANDARD_FIELD_IDS.custom,
|
||||
}),
|
||||
objectMetadataId: favoriteObjectMetadata.id,
|
||||
workspaceId: workspaceId,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
type: objectPrimaryKeyType,
|
||||
name: `${createdObjectMetadata.nameSingular}Id`,
|
||||
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
|
||||
description: `Favorite ${createdObjectMetadata.labelSingular} id foreign key`,
|
||||
icon: undefined,
|
||||
isNullable: true,
|
||||
isSystem: true,
|
||||
defaultValue: undefined,
|
||||
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
|
||||
},
|
||||
);
|
||||
|
||||
return favoriteObjectMetadata;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import {
|
||||
WorkspaceMigrationTableAction,
|
||||
WorkspaceMigrationColumnActionType,
|
||||
WorkspaceMigrationColumnCreate,
|
||||
WorkspaceMigrationTableActionType,
|
||||
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
|
||||
export const buildMigrationsToCreateRemoteTableRelations = (
|
||||
createdObjectNameSingular: string,
|
||||
targetObjectMetadataList: ObjectMetadataEntity[],
|
||||
primaryKeyColumnType: string,
|
||||
): WorkspaceMigrationTableAction[] =>
|
||||
targetObjectMetadataList.map((targetObjectMetadata) => ({
|
||||
name: computeObjectTargetTable(targetObjectMetadata),
|
||||
action: WorkspaceMigrationTableActionType.ALTER,
|
||||
columns: [
|
||||
{
|
||||
action: WorkspaceMigrationColumnActionType.CREATE,
|
||||
columnName: computeColumnName(createdObjectNameSingular, {
|
||||
isForeignKey: true,
|
||||
}),
|
||||
columnType: primaryKeyColumnType,
|
||||
isNullable: true,
|
||||
} satisfies WorkspaceMigrationColumnCreate,
|
||||
],
|
||||
}));
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import {
|
||||
WorkspaceMigrationTableAction,
|
||||
WorkspaceMigrationTableActionType,
|
||||
WorkspaceMigrationColumnActionType,
|
||||
WorkspaceMigrationColumnDrop,
|
||||
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||
|
||||
export const buildMigrationsToRemoveRemoteTableRelations = (
|
||||
targetColumnName: string,
|
||||
targetObjectMetadataList: ObjectMetadataEntity[],
|
||||
): WorkspaceMigrationTableAction[] =>
|
||||
targetObjectMetadataList.map((objectMetadata) => ({
|
||||
name: computeTableName(
|
||||
objectMetadata.nameSingular,
|
||||
objectMetadata.isCustom,
|
||||
),
|
||||
action: WorkspaceMigrationTableActionType.ALTER,
|
||||
columns: [
|
||||
{
|
||||
action: WorkspaceMigrationColumnActionType.DROP,
|
||||
columnName: targetColumnName,
|
||||
} satisfies WorkspaceMigrationColumnDrop,
|
||||
],
|
||||
}));
|
||||
Reference in New Issue
Block a user