Add applicationId to syncableEntity and fix syncApp deletion (#15170)

## Context
- All flatEntity should extend SyncableEntity
- SyncableEntity should now have applicationId and application relation
- Fix syncApp deletion, should now properly use migration v2 to delete
syncable entities
This commit is contained in:
Weiko
2025-10-17 19:23:00 +02:00
committed by GitHub
parent 84e7fabaab
commit cceeb6ed4d
79 changed files with 734 additions and 271 deletions

View File

@@ -15,15 +15,14 @@ describe.each(COVERED_APPLICATION_FOLDERS)(
expect(existsSync(appPath)).toBe(true);
});
// TODO uncomment when reinstall is fix to cleanup gracefully
// afterAll(async () => {
// const result = await deleteCommand.execute({
// appPath,
// askForConfirmation: false,
// });
afterAll(async () => {
const result = await deleteCommand.execute({
appPath,
askForConfirmation: false,
});
// expect(result.success).toBe(true);
// });
expect(result.success).toBe(true);
});
it(`should successfully install ${applicationName} application`, async () => {
const result = await syncCommand.execute(appPath);
@@ -40,13 +39,10 @@ describe.each(COVERED_APPLICATION_FOLDERS)(
expect(result.success).toBe(true);
});
it.failing(
`should successfully re-install ${applicationName} application`,
async () => {
const result = await syncCommand.execute(appPath);
it(`should successfully re-install ${applicationName} application`, async () => {
const result = await syncCommand.execute(appPath);
expect(result.success).toBe(true);
},
);
expect(result.success).toBe(true);
});
},
);

View File

@@ -0,0 +1,217 @@
import { type MigrationInterface, type QueryRunner } from 'typeorm';
export class AddApplicationIdToSyncableEntities1760700501795
implements MigrationInterface
{
name = 'AddApplicationIdToSyncableEntities1760700501795';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."application" DROP CONSTRAINT "FK_eec488855d08b312a869a13ccb1"`,
);
await queryRunner.query(
`ALTER TABLE "core"."agent" ADD "universalIdentifier" uuid`,
);
await queryRunner.query(
`ALTER TABLE "core"."role" ADD "universalIdentifier" uuid`,
);
await queryRunner.query(
`ALTER TABLE "core"."role" ADD "applicationId" uuid`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewFilter" ADD "applicationId" uuid`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewFilterGroup" ADD "applicationId" uuid`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewGroup" ADD "applicationId" uuid`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewSort" ADD "applicationId" uuid`,
);
await queryRunner.query(
`ALTER TABLE "core"."view" ADD "applicationId" uuid`,
);
await queryRunner.query(
`ALTER TABLE "core"."objectMetadata" ADD "universalIdentifier" uuid`,
);
await queryRunner.query(
`ALTER TABLE "core"."indexMetadata" ADD "applicationId" uuid`,
);
await queryRunner.query(
`ALTER TABLE "core"."fieldMetadata" ADD "universalIdentifier" uuid`,
);
await queryRunner.query(
`ALTER TABLE "core"."fieldMetadata" ADD "applicationId" uuid`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewField" ADD "applicationId" uuid`,
);
await queryRunner.query(
`ALTER TABLE "core"."cronTrigger" ADD "applicationId" uuid`,
);
await queryRunner.query(
`ALTER TABLE "core"."databaseEventTrigger" ADD "applicationId" uuid`,
);
await queryRunner.query(
`ALTER TABLE "core"."routeTrigger" ADD "applicationId" uuid`,
);
await queryRunner.query(
`ALTER TABLE "core"."application" DROP CONSTRAINT "UQ_eec488855d08b312a869a13ccb1"`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_0cc4d03dbcc269e77ba4d297fb" ON "core"."agent" ("workspaceId", "universalIdentifier") `,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_3b7ff27925c0959777682c1adc" ON "core"."role" ("workspaceId", "universalIdentifier") `,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_3a00d35710f4227ded320fd96d" ON "core"."objectMetadata" ("workspaceId", "universalIdentifier") `,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_f1c88fdfc3ad8910b17fc1fd73" ON "core"."fieldMetadata" ("workspaceId", "universalIdentifier") `,
);
await queryRunner.query(
`ALTER TABLE "core"."role" ADD CONSTRAINT "FK_7f3b96f15aaf5a27549288d264b" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewFilter" ADD CONSTRAINT "FK_d5651cf33fa56a47cd262a3fb2c" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewFilterGroup" ADD CONSTRAINT "FK_bfc3498b964ef1bfc89b1f2bee3" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewGroup" ADD CONSTRAINT "FK_5aff384532c78fa8a42ceeae282" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewSort" ADD CONSTRAINT "FK_ff8cbebe1704954120df82bf393" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."view" ADD CONSTRAINT "FK_348e25d584c7e51417f4e097941" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."indexMetadata" ADD CONSTRAINT "FK_056363e1599f5b9a0e33323d9da" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."fieldMetadata" ADD CONSTRAINT "FK_05453a954e458e3d91f2ff5043f" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewField" ADD CONSTRAINT "FK_b560ea62a958deff0c6059caa45" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."cronTrigger" ADD CONSTRAINT "FK_817ea28e71e3b19acc258dd7dcd" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."databaseEventTrigger" ADD CONSTRAINT "FK_9acc2804037a5c885633024368d" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."routeTrigger" ADD CONSTRAINT "FK_6edf47a8bfe17a5811998dc7162" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."routeTrigger" DROP CONSTRAINT "FK_6edf47a8bfe17a5811998dc7162"`,
);
await queryRunner.query(
`ALTER TABLE "core"."databaseEventTrigger" DROP CONSTRAINT "FK_9acc2804037a5c885633024368d"`,
);
await queryRunner.query(
`ALTER TABLE "core"."cronTrigger" DROP CONSTRAINT "FK_817ea28e71e3b19acc258dd7dcd"`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewField" DROP CONSTRAINT "FK_b560ea62a958deff0c6059caa45"`,
);
await queryRunner.query(
`ALTER TABLE "core"."fieldMetadata" DROP CONSTRAINT "FK_05453a954e458e3d91f2ff5043f"`,
);
await queryRunner.query(
`ALTER TABLE "core"."indexMetadata" DROP CONSTRAINT "FK_056363e1599f5b9a0e33323d9da"`,
);
await queryRunner.query(
`ALTER TABLE "core"."view" DROP CONSTRAINT "FK_348e25d584c7e51417f4e097941"`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewSort" DROP CONSTRAINT "FK_ff8cbebe1704954120df82bf393"`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewGroup" DROP CONSTRAINT "FK_5aff384532c78fa8a42ceeae282"`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewFilterGroup" DROP CONSTRAINT "FK_bfc3498b964ef1bfc89b1f2bee3"`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewFilter" DROP CONSTRAINT "FK_d5651cf33fa56a47cd262a3fb2c"`,
);
await queryRunner.query(
`ALTER TABLE "core"."role" DROP CONSTRAINT "FK_7f3b96f15aaf5a27549288d264b"`,
);
await queryRunner.query(
`DROP INDEX "core"."IDX_f1c88fdfc3ad8910b17fc1fd73"`,
);
await queryRunner.query(
`DROP INDEX "core"."IDX_3a00d35710f4227ded320fd96d"`,
);
await queryRunner.query(
`DROP INDEX "core"."IDX_3b7ff27925c0959777682c1adc"`,
);
await queryRunner.query(
`DROP INDEX "core"."IDX_0cc4d03dbcc269e77ba4d297fb"`,
);
await queryRunner.query(
`ALTER TABLE "core"."application" ADD CONSTRAINT "UQ_eec488855d08b312a869a13ccb1" UNIQUE ("serverlessFunctionLayerId")`,
);
await queryRunner.query(
`ALTER TABLE "core"."routeTrigger" DROP COLUMN "applicationId"`,
);
await queryRunner.query(
`ALTER TABLE "core"."databaseEventTrigger" DROP COLUMN "applicationId"`,
);
await queryRunner.query(
`ALTER TABLE "core"."cronTrigger" DROP COLUMN "applicationId"`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewField" DROP COLUMN "applicationId"`,
);
await queryRunner.query(
`ALTER TABLE "core"."fieldMetadata" DROP COLUMN "applicationId"`,
);
await queryRunner.query(
`ALTER TABLE "core"."fieldMetadata" DROP COLUMN "universalIdentifier"`,
);
await queryRunner.query(
`ALTER TABLE "core"."indexMetadata" DROP COLUMN "applicationId"`,
);
await queryRunner.query(
`ALTER TABLE "core"."objectMetadata" DROP COLUMN "universalIdentifier"`,
);
await queryRunner.query(
`ALTER TABLE "core"."view" DROP COLUMN "applicationId"`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewSort" DROP COLUMN "applicationId"`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewGroup" DROP COLUMN "applicationId"`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewFilterGroup" DROP COLUMN "applicationId"`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewFilter" DROP COLUMN "applicationId"`,
);
await queryRunner.query(
`ALTER TABLE "core"."role" DROP COLUMN "applicationId"`,
);
await queryRunner.query(
`ALTER TABLE "core"."role" DROP COLUMN "universalIdentifier"`,
);
await queryRunner.query(
`ALTER TABLE "core"."agent" DROP COLUMN "universalIdentifier"`,
);
await queryRunner.query(
`ALTER TABLE "core"."application" ADD CONSTRAINT "FK_eec488855d08b312a869a13ccb1" FOREIGN KEY ("serverlessFunctionLayerId") REFERENCES "core"."serverlessFunctionLayer"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
}
}

View File

@@ -15,20 +15,26 @@ import {
ServerlessFunctionManifest,
ServerlessFunctionTriggerManifest,
} from 'src/engine/core-modules/application/types/application.types';
import { ApplicationVariableService } from 'src/engine/core-modules/applicationVariable/application-variable.service';
import { AgentService } from 'src/engine/metadata-modules/agent/agent.service';
import { CronTriggerV2Service } from 'src/engine/metadata-modules/cron-trigger/services/cron-trigger-v2.service';
import { FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { DatabaseEventTriggerV2Service } from 'src/engine/metadata-modules/database-event-trigger/services/database-event-trigger-v2.service';
import { FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
import { ALL_METADATA_NAME } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-name.constant';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
import { getFlatEntitiesByApplicationId } from 'src/engine/metadata-modules/flat-entity/utils/get-flat-entities-by-application-id.util';
import { getSubFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/get-sub-flat-entity-maps-or-throw.util';
import { ObjectMetadataServiceV2 } from 'src/engine/metadata-modules/object-metadata/object-metadata-v2.service';
import { RouteTriggerV2Service } from 'src/engine/metadata-modules/route-trigger/services/route-trigger-v2.service';
import { FlatRouteTrigger } from 'src/engine/metadata-modules/route-trigger/types/flat-route-trigger.type';
import { ServerlessFunctionLayerService } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.service';
import { ServerlessFunctionV2Service } from 'src/engine/metadata-modules/serverless-function/services/serverless-function-v2.service';
import { FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
import { ApplicationVariableService } from 'src/engine/core-modules/applicationVariable/application-variable.service';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
@Injectable()
export class ApplicationSyncService {
@@ -46,6 +52,7 @@ export class ApplicationSyncService {
private readonly databaseEventTriggerV2Service: DatabaseEventTriggerV2Service,
private readonly cronTriggerV2Service: CronTriggerV2Service,
private readonly routeTriggerV2Service: RouteTriggerV2Service,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
) {}
public async synchronizeFromManifest({
@@ -795,4 +802,109 @@ export class ApplicationSyncService {
);
}
}
public async deleteApplication({
workspaceId,
applicationUniversalIdentifier,
}: {
workspaceId: string;
applicationUniversalIdentifier: string;
}) {
const {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatIndexMaps: existingFlatIndexMetadataMaps,
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
} = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: [
'flatObjectMetadataMaps',
'flatIndexMaps',
'flatFieldMetadataMaps',
],
},
);
const application = await this.applicationService.findByUniversalIdentifier(
applicationUniversalIdentifier,
workspaceId,
);
if (!isDefined(application)) {
throw new ApplicationException(
`Application with universalIdentifier ${applicationUniversalIdentifier} not found`,
ApplicationExceptionCode.ENTITY_NOT_FOUND,
);
}
const flatObjectMetadataMapsByApplicationId =
getFlatEntitiesByApplicationId(
existingFlatObjectMetadataMaps,
application.id,
);
const fromFlatObjectMetadataMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: flatObjectMetadataMapsByApplicationId.map((obj) => obj.id),
flatEntityMaps: existingFlatObjectMetadataMaps,
});
const flatIndexMetadataMapsByApplicationId = getFlatEntitiesByApplicationId(
existingFlatIndexMetadataMaps,
application.id,
);
const fromFlatIndexMetadataMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: flatIndexMetadataMapsByApplicationId.map(
(field) => field.id,
),
flatEntityMaps: existingFlatIndexMetadataMaps,
});
const flatFieldMetadataMapsByApplicationId = getFlatEntitiesByApplicationId(
existingFlatFieldMetadataMaps,
application.id,
);
const fromFlatFieldMetadataMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: flatFieldMetadataMapsByApplicationId.map((idx) => idx.id),
flatEntityMaps: existingFlatFieldMetadataMaps,
});
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatObjectMetadataMaps: {
from: fromFlatObjectMetadataMaps,
to: EMPTY_FLAT_ENTITY_MAPS,
},
flatIndexMaps: {
from: fromFlatIndexMetadataMaps,
to: EMPTY_FLAT_ENTITY_MAPS,
},
flatFieldMetadataMaps: {
from: fromFlatFieldMetadataMaps,
to: EMPTY_FLAT_ENTITY_MAPS,
},
},
workspaceId,
buildOptions: {
isSystemBuild: true,
inferDeletionFromMissingEntities: {
...Object.values(ALL_METADATA_NAME).reduce(
(acc, metadataName) => ({
...acc,
[metadataName]: true,
}),
{} as Partial<Record<AllMetadataName, boolean>>,
),
},
},
},
);
await this.applicationService.delete(
applicationUniversalIdentifier,
workspaceId,
);
}
}

View File

@@ -7,18 +7,16 @@ import {
JoinColumn,
ManyToOne,
OneToMany,
OneToOne,
PrimaryGeneratedColumn,
Relation,
UpdateDateColumn,
} from 'typeorm';
import { ApplicationVariable } from 'src/engine/core-modules/applicationVariable/application-variable.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ServerlessFunctionLayerEntity } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.entity';
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
import { ApplicationVariable } from 'src/engine/core-modules/applicationVariable/application-variable.entity';
@Entity({ name: 'application', schema: 'core' })
@Index('IDX_APPLICATION_WORKSPACE_ID', ['workspaceId'])
@@ -58,15 +56,11 @@ export class ApplicationEntity {
@Column({ nullable: false, type: 'uuid' })
serverlessFunctionLayerId: string;
@OneToOne(
() => ServerlessFunctionLayerEntity,
(serverlessFunctionLayer) => serverlessFunctionLayer.application,
{
onDelete: 'CASCADE',
},
)
@JoinColumn({ name: 'serverlessFunctionLayerId' })
serverlessFunctionLayer: Relation<ServerlessFunctionLayerEntity>;
@ManyToOne(() => Workspace, {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'workspaceId' })
workspace: Relation<Workspace>;
@OneToMany(() => AgentEntity, (agent) => agent.application, {
onDelete: 'CASCADE',
@@ -96,12 +90,6 @@ export class ApplicationEntity {
)
applicationVariables: Relation<ApplicationVariable[]>;
@ManyToOne(() => Workspace, {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'workspaceId' })
workspace: Relation<Workspace>;
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;

View File

@@ -5,6 +5,7 @@ import { ApplicationSyncService } from 'src/engine/core-modules/application/appl
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { ApplicationResolver } from 'src/engine/core-modules/application/application.resolver';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationVariableModule } from 'src/engine/core-modules/applicationVariable/application-variable.module';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity';
import { AgentModule } from 'src/engine/metadata-modules/agent/agent.module';
@@ -16,7 +17,7 @@ import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadat
import { RouteTriggerModule } from 'src/engine/metadata-modules/route-trigger/route-trigger.module';
import { ServerlessFunctionLayerModule } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.module';
import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module';
import { ApplicationVariableModule } from 'src/engine/core-modules/applicationVariable/application-variable.module';
import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module';
@Module({
imports: [
@@ -31,6 +32,7 @@ import { ApplicationVariableModule } from 'src/engine/core-modules/applicationVa
DatabaseEventTriggerModule,
CronTriggerModule,
RouteTriggerModule,
WorkspaceMigrationV2Module,
],
providers: [ApplicationResolver, ApplicationService, ApplicationSyncService],
})

View File

@@ -1,18 +1,18 @@
import { UseFilters, UseGuards } from '@nestjs/common';
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-sync.service';
import { ApplicationInput } from 'src/engine/core-modules/application/dtos/application.input';
import { DeleteApplicationInput } from 'src/engine/core-modules/application/dtos/deleteApplication.input';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { RequireFeatureFlag } from 'src/engine/guards/feature-flag.guard';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { ApplicationDTO } from 'src/engine/core-modules/application/dtos/application.dto';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { ApplicationExceptionFilter } from 'src/engine/core-modules/application/application-exception-filter';
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-sync.service';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationDTO } from 'src/engine/core-modules/application/dtos/application.dto';
import { ApplicationInput } from 'src/engine/core-modules/application/dtos/application.input';
import { DeleteApplicationInput } from 'src/engine/core-modules/application/dtos/deleteApplication.input';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { RequireFeatureFlag } from 'src/engine/guards/feature-flag.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
@UseGuards(WorkspaceAuthGuard)
@Resolver()
@@ -58,10 +58,10 @@ export class ApplicationResolver {
@Args() { packageJson }: DeleteApplicationInput,
@AuthWorkspace() { id: workspaceId }: Workspace,
) {
await this.applicationService.delete(
packageJson.universalIdentifier,
await this.applicationSyncService.deleteApplication({
applicationUniversalIdentifier: packageJson.universalIdentifier,
workspaceId,
);
});
return true;
}

View File

@@ -1,5 +1,6 @@
import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { In, Not, Repository } from 'typeorm';
import { ApplicationVariable } from 'src/engine/core-modules/applicationVariable/application-variable.entity';
@@ -37,6 +38,10 @@ export class ApplicationVariableService {
>;
applicationId: string;
}) {
if (!isDefined(env)) {
return;
}
for (const [key, { value, description, isSecret }] of Object.entries(env)) {
await this.applicationVariableRepository.upsert(
{

View File

@@ -12,9 +12,9 @@ import {
} from 'typeorm';
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface';
import { ModelId } from 'src/engine/core-modules/ai/constants/ai-models.const';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { ModelConfiguration } from 'src/engine/metadata-modules/agent/types/modelConfiguration';
@@ -27,12 +27,15 @@ import { AgentHandoffEntity } from './agent-handoff.entity';
unique: true,
where: '"deletedAt" IS NULL',
})
export class AgentEntity {
export class AgentEntity
extends SyncableEntity
implements Required<AgentEntity>
{
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ nullable: true, type: 'uuid' })
standardId?: string;
standardId: string | null;
@Column({ nullable: false })
name: string;
@@ -58,9 +61,6 @@ export class AgentEntity {
@Column({ nullable: false, type: 'uuid' })
workspaceId: string;
@Column({ nullable: true, type: 'uuid' })
applicationId: string | null;
@Column({ default: false })
isCustom: boolean;
@@ -70,13 +70,6 @@ export class AgentEntity {
@JoinColumn({ name: 'workspaceId' })
workspace: Relation<Workspace>;
@ManyToOne(() => ApplicationEntity, (application) => application.agents, {
onDelete: 'CASCADE',
nullable: true,
})
@JoinColumn({ name: 'applicationId' })
application: Relation<ApplicationEntity> | null;
@OneToMany(() => AgentChatThreadEntity, (chatThread) => chatThread.agent)
chatThreads: Relation<AgentChatThreadEntity[]>;
@@ -93,7 +86,7 @@ export class AgentEntity {
updatedAt: Date;
@DeleteDateColumn({ type: 'timestamptz' })
deletedAt?: Date;
deletedAt: Date | null;
@Column({ nullable: true, type: 'jsonb' })
modelConfiguration: ModelConfiguration;

View File

@@ -21,7 +21,7 @@ export class AgentDTO {
id: string;
@Field(() => UUIDScalarType, { nullable: true })
standardId?: string;
standardId: string | null;
@IsString()
@Field()

View File

@@ -19,4 +19,7 @@ export class CreateCronTriggerInput {
@HideField()
universalIdentifier?: string;
@HideField()
applicationId?: string;
}

View File

@@ -25,7 +25,10 @@ export const CRON_TRIGGER_ENTITY_RELATION_PROPERTIES = [
@Entity({ name: 'cronTrigger', schema: 'core' })
@Index('IDX_CRON_TRIGGER_WORKSPACE_ID', ['workspaceId'])
export class CronTrigger extends SyncableEntity {
export class CronTrigger
extends SyncableEntity
implements Required<CronTrigger>
{
@PrimaryGeneratedColumn('uuid')
id: string;

View File

@@ -14,6 +14,7 @@ import {
import { FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { WorkspaceFlatMapCache } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator';
import { WorkspaceFlatMapCacheService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service';
@@ -42,28 +43,19 @@ export class WorkspaceFlatCronTriggerMapCacheService extends WorkspaceFlatMapCac
},
});
const flatCronTriggerMaps = cronTriggers.reduce<
FlatEntityMaps<FlatCronTrigger>
>((flatEntityMaps, cronTrigger) => {
return cronTriggers.reduce((flatCronTriggerMaps, cronTriggerEntity) => {
const flatCronTrigger = {
...removePropertiesFromRecord(cronTrigger, [
...removePropertiesFromRecord(cronTriggerEntity, [
...CRON_TRIGGER_ENTITY_RELATION_PROPERTIES,
]),
universalIdentifier: cronTrigger.universalIdentifier ?? cronTrigger.id,
universalIdentifier:
cronTriggerEntity.universalIdentifier ?? cronTriggerEntity.id,
} satisfies FlatCronTrigger;
return {
byId: {
...flatEntityMaps.byId,
[flatCronTrigger.id]: flatCronTrigger,
},
idByUniversalIdentifier: {
...flatEntityMaps.idByUniversalIdentifier,
[flatCronTrigger.universalIdentifier]: flatCronTrigger.id,
},
};
return addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatCronTrigger,
flatEntityMaps: flatCronTriggerMaps,
});
}, EMPTY_FLAT_ENTITY_MAPS);
return flatCronTriggerMaps;
}
}

View File

@@ -1,5 +1,6 @@
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { type CronTrigger } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
@@ -9,9 +10,7 @@ export type CronTriggerEntityRelationProperties =
ServerlessFunctionEntity | Workspace
>;
export type FlatCronTrigger = Omit<
export type FlatCronTrigger = FlatEntityFrom<
CronTrigger,
CronTriggerEntityRelationProperties
> & {
universalIdentifier: string;
};
>;

View File

@@ -17,6 +17,7 @@ export const fromCreateCronTriggerInputToFlatCronTrigger = ({
universalIdentifier: createCronTriggerInput.universalIdentifier ?? v4(),
settings: createCronTriggerInput.settings,
serverlessFunctionId: createCronTriggerInput.serverlessFunctionId,
applicationId: createCronTriggerInput.applicationId ?? null,
workspaceId,
createdAt: now,
updatedAt: now,

View File

@@ -19,4 +19,7 @@ export class CreateDatabaseEventTriggerInput {
@HideField()
universalIdentifier?: string;
@HideField()
applicationId?: string;
}

View File

@@ -14,6 +14,7 @@ import {
import { FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { WorkspaceFlatMapCache } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator';
import { WorkspaceFlatMapCacheService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service';
@@ -43,29 +44,23 @@ export class WorkspaceFlatDatabaseEventTriggerMapCacheService extends WorkspaceF
},
});
const flatDatabaseEventTriggerMaps = databaseEventTriggers.reduce<
FlatEntityMaps<FlatDatabaseEventTrigger>
>((flatEntityMaps, databaseEventTrigger) => {
const flatDatabaseEventTrigger = {
...removePropertiesFromRecord(databaseEventTrigger, [
...DATABASE_EVENT_TRIGGER_ENTITY_RELATION_PROPERTIES,
]),
universalIdentifier: databaseEventTrigger.universalIdentifier ?? '',
} satisfies FlatDatabaseEventTrigger;
return databaseEventTriggers.reduce(
(flatDatabaseEventTriggerMaps, databaseEventTriggerEntity) => {
const flatDatabaseEventTrigger = {
...removePropertiesFromRecord(databaseEventTriggerEntity, [
...DATABASE_EVENT_TRIGGER_ENTITY_RELATION_PROPERTIES,
]),
universalIdentifier:
databaseEventTriggerEntity.universalIdentifier ??
databaseEventTriggerEntity.id,
} satisfies FlatDatabaseEventTrigger;
return {
byId: {
...flatEntityMaps.byId,
[flatDatabaseEventTrigger.id]: flatDatabaseEventTrigger,
},
idByUniversalIdentifier: {
...flatEntityMaps.idByUniversalIdentifier,
[flatDatabaseEventTrigger.universalIdentifier]:
flatDatabaseEventTrigger.id,
},
};
}, EMPTY_FLAT_ENTITY_MAPS);
return flatDatabaseEventTriggerMaps;
return addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatDatabaseEventTrigger,
flatEntityMaps: flatDatabaseEventTriggerMaps,
});
},
EMPTY_FLAT_ENTITY_MAPS,
);
}
}

View File

@@ -1,5 +1,6 @@
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { type DatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
@@ -9,9 +10,7 @@ export type DatabaseEventTriggerEntityRelationProperties =
ServerlessFunctionEntity | Workspace
>;
export type FlatDatabaseEventTrigger = Omit<
export type FlatDatabaseEventTrigger = FlatEntityFrom<
DatabaseEventTrigger,
DatabaseEventTriggerEntityRelationProperties
> & {
universalIdentifier: string;
};
>;

View File

@@ -21,5 +21,6 @@ export const fromCreateDatabaseEventTriggerInputToFlatDatabaseEventTrigger = ({
workspaceId,
createdAt: now,
updatedAt: now,
applicationId: createDatabaseEventTriggerInput.applicationId ?? null,
};
};

View File

@@ -18,6 +18,7 @@ import {
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface';
import { type FieldStandardOverridesDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-standard-overrides.dto';
import { AssignIfIsGivenFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/assign-if-is-given-field-metadata-type.type';
@@ -49,10 +50,11 @@ import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities
'objectMetadataId',
'workspaceId',
])
// TODO add some documentation about this entity
export class FieldMetadataEntity<
TFieldMetadataType extends FieldMetadataType = FieldMetadataType,
> implements Required<FieldMetadataEntity>
TFieldMetadataType extends FieldMetadataType = FieldMetadataType,
>
extends SyncableEntity
implements Required<FieldMetadataEntity>
{
@PrimaryGeneratedColumn('uuid')
id: string;

View File

@@ -1,4 +1,5 @@
import { type AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity';
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
export const agentEntityRelationProperties = [
'workspace',
@@ -11,9 +12,7 @@ export const agentEntityRelationProperties = [
export type AgentEntityRelationProperties =
(typeof agentEntityRelationProperties)[number];
export type FlatAgent = Omit<
export type FlatAgent = FlatEntityFrom<
AgentEntity,
AgentEntityRelationProperties | 'createdAt' | 'updatedAt' | 'deletedAt'
> & {
universalIdentifier: string;
};
>;

View File

@@ -4,4 +4,5 @@ import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/f
export const EMPTY_FLAT_ENTITY_MAPS = {
byId: {},
idByUniversalIdentifier: {},
universalIdentifiersByApplicationId: {},
} as const satisfies FlatEntityMaps<FlatEntity>;

View File

@@ -3,4 +3,5 @@ import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/f
export type FlatEntityMaps<T extends FlatEntity> = {
byId: Partial<Record<string, T>>;
idByUniversalIdentifier: Partial<Record<string, string>>;
universalIdentifiersByApplicationId: Partial<Record<string, string[]>>;
};

View File

@@ -1,4 +1,22 @@
export interface FlatEntity {
import { type SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface';
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
import { type NonNullableProperties } from 'src/types/non-nullable-properties.type';
export type SyncableEntityRelationProperties =
ExtractRecordTypeOrmRelationProperties<SyncableEntity, ApplicationEntity>;
export interface FlatEntity
extends NonNullableProperties<
Omit<SyncableEntity, SyncableEntityRelationProperties | 'applicationId'>
> {
id: string;
universalIdentifier: string;
applicationId: string | null;
}
export type FlatEntityFrom<
TEntity extends SyncableEntity,
TEntityRelationProperties extends keyof TEntity,
> = FlatEntity &
Omit<TEntity, TEntityRelationProperties | SyncableEntityRelationProperties>;

View File

@@ -1,4 +1,4 @@
import { isDefined } from 'class-validator';
import { isDefined } from 'twenty-shared/utils';
import {
FlatEntityMapsException,
@@ -32,5 +32,20 @@ export const addFlatEntityToFlatEntityMapsOrThrow = <T extends FlatEntity>({
...flatEntityMaps.idByUniversalIdentifier,
[flatEntity.universalIdentifier]: flatEntity.id,
},
universalIdentifiersByApplicationId: {
...flatEntityMaps.universalIdentifiersByApplicationId,
...(isDefined(flatEntity.applicationId)
? {
[flatEntity.applicationId]: Array.from(
new Set([
...(flatEntityMaps.universalIdentifiersByApplicationId?.[
flatEntity.applicationId
] ?? []),
flatEntity.universalIdentifier,
]),
),
}
: {}),
},
};
};

View File

@@ -1,3 +1,4 @@
import isEmpty from 'lodash.isempty';
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
import {
@@ -31,10 +32,36 @@ export const deleteFlatEntityFromFlatEntityMapsOrThrow = <
flatEntityMaps.idByUniversalIdentifier,
).filter(([_universalIdentifier, id]) => id !== entityToDeleteId);
const updatedUniversalIdentifiersByApplicationIdEntries = Object.entries(
flatEntityMaps.universalIdentifiersByApplicationId,
)
.map(([applicationId, universalIdentifiers]) => {
const stillPresentUniversalIdentifiers = universalIdentifiers?.filter(
(universalIdentifier) =>
updatedIdByUniversalIdentifierEntries.some(
([existingUniversalIdentifier]) =>
existingUniversalIdentifier === universalIdentifier,
),
);
if (
isDefined(stillPresentUniversalIdentifiers) ||
isEmpty(stillPresentUniversalIdentifiers)
) {
return undefined;
}
return [applicationId, stillPresentUniversalIdentifiers];
})
.filter(isDefined);
return {
byId: removePropertiesFromRecord(flatEntityMaps.byId, [entityToDeleteId]),
idByUniversalIdentifier: Object.fromEntries(
updatedIdByUniversalIdentifierEntries,
),
universalIdentifiersByApplicationId: Object.fromEntries(
updatedUniversalIdentifiersByApplicationIdEntries,
),
};
};

View File

@@ -0,0 +1,38 @@
import { isDefined } from 'twenty-shared/utils';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
export const getFlatEntitiesByApplicationId = <T extends FlatEntity>(
maps: FlatEntityMaps<T>,
applicationId: string,
): T[] => {
const universalIdentifiers =
maps.universalIdentifiersByApplicationId[applicationId];
if (!isDefined(universalIdentifiers)) {
return [];
}
return universalIdentifiers
.map((universalId) => {
const id = maps.idByUniversalIdentifier[universalId];
if (!isDefined(id)) {
return undefined;
}
const entity = maps.byId[id];
if (!isDefined(entity)) {
return undefined;
}
if (entity.applicationId !== applicationId) {
return undefined;
}
return entity;
})
.filter(isDefined);
};

View File

@@ -43,7 +43,7 @@ export const getFlatFieldMetadataMock = <T extends FieldMetadataType>(
standardId: null,
standardOverrides: null,
workspaceId: faker.string.uuid(),
applicationId: faker.string.uuid(),
relationTargetFieldMetadataId: null,
relationTargetObjectMetadataId: null,
...overrides,

View File

@@ -62,5 +62,6 @@ export const getRelationTargetFlatFieldMetadataMock = ({
...overrides,
defaultValue: null,
options: null,
applicationId: faker.string.uuid(),
};
};

View File

@@ -104,6 +104,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"result": {
"flatFieldMetadatas": [
{
"applicationId": null,
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": "new field description",
@@ -137,6 +138,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"workspaceId": Any<String>,
},
{
"applicationId": null,
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": null,
@@ -172,6 +174,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"workspaceId": Any<String>,
},
{
"applicationId": null,
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": "new field description",
@@ -205,6 +208,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"workspaceId": Any<String>,
},
{
"applicationId": null,
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": null,
@@ -242,6 +246,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
],
"indexMetadatas": [
{
"applicationId": null,
"createdAt": Any<ClockDate>,
"flatIndexFieldMetadatas": [
{
@@ -265,6 +270,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"workspaceId": Any<String>,
},
{
"applicationId": null,
"createdAt": Any<ClockDate>,
"flatIndexFieldMetadatas": [
{

View File

@@ -9,6 +9,7 @@ export const FIELD_METADATA_RELATION_PROPERTIES = [
'indexFieldMetadatas',
'object',
'viewFields',
'application',
'viewFilters',
'viewGroups',
] as const satisfies (keyof FieldMetadataEntity)[];

View File

@@ -44,6 +44,7 @@ export const generateIndexForFlatFieldMetadata = ({
universalIdentifier: indexId,
updatedAt: createdAt,
workspaceId,
applicationId: null,
},
flatObjectMetadata,
},

View File

@@ -54,6 +54,7 @@ export const getDefaultFlatFieldMetadata = ({
updatedAt: createdAt,
isUIReadOnly: createFieldInput.isUIReadOnly ?? false,
morphId: null,
applicationId: null,
viewFilterIds: [],
viewGroupIds: [],
} as const satisfies FlatFieldMetadata;

View File

@@ -71,6 +71,7 @@ export const recomputeViewGroupsOnEnumFlatFieldMetadataIsNullableUpdate = ({
updatedAt: createdAt,
deletedAt: null,
viewId,
applicationId: toFlatFieldMetadata.applicationId,
});
} else if (isDefined(emptyValueFlatViewGroup)) {
sideEffectResult.flatViewGroupsToDelete.push(emptyValueFlatViewGroup);

View File

@@ -109,6 +109,7 @@ export const recomputeViewGroupsOnFlatFieldMetadataOptionsUpdate = ({
isVisible: true,
fieldValue: option.value,
position: viewGroupHighestPosition + createdOptionIndex + 1,
applicationId: fromFlatFieldMetadata.applicationId,
};
}),
);

View File

@@ -23,6 +23,7 @@ export const getFlatIndexMetadataMock = (
name: 'defaultFlatIndexMetadataName',
updatedAt: createdAt,
workspaceId: faker.string.uuid(),
applicationId: faker.string.uuid(),
...overrides,
};
};

View File

@@ -1,3 +1,4 @@
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { type IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
import { type IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
@@ -9,6 +10,7 @@ export type IndexMetadataRelationProperties =
MetadataEntitiesRelationTarget
>;
// Can't use FlatEntityFrom here because IndexFieldMetadataEntity is not a SyncableEntity
export type FlatIndexFieldMetadata = Omit<
IndexFieldMetadataEntity,
ExtractRecordTypeOrmRelationProperties<
@@ -17,7 +19,7 @@ export type FlatIndexFieldMetadata = Omit<
>
>;
export type FlatIndexMetadata = Omit<
export type FlatIndexMetadata = FlatEntityFrom<
IndexMetadataEntity,
IndexMetadataRelationProperties
> & {

View File

@@ -8,6 +8,7 @@ import { CacheStorageService } from 'src/engine/core-modules/cache-storage/servi
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { fromObjectMetadataEntityToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@@ -53,24 +54,17 @@ export class WorkspaceFlatObjectMetadataMapCacheService extends WorkspaceFlatMap
relations: ['fields', 'indexMetadatas', 'views'],
});
const flatObjectMetadataMaps = objectMetadatas.reduce<
FlatEntityMaps<FlatObjectMetadata>
>((flatEntityMaps, object) => {
const flatObjectMetadata =
fromObjectMetadataEntityToFlatObjectMetadata(object);
return objectMetadatas.reduce(
(flatObjectMetadataMaps, objectMetadataEntity) => {
const flatObjectMetadata =
fromObjectMetadataEntityToFlatObjectMetadata(objectMetadataEntity);
return {
byId: {
...flatEntityMaps.byId,
[flatObjectMetadata.id]: flatObjectMetadata,
},
idByUniversalIdentifier: {
...flatEntityMaps.idByUniversalIdentifier,
[flatObjectMetadata.universalIdentifier]: flatObjectMetadata.id,
},
};
}, EMPTY_FLAT_ENTITY_MAPS);
return flatObjectMetadataMaps;
return addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatObjectMetadata,
flatEntityMaps: flatObjectMetadataMaps,
});
},
EMPTY_FLAT_ENTITY_MAPS,
);
}
}

View File

@@ -1,3 +1,4 @@
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
import { type MetadataEntitiesRelationTarget } from 'src/engine/workspace-manager/workspace-migration-v2/types/metadata-entities-relation-targets.type';
@@ -18,11 +19,10 @@ type ObjectMetadataRelationProperties = ExtractRecordTypeOrmRelationProperties<
MetadataEntitiesRelationTarget
>;
export type FlatObjectMetadata = Omit<
export type FlatObjectMetadata = FlatEntityFrom<
ObjectMetadataEntity,
ObjectMetadataRelationProperties | 'dataSourceId'
> & {
universalIdentifier: string;
fieldMetadataIds: string[];
indexMetadataIds: string[];
viewIds: string[];

View File

@@ -48,6 +48,7 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre
buildDefaultFlatFieldMetadatasForCustomObject({
flatObjectMetadata: {
id: objectMetadataId,
applicationId: createObjectInput.applicationId ?? null,
},
workspaceId,
});

View File

@@ -66,6 +66,7 @@ export const recomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdate = ({
deletedAt: null,
universalIdentifier: viewFieldId,
aggregateOperation: null,
applicationId: existingFlatObjectMetadata.applicationId,
};
accumulator.flatViewFieldToCreate.push(flatViewFieldToCreate);

View File

@@ -1,3 +1,4 @@
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { type RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
export const roleEntityRelationProperties = [
@@ -10,9 +11,7 @@ export const roleEntityRelationProperties = [
export type RoleEntityRelationProperties =
(typeof roleEntityRelationProperties)[number];
export type FlatRole = Omit<
export type FlatRole = FlatEntityFrom<
RoleEntity,
RoleEntityRelationProperties | 'createdAt' | 'updatedAt'
> & {
universalIdentifier: string;
};
>;

View File

@@ -20,5 +20,6 @@ export const fromRoleEntityToFlatRole = (role: RoleEntity): FlatRole => {
canBeAssignedToApiKeys: role.canBeAssignedToApiKeys,
workspaceId: role.workspaceId,
universalIdentifier: role.standardId || role.id,
applicationId: role.applicationId ?? null,
};
};

View File

@@ -1,5 +1,6 @@
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { type ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
@@ -10,9 +11,7 @@ export type ViewFieldEntityRelationProperties =
FieldMetadataEntity | ViewEntity | Workspace
>;
export type FlatViewField = Omit<
export type FlatViewField = FlatEntityFrom<
ViewFieldEntity,
ViewFieldEntityRelationProperties
> & {
universalIdentifier: string;
};
>;

View File

@@ -29,10 +29,12 @@ export const fromCreateViewFieldInputToFlatViewFieldToCreate = ({
createdAt: createdAt,
updatedAt: createdAt,
deletedAt: null,
universalIdentifier: viewFieldId,
universalIdentifier:
createViewFieldInput.universalIdentifier ?? viewFieldId,
isVisible: createViewFieldInput.isVisible ?? true,
size: createViewFieldInput.size ?? DEFAULT_VIEW_FIELD_SIZE,
position: createViewFieldInput.position ?? 0,
aggregateOperation: createViewFieldInput.aggregateOperation ?? null,
applicationId: createViewFieldInput.applicationId ?? null,
};
};

View File

@@ -1,5 +1,6 @@
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { type ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity';
import { type ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
@@ -11,9 +12,7 @@ export type ViewFilterEntityRelationProperties =
FieldMetadataEntity | ViewEntity | ViewFilterGroupEntity | Workspace
>;
export type FlatViewFilter = Omit<
export type FlatViewFilter = FlatEntityFrom<
ViewFilterEntity,
ViewFilterEntityRelationProperties
> & {
universalIdentifier: string;
};
>;

View File

@@ -36,12 +36,14 @@ export const fromCreateViewFilterInputToFlatViewFilterToCreate = ({
createdAt: createdAt,
updatedAt: createdAt,
deletedAt: null,
universalIdentifier: viewFilterId,
universalIdentifier:
createViewFilterInput.universalIdentifier ?? viewFilterId,
operand: createViewFilterInput.operand ?? ViewFilterOperand.CONTAINS,
value: value,
viewFilterGroupId: createViewFilterInput.viewFilterGroupId ?? null,
positionInViewFilterGroup:
createViewFilterInput.positionInViewFilterGroup ?? null,
subFieldName: createViewFilterInput.subFieldName ?? null,
applicationId: createViewFilterInput.applicationId ?? null,
};
};

View File

@@ -1,5 +1,6 @@
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { type ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity';
import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
@@ -10,9 +11,7 @@ export type ViewGroupEntityRelationProperties =
FieldMetadataEntity | ViewEntity | Workspace
>;
export type FlatViewGroup = Omit<
export type FlatViewGroup = FlatEntityFrom<
ViewGroupEntity,
ViewGroupEntityRelationProperties
> & {
universalIdentifier: string;
};
>;

View File

@@ -28,9 +28,11 @@ export const fromCreateViewGroupInputToFlatViewGroupToCreate = ({
createdAt: createdAt,
updatedAt: createdAt,
deletedAt: null,
universalIdentifier: viewGroupId,
universalIdentifier:
createViewGroupInput.universalIdentifier ?? viewGroupId,
isVisible: createViewGroupInput.isVisible ?? true,
fieldValue: createViewGroupInput.fieldValue,
position: createViewGroupInput.position ?? 0,
applicationId: createViewGroupInput.applicationId ?? null,
};
};

View File

@@ -1,4 +1,5 @@
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
import { type ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity';
@@ -20,8 +21,10 @@ export type ViewEntityRelationProperties =
| Workspace
>;
export type FlatView = Omit<ViewEntity, ViewEntityRelationProperties> & {
universalIdentifier: string;
export type FlatView = FlatEntityFrom<
ViewEntity,
ViewEntityRelationProperties
> & {
viewFieldIds: string[];
viewFilterIds: string[];
viewGroupIds: string[];

View File

@@ -20,9 +20,10 @@ export const fromCreateViewInputToFlatViewToCreate = ({
);
const createdAt = new Date();
const viewId = createViewInput.id ?? v4();
return {
id: createViewInput.id ?? v4(),
id: viewId,
objectMetadataId,
workspaceId,
name: createViewInput.name,
@@ -42,9 +43,10 @@ export const fromCreateViewInputToFlatViewToCreate = ({
openRecordIn: createViewInput.openRecordIn ?? ViewOpenRecordIn.SIDE_PANEL,
position: createViewInput.position ?? 0,
type: createViewInput.type ?? ViewType.TABLE,
universalIdentifier: v4(),
universalIdentifier: createViewInput.universalIdentifier ?? viewId,
viewFieldIds: [],
viewFilterIds: [],
viewGroupIds: [],
applicationId: createViewInput.applicationId ?? null,
};
};

View File

@@ -2,7 +2,6 @@ import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
@@ -11,8 +10,9 @@ import {
UpdateDateColumn,
} from 'typeorm';
import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface';
import { type WorkspaceEntityDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/types/workspace-entity-duplicate-criteria.type';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
@@ -30,16 +30,16 @@ import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entit
'namePlural',
'workspaceId',
])
export class ObjectMetadataEntity implements Required<ObjectMetadataEntity> {
export class ObjectMetadataEntity
extends SyncableEntity
implements Required<ObjectMetadataEntity>
{
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ nullable: true, type: 'uuid' })
standardId: string | null;
@Column({ nullable: true, type: 'uuid' })
applicationId: string | null;
@Column({ nullable: false, type: 'uuid' })
dataSourceId: string;
@@ -137,13 +137,6 @@ export class ObjectMetadataEntity implements Required<ObjectMetadataEntity> {
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt: Date;
@ManyToOne(() => ApplicationEntity, (application) => application.objects, {
onDelete: 'CASCADE',
nullable: true,
})
@JoinColumn({ name: 'applicationId' })
application: Relation<ApplicationEntity> | null;
@OneToMany(
() => ObjectPermissionEntity,
(objectPermission) => objectPermission.objectMetadata,

View File

@@ -12,7 +12,7 @@ import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-mana
type BuildDefaultFlatFieldMetadataForCustomObjectArgs = {
workspaceId: string;
flatObjectMetadata: Pick<FlatObjectMetadata, 'id'>;
flatObjectMetadata: Pick<FlatObjectMetadata, 'id' | 'applicationId'>;
};
export type DefaultFlatFieldForCustomObjectMaps = ReturnType<
@@ -21,7 +21,7 @@ export type DefaultFlatFieldForCustomObjectMaps = ReturnType<
// This could be replaced totally by an import schema + its transpilation when it's ready
export const buildDefaultFlatFieldMetadatasForCustomObject = ({
workspaceId,
flatObjectMetadata: { id: objectMetadataId },
flatObjectMetadata: { id: objectMetadataId, applicationId },
}: BuildDefaultFlatFieldMetadataForCustomObjectArgs) => {
const createdAt = new Date();
const idField: FlatFieldMetadata<FieldMetadataType.UUID> = {
@@ -58,6 +58,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
relationTargetObjectMetadataId: null,
settings: null,
morphId: null,
applicationId: applicationId ?? null,
};
const nameField: FlatFieldMetadata<FieldMetadataType.TEXT> = {
@@ -94,6 +95,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
relationTargetObjectMetadataId: null,
settings: null,
morphId: null,
applicationId: applicationId ?? null,
};
const createdAtField: FlatFieldMetadata<FieldMetadataType.DATE_TIME> = {
@@ -130,6 +132,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
relationTargetObjectMetadataId: null,
settings: null,
morphId: null,
applicationId: applicationId ?? null,
};
const updatedAtField: FlatFieldMetadata<FieldMetadataType.DATE_TIME> = {
@@ -166,6 +169,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
relationTargetObjectMetadataId: null,
settings: null,
morphId: null,
applicationId: applicationId ?? null,
};
const deletedAtField: FlatFieldMetadata<FieldMetadataType.DATE_TIME> = {
@@ -202,6 +206,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
relationTargetObjectMetadataId: null,
settings: null,
morphId: null,
applicationId: applicationId ?? null,
};
const createdByField: FlatFieldMetadata<FieldMetadataType.ACTOR> = {
@@ -237,6 +242,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
relationTargetObjectMetadataId: null,
settings: null,
morphId: null,
applicationId: applicationId ?? null,
};
const positionField: FlatFieldMetadata<FieldMetadataType.POSITION> = {
@@ -273,6 +279,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
relationTargetObjectMetadataId: null,
settings: null,
morphId: null,
applicationId: applicationId ?? null,
};
const searchVectorField: FlatFieldMetadata<FieldMetadataType.TS_VECTOR> = {
@@ -312,6 +319,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
generatedType: 'STORED',
},
morphId: null,
applicationId: applicationId ?? null,
};
return {

View File

@@ -44,6 +44,7 @@ export const buildDefaultIndexesForCustomObject = ({
universalIdentifier: tsFlatVectorIndexId,
updatedAt: createdAt,
workspaceId,
applicationId: flatObjectMetadata.applicationId ?? null,
},
flatObjectMetadata,
});

View File

@@ -97,6 +97,7 @@ const generateSourceFlatFieldMetadata = ({
]),
workspaceId,
morphId: null,
applicationId: sourceFlatObjectMetadata.applicationId ?? null,
};
};
@@ -165,6 +166,7 @@ const generateTargetFlatFieldMetadata = ({
targetFlatObjectMetadata.id,
standardId,
]),
applicationId: sourceFlatObjectMetadata.applicationId ?? null,
};
};

View File

@@ -9,6 +9,8 @@ import {
UpdateDateColumn,
} from 'typeorm';
import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface';
import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity';
import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity';
@@ -16,7 +18,7 @@ import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets
@Entity('role')
@Unique('IDX_ROLE_LABEL_WORKSPACE_ID_UNIQUE', ['label', 'workspaceId'])
export class RoleEntity {
export class RoleEntity extends SyncableEntity {
@PrimaryGeneratedColumn('uuid')
id: string;

View File

@@ -34,4 +34,7 @@ export class CreateRouteTriggerInput {
@HideField()
universalIdentifier?: string;
@HideField()
applicationId?: string;
}

View File

@@ -9,6 +9,7 @@ import { CacheStorageService } from 'src/engine/core-modules/cache-storage/servi
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import {
ROUTE_TRIGGER_ENTITY_RELATION_PROPERTIES,
RouteTrigger,
@@ -42,28 +43,19 @@ export class WorkspaceFlatRouteTriggerMapCacheService extends WorkspaceFlatMapCa
},
});
const flatRouteTriggerMaps = routeTriggers.reduce<
FlatEntityMaps<FlatRouteTrigger>
>((flatEntityMaps, routeTrigger) => {
return routeTriggers.reduce((flatRouteTriggerMaps, routeTriggerEntity) => {
const flatRouteTrigger = {
...removePropertiesFromRecord(routeTrigger, [
...removePropertiesFromRecord(routeTriggerEntity, [
...ROUTE_TRIGGER_ENTITY_RELATION_PROPERTIES,
]),
universalIdentifier: routeTrigger.universalIdentifier ?? '',
universalIdentifier:
routeTriggerEntity.universalIdentifier ?? routeTriggerEntity.id,
} satisfies FlatRouteTrigger;
return {
byId: {
...flatEntityMaps.byId,
[flatRouteTrigger.id]: flatRouteTrigger,
},
idByUniversalIdentifier: {
...flatEntityMaps.idByUniversalIdentifier,
[flatRouteTrigger.universalIdentifier]: flatRouteTrigger.id,
},
};
return addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatRouteTrigger,
flatEntityMaps: flatRouteTriggerMaps,
});
}, EMPTY_FLAT_ENTITY_MAPS);
return flatRouteTriggerMaps;
}
}

View File

@@ -1,4 +1,5 @@
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { type RouteTrigger } from 'src/engine/metadata-modules/route-trigger/route-trigger.entity';
import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
@@ -9,9 +10,7 @@ export type RouteTriggerEntityRelationProperties =
ServerlessFunctionEntity | Workspace
>;
export type FlatRouteTrigger = Omit<
export type FlatRouteTrigger = FlatEntityFrom<
RouteTrigger,
RouteTriggerEntityRelationProperties
> & {
universalIdentifier: string;
};
>;

View File

@@ -23,5 +23,6 @@ export const fromCreateRouteTriggerInputToFlatRouteTrigger = ({
workspaceId,
createdAt: now,
updatedAt: now,
applicationId: createRouteTriggerInput.applicationId ?? null,
};
};

View File

@@ -3,7 +3,6 @@ import {
CreateDateColumn,
Entity,
OneToMany,
OneToOne,
PrimaryGeneratedColumn,
Relation,
UpdateDateColumn,
@@ -11,7 +10,6 @@ import {
import { PackageJson } from 'src/engine/core-modules/application/types/application.types';
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
@Entity('serverlessFunctionLayer')
export class ServerlessFunctionLayerEntity {
@@ -39,15 +37,6 @@ export class ServerlessFunctionLayerEntity {
)
serverlessFunctions: Relation<ServerlessFunctionEntity[]>;
@OneToOne(
() => ApplicationEntity,
(application) => application.serverlessFunctionLayer,
{
nullable: true,
},
)
application: Relation<ApplicationEntity> | null;
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;

View File

@@ -18,9 +18,8 @@ import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/inte
import { CronTrigger } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
import { DatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import { RouteTrigger } from 'src/engine/metadata-modules/route-trigger/route-trigger.entity';
import { ServerlessFunctionEntityRelationProperties } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { ServerlessFunctionLayerEntity } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.entity';
import { ServerlessFunctionEntityRelationProperties } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
const DEFAULT_SERVERLESS_TIMEOUT_SECONDS = 300; // 5 minutes
@@ -66,9 +65,6 @@ export class ServerlessFunctionEntity
@Column({ nullable: false, type: 'uuid' })
workspaceId: string;
@Column({ nullable: true, type: 'uuid' })
applicationId: string | null;
@Column({ nullable: true, type: 'text' })
checksum: string | null;
@@ -84,17 +80,6 @@ export class ServerlessFunctionEntity
@JoinColumn({ name: 'serverlessFunctionLayerId' })
serverlessFunctionLayer: Relation<ServerlessFunctionLayerEntity>;
@ManyToOne(
() => ApplicationEntity,
(application) => application.serverlessFunctions,
{
onDelete: 'CASCADE',
nullable: true,
},
)
@JoinColumn({ name: 'applicationId' })
application: Relation<ApplicationEntity> | null;
@OneToMany(
() => CronTrigger,
(cronTrigger) => cronTrigger.serverlessFunction,

View File

@@ -9,6 +9,7 @@ import { CacheStorageService } from 'src/engine/core-modules/cache-storage/servi
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import {
SERVERLESS_FUNCTION_ENTITY_RELATION_PROPERTIES,
ServerlessFunctionEntity,
@@ -43,29 +44,23 @@ export class WorkspaceFlatServerlessFunctionMapCacheService extends WorkspaceFla
withDeleted: true,
});
const flatServerlessFunctionMaps = serverlessFunctions.reduce<
FlatEntityMaps<FlatServerlessFunction>
>((flatEntityMaps, serverlessFunction) => {
const flatServerlessFunction = {
...removePropertiesFromRecord(serverlessFunction, [
...SERVERLESS_FUNCTION_ENTITY_RELATION_PROPERTIES,
]),
universalIdentifier: serverlessFunction.universalIdentifier ?? '',
} satisfies FlatServerlessFunction;
return serverlessFunctions.reduce(
(flatServerlessFunctionMaps, serverlessFunctionEntity) => {
const flatServerlessFunction = {
...removePropertiesFromRecord(serverlessFunctionEntity, [
...SERVERLESS_FUNCTION_ENTITY_RELATION_PROPERTIES,
]),
universalIdentifier:
serverlessFunctionEntity.universalIdentifier ??
serverlessFunctionEntity.id,
} satisfies FlatServerlessFunction;
return {
byId: {
...flatEntityMaps.byId,
[flatServerlessFunction.id]: flatServerlessFunction,
},
idByUniversalIdentifier: {
...flatEntityMaps.idByUniversalIdentifier,
[flatServerlessFunction.universalIdentifier]:
flatServerlessFunction.id,
},
};
}, EMPTY_FLAT_ENTITY_MAPS);
return flatServerlessFunctionMaps;
return addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatServerlessFunction,
flatEntityMaps: flatServerlessFunctionMaps,
});
},
EMPTY_FLAT_ENTITY_MAPS,
);
}
}

View File

@@ -1,12 +1,12 @@
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { type CronTrigger } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
import { type DatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { type RouteTrigger } from 'src/engine/metadata-modules/route-trigger/route-trigger.entity';
import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { type ServerlessFunctionLayerEntity } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.entity';
import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
import { type ServerlessFunctionCode } from 'src/engine/metadata-modules/serverless-function/types/serverless-function-code.type';
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
export type ServerlessFunctionEntityRelationProperties =
ExtractRecordTypeOrmRelationProperties<
@@ -15,14 +15,12 @@ export type ServerlessFunctionEntityRelationProperties =
| DatabaseEventTrigger
| RouteTrigger
| Workspace
| ApplicationEntity
| ServerlessFunctionLayerEntity
>;
export type FlatServerlessFunction = Omit<
export type FlatServerlessFunction = FlatEntityFrom<
ServerlessFunctionEntity,
ServerlessFunctionEntityRelationProperties
> & {
universalIdentifier: string;
code?: ServerlessFunctionCode;
};

View File

@@ -1,4 +1,4 @@
import { Field, InputType } from '@nestjs/graphql';
import { Field, HideField, InputType } from '@nestjs/graphql';
import {
IsBoolean,
@@ -45,4 +45,10 @@ export class CreateViewFieldInput {
@IsEnum(AggregateOperations)
@Field(() => AggregateOperations, { nullable: true })
aggregateOperation?: AggregateOperations;
@HideField()
universalIdentifier?: string;
@HideField()
applicationId?: string;
}

View File

@@ -1,7 +1,5 @@
import { Field, InputType } from '@nestjs/graphql';
import { Field, HideField, InputType } from '@nestjs/graphql';
import GraphQLJSON from 'graphql-type-json';
import { ViewFilterOperand } from 'twenty-shared/types';
import {
IsDefined,
IsEnum,
@@ -10,6 +8,8 @@ import {
IsString,
IsUUID,
} from 'class-validator';
import GraphQLJSON from 'graphql-type-json';
import { ViewFilterOperand } from 'twenty-shared/types';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { ViewFilterValue } from 'src/engine/metadata-modules/view-filter/types/view-filter-value.type';
@@ -52,4 +52,10 @@ export class CreateViewFilterInput {
@IsUUID()
@Field(() => UUIDScalarType, { nullable: false })
viewId: string;
@HideField()
universalIdentifier?: string;
@HideField()
applicationId?: string;
}

View File

@@ -1,4 +1,4 @@
import { Field, InputType } from '@nestjs/graphql';
import { Field, HideField, InputType } from '@nestjs/graphql';
import {
IsBoolean,
@@ -38,4 +38,10 @@ export class CreateViewGroupInput {
@IsUUID()
@Field(() => UUIDScalarType, { nullable: false })
viewId: string;
@HideField()
universalIdentifier?: string;
@HideField()
applicationId?: string;
}

View File

@@ -22,7 +22,10 @@ import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entit
@Index('IDX_VIEW_GROUP_VIEW_ID', ['viewId'], {
where: '"deletedAt" IS NULL',
})
export class ViewGroupEntity extends SyncableEntity {
export class ViewGroupEntity
extends SyncableEntity
implements Required<ViewGroupEntity>
{
@PrimaryGeneratedColumn('uuid')
id: string;

View File

@@ -1,4 +1,4 @@
import { Field, InputType } from '@nestjs/graphql';
import { Field, HideField, InputType } from '@nestjs/graphql';
import {
IsBoolean,
@@ -90,4 +90,10 @@ export class CreateViewInput {
@IsUUID()
@Field(() => UUIDScalarType, { nullable: true })
calendarFieldMetadataId?: string;
@HideField()
universalIdentifier?: string;
@HideField()
applicationId?: string;
}

View File

@@ -127,6 +127,14 @@ export class WorkspaceMigrationBuildOrchestratorService {
...flatFieldMetadataMaps?.from.idByUniversalIdentifier,
...flatFieldMetadataMaps?.to.idByUniversalIdentifier,
},
universalIdentifiersByApplicationId: {
...dependencyAllFlatEntityMaps?.flatFieldMetadataMaps
?.universalIdentifiersByApplicationId,
...flatFieldMetadataMaps?.from
.universalIdentifiersByApplicationId,
...flatFieldMetadataMaps?.to
.universalIdentifiersByApplicationId,
},
},
},
///

View File

@@ -8,6 +8,8 @@ export type PartialIndexMetadata = Omit<
| 'createdAt'
| 'updatedAt'
| 'universalIdentifier'
| 'application'
| 'applicationId'
> & {
columns: string[];
};

View File

@@ -15,4 +15,5 @@ export const ADMIN_ROLE: StandardRoleDefinition = {
canBeAssignedToUsers: true,
canBeAssignedToAgents: false,
canBeAssignedToApiKeys: true,
applicationId: null, // TODO: Replace with Twenty application ID
};

View File

@@ -15,4 +15,5 @@ export const DATA_MANIPULATOR_ROLE: StandardRoleDefinition = {
canBeAssignedToUsers: false,
canBeAssignedToAgents: true,
canBeAssignedToApiKeys: false,
applicationId: null, // TODO: Replace with Twenty application ID
};

View File

@@ -15,4 +15,5 @@ export const DATA_NAVIGATOR_ROLE: StandardRoleDefinition = {
canBeAssignedToUsers: false,
canBeAssignedToAgents: true,
canBeAssignedToApiKeys: false,
applicationId: null, // TODO: Replace with Twenty application ID
};

View File

@@ -17,4 +17,5 @@ export const WORKFLOW_MANAGER_ROLE: StandardRoleDefinition = {
canBeAssignedToAgents: true,
canBeAssignedToApiKeys: false,
permissionFlags: [PermissionFlagType.WORKFLOWS],
applicationId: null, // TODO: Replace with Twenty application ID
};

View File

@@ -1,4 +1,6 @@
import { Column, Index } from 'typeorm';
import { Column, Index, JoinColumn, ManyToOne, Relation } from 'typeorm';
import type { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
@Index(['workspaceId', 'universalIdentifier'], {
unique: true,
@@ -6,4 +8,14 @@ import { Column, Index } from 'typeorm';
export abstract class SyncableEntity {
@Column({ nullable: true, type: 'uuid' })
universalIdentifier: string | null;
@Column({ nullable: true, type: 'uuid' })
applicationId: string | null;
@ManyToOne('ApplicationEntity', {
onDelete: 'CASCADE',
nullable: true,
})
@JoinColumn({ name: 'applicationId' })
application: Relation<ApplicationEntity>;
}

View File

@@ -0,0 +1,3 @@
export type NonNullableProperties<T> = {
[P in keyof T]: NonNullable<T[P]>;
};

View File

@@ -1,6 +1,7 @@
import { faker } from '@faker-js/faker';
import { type FieldMetadataType } from 'twenty-shared/types';
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { type IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@@ -49,6 +50,9 @@ export const getMockFieldMetadataEntity = <
createdAt: new Date(),
updatedAt: new Date(),
isActive: true,
application: {} as ApplicationEntity,
applicationId: faker.string.uuid(),
universalIdentifier: faker.string.uuid(),
...overrides,
};
};

View File

@@ -1,5 +1,6 @@
import { faker } from '@faker-js/faker';
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { type DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@@ -41,8 +42,9 @@ export const getMockObjectMetadataEntity = (
objectPermissions: [],
shortcut: null,
standardId: null,
applicationId: null,
application: null,
universalIdentifier: faker.string.uuid(),
applicationId: faker.string.uuid(),
application: {} as ApplicationEntity,
targetRelationFields: [],
standardOverrides: null,
targetTableName: faker.string.uuid(),

View File

@@ -5,6 +5,7 @@ import { type Repository } from 'typeorm';
import { ToolAdapterService } from 'src/engine/core-modules/ai/services/tool-adapter.service';
import { ToolService } from 'src/engine/core-modules/ai/services/tool.service';
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { CreateRecordService } from 'src/engine/core-modules/record-crud/services/create-record.service';
import { DeleteRecordService } from 'src/engine/core-modules/record-crud/services/delete-record.service';
import { FindRecordsService } from 'src/engine/core-modules/record-crud/services/find-records.service';
@@ -210,7 +211,10 @@ export const createAgentToolTestModule =
icon: 'IconTest',
isCustom: false,
applicationId: null,
application: null,
application: {} as ApplicationEntity,
standardId: null,
deletedAt: null,
universalIdentifier: testAgentId,
description: 'Test agent for integration tests',
prompt: 'You are a test agent',
modelId: 'gpt-4o',