diff --git a/packages/twenty-oxlint-rules/rules/upgrade-command-filename.spec.ts b/packages/twenty-oxlint-rules/rules/upgrade-command-filename.spec.ts index 61057522859..a2f304c5029 100644 --- a/packages/twenty-oxlint-rules/rules/upgrade-command-filename.spec.ts +++ b/packages/twenty-oxlint-rules/rules/upgrade-command-filename.spec.ts @@ -36,6 +36,12 @@ ruleTester.run(RULE_NAME, rule, { '1-22/1-22-instance-command-fast-1780000000000-create-task-table.ts', ), }, + { + code: DUMMY_CODE, + filename: filename( + '1-22/1-22-instance-command-slow-1775758621018-backfill-workspace-id.ts', + ), + }, { code: DUMMY_CODE, filename: filename('1-21/1-21-upgrade-version-command.module.ts'), @@ -75,7 +81,7 @@ ruleTester.run(RULE_NAME, rule, { filename: filename( '1-21/1-21-instance-command-fast-add-column.ts', ), - errors: [{ messageId: 'invalidInstanceCommandFilename' }], + errors: [{ messageId: 'invalidInstanceCommandFastFilename' }], }, { code: DUMMY_CODE, diff --git a/packages/twenty-oxlint-rules/rules/upgrade-command-filename.ts b/packages/twenty-oxlint-rules/rules/upgrade-command-filename.ts index 09336c052f9..b459d80f290 100644 --- a/packages/twenty-oxlint-rules/rules/upgrade-command-filename.ts +++ b/packages/twenty-oxlint-rules/rules/upgrade-command-filename.ts @@ -5,9 +5,12 @@ export const RULE_NAME = 'upgrade-command-filename'; const WORKSPACE_COMMAND_REGEX = /^\d+-\d+-workspace-command-\d{13,}-[a-z0-9]+(?:-[a-z0-9]+)*\.command\.ts$/; -const INSTANCE_COMMAND_REGEX = +const INSTANCE_COMMAND_FAST_REGEX = /^\d+-\d+-instance-command-fast-\d{13,}-[a-z0-9]+(?:-[a-z0-9]+)*\.ts$/; +const INSTANCE_COMMAND_SLOW_REGEX = + /^\d+-\d+-instance-command-slow-\d{13,}-[a-z0-9]+(?:-[a-z0-9]+)*\.ts$/; + const SKIPPED_FILE_REGEX = /\.(module|spec|test|snap)\.ts$|__tests__|__mocks__|__snapshots__/; @@ -37,10 +40,12 @@ export const rule = defineRule({ messages: { invalidWorkspaceCommandFilename: "Workspace command filename '{{ name }}' must match pattern: {major}-{minor}-workspace-command-{timestamp}-{description}.command.ts (e.g. '1-21-workspace-command-1775500001000-add-feature.command.ts')", - invalidInstanceCommandFilename: + invalidInstanceCommandFastFilename: "Instance command filename '{{ name }}' must match pattern: {major}-{minor}-instance-command-fast-{timestamp}-{description}.ts (e.g. '1-21-instance-command-fast-1775500001000-add-column.ts')", + invalidInstanceCommandSlowFilename: + "Instance command filename '{{ name }}' must match pattern: {major}-{minor}-instance-command-slow-{timestamp}-{description}.ts (e.g. '1-22-instance-command-slow-1775500001000-backfill-data.ts')", invalidUpgradeCommandFilename: - "Upgrade command filename '{{ name }}' does not match any recognized pattern. Expected workspace-command or instance-command-fast format.", + "Upgrade command filename '{{ name }}' does not match any recognized pattern. Expected workspace-command, instance-command-fast, or instance-command-slow format.", }, }, create: (context) => { @@ -78,10 +83,22 @@ export const rule = defineRule({ } if (basename.includes('instance-command-fast-')) { - if (!INSTANCE_COMMAND_REGEX.test(basename)) { + if (!INSTANCE_COMMAND_FAST_REGEX.test(basename)) { context.report({ node, - messageId: 'invalidInstanceCommandFilename', + messageId: 'invalidInstanceCommandFastFilename', + data: { name: basename }, + }); + } + + return; + } + + if (basename.includes('instance-command-slow-')) { + if (!INSTANCE_COMMAND_SLOW_REGEX.test(basename)) { + context.report({ + node, + messageId: 'invalidInstanceCommandSlowFilename', data: { name: basename }, }); } diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775749486425-auto-generated.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775749486425-add-permission-flag-role-id-index.ts similarity index 87% rename from packages/twenty-server/src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775749486425-auto-generated.ts rename to packages/twenty-server/src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775749486425-add-permission-flag-role-id-index.ts index e5e2ba0edca..b10b17bbd60 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775749486425-auto-generated.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775749486425-add-permission-flag-role-id-index.ts @@ -4,7 +4,9 @@ import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decor import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface'; @RegisteredInstanceCommand('1.22.0', 1775749486425) -export class AutoGeneratedFastInstanceCommand implements FastInstanceCommand { +export class AddPermissionFlagRoleIdIndexFastInstanceCommand + implements FastInstanceCommand +{ public async up(queryRunner: QueryRunner): Promise { await queryRunner.query( 'CREATE INDEX "IDX_PERMISSION_FLAG_ROLE_ID" ON "core"."permissionFlag" ("roleId") ', diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775758621017-add-workspace-id-to-indirect-entities.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775758621017-add-workspace-id-to-indirect-entities.ts new file mode 100644 index 00000000000..1ba3ccf8e05 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775758621017-add-workspace-id-to-indirect-entities.ts @@ -0,0 +1,36 @@ +import { QueryRunner } from 'typeorm'; + +import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator'; +import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface'; + +const TABLES = [ + 'applicationVariable', + 'indexFieldMetadata', + 'twoFactorAuthenticationMethod', + 'agentMessagePart', + 'agentTurnEvaluation', + 'agentChatThread', + 'agentTurn', + 'agentMessage', +]; + +@RegisteredInstanceCommand('1.22.0', 1775758621017) +export class AddWorkspaceIdToIndirectEntitiesFastInstanceCommand + implements FastInstanceCommand +{ + public async up(queryRunner: QueryRunner): Promise { + for (const table of TABLES) { + await queryRunner.query( + `ALTER TABLE "core"."${table}" ADD "workspaceId" uuid`, + ); + } + } + + public async down(queryRunner: QueryRunner): Promise { + for (const table of TABLES) { + await queryRunner.query( + `ALTER TABLE "core"."${table}" DROP COLUMN "workspaceId"`, + ); + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775761294897-add-workspace-id-indexes-and-fks-to-indirect-entities.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775761294897-add-workspace-id-indexes-and-fks-to-indirect-entities.ts new file mode 100644 index 00000000000..5bf2edb89c4 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775761294897-add-workspace-id-indexes-and-fks-to-indirect-entities.ts @@ -0,0 +1,111 @@ +import { QueryRunner } from 'typeorm'; + +import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator'; +import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface'; + +@RegisteredInstanceCommand('1.22.0', 1775761294897) +export class AddWorkspaceIdIndexesAndFksFastInstanceCommand + implements FastInstanceCommand +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'CREATE INDEX "IDX_78ae6cfe5f49a76c4bf842ad58" ON "core"."applicationVariable" ("workspaceId") ', + ); + await queryRunner.query( + 'CREATE INDEX "IDX_d8cf7f15cf6466ac0e3b443b3d" ON "core"."indexFieldMetadata" ("workspaceId") ', + ); + await queryRunner.query( + 'CREATE INDEX "IDX_b8282d1e10fbb7856950f86c61" ON "core"."twoFactorAuthenticationMethod" ("workspaceId") ', + ); + await queryRunner.query( + 'CREATE INDEX "IDX_70b398dc45219db8f3e36b3a07" ON "core"."agentMessagePart" ("workspaceId") ', + ); + await queryRunner.query( + 'CREATE INDEX "IDX_c81d8fabdda94b7fa86fb6f1e7" ON "core"."agentTurnEvaluation" ("workspaceId") ', + ); + await queryRunner.query( + 'CREATE INDEX "IDX_3d097ed53841d80904ed02c837" ON "core"."agentChatThread" ("workspaceId") ', + ); + await queryRunner.query( + 'CREATE INDEX "IDX_a4bb3c6176c2607693a6756ff6" ON "core"."agentTurn" ("workspaceId") ', + ); + await queryRunner.query( + 'CREATE INDEX "IDX_75db4f2e80922078e8171ae130" ON "core"."agentMessage" ("workspaceId") ', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationVariable" ADD CONSTRAINT "FK_78ae6cfe5f49a76c4bf842ad58b" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION', + ); + await queryRunner.query( + 'ALTER TABLE "core"."indexFieldMetadata" ADD CONSTRAINT "FK_d8cf7f15cf6466ac0e3b443b3d2" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION', + ); + await queryRunner.query( + 'ALTER TABLE "core"."twoFactorAuthenticationMethod" ADD CONSTRAINT "FK_b8282d1e10fbb7856950f86c616" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION', + ); + await queryRunner.query( + 'ALTER TABLE "core"."agentMessagePart" ADD CONSTRAINT "FK_70b398dc45219db8f3e36b3a078" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION', + ); + await queryRunner.query( + 'ALTER TABLE "core"."agentTurnEvaluation" ADD CONSTRAINT "FK_c81d8fabdda94b7fa86fb6f1e70" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION', + ); + await queryRunner.query( + 'ALTER TABLE "core"."agentChatThread" ADD CONSTRAINT "FK_3d097ed53841d80904ed02c8373" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION', + ); + await queryRunner.query( + 'ALTER TABLE "core"."agentTurn" ADD CONSTRAINT "FK_a4bb3c6176c2607693a6756ff6c" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION', + ); + await queryRunner.query( + 'ALTER TABLE "core"."agentMessage" ADD CONSTRAINT "FK_75db4f2e80922078e8171ae130a" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION', + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "core"."agentMessage" DROP CONSTRAINT "FK_75db4f2e80922078e8171ae130a"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."agentTurn" DROP CONSTRAINT "FK_a4bb3c6176c2607693a6756ff6c"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."agentChatThread" DROP CONSTRAINT "FK_3d097ed53841d80904ed02c8373"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."agentTurnEvaluation" DROP CONSTRAINT "FK_c81d8fabdda94b7fa86fb6f1e70"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."agentMessagePart" DROP CONSTRAINT "FK_70b398dc45219db8f3e36b3a078"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."twoFactorAuthenticationMethod" DROP CONSTRAINT "FK_b8282d1e10fbb7856950f86c616"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."indexFieldMetadata" DROP CONSTRAINT "FK_d8cf7f15cf6466ac0e3b443b3d2"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationVariable" DROP CONSTRAINT "FK_78ae6cfe5f49a76c4bf842ad58b"', + ); + await queryRunner.query( + 'DROP INDEX "core"."IDX_75db4f2e80922078e8171ae130"', + ); + await queryRunner.query( + 'DROP INDEX "core"."IDX_a4bb3c6176c2607693a6756ff6"', + ); + await queryRunner.query( + 'DROP INDEX "core"."IDX_3d097ed53841d80904ed02c837"', + ); + await queryRunner.query( + 'DROP INDEX "core"."IDX_c81d8fabdda94b7fa86fb6f1e7"', + ); + await queryRunner.query( + 'DROP INDEX "core"."IDX_70b398dc45219db8f3e36b3a07"', + ); + await queryRunner.query( + 'DROP INDEX "core"."IDX_b8282d1e10fbb7856950f86c61"', + ); + await queryRunner.query( + 'DROP INDEX "core"."IDX_d8cf7f15cf6466ac0e3b443b3d"', + ); + await queryRunner.query( + 'DROP INDEX "core"."IDX_78ae6cfe5f49a76c4bf842ad58"', + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-22/1-22-instance-command-slow-1775758621018-backfill-workspace-id-on-indirect-entities.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-22/1-22-instance-command-slow-1775758621018-backfill-workspace-id-on-indirect-entities.ts new file mode 100644 index 00000000000..b5c62f643e7 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-22/1-22-instance-command-slow-1775758621018-backfill-workspace-id-on-indirect-entities.ts @@ -0,0 +1,89 @@ +import { DataSource, QueryRunner } from 'typeorm'; + +import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator'; +import { SlowInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/slow-instance-command.interface'; + +type BackfillDefinition = { + table: string; + parentTable: string; + foreignKey: string; +}; + +// Order matters: parents must be backfilled before children +const BACKFILL_DEFINITIONS: BackfillDefinition[] = [ + { + table: 'twoFactorAuthenticationMethod', + parentTable: 'userWorkspace', + foreignKey: 'userWorkspaceId', + }, + { + table: 'agentChatThread', + parentTable: 'userWorkspace', + foreignKey: 'userWorkspaceId', + }, + { + table: 'agentTurn', + parentTable: 'agentChatThread', + foreignKey: 'threadId', + }, + { + table: 'agentMessage', + parentTable: 'agentChatThread', + foreignKey: 'threadId', + }, + { + table: 'agentTurnEvaluation', + parentTable: 'agentTurn', + foreignKey: 'turnId', + }, + { + table: 'agentMessagePart', + parentTable: 'agentMessage', + foreignKey: 'messageId', + }, + { + table: 'indexFieldMetadata', + parentTable: 'indexMetadata', + foreignKey: 'indexMetadataId', + }, + { + table: 'applicationVariable', + parentTable: 'application', + foreignKey: 'applicationId', + }, +]; + +const TABLES = BACKFILL_DEFINITIONS.map((definition) => definition.table); + +@RegisteredInstanceCommand('1.22.0', 1775758621018, { type: 'slow' }) +export class BackfillWorkspaceIdOnIndirectEntitiesSlowInstanceCommand + implements SlowInstanceCommand +{ + async runDataMigration(dataSource: DataSource): Promise { + for (const { table, parentTable, foreignKey } of BACKFILL_DEFINITIONS) { + await dataSource.query( + `UPDATE "core"."${table}" t + SET "workspaceId" = p."workspaceId" + FROM "core"."${parentTable}" p + WHERE t."${foreignKey}" = p."id" + AND t."workspaceId" IS NULL`, + ); + } + } + + public async up(queryRunner: QueryRunner): Promise { + for (const table of TABLES) { + await queryRunner.query( + `ALTER TABLE "core"."${table}" ALTER COLUMN "workspaceId" SET NOT NULL`, + ); + } + } + + public async down(queryRunner: QueryRunner): Promise { + for (const table of TABLES) { + await queryRunner.query( + `ALTER TABLE "core"."${table}" ALTER COLUMN "workspaceId" DROP NOT NULL`, + ); + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts index 38d11c310d4..d5da5596c22 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts @@ -3,11 +3,17 @@ import { AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-instance-command-fast-1775129420309-add-view-field-group-id-index-on-view-field'; import { MigrateMessagingCalendarToCoreFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-instance-command-fast-1775165049548-migrate-messaging-calendar-to-core'; import { AddEmailThreadWidgetTypeFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-instance-command-fast-1775200000000-add-email-thread-widget-type'; -import { AutoGeneratedFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775749486425-auto-generated'; +import { AddPermissionFlagRoleIdIndexFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775749486425-add-permission-flag-role-id-index'; +import { AddWorkspaceIdToIndirectEntitiesFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775758621017-add-workspace-id-to-indirect-entities'; +import { BackfillWorkspaceIdOnIndirectEntitiesSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/1-22/1-22-instance-command-slow-1775758621018-backfill-workspace-id-on-indirect-entities'; +import { AddWorkspaceIdIndexesAndFksFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775761294897-add-workspace-id-indexes-and-fks-to-indirect-entities'; export const INSTANCE_COMMANDS = [ AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand, MigrateMessagingCalendarToCoreFastInstanceCommand, AddEmailThreadWidgetTypeFastInstanceCommand, - AutoGeneratedFastInstanceCommand, + AddPermissionFlagRoleIdIndexFastInstanceCommand, + AddWorkspaceIdToIndirectEntitiesFastInstanceCommand, + BackfillWorkspaceIdOnIndirectEntitiesSlowInstanceCommand, + AddWorkspaceIdIndexesAndFksFastInstanceCommand, ]; diff --git a/packages/twenty-server/src/engine/core-modules/application/application-variable/__tests__/application-variable.service.spec.ts b/packages/twenty-server/src/engine/core-modules/application/application-variable/__tests__/application-variable.service.spec.ts index 756942268f5..3fa36f8908f 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-variable/__tests__/application-variable.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-variable/__tests__/application-variable.service.spec.ts @@ -186,6 +186,7 @@ describe('ApplicationVariableEntityService', () => { description: 'A secret key', isSecret: true, applicationId: mockApplicationId, + workspaceId: mockWorkspaceId, }, ]); }); @@ -216,6 +217,7 @@ describe('ApplicationVariableEntityService', () => { description: 'Public URL', isSecret: false, applicationId: mockApplicationId, + workspaceId: mockWorkspaceId, }, ]); }); diff --git a/packages/twenty-server/src/engine/core-modules/application/application-variable/application-variable.entity.ts b/packages/twenty-server/src/engine/core-modules/application/application-variable/application-variable.entity.ts index 28c09cc3393..1e78e25bc78 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-variable/application-variable.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-variable/application-variable.entity.ts @@ -5,6 +5,7 @@ import { Column, CreateDateColumn, Entity, + Index, JoinColumn, ManyToOne, PrimaryGeneratedColumn, @@ -14,6 +15,7 @@ import { import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; +import type { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { EntityRelation } from 'src/engine/workspace-manager/workspace-migration/types/entity-relation.interface'; @Entity({ @@ -30,6 +32,14 @@ export class ApplicationVariableEntity { @PrimaryGeneratedColumn('uuid') id: string; + @Column({ nullable: false, type: 'uuid' }) + @Index() + workspaceId: string; + + @ManyToOne('WorkspaceEntity', { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'workspaceId' }) + workspace: EntityRelation; + @Column({ nullable: false, type: 'text' }) key: string; diff --git a/packages/twenty-server/src/engine/core-modules/application/application-variable/application-variable.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-variable/application-variable.service.ts index 23507e0fbe0..9a214148fa9 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-variable/application-variable.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-variable/application-variable.service.ts @@ -136,6 +136,7 @@ export class ApplicationVariableEntityService { description: description ?? '', isSecret: isSecretValue, applicationId, + workspaceId, }); } } diff --git a/packages/twenty-server/src/engine/core-modules/application/application-variable/utils/from-application-variable-entity-to-flat-application-variable.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-variable/utils/from-application-variable-entity-to-flat-application-variable.util.ts index cae76b726bd..42b3f8ab646 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-variable/utils/from-application-variable-entity-to-flat-application-variable.util.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-variable/utils/from-application-variable-entity-to-flat-application-variable.util.ts @@ -10,6 +10,7 @@ export const fromApplicationVariableEntityToFlatApplicationVariable = ( description: entity.description, isSecret: entity.isSecret, applicationId: entity.applicationId, + workspaceId: entity.workspaceId, createdAt: entity.createdAt.toISOString(), updatedAt: entity.updatedAt.toISOString(), }); diff --git a/packages/twenty-server/src/engine/core-modules/billing-webhook/services/billing-webhook-subscription.service.ts b/packages/twenty-server/src/engine/core-modules/billing-webhook/services/billing-webhook-subscription.service.ts index eb9f829cd98..52d39a9e420 100644 --- a/packages/twenty-server/src/engine/core-modules/billing-webhook/services/billing-webhook-subscription.service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing-webhook/services/billing-webhook-subscription.service.ts @@ -137,6 +137,7 @@ export class BillingWebhookSubscriptionService { await this.updateBillingSubscriptionItems( updatedBillingSubscription.id, event, + workspaceId, ); const shouldSuspend = this.shouldSuspendWorkspace(data); @@ -224,6 +225,7 @@ export class BillingWebhookSubscriptionService { | Stripe.CustomerSubscriptionUpdatedEvent | Stripe.CustomerSubscriptionCreatedEvent | Stripe.CustomerSubscriptionDeletedEvent, + workspaceId: string, ) { const deletedSubscriptionItemIds = getDeletedStripeSubscriptionItemIdsFromStripeSubscriptionEvent(event); @@ -239,6 +241,7 @@ export class BillingWebhookSubscriptionService { transformStripeSubscriptionEventToDatabaseSubscriptionItem( subscriptionId, event.data, + workspaceId, ), { conflictPaths: ['stripeSubscriptionItemId'], diff --git a/packages/twenty-server/src/engine/core-modules/billing-webhook/utils/transform-stripe-subscription-event-to-database-subscription-item.util.ts b/packages/twenty-server/src/engine/core-modules/billing-webhook/utils/transform-stripe-subscription-event-to-database-subscription-item.util.ts index 45b947d3353..b3ca1b1b6e8 100644 --- a/packages/twenty-server/src/engine/core-modules/billing-webhook/utils/transform-stripe-subscription-event-to-database-subscription-item.util.ts +++ b/packages/twenty-server/src/engine/core-modules/billing-webhook/utils/transform-stripe-subscription-event-to-database-subscription-item.util.ts @@ -8,10 +8,12 @@ export const transformStripeSubscriptionEventToDatabaseSubscriptionItem = ( | Stripe.CustomerSubscriptionUpdatedEvent.Data | Stripe.CustomerSubscriptionCreatedEvent.Data | Stripe.CustomerSubscriptionDeletedEvent.Data, + workspaceId: string, ) => { return data.object.items.data.map((item) => { return { billingSubscriptionId, + workspaceId, stripeSubscriptionId: data.object.id, stripeProductId: String(item.price.product), stripePriceId: item.price.id, diff --git a/packages/twenty-server/src/engine/core-modules/billing/entities/billing-subscription-item.entity.ts b/packages/twenty-server/src/engine/core-modules/billing/entities/billing-subscription-item.entity.ts index 663ad541e24..edf415ec3b4 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/entities/billing-subscription-item.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/entities/billing-subscription-item.entity.ts @@ -5,6 +5,7 @@ import { Column, CreateDateColumn, Entity, + Index, JoinColumn, ManyToOne, PrimaryGeneratedColumn, @@ -16,6 +17,7 @@ import { import { BillingProductEntity } from 'src/engine/core-modules/billing/entities/billing-product.entity'; import { BillingSubscriptionEntity } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; import { BillingSubscriptionItemMetadata } from 'src/engine/core-modules/billing/types/billing-subscription-item-metadata.type'; +import type { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; @Entity({ name: 'billingSubscriptionItem', schema: 'core' }) @Unique( 'IDX_BILLING_SUBSCRIPTION_ITEM_BILLING_SUBSCRIPTION_ID_STRIPE_PRODUCT_ID_UNIQUE', @@ -25,6 +27,14 @@ export class BillingSubscriptionItemEntity { @PrimaryGeneratedColumn('uuid') id: string; + @Column({ nullable: false, type: 'uuid' }) + @Index() + workspaceId: string; + + @ManyToOne('WorkspaceEntity', { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'workspaceId' }) + workspace: Relation; + @Column({ nullable: true, type: 'timestamptz' }) deletedAt?: Date; diff --git a/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts b/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts index 33dcacf2a72..f9a4002c4e4 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts @@ -348,6 +348,7 @@ export class BillingSubscriptionService { { object: subscription, }, + workspaceId, ); const meterBillingSubscriptionItem = findOrThrow( diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/utils/__tests__/build-env-var.spec.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/utils/__tests__/build-env-var.spec.ts index 51e8f8178e5..a388bc5a192 100644 --- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/utils/__tests__/build-env-var.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/utils/__tests__/build-env-var.spec.ts @@ -27,6 +27,7 @@ describe('buildEnvVar', () => { description: 'Public URL', isSecret: false, applicationId: 'app-1', + workspaceId: '00000000-0000-0000-0000-000000000000', createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-01T00:00:00.000Z', }, @@ -37,6 +38,7 @@ describe('buildEnvVar', () => { description: 'API secret', isSecret: true, applicationId: 'app-1', + workspaceId: '00000000-0000-0000-0000-000000000000', createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-01T00:00:00.000Z', }, @@ -47,6 +49,7 @@ describe('buildEnvVar', () => { description: 'Debug flag', isSecret: false, applicationId: 'app-1', + workspaceId: '00000000-0000-0000-0000-000000000000', createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-01T00:00:00.000Z', }, @@ -74,6 +77,7 @@ describe('buildEnvVar', () => { description: '', isSecret: false, applicationId: 'app-1', + workspaceId: '00000000-0000-0000-0000-000000000000', createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-01T00:00:00.000Z', }, @@ -84,6 +88,7 @@ describe('buildEnvVar', () => { description: '', isSecret: false, applicationId: 'app-1', + workspaceId: '00000000-0000-0000-0000-000000000000', createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-01T00:00:00.000Z', }, @@ -106,6 +111,7 @@ describe('buildEnvVar', () => { description: '', isSecret: false, applicationId: 'app-1', + workspaceId: '00000000-0000-0000-0000-000000000000', createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-01T00:00:00.000Z', }, diff --git a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/entities/two-factor-authentication-method.entity.ts b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/entities/two-factor-authentication-method.entity.ts index 714439e4350..f82c0866905 100644 --- a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/entities/two-factor-authentication-method.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/entities/two-factor-authentication-method.entity.ts @@ -13,6 +13,7 @@ import { import { OTPStatus } from 'src/engine/core-modules/two-factor-authentication/strategies/otp/otp.constants'; import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import type { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; @Index(['userWorkspaceId', 'strategy'], { unique: true }) @Entity({ name: 'twoFactorAuthenticationMethod', schema: 'core' }) @@ -20,6 +21,14 @@ export class TwoFactorAuthenticationMethodEntity { @PrimaryGeneratedColumn('uuid') id: string; + @Column({ nullable: false, type: 'uuid' }) + @Index() + workspaceId: string; + + @ManyToOne('WorkspaceEntity', { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'workspaceId' }) + workspace: Relation; + @Column({ nullable: false, type: 'uuid' }) userWorkspaceId: string; diff --git a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.service.spec.ts b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.service.spec.ts index df37d4aa4b5..5214ce42f04 100644 --- a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.service.spec.ts @@ -191,6 +191,7 @@ describe('TwoFactorAuthenticationService', () => { ); expect(repository.save).toHaveBeenCalledWith({ id: undefined, + workspaceId: workspace.id, userWorkspace: mockUserWorkspace, secret: encryptedSecret, status: 'PENDING', diff --git a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.service.ts b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.service.ts index d8022ed3d9c..82c809da2c4 100644 --- a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.service.ts +++ b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.service.ts @@ -140,6 +140,7 @@ export class TwoFactorAuthenticationService { await this.twoFactorAuthenticationMethodRepository.save({ id: existing2FAMethod?.id, + workspaceId, userWorkspace: userWorkspace, secret: encryptedSecret, status: context.status, diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message-part.entity.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message-part.entity.ts index a8f65f92c67..3f2b5e315ff 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message-part.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message-part.entity.ts @@ -12,12 +12,21 @@ import { import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity'; import { AgentMessageEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message.entity'; +import type { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -@Entity('agentMessagePart') +@Entity({ name: 'agentMessagePart', schema: 'core' }) export class AgentMessagePartEntity { @PrimaryGeneratedColumn('uuid') id: string; + @Column({ nullable: false, type: 'uuid' }) + @Index() + workspaceId: string; + + @ManyToOne('WorkspaceEntity', { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'workspaceId' }) + workspace: Relation; + @Column('uuid') @Index() messageId: string; diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message.entity.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message.entity.ts index ffeb0c4c745..4b9af6ea8ed 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message.entity.ts @@ -13,6 +13,7 @@ import { import { AgentMessagePartEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message-part.entity'; import { AgentTurnEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-turn.entity'; import { AgentChatThreadEntity } from 'src/engine/metadata-modules/ai/ai-chat/entities/agent-chat-thread.entity'; +import type { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; export enum AgentMessageRole { SYSTEM = 'system', @@ -25,11 +26,19 @@ export enum AgentMessageStatus { SENT = 'sent', } -@Entity('agentMessage') +@Entity({ name: 'agentMessage', schema: 'core' }) export class AgentMessageEntity { @PrimaryGeneratedColumn('uuid') id: string; + @Column({ nullable: false, type: 'uuid' }) + @Index() + workspaceId: string; + + @ManyToOne('WorkspaceEntity', { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'workspaceId' }) + workspace: Relation; + @Column('uuid') @Index() threadId: string; diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-turn.entity.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-turn.entity.ts index 5ef2ba1bc22..9cc1029dfac 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-turn.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-turn.entity.ts @@ -13,12 +13,21 @@ import { import { AgentMessageEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message.entity'; import { AgentTurnEvaluationEntity } from 'src/engine/metadata-modules/ai/ai-agent-monitor/entities/agent-turn-evaluation.entity'; import { AgentChatThreadEntity } from 'src/engine/metadata-modules/ai/ai-chat/entities/agent-chat-thread.entity'; +import type { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -@Entity('agentTurn') +@Entity({ name: 'agentTurn', schema: 'core' }) export class AgentTurnEntity { @PrimaryGeneratedColumn('uuid') id: string; + @Column({ nullable: false, type: 'uuid' }) + @Index() + workspaceId: string; + + @ManyToOne('WorkspaceEntity', { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'workspaceId' }) + workspace: Relation; + @Column('uuid') @Index() threadId: string; diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/utils/mapUIMessagePartsToDBParts.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/utils/mapUIMessagePartsToDBParts.ts index 9267febe632..0ea11b4de7c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/utils/mapUIMessagePartsToDBParts.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/utils/mapUIMessagePartsToDBParts.ts @@ -13,6 +13,7 @@ const isToolPart = (part: ExtendedUIMessagePart): part is ToolUIPart => { export const mapUIMessagePartsToDBParts = ( uiMessageParts: ExtendedUIMessagePart[], messageId: string, + workspaceId: string, ): Partial[] => { return uiMessageParts .map((part, index) => { @@ -20,6 +21,7 @@ export const mapUIMessagePartsToDBParts = ( messageId, orderIndex: index, type: part.type, + workspaceId, }; switch (part.type) { diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-monitor/entities/agent-turn-evaluation.entity.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-monitor/entities/agent-turn-evaluation.entity.ts index e98d695ccf2..6af29a99baa 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-monitor/entities/agent-turn-evaluation.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-monitor/entities/agent-turn-evaluation.entity.ts @@ -10,12 +10,21 @@ import { } from 'typeorm'; import { AgentTurnEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-turn.entity'; +import type { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -@Entity('agentTurnEvaluation') +@Entity({ name: 'agentTurnEvaluation', schema: 'core' }) export class AgentTurnEvaluationEntity { @PrimaryGeneratedColumn('uuid') id: string; + @Column({ nullable: false, type: 'uuid' }) + @Index() + workspaceId: string; + + @ManyToOne('WorkspaceEntity', { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'workspaceId' }) + workspace: Relation; + @Column('uuid') @Index() turnId: string; diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-monitor/jobs/run-evaluation-input.job.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-monitor/jobs/run-evaluation-input.job.ts index 505d3b73e39..75ba76c24c7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-monitor/jobs/run-evaluation-input.job.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-monitor/jobs/run-evaluation-input.job.ts @@ -44,6 +44,7 @@ export class RunEvaluationInputJob { role: 'user', parts: [{ type: 'text', text: data.input }], }, + workspaceId: data.workspaceId, }); const agent = await this.agentRepository.findOne({ @@ -72,6 +73,7 @@ export class RunEvaluationInputJob { }, ], }, + workspaceId: data.workspaceId, }); await this.messageQueueService.add<{ diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-monitor/resolvers/agent-turn.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-monitor/resolvers/agent-turn.resolver.ts index 7fc854fc8db..4c9cd71efb5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-monitor/resolvers/agent-turn.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-monitor/resolvers/agent-turn.resolver.ts @@ -68,6 +68,7 @@ export class AgentTurnResolver { ): Promise { const thread = this.threadRepository.create({ userWorkspaceId, + workspaceId: workspace.id, title: `Eval: ${input.substring(0, 50)}...`, }); const savedThread = await this.threadRepository.save(thread); @@ -75,6 +76,7 @@ export class AgentTurnResolver { const turn = this.turnRepository.create({ threadId: savedThread.id, agentId, + workspaceId: workspace.id, }); const savedTurn = await this.turnRepository.save(turn); diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-monitor/services/agent-turn-grader.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-monitor/services/agent-turn-grader.service.ts index 22797b5dfc2..d7c781b1416 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-monitor/services/agent-turn-grader.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-monitor/services/agent-turn-grader.service.ts @@ -36,6 +36,7 @@ export class AgentTurnGraderService { const evaluation = this.evaluationRepository.create({ turnId, + workspaceId: turn.workspaceId, score, comment, }); diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/entities/agent-chat-thread.entity.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/entities/agent-chat-thread.entity.ts index 9e18890f55f..ff908a00e32 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/entities/agent-chat-thread.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/entities/agent-chat-thread.entity.ts @@ -13,13 +13,22 @@ import { import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { AgentMessageEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-message.entity'; import { AgentTurnEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-turn.entity'; +import type { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { EntityRelation } from 'src/engine/workspace-manager/workspace-migration/types/entity-relation.interface'; -@Entity('agentChatThread') +@Entity({ name: 'agentChatThread', schema: 'core' }) export class AgentChatThreadEntity { @PrimaryGeneratedColumn('uuid') id: string; + @Column({ nullable: false, type: 'uuid' }) + @Index() + workspaceId: string; + + @ManyToOne('WorkspaceEntity', { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'workspaceId' }) + workspace: EntityRelation; + @Column({ nullable: false, type: 'uuid' }) @Index() userWorkspaceId: string; diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/jobs/stream-agent-chat.job.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/jobs/stream-agent-chat.job.ts index ba3c969fb3d..6c08f30e2f6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/jobs/stream-agent-chat.job.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/jobs/stream-agent-chat.job.ts @@ -144,6 +144,7 @@ export class StreamAgentChatJob { part.type === 'text' || part.type === 'file', ), }, + workspaceId: data.workspaceId, }); userMessagePromise.catch(() => {}); @@ -271,6 +272,7 @@ export class StreamAgentChatJob { await this.handleStreamFinish({ responseMessage, threadId: data.threadId, + workspaceId: data.workspaceId, streamUsage, lastStepConversationSize, modelConfig, @@ -410,6 +412,7 @@ export class StreamAgentChatJob { private async handleStreamFinish({ responseMessage, threadId, + workspaceId, streamUsage, lastStepConversationSize, modelConfig, @@ -417,6 +420,7 @@ export class StreamAgentChatJob { }: { responseMessage: Omit; threadId: string; + workspaceId: string; streamUsage: { inputTokens: number; outputTokens: number; @@ -437,6 +441,7 @@ export class StreamAgentChatJob { threadId, uiMessage: responseMessage, turnId: userMessage.turnId ?? undefined, + workspaceId, }); await this.threadRepository.update(threadId, { diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/resolvers/agent-chat.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/resolvers/agent-chat.resolver.ts index 12cd337c8ec..97378837ce7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/resolvers/agent-chat.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/resolvers/agent-chat.resolver.ts @@ -174,6 +174,7 @@ export class AgentChatResolver { threadId, text, id: messageId, + workspaceId: workspace.id, }); await this.eventPublisherService.publish({ diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-chat-streaming.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-chat-streaming.service.ts index 0a3ace57700..ff196a460e9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-chat-streaming.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-chat-streaming.service.ts @@ -77,6 +77,7 @@ export class AgentChatStreamingService { role: AgentMessageRole.USER, parts: [{ type: 'text' as const, text }], }, + workspaceId: workspace.id, }); const previousMessages = await this.loadMessagesFromDB( @@ -138,6 +139,7 @@ export class AgentChatStreamingService { const turnId = await this.agentChatService.promoteQueuedMessage( nextQueued.id, threadId, + workspaceId, ); if (turnId === null) { diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-chat.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-chat.service.ts index 6bff111bbd7..d36e1165373 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-chat.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-chat.service.ts @@ -60,6 +60,7 @@ export class AgentChatService { }) { const thread = this.threadRepository.create({ userWorkspaceId, + workspaceId, }); const savedThread = await this.threadRepository.save(thread); @@ -105,6 +106,7 @@ export class AgentChatService { agentId, turnId, id, + workspaceId, }: { threadId: string; uiMessage: Omit; @@ -112,6 +114,7 @@ export class AgentChatService { agentId?: string; turnId?: string; id?: string; + workspaceId: string; }) { let actualTurnId = turnId; @@ -119,6 +122,7 @@ export class AgentChatService { const turn = this.turnRepository.create({ threadId, agentId: agentId ?? null, + workspaceId, }); const savedTurn = await this.turnRepository.save(turn); @@ -133,6 +137,7 @@ export class AgentChatService { role: uiMessage.role as AgentMessageRole, agentId: agentId ?? null, processedAt: new Date(), + workspaceId, }); const savedMessage = await this.messageRepository.save(message); @@ -141,6 +146,7 @@ export class AgentChatService { const dbParts = mapUIMessagePartsToDBParts( uiMessage.parts, savedMessage.id, + workspaceId, ); await this.messagePartRepository.save(dbParts); @@ -175,10 +181,12 @@ export class AgentChatService { threadId, text, id, + workspaceId, }: { threadId: string; text: string; id?: string; + workspaceId: string; }): Promise { const message = this.messageRepository.create({ ...(id ? { id } : {}), @@ -187,6 +195,7 @@ export class AgentChatService { role: AgentMessageRole.USER, agentId: null, status: AgentMessageStatus.QUEUED, + workspaceId, }); const savedMessage = await this.messageRepository.save(message); @@ -196,6 +205,7 @@ export class AgentChatService { orderIndex: 0, type: 'text', textContent: text, + workspaceId, }); await this.messagePartRepository.save(part); @@ -234,10 +244,12 @@ export class AgentChatService { async promoteQueuedMessage( messageId: string, threadId: string, + workspaceId: string, ): Promise { const turn = this.turnRepository.create({ threadId, agentId: null, + workspaceId, }); const savedTurn = await this.turnRepository.save(turn); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/utils/from-index-metadata-entity-to-flat-index-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/utils/from-index-metadata-entity-to-flat-index-metadata.util.ts index 5208225a003..99cb9a0be08 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/utils/from-index-metadata-entity-to-flat-index-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/utils/from-index-metadata-entity-to-flat-index-metadata.util.ts @@ -59,6 +59,7 @@ export const fromIndexMetadataEntityToFlatIndexMetadata = ({ ]), createdAt: indexFieldMetadata.createdAt.toISOString(), updatedAt: indexFieldMetadata.updatedAt.toISOString(), + workspaceId: indexFieldMetadata.workspaceId, }), ), universalFlatIndexFieldMetadatas: diff --git a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-field-metadata.entity.ts b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-field-metadata.entity.ts index ac9b61e1c56..8f247cc6c61 100644 --- a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-field-metadata.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-field-metadata.entity.ts @@ -12,14 +12,23 @@ import { import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; +import type { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -@Entity('indexFieldMetadata') +@Entity({ name: 'indexFieldMetadata', schema: 'core' }) export class IndexFieldMetadataEntity implements Required { @PrimaryGeneratedColumn('uuid') id: string; + @Column({ nullable: false, type: 'uuid' }) + @Index() + workspaceId: string; + + @ManyToOne('WorkspaceEntity', { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'workspaceId' }) + workspace: Relation; + @Column({ nullable: false }) indexMetadataId: string; diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-agents.util.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-agents.util.ts index dd761e665be..1c4aed3739d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-agents.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-agents.util.ts @@ -77,6 +77,7 @@ const seedChatThreads = async ({ .insert() .into(`${schemaName}.${agentChatThreadTableName}`, [ 'id', + 'workspaceId', 'userWorkspaceId', 'createdAt', 'updatedAt', @@ -85,6 +86,7 @@ const seedChatThreads = async ({ .values([ { id: threadId, + workspaceId, userWorkspaceId, createdAt: now, updatedAt: now, @@ -113,6 +115,7 @@ const seedChatMessages = async ({ let turnIds: string[]; let messages: Array<{ id: string; + workspaceId: string; threadId: string; turnId: string; role: AgentMessageRole; @@ -120,6 +123,7 @@ const seedChatMessages = async ({ }>; let messageParts: Array<{ id: string; + workspaceId: string; messageId: string; orderIndex: number; type: string; @@ -150,6 +154,7 @@ const seedChatMessages = async ({ messages = [ { id: messageIds[0], + workspaceId, threadId, turnId: turnIds[0], role: AgentMessageRole.USER, @@ -157,6 +162,7 @@ const seedChatMessages = async ({ }, { id: messageIds[1], + workspaceId, threadId, turnId: turnIds[0], role: AgentMessageRole.ASSISTANT, @@ -164,6 +170,7 @@ const seedChatMessages = async ({ }, { id: messageIds[2], + workspaceId, threadId, turnId: turnIds[1], role: AgentMessageRole.USER, @@ -171,6 +178,7 @@ const seedChatMessages = async ({ }, { id: messageIds[3], + workspaceId, threadId, turnId: turnIds[1], role: AgentMessageRole.ASSISTANT, @@ -180,6 +188,7 @@ const seedChatMessages = async ({ messageParts = [ { id: partIds[0], + workspaceId, messageId: messageIds[0], orderIndex: 0, type: 'text', @@ -189,6 +198,7 @@ const seedChatMessages = async ({ }, { id: partIds[1], + workspaceId, messageId: messageIds[1], orderIndex: 0, type: 'text', @@ -198,6 +208,7 @@ const seedChatMessages = async ({ }, { id: partIds[2], + workspaceId, messageId: messageIds[2], orderIndex: 0, type: 'text', @@ -207,6 +218,7 @@ const seedChatMessages = async ({ }, { id: partIds[3], + workspaceId, messageId: messageIds[3], orderIndex: 0, type: 'text', @@ -235,6 +247,7 @@ const seedChatMessages = async ({ messages = [ { id: messageIds[0], + workspaceId, threadId, turnId: turnIds[0], role: AgentMessageRole.USER, @@ -242,6 +255,7 @@ const seedChatMessages = async ({ }, { id: messageIds[1], + workspaceId, threadId, turnId: turnIds[0], role: AgentMessageRole.ASSISTANT, @@ -249,6 +263,7 @@ const seedChatMessages = async ({ }, { id: messageIds[2], + workspaceId, threadId, turnId: turnIds[1], role: AgentMessageRole.USER, @@ -256,6 +271,7 @@ const seedChatMessages = async ({ }, { id: messageIds[3], + workspaceId, threadId, turnId: turnIds[1], role: AgentMessageRole.ASSISTANT, @@ -265,6 +281,7 @@ const seedChatMessages = async ({ messageParts = [ { id: partIds[0], + workspaceId, messageId: messageIds[0], orderIndex: 0, type: 'text', @@ -274,6 +291,7 @@ const seedChatMessages = async ({ }, { id: partIds[1], + workspaceId, messageId: messageIds[1], orderIndex: 0, type: 'text', @@ -283,6 +301,7 @@ const seedChatMessages = async ({ }, { id: partIds[2], + workspaceId, messageId: messageIds[2], orderIndex: 0, type: 'text', @@ -292,6 +311,7 @@ const seedChatMessages = async ({ }, { id: partIds[3], + workspaceId, messageId: messageIds[3], orderIndex: 0, type: 'text', @@ -309,6 +329,7 @@ const seedChatMessages = async ({ // Create turns first const turns = turnIds.map((id, index) => ({ id, + workspaceId, threadId, createdAt: messages[index * 2].createdAt, })); @@ -318,6 +339,7 @@ const seedChatMessages = async ({ .insert() .into(`${schemaName}.${agentTurnTableName}`, [ 'id', + 'workspaceId', 'threadId', 'createdAt', ]) @@ -330,6 +352,7 @@ const seedChatMessages = async ({ .insert() .into(`${schemaName}.${agentMessageTableName}`, [ 'id', + 'workspaceId', 'threadId', 'turnId', 'role', @@ -344,6 +367,7 @@ const seedChatMessages = async ({ .insert() .into(`${schemaName}.${agentMessagePartTableName}`, [ 'id', + 'workspaceId', 'messageId', 'orderIndex', 'type', diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/create-standard-index-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/create-standard-index-flat-metadata.util.ts index 88400b3ed7f..a56b5bbf4bd 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/create-standard-index-flat-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/create-standard-index-flat-metadata.util.ts @@ -131,6 +131,7 @@ export const createStandardIndexFlatMetadata = < indexMetadataId: indexId, order: index, updatedAt: now, + workspaceId, }), ), workspaceId, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-index-metadata.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-index-metadata.type.ts index 17aaefe28c0..cc715f2023b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-index-metadata.type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-index-metadata.type.ts @@ -11,6 +11,8 @@ export type UniversalFlatIndexFieldMetadata = Omit< | 'fieldMetadataId' | 'fieldMetadata' | 'id' + | 'workspaceId' + | 'workspace' | keyof CastRecordTypeOrmDatePropertiesToString > & { indexMetadataUniversalIdentifier: string; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/index/utils/from-universal-flat-index-to-flat-index.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/index/utils/from-universal-flat-index-to-flat-index.util.ts index 5667f225da7..9a0b325b7e9 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/index/utils/from-universal-flat-index-to-flat-index.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/index/utils/from-universal-flat-index-to-flat-index.util.ts @@ -61,6 +61,7 @@ export const fromUniversalFlatIndexToFlatIndex = ({ order: universalFlatIndexFieldMetadata.order, createdAt: now, updatedAt: now, + workspaceId, }; }, );