Compare commits

..
3 Commits
Author SHA1 Message Date
ishan-karmakar 12bd695c59 Change host port in docker-compose.yml
CI Create App / changed-files-check (push) Has been cancelled
CD deploy main / deploy-main (push) Has been cancelled
CI Codex Plugin / changed-files-check (push) Has been cancelled
CI Codex Plugin / codex-plugin-validate (push) Has been cancelled
CI Create App E2E minimal / changed-files-check (push) Has been cancelled
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI Create App / create-app-test (lint) (push) Has been cancelled
CI Create App / create-app-test (test) (push) Has been cancelled
CI Create App / create-app-test (typecheck) (push) Has been cancelled
CI Create App / ci-create-app-status-check (push) Has been cancelled
CI Docs / changed-files-check (push) Has been cancelled
CI Docs / docs-lint (push) Has been cancelled
CI Emails / changed-files-check (push) Has been cancelled
CI Emails / emails-test (push) Has been cancelled
CI Emails / ci-emails-status-check (push) Has been cancelled
CI Example App Hello World / changed-files-check (push) Has been cancelled
CI Example App Hello World / example-app-hello-world (push) Has been cancelled
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Has been cancelled
CI Example App Postcard / changed-files-check (push) Has been cancelled
CI Example App Postcard / example-app-postcard (push) Has been cancelled
CI Example App Postcard / ci-example-app-postcard-status-check (push) Has been cancelled
CI UI / changed-files-check (push) Has been cancelled
CI UI / ui-task (lint) (push) Has been cancelled
CI UI / ui-task (test) (push) Has been cancelled
CI UI / ui-task (typecheck) (push) Has been cancelled
CI UI / ui-sb-build (push) Has been cancelled
CI UI / ui-sb-test (push) Has been cancelled
CI UI / ci-ui-status-check (push) Has been cancelled
Push translations to Crowdin / Extract and upload translations (push) Has been cancelled
2026-06-07 17:38:49 +00:00
ishan-karmakar 17347736be Update docker-compose for our purposes
CI Codex Plugin / changed-files-check (push) Has been cancelled
CI Codex Plugin / codex-plugin-validate (push) Has been cancelled
CI Create App E2E minimal / changed-files-check (push) Has been cancelled
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI Create App / changed-files-check (push) Has been cancelled
CI Create App / create-app-test (lint) (push) Has been cancelled
CI Create App / create-app-test (test) (push) Has been cancelled
CI Create App / create-app-test (typecheck) (push) Has been cancelled
CI Create App / ci-create-app-status-check (push) Has been cancelled
CI Docs / changed-files-check (push) Has been cancelled
CI Docs / docs-lint (push) Has been cancelled
CI Emails / changed-files-check (push) Has been cancelled
CI Emails / emails-test (push) Has been cancelled
CI Emails / ci-emails-status-check (push) Has been cancelled
CI Example App Hello World / changed-files-check (push) Has been cancelled
CI Example App Hello World / example-app-hello-world (push) Has been cancelled
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Has been cancelled
CI Example App Postcard / changed-files-check (push) Has been cancelled
CI Example App Postcard / example-app-postcard (push) Has been cancelled
CI Example App Postcard / ci-example-app-postcard-status-check (push) Has been cancelled
CI UI / changed-files-check (push) Has been cancelled
CI UI / ui-task (lint) (push) Has been cancelled
CI UI / ui-task (test) (push) Has been cancelled
CI UI / ui-task (typecheck) (push) Has been cancelled
CI UI / ui-sb-build (push) Has been cancelled
CI UI / ui-sb-test (push) Has been cancelled
CI UI / ci-ui-status-check (push) Has been cancelled
CD deploy main / deploy-main (push) Has been cancelled
Push translations to Crowdin / Extract and upload translations (push) Has been cancelled
2026-06-07 17:34:40 +00:00
claude[bot]GitHubclaude[bot] <41898282+claude[bot]@users.noreply.github.com>Félix Malfait
dfe0b5bfd4 chore(server): rename TypeORM migrations dir to legacy-typeorm-migrations-do-not-add (#21295)
## What

Renames the historical TypeORM migrations directory so it's obvious at a
glance the path is frozen.

- `packages/twenty-server/src/database/typeorm/core/migrations/common/`
→ `…/typeorm/core/legacy-typeorm-migrations-do-not-add/common/`
- `packages/twenty-server/src/database/typeorm/core/migrations/billing/`
→ `…/typeorm/core/legacy-typeorm-migrations-do-not-add/billing/`
- `packages/twenty-server/src/database/typeorm/core/migrations/utils/` —
**left in place** (those SQL helpers are still imported by current
instance/workspace commands, see e.g.
`1-21-workspace-command-1775500002000-add-global-key-value-pair-unique-index.command.ts`)
- Updates `core.datasource.ts` globs to the new path and adds an inline
comment explaining the dir is frozen
- Adds a `README.md` at the new dir pointing readers at
`UPGRADE_COMMANDS.md` and the active `upgrade-version-command/` tree

## Why

The TypeORM migration system was replaced by fast/slow instance commands
+ workspace commands (PR #19356), but `typeorm/core/migrations/common/`
still looked structurally identical to an active migrations folder, with
new files merging in as recently as last week. New contributors — and AI
agents — kept inferring it was the active path and adding TypeORM
`MigrationInterface` files. See #21286 for the most recent instance.

This is recommendation **1b** from #21286
(`https://github.com/twentyhq/twenty/pull/21286#discussion_r3369356792`).
A renamed folder is the single strongest signal — no one instinctively
adds to a `do-not-add` directory.

## Notes

- Imports from `src/database/typeorm/core/migrations/utils/…` keep
working because `utils/` did not move.
- No behavior change at runtime: TypeORM loads the same files via
`_typeorm_migrations`, just from the new path.

## Test plan

- [ ] CI green
- [ ] `nx build twenty-server` succeeds
- [ ] Fresh `nx database:reset twenty-server` from a clean DB still
replays the legacy TypeORM migrations (i.e. `_typeorm_migrations` rows
get inserted at boot/init)
- [ ] Spot-check that an instance command that imports from
`migrations/utils/` still resolves at build time (e.g.
`1-21-workspace-command-1775500002000-add-global-key-value-pair-unique-index.command.ts`)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Félix Malfait <FelixMalfait@users.noreply.github.com>
2026-06-07 14:54:36 +02:00
212 changed files with 125 additions and 1337 deletions
@@ -3008,7 +3008,6 @@ type Query {
findManyAgents: [Agent!]!
findOneAgent(input: AgentIdInput!): Agent!
getRoles: [Role!]!
myConnectedAccounts: [ConnectedAccountPublicDTO!]!
getToolIndex: [ToolIndexEntry!]!
getToolInputSchema(toolName: String!): JSON
webhooks: [Webhook!]!
@@ -3028,6 +3027,7 @@ type Query {
getViewGroup(id: String!): ViewGroup
myMessageFolders(messageChannelId: UUID): [MessageFolder!]!
myMessageChannels(connectedAccountId: UUID): [MessageChannel!]!
myConnectedAccounts: [ConnectedAccountPublicDTO!]!
myCalendarChannels(connectedAccountId: UUID): [CalendarChannel!]!
minimalMetadata: MinimalMetadata!
appConnections(filter: ListAppConnectionsInput): [AppConnection!]!
@@ -3262,7 +3262,6 @@ type Mutation {
upsertRowLevelPermissionPredicates(input: UpsertRowLevelPermissionPredicatesInput!): UpsertRowLevelPermissionPredicatesResult!
assignRoleToAgent(agentId: UUID!, roleId: UUID!): Boolean!
removeRoleFromAgent(agentId: UUID!): Boolean!
deleteConnectedAccount(id: UUID!): ConnectedAccountPublicDTO!
runAgent(input: RunAgentInput!): RunAgentResult!
createWebhook(input: CreateWebhookInput!): Webhook!
updateWebhook(input: UpdateWebhookInput!): Webhook!
@@ -3281,6 +3280,7 @@ type Mutation {
updateMessageChannel(input: UpdateMessageChannelInput!): MessageChannel!
createEmailGroupChannel(input: CreateEmailGroupChannelInput!): CreateEmailGroupChannelOutput!
deleteEmailGroupChannel(id: UUID!): MessageChannel!
deleteConnectedAccount(id: UUID!): ConnectedAccountPublicDTO!
updateCalendarChannel(input: UpdateCalendarChannelInput!): CalendarChannel!
createChatThread: AgentChatThread!
sendChatMessage(threadId: UUID!, text: String!, messageId: UUID!, browsingContext: JSON, modelId: String, fileAttachments: [FileAttachmentInput!]): SendChatMessageResult!
@@ -2614,7 +2614,6 @@ export interface Query {
findManyAgents: Agent[]
findOneAgent: Agent
getRoles: Role[]
myConnectedAccounts: ConnectedAccountPublicDTO[]
getToolIndex: ToolIndexEntry[]
getToolInputSchema?: Scalars['JSON']
webhooks: Webhook[]
@@ -2625,6 +2624,7 @@ export interface Query {
getViewGroup?: ViewGroup
myMessageFolders: MessageFolder[]
myMessageChannels: MessageChannel[]
myConnectedAccounts: ConnectedAccountPublicDTO[]
myCalendarChannels: CalendarChannel[]
minimalMetadata: MinimalMetadata
appConnections: AppConnection[]
@@ -2786,7 +2786,6 @@ export interface Mutation {
upsertRowLevelPermissionPredicates: UpsertRowLevelPermissionPredicatesResult
assignRoleToAgent: Scalars['Boolean']
removeRoleFromAgent: Scalars['Boolean']
deleteConnectedAccount: ConnectedAccountPublicDTO
runAgent: RunAgentResult
createWebhook: Webhook
updateWebhook: Webhook
@@ -2805,6 +2804,7 @@ export interface Mutation {
updateMessageChannel: MessageChannel
createEmailGroupChannel: CreateEmailGroupChannelOutput
deleteEmailGroupChannel: MessageChannel
deleteConnectedAccount: ConnectedAccountPublicDTO
updateCalendarChannel: CalendarChannel
createChatThread: AgentChatThread
sendChatMessage: SendChatMessageResult
@@ -5673,7 +5673,6 @@ export interface QueryGenqlSelection{
findManyAgents?: AgentGenqlSelection
findOneAgent?: (AgentGenqlSelection & { __args: {input: AgentIdInput} })
getRoles?: RoleGenqlSelection
myConnectedAccounts?: ConnectedAccountPublicDTOGenqlSelection
getToolIndex?: ToolIndexEntryGenqlSelection
getToolInputSchema?: { __args: {toolName: Scalars['String']} }
webhooks?: WebhookGenqlSelection
@@ -5690,6 +5689,7 @@ export interface QueryGenqlSelection{
getViewGroup?: (ViewGroupGenqlSelection & { __args: {id: Scalars['String']} })
myMessageFolders?: (MessageFolderGenqlSelection & { __args?: {messageChannelId?: (Scalars['UUID'] | null)} })
myMessageChannels?: (MessageChannelGenqlSelection & { __args?: {connectedAccountId?: (Scalars['UUID'] | null)} })
myConnectedAccounts?: ConnectedAccountPublicDTOGenqlSelection
myCalendarChannels?: (CalendarChannelGenqlSelection & { __args?: {connectedAccountId?: (Scalars['UUID'] | null)} })
minimalMetadata?: MinimalMetadataGenqlSelection
appConnections?: (AppConnectionGenqlSelection & { __args?: {filter?: (ListAppConnectionsInput | null)} })
@@ -5874,7 +5874,6 @@ export interface MutationGenqlSelection{
upsertRowLevelPermissionPredicates?: (UpsertRowLevelPermissionPredicatesResultGenqlSelection & { __args: {input: UpsertRowLevelPermissionPredicatesInput} })
assignRoleToAgent?: { __args: {agentId: Scalars['UUID'], roleId: Scalars['UUID']} }
removeRoleFromAgent?: { __args: {agentId: Scalars['UUID']} }
deleteConnectedAccount?: (ConnectedAccountPublicDTOGenqlSelection & { __args: {id: Scalars['UUID']} })
runAgent?: (RunAgentResultGenqlSelection & { __args: {input: RunAgentInput} })
createWebhook?: (WebhookGenqlSelection & { __args: {input: CreateWebhookInput} })
updateWebhook?: (WebhookGenqlSelection & { __args: {input: UpdateWebhookInput} })
@@ -5893,6 +5892,7 @@ export interface MutationGenqlSelection{
updateMessageChannel?: (MessageChannelGenqlSelection & { __args: {input: UpdateMessageChannelInput} })
createEmailGroupChannel?: (CreateEmailGroupChannelOutputGenqlSelection & { __args: {input: CreateEmailGroupChannelInput} })
deleteEmailGroupChannel?: (MessageChannelGenqlSelection & { __args: {id: Scalars['UUID']} })
deleteConnectedAccount?: (ConnectedAccountPublicDTOGenqlSelection & { __args: {id: Scalars['UUID']} })
updateCalendarChannel?: (CalendarChannelGenqlSelection & { __args: {input: UpdateCalendarChannelInput} })
createChatThread?: AgentChatThreadGenqlSelection
sendChatMessage?: (SendChatMessageResultGenqlSelection & { __args: {threadId: Scalars['UUID'], text: Scalars['String'], messageId: Scalars['UUID'], browsingContext?: (Scalars['JSON'] | null), modelId?: (Scalars['String'] | null), fileAttachments?: (FileAttachmentInput[] | null)} })
@@ -6153,9 +6153,6 @@ export default {
"getRoles": [
29
],
"myConnectedAccounts": [
271
],
"getToolIndex": [
277
],
@@ -6235,6 +6232,9 @@ export default {
]
}
],
"myConnectedAccounts": [
271
],
"myCalendarChannels": [
305,
{
@@ -7714,15 +7714,6 @@ export default {
]
}
],
"deleteConnectedAccount": [
271,
{
"id": [
3,
"UUID!"
]
}
],
"runAgent": [
279,
{
@@ -7885,6 +7876,15 @@ export default {
]
}
],
"deleteConnectedAccount": [
271,
{
"id": [
3,
"UUID!"
]
}
],
"updateCalendarChannel": [
305,
{
+3 -39
View File
@@ -6,10 +6,10 @@ services:
volumes:
- server-local-data:/app/packages/twenty-server/.local-storage
ports:
- "3000:3000"
- "3010:3000"
environment:
NODE_PORT: 3000
PG_DATABASE_URL: postgres://${PG_DATABASE_USER:-postgres}:${PG_DATABASE_PASSWORD:-postgres}@${PG_DATABASE_HOST:-db}:${PG_DATABASE_PORT:-5432}/default
PG_DATABASE_URL: postgres://${PG_DATABASE_USER:-postgres}:${PG_DATABASE_PASSWORD:-postgres}@${PG_DATABASE_HOST:-db}:${PG_DATABASE_PORT:-5432}/${PG_DATABASE_NAME}
SERVER_URL: ${SERVER_URL}
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
DISABLE_DB_MIGRATIONS: ${DISABLE_DB_MIGRATIONS}
@@ -46,11 +46,6 @@ services:
# EMAIL_SMTP_USER: ${EMAIL_SMTP_USER:-}
# EMAIL_SMTP_PASSWORD: ${EMAIL_SMTP_PASSWORD:-}
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: curl --fail http://localhost:3000/healthz
interval: 5s
@@ -64,7 +59,7 @@ services:
- server-local-data:/app/packages/twenty-server/.local-storage
command: ["yarn", "worker:prod"]
environment:
PG_DATABASE_URL: postgres://${PG_DATABASE_USER:-postgres}:${PG_DATABASE_PASSWORD:-postgres}@${PG_DATABASE_HOST:-db}:${PG_DATABASE_PORT:-5432}/default
PG_DATABASE_URL: postgres://${PG_DATABASE_USER:-postgres}:${PG_DATABASE_PASSWORD:-postgres}@${PG_DATABASE_HOST:-db}:${PG_DATABASE_PORT:-5432}/${PG_DATABASE_NAME}
SERVER_URL: ${SERVER_URL}
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
DISABLE_DB_MIGRATIONS: "true" # it already runs on the server
@@ -101,38 +96,7 @@ services:
# EMAIL_SMTP_USER: ${EMAIL_SMTP_USER:-}
# EMAIL_SMTP_PASSWORD: ${EMAIL_SMTP_PASSWORD:-}
depends_on:
db:
condition: service_healthy
server:
condition: service_healthy
restart: always
db:
image: postgres:16
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${PG_DATABASE_NAME:-default}
POSTGRES_PASSWORD: ${PG_DATABASE_PASSWORD:-postgres}
POSTGRES_USER: ${PG_DATABASE_USER:-postgres}
healthcheck:
test: pg_isready -U ${PG_DATABASE_USER:-postgres} -h localhost -d postgres
interval: 5s
timeout: 5s
retries: 10
restart: always
redis:
image: redis
restart: always
command: ["--maxmemory-policy", "noeviction"]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 10
volumes:
db-data:
server-local-data:
@@ -1,16 +0,0 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
import { ReassignWorkflowCreatorToEmailAccountOwnerCommand } from 'src/database/commands/upgrade-version-command/2-11/2-11-workspace-command-1799100000000-reassign-workflow-creator-to-email-account-owner.command';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
@Module({
imports: [
TypeOrmModule.forFeature([ConnectedAccountEntity, UserWorkspaceEntity]),
WorkspaceIteratorModule,
],
providers: [ReassignWorkflowCreatorToEmailAccountOwnerCommand],
})
export class V2_11_UpgradeVersionCommandModule {}
@@ -1,253 +0,0 @@
import { InjectRepository } from '@nestjs/typeorm';
import { isNonEmptyString } from '@sniptt/guards';
import { Command } from 'nest-commander';
import { isDefined, isValidUuid } from 'twenty-shared/utils';
import { In, Repository } from 'typeorm';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import {
WorkflowVersionStatus,
type WorkflowVersionWorkspaceEntity,
} from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
import { type WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
const EMAIL_ACTION_TYPES: WorkflowActionType[] = [
WorkflowActionType.SEND_EMAIL,
WorkflowActionType.DRAFT_EMAIL,
];
@RegisteredWorkspaceCommand('2.11.0', 1799100000000)
@Command({
name: 'upgrade:2-11:reassign-workflow-creator-to-email-account-owner',
description:
'Reassign the creator of active workflows whose email steps use a user-visibility connected account owned by another member, so workflow runs can resolve the account through the acting-user access check',
})
export class ReassignWorkflowCreatorToEmailAccountOwnerCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
protected readonly workspaceIteratorService: WorkspaceIteratorService,
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
@InjectRepository(ConnectedAccountEntity)
private readonly connectedAccountRepository: Repository<ConnectedAccountEntity>,
@InjectRepository(UserWorkspaceEntity)
private readonly userWorkspaceRepository: Repository<UserWorkspaceEntity>,
) {
super(workspaceIteratorService);
}
override async runOnWorkspace({
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
const isDryRun = options.dryRun ?? false;
const accountIdsByWorkflowId =
await this.getEmailStepAccountIdsByWorkflowId(workspaceId);
if (accountIdsByWorkflowId.size === 0) {
return;
}
const referencedAccountIds = [
...new Set([...accountIdsByWorkflowId.values()].flat()),
];
const userVisibilityAccounts = await this.connectedAccountRepository.find({
where: {
id: In(referencedAccountIds),
workspaceId,
visibility: 'user',
},
});
if (userVisibilityAccounts.length === 0) {
return;
}
const accountById = new Map(
userVisibilityAccounts.map((account) => [account.id, account]),
);
const workflowRepository =
await this.globalWorkspaceOrmManager.getRepository<WorkflowWorkspaceEntity>(
workspaceId,
'workflow',
{ shouldBypassPermissionChecks: true },
);
const workflows = await workflowRepository.find({
where: { id: In([...accountIdsByWorkflowId.keys()]) },
});
const memberByUserWorkspaceId = await this.getMemberByUserWorkspaceId({
workspaceId,
userWorkspaceIds: userVisibilityAccounts
.map((account) => account.userWorkspaceId)
.filter(isDefined),
});
let reassignedCount = 0;
for (const workflow of workflows) {
const ownerUserWorkspaceIds = [
...new Set(
(accountIdsByWorkflowId.get(workflow.id) ?? [])
.map((accountId) => accountById.get(accountId)?.userWorkspaceId)
.filter(isDefined),
),
];
if (ownerUserWorkspaceIds.length === 0) {
continue;
}
if (ownerUserWorkspaceIds.length > 1) {
this.logger.warn(
`Workflow ${workflow.id} in workspace ${workspaceId} references user-visibility accounts of ${ownerUserWorkspaceIds.length} different owners; skipping`,
);
continue;
}
const ownerMember = memberByUserWorkspaceId.get(ownerUserWorkspaceIds[0]);
if (!isDefined(ownerMember)) {
this.logger.warn(
`Workflow ${workflow.id} in workspace ${workspaceId} references an account whose owner has no workspace member; skipping`,
);
continue;
}
if (workflow.createdBy?.workspaceMemberId === ownerMember.id) {
continue;
}
const ownerFullName =
`${ownerMember.name?.firstName ?? ''} ${ownerMember.name?.lastName ?? ''}`.trim();
this.logger.log(
`${isDryRun ? '[DRY RUN] ' : ''}Reassigning creator of workflow ${workflow.id} in workspace ${workspaceId} to account owner member ${ownerMember.id}`,
);
if (isDryRun) {
continue;
}
await workflowRepository.update(workflow.id, {
createdBy: {
...workflow.createdBy,
workspaceMemberId: ownerMember.id,
name: ownerFullName,
},
});
reassignedCount++;
}
if (reassignedCount > 0) {
this.logger.log(
`Reassigned creator on ${reassignedCount} workflow(s) for workspace ${workspaceId}`,
);
}
}
private async getEmailStepAccountIdsByWorkflowId(
workspaceId: string,
): Promise<Map<string, string[]>> {
const workflowVersionRepository =
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
workspaceId,
'workflowVersion',
{ shouldBypassPermissionChecks: true },
);
const activeVersions = await workflowVersionRepository.find({
where: { status: WorkflowVersionStatus.ACTIVE },
});
const accountIdsByWorkflowId = new Map<string, string[]>();
for (const version of activeVersions) {
if (!Array.isArray(version.steps)) {
continue;
}
const accountIds = version.steps
.filter((step: WorkflowAction) =>
EMAIL_ACTION_TYPES.includes(step.type),
)
.map(
(step: WorkflowAction) =>
(step.settings?.input as { connectedAccountId?: string })
?.connectedAccountId,
)
.filter(
(accountId): accountId is string =>
isNonEmptyString(accountId) && isValidUuid(accountId),
);
if (accountIds.length === 0) {
continue;
}
accountIdsByWorkflowId.set(version.workflowId, [
...(accountIdsByWorkflowId.get(version.workflowId) ?? []),
...accountIds,
]);
}
return accountIdsByWorkflowId;
}
private async getMemberByUserWorkspaceId({
workspaceId,
userWorkspaceIds,
}: {
workspaceId: string;
userWorkspaceIds: string[];
}): Promise<Map<string, WorkspaceMemberWorkspaceEntity>> {
if (userWorkspaceIds.length === 0) {
return new Map();
}
const userWorkspaces = await this.userWorkspaceRepository.find({
where: { id: In(userWorkspaceIds), workspaceId },
});
const workspaceMemberRepository =
await this.globalWorkspaceOrmManager.getRepository<WorkspaceMemberWorkspaceEntity>(
workspaceId,
'workspaceMember',
{ shouldBypassPermissionChecks: true },
);
const members = await workspaceMemberRepository.find({
where: { userId: In(userWorkspaces.map((uw) => uw.userId)) },
});
const memberByUserId = new Map(
members.map((member) => [member.userId, member]),
);
return new Map(
userWorkspaces
.map(
(userWorkspace): [string, WorkspaceMemberWorkspaceEntity] | null => {
const member = memberByUserId.get(userWorkspace.userId);
return isDefined(member) ? [userWorkspace.id, member] : null;
},
)
.filter(isDefined),
);
}
}
@@ -13,7 +13,6 @@ import { V2_7_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-
import { V2_8_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-8/2-8-upgrade-version-command.module';
import { V2_9_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-9/2-9-upgrade-version-command.module';
import { V2_10_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-10/2-10-upgrade-version-command.module';
import { V2_11_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-11/2-11-upgrade-version-command.module';
@Module({
imports: [
@@ -30,7 +29,6 @@ import { V2_11_UpgradeVersionCommandModule } from 'src/database/commands/upgrade
V2_8_UpgradeVersionCommandModule,
V2_9_UpgradeVersionCommandModule,
V2_10_UpgradeVersionCommandModule,
V2_11_UpgradeVersionCommandModule,
],
})
export class WorkspaceCommandProviderModule {}
@@ -56,14 +56,19 @@ export const typeORMCoreModuleOptions: TypeOrmModuleOptions = {
migrationsRun: false,
migrationsTableName: '_typeorm_migrations',
metadataTableName: '_typeorm_generated_columns_and_materialized_views',
// The TypeORM migration system is frozen — historical migrations live in
// `legacy-typeorm-migrations-do-not-add/` and are loaded here only so the
// `_typeorm_migrations` table stays consistent for older deployments.
// Do NOT add new files there: write a fast/slow instance command instead.
// See `packages/twenty-server/docs/UPGRADE_COMMANDS.md`.
migrations:
process.env.IS_BILLING_ENABLED === 'true'
? [
`${isJest ? 'src/' : 'dist/'}database/typeorm/core/migrations/common/*{.ts,.js}`,
`${isJest ? 'src/' : 'dist/'}database/typeorm/core/migrations/billing/*{.ts,.js}`,
`${isJest ? 'src/' : 'dist/'}database/typeorm/core/legacy-typeorm-migrations-do-not-add/common/*{.ts,.js}`,
`${isJest ? 'src/' : 'dist/'}database/typeorm/core/legacy-typeorm-migrations-do-not-add/billing/*{.ts,.js}`,
]
: [
`${isJest ? 'src/' : 'dist/'}database/typeorm/core/migrations/common/*{.ts,.js}`,
`${isJest ? 'src/' : 'dist/'}database/typeorm/core/legacy-typeorm-migrations-do-not-add/common/*{.ts,.js}`,
],
ssl:
process.env.PG_SSL_ALLOW_SELF_SIGNED === 'true'
@@ -0,0 +1,23 @@
# Legacy TypeORM migrations — do not add new files here
This directory contains historical TypeORM migrations (`common/` and `billing/`).
They are kept so that older deployments can still replay them against the
`_typeorm_migrations` table.
**The TypeORM migration system is frozen.** Do not add new files here.
The active upgrade system is **instance commands** (fast / slow) and
**workspace commands**, registered with `@RegisteredInstanceCommand` and
`@RegisteredWorkspaceCommand`. Generate one with:
```bash
npx nx run twenty-server:database:migrate:generate --name <name> --type <fast|slow>
```
See `packages/twenty-server/docs/UPGRADE_COMMANDS.md` for the full guide and
`packages/twenty-server/src/database/commands/upgrade-version-command/` for
existing examples.
Note: `../migrations/utils/` is **not** legacy — those SQL helpers are still
imported by current instance/workspace commands and live outside this folder
on purpose.

Some files were not shown because too many files have changed in this diff Show More