Metadata api create entity in workspace custom app (#15911)

# 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
This commit is contained in:
Paul Rastoin
2025-11-23 22:35:17 +01:00
committed by GitHub
parent 4848bc03f3
commit f9ab09c404
99 changed files with 3458 additions and 730 deletions
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { CronTriggerCronCommand } from 'src/engine/metadata-modules/cron-trigger/crons/commands/cron-trigger.cron.command';
import { CronTriggerCronJob } from 'src/engine/metadata-modules/cron-trigger/crons/jobs/cron-trigger.cron.job';
@@ -15,6 +16,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
@Module({
imports: [
TypeOrmModule.forFeature([WorkspaceEntity, CronTriggerEntity]),
ApplicationModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
PermissionsModule,
WorkspaceMigrationV2Module,
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { CreateCronTriggerInput } from 'src/engine/metadata-modules/cron-trigger/dtos/create-cron-trigger.input';
import { CronTriggerIdInput } from 'src/engine/metadata-modules/cron-trigger/dtos/cron-trigger-id.input';
import { UpdateCronTriggerInput } from 'src/engine/metadata-modules/cron-trigger/dtos/update-cron-trigger.input';
@@ -23,12 +24,25 @@ export class CronTriggerV2Service {
constructor(
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly applicationService: ApplicationService,
) {}
async createOne(
cronTriggerInput: CreateCronTriggerInput,
workspaceId: string,
/**
* @deprecated do not use call validateBuildAndRunWorkspaceMigration contextually
* when interacting with another application than workspace custom one
* */
applicationId?: string,
) {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const flatEntityMaps =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
@@ -43,6 +57,8 @@ export class CronTriggerV2Service {
{
createCronTriggerInput: cronTriggerInput,
workspaceId,
workspaceCustomApplicationId:
applicationId ?? workspaceCustomFlatApplication.id,
},
);
@@ -6,18 +6,21 @@ import { type FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/t
export const fromCreateCronTriggerInputToFlatCronTrigger = ({
createCronTriggerInput,
workspaceId,
workspaceCustomApplicationId,
}: {
createCronTriggerInput: CreateCronTriggerInput;
workspaceId: string;
workspaceCustomApplicationId: string;
}): FlatCronTrigger => {
const now = new Date();
const id = v4();
return {
id: v4(),
universalIdentifier: createCronTriggerInput.universalIdentifier ?? v4(),
id,
universalIdentifier: createCronTriggerInput.universalIdentifier ?? id,
settings: createCronTriggerInput.settings,
serverlessFunctionId: createCronTriggerInput.serverlessFunctionId,
applicationId: createCronTriggerInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
workspaceId,
createdAt: now,
updatedAt: now,
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DatabaseEventTriggerEntity } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import { CallDatabaseEventTriggerJobsJob } from 'src/engine/metadata-modules/database-event-trigger/jobs/call-database-event-trigger-jobs.job';
@@ -14,6 +15,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
@Module({
imports: [
TypeOrmModule.forFeature([WorkspaceEntity, DatabaseEventTriggerEntity]),
ApplicationModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
PermissionsModule,
WorkspaceMigrationV2Module,
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { CreateDatabaseEventTriggerInput } from 'src/engine/metadata-modules/database-event-trigger/dtos/create-database-event-trigger.input';
import { DatabaseEventTriggerIdInput } from 'src/engine/metadata-modules/database-event-trigger/dtos/database-event-trigger-id.input';
import { UpdateDatabaseEventTriggerInput } from 'src/engine/metadata-modules/database-event-trigger/dtos/update-database-event-trigger.input';
@@ -23,12 +24,25 @@ export class DatabaseEventTriggerV2Service {
constructor(
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly applicationService: ApplicationService,
) {}
async createOne(
databaseEventTriggerInput: CreateDatabaseEventTriggerInput,
workspaceId: string,
/**
* @deprecated do not use call validateBuildAndRunWorkspaceMigration contextually
* when interacting with another application than workspace custom one
* */
applicationId?: string,
) {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const flatEntityMaps =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
@@ -47,6 +61,8 @@ export class DatabaseEventTriggerV2Service {
fromCreateDatabaseEventTriggerInputToFlatDatabaseEventTrigger({
createDatabaseEventTriggerInput: databaseEventTriggerInput,
workspaceId,
workspaceCustomApplicationId:
applicationId ?? workspaceCustomFlatApplication.id,
});
const validateAndBuildResult =
@@ -6,21 +6,24 @@ import { type FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/datab
export const fromCreateDatabaseEventTriggerInputToFlatDatabaseEventTrigger = ({
createDatabaseEventTriggerInput,
workspaceId,
workspaceCustomApplicationId,
}: {
createDatabaseEventTriggerInput: CreateDatabaseEventTriggerInput;
workspaceId: string;
workspaceCustomApplicationId: string;
}): FlatDatabaseEventTrigger => {
const now = new Date();
const id = uuidV4();
return {
id: uuidV4(),
id,
universalIdentifier:
createDatabaseEventTriggerInput.universalIdentifier ?? uuidV4(),
createDatabaseEventTriggerInput.universalIdentifier ?? id,
settings: createDatabaseEventTriggerInput.settings,
serverlessFunctionId: createDatabaseEventTriggerInput.serverlessFunctionId,
workspaceId,
createdAt: now,
updatedAt: now,
applicationId: createDatabaseEventTriggerInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
};
};
@@ -9,6 +9,7 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { ActorModule } from 'src/engine/core-modules/actor/actor.module';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
@@ -60,6 +61,7 @@ import { UpdateFieldInput } from './dtos/update-field.input';
DataSourceModule,
TypeORMModule,
ActorModule,
ApplicationModule,
FeatureFlagModule,
ViewModule,
ViewFieldModule,
@@ -5,6 +5,7 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { isDefined } from 'twenty-shared/utils';
import { FindOneOptions, Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { type DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
import { type UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
@@ -32,6 +33,7 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly applicationService: ApplicationService,
) {
super(fieldMetadataRepository);
}
@@ -39,13 +41,20 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
async createOneField({
createFieldInput,
workspaceId,
applicationId,
}: {
createFieldInput: Omit<CreateFieldInput, 'workspaceId'>;
workspaceId: string;
/**
* @deprecated do not use call validateBuildAndRunWorkspaceMigration contextually
* when interacting with another application than workspace custom one
* */
applicationId?: string;
}): Promise<FlatFieldMetadata> {
const [createdFieldMetadata] = await this.createManyFields({
workspaceId,
createFieldInputs: [createFieldInput],
applicationId,
});
if (!isDefined(createdFieldMetadata)) {
@@ -283,14 +292,27 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
async createManyFields({
createFieldInputs,
workspaceId,
applicationId,
}: {
createFieldInputs: Omit<CreateFieldInput, 'workspaceId'>[];
workspaceId: string;
/**
* @deprecated do not use call validateBuildAndRunWorkspaceMigration contextually
* when interacting with another application than workspace custom one
* */
applicationId?: string;
}): Promise<FlatFieldMetadata[]> {
if (createFieldInputs.length === 0) {
return [];
}
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatIndexMaps: existingFlatIndexMaps,
@@ -316,6 +338,8 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
createFieldInput,
workspaceId,
workspaceCustomApplicationId:
applicationId ?? workspaceCustomFlatApplication.id,
}),
);
}
@@ -104,7 +104,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"result": {
"flatFieldMetadatas": [
{
"applicationId": null,
"applicationId": Any<String>,
"calendarViewIds": [],
"createdAt": Any<ClockDate>,
"defaultValue": null,
@@ -140,7 +140,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"workspaceId": Any<String>,
},
{
"applicationId": null,
"applicationId": Any<String>,
"calendarViewIds": [],
"createdAt": Any<ClockDate>,
"defaultValue": null,
@@ -178,7 +178,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"workspaceId": Any<String>,
},
{
"applicationId": null,
"applicationId": Any<String>,
"calendarViewIds": [],
"createdAt": Any<ClockDate>,
"defaultValue": null,
@@ -214,7 +214,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"workspaceId": Any<String>,
},
{
"applicationId": null,
"applicationId": Any<String>,
"calendarViewIds": [],
"createdAt": Any<ClockDate>,
"defaultValue": null,
@@ -254,7 +254,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
],
"indexMetadatas": [
{
"applicationId": null,
"applicationId": Any<String>,
"createdAt": Any<ClockDate>,
"flatIndexFieldMetadatas": [
{
@@ -278,7 +278,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"workspaceId": Any<String>,
},
{
"applicationId": null,
"applicationId": Any<String>,
"createdAt": Any<ClockDate>,
"flatIndexFieldMetadatas": [
{
@@ -42,6 +42,8 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
'should create morph relation field metadata with valid input on rocket object to pet object',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -100,6 +102,8 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
title: 'should fail when morphRelationsCreationPayload is missing',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -118,6 +122,8 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
title: 'should fail when morphRelationsCreationPayload is empty array',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -137,6 +143,8 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
'should fail when morphRelationsCreationPayload has different relation types',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -169,6 +177,8 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
'should fail when morphRelationsCreationPayload has several references to same object metadata',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -201,6 +211,8 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
'should fail when morphRelationsCreationPayload has invalid relation payload',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -225,6 +237,8 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
title: 'should fail when target object metadata is not found',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -25,12 +25,14 @@ import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-m
export type FromCreateFieldInputToFlatObjectMetadataArgs = {
createFieldInput: Omit<CreateFieldInput, 'workspaceId'>;
workspaceId: string;
workspaceCustomApplicationId: string;
} & Pick<AllFlatEntityMaps, 'flatObjectMetadataMaps'>;
export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
createFieldInput: rawCreateFieldInput,
workspaceId,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
workspaceCustomApplicationId,
}: FromCreateFieldInputToFlatObjectMetadataArgs): Promise<
FieldInputTranspilationResult<{
flatFieldMetadatas: FlatFieldMetadata[];
@@ -71,6 +73,7 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
createFieldInput,
workspaceId,
fieldMetadataId,
workspaceCustomApplicationId,
});
switch (createFieldInput.type) {
@@ -83,6 +86,7 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
existingFlatObjectMetadataMaps,
sourceFlatObjectMetadata: parentFlatObjectMetadata,
workspaceId,
workspaceCustomApplicationId,
});
}
case FieldMetadataType.RELATION: {
@@ -94,6 +98,7 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
type: createFieldInput.type,
},
workspaceId,
workspaceCustomApplicationId,
});
}
case FieldMetadataType.RATING: {
@@ -21,12 +21,14 @@ type FromMorphRelationCreateFieldInputToFlatFieldMetadatasArgs = {
existingFlatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
sourceFlatObjectMetadata: FlatObjectMetadata;
workspaceId: string;
workspaceCustomApplicationId: string;
};
export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({
createFieldInput,
existingFlatObjectMetadataMaps,
sourceFlatObjectMetadata,
workspaceId,
workspaceCustomApplicationId,
}: FromMorphRelationCreateFieldInputToFlatFieldMetadatasArgs): Promise<
FieldInputTranspilationResult<{
flatFieldMetadatas: FlatFieldMetadata[];
@@ -90,6 +92,7 @@ export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({
targetFlatObjectMetadata,
workspaceId,
morphId,
workspaceCustomApplicationId,
});
return {
@@ -21,12 +21,14 @@ type FromRelationCreateFieldInputToFlatFieldMetadataArgs = {
existingFlatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
sourceFlatObjectMetadata: FlatObjectMetadata;
workspaceId: string;
workspaceCustomApplicationId: string;
};
export const fromRelationCreateFieldInputToFlatFieldMetadatas = async ({
existingFlatObjectMetadataMaps,
sourceFlatObjectMetadata,
createFieldInput,
workspaceId,
workspaceCustomApplicationId,
}: FromRelationCreateFieldInputToFlatFieldMetadataArgs): Promise<
FieldInputTranspilationResult<SourceTargetMorphOrRelationFlatFieldAndFlatIndex>
> => {
@@ -67,6 +69,7 @@ export const fromRelationCreateFieldInputToFlatFieldMetadatas = async ({
sourceFlatObjectMetadata,
targetFlatObjectMetadata,
workspaceId,
workspaceCustomApplicationId,
});
return {
@@ -44,7 +44,7 @@ export const generateIndexForFlatFieldMetadata = ({
universalIdentifier: indexId,
updatedAt: createdAt,
workspaceId,
applicationId: null,
applicationId: flatFieldMetadata.applicationId,
},
flatObjectMetadata,
},
@@ -1,7 +1,7 @@
import {
FieldMetadataType,
RelationType,
RelationOnDeleteAction,
RelationType,
} from 'twenty-shared/types';
import { v4 } from 'uuid';
@@ -46,6 +46,7 @@ type GenerateMorphOrRelationFlatFieldMetadataPairArgs = {
> & { type: MorphOrRelationFieldMetadataType };
workspaceId: string;
morphId?: string | null;
workspaceCustomApplicationId: string;
};
export type SourceTargetMorphOrRelationFlatFieldAndFlatIndex = {
@@ -58,6 +59,7 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({
sourceFlatObjectMetadata,
targetFlatObjectMetadata,
workspaceId,
workspaceCustomApplicationId,
sourceFlatObjectMetadataJoinColumnName,
morphId = null,
}: GenerateMorphOrRelationFlatFieldMetadataPairArgs): SourceTargetMorphOrRelationFlatFieldAndFlatIndex => {
@@ -78,6 +80,7 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({
createFieldInput,
workspaceId,
fieldMetadataId: sourceRelationTargetFieldMetadataId,
workspaceCustomApplicationId,
}),
morphId,
objectMetadataId: sourceFlatObjectMetadata.id,
@@ -116,6 +119,7 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({
createFieldInput: targetCreateFieldInput,
workspaceId,
fieldMetadataId: targetRelationTargetFieldMetadataId,
workspaceCustomApplicationId,
}),
type: FieldMetadataType.RELATION,
defaultValue: null,
@@ -9,11 +9,13 @@ type GetDefaultFlatFieldMetadataArgs = {
fieldMetadataId: string;
createFieldInput: Omit<CreateFieldInput, 'workspaceId'>;
workspaceId: string;
workspaceCustomApplicationId: string;
};
export const getDefaultFlatFieldMetadata = ({
createFieldInput,
fieldMetadataId,
workspaceId,
workspaceCustomApplicationId,
}: GetDefaultFlatFieldMetadataArgs) => {
const { defaultValue, settings } = extractAndSanitizeObjectStringFields(
createFieldInput,
@@ -56,7 +58,7 @@ export const getDefaultFlatFieldMetadata = ({
updatedAt: createdAt,
isUIReadOnly: createFieldInput.isUIReadOnly ?? false,
morphId: null,
applicationId: createFieldInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
viewFilterIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
@@ -17,11 +17,13 @@ type FromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreateArgs
{
createObjectInput: CreateObjectInput;
workspaceId: string;
workspaceCustomApplicationId: string;
} & Pick<AllFlatEntityMaps, 'flatObjectMetadataMaps'>;
export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreate =
({
createObjectInput: rawCreateObjectInput,
workspaceId,
workspaceCustomApplicationId,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
}: FromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreateArgs): {
flatObjectMetadataToCreate: FlatObjectMetadata;
@@ -48,7 +50,7 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre
buildDefaultFlatFieldMetadatasForCustomObject({
flatObjectMetadata: {
id: objectMetadataId,
applicationId: createObjectInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
},
workspaceId,
});
@@ -81,7 +83,7 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre
shortcut: createObjectInput.shortcut ?? null,
standardId: createObjectInput.standardId ?? null,
standardOverrides: null,
applicationId: createObjectInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
universalIdentifier: objectMetadataId,
targetTableName: 'DEPRECATED',
workspaceId,
@@ -8,11 +8,13 @@ import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/workspace-manager/standard-o
export type FromCreateViewFieldInputToFlatViewFieldToCreateArgs = {
createViewFieldInput: CreateViewFieldInput;
workspaceId: string;
workspaceCustomApplicationId: string;
};
export const fromCreateViewFieldInputToFlatViewFieldToCreate = ({
createViewFieldInput: rawCreateViewFieldInput,
workspaceId,
workspaceCustomApplicationId,
}: FromCreateViewFieldInputToFlatViewFieldToCreateArgs): FlatViewField => {
const { fieldMetadataId, viewId, ...createViewFieldInput } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
@@ -37,6 +39,6 @@ export const fromCreateViewFieldInputToFlatViewFieldToCreate = ({
size: createViewFieldInput.size ?? DEFAULT_VIEW_FIELD_SIZE,
position: createViewFieldInput.position ?? 0,
aggregateOperation: createViewFieldInput.aggregateOperation ?? null,
applicationId: createViewFieldInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
};
};
@@ -8,9 +8,11 @@ import { type CreateViewFilterInput } from 'src/engine/metadata-modules/view-fil
export const fromCreateViewFilterInputToFlatViewFilterToCreate = ({
createViewFilterInput: rawCreateViewFilterInput,
workspaceId,
workspaceCustomApplicationId,
}: {
createViewFilterInput: CreateViewFilterInput;
workspaceId: string;
workspaceCustomApplicationId: string;
}): FlatViewFilter => {
const { fieldMetadataId, viewId, value, ...createViewFilterInput } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
@@ -44,6 +46,6 @@ export const fromCreateViewFilterInputToFlatViewFilterToCreate = ({
positionInViewFilterGroup:
createViewFilterInput.positionInViewFilterGroup ?? null,
subFieldName: createViewFilterInput.subFieldName ?? null,
applicationId: createViewFilterInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
};
};
@@ -7,9 +7,11 @@ import { type CreateViewGroupInput } from 'src/engine/metadata-modules/view-grou
export const fromCreateViewGroupInputToFlatViewGroupToCreate = ({
createViewGroupInput: rawCreateViewGroupInput,
workspaceId,
workspaceCustomApplicationId,
}: {
createViewGroupInput: CreateViewGroupInput;
workspaceId: string;
workspaceCustomApplicationId: string;
}): FlatViewGroup => {
const { fieldMetadataId, viewId, ...createViewGroupInput } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
@@ -33,6 +35,6 @@ export const fromCreateViewGroupInputToFlatViewGroupToCreate = ({
isVisible: createViewGroupInput.isVisible ?? true,
fieldValue: createViewGroupInput.fieldValue,
position: createViewGroupInput.position ?? 0,
applicationId: createViewGroupInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
};
};
@@ -11,10 +11,12 @@ export const fromCreateViewInputToFlatViewToCreate = ({
createViewInput: rawCreateViewInput,
workspaceId,
createdByUserWorkspaceId,
workspaceCustomApplicationId,
}: {
createViewInput: CreateViewInput;
workspaceId: string;
createdByUserWorkspaceId?: string;
workspaceCustomApplicationId: string;
}): FlatView => {
const { objectMetadataId, ...createViewInput } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
@@ -52,6 +54,6 @@ export const fromCreateViewInputToFlatViewToCreate = ({
viewFieldIds: [],
viewFilterIds: [],
viewGroupIds: [],
applicationId: createViewInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
};
};
@@ -5,6 +5,7 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { fromArrayToUniqueKeyRecord, isDefined } from 'twenty-shared/utils';
import { FindManyOptions, FindOneOptions, Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
@@ -43,6 +44,7 @@ export class ObjectMetadataServiceV2 extends TypeOrmQueryService<ObjectMetadataE
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly applicationService: ApplicationService,
) {
super(objectMetadataRepository);
}
@@ -323,10 +325,22 @@ export class ObjectMetadataServiceV2 extends TypeOrmQueryService<ObjectMetadataE
async createOneObject({
createObjectInput,
workspaceId,
applicationId,
}: {
createObjectInput: CreateObjectInput;
workspaceId: string;
/**
* @deprecated do not use call validateBuildAndRunWorkspaceMigration contextually
* when interacting with another application than workspace custom one
* */
applicationId?: string;
}): Promise<FlatObjectMetadata> {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatViewMaps: existingFlatViewMaps,
@@ -354,6 +368,8 @@ export class ObjectMetadataServiceV2 extends TypeOrmQueryService<ObjectMetadataE
} = fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreate({
createObjectInput,
workspaceId,
workspaceCustomApplicationId:
applicationId ?? workspaceCustomFlatApplication.id,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
});
@@ -367,13 +383,15 @@ export class ObjectMetadataServiceV2 extends TypeOrmQueryService<ObjectMetadataE
flatEntityToUpdate: [],
});
const flatDefaultViewToCreate = await this.createDefaultFlatView({
const flatDefaultViewToCreate = await this.computeFlatViewToCreate({
objectMetadata: flatObjectMetadataToCreate,
workspaceId,
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
});
const flatDefaultViewFieldsToCreate =
await this.createDefaultFlatViewFields({
await this.computeFlatViewFieldsToCreate({
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
objectFlatFieldMetadatas: findManyFlatEntityByIdInFlatEntityMapsOrThrow(
{
flatEntityMaps: flatFieldMetadataMapsFromTo.to,
@@ -456,10 +474,12 @@ export class ObjectMetadataServiceV2 extends TypeOrmQueryService<ObjectMetadataE
return createdFlatObjectMetadata;
}
private async createDefaultFlatView({
private async computeFlatViewToCreate({
objectMetadata,
workspaceId,
workspaceCustomApplicationId,
}: {
workspaceCustomApplicationId: string;
objectMetadata: FlatObjectMetadata;
workspaceId: string;
}) {
@@ -475,16 +495,19 @@ export class ObjectMetadataServiceV2 extends TypeOrmQueryService<ObjectMetadataE
const flatViewFromCreateInput = fromCreateViewInputToFlatViewToCreate({
createViewInput: defaultViewInput,
workspaceId,
workspaceCustomApplicationId,
});
return flatViewFromCreateInput;
}
private async createDefaultFlatViewFields({
private async computeFlatViewFieldsToCreate({
objectFlatFieldMetadatas,
viewId,
workspaceId,
workspaceCustomApplicationId,
}: {
workspaceCustomApplicationId: string;
objectFlatFieldMetadatas: FlatFieldMetadata[];
viewId: string;
workspaceId: string;
@@ -500,6 +523,7 @@ export class ObjectMetadataServiceV2 extends TypeOrmQueryService<ObjectMetadataE
size: DEFAULT_VIEW_FIELD_SIZE,
viewId: viewId,
},
workspaceCustomApplicationId,
workspaceId: workspaceId,
}),
);
@@ -9,6 +9,7 @@ import {
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
@@ -50,6 +51,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
FieldMetadataEntity,
]),
TypeOrmModule.forFeature([FeatureFlagEntity, ViewEntity]),
ApplicationModule,
DataSourceModule,
WorkspaceMigrationModule,
WorkspaceMigrationRunnerModule,
@@ -1,123 +0,0 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { v4 } from 'uuid';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
CUSTOM_OBJECT_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
export const buildDefaultFieldsForCustomObject = (
workspaceId: string,
): Array<Partial<FieldMetadataEntity> & { id: string }> => [
{
id: v4(),
standardId: BASE_OBJECT_STANDARD_FIELD_IDS.id,
type: FieldMetadataType.UUID,
name: 'id',
label: 'Id',
icon: 'Icon123',
description: 'Id',
isNullable: false,
isActive: true,
isCustom: false,
isSystem: true,
isUIReadOnly: true,
workspaceId,
defaultValue: 'uuid',
},
{
id: v4(),
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.name,
type: FieldMetadataType.TEXT,
name: 'name',
label: 'Name',
icon: 'IconAbc',
description: 'Name',
isNullable: false,
isActive: true,
isCustom: false,
isUIReadOnly: false,
workspaceId,
defaultValue: "''",
},
{
id: v4(),
standardId: BASE_OBJECT_STANDARD_FIELD_IDS.createdAt,
type: FieldMetadataType.DATE_TIME,
name: 'createdAt',
label: 'Creation date',
icon: 'IconCalendar',
description: 'Creation date',
isNullable: false,
isActive: true,
isCustom: false,
isUIReadOnly: false,
workspaceId,
defaultValue: 'now',
},
{
id: v4(),
standardId: BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt,
type: FieldMetadataType.DATE_TIME,
name: 'updatedAt',
label: 'Last update',
icon: 'IconCalendarClock',
description: 'Last time the record was changed',
isNullable: false,
isActive: true,
isCustom: false,
isSystem: false,
isUIReadOnly: false,
workspaceId,
defaultValue: 'now',
},
{
id: v4(),
standardId: BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt,
type: FieldMetadataType.DATE_TIME,
name: 'deletedAt',
label: 'Deleted at',
icon: 'IconCalendarClock',
description: 'Deletion date',
isNullable: true,
isActive: true,
isCustom: false,
isSystem: false,
isUIReadOnly: true,
workspaceId,
defaultValue: null,
},
{
id: v4(),
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.createdBy,
type: FieldMetadataType.ACTOR,
name: 'createdBy',
label: 'Created by',
icon: 'IconCreativeCommonsSa',
description: 'The creator of the record',
isNullable: false,
isActive: true,
isCustom: false,
isSystem: false,
isUIReadOnly: true,
workspaceId,
defaultValue: { name: "''", source: "'MANUAL'" },
},
{
id: v4(),
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.position,
type: FieldMetadataType.POSITION,
name: 'position',
label: 'Position',
icon: 'IconHierarchy2',
description: 'Position',
isNullable: false,
isActive: true,
isCustom: false,
isSystem: true,
isUIReadOnly: false,
workspaceId,
defaultValue: 0,
},
];
@@ -7,7 +7,6 @@ import {
BASE_OBJECT_STANDARD_FIELD_IDS,
CUSTOM_OBJECT_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { createDeterministicUuid } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util';
type BuildDefaultFlatFieldMetadataForCustomObjectArgs = {
@@ -24,9 +23,10 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
flatObjectMetadata: { id: objectMetadataId, applicationId },
}: BuildDefaultFlatFieldMetadataForCustomObjectArgs) => {
const createdAt = new Date();
const idFieldId = v4();
const idField: FlatFieldMetadata<FieldMetadataType.UUID> = {
type: FieldMetadataType.UUID,
id: v4(),
id: idFieldId,
viewFieldIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
@@ -34,10 +34,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isLabelSyncedWithName: false,
isUnique: true,
objectMetadataId,
universalIdentifier: createDeterministicUuid([
objectMetadataId,
BASE_OBJECT_STANDARD_FIELD_IDS.id,
]),
universalIdentifier: idFieldId,
workspaceId,
standardId: BASE_OBJECT_STANDARD_FIELD_IDS.id,
name: 'id',
@@ -63,9 +60,10 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
applicationId: applicationId ?? null,
};
const nameFieldId = v4();
const nameField: FlatFieldMetadata<FieldMetadataType.TEXT> = {
type: FieldMetadataType.TEXT,
id: v4(),
id: nameFieldId,
viewFieldIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
@@ -73,10 +71,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
universalIdentifier: createDeterministicUuid([
objectMetadataId,
CUSTOM_OBJECT_STANDARD_FIELD_IDS.name,
]),
universalIdentifier: nameFieldId,
workspaceId,
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.name,
name: 'name',
@@ -102,9 +97,10 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
applicationId: applicationId ?? null,
};
const createdAtFieldId = v4();
const createdAtField: FlatFieldMetadata<FieldMetadataType.DATE_TIME> = {
type: FieldMetadataType.DATE_TIME,
id: v4(),
id: createdAtFieldId,
viewFieldIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
@@ -112,10 +108,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
universalIdentifier: createDeterministicUuid([
objectMetadataId,
BASE_OBJECT_STANDARD_FIELD_IDS.createdAt,
]),
universalIdentifier: createdAtFieldId,
workspaceId,
standardId: BASE_OBJECT_STANDARD_FIELD_IDS.createdAt,
name: 'createdAt',
@@ -141,9 +134,10 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
applicationId: applicationId ?? null,
};
const updatedAtFieldId = v4();
const updatedAtField: FlatFieldMetadata<FieldMetadataType.DATE_TIME> = {
type: FieldMetadataType.DATE_TIME,
id: v4(),
id: updatedAtFieldId,
viewFieldIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
@@ -151,10 +145,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
universalIdentifier: createDeterministicUuid([
objectMetadataId,
BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt,
]),
universalIdentifier: updatedAtFieldId,
workspaceId,
standardId: BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt,
name: 'updatedAt',
@@ -180,9 +171,10 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
applicationId: applicationId ?? null,
};
const deletedAtFieldId = v4();
const deletedAtField: FlatFieldMetadata<FieldMetadataType.DATE_TIME> = {
type: FieldMetadataType.DATE_TIME,
id: v4(),
id: deletedAtFieldId,
viewFieldIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
@@ -190,10 +182,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
universalIdentifier: createDeterministicUuid([
objectMetadataId,
BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt,
]),
universalIdentifier: deletedAtFieldId,
workspaceId,
standardId: BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt,
name: 'deletedAt',
@@ -219,9 +208,10 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
applicationId: applicationId ?? null,
};
const createdByFieldId = v4();
const createdByField: FlatFieldMetadata<FieldMetadataType.ACTOR> = {
type: FieldMetadataType.ACTOR,
id: v4(),
id: createdByFieldId,
viewFieldIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
@@ -229,10 +219,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
universalIdentifier: createDeterministicUuid([
objectMetadataId,
CUSTOM_OBJECT_STANDARD_FIELD_IDS.createdBy,
]),
universalIdentifier: createdByFieldId,
workspaceId,
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.createdBy,
name: 'createdBy',
@@ -257,9 +244,10 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
applicationId: applicationId ?? null,
};
const positionFieldId = v4();
const positionField: FlatFieldMetadata<FieldMetadataType.POSITION> = {
type: FieldMetadataType.POSITION,
id: v4(),
id: positionFieldId,
viewFieldIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
@@ -267,10 +255,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
universalIdentifier: createDeterministicUuid([
objectMetadataId,
CUSTOM_OBJECT_STANDARD_FIELD_IDS.position,
]),
universalIdentifier: positionFieldId,
workspaceId,
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.position,
name: 'position',
@@ -296,20 +281,18 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
applicationId: applicationId ?? null,
};
const searchVectorFieldId = v4();
const searchVectorField: FlatFieldMetadata<FieldMetadataType.TS_VECTOR> = {
type: FieldMetadataType.TS_VECTOR,
viewFieldIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
calendarViewIds: [],
id: v4(),
id: searchVectorFieldId,
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
universalIdentifier: createDeterministicUuid([
objectMetadataId,
CUSTOM_OBJECT_STANDARD_FIELD_IDS.searchVector,
]),
universalIdentifier: searchVectorFieldId,
workspaceId,
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.searchVector,
name: 'searchVector',
@@ -1,7 +1,7 @@
import { type STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
@@ -16,15 +16,9 @@ import {
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
import { buildDescriptionForRelationFieldMetadataOnFromField } from 'src/engine/metadata-modules/object-metadata/utils/build-description-for-relation-field-on-from-field.util';
import { buildDescriptionForRelationFieldMetadataOnToField } from 'src/engine/metadata-modules/object-metadata/utils/build-description-for-relation-field-on-to-field.util';
import {
CUSTOM_OBJECT_STANDARD_FIELD_IDS,
STANDARD_OBJECT_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { STANDARD_OBJECT_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { STANDARD_OBJECT_ICONS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-icons';
import {
createDeterministicUuid,
createRelationDeterministicUuid,
} from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
import { createRelationDeterministicUuid } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
const generateSourceFlatFieldMetadata = ({
workspaceId,
@@ -49,17 +43,6 @@ const generateSourceFlatFieldMetadata = ({
STANDARD_OBJECT_ICONS[
targetFlatObjectMetadata.nameSingular as keyof typeof STANDARD_OBJECT_ICONS
] || 'IconBuildingSkyscraper';
const standardId =
CUSTOM_OBJECT_STANDARD_FIELD_IDS[
targetFlatObjectMetadata.namePlural as keyof typeof CUSTOM_OBJECT_STANDARD_FIELD_IDS
];
if (!isDefined(standardId)) {
throw new ObjectMetadataException(
`Standard field ID not found for target object ${targetFlatObjectMetadata.namePlural}`,
ObjectMetadataExceptionCode.INTERNAL_SERVER_ERROR,
);
}
return {
calendarViewIds: [],
@@ -89,16 +72,13 @@ const generateSourceFlatFieldMetadata = ({
settings: {
relationType: RelationType.ONE_TO_MANY,
},
standardId,
standardId: null,
standardOverrides: null,
type: FieldMetadataType.RELATION,
universalIdentifier: createDeterministicUuid([
sourceFlatObjectMetadata.id,
standardId,
]),
universalIdentifier: sourceFieldMetadataId,
workspaceId,
morphId: null,
applicationId: sourceFlatObjectMetadata.applicationId ?? null,
applicationId: sourceFlatObjectMetadata.applicationId,
};
};
@@ -165,11 +145,8 @@ const generateTargetFlatFieldMetadata = ({
relationTargetFieldMetadataId: sourceFlatFieldMetadata.id,
relationTargetObjectMetadataId: sourceFlatObjectMetadata.id,
standardOverrides: null,
universalIdentifier: createDeterministicUuid([
targetFlatObjectMetadata.id,
standardId,
]),
applicationId: sourceFlatObjectMetadata.applicationId ?? null,
universalIdentifier: sourceFlatFieldMetadata.relationTargetFieldMetadataId,
applicationId: sourceFlatObjectMetadata.applicationId,
};
};
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FileModule } from 'src/engine/core-modules/file/file.module';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
@@ -30,6 +31,7 @@ import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/wor
PermissionFlagModule,
WorkspacePermissionsCacheModule,
FileModule,
ApplicationModule,
],
providers: [RoleService, RoleResolver],
exports: [RoleService],
@@ -3,7 +3,9 @@ import { InjectRepository } from '@nestjs/typeorm';
import { msg } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { v4 } from 'uuid';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { MEMBER_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/member-role-label.constants';
import {
@@ -29,6 +31,7 @@ export class RoleService {
private readonly roleRepository: Repository<RoleEntity>,
private readonly userRoleService: UserRoleService,
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
private readonly applicationService: ApplicationService,
) {}
public async getWorkspaceRoles(workspaceId: string): Promise<RoleEntity[]> {
@@ -70,10 +73,18 @@ export class RoleService {
input: CreateRoleInput;
workspaceId: string;
}): Promise<RoleEntity> {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
await this.validateRoleInputOrThrow({ input, workspaceId });
const id = input.id ?? v4();
const role = await this.roleRepository.save({
id: input.id,
id,
label: input.label,
description: input.description,
icon: input.icon,
@@ -88,6 +99,8 @@ export class RoleService {
canBeAssignedToApiKeys: input.canBeAssignedToApiKeys,
isEditable: true,
workspaceId,
applicationId: workspaceCustomFlatApplication.id,
universalIdentifier: id,
});
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache({
@@ -198,9 +211,13 @@ export class RoleService {
public async createMemberRole({
workspaceId,
applicationId,
}: {
applicationId: string;
workspaceId: string;
}): Promise<RoleEntity> {
const id = v4();
return this.roleRepository.save({
label: MEMBER_ROLE_LABEL,
description: 'Member role',
@@ -216,15 +233,22 @@ export class RoleService {
canBeAssignedToApiKeys: false,
isEditable: true,
workspaceId,
applicationId,
id,
universalIdentifier: id,
});
}
// Only used for dev seeding and testing
public async createGuestRole({
workspaceId,
applicationId,
}: {
workspaceId: string;
applicationId: string;
}): Promise<RoleEntity> {
const id = v4();
return this.roleRepository.save({
label: 'Guest',
description: 'Guest role',
@@ -240,6 +264,9 @@ export class RoleService {
canBeAssignedToApiKeys: false,
isEditable: false,
workspaceId,
applicationId,
id,
universalIdentifier: id,
});
}
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
@@ -17,6 +18,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
@Module({
imports: [
TypeOrmModule.forFeature([RouteTriggerEntity]),
ApplicationModule,
TokenModule,
WorkspaceDomainsModule,
ServerlessFunctionModule,
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
@@ -23,12 +24,25 @@ export class RouteTriggerV2Service {
constructor(
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly applicationService: ApplicationService,
) {}
async createOne(
routeTriggerInput: CreateRouteTriggerInput,
workspaceId: string,
/**
* @deprecated do not use call validateBuildAndRunWorkspaceMigration contextually
* when interacting with another application than workspace custom one
* */
applicationId?: string,
) {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const flatEntityMaps =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
@@ -43,6 +57,8 @@ export class RouteTriggerV2Service {
fromCreateRouteTriggerInputToFlatRouteTrigger({
createRouteTriggerInput: routeTriggerInput,
workspaceId,
workspaceCustomApplicationId:
applicationId ?? workspaceCustomFlatApplication.id,
});
const validateAndBuildResult =
@@ -6,16 +6,18 @@ import { type FlatRouteTrigger } from 'src/engine/metadata-modules/route-trigger
export const fromCreateRouteTriggerInputToFlatRouteTrigger = ({
createRouteTriggerInput,
workspaceId,
workspaceCustomApplicationId,
}: {
createRouteTriggerInput: CreateRouteTriggerInput;
workspaceId: string;
workspaceCustomApplicationId: string;
}): FlatRouteTrigger => {
const now = new Date();
const id = uuidV4();
return {
id: uuidV4(),
universalIdentifier:
createRouteTriggerInput.universalIdentifier ?? uuidV4(),
id,
universalIdentifier: createRouteTriggerInput.universalIdentifier ?? id,
path: createRouteTriggerInput.path,
isAuthRequired: createRouteTriggerInput.isAuthRequired,
httpMethod: createRouteTriggerInput.httpMethod,
@@ -23,6 +25,6 @@ export const fromCreateRouteTriggerInputToFlatRouteTrigger = ({
workspaceId,
createdAt: now,
updatedAt: now,
applicationId: createRouteTriggerInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
};
};
@@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
@@ -35,6 +36,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
]),
FileModule,
ThrottlerModule,
ApplicationModule,
AuditModule,
FeatureFlagModule,
PermissionsModule,
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
@@ -23,6 +24,7 @@ export class ServerlessFunctionV2Service {
constructor(
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly applicationService: ApplicationService,
) {}
async createOne({
@@ -34,6 +36,13 @@ export class ServerlessFunctionV2Service {
};
workspaceId: string;
}) {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const flatEntityMaps =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
@@ -49,6 +58,7 @@ export class ServerlessFunctionV2Service {
fromCreateServerlessFunctionInputToFlatServerlessFunction({
createServerlessFunctionInput,
workspaceId,
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
});
const validateAndBuildResult =
@@ -14,11 +14,13 @@ export type FromCreateServerlessFunctionInputToFlatServerlessFunctionArgs = {
serverlessFunctionLayerId: string;
};
workspaceId: string;
workspaceCustomApplicationId: string;
};
export const fromCreateServerlessFunctionInputToFlatServerlessFunction = ({
createServerlessFunctionInput: rawCreateServerlessFunctionInput,
workspaceId,
workspaceCustomApplicationId,
}: FromCreateServerlessFunctionInputToFlatServerlessFunctionArgs): FlatServerlessFunction => {
const id = v4();
const currentDate = new Date();
@@ -35,13 +37,13 @@ export const fromCreateServerlessFunctionInputToFlatServerlessFunction = ({
handlerName:
rawCreateServerlessFunctionInput.handlerName ?? DEFAULT_HANDLER_NAME,
universalIdentifier:
rawCreateServerlessFunctionInput.universalIdentifier ?? v4(),
rawCreateServerlessFunctionInput.universalIdentifier ?? id,
createdAt: currentDate,
updatedAt: currentDate,
deletedAt: null,
latestVersion: null,
publishedVersions: [],
applicationId: rawCreateServerlessFunctionInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
runtime: ServerlessFunctionRuntime.NODE22,
timeoutSeconds: rawCreateServerlessFunctionInput.timeoutSeconds ?? 300,
serverlessFunctionLayerId:
@@ -4,6 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { IsNull, Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
@@ -32,6 +33,7 @@ export class ViewFieldV2Service {
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
@InjectRepository(ViewFieldEntity)
private readonly viewFieldRepository: Repository<ViewFieldEntity>,
private readonly applicationService: ApplicationService,
) {}
async createOne({
@@ -67,6 +69,13 @@ export class ViewFieldV2Service {
return [];
}
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const {
flatViewFieldMaps: existingFlatViewFieldMaps,
flatViewMaps,
@@ -89,6 +98,7 @@ export class ViewFieldV2Service {
fromCreateViewFieldInputToFlatViewFieldToCreate({
createViewFieldInput,
workspaceId,
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
}),
);
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { ViewFieldController } from 'src/engine/metadata-modules/view-field/controllers/view-field.controller';
@@ -16,6 +17,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
imports: [
TypeOrmModule.forFeature([ViewFieldEntity, ViewEntity]),
WorkspaceCacheStorageModule,
ApplicationModule,
PermissionsModule,
WorkspaceMigrationV2Module,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
@@ -22,6 +23,7 @@ export class ViewFilterV2Service {
constructor(
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly applicationService: ApplicationService,
) {}
async createOne({
@@ -31,6 +33,13 @@ export class ViewFilterV2Service {
createViewFilterInput: CreateViewFilterInput;
workspaceId: string;
}): Promise<ViewFilterDTO> {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const {
flatViewFilterMaps: existingFlatViewFilterMaps,
flatViewMaps,
@@ -52,6 +61,7 @@ export class ViewFilterV2Service {
fromCreateViewFilterInputToFlatViewFilterToCreate({
createViewFilterInput,
workspaceId,
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
});
const validateAndBuildResult =
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
@@ -18,6 +19,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
imports: [
TypeOrmModule.forFeature([ViewFilterEntity, ViewEntity]),
WorkspaceCacheStorageModule,
ApplicationModule,
FeatureFlagModule,
PermissionsModule,
WorkspaceMigrationV2Module,
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
@@ -27,6 +28,7 @@ export class ViewGroupV2Service {
constructor(
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly applicationService: ApplicationService,
) {}
async createOne({
@@ -62,6 +64,13 @@ export class ViewGroupV2Service {
return [];
}
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const {
flatViewGroupMaps: existingFlatViewGroupMaps,
flatViewMaps,
@@ -82,6 +91,7 @@ export class ViewGroupV2Service {
fromCreateViewGroupInputToFlatViewGroupToCreate({
createViewGroupInput,
workspaceId,
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
}),
);
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
@@ -18,6 +19,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
imports: [
TypeOrmModule.forFeature([ViewGroupEntity, ViewEntity]),
WorkspaceCacheStorageModule,
ApplicationModule,
FeatureFlagModule,
PermissionsModule,
WorkspaceMigrationV2Module,
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
@@ -22,6 +23,7 @@ export class ViewV2Service {
constructor(
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly applicationService: ApplicationService,
) {}
async createOne({
@@ -33,6 +35,13 @@ export class ViewV2Service {
workspaceId: string;
createdByUserWorkspaceId?: string;
}): Promise<ViewDTO> {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const {
flatObjectMetadataMaps,
flatViewMaps: existingFlatViewMaps,
@@ -52,6 +61,7 @@ export class ViewV2Service {
createViewInput,
workspaceId,
createdByUserWorkspaceId,
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
});
const validateAndBuildResult =
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { I18nModule } from 'src/engine/core-modules/i18n/i18n.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
@@ -32,6 +33,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
ViewGroupModule,
ViewSortModule,
I18nModule,
ApplicationModule,
FeatureFlagModule,
PermissionsModule,
UserRoleModule,