refactor(core): Make workflow services injectable (no-changelog) (#8033)
Refactor static workflow service classes into DI-compatible classes Context: https://n8nio.slack.com/archives/C069HS026UF/p1702466571648889 Up next: - Inject dependencies into workflow services - Consolidate workflow controllers into one - Make workflow controller injectable - Inject dependencies into workflow controller --------- Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
@@ -5,7 +5,7 @@ import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||
import { UserService } from '@/services/user.service';
|
||||
import { WorkflowsService } from './workflows.services';
|
||||
import { WorkflowService } from './workflow.service';
|
||||
import type {
|
||||
CredentialUsedByWorkflow,
|
||||
WorkflowWithSharingsAndCredentials,
|
||||
@@ -13,14 +13,15 @@ import type {
|
||||
import { CredentialsService } from '@/credentials/credentials.service';
|
||||
import { ApplicationError, NodeOperationError } from 'n8n-workflow';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import Container from 'typedi';
|
||||
import Container, { Service } from 'typedi';
|
||||
import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||
|
||||
export class EEWorkflowsService extends WorkflowsService {
|
||||
static async isOwned(
|
||||
@Service()
|
||||
export class EnterpriseWorkflowService extends WorkflowService {
|
||||
async isOwned(
|
||||
user: User,
|
||||
workflowId: string,
|
||||
): Promise<{ ownsWorkflow: boolean; workflow?: WorkflowEntity }> {
|
||||
@@ -36,7 +37,7 @@ export class EEWorkflowsService extends WorkflowsService {
|
||||
return { ownsWorkflow: true, workflow };
|
||||
}
|
||||
|
||||
static async getSharings(
|
||||
async getSharings(
|
||||
transaction: EntityManager,
|
||||
workflowId: string,
|
||||
relations = ['shared'],
|
||||
@@ -48,7 +49,7 @@ export class EEWorkflowsService extends WorkflowsService {
|
||||
return workflow?.shared ?? [];
|
||||
}
|
||||
|
||||
static async pruneSharings(
|
||||
async pruneSharings(
|
||||
transaction: EntityManager,
|
||||
workflowId: string,
|
||||
userIds: string[],
|
||||
@@ -59,7 +60,7 @@ export class EEWorkflowsService extends WorkflowsService {
|
||||
});
|
||||
}
|
||||
|
||||
static async share(
|
||||
async share(
|
||||
transaction: EntityManager,
|
||||
workflow: WorkflowEntity,
|
||||
shareWithIds: string[],
|
||||
@@ -83,7 +84,7 @@ export class EEWorkflowsService extends WorkflowsService {
|
||||
return transaction.save(newSharedWorkflows);
|
||||
}
|
||||
|
||||
static addOwnerAndSharings(workflow: WorkflowWithSharingsAndCredentials): void {
|
||||
addOwnerAndSharings(workflow: WorkflowWithSharingsAndCredentials): void {
|
||||
workflow.ownedBy = null;
|
||||
workflow.sharedWith = [];
|
||||
if (!workflow.usedCredentials) {
|
||||
@@ -104,7 +105,7 @@ export class EEWorkflowsService extends WorkflowsService {
|
||||
delete workflow.shared;
|
||||
}
|
||||
|
||||
static async addCredentialsToWorkflow(
|
||||
async addCredentialsToWorkflow(
|
||||
workflow: WorkflowWithSharingsAndCredentials,
|
||||
currentUser: User,
|
||||
): Promise<void> {
|
||||
@@ -150,7 +151,7 @@ export class EEWorkflowsService extends WorkflowsService {
|
||||
});
|
||||
}
|
||||
|
||||
static validateCredentialPermissionsToUser(
|
||||
validateCredentialPermissionsToUser(
|
||||
workflow: WorkflowEntity,
|
||||
allowedCredentials: CredentialsEntity[],
|
||||
) {
|
||||
@@ -171,8 +172,8 @@ export class EEWorkflowsService extends WorkflowsService {
|
||||
});
|
||||
}
|
||||
|
||||
static async preventTampering(workflow: WorkflowEntity, workflowId: string, user: User) {
|
||||
const previousVersion = await EEWorkflowsService.get({ id: workflowId });
|
||||
async preventTampering(workflow: WorkflowEntity, workflowId: string, user: User) {
|
||||
const previousVersion = await this.get({ id: workflowId });
|
||||
|
||||
if (!previousVersion) {
|
||||
throw new NotFoundError('Workflow not found');
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Container } from 'typedi';
|
||||
import { Container, Service } from 'typedi';
|
||||
import type { IDataObject, INode, IPinData } from 'n8n-workflow';
|
||||
import { NodeApiError, ErrorReporterProxy as ErrorReporter, Workflow } from 'n8n-workflow';
|
||||
import type { FindManyOptions, FindOptionsSelect, FindOptionsWhere, UpdateResult } from 'typeorm';
|
||||
@@ -41,8 +41,9 @@ export type WorkflowsGetSharedOptions =
|
||||
| { allowGlobalScope: true; globalScope: Scope }
|
||||
| { allowGlobalScope: false };
|
||||
|
||||
export class WorkflowsService {
|
||||
static async getSharing(
|
||||
@Service()
|
||||
export class WorkflowService {
|
||||
async getSharing(
|
||||
user: User,
|
||||
workflowId: string,
|
||||
options: WorkflowsGetSharedOptions,
|
||||
@@ -68,7 +69,7 @@ export class WorkflowsService {
|
||||
* - select the _first_ pinned trigger that leads to the executed node,
|
||||
* - else select the executed pinned trigger.
|
||||
*/
|
||||
static findPinnedTrigger(workflow: IWorkflowDb, startNodes?: string[], pinData?: IPinData) {
|
||||
findPinnedTrigger(workflow: IWorkflowDb, startNodes?: string[], pinData?: IPinData) {
|
||||
if (!pinData || !startNodes) return null;
|
||||
|
||||
const isTrigger = (nodeTypeName: string) =>
|
||||
@@ -102,14 +103,14 @@ export class WorkflowsService {
|
||||
return pinnedTriggers.find((pt) => pt.name === checkNodeName) ?? null; // partial execution
|
||||
}
|
||||
|
||||
static async get(workflow: FindOptionsWhere<WorkflowEntity>, options?: { relations: string[] }) {
|
||||
async get(workflow: FindOptionsWhere<WorkflowEntity>, options?: { relations: string[] }) {
|
||||
return Container.get(WorkflowRepository).findOne({
|
||||
where: workflow,
|
||||
relations: options?.relations,
|
||||
});
|
||||
}
|
||||
|
||||
static async getMany(sharedWorkflowIds: string[], options?: ListQuery.Options) {
|
||||
async getMany(sharedWorkflowIds: string[], options?: ListQuery.Options) {
|
||||
if (sharedWorkflowIds.length === 0) return { workflows: [], count: 0 };
|
||||
|
||||
const where: FindOptionsWhere<WorkflowEntity> = {
|
||||
@@ -188,7 +189,7 @@ export class WorkflowsService {
|
||||
: { workflows, count };
|
||||
}
|
||||
|
||||
static async update(
|
||||
async update(
|
||||
user: User,
|
||||
workflow: WorkflowEntity,
|
||||
workflowId: string,
|
||||
@@ -381,7 +382,7 @@ export class WorkflowsService {
|
||||
return updatedWorkflow;
|
||||
}
|
||||
|
||||
static async runManually(
|
||||
async runManually(
|
||||
{
|
||||
workflowData,
|
||||
runData,
|
||||
@@ -395,7 +396,7 @@ export class WorkflowsService {
|
||||
const EXECUTION_MODE = 'manual';
|
||||
const ACTIVATION_MODE = 'manual';
|
||||
|
||||
const pinnedTrigger = WorkflowsService.findPinnedTrigger(workflowData, startNodes, pinData);
|
||||
const pinnedTrigger = this.findPinnedTrigger(workflowData, startNodes, pinData);
|
||||
|
||||
// If webhooks nodes exist and are active we have to wait for till we receive a call
|
||||
if (
|
||||
@@ -463,7 +464,7 @@ export class WorkflowsService {
|
||||
};
|
||||
}
|
||||
|
||||
static async delete(user: User, workflowId: string): Promise<WorkflowEntity | undefined> {
|
||||
async delete(user: User, workflowId: string): Promise<WorkflowEntity | undefined> {
|
||||
await Container.get(ExternalHooks).run('workflow.delete', [workflowId]);
|
||||
|
||||
const sharedWorkflow = await Container.get(SharedWorkflowRepository).findOne({
|
||||
@@ -502,7 +503,7 @@ export class WorkflowsService {
|
||||
return sharedWorkflow.workflow;
|
||||
}
|
||||
|
||||
static async updateWorkflowTriggerCount(id: string, triggerCount: number): Promise<UpdateResult> {
|
||||
async updateWorkflowTriggerCount(id: string, triggerCount: number): Promise<UpdateResult> {
|
||||
const qb = Container.get(WorkflowRepository).createQueryBuilder('workflow');
|
||||
return qb
|
||||
.update()
|
||||
@@ -522,19 +523,19 @@ export class WorkflowsService {
|
||||
/**
|
||||
* Saves the static data if it changed
|
||||
*/
|
||||
static async saveStaticData(workflow: Workflow): Promise<void> {
|
||||
async saveStaticData(workflow: Workflow): Promise<void> {
|
||||
if (workflow.staticData.__dataChanged === true) {
|
||||
// Static data of workflow changed and so has to be saved
|
||||
if (isWorkflowIdValid(workflow.id)) {
|
||||
// Workflow is saved so update in database
|
||||
try {
|
||||
await WorkflowsService.saveStaticDataById(workflow.id, workflow.staticData);
|
||||
await this.saveStaticDataById(workflow.id, workflow.staticData);
|
||||
workflow.staticData.__dataChanged = false;
|
||||
} catch (error) {
|
||||
ErrorReporter.error(error);
|
||||
Container.get(Logger).error(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
`There was a problem saving the workflow with id "${workflow.id}" to save changed staticData: "${error.message}"`,
|
||||
`There was a problem saving the workflow with id "${workflow.id}" to save changed Data: "${error.message}"`,
|
||||
{ workflowId: workflow.id },
|
||||
);
|
||||
}
|
||||
@@ -548,7 +549,7 @@ export class WorkflowsService {
|
||||
* @param {(string)} workflowId The id of the workflow to save data on
|
||||
* @param {IDataObject} newStaticData The static data to save
|
||||
*/
|
||||
static async saveStaticDataById(workflowId: string, newStaticData: IDataObject): Promise<void> {
|
||||
async saveStaticDataById(workflowId: string, newStaticData: IDataObject): Promise<void> {
|
||||
await Container.get(WorkflowRepository).update(workflowId, {
|
||||
staticData: newStaticData,
|
||||
});
|
||||
@@ -8,7 +8,7 @@ import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||
import { validateEntity } from '@/GenericHelpers';
|
||||
import type { ListQuery, WorkflowRequest } from '@/requests';
|
||||
import { isSharingEnabled, rightDiff } from '@/UserManagement/UserManagementHelper';
|
||||
import { EEWorkflowsService as EEWorkflows } from './workflows.services.ee';
|
||||
import { EnterpriseWorkflowService } from './workflow.service.ee';
|
||||
import { ExternalHooks } from '@/ExternalHooks';
|
||||
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||
import { CredentialsService } from '../credentials/credentials.service';
|
||||
@@ -59,7 +59,7 @@ EEWorkflowController.put(
|
||||
throw new BadRequestError('Bad request');
|
||||
}
|
||||
|
||||
const isOwnedRes = await EEWorkflows.isOwned(req.user, workflowId);
|
||||
const isOwnedRes = await Container.get(EnterpriseWorkflowService).isOwned(req.user, workflowId);
|
||||
const { ownsWorkflow } = isOwnedRes;
|
||||
let { workflow } = isOwnedRes;
|
||||
|
||||
@@ -67,10 +67,14 @@ EEWorkflowController.put(
|
||||
workflow = undefined;
|
||||
// Allow owners/admins to share
|
||||
if (await req.user.hasGlobalScope('workflow:share')) {
|
||||
const sharedRes = await EEWorkflows.getSharing(req.user, workflowId, {
|
||||
allowGlobalScope: true,
|
||||
globalScope: 'workflow:share',
|
||||
});
|
||||
const sharedRes = await Container.get(EnterpriseWorkflowService).getSharing(
|
||||
req.user,
|
||||
workflowId,
|
||||
{
|
||||
allowGlobalScope: true,
|
||||
globalScope: 'workflow:share',
|
||||
},
|
||||
);
|
||||
workflow = sharedRes?.workflow;
|
||||
}
|
||||
if (!workflow) {
|
||||
@@ -79,10 +83,11 @@ EEWorkflowController.put(
|
||||
}
|
||||
|
||||
const ownerIds = (
|
||||
await EEWorkflows.getSharings(Db.getConnection().createEntityManager(), workflowId, [
|
||||
'shared',
|
||||
'shared.role',
|
||||
])
|
||||
await Container.get(EnterpriseWorkflowService).getSharings(
|
||||
Db.getConnection().createEntityManager(),
|
||||
workflowId,
|
||||
['shared', 'shared.role'],
|
||||
)
|
||||
)
|
||||
.filter((e) => e.role.name === 'owner')
|
||||
.map((e) => e.userId);
|
||||
@@ -90,9 +95,12 @@ EEWorkflowController.put(
|
||||
let newShareeIds: string[] = [];
|
||||
await Db.transaction(async (trx) => {
|
||||
// remove all sharings that are not supposed to exist anymore
|
||||
await EEWorkflows.pruneSharings(trx, workflowId, [...ownerIds, ...shareWithIds]);
|
||||
await Container.get(EnterpriseWorkflowService).pruneSharings(trx, workflowId, [
|
||||
...ownerIds,
|
||||
...shareWithIds,
|
||||
]);
|
||||
|
||||
const sharings = await EEWorkflows.getSharings(trx, workflowId);
|
||||
const sharings = await Container.get(EnterpriseWorkflowService).getSharings(trx, workflowId);
|
||||
|
||||
// extract the new sharings that need to be added
|
||||
newShareeIds = rightDiff(
|
||||
@@ -101,7 +109,7 @@ EEWorkflowController.put(
|
||||
);
|
||||
|
||||
if (newShareeIds.length) {
|
||||
await EEWorkflows.share(trx, workflow!, newShareeIds);
|
||||
await Container.get(EnterpriseWorkflowService).share(trx, workflow!, newShareeIds);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -124,7 +132,9 @@ EEWorkflowController.get(
|
||||
relations.push('tags');
|
||||
}
|
||||
|
||||
const workflow = await EEWorkflows.get({ id: workflowId }, { relations });
|
||||
const enterpriseWorkflowService = Container.get(EnterpriseWorkflowService);
|
||||
|
||||
const workflow = await enterpriseWorkflowService.get({ id: workflowId }, { relations });
|
||||
|
||||
if (!workflow) {
|
||||
throw new NotFoundError(`Workflow with ID "${workflowId}" does not exist`);
|
||||
@@ -137,8 +147,8 @@ EEWorkflowController.get(
|
||||
);
|
||||
}
|
||||
|
||||
EEWorkflows.addOwnerAndSharings(workflow);
|
||||
await EEWorkflows.addCredentialsToWorkflow(workflow, req.user);
|
||||
enterpriseWorkflowService.addOwnerAndSharings(workflow);
|
||||
await enterpriseWorkflowService.addCredentialsToWorkflow(workflow, req.user);
|
||||
return workflow;
|
||||
}),
|
||||
);
|
||||
@@ -179,7 +189,10 @@ EEWorkflowController.post(
|
||||
const allCredentials = await CredentialsService.getMany(req.user);
|
||||
|
||||
try {
|
||||
EEWorkflows.validateCredentialPermissionsToUser(newWorkflow, allCredentials);
|
||||
Container.get(EnterpriseWorkflowService).validateCredentialPermissionsToUser(
|
||||
newWorkflow,
|
||||
allCredentials,
|
||||
);
|
||||
} catch (error) {
|
||||
throw new BadRequestError(
|
||||
'The workflow you are trying to save contains credentials that are not shared with you',
|
||||
@@ -240,7 +253,7 @@ EEWorkflowController.get(
|
||||
try {
|
||||
const sharedWorkflowIds = await WorkflowHelpers.getSharedWorkflowIds(req.user);
|
||||
|
||||
const { workflows: data, count } = await EEWorkflows.getMany(
|
||||
const { workflows: data, count } = await Container.get(EnterpriseWorkflowService).getMany(
|
||||
sharedWorkflowIds,
|
||||
req.listQueryOptions,
|
||||
);
|
||||
@@ -264,9 +277,13 @@ EEWorkflowController.patch(
|
||||
const { tags, ...rest } = req.body;
|
||||
Object.assign(updateData, rest);
|
||||
|
||||
const safeWorkflow = await EEWorkflows.preventTampering(updateData, workflowId, req.user);
|
||||
const safeWorkflow = await Container.get(EnterpriseWorkflowService).preventTampering(
|
||||
updateData,
|
||||
workflowId,
|
||||
req.user,
|
||||
);
|
||||
|
||||
const updatedWorkflow = await EEWorkflows.update(
|
||||
const updatedWorkflow = await Container.get(EnterpriseWorkflowService).update(
|
||||
req.user,
|
||||
safeWorkflow,
|
||||
workflowId,
|
||||
@@ -288,10 +305,18 @@ EEWorkflowController.post(
|
||||
Object.assign(workflow, req.body.workflowData);
|
||||
|
||||
if (req.body.workflowData.id !== undefined) {
|
||||
const safeWorkflow = await EEWorkflows.preventTampering(workflow, workflow.id, req.user);
|
||||
const safeWorkflow = await Container.get(EnterpriseWorkflowService).preventTampering(
|
||||
workflow,
|
||||
workflow.id,
|
||||
req.user,
|
||||
);
|
||||
req.body.workflowData.nodes = safeWorkflow.nodes;
|
||||
}
|
||||
|
||||
return EEWorkflows.runManually(req.body, req.user, GenericHelpers.getSessionId(req));
|
||||
return Container.get(EnterpriseWorkflowService).runManually(
|
||||
req.body,
|
||||
req.user,
|
||||
GenericHelpers.getSessionId(req),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@ import { ExternalHooks } from '@/ExternalHooks';
|
||||
import type { ListQuery, WorkflowRequest } from '@/requests';
|
||||
import { isBelowOnboardingThreshold } from '@/WorkflowHelpers';
|
||||
import { EEWorkflowController } from './workflows.controller.ee';
|
||||
import { WorkflowsService } from './workflows.services';
|
||||
import { WorkflowService } from './workflow.service';
|
||||
import { whereClause } from '@/UserManagement/UserManagementHelper';
|
||||
import { In } from 'typeorm';
|
||||
import { Container } from 'typedi';
|
||||
@@ -120,7 +120,7 @@ workflowsController.get(
|
||||
try {
|
||||
const sharedWorkflowIds = await WorkflowHelpers.getSharedWorkflowIds(req.user, ['owner']);
|
||||
|
||||
const { workflows: data, count } = await WorkflowsService.getMany(
|
||||
const { workflows: data, count } = await Container.get(WorkflowService).getMany(
|
||||
sharedWorkflowIds,
|
||||
req.listQueryOptions,
|
||||
);
|
||||
@@ -245,7 +245,7 @@ workflowsController.patch(
|
||||
const { tags, ...rest } = req.body;
|
||||
Object.assign(updateData, rest);
|
||||
|
||||
const updatedWorkflow = await WorkflowsService.update(
|
||||
const updatedWorkflow = await Container.get(WorkflowService).update(
|
||||
req.user,
|
||||
updateData,
|
||||
workflowId,
|
||||
@@ -267,7 +267,7 @@ workflowsController.delete(
|
||||
ResponseHelper.send(async (req: WorkflowRequest.Delete) => {
|
||||
const { id: workflowId } = req.params;
|
||||
|
||||
const workflow = await WorkflowsService.delete(req.user, workflowId);
|
||||
const workflow = await Container.get(WorkflowService).delete(req.user, workflowId);
|
||||
if (!workflow) {
|
||||
Container.get(Logger).verbose('User attempted to delete a workflow without permissions', {
|
||||
workflowId,
|
||||
@@ -288,6 +288,10 @@ workflowsController.delete(
|
||||
workflowsController.post(
|
||||
'/run',
|
||||
ResponseHelper.send(async (req: WorkflowRequest.ManualRun): Promise<IExecutionPushResponse> => {
|
||||
return WorkflowsService.runManually(req.body, req.user, GenericHelpers.getSessionId(req));
|
||||
return Container.get(WorkflowService).runManually(
|
||||
req.body,
|
||||
req.user,
|
||||
GenericHelpers.getSessionId(req),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user