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
10 changed files with 179 additions and 232 deletions

View File

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