2024-09-12 09:07:18 -07:00
|
|
|
import type { Scope } from '@n8n/permissions';
|
|
|
|
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
|
|
|
|
import type { EntityManager } from '@n8n/typeorm';
|
|
|
|
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
|
|
|
|
import { In } from '@n8n/typeorm';
|
2023-12-13 03:41:06 -08:00
|
|
|
import omit from 'lodash/omit';
|
2024-09-12 09:07:18 -07:00
|
|
|
import pick from 'lodash/pick';
|
2024-01-17 01:16:13 -08:00
|
|
|
import { BinaryDataService } from 'n8n-core';
|
2024-09-12 09:07:18 -07:00
|
|
|
import { NodeApiError } from 'n8n-workflow';
|
|
|
|
import { Service } from 'typedi';
|
|
|
|
import { v4 as uuid } from 'uuid';
|
2024-01-17 01:16:13 -08:00
|
|
|
|
2024-09-12 09:07:18 -07:00
|
|
|
import { ActiveWorkflowManager } from '@/active-workflow-manager';
|
2022-11-09 06:25:00 -08:00
|
|
|
import config from '@/config';
|
2024-09-12 09:07:18 -07:00
|
|
|
import { SharedWorkflow } from '@/databases/entities/shared-workflow';
|
2024-08-28 08:57:46 -07:00
|
|
|
import type { User } from '@/databases/entities/user';
|
2024-08-27 08:24:20 -07:00
|
|
|
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
|
2024-09-12 09:07:18 -07:00
|
|
|
import { ExecutionRepository } from '@/databases/repositories/execution.repository';
|
2024-08-27 07:44:32 -07:00
|
|
|
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
|
|
|
|
import { WorkflowTagMappingRepository } from '@/databases/repositories/workflow-tag-mapping.repository';
|
2024-08-27 08:24:20 -07:00
|
|
|
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
2024-09-12 09:07:18 -07:00
|
|
|
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
|
|
|
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
|
|
|
import { EventService } from '@/events/event.service';
|
2024-08-22 02:10:37 -07:00
|
|
|
import { ExternalHooks } from '@/external-hooks';
|
2024-09-12 09:07:18 -07:00
|
|
|
import { validateEntity } from '@/generic-helpers';
|
2024-10-01 03:16:09 -07:00
|
|
|
import { Logger } from '@/logging/logger.service';
|
2024-09-12 09:07:18 -07:00
|
|
|
import { hasSharing, type ListQuery } from '@/requests';
|
2024-01-22 02:16:29 -08:00
|
|
|
import { OrchestrationService } from '@/services/orchestration.service';
|
2024-09-12 09:07:18 -07:00
|
|
|
import { OwnershipService } from '@/services/ownership.service';
|
|
|
|
import { ProjectService } from '@/services/project.service';
|
2024-05-17 01:53:15 -07:00
|
|
|
import { RoleService } from '@/services/role.service';
|
2024-09-12 09:07:18 -07:00
|
|
|
import { TagService } from '@/services/tag.service';
|
|
|
|
import * as WorkflowHelpers from '@/workflow-helpers';
|
|
|
|
|
|
|
|
import { WorkflowHistoryService } from './workflow-history/workflow-history.service.ee';
|
2024-08-22 02:10:37 -07:00
|
|
|
import { WorkflowSharingService } from './workflow-sharing.service';
|
2022-10-11 05:55:05 -07:00
|
|
|
|
2023-12-15 03:59:56 -08:00
|
|
|
@Service()
|
|
|
|
export class WorkflowService {
|
2023-12-18 07:10:30 -08:00
|
|
|
constructor(
|
|
|
|
private readonly logger: Logger,
|
|
|
|
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
|
|
|
|
private readonly workflowRepository: WorkflowRepository,
|
|
|
|
private readonly workflowTagMappingRepository: WorkflowTagMappingRepository,
|
|
|
|
private readonly binaryDataService: BinaryDataService,
|
|
|
|
private readonly ownershipService: OwnershipService,
|
|
|
|
private readonly tagService: TagService,
|
|
|
|
private readonly workflowHistoryService: WorkflowHistoryService,
|
2024-01-22 02:16:29 -08:00
|
|
|
private readonly orchestrationService: OrchestrationService,
|
2023-12-18 07:10:30 -08:00
|
|
|
private readonly externalHooks: ExternalHooks,
|
2024-05-06 08:54:05 -07:00
|
|
|
private readonly activeWorkflowManager: ActiveWorkflowManager,
|
2024-05-17 01:53:15 -07:00
|
|
|
private readonly roleService: RoleService,
|
|
|
|
private readonly workflowSharingService: WorkflowSharingService,
|
|
|
|
private readonly projectService: ProjectService,
|
|
|
|
private readonly executionRepository: ExecutionRepository,
|
2024-07-19 03:55:38 -07:00
|
|
|
private readonly eventService: EventService,
|
2023-12-18 07:10:30 -08:00
|
|
|
) {}
|
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
async getMany(user: User, options?: ListQuery.Options, includeScopes?: boolean) {
|
|
|
|
const sharedWorkflowIds = await this.workflowSharingService.getSharedWorkflowIds(user, {
|
|
|
|
scopes: ['workflow:read'],
|
|
|
|
});
|
|
|
|
|
|
|
|
// eslint-disable-next-line prefer-const
|
|
|
|
let { workflows, count } = await this.workflowRepository.getMany(sharedWorkflowIds, options);
|
|
|
|
|
|
|
|
if (hasSharing(workflows)) {
|
|
|
|
workflows = workflows.map((w) => this.ownershipService.addOwnedByAndSharedWith(w));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (includeScopes) {
|
|
|
|
const projectRelations = await this.projectService.getProjectRelationsForUser(user);
|
|
|
|
workflows = workflows.map((w) => this.roleService.addScopes(w, user, projectRelations));
|
|
|
|
}
|
|
|
|
|
|
|
|
workflows.forEach((w) => {
|
|
|
|
// @ts-expect-error: This is to emulate the old behaviour of removing the shared
|
|
|
|
// field as part of `addOwnedByAndSharedWith`. We need this field in `addScopes`
|
|
|
|
// though. So to avoid leaking the information we just delete it.
|
|
|
|
delete w.shared;
|
|
|
|
});
|
2023-08-22 04:19:37 -07:00
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
return { workflows, count };
|
2022-11-08 08:52:42 -08:00
|
|
|
}
|
|
|
|
|
2024-04-10 05:02:02 -07:00
|
|
|
// eslint-disable-next-line complexity
|
2023-12-15 03:59:56 -08:00
|
|
|
async update(
|
2022-10-26 06:49:43 -07:00
|
|
|
user: User,
|
2024-05-17 01:53:15 -07:00
|
|
|
workflowUpdateData: WorkflowEntity,
|
2022-10-26 06:49:43 -07:00
|
|
|
workflowId: string,
|
2023-03-30 07:25:51 -07:00
|
|
|
tagIds?: string[],
|
2022-10-31 02:35:24 -07:00
|
|
|
forceSave?: boolean,
|
2022-10-26 06:49:43 -07:00
|
|
|
): Promise<WorkflowEntity> {
|
2024-05-17 01:53:15 -07:00
|
|
|
const workflow = await this.sharedWorkflowRepository.findWorkflowForUser(workflowId, user, [
|
2023-12-28 04:14:10 -08:00
|
|
|
'workflow:update',
|
2024-05-17 01:53:15 -07:00
|
|
|
]);
|
2022-10-26 06:49:43 -07:00
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
if (!workflow) {
|
2024-08-28 00:32:53 -07:00
|
|
|
this.logger.warn('User attempted to update a workflow without permissions', {
|
2022-10-26 06:49:43 -07:00
|
|
|
workflowId,
|
|
|
|
userId: user.id,
|
|
|
|
});
|
2023-11-28 01:19:27 -08:00
|
|
|
throw new NotFoundError(
|
2022-11-22 04:05:51 -08:00
|
|
|
'You do not have permission to update this workflow. Ask the owner to share it with you.',
|
2022-10-26 06:49:43 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-12-06 00:25:39 -08:00
|
|
|
if (
|
|
|
|
!forceSave &&
|
2024-05-17 01:53:15 -07:00
|
|
|
workflowUpdateData.versionId !== '' &&
|
|
|
|
workflowUpdateData.versionId !== workflow.versionId
|
2022-12-06 00:25:39 -08:00
|
|
|
) {
|
2023-11-28 01:19:27 -08:00
|
|
|
throw new BadRequestError(
|
2022-11-28 12:05:19 -08:00
|
|
|
'Your most recent changes may be lost, because someone else just updated this workflow. Open this workflow in a new tab to see those new updates.',
|
2022-12-06 00:25:39 -08:00
|
|
|
100,
|
2022-11-14 06:38:19 -08:00
|
|
|
);
|
|
|
|
}
|
2022-10-31 02:35:24 -07:00
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
if (Object.keys(omit(workflowUpdateData, ['id', 'versionId', 'active'])).length > 0) {
|
2023-12-13 03:41:06 -08:00
|
|
|
// Update the workflow's version when changing properties such as
|
|
|
|
// `name`, `pinData`, `nodes`, `connections`, `settings` or `tags`
|
2024-05-17 01:53:15 -07:00
|
|
|
workflowUpdateData.versionId = uuid();
|
2024-08-28 00:32:53 -07:00
|
|
|
this.logger.debug(
|
2023-07-26 00:25:01 -07:00
|
|
|
`Updating versionId for workflow ${workflowId} for user ${user.id} after saving`,
|
|
|
|
{
|
2024-05-17 01:53:15 -07:00
|
|
|
previousVersionId: workflow.versionId,
|
|
|
|
newVersionId: workflowUpdateData.versionId,
|
2023-07-26 00:25:01 -07:00
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
2022-12-06 00:25:39 -08:00
|
|
|
|
2022-10-26 06:49:43 -07:00
|
|
|
// check credentials for old format
|
2024-05-17 01:53:15 -07:00
|
|
|
await WorkflowHelpers.replaceInvalidCredentials(workflowUpdateData);
|
2022-10-26 06:49:43 -07:00
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
WorkflowHelpers.addNodeIds(workflowUpdateData);
|
2022-10-26 06:49:43 -07:00
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
await this.externalHooks.run('workflow.update', [workflowUpdateData]);
|
2022-10-26 06:49:43 -07:00
|
|
|
|
2023-11-17 06:58:50 -08:00
|
|
|
/**
|
|
|
|
* If the workflow being updated is stored as `active`, remove it from
|
|
|
|
* active workflows in memory, and re-add it after the update.
|
|
|
|
*
|
|
|
|
* If a trigger or poller in the workflow was updated, the new value
|
|
|
|
* will take effect only on removing and re-adding.
|
|
|
|
*/
|
2024-05-17 01:53:15 -07:00
|
|
|
if (workflow.active) {
|
2024-05-06 08:54:05 -07:00
|
|
|
await this.activeWorkflowManager.remove(workflowId);
|
2022-10-26 06:49:43 -07:00
|
|
|
}
|
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
const workflowSettings = workflowUpdateData.settings ?? {};
|
2023-03-24 05:11:48 -07:00
|
|
|
|
|
|
|
const keysAllowingDefault = [
|
|
|
|
'timezone',
|
|
|
|
'saveDataErrorExecution',
|
|
|
|
'saveDataSuccessExecution',
|
|
|
|
'saveManualExecutions',
|
|
|
|
'saveExecutionProgress',
|
|
|
|
] as const;
|
|
|
|
for (const key of keysAllowingDefault) {
|
|
|
|
// Do not save the default value
|
|
|
|
if (workflowSettings[key] === 'DEFAULT') {
|
|
|
|
delete workflowSettings[key];
|
2022-10-26 06:49:43 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-24 05:11:48 -07:00
|
|
|
if (workflowSettings.executionTimeout === config.get('executions.timeout')) {
|
|
|
|
// Do not save when default got set
|
|
|
|
delete workflowSettings.executionTimeout;
|
|
|
|
}
|
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
if (workflowUpdateData.name) {
|
|
|
|
workflowUpdateData.updatedAt = new Date(); // required due to atomic update
|
|
|
|
await validateEntity(workflowUpdateData);
|
2022-10-26 06:49:43 -07:00
|
|
|
}
|
|
|
|
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.workflowRepository.update(
|
2022-11-21 06:51:23 -08:00
|
|
|
workflowId,
|
2024-05-17 01:53:15 -07:00
|
|
|
pick(workflowUpdateData, [
|
2022-11-21 06:51:23 -08:00
|
|
|
'name',
|
|
|
|
'active',
|
|
|
|
'nodes',
|
|
|
|
'connections',
|
2024-01-08 03:59:04 -08:00
|
|
|
'meta',
|
2022-11-21 06:51:23 -08:00
|
|
|
'settings',
|
|
|
|
'staticData',
|
|
|
|
'pinData',
|
2022-12-06 00:25:39 -08:00
|
|
|
'versionId',
|
2022-11-21 06:51:23 -08:00
|
|
|
]),
|
|
|
|
);
|
2022-10-26 06:49:43 -07:00
|
|
|
|
2023-03-30 07:25:51 -07:00
|
|
|
if (tagIds && !config.getEnv('workflowTagsDisabled')) {
|
2024-02-02 03:36:55 -08:00
|
|
|
await this.workflowTagMappingRepository.overwriteTaggings(workflowId, tagIds);
|
2022-10-26 06:49:43 -07:00
|
|
|
}
|
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
if (workflowUpdateData.versionId !== workflow.versionId) {
|
|
|
|
await this.workflowHistoryService.saveVersion(user, workflowUpdateData, workflowId);
|
2023-09-27 07:22:39 -07:00
|
|
|
}
|
|
|
|
|
2023-01-02 08:42:32 -08:00
|
|
|
const relations = config.getEnv('workflowTagsDisabled') ? [] : ['tags'];
|
2022-10-26 06:49:43 -07:00
|
|
|
|
|
|
|
// We sadly get nothing back from "update". Neither if it updated a record
|
|
|
|
// nor the new value. So query now the hopefully updated entry.
|
2023-12-18 07:10:30 -08:00
|
|
|
const updatedWorkflow = await this.workflowRepository.findOne({
|
2023-01-13 09:12:22 -08:00
|
|
|
where: { id: workflowId },
|
|
|
|
relations,
|
|
|
|
});
|
2022-10-26 06:49:43 -07:00
|
|
|
|
2023-01-13 09:12:22 -08:00
|
|
|
if (updatedWorkflow === null) {
|
2023-11-28 01:19:27 -08:00
|
|
|
throw new BadRequestError(
|
2022-10-26 06:49:43 -07:00
|
|
|
`Workflow with ID "${workflowId}" could not be found to be updated.`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-03-30 07:25:51 -07:00
|
|
|
if (updatedWorkflow.tags?.length && tagIds?.length) {
|
2023-12-18 07:10:30 -08:00
|
|
|
updatedWorkflow.tags = this.tagService.sortByRequestOrder(updatedWorkflow.tags, {
|
2023-03-30 07:25:51 -07:00
|
|
|
requestOrder: tagIds,
|
2022-10-26 06:49:43 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.externalHooks.run('workflow.afterUpdate', [updatedWorkflow]);
|
2024-07-19 03:55:38 -07:00
|
|
|
this.eventService.emit('workflow-saved', {
|
2024-06-20 03:32:22 -07:00
|
|
|
user,
|
2024-08-01 04:44:23 -07:00
|
|
|
workflow: updatedWorkflow,
|
|
|
|
publicApi: false,
|
2024-06-20 03:32:22 -07:00
|
|
|
});
|
2022-10-26 06:49:43 -07:00
|
|
|
|
|
|
|
if (updatedWorkflow.active) {
|
|
|
|
// When the workflow is supposed to be active add it again
|
|
|
|
try {
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.externalHooks.run('workflow.activate', [updatedWorkflow]);
|
2024-05-17 01:53:15 -07:00
|
|
|
await this.activeWorkflowManager.add(workflowId, workflow.active ? 'update' : 'activate');
|
2022-10-26 06:49:43 -07:00
|
|
|
} catch (error) {
|
|
|
|
// If workflow could not be activated set it again to inactive
|
2023-02-20 03:22:27 -08:00
|
|
|
// and revert the versionId change so UI remains consistent
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.workflowRepository.update(workflowId, {
|
2023-02-20 03:22:27 -08:00
|
|
|
active: false,
|
2024-05-17 01:53:15 -07:00
|
|
|
versionId: workflow.versionId,
|
2023-02-20 03:22:27 -08:00
|
|
|
});
|
2022-10-26 06:49:43 -07:00
|
|
|
|
|
|
|
// Also set it in the returned data
|
|
|
|
updatedWorkflow.active = false;
|
|
|
|
|
2023-02-01 16:00:24 -08:00
|
|
|
let message;
|
|
|
|
if (error instanceof NodeApiError) message = error.description;
|
|
|
|
message = message ?? (error as Error).message;
|
|
|
|
|
2022-10-26 06:49:43 -07:00
|
|
|
// Now return the original error for UI to display
|
2023-11-28 01:19:27 -08:00
|
|
|
throw new BadRequestError(message);
|
2022-10-26 06:49:43 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-22 02:16:29 -08:00
|
|
|
await this.orchestrationService.init();
|
2023-11-17 06:58:50 -08:00
|
|
|
|
2022-10-26 06:49:43 -07:00
|
|
|
return updatedWorkflow;
|
|
|
|
}
|
2022-11-11 02:14:45 -08:00
|
|
|
|
2023-12-15 03:59:56 -08:00
|
|
|
async delete(user: User, workflowId: string): Promise<WorkflowEntity | undefined> {
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.externalHooks.run('workflow.delete', [workflowId]);
|
2023-01-10 00:23:44 -08:00
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
const workflow = await this.sharedWorkflowRepository.findWorkflowForUser(workflowId, user, [
|
2023-12-28 04:14:10 -08:00
|
|
|
'workflow:delete',
|
2024-05-17 01:53:15 -07:00
|
|
|
]);
|
2023-01-10 00:23:44 -08:00
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
if (!workflow) {
|
2023-01-10 00:23:44 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
if (workflow.active) {
|
2023-01-10 00:23:44 -08:00
|
|
|
// deactivate before deleting
|
2024-05-06 08:54:05 -07:00
|
|
|
await this.activeWorkflowManager.remove(workflowId);
|
2023-01-10 00:23:44 -08:00
|
|
|
}
|
|
|
|
|
2023-12-18 07:10:30 -08:00
|
|
|
const idsForDeletion = await this.executionRepository
|
2023-11-10 06:04:26 -08:00
|
|
|
.find({
|
|
|
|
select: ['id'],
|
|
|
|
where: { workflowId },
|
|
|
|
})
|
|
|
|
.then((rows) => rows.map(({ id: executionId }) => ({ workflowId, executionId })));
|
2023-10-10 01:06:06 -07:00
|
|
|
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.workflowRepository.delete(workflowId);
|
|
|
|
await this.binaryDataService.deleteMany(idsForDeletion);
|
2023-01-10 00:23:44 -08:00
|
|
|
|
2024-08-01 04:44:23 -07:00
|
|
|
this.eventService.emit('workflow-deleted', { user, workflowId, publicApi: false });
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.externalHooks.run('workflow.afterDelete', [workflowId]);
|
2023-01-10 00:23:44 -08:00
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
return workflow;
|
|
|
|
}
|
|
|
|
|
|
|
|
async getWorkflowScopes(user: User, workflowId: string): Promise<Scope[]> {
|
|
|
|
const userProjectRelations = await this.projectService.getProjectRelationsForUser(user);
|
|
|
|
const shared = await this.sharedWorkflowRepository.find({
|
|
|
|
where: {
|
|
|
|
projectId: In([...new Set(userProjectRelations.map((pr) => pr.projectId))]),
|
|
|
|
workflowId,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
return this.roleService.combineResourceScopes('workflow', user, shared, userProjectRelations);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Transfers all workflows owned by a project to another one.
|
|
|
|
* This has only been tested for personal projects. It may need to be amended
|
|
|
|
* for team projects.
|
|
|
|
**/
|
|
|
|
async transferAll(fromProjectId: string, toProjectId: string, trx?: EntityManager) {
|
|
|
|
trx = trx ?? this.workflowRepository.manager;
|
|
|
|
|
|
|
|
// Get all shared workflows for both projects.
|
|
|
|
const allSharedWorkflows = await trx.findBy(SharedWorkflow, {
|
|
|
|
projectId: In([fromProjectId, toProjectId]),
|
|
|
|
});
|
|
|
|
const sharedWorkflowsOfFromProject = allSharedWorkflows.filter(
|
|
|
|
(sw) => sw.projectId === fromProjectId,
|
|
|
|
);
|
|
|
|
|
|
|
|
// For all workflows that the from-project owns transfer the ownership to
|
|
|
|
// the to-project.
|
|
|
|
// This will override whatever relationship the to-project already has to
|
|
|
|
// the resources at the moment.
|
|
|
|
|
|
|
|
const ownedWorkflowIds = sharedWorkflowsOfFromProject
|
|
|
|
.filter((sw) => sw.role === 'workflow:owner')
|
|
|
|
.map((sw) => sw.workflowId);
|
|
|
|
|
|
|
|
await this.sharedWorkflowRepository.makeOwner(ownedWorkflowIds, toProjectId, trx);
|
|
|
|
|
|
|
|
// Delete the relationship to the from-project.
|
|
|
|
await this.sharedWorkflowRepository.deleteByIds(ownedWorkflowIds, fromProjectId, trx);
|
|
|
|
|
|
|
|
// Transfer relationships that are not `workflow:owner`.
|
|
|
|
// This will NOT override whatever relationship the from-project already
|
|
|
|
// has to the resource at the moment.
|
|
|
|
const sharedWorkflowIdsOfTransferee = allSharedWorkflows
|
|
|
|
.filter((sw) => sw.projectId === toProjectId)
|
|
|
|
.map((sw) => sw.workflowId);
|
|
|
|
|
|
|
|
// All resources that are shared with the from-project, but not with the
|
|
|
|
// to-project.
|
|
|
|
const sharedWorkflowsToTransfer = sharedWorkflowsOfFromProject.filter(
|
|
|
|
(sw) =>
|
|
|
|
sw.role !== 'workflow:owner' && !sharedWorkflowIdsOfTransferee.includes(sw.workflowId),
|
|
|
|
);
|
|
|
|
|
|
|
|
await trx.insert(
|
|
|
|
SharedWorkflow,
|
|
|
|
sharedWorkflowsToTransfer.map((sw) => ({
|
|
|
|
workflowId: sw.workflowId,
|
|
|
|
projectId: toProjectId,
|
|
|
|
role: sw.role,
|
|
|
|
})),
|
|
|
|
);
|
2023-01-10 00:23:44 -08:00
|
|
|
}
|
2022-10-11 05:55:05 -07:00
|
|
|
}
|