2024-01-17 01:16:13 -08:00
|
|
|
import { Service } from 'typedi';
|
2022-07-20 08:50:39 -07:00
|
|
|
import express from 'express';
|
2022-12-06 00:25:39 -08:00
|
|
|
import { v4 as uuid } from 'uuid';
|
2022-08-26 08:26:32 -07:00
|
|
|
import axios from 'axios';
|
2024-01-17 01:16:13 -08:00
|
|
|
|
2022-11-09 06:25:00 -08:00
|
|
|
import * as Db from '@/Db';
|
|
|
|
import * as GenericHelpers from '@/GenericHelpers';
|
|
|
|
import * as ResponseHelper from '@/ResponseHelper';
|
|
|
|
import * as WorkflowHelpers from '@/WorkflowHelpers';
|
2024-01-08 03:54:23 -08:00
|
|
|
import type { IWorkflowResponse } from '@/Interfaces';
|
2022-11-09 06:25:00 -08:00
|
|
|
import config from '@/config';
|
2024-01-17 01:16:13 -08:00
|
|
|
import { Authorized, Delete, Get, Patch, Post, Put, RestController } from '@/decorators';
|
|
|
|
import type { RoleNames } from '@db/entities/Role';
|
2022-11-09 06:25:00 -08:00
|
|
|
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
|
|
|
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
2024-01-17 01:16:13 -08:00
|
|
|
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
|
|
|
import { TagRepository } from '@db/repositories/tag.repository';
|
|
|
|
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
|
|
|
import { UserRepository } from '@db/repositories/user.repository';
|
2022-11-09 06:25:00 -08:00
|
|
|
import { validateEntity } from '@/GenericHelpers';
|
2022-12-21 01:46:26 -08:00
|
|
|
import { ExternalHooks } from '@/ExternalHooks';
|
2024-01-08 03:54:23 -08:00
|
|
|
import { ListQuery } from '@/requests';
|
2023-12-15 03:59:56 -08:00
|
|
|
import { WorkflowService } from './workflow.service';
|
2024-01-08 03:54:23 -08:00
|
|
|
import { isSharingEnabled } from '@/UserManagement/UserManagementHelper';
|
2023-02-21 10:21:56 -08:00
|
|
|
import { InternalHooks } from '@/InternalHooks';
|
2023-08-02 23:58:36 -07:00
|
|
|
import { RoleService } from '@/services/role.service';
|
2023-08-09 03:30:02 -07:00
|
|
|
import * as utils from '@/utils';
|
|
|
|
import { listQueryMiddleware } from '@/middlewares';
|
2023-08-22 03:24:43 -07:00
|
|
|
import { TagService } from '@/services/tag.service';
|
2023-10-23 07:30:36 -07:00
|
|
|
import { WorkflowHistoryService } from './workflowHistory/workflowHistory.service.ee';
|
2023-10-25 07:35:22 -07:00
|
|
|
import { Logger } from '@/Logger';
|
2023-11-28 01:19:27 -08:00
|
|
|
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
|
|
|
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
|
|
|
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
2023-12-29 05:23:58 -08:00
|
|
|
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
|
2024-01-17 01:16:13 -08:00
|
|
|
import { NamingService } from '@/services/naming.service';
|
|
|
|
import { UserOnboardingService } from '@/services/userOnboarding.service';
|
2023-12-29 05:23:58 -08:00
|
|
|
import { CredentialsService } from '../credentials/credentials.service';
|
2024-01-08 03:54:23 -08:00
|
|
|
import { WorkflowRequest } from './workflow.request';
|
2024-01-17 01:16:13 -08:00
|
|
|
import { EnterpriseWorkflowService } from './workflow.service.ee';
|
|
|
|
import { WorkflowExecutionService } from './workflowExecution.service';
|
|
|
|
import { WorkflowSharingService } from './workflowSharing.service';
|
2024-01-08 03:54:23 -08:00
|
|
|
|
|
|
|
@Service()
|
|
|
|
@Authorized()
|
|
|
|
@RestController('/workflows')
|
|
|
|
export class WorkflowsController {
|
|
|
|
constructor(
|
|
|
|
private readonly logger: Logger,
|
|
|
|
private readonly internalHooks: InternalHooks,
|
|
|
|
private readonly externalHooks: ExternalHooks,
|
|
|
|
private readonly tagRepository: TagRepository,
|
|
|
|
private readonly enterpriseWorkflowService: EnterpriseWorkflowService,
|
|
|
|
private readonly roleService: RoleService,
|
|
|
|
private readonly workflowHistoryService: WorkflowHistoryService,
|
|
|
|
private readonly tagService: TagService,
|
|
|
|
private readonly namingService: NamingService,
|
2024-01-17 01:16:13 -08:00
|
|
|
private readonly userOnboardingService: UserOnboardingService,
|
2024-01-08 03:54:23 -08:00
|
|
|
private readonly workflowRepository: WorkflowRepository,
|
|
|
|
private readonly workflowService: WorkflowService,
|
2024-01-17 01:16:13 -08:00
|
|
|
private readonly workflowExecutionService: WorkflowExecutionService,
|
|
|
|
private readonly workflowSharingService: WorkflowSharingService,
|
2024-01-08 03:54:23 -08:00
|
|
|
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
|
|
|
|
private readonly userRepository: UserRepository,
|
|
|
|
) {}
|
|
|
|
|
|
|
|
@Post('/')
|
|
|
|
async create(req: WorkflowRequest.Create) {
|
2022-07-20 08:50:39 -07:00
|
|
|
delete req.body.id; // delete if sent
|
|
|
|
|
|
|
|
const newWorkflow = new WorkflowEntity();
|
|
|
|
|
|
|
|
Object.assign(newWorkflow, req.body);
|
|
|
|
|
2022-12-06 00:25:39 -08:00
|
|
|
newWorkflow.versionId = uuid();
|
|
|
|
|
2022-07-20 08:50:39 -07:00
|
|
|
await validateEntity(newWorkflow);
|
|
|
|
|
2024-01-08 03:54:23 -08:00
|
|
|
await this.externalHooks.run('workflow.create', [newWorkflow]);
|
2022-07-20 08:50:39 -07:00
|
|
|
|
|
|
|
const { tags: tagIds } = req.body;
|
|
|
|
|
|
|
|
if (tagIds?.length && !config.getEnv('workflowTagsDisabled')) {
|
2024-01-08 03:54:23 -08:00
|
|
|
newWorkflow.tags = await this.tagRepository.findMany(tagIds);
|
2022-07-20 08:50:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
await WorkflowHelpers.replaceInvalidCredentials(newWorkflow);
|
|
|
|
|
2022-08-03 04:06:53 -07:00
|
|
|
WorkflowHelpers.addNodeIds(newWorkflow);
|
|
|
|
|
2023-12-29 05:23:58 -08:00
|
|
|
if (isSharingEnabled()) {
|
|
|
|
// This is a new workflow, so we simply check if the user has access to
|
|
|
|
// all used workflows
|
|
|
|
|
|
|
|
const allCredentials = await CredentialsService.getMany(req.user);
|
|
|
|
|
|
|
|
try {
|
2024-01-08 03:54:23 -08:00
|
|
|
this.enterpriseWorkflowService.validateCredentialPermissionsToUser(
|
2023-12-29 05:23:58 -08:00
|
|
|
newWorkflow,
|
|
|
|
allCredentials,
|
|
|
|
);
|
|
|
|
} catch (error) {
|
|
|
|
throw new BadRequestError(
|
|
|
|
'The workflow you are trying to save contains credentials that are not shared with you',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-20 08:50:39 -07:00
|
|
|
let savedWorkflow: undefined | WorkflowEntity;
|
|
|
|
|
|
|
|
await Db.transaction(async (transactionManager) => {
|
|
|
|
savedWorkflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
|
|
|
|
|
2024-01-08 03:54:23 -08:00
|
|
|
const role = await this.roleService.findWorkflowOwnerRole();
|
2022-07-20 08:50:39 -07:00
|
|
|
|
|
|
|
const newSharedWorkflow = new SharedWorkflow();
|
|
|
|
|
|
|
|
Object.assign(newSharedWorkflow, {
|
|
|
|
role,
|
|
|
|
user: req.user,
|
|
|
|
workflow: savedWorkflow,
|
|
|
|
});
|
|
|
|
|
|
|
|
await transactionManager.save<SharedWorkflow>(newSharedWorkflow);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!savedWorkflow) {
|
2024-01-08 03:54:23 -08:00
|
|
|
this.logger.error('Failed to create workflow', { userId: req.user.id });
|
2023-11-28 01:19:27 -08:00
|
|
|
throw new InternalServerError('Failed to save workflow');
|
2022-07-20 08:50:39 -07:00
|
|
|
}
|
|
|
|
|
2024-01-08 03:54:23 -08:00
|
|
|
await this.workflowHistoryService.saveVersion(req.user, savedWorkflow, savedWorkflow.id);
|
2023-10-23 07:30:36 -07:00
|
|
|
|
2022-08-02 08:18:57 -07:00
|
|
|
if (tagIds && !config.getEnv('workflowTagsDisabled') && savedWorkflow.tags) {
|
2024-01-08 03:54:23 -08:00
|
|
|
savedWorkflow.tags = this.tagService.sortByRequestOrder(savedWorkflow.tags, {
|
2022-07-20 08:50:39 -07:00
|
|
|
requestOrder: tagIds,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-01-08 03:54:23 -08:00
|
|
|
await this.externalHooks.run('workflow.afterCreate', [savedWorkflow]);
|
|
|
|
void this.internalHooks.onWorkflowCreated(req.user, newWorkflow, false);
|
2022-07-20 08:50:39 -07:00
|
|
|
|
2023-01-02 08:42:32 -08:00
|
|
|
return savedWorkflow;
|
2024-01-08 03:54:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Get('/', { middlewares: listQueryMiddleware })
|
|
|
|
async getAll(req: ListQuery.Request, res: express.Response) {
|
2023-08-09 03:30:02 -07:00
|
|
|
try {
|
2023-12-29 05:23:58 -08:00
|
|
|
const roles: RoleNames[] = isSharingEnabled() ? [] : ['owner'];
|
2024-01-17 01:16:13 -08:00
|
|
|
const sharedWorkflowIds = await this.workflowSharingService.getSharedWorkflowIds(
|
|
|
|
req.user,
|
|
|
|
roles,
|
|
|
|
);
|
2023-08-22 04:19:37 -07:00
|
|
|
|
2024-01-17 01:16:13 -08:00
|
|
|
const { workflows: data, count } = await this.workflowService.getMany(
|
2023-08-22 04:19:37 -07:00
|
|
|
sharedWorkflowIds,
|
|
|
|
req.listQueryOptions,
|
|
|
|
);
|
2023-08-09 03:30:02 -07:00
|
|
|
|
|
|
|
res.json({ count, data });
|
|
|
|
} catch (maybeError) {
|
|
|
|
const error = utils.toError(maybeError);
|
|
|
|
ResponseHelper.reportError(error);
|
|
|
|
ResponseHelper.sendErrorResponse(res, error);
|
|
|
|
}
|
2024-01-08 03:54:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Get('/new')
|
|
|
|
async getNewName(req: WorkflowRequest.NewName) {
|
2023-12-11 03:35:14 -08:00
|
|
|
const requestedName = req.query.name ?? config.getEnv('workflows.defaultName');
|
2022-08-26 08:26:32 -07:00
|
|
|
|
2024-01-08 03:54:23 -08:00
|
|
|
const name = await this.namingService.getUniqueWorkflowName(requestedName);
|
2022-08-26 08:26:32 -07:00
|
|
|
|
|
|
|
const onboardingFlowEnabled =
|
|
|
|
!config.getEnv('workflows.onboardingFlowDisabled') &&
|
|
|
|
!req.user.settings?.isOnboarded &&
|
2024-01-17 01:16:13 -08:00
|
|
|
(await this.userOnboardingService.isBelowThreshold(req.user));
|
2022-08-26 08:26:32 -07:00
|
|
|
|
|
|
|
return { name, onboardingFlowEnabled };
|
2024-01-08 03:54:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Get('/from-url')
|
|
|
|
async getFromUrl(req: WorkflowRequest.FromUrl) {
|
2022-08-26 08:26:32 -07:00
|
|
|
if (req.query.url === undefined) {
|
2023-11-28 01:19:27 -08:00
|
|
|
throw new BadRequestError('The parameter "url" is missing!');
|
2022-08-26 08:26:32 -07:00
|
|
|
}
|
2024-01-08 03:54:23 -08:00
|
|
|
if (!/^http[s]?:\/\/.*\.json$/i.exec(req.query.url)) {
|
2023-11-28 01:19:27 -08:00
|
|
|
throw new BadRequestError(
|
2022-12-29 03:20:43 -08:00
|
|
|
'The parameter "url" is not valid! It does not seem to be a URL pointing to a n8n workflow JSON file.',
|
2022-08-26 08:26:32 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
let workflowData: IWorkflowResponse | undefined;
|
|
|
|
try {
|
2024-01-08 03:54:23 -08:00
|
|
|
const { data } = await axios.get<IWorkflowResponse>(req.query.url);
|
2022-08-26 08:26:32 -07:00
|
|
|
workflowData = data;
|
|
|
|
} catch (error) {
|
2023-11-28 01:19:27 -08:00
|
|
|
throw new BadRequestError('The URL does not point to valid JSON file!');
|
2022-08-26 08:26:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Do a very basic check if it is really a n8n-workflow-json
|
|
|
|
if (
|
2023-07-28 09:28:17 -07:00
|
|
|
workflowData?.nodes === undefined ||
|
2022-08-26 08:26:32 -07:00
|
|
|
!Array.isArray(workflowData.nodes) ||
|
|
|
|
workflowData.connections === undefined ||
|
|
|
|
typeof workflowData.connections !== 'object' ||
|
|
|
|
Array.isArray(workflowData.connections)
|
|
|
|
) {
|
2023-11-28 01:19:27 -08:00
|
|
|
throw new BadRequestError(
|
2022-12-29 03:20:43 -08:00
|
|
|
'The data in the file does not seem to be a n8n workflow JSON file!',
|
2022-08-26 08:26:32 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return workflowData;
|
2024-01-08 03:54:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Get('/:id')
|
|
|
|
async getWorkflow(req: WorkflowRequest.Get) {
|
2022-07-20 08:50:39 -07:00
|
|
|
const { id: workflowId } = req.params;
|
|
|
|
|
2023-12-29 05:23:58 -08:00
|
|
|
if (isSharingEnabled()) {
|
|
|
|
const relations = ['shared', 'shared.user', 'shared.role'];
|
|
|
|
if (!config.getEnv('workflowTagsDisabled')) {
|
|
|
|
relations.push('tags');
|
|
|
|
}
|
|
|
|
|
2024-01-08 03:54:23 -08:00
|
|
|
const workflow = await this.workflowRepository.get({ id: workflowId }, { relations });
|
2023-12-29 05:23:58 -08:00
|
|
|
|
|
|
|
if (!workflow) {
|
|
|
|
throw new NotFoundError(`Workflow with ID "${workflowId}" does not exist`);
|
|
|
|
}
|
|
|
|
|
|
|
|
const userSharing = workflow.shared?.find((shared) => shared.user.id === req.user.id);
|
|
|
|
if (!userSharing && !req.user.hasGlobalScope('workflow:read')) {
|
|
|
|
throw new UnauthorizedError(
|
|
|
|
'You do not have permission to access this workflow. Ask the owner to share it with you',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-01-08 03:54:23 -08:00
|
|
|
const enterpriseWorkflowService = this.enterpriseWorkflowService;
|
2023-12-29 05:23:58 -08:00
|
|
|
|
|
|
|
enterpriseWorkflowService.addOwnerAndSharings(workflow);
|
|
|
|
await enterpriseWorkflowService.addCredentialsToWorkflow(workflow, req.user);
|
|
|
|
return workflow;
|
|
|
|
}
|
|
|
|
|
|
|
|
// sharing disabled
|
|
|
|
|
2023-12-28 04:14:10 -08:00
|
|
|
const extraRelations = config.getEnv('workflowTagsDisabled') ? [] : ['workflow.tags'];
|
2022-07-20 08:50:39 -07:00
|
|
|
|
2024-01-08 03:54:23 -08:00
|
|
|
const shared = await this.sharedWorkflowRepository.findSharing(
|
2023-12-28 04:14:10 -08:00
|
|
|
workflowId,
|
|
|
|
req.user,
|
|
|
|
'workflow:read',
|
|
|
|
{ extraRelations },
|
|
|
|
);
|
2022-07-20 08:50:39 -07:00
|
|
|
|
|
|
|
if (!shared) {
|
2024-01-08 03:54:23 -08:00
|
|
|
this.logger.verbose('User attempted to access a workflow without permissions', {
|
2022-07-20 08:50:39 -07:00
|
|
|
workflowId,
|
|
|
|
userId: req.user.id,
|
|
|
|
});
|
2023-11-28 01:19:27 -08:00
|
|
|
throw new NotFoundError(
|
2022-11-22 04:05:51 -08:00
|
|
|
'Could not load the workflow - you can only access workflows owned by you',
|
2022-07-20 08:50:39 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-01-02 08:42:32 -08:00
|
|
|
return shared.workflow;
|
2024-01-08 03:54:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Patch('/:id')
|
|
|
|
async update(req: WorkflowRequest.Update) {
|
2022-08-26 08:26:32 -07:00
|
|
|
const { id: workflowId } = req.params;
|
2023-12-29 05:23:58 -08:00
|
|
|
const forceSave = req.query.forceSave === 'true';
|
2022-08-26 08:26:32 -07:00
|
|
|
|
2023-12-29 05:23:58 -08:00
|
|
|
let updateData = new WorkflowEntity();
|
2022-08-26 08:26:32 -07:00
|
|
|
const { tags, ...rest } = req.body;
|
|
|
|
Object.assign(updateData, rest);
|
|
|
|
|
2023-12-29 05:23:58 -08:00
|
|
|
if (isSharingEnabled()) {
|
2024-01-08 03:54:23 -08:00
|
|
|
updateData = await this.enterpriseWorkflowService.preventTampering(
|
2023-12-29 05:23:58 -08:00
|
|
|
updateData,
|
|
|
|
workflowId,
|
|
|
|
req.user,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-01-08 03:54:23 -08:00
|
|
|
const updatedWorkflow = await this.workflowService.update(
|
2022-11-18 04:07:39 -08:00
|
|
|
req.user,
|
|
|
|
updateData,
|
|
|
|
workflowId,
|
|
|
|
tags,
|
2023-12-29 05:23:58 -08:00
|
|
|
isSharingEnabled() ? forceSave : true,
|
|
|
|
isSharingEnabled() ? undefined : ['owner'],
|
2022-11-18 04:07:39 -08:00
|
|
|
);
|
2022-08-26 08:26:32 -07:00
|
|
|
|
2023-01-02 08:42:32 -08:00
|
|
|
return updatedWorkflow;
|
2024-01-08 03:54:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Delete('/:id')
|
|
|
|
async delete(req: WorkflowRequest.Delete) {
|
2022-08-26 08:26:32 -07:00
|
|
|
const { id: workflowId } = req.params;
|
|
|
|
|
2024-01-08 03:54:23 -08:00
|
|
|
const workflow = await this.workflowService.delete(req.user, workflowId);
|
2023-01-10 00:23:44 -08:00
|
|
|
if (!workflow) {
|
2024-01-08 03:54:23 -08:00
|
|
|
this.logger.verbose('User attempted to delete a workflow without permissions', {
|
2022-08-26 08:26:32 -07:00
|
|
|
workflowId,
|
|
|
|
userId: req.user.id,
|
|
|
|
});
|
2023-11-28 01:19:27 -08:00
|
|
|
throw new BadRequestError(
|
2022-11-22 04:05:51 -08:00
|
|
|
'Could not delete the workflow - you can only remove workflows owned by you',
|
2022-08-26 08:26:32 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2024-01-08 03:54:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Post('/run')
|
|
|
|
async runManually(req: WorkflowRequest.ManualRun) {
|
2023-12-29 05:23:58 -08:00
|
|
|
if (isSharingEnabled()) {
|
2024-01-08 03:54:23 -08:00
|
|
|
const workflow = this.workflowRepository.create(req.body.workflowData);
|
2023-12-29 05:23:58 -08:00
|
|
|
|
|
|
|
if (req.body.workflowData.id !== undefined) {
|
2024-01-08 03:54:23 -08:00
|
|
|
const safeWorkflow = await this.enterpriseWorkflowService.preventTampering(
|
2023-12-29 05:23:58 -08:00
|
|
|
workflow,
|
|
|
|
workflow.id,
|
|
|
|
req.user,
|
|
|
|
);
|
|
|
|
req.body.workflowData.nodes = safeWorkflow.nodes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-17 01:16:13 -08:00
|
|
|
return this.workflowExecutionService.executeManually(
|
|
|
|
req.body,
|
|
|
|
req.user,
|
|
|
|
GenericHelpers.getSessionId(req),
|
|
|
|
);
|
2024-01-08 03:54:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Put('/:workflowId/share')
|
|
|
|
async share(req: WorkflowRequest.Share) {
|
2023-12-29 05:23:58 -08:00
|
|
|
if (!isSharingEnabled()) throw new NotFoundError('Route not found');
|
|
|
|
|
|
|
|
const { workflowId } = req.params;
|
|
|
|
const { shareWithIds } = req.body;
|
|
|
|
|
|
|
|
if (
|
|
|
|
!Array.isArray(shareWithIds) ||
|
|
|
|
!shareWithIds.every((userId) => typeof userId === 'string')
|
|
|
|
) {
|
|
|
|
throw new BadRequestError('Bad request');
|
|
|
|
}
|
|
|
|
|
2024-01-08 03:54:23 -08:00
|
|
|
const isOwnedRes = await this.enterpriseWorkflowService.isOwned(req.user, workflowId);
|
2023-12-29 05:23:58 -08:00
|
|
|
const { ownsWorkflow } = isOwnedRes;
|
|
|
|
let { workflow } = isOwnedRes;
|
|
|
|
|
|
|
|
if (!ownsWorkflow || !workflow) {
|
|
|
|
workflow = undefined;
|
|
|
|
// Allow owners/admins to share
|
|
|
|
if (req.user.hasGlobalScope('workflow:share')) {
|
2024-01-08 03:54:23 -08:00
|
|
|
const sharedRes = await this.sharedWorkflowRepository.getSharing(req.user, workflowId, {
|
|
|
|
allowGlobalScope: true,
|
|
|
|
globalScope: 'workflow:share',
|
|
|
|
});
|
2023-12-29 05:23:58 -08:00
|
|
|
workflow = sharedRes?.workflow;
|
|
|
|
}
|
|
|
|
if (!workflow) {
|
|
|
|
throw new UnauthorizedError('Forbidden');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const ownerIds = (
|
2024-01-08 03:54:23 -08:00
|
|
|
await this.workflowRepository.getSharings(
|
2023-12-29 05:23:58 -08:00
|
|
|
Db.getConnection().createEntityManager(),
|
|
|
|
workflowId,
|
|
|
|
['shared', 'shared.role'],
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.filter((e) => e.role.name === 'owner')
|
|
|
|
.map((e) => e.userId);
|
|
|
|
|
|
|
|
let newShareeIds: string[] = [];
|
|
|
|
await Db.transaction(async (trx) => {
|
|
|
|
// remove all sharings that are not supposed to exist anymore
|
2024-01-08 03:54:23 -08:00
|
|
|
await this.workflowRepository.pruneSharings(trx, workflowId, [...ownerIds, ...shareWithIds]);
|
2023-12-29 05:23:58 -08:00
|
|
|
|
2024-01-08 03:54:23 -08:00
|
|
|
const sharings = await this.workflowRepository.getSharings(trx, workflowId);
|
2023-12-29 05:23:58 -08:00
|
|
|
|
|
|
|
// extract the new sharings that need to be added
|
2024-01-08 03:54:23 -08:00
|
|
|
newShareeIds = utils.rightDiff(
|
2023-12-29 05:23:58 -08:00
|
|
|
[sharings, (sharing) => sharing.userId],
|
|
|
|
[shareWithIds, (shareeId) => shareeId],
|
|
|
|
);
|
|
|
|
|
|
|
|
if (newShareeIds.length) {
|
2024-01-08 03:54:23 -08:00
|
|
|
const users = await this.userRepository.getByIds(trx, newShareeIds);
|
|
|
|
const role = await this.roleService.findWorkflowEditorRole();
|
2024-01-05 04:06:24 -08:00
|
|
|
|
2024-01-08 03:54:23 -08:00
|
|
|
await this.sharedWorkflowRepository.share(trx, workflow!, users, role.id);
|
2023-12-29 05:23:58 -08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-01-08 03:54:23 -08:00
|
|
|
void this.internalHooks.onWorkflowSharingUpdate(workflowId, req.user.id, shareWithIds);
|
|
|
|
}
|
|
|
|
}
|