refactor(core): Convert workflows controller to DI (no-changelog) (#8253)

This commit is contained in:
Iván Ovejero 2024-01-08 12:54:23 +01:00 committed by GitHub
parent ac1c642fdd
commit 90c065e999
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 179 additions and 232 deletions

View file

@ -30,7 +30,7 @@ import config from '@/config';
import { Queue } from '@/Queue'; import { Queue } from '@/Queue';
import { getSharedWorkflowIds } from '@/WorkflowHelpers'; import { getSharedWorkflowIds } from '@/WorkflowHelpers';
import { workflowsController } from '@/workflows/workflows.controller'; import { WorkflowsController } from '@/workflows/workflows.controller';
import { import {
EDITOR_UI_DIST_DIR, EDITOR_UI_DIST_DIR,
inDevelopment, inDevelopment,
@ -246,10 +246,11 @@ export class Server extends AbstractServer {
VariablesController, VariablesController,
RoleController, RoleController,
ActiveWorkflowsController, ActiveWorkflowsController,
WorkflowsController,
]; ];
if (process.env.NODE_ENV !== 'production' && Container.get(MultiMainSetup).isEnabled) { if (process.env.NODE_ENV !== 'production' && Container.get(MultiMainSetup).isEnabled) {
const { DebugController } = await import('./controllers/debug.controller'); const { DebugController } = await import('@/controllers/debug.controller');
controllers.push(DebugController); controllers.push(DebugController);
} }
@ -358,11 +359,6 @@ export class Server extends AbstractServer {
this.app.use(`/${this.restEndpoint}/credentials`, credentialsController); this.app.use(`/${this.restEndpoint}/credentials`, credentialsController);
// ----------------------------------------
// Workflow
// ----------------------------------------
this.app.use(`/${this.restEndpoint}/workflows`, workflowsController);
// ---------------------------------------- // ----------------------------------------
// SAML // SAML
// ---------------------------------------- // ----------------------------------------

View file

@ -4,23 +4,3 @@ import { License } from '@/License';
export function isSharingEnabled(): boolean { export function isSharingEnabled(): boolean {
return Container.get(License).isSharingEnabled(); return Container.get(License).isSharingEnabled();
} }
// return the difference between two arrays
export function rightDiff<T1, T2>(
[arr1, keyExtractor1]: [T1[], (item: T1) => string],
[arr2, keyExtractor2]: [T2[], (item: T2) => string],
): T2[] {
// create map { itemKey => true } for fast lookup for diff
const keyMap = arr1.reduce<{ [key: string]: true }>((map, item) => {
map[keyExtractor1(item)] = true;
return map;
}, {});
// diff against map
return arr2.reduce<T2[]>((acc, item) => {
if (!keyMap[keyExtractor2(item)]) {
acc.push(item);
}
return acc;
}, []);
}

View file

@ -1,5 +1,5 @@
import { Authorized, Get, RestController } from '@/decorators'; import { Authorized, Get, RestController } from '@/decorators';
import { WorkflowRequest } from '@/requests'; import { ActiveWorkflowRequest } from '@/requests';
import { ActiveWorkflowsService } from '@/services/activeWorkflows.service'; import { ActiveWorkflowsService } from '@/services/activeWorkflows.service';
@Authorized() @Authorized()
@ -8,12 +8,12 @@ export class ActiveWorkflowsController {
constructor(private readonly activeWorkflowsService: ActiveWorkflowsService) {} constructor(private readonly activeWorkflowsService: ActiveWorkflowsService) {}
@Get('/') @Get('/')
async getActiveWorkflows(req: WorkflowRequest.GetAllActive) { async getActiveWorkflows(req: ActiveWorkflowRequest.GetAllActive) {
return this.activeWorkflowsService.getAllActiveIdsFor(req.user); return this.activeWorkflowsService.getAllActiveIdsFor(req.user);
} }
@Get('/error/:id') @Get('/error/:id')
async getActivationError(req: WorkflowRequest.GetActivationError) { async getActivationError(req: ActiveWorkflowRequest.GetActivationError) {
const { const {
user, user,
params: { id: workflowId }, params: { id: workflowId },

View file

@ -5,7 +5,7 @@ import * as Db from '@/Db';
import * as ResponseHelper from '@/ResponseHelper'; import * as ResponseHelper from '@/ResponseHelper';
import type { CredentialRequest } from '@/requests'; import type { CredentialRequest } from '@/requests';
import { isSharingEnabled, rightDiff } from '@/UserManagement/UserManagementHelper'; import { isSharingEnabled } from '@/UserManagement/UserManagementHelper';
import { EECredentialsService as EECredentials } from './credentials.service.ee'; import { EECredentialsService as EECredentials } from './credentials.service.ee';
import { OwnershipService } from '@/services/ownership.service'; import { OwnershipService } from '@/services/ownership.service';
import { Container } from 'typedi'; import { Container } from 'typedi';
@ -14,6 +14,7 @@ import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error'; import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
import { CredentialsRepository } from '@/databases/repositories/credentials.repository'; import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
import * as utils from '@/utils';
export const EECredentialsController = express.Router(); export const EECredentialsController = express.Router();
@ -165,7 +166,7 @@ EECredentialsController.put(
const sharings = await EECredentials.getSharings(trx, credentialId); const sharings = await EECredentials.getSharings(trx, credentialId);
// extract the new sharings that need to be added // extract the new sharings that need to be added
newShareeIds = rightDiff( newShareeIds = utils.rightDiff(
[sharings, (sharing) => sharing.userId], [sharings, (sharing) => sharing.userId],
[shareWithIds, (shareeId) => shareeId], [shareWithIds, (shareeId) => shareeId],
); );

View file

@ -1,19 +1,14 @@
import type express from 'express'; import type express from 'express';
import type { import type {
BannerName, BannerName,
IConnections,
ICredentialDataDecryptedObject, ICredentialDataDecryptedObject,
ICredentialNodeAccess, ICredentialNodeAccess,
IDataObject, IDataObject,
INode,
INodeCredentialTestRequest, INodeCredentialTestRequest,
INodeCredentials, INodeCredentials,
INodeParameters, INodeParameters,
INodeTypeNameVersion, INodeTypeNameVersion,
IPinData,
IRunData,
IUser, IUser,
IWorkflowSettings,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { IsBoolean, IsEmail, IsIn, IsOptional, IsString, Length } from 'class-validator'; import { IsBoolean, IsEmail, IsIn, IsOptional, IsString, Length } from 'class-validator';
@ -21,7 +16,6 @@ import { NoXss } from '@db/utils/customValidators';
import type { import type {
PublicUser, PublicUser,
IExecutionDeleteFilter, IExecutionDeleteFilter,
IWorkflowDb,
SecretsProvider, SecretsProvider,
SecretsProviderState, SecretsProviderState,
} from '@/Interfaces'; } from '@/Interfaces';
@ -81,55 +75,6 @@ export type AuthenticatedRequest<
globalMemberRole?: Role; globalMemberRole?: Role;
}; };
// ----------------------------------
// /workflows
// ----------------------------------
export declare namespace WorkflowRequest {
type CreateUpdatePayload = Partial<{
id: string; // delete if sent
name: string;
nodes: INode[];
connections: IConnections;
settings: IWorkflowSettings;
active: boolean;
tags: string[];
hash: string;
meta: Record<string, unknown>;
}>;
type ManualRunPayload = {
workflowData: IWorkflowDb;
runData: IRunData;
pinData: IPinData;
startNodes?: string[];
destinationNode?: string;
};
type Create = AuthenticatedRequest<{}, {}, CreateUpdatePayload>;
type Get = AuthenticatedRequest<{ id: string }>;
type Delete = Get;
type Update = AuthenticatedRequest<
{ id: string },
{},
CreateUpdatePayload,
{ forceSave?: string }
>;
type NewName = AuthenticatedRequest<{}, {}, {}, { name?: string }>;
type GetAllActive = AuthenticatedRequest;
type GetActivationError = Get;
type ManualRun = AuthenticatedRequest<{}, {}, ManualRunPayload>;
type Share = AuthenticatedRequest<{ workflowId: string }, {}, { shareWithIds: string[] }>;
}
// ---------------------------------- // ----------------------------------
// list query // list query
// ---------------------------------- // ----------------------------------
@ -219,7 +164,7 @@ export declare namespace CredentialRequest {
type Update = AuthenticatedRequest<{ id: string }, {}, CredentialProperties>; type Update = AuthenticatedRequest<{ id: string }, {}, CredentialProperties>;
type NewName = WorkflowRequest.NewName; type NewName = AuthenticatedRequest<{}, {}, {}, { name?: string }>;
type Test = AuthenticatedRequest<{}, {}, INodeCredentialTestRequest>; type Test = AuthenticatedRequest<{}, {}, INodeCredentialTestRequest>;
@ -576,3 +521,13 @@ export declare namespace WorkflowHistoryRequest {
WorkflowHistory WorkflowHistory
>; >;
} }
// ----------------------------------
// /active-workflows
// ----------------------------------
export declare namespace ActiveWorkflowRequest {
type GetAllActive = AuthenticatedRequest;
type GetActivationError = AuthenticatedRequest<{ id: string }>;
}

View file

@ -77,3 +77,23 @@ export function isObjectLiteral(item: unknown): item is { [key: string]: string
export function removeTrailingSlash(path: string) { export function removeTrailingSlash(path: string) {
return path.endsWith('/') ? path.slice(0, -1) : path; return path.endsWith('/') ? path.slice(0, -1) : path;
} }
// return the difference between two arrays
export function rightDiff<T1, T2>(
[arr1, keyExtractor1]: [T1[], (item: T1) => string],
[arr2, keyExtractor2]: [T2[], (item: T2) => string],
): T2[] {
// create map { itemKey => true } for fast lookup for diff
const keyMap = arr1.reduce<{ [key: string]: true }>((map, item) => {
map[keyExtractor1(item)] = true;
return map;
}, {});
// diff against map
return arr2.reduce<T2[]>((acc, item) => {
if (!keyMap[keyExtractor2(item)]) {
acc.push(item);
}
return acc;
}, []);
}

View file

@ -0,0 +1,46 @@
import type { IWorkflowDb } from '@/Interfaces';
import type { AuthenticatedRequest } from '@/requests';
import type { INode, IConnections, IWorkflowSettings, IRunData, IPinData } from 'n8n-workflow';
export declare namespace WorkflowRequest {
type CreateUpdatePayload = Partial<{
id: string; // delete if sent
name: string;
nodes: INode[];
connections: IConnections;
settings: IWorkflowSettings;
active: boolean;
tags: string[];
hash: string;
meta: Record<string, unknown>;
}>;
type ManualRunPayload = {
workflowData: IWorkflowDb;
runData: IRunData;
pinData: IPinData;
startNodes?: string[];
destinationNode?: string;
};
type Create = AuthenticatedRequest<{}, {}, CreateUpdatePayload>;
type Get = AuthenticatedRequest<{ id: string }>;
type Delete = Get;
type Update = AuthenticatedRequest<
{ id: string },
{},
CreateUpdatePayload,
{ forceSave?: string }
>;
type NewName = AuthenticatedRequest<{}, {}, {}, { name?: string }>;
type ManualRun = AuthenticatedRequest<{}, {}, ManualRunPayload>;
type Share = AuthenticatedRequest<{ workflowId: string }, {}, { shareWithIds: string[] }>;
type FromUrl = AuthenticatedRequest<{}, {}, {}, { url?: string }>;
}

View file

@ -11,7 +11,8 @@ import type { User } from '@db/entities/User';
import type { WorkflowEntity } from '@db/entities/WorkflowEntity'; import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
import { validateEntity } from '@/GenericHelpers'; import { validateEntity } from '@/GenericHelpers';
import { ExternalHooks } from '@/ExternalHooks'; import { ExternalHooks } from '@/ExternalHooks';
import { type WorkflowRequest, hasSharing, type ListQuery } from '@/requests'; import { hasSharing, type ListQuery } from '@/requests';
import type { WorkflowRequest } from '@/workflows/workflow.request';
import { TagService } from '@/services/tag.service'; import { TagService } from '@/services/tag.service';
import type { IWorkflowDb, IWorkflowExecutionDataProcess } from '@/Interfaces'; import type { IWorkflowDb, IWorkflowExecutionDataProcess } from '@/Interfaces';
import { NodeTypes } from '@/NodeTypes'; import { NodeTypes } from '@/NodeTypes';

View file

@ -6,17 +6,17 @@ import * as Db from '@/Db';
import * as GenericHelpers from '@/GenericHelpers'; import * as GenericHelpers from '@/GenericHelpers';
import * as ResponseHelper from '@/ResponseHelper'; import * as ResponseHelper from '@/ResponseHelper';
import * as WorkflowHelpers from '@/WorkflowHelpers'; import * as WorkflowHelpers from '@/WorkflowHelpers';
import type { IWorkflowResponse, IExecutionPushResponse } from '@/Interfaces'; import type { IWorkflowResponse } from '@/Interfaces';
import config from '@/config'; import config from '@/config';
import { SharedWorkflow } from '@db/entities/SharedWorkflow'; import { SharedWorkflow } from '@db/entities/SharedWorkflow';
import { WorkflowEntity } from '@db/entities/WorkflowEntity'; import { WorkflowEntity } from '@db/entities/WorkflowEntity';
import { validateEntity } from '@/GenericHelpers'; import { validateEntity } from '@/GenericHelpers';
import { ExternalHooks } from '@/ExternalHooks'; import { ExternalHooks } from '@/ExternalHooks';
import type { ListQuery, WorkflowRequest } from '@/requests'; import { ListQuery } from '@/requests';
import { isBelowOnboardingThreshold } from '@/WorkflowHelpers'; import { isBelowOnboardingThreshold } from '@/WorkflowHelpers';
import { WorkflowService } from './workflow.service'; import { WorkflowService } from './workflow.service';
import { isSharingEnabled, rightDiff } from '@/UserManagement/UserManagementHelper'; import { isSharingEnabled } from '@/UserManagement/UserManagementHelper';
import { Container } from 'typedi'; import Container, { Service } from 'typedi';
import { InternalHooks } from '@/InternalHooks'; import { InternalHooks } from '@/InternalHooks';
import { RoleService } from '@/services/role.service'; import { RoleService } from '@/services/role.service';
import * as utils from '@/utils'; import * as utils from '@/utils';
@ -36,15 +36,31 @@ import type { RoleNames } from '@/databases/entities/Role';
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error'; import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
import { CredentialsService } from '../credentials/credentials.service'; import { CredentialsService } from '../credentials/credentials.service';
import { UserRepository } from '@/databases/repositories/user.repository'; import { UserRepository } from '@/databases/repositories/user.repository';
import { Authorized, Delete, Get, Patch, Post, Put, RestController } from '@/decorators';
import { WorkflowRequest } from './workflow.request';
export const workflowsController = express.Router(); @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,
private readonly workflowRepository: WorkflowRepository,
private readonly workflowService: WorkflowService,
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
private readonly userRepository: UserRepository,
) {}
/** @Post('/')
* POST /workflows async create(req: WorkflowRequest.Create) {
*/
workflowsController.post(
'/',
ResponseHelper.send(async (req: WorkflowRequest.Create) => {
delete req.body.id; // delete if sent delete req.body.id; // delete if sent
const newWorkflow = new WorkflowEntity(); const newWorkflow = new WorkflowEntity();
@ -55,12 +71,12 @@ workflowsController.post(
await validateEntity(newWorkflow); await validateEntity(newWorkflow);
await Container.get(ExternalHooks).run('workflow.create', [newWorkflow]); await this.externalHooks.run('workflow.create', [newWorkflow]);
const { tags: tagIds } = req.body; const { tags: tagIds } = req.body;
if (tagIds?.length && !config.getEnv('workflowTagsDisabled')) { if (tagIds?.length && !config.getEnv('workflowTagsDisabled')) {
newWorkflow.tags = await Container.get(TagRepository).findMany(tagIds); newWorkflow.tags = await this.tagRepository.findMany(tagIds);
} }
await WorkflowHelpers.replaceInvalidCredentials(newWorkflow); await WorkflowHelpers.replaceInvalidCredentials(newWorkflow);
@ -74,7 +90,7 @@ workflowsController.post(
const allCredentials = await CredentialsService.getMany(req.user); const allCredentials = await CredentialsService.getMany(req.user);
try { try {
Container.get(EnterpriseWorkflowService).validateCredentialPermissionsToUser( this.enterpriseWorkflowService.validateCredentialPermissionsToUser(
newWorkflow, newWorkflow,
allCredentials, allCredentials,
); );
@ -90,7 +106,7 @@ workflowsController.post(
await Db.transaction(async (transactionManager) => { await Db.transaction(async (transactionManager) => {
savedWorkflow = await transactionManager.save<WorkflowEntity>(newWorkflow); savedWorkflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
const role = await Container.get(RoleService).findWorkflowOwnerRole(); const role = await this.roleService.findWorkflowOwnerRole();
const newSharedWorkflow = new SharedWorkflow(); const newSharedWorkflow = new SharedWorkflow();
@ -104,36 +120,26 @@ workflowsController.post(
}); });
if (!savedWorkflow) { if (!savedWorkflow) {
Container.get(Logger).error('Failed to create workflow', { userId: req.user.id }); this.logger.error('Failed to create workflow', { userId: req.user.id });
throw new InternalServerError('Failed to save workflow'); throw new InternalServerError('Failed to save workflow');
} }
await Container.get(WorkflowHistoryService).saveVersion( await this.workflowHistoryService.saveVersion(req.user, savedWorkflow, savedWorkflow.id);
req.user,
savedWorkflow,
savedWorkflow.id,
);
if (tagIds && !config.getEnv('workflowTagsDisabled') && savedWorkflow.tags) { if (tagIds && !config.getEnv('workflowTagsDisabled') && savedWorkflow.tags) {
savedWorkflow.tags = Container.get(TagService).sortByRequestOrder(savedWorkflow.tags, { savedWorkflow.tags = this.tagService.sortByRequestOrder(savedWorkflow.tags, {
requestOrder: tagIds, requestOrder: tagIds,
}); });
} }
await Container.get(ExternalHooks).run('workflow.afterCreate', [savedWorkflow]); await this.externalHooks.run('workflow.afterCreate', [savedWorkflow]);
void Container.get(InternalHooks).onWorkflowCreated(req.user, newWorkflow, false); void this.internalHooks.onWorkflowCreated(req.user, newWorkflow, false);
return savedWorkflow; return savedWorkflow;
}), }
);
/** @Get('/', { middlewares: listQueryMiddleware })
* GET /workflows async getAll(req: ListQuery.Request, res: express.Response) {
*/
workflowsController.get(
'/',
listQueryMiddleware,
async (req: ListQuery.Request, res: express.Response) => {
try { try {
const roles: RoleNames[] = isSharingEnabled() ? [] : ['owner']; const roles: RoleNames[] = isSharingEnabled() ? [] : ['owner'];
const sharedWorkflowIds = await WorkflowHelpers.getSharedWorkflowIds(req.user, roles); const sharedWorkflowIds = await WorkflowHelpers.getSharedWorkflowIds(req.user, roles);
@ -149,18 +155,13 @@ workflowsController.get(
ResponseHelper.reportError(error); ResponseHelper.reportError(error);
ResponseHelper.sendErrorResponse(res, error); ResponseHelper.sendErrorResponse(res, error);
} }
}, }
);
/** @Get('/new')
* GET /workflows/new async getNewName(req: WorkflowRequest.NewName) {
*/
workflowsController.get(
'/new',
ResponseHelper.send(async (req: WorkflowRequest.NewName) => {
const requestedName = req.query.name ?? config.getEnv('workflows.defaultName'); const requestedName = req.query.name ?? config.getEnv('workflows.defaultName');
const name = await Container.get(NamingService).getUniqueWorkflowName(requestedName); const name = await this.namingService.getUniqueWorkflowName(requestedName);
const onboardingFlowEnabled = const onboardingFlowEnabled =
!config.getEnv('workflows.onboardingFlowDisabled') && !config.getEnv('workflows.onboardingFlowDisabled') &&
@ -168,27 +169,21 @@ workflowsController.get(
(await isBelowOnboardingThreshold(req.user)); (await isBelowOnboardingThreshold(req.user));
return { name, onboardingFlowEnabled }; return { name, onboardingFlowEnabled };
}), }
);
// Reads and returns workflow data from an URL @Get('/from-url')
/** async getFromUrl(req: WorkflowRequest.FromUrl) {
* GET /workflows/from-url
*/
workflowsController.get(
'/from-url',
ResponseHelper.send(async (req: express.Request): Promise<IWorkflowResponse> => {
if (req.query.url === undefined) { if (req.query.url === undefined) {
throw new BadRequestError('The parameter "url" is missing!'); throw new BadRequestError('The parameter "url" is missing!');
} }
if (!/^http[s]?:\/\/.*\.json$/i.exec(req.query.url as string)) { if (!/^http[s]?:\/\/.*\.json$/i.exec(req.query.url)) {
throw new BadRequestError( throw new BadRequestError(
'The parameter "url" is not valid! It does not seem to be a URL pointing to a n8n workflow JSON file.', 'The parameter "url" is not valid! It does not seem to be a URL pointing to a n8n workflow JSON file.',
); );
} }
let workflowData: IWorkflowResponse | undefined; let workflowData: IWorkflowResponse | undefined;
try { try {
const { data } = await axios.get<IWorkflowResponse>(req.query.url as string); const { data } = await axios.get<IWorkflowResponse>(req.query.url);
workflowData = data; workflowData = data;
} catch (error) { } catch (error) {
throw new BadRequestError('The URL does not point to valid JSON file!'); throw new BadRequestError('The URL does not point to valid JSON file!');
@ -208,15 +203,10 @@ workflowsController.get(
} }
return workflowData; return workflowData;
}), }
);
/** @Get('/:id')
* GET /workflows/:id async getWorkflow(req: WorkflowRequest.Get) {
*/
workflowsController.get(
'/:id(\\w+)',
ResponseHelper.send(async (req: WorkflowRequest.Get) => {
const { id: workflowId } = req.params; const { id: workflowId } = req.params;
if (isSharingEnabled()) { if (isSharingEnabled()) {
@ -225,10 +215,7 @@ workflowsController.get(
relations.push('tags'); relations.push('tags');
} }
const workflow = await Container.get(WorkflowRepository).get( const workflow = await this.workflowRepository.get({ id: workflowId }, { relations });
{ id: workflowId },
{ relations },
);
if (!workflow) { if (!workflow) {
throw new NotFoundError(`Workflow with ID "${workflowId}" does not exist`); throw new NotFoundError(`Workflow with ID "${workflowId}" does not exist`);
@ -241,7 +228,7 @@ workflowsController.get(
); );
} }
const enterpriseWorkflowService = Container.get(EnterpriseWorkflowService); const enterpriseWorkflowService = this.enterpriseWorkflowService;
enterpriseWorkflowService.addOwnerAndSharings(workflow); enterpriseWorkflowService.addOwnerAndSharings(workflow);
await enterpriseWorkflowService.addCredentialsToWorkflow(workflow, req.user); await enterpriseWorkflowService.addCredentialsToWorkflow(workflow, req.user);
@ -252,7 +239,7 @@ workflowsController.get(
const extraRelations = config.getEnv('workflowTagsDisabled') ? [] : ['workflow.tags']; const extraRelations = config.getEnv('workflowTagsDisabled') ? [] : ['workflow.tags'];
const shared = await Container.get(SharedWorkflowRepository).findSharing( const shared = await this.sharedWorkflowRepository.findSharing(
workflowId, workflowId,
req.user, req.user,
'workflow:read', 'workflow:read',
@ -260,7 +247,7 @@ workflowsController.get(
); );
if (!shared) { if (!shared) {
Container.get(Logger).verbose('User attempted to access a workflow without permissions', { this.logger.verbose('User attempted to access a workflow without permissions', {
workflowId, workflowId,
userId: req.user.id, userId: req.user.id,
}); });
@ -270,16 +257,10 @@ workflowsController.get(
} }
return shared.workflow; return shared.workflow;
}), }
);
// Updates an existing workflow @Patch('/:id')
/** async update(req: WorkflowRequest.Update) {
* PATCH /workflows/:id
*/
workflowsController.patch(
'/:id(\\w+)',
ResponseHelper.send(async (req: WorkflowRequest.Update) => {
const { id: workflowId } = req.params; const { id: workflowId } = req.params;
const forceSave = req.query.forceSave === 'true'; const forceSave = req.query.forceSave === 'true';
@ -288,14 +269,14 @@ workflowsController.patch(
Object.assign(updateData, rest); Object.assign(updateData, rest);
if (isSharingEnabled()) { if (isSharingEnabled()) {
updateData = await Container.get(EnterpriseWorkflowService).preventTampering( updateData = await this.enterpriseWorkflowService.preventTampering(
updateData, updateData,
workflowId, workflowId,
req.user, req.user,
); );
} }
const updatedWorkflow = await Container.get(WorkflowService).update( const updatedWorkflow = await this.workflowService.update(
req.user, req.user,
updateData, updateData,
workflowId, workflowId,
@ -305,21 +286,15 @@ workflowsController.patch(
); );
return updatedWorkflow; return updatedWorkflow;
}), }
);
// Deletes a specific workflow @Delete('/:id')
/** async delete(req: WorkflowRequest.Delete) {
* DELETE /workflows/:id
*/
workflowsController.delete(
'/:id(\\w+)',
ResponseHelper.send(async (req: WorkflowRequest.Delete) => {
const { id: workflowId } = req.params; const { id: workflowId } = req.params;
const workflow = await Container.get(WorkflowService).delete(req.user, workflowId); const workflow = await this.workflowService.delete(req.user, workflowId);
if (!workflow) { if (!workflow) {
Container.get(Logger).verbose('User attempted to delete a workflow without permissions', { this.logger.verbose('User attempted to delete a workflow without permissions', {
workflowId, workflowId,
userId: req.user.id, userId: req.user.id,
}); });
@ -329,20 +304,15 @@ workflowsController.delete(
} }
return true; return true;
}), }
);
/** @Post('/run')
* POST /workflows/run async runManually(req: WorkflowRequest.ManualRun) {
*/
workflowsController.post(
'/run',
ResponseHelper.send(async (req: WorkflowRequest.ManualRun): Promise<IExecutionPushResponse> => {
if (isSharingEnabled()) { if (isSharingEnabled()) {
const workflow = Container.get(WorkflowRepository).create(req.body.workflowData); const workflow = this.workflowRepository.create(req.body.workflowData);
if (req.body.workflowData.id !== undefined) { if (req.body.workflowData.id !== undefined) {
const safeWorkflow = await Container.get(EnterpriseWorkflowService).preventTampering( const safeWorkflow = await this.enterpriseWorkflowService.preventTampering(
workflow, workflow,
workflow.id, workflow.id,
req.user, req.user,
@ -351,22 +321,11 @@ workflowsController.post(
} }
} }
return Container.get(WorkflowService).runManually( return this.workflowService.runManually(req.body, req.user, GenericHelpers.getSessionId(req));
req.body, }
req.user,
GenericHelpers.getSessionId(req),
);
}),
);
/** @Put('/:workflowId/share')
* (EE) PUT /workflows/:id/share async share(req: WorkflowRequest.Share) {
*
* Grant or remove users' access to a workflow.
*/
workflowsController.put(
'/:workflowId/share',
ResponseHelper.send(async (req: WorkflowRequest.Share) => {
if (!isSharingEnabled()) throw new NotFoundError('Route not found'); if (!isSharingEnabled()) throw new NotFoundError('Route not found');
const { workflowId } = req.params; const { workflowId } = req.params;
@ -379,7 +338,7 @@ workflowsController.put(
throw new BadRequestError('Bad request'); throw new BadRequestError('Bad request');
} }
const isOwnedRes = await Container.get(EnterpriseWorkflowService).isOwned(req.user, workflowId); const isOwnedRes = await this.enterpriseWorkflowService.isOwned(req.user, workflowId);
const { ownsWorkflow } = isOwnedRes; const { ownsWorkflow } = isOwnedRes;
let { workflow } = isOwnedRes; let { workflow } = isOwnedRes;
@ -387,14 +346,10 @@ workflowsController.put(
workflow = undefined; workflow = undefined;
// Allow owners/admins to share // Allow owners/admins to share
if (req.user.hasGlobalScope('workflow:share')) { if (req.user.hasGlobalScope('workflow:share')) {
const sharedRes = await Container.get(SharedWorkflowRepository).getSharing( const sharedRes = await this.sharedWorkflowRepository.getSharing(req.user, workflowId, {
req.user, allowGlobalScope: true,
workflowId, globalScope: 'workflow:share',
{ });
allowGlobalScope: true,
globalScope: 'workflow:share',
},
);
workflow = sharedRes?.workflow; workflow = sharedRes?.workflow;
} }
if (!workflow) { if (!workflow) {
@ -403,7 +358,7 @@ workflowsController.put(
} }
const ownerIds = ( const ownerIds = (
await Container.get(WorkflowRepository).getSharings( await this.workflowRepository.getSharings(
Db.getConnection().createEntityManager(), Db.getConnection().createEntityManager(),
workflowId, workflowId,
['shared', 'shared.role'], ['shared', 'shared.role'],
@ -415,31 +370,24 @@ workflowsController.put(
let newShareeIds: string[] = []; let newShareeIds: string[] = [];
await Db.transaction(async (trx) => { await Db.transaction(async (trx) => {
// remove all sharings that are not supposed to exist anymore // remove all sharings that are not supposed to exist anymore
await Container.get(WorkflowRepository).pruneSharings(trx, workflowId, [ await this.workflowRepository.pruneSharings(trx, workflowId, [...ownerIds, ...shareWithIds]);
...ownerIds,
...shareWithIds,
]);
const sharings = await Container.get(WorkflowRepository).getSharings(trx, workflowId); const sharings = await this.workflowRepository.getSharings(trx, workflowId);
// extract the new sharings that need to be added // extract the new sharings that need to be added
newShareeIds = rightDiff( newShareeIds = utils.rightDiff(
[sharings, (sharing) => sharing.userId], [sharings, (sharing) => sharing.userId],
[shareWithIds, (shareeId) => shareeId], [shareWithIds, (shareeId) => shareeId],
); );
if (newShareeIds.length) { if (newShareeIds.length) {
const users = await Container.get(UserRepository).getByIds(trx, newShareeIds); const users = await this.userRepository.getByIds(trx, newShareeIds);
const role = await Container.get(RoleService).findWorkflowEditorRole(); const role = await this.roleService.findWorkflowEditorRole();
await Container.get(SharedWorkflowRepository).share(trx, workflow!, users, role.id); await this.sharedWorkflowRepository.share(trx, workflow!, users, role.id);
} }
}); });
void Container.get(InternalHooks).onWorkflowSharingUpdate( void this.internalHooks.onWorkflowSharingUpdate(workflowId, req.user.id, shareWithIds);
workflowId, }
req.user.id, }
shareWithIds,
);
}),
);

View file

@ -126,8 +126,8 @@ export const setupTestServer = ({
break; break;
case 'workflows': case 'workflows':
const { workflowsController } = await import('@/workflows/workflows.controller'); const { WorkflowsController } = await import('@/workflows/workflows.controller');
app.use(`/${REST_PATH_SEGMENT}/workflows`, workflowsController); registerController(app, WorkflowsController);
break; break;
case 'executions': case 'executions':