f9ab09c404
# Introduction Cleaner and fewer scope version of https://github.com/twentyhq/twenty/pull/15745 ( removed sync-metadata hack through, too ambitious migration and upgrade ) Please note that this PR won't have any interaction with the existing sync-metadata Which mean that the sync metadata does not update the standard entities applicationId and universalIdentifier, and it won't we will deprecate it on favor of a workspace migration aka twenty-standard app installation ## API Metadata Any operation going through the api metadata nows automatically scope the related entity to the workspace custom application instance. ( optionally passing an applicationId to allow current hacky implem of app sync service ) We need to either ignore the tests or remove the cli status check from the blocking status badges for a PR to be merged ## New workspace Already handled in previous https://github.com/twentyhq/twenty/pull/15625, when a workspace is created it gets created a twenty standard and custom workspace instance All his views and permissions will be prefilled to the its twenty standard app instance with a specific universalIdentifier ## New universalIdentifier At the contrary as before with standardIds, universalIdentifier are unique for a given workspace This means that createdAt field of both object company and opportunity will have a unique universalIdentifier whereas they share the same standardId ## FlatApplication Introduced the flatApplication and cache. Will migrate existing `MetadataName` to be `SyncableMetadataName` in a following PR ## What's next Next we will describe a twenty standard app configuration as json that will be used to generate a workspace migration that will be run instead of the sync metadata, in a nutshell we aim to deprecated the sync metadata So we can standardize any entity to have a non nullable applicationId and universalIdentifier ## Upgrade command Introduced an upgrade command that will create a custom workspace instance for any workspace that do not have one in order to align with the new behavior when creating a new workspace
114 lines
4.7 KiB
TypeScript
114 lines
4.7 KiB
TypeScript
import { msg } from '@lingui/core/macro';
|
|
import { type FieldMetadataType } from 'twenty-shared/types';
|
|
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
|
import { v4 } from 'uuid';
|
|
|
|
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
|
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
|
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
|
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
|
import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
|
|
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
|
import { generateMorphOrRelationFlatFieldMetadataPair } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util';
|
|
import { validateMorphRelationCreationPayload } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-relation-creation-payload.util';
|
|
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
|
|
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
|
|
|
type FromMorphRelationCreateFieldInputToFlatFieldMetadatasArgs = {
|
|
createFieldInput: Omit<CreateFieldInput, 'workspaceId'> & {
|
|
type: FieldMetadataType.MORPH_RELATION;
|
|
};
|
|
existingFlatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
|
sourceFlatObjectMetadata: FlatObjectMetadata;
|
|
workspaceId: string;
|
|
workspaceCustomApplicationId: string;
|
|
};
|
|
export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({
|
|
createFieldInput,
|
|
existingFlatObjectMetadataMaps,
|
|
sourceFlatObjectMetadata,
|
|
workspaceId,
|
|
workspaceCustomApplicationId,
|
|
}: FromMorphRelationCreateFieldInputToFlatFieldMetadatasArgs): Promise<
|
|
FieldInputTranspilationResult<{
|
|
flatFieldMetadatas: FlatFieldMetadata[];
|
|
indexMetadatas: FlatIndexMetadata[];
|
|
}>
|
|
> => {
|
|
const rawMorphCreationPayload =
|
|
createFieldInput.morphRelationsCreationPayload;
|
|
|
|
if (
|
|
!isDefined(rawMorphCreationPayload) ||
|
|
!Array.isArray(rawMorphCreationPayload)
|
|
) {
|
|
return {
|
|
status: 'fail',
|
|
error: {
|
|
code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
|
message: `Relation creation payload is required`,
|
|
userFriendlyMessage: msg`Relation creation payload is required`,
|
|
value: rawMorphCreationPayload,
|
|
},
|
|
};
|
|
}
|
|
|
|
const morphRelationCreationPayloadValidation =
|
|
await validateMorphRelationCreationPayload({
|
|
existingFlatObjectMetadataMaps,
|
|
morphRelationCreationPayload: rawMorphCreationPayload,
|
|
objectMetadataId: sourceFlatObjectMetadata.id,
|
|
});
|
|
|
|
if (morphRelationCreationPayloadValidation.status === 'fail') {
|
|
return morphRelationCreationPayloadValidation;
|
|
}
|
|
|
|
const morphRelationCreationPayload =
|
|
morphRelationCreationPayloadValidation.result;
|
|
const morphId = v4();
|
|
const flatFieldsAndIndexes = morphRelationCreationPayload.reduce(
|
|
(acc, { relationCreationPayload, targetFlatObjectMetadata }) => {
|
|
const currentMorphRelationFieldName = computeMorphRelationFieldName({
|
|
fieldName: createFieldInput.name,
|
|
relationType: relationCreationPayload.type,
|
|
targetObjectMetadataNameSingular: targetFlatObjectMetadata.nameSingular,
|
|
targetObjectMetadataNamePlural: targetFlatObjectMetadata.namePlural,
|
|
});
|
|
const sourceFlatObjectMetadataJoinColumnName =
|
|
computeMorphOrRelationFieldJoinColumnName({
|
|
name: currentMorphRelationFieldName,
|
|
});
|
|
|
|
const { flatFieldMetadatas, indexMetadatas } =
|
|
generateMorphOrRelationFlatFieldMetadataPair({
|
|
createFieldInput: {
|
|
...createFieldInput,
|
|
relationCreationPayload,
|
|
name: currentMorphRelationFieldName,
|
|
},
|
|
sourceFlatObjectMetadataJoinColumnName,
|
|
sourceFlatObjectMetadata,
|
|
targetFlatObjectMetadata,
|
|
workspaceId,
|
|
morphId,
|
|
workspaceCustomApplicationId,
|
|
});
|
|
|
|
return {
|
|
indexMetadatas: [...acc.indexMetadatas, ...indexMetadatas],
|
|
flatFieldMetadatas: [...acc.flatFieldMetadatas, ...flatFieldMetadatas],
|
|
};
|
|
},
|
|
{
|
|
indexMetadatas: [],
|
|
flatFieldMetadatas: [],
|
|
},
|
|
);
|
|
|
|
return {
|
|
status: 'success',
|
|
result: flatFieldsAndIndexes,
|
|
};
|
|
};
|