Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d5d31eca2 | ||
|
|
3b109ec14e | ||
|
|
7781dd8457 | ||
|
|
e5ed2889ed | ||
|
|
026b8b5c34 | ||
|
|
5a6813d9e0 |
@@ -3008,6 +3008,7 @@ type Query {
|
||||
findManyAgents: [Agent!]!
|
||||
findOneAgent(input: AgentIdInput!): Agent!
|
||||
getRoles: [Role!]!
|
||||
myConnectedAccounts: [ConnectedAccountPublicDTO!]!
|
||||
getToolIndex: [ToolIndexEntry!]!
|
||||
getToolInputSchema(toolName: String!): JSON
|
||||
webhooks: [Webhook!]!
|
||||
@@ -3027,7 +3028,6 @@ 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,6 +3262,7 @@ 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!
|
||||
@@ -3280,7 +3281,6 @@ 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,6 +2614,7 @@ export interface Query {
|
||||
findManyAgents: Agent[]
|
||||
findOneAgent: Agent
|
||||
getRoles: Role[]
|
||||
myConnectedAccounts: ConnectedAccountPublicDTO[]
|
||||
getToolIndex: ToolIndexEntry[]
|
||||
getToolInputSchema?: Scalars['JSON']
|
||||
webhooks: Webhook[]
|
||||
@@ -2624,7 +2625,6 @@ export interface Query {
|
||||
getViewGroup?: ViewGroup
|
||||
myMessageFolders: MessageFolder[]
|
||||
myMessageChannels: MessageChannel[]
|
||||
myConnectedAccounts: ConnectedAccountPublicDTO[]
|
||||
myCalendarChannels: CalendarChannel[]
|
||||
minimalMetadata: MinimalMetadata
|
||||
appConnections: AppConnection[]
|
||||
@@ -2786,6 +2786,7 @@ export interface Mutation {
|
||||
upsertRowLevelPermissionPredicates: UpsertRowLevelPermissionPredicatesResult
|
||||
assignRoleToAgent: Scalars['Boolean']
|
||||
removeRoleFromAgent: Scalars['Boolean']
|
||||
deleteConnectedAccount: ConnectedAccountPublicDTO
|
||||
runAgent: RunAgentResult
|
||||
createWebhook: Webhook
|
||||
updateWebhook: Webhook
|
||||
@@ -2804,7 +2805,6 @@ export interface Mutation {
|
||||
updateMessageChannel: MessageChannel
|
||||
createEmailGroupChannel: CreateEmailGroupChannelOutput
|
||||
deleteEmailGroupChannel: MessageChannel
|
||||
deleteConnectedAccount: ConnectedAccountPublicDTO
|
||||
updateCalendarChannel: CalendarChannel
|
||||
createChatThread: AgentChatThread
|
||||
sendChatMessage: SendChatMessageResult
|
||||
@@ -5673,6 +5673,7 @@ export interface QueryGenqlSelection{
|
||||
findManyAgents?: AgentGenqlSelection
|
||||
findOneAgent?: (AgentGenqlSelection & { __args: {input: AgentIdInput} })
|
||||
getRoles?: RoleGenqlSelection
|
||||
myConnectedAccounts?: ConnectedAccountPublicDTOGenqlSelection
|
||||
getToolIndex?: ToolIndexEntryGenqlSelection
|
||||
getToolInputSchema?: { __args: {toolName: Scalars['String']} }
|
||||
webhooks?: WebhookGenqlSelection
|
||||
@@ -5689,7 +5690,6 @@ 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,6 +5874,7 @@ 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} })
|
||||
@@ -5892,7 +5893,6 @@ 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,6 +6153,9 @@ export default {
|
||||
"getRoles": [
|
||||
29
|
||||
],
|
||||
"myConnectedAccounts": [
|
||||
271
|
||||
],
|
||||
"getToolIndex": [
|
||||
277
|
||||
],
|
||||
@@ -6232,9 +6235,6 @@ export default {
|
||||
]
|
||||
}
|
||||
],
|
||||
"myConnectedAccounts": [
|
||||
271
|
||||
],
|
||||
"myCalendarChannels": [
|
||||
305,
|
||||
{
|
||||
@@ -7714,6 +7714,15 @@ export default {
|
||||
]
|
||||
}
|
||||
],
|
||||
"deleteConnectedAccount": [
|
||||
271,
|
||||
{
|
||||
"id": [
|
||||
3,
|
||||
"UUID!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"runAgent": [
|
||||
279,
|
||||
{
|
||||
@@ -7876,15 +7885,6 @@ export default {
|
||||
]
|
||||
}
|
||||
],
|
||||
"deleteConnectedAccount": [
|
||||
271,
|
||||
{
|
||||
"id": [
|
||||
3,
|
||||
"UUID!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"updateCalendarChannel": [
|
||||
305,
|
||||
{
|
||||
|
||||
@@ -6,10 +6,10 @@ services:
|
||||
volumes:
|
||||
- server-local-data:/app/packages/twenty-server/.local-storage
|
||||
ports:
|
||||
- "3010:3000"
|
||||
- "3000: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}/${PG_DATABASE_NAME}
|
||||
PG_DATABASE_URL: postgres://${PG_DATABASE_USER:-postgres}:${PG_DATABASE_PASSWORD:-postgres}@${PG_DATABASE_HOST:-db}:${PG_DATABASE_PORT:-5432}/default
|
||||
SERVER_URL: ${SERVER_URL}
|
||||
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
|
||||
DISABLE_DB_MIGRATIONS: ${DISABLE_DB_MIGRATIONS}
|
||||
@@ -46,6 +46,11 @@ 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
|
||||
@@ -59,7 +64,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}/${PG_DATABASE_NAME}
|
||||
PG_DATABASE_URL: postgres://${PG_DATABASE_USER:-postgres}:${PG_DATABASE_PASSWORD:-postgres}@${PG_DATABASE_HOST:-db}:${PG_DATABASE_PORT:-5432}/default
|
||||
SERVER_URL: ${SERVER_URL}
|
||||
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
|
||||
DISABLE_DB_MIGRATIONS: "true" # it already runs on the server
|
||||
@@ -96,7 +101,38 @@ 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:
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
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 {}
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
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),
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
@@ -13,6 +13,7 @@ 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: [
|
||||
@@ -29,6 +30,7 @@ import { V2_10_UpgradeVersionCommandModule } from 'src/database/commands/upgrade
|
||||
V2_8_UpgradeVersionCommandModule,
|
||||
V2_9_UpgradeVersionCommandModule,
|
||||
V2_10_UpgradeVersionCommandModule,
|
||||
V2_11_UpgradeVersionCommandModule,
|
||||
],
|
||||
})
|
||||
export class WorkspaceCommandProviderModule {}
|
||||
|
||||
@@ -56,19 +56,14 @@ 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/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/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/migrations/common/*{.ts,.js}`,
|
||||
],
|
||||
ssl:
|
||||
process.env.PG_SSL_ALLOW_SELF_SIGNED === 'true'
|
||||
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
# 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
Reference in New Issue
Block a user