refactor(core): Post-release refactorings of Public API (#3495)
* ⚡ Post-release refactorings * 🧪 Add `--forceExit` * 🛠 typing refactor (#3486) * 🐛 Fix middleware arguments * 👕 Fix lint * ⚡ Restore commented out block Co-authored-by: Ben Hesseldieck <1849459+BHesseldieck@users.noreply.github.com>
This commit is contained in:
@@ -178,8 +178,6 @@ export interface ICredentialsDecryptedResponse extends ICredentialsDecryptedDb {
|
||||
export type DatabaseType = 'mariadb' | 'postgresdb' | 'mysqldb' | 'sqlite';
|
||||
export type SaveExecutionDataType = 'all' | 'none';
|
||||
|
||||
export type ExecutionDataFieldFormat = 'empty' | 'flattened' | 'json';
|
||||
|
||||
export interface IExecutionBase {
|
||||
id?: number | string;
|
||||
mode: WorkflowExecuteMode;
|
||||
@@ -240,7 +238,7 @@ export interface IExecutionResponseApi {
|
||||
finished: boolean;
|
||||
retryOf?: number | string;
|
||||
retrySuccessId?: number | string;
|
||||
data?: string; // Just that we can remove it
|
||||
data?: object;
|
||||
waitTill?: Date | null;
|
||||
workflowData: IWorkflowBase;
|
||||
}
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable import/no-cycle */
|
||||
import express, { Router } from 'express';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
import * as OpenApiValidator from 'express-openapi-validator';
|
||||
import { HttpError } from 'express-openapi-validator/dist/framework/types';
|
||||
import fs from 'fs/promises';
|
||||
import { OpenAPIV3 } from 'openapi-types';
|
||||
import path from 'path';
|
||||
import * as swaggerUi from 'swagger-ui-express';
|
||||
import swaggerUi from 'swagger-ui-express';
|
||||
import validator from 'validator';
|
||||
import * as YAML from 'yamljs';
|
||||
import { Db, InternalHooksManager } from '..';
|
||||
import YAML from 'yamljs';
|
||||
|
||||
import config from '../../config';
|
||||
import { Db, InternalHooksManager } from '..';
|
||||
import { getInstanceBaseUrl } from '../UserManagement/UserManagementHelper';
|
||||
|
||||
function createApiRouter(
|
||||
version: string,
|
||||
openApiSpecPath: string,
|
||||
hanldersDirectory: string,
|
||||
handlersDirectory: string,
|
||||
swaggerThemeCss: string,
|
||||
publicApiEndpoint: string,
|
||||
): Router {
|
||||
const n8nPath = config.getEnv('path');
|
||||
const swaggerDocument = YAML.load(openApiSpecPath) as swaggerUi.JsonObject;
|
||||
// add the server depeding on the config so the user can interact with the API
|
||||
// from the swagger UI
|
||||
// from the Swagger UI
|
||||
swaggerDocument.server = [
|
||||
{
|
||||
url: `${getInstanceBaseUrl()}/${publicApiEndpoint}/${version}}`,
|
||||
},
|
||||
];
|
||||
const apiController = express.Router();
|
||||
|
||||
apiController.use(
|
||||
`/${publicApiEndpoint}/${version}/docs`,
|
||||
swaggerUi.serveFiles(swaggerDocument),
|
||||
@@ -42,12 +42,14 @@ function createApiRouter(
|
||||
customfavIcon: `${n8nPath}favicon.ico`,
|
||||
}),
|
||||
);
|
||||
|
||||
apiController.use(`/${publicApiEndpoint}/${version}`, express.json());
|
||||
|
||||
apiController.use(
|
||||
`/${publicApiEndpoint}/${version}`,
|
||||
OpenApiValidator.middleware({
|
||||
apiSpec: openApiSpecPath,
|
||||
operationHandlers: hanldersDirectory,
|
||||
operationHandlers: handlersDirectory,
|
||||
validateRequests: true,
|
||||
validateApiSpec: true,
|
||||
formats: [
|
||||
@@ -71,16 +73,12 @@ function createApiRouter(
|
||||
schema: OpenAPIV3.ApiKeySecurityScheme,
|
||||
): Promise<boolean> => {
|
||||
const apiKey = req.headers[schema.name.toLowerCase()];
|
||||
const user = await Db.collections.User?.findOne({
|
||||
where: {
|
||||
apiKey,
|
||||
},
|
||||
const user = await Db.collections.User.findOne({
|
||||
where: { apiKey },
|
||||
relations: ['globalRole'],
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
if (!user) return false;
|
||||
|
||||
void InternalHooksManager.getInstance().onUserInvokedApi({
|
||||
user_id: user.id,
|
||||
@@ -97,13 +95,20 @@ function createApiRouter(
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
apiController.use(
|
||||
(error: HttpError, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
(
|
||||
error: HttpError,
|
||||
_req: express.Request,
|
||||
res: express.Response,
|
||||
_next: express.NextFunction,
|
||||
) => {
|
||||
return res.status(error.status || 400).json({
|
||||
message: error.message,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
return apiController;
|
||||
}
|
||||
|
||||
@@ -114,11 +119,12 @@ export const loadPublicApiVersions = async (
|
||||
const folders = await fs.readdir(__dirname);
|
||||
const css = (await fs.readFile(swaggerThemePath)).toString();
|
||||
const versions = folders.filter((folderName) => folderName.startsWith('v'));
|
||||
const apiRouters: express.Router[] = [];
|
||||
for (const version of versions) {
|
||||
|
||||
const apiRouters = versions.map((version) => {
|
||||
const openApiPath = path.join(__dirname, version, 'openapi.yml');
|
||||
apiRouters.push(createApiRouter(version, openApiPath, __dirname, css, publicApiEndpoint));
|
||||
}
|
||||
return createApiRouter(version, openApiPath, __dirname, css, publicApiEndpoint);
|
||||
});
|
||||
|
||||
return {
|
||||
apiRouters,
|
||||
apiLatestVersion: Number(versions.pop()?.charAt(1)) ?? 1,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import express = require('express');
|
||||
import express from 'express';
|
||||
|
||||
import { CredentialsHelper } from '../../../../CredentialsHelper';
|
||||
import { CredentialTypes } from '../../../../CredentialTypes';
|
||||
|
||||
import { CredentialsEntity } from '../../../../databases/entities/CredentialsEntity';
|
||||
import { CredentialRequest } from '../../../../requests';
|
||||
import { CredentialTypeRequest } from '../../../types';
|
||||
@@ -29,7 +29,7 @@ export = {
|
||||
res: express.Response,
|
||||
): Promise<express.Response<Partial<CredentialsEntity>>> => {
|
||||
try {
|
||||
const newCredential = await createCredential(req.body as Partial<CredentialsEntity>);
|
||||
const newCredential = await createCredential(req.body);
|
||||
|
||||
const encryptedData = await encryptCredential(newCredential);
|
||||
|
||||
@@ -56,7 +56,7 @@ export = {
|
||||
res: express.Response,
|
||||
): Promise<express.Response<Partial<CredentialsEntity>>> => {
|
||||
const { id: credentialId } = req.params;
|
||||
let credentials: CredentialsEntity | undefined;
|
||||
let credential: CredentialsEntity | undefined;
|
||||
|
||||
if (req.user.globalRole.name !== 'owner') {
|
||||
const shared = await getSharedCredentials(req.user.id, credentialId, [
|
||||
@@ -65,27 +65,20 @@ export = {
|
||||
]);
|
||||
|
||||
if (shared?.role.name === 'owner') {
|
||||
credentials = shared.credentials;
|
||||
} else {
|
||||
// LoggerProxy.info('Attempt to delete credential blocked due to lack of permissions', {
|
||||
// credentialId,
|
||||
// userId: req.user.id,
|
||||
// });
|
||||
credential = shared.credentials;
|
||||
}
|
||||
} else {
|
||||
credentials = (await getCredentials(credentialId)) as CredentialsEntity;
|
||||
credential = (await getCredentials(credentialId)) as CredentialsEntity;
|
||||
}
|
||||
|
||||
if (!credentials) {
|
||||
return res.status(404).json({
|
||||
message: 'Not Found',
|
||||
});
|
||||
if (!credential) {
|
||||
return res.status(404).json({ message: 'Not Found' });
|
||||
}
|
||||
|
||||
await removeCredential(credentials);
|
||||
credentials.id = Number(credentialId);
|
||||
await removeCredential(credential);
|
||||
credential.id = Number(credentialId);
|
||||
|
||||
return res.json(sanitizeCredentials(credentials));
|
||||
return res.json(sanitizeCredentials(credential));
|
||||
},
|
||||
],
|
||||
|
||||
@@ -97,14 +90,12 @@ export = {
|
||||
try {
|
||||
CredentialTypes().getByName(credentialTypeName);
|
||||
} catch (error) {
|
||||
return res.status(404).json({
|
||||
message: 'Not Found',
|
||||
});
|
||||
return res.status(404).json({ message: 'Not Found' });
|
||||
}
|
||||
|
||||
let schema = new CredentialsHelper('').getCredentialsProperties(credentialTypeName);
|
||||
|
||||
schema = schema.filter((nodeProperty) => nodeProperty.type !== 'hidden');
|
||||
const schema = new CredentialsHelper('')
|
||||
.getCredentialsProperties(credentialTypeName)
|
||||
.filter((property) => property.type !== 'hidden');
|
||||
|
||||
return res.json(toJsonSchema(schema));
|
||||
},
|
||||
|
||||
@@ -1,37 +1,36 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable consistent-return */
|
||||
import { RequestHandler } from 'express';
|
||||
/* eslint-disable @typescript-eslint/no-invalid-void-type */
|
||||
|
||||
import express from 'express';
|
||||
import { validate } from 'jsonschema';
|
||||
|
||||
import { CredentialsHelper, CredentialTypes } from '../../../..';
|
||||
import { CredentialRequest } from '../../../types';
|
||||
import { toJsonSchema } from './credentials.service';
|
||||
|
||||
export const validCredentialType: RequestHandler = async (
|
||||
export const validCredentialType = (
|
||||
req: CredentialRequest.Create,
|
||||
res,
|
||||
next,
|
||||
): Promise<any> => {
|
||||
const { type } = req.body;
|
||||
res: express.Response,
|
||||
next: express.NextFunction,
|
||||
): express.Response | void => {
|
||||
try {
|
||||
CredentialTypes().getByName(type);
|
||||
} catch (error) {
|
||||
return res.status(400).json({
|
||||
message: 'req.body.type is not a known type',
|
||||
});
|
||||
CredentialTypes().getByName(req.body.type);
|
||||
} catch (_) {
|
||||
return res.status(400).json({ message: 'req.body.type is not a known type' });
|
||||
}
|
||||
next();
|
||||
|
||||
return next();
|
||||
};
|
||||
|
||||
export const validCredentialsProperties: RequestHandler = async (
|
||||
export const validCredentialsProperties = (
|
||||
req: CredentialRequest.Create,
|
||||
res,
|
||||
next,
|
||||
): Promise<any> => {
|
||||
res: express.Response,
|
||||
next: express.NextFunction,
|
||||
): express.Response | void => {
|
||||
const { type, data } = req.body;
|
||||
|
||||
let properties = new CredentialsHelper('').getCredentialsProperties(type);
|
||||
|
||||
properties = properties.filter((nodeProperty) => nodeProperty.type !== 'hidden');
|
||||
const properties = new CredentialsHelper('')
|
||||
.getCredentialsProperties(type)
|
||||
.filter((property) => property.type !== 'hidden');
|
||||
|
||||
const schema = toJsonSchema(properties);
|
||||
|
||||
@@ -43,5 +42,5 @@ export const validCredentialsProperties: RequestHandler = async (
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
return next();
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ import { SharedCredentials } from '../../../../databases/entities/SharedCredenti
|
||||
import { User } from '../../../../databases/entities/User';
|
||||
import { externalHooks } from '../../../../Server';
|
||||
import { IDependency, IJsonSchema } from '../../../types';
|
||||
import { CredentialRequest } from '../../../../requests';
|
||||
|
||||
export async function getCredentials(
|
||||
credentialId: number | string,
|
||||
@@ -38,7 +39,7 @@ export async function getSharedCredentials(
|
||||
}
|
||||
|
||||
export async function createCredential(
|
||||
properties: Partial<CredentialsEntity>,
|
||||
properties: CredentialRequest.CredentialProperties,
|
||||
): Promise<CredentialsEntity> {
|
||||
const newCredential = new CredentialsEntity();
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import express = require('express');
|
||||
import express from 'express';
|
||||
|
||||
import { BinaryDataManager } from 'n8n-core';
|
||||
|
||||
import {
|
||||
getExecutions,
|
||||
getExecutionInWorkflows,
|
||||
deleteExecution,
|
||||
getExecutionsCount,
|
||||
} from './executions.service';
|
||||
|
||||
import { ActiveExecutions } from '../../../..';
|
||||
import { authorize, validCursor } from '../../shared/middlewares/global.middleware';
|
||||
|
||||
import { ExecutionRequest } from '../../../types';
|
||||
import { getSharedWorkflowIds } from '../workflows/workflows.service';
|
||||
import { encodeNextCursor } from '../../shared/services/pagination.service';
|
||||
@@ -20,31 +19,24 @@ export = {
|
||||
deleteExecution: [
|
||||
authorize(['owner', 'member']),
|
||||
async (req: ExecutionRequest.Delete, res: express.Response): Promise<express.Response> => {
|
||||
const { id } = req.params;
|
||||
|
||||
const sharedWorkflowsIds = await getSharedWorkflowIds(req.user);
|
||||
|
||||
// user does not have workflows hence no executions
|
||||
// or the execution he is trying to access belongs to a workflow he does not own
|
||||
if (!sharedWorkflowsIds.length) {
|
||||
return res.status(404).json({
|
||||
message: 'Not Found',
|
||||
});
|
||||
return res.status(404).json({ message: 'Not Found' });
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
|
||||
// look for the execution on the workflow the user owns
|
||||
const execution = await getExecutionInWorkflows(id, sharedWorkflowsIds, false);
|
||||
|
||||
// execution was not found
|
||||
if (!execution) {
|
||||
return res.status(404).json({
|
||||
message: 'Not Found',
|
||||
});
|
||||
return res.status(404).json({ message: 'Not Found' });
|
||||
}
|
||||
|
||||
const binaryDataManager = BinaryDataManager.getInstance();
|
||||
|
||||
await binaryDataManager.deleteBinaryDataByExecutionId(execution.id.toString());
|
||||
await BinaryDataManager.getInstance().deleteBinaryDataByExecutionId(execution.id.toString());
|
||||
|
||||
await deleteExecution(execution);
|
||||
|
||||
@@ -56,35 +48,28 @@ export = {
|
||||
getExecution: [
|
||||
authorize(['owner', 'member']),
|
||||
async (req: ExecutionRequest.Get, res: express.Response): Promise<express.Response> => {
|
||||
const { id } = req.params;
|
||||
const { includeData = false } = req.query;
|
||||
|
||||
const sharedWorkflowsIds = await getSharedWorkflowIds(req.user);
|
||||
|
||||
// user does not have workflows hence no executions
|
||||
// or the execution he is trying to access belongs to a workflow he does not own
|
||||
if (!sharedWorkflowsIds.length) {
|
||||
return res.status(404).json({
|
||||
message: 'Not Found',
|
||||
});
|
||||
return res.status(404).json({ message: 'Not Found' });
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
const { includeData = false } = req.query;
|
||||
|
||||
// look for the execution on the workflow the user owns
|
||||
const execution = await getExecutionInWorkflows(id, sharedWorkflowsIds, includeData);
|
||||
|
||||
// execution was not found
|
||||
if (!execution) {
|
||||
return res.status(404).json({
|
||||
message: 'Not Found',
|
||||
});
|
||||
return res.status(404).json({ message: 'Not Found' });
|
||||
}
|
||||
|
||||
const telemetryData = {
|
||||
void InternalHooksManager.getInstance().onUserRetrievedExecution({
|
||||
user_id: req.user.id,
|
||||
public_api: true,
|
||||
};
|
||||
|
||||
void InternalHooksManager.getInstance().onUserRetrievedExecution(telemetryData);
|
||||
});
|
||||
|
||||
return res.json(execution);
|
||||
},
|
||||
@@ -106,10 +91,7 @@ export = {
|
||||
// user does not have workflows hence no executions
|
||||
// or the execution he is trying to access belongs to a workflow he does not own
|
||||
if (!sharedWorkflowsIds.length) {
|
||||
return res.status(200).json({
|
||||
data: [],
|
||||
nextCursor: null,
|
||||
});
|
||||
return res.status(200).json({ data: [], nextCursor: null });
|
||||
}
|
||||
|
||||
// get running workflows so we exclude them from the result
|
||||
@@ -134,12 +116,10 @@ export = {
|
||||
|
||||
const count = await getExecutionsCount(filters);
|
||||
|
||||
const telemetryData = {
|
||||
void InternalHooksManager.getInstance().onUserRetrievedAllExecutions({
|
||||
user_id: req.user.id,
|
||||
public_api: true,
|
||||
};
|
||||
|
||||
void InternalHooksManager.getInstance().onUserRetrievedAllExecutions(telemetryData);
|
||||
});
|
||||
|
||||
return res.json({
|
||||
data: executions,
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
import { parse } from 'flatted';
|
||||
import { In, Not, ObjectLiteral, LessThan, IsNull } from 'typeorm';
|
||||
|
||||
import { Db, IExecutionFlattedDb, IExecutionResponseApi } from '../../../..';
|
||||
import { ExecutionStatus } from '../../../types';
|
||||
|
||||
function prepareExecutionData(
|
||||
execution: IExecutionFlattedDb | undefined,
|
||||
): IExecutionResponseApi | undefined {
|
||||
if (execution === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (!execution) return undefined;
|
||||
|
||||
if (!execution.data) {
|
||||
return execution;
|
||||
}
|
||||
// @ts-ignore
|
||||
if (!execution.data) return execution;
|
||||
|
||||
return {
|
||||
...execution,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
data: parse(execution.data),
|
||||
data: parse(execution.data) as object,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,11 +29,12 @@ function getStatusCondition(status: ExecutionStatus): ObjectLiteral {
|
||||
condition.stoppedAt = Not(IsNull());
|
||||
condition.finished = false;
|
||||
}
|
||||
|
||||
return condition;
|
||||
}
|
||||
|
||||
function getExecutionSelectableProperties(includeData?: boolean): Array<keyof IExecutionFlattedDb> {
|
||||
const returnData: Array<keyof IExecutionFlattedDb> = [
|
||||
const selectFields: Array<keyof IExecutionFlattedDb> = [
|
||||
'id',
|
||||
'mode',
|
||||
'retryOf',
|
||||
@@ -47,10 +45,10 @@ function getExecutionSelectableProperties(includeData?: boolean): Array<keyof IE
|
||||
'waitTill',
|
||||
'finished',
|
||||
];
|
||||
if (includeData) {
|
||||
returnData.push('data');
|
||||
}
|
||||
return returnData;
|
||||
|
||||
if (includeData) selectFields.push('data');
|
||||
|
||||
return selectFields;
|
||||
}
|
||||
|
||||
export async function getExecutions(data: {
|
||||
@@ -92,6 +90,7 @@ export async function getExecutionsCount(data: {
|
||||
},
|
||||
take: data.limit,
|
||||
});
|
||||
|
||||
return executions;
|
||||
}
|
||||
|
||||
@@ -107,9 +106,13 @@ export async function getExecutionInWorkflows(
|
||||
workflowId: In(workflows),
|
||||
},
|
||||
});
|
||||
|
||||
return prepareExecutionData(execution);
|
||||
}
|
||||
|
||||
export async function deleteExecution(execution: IExecutionResponseApi | undefined): Promise<void> {
|
||||
await Db.collections.Execution.remove(execution as IExecutionFlattedDb);
|
||||
export async function deleteExecution(
|
||||
execution: IExecutionResponseApi | undefined,
|
||||
): Promise<IExecutionFlattedDb> {
|
||||
// @ts-ignore
|
||||
return Db.collections.Execution.remove(execution);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import express = require('express');
|
||||
import express from 'express';
|
||||
|
||||
import { FindManyOptions, In } from 'typeorm';
|
||||
|
||||
import { ActiveWorkflowRunner, Db } from '../../../..';
|
||||
import config = require('../../../../../config');
|
||||
import { WorkflowEntity } from '../../../../databases/entities/WorkflowEntity';
|
||||
@@ -30,25 +32,24 @@ export = {
|
||||
createWorkflow: [
|
||||
authorize(['owner', 'member']),
|
||||
async (req: WorkflowRequest.Create, res: express.Response): Promise<express.Response> => {
|
||||
let workflow = req.body;
|
||||
const workflow = req.body;
|
||||
|
||||
workflow.active = false;
|
||||
|
||||
// if the workflow does not have a start node, add it.
|
||||
if (!hasStartNode(workflow)) {
|
||||
workflow.nodes.push(getStartNode());
|
||||
}
|
||||
|
||||
const role = await getWorkflowOwnerRole();
|
||||
|
||||
await replaceInvalidCredentials(workflow);
|
||||
|
||||
workflow = await createWorkflow(workflow, req.user, role);
|
||||
const role = await getWorkflowOwnerRole();
|
||||
|
||||
await externalHooks.run('workflow.afterCreate', [workflow]);
|
||||
void InternalHooksManager.getInstance().onWorkflowCreated(req.user.id, workflow, true);
|
||||
const createdWorkflow = await createWorkflow(workflow, req.user, role);
|
||||
|
||||
return res.json(workflow);
|
||||
await externalHooks.run('workflow.afterCreate', [createdWorkflow]);
|
||||
void InternalHooksManager.getInstance().onWorkflowCreated(req.user.id, createdWorkflow, true);
|
||||
|
||||
return res.json(createdWorkflow);
|
||||
},
|
||||
],
|
||||
deleteWorkflow: [
|
||||
@@ -61,16 +62,12 @@ export = {
|
||||
if (!sharedWorkflow) {
|
||||
// user trying to access a workflow he does not own
|
||||
// or workflow does not exist
|
||||
return res.status(404).json({
|
||||
message: 'Not Found',
|
||||
});
|
||||
return res.status(404).json({ message: 'Not Found' });
|
||||
}
|
||||
|
||||
const workflowRunner = ActiveWorkflowRunner.getInstance();
|
||||
|
||||
if (sharedWorkflow.workflow.active) {
|
||||
// deactivate before deleting
|
||||
await workflowRunner.remove(id.toString());
|
||||
await ActiveWorkflowRunner.getInstance().remove(id.toString());
|
||||
}
|
||||
|
||||
await Db.collections.Workflow.delete(id);
|
||||
@@ -91,17 +88,13 @@ export = {
|
||||
if (!sharedWorkflow) {
|
||||
// user trying to access a workflow he does not own
|
||||
// or workflow does not exist
|
||||
return res.status(404).json({
|
||||
message: 'Not Found',
|
||||
});
|
||||
return res.status(404).json({ message: 'Not Found' });
|
||||
}
|
||||
|
||||
const telemetryData = {
|
||||
void InternalHooksManager.getInstance().onUserRetrievedWorkflow({
|
||||
user_id: req.user.id,
|
||||
public_api: true,
|
||||
};
|
||||
|
||||
void InternalHooksManager.getInstance().onUserRetrievedWorkflow(telemetryData);
|
||||
});
|
||||
|
||||
return res.json(sharedWorkflow.workflow);
|
||||
},
|
||||
@@ -158,12 +151,10 @@ export = {
|
||||
count = await getWorkflowsCount(query);
|
||||
}
|
||||
|
||||
const telemetryData = {
|
||||
void InternalHooksManager.getInstance().onUserRetrievedAllWorkflows({
|
||||
user_id: req.user.id,
|
||||
public_api: true,
|
||||
};
|
||||
|
||||
void InternalHooksManager.getInstance().onUserRetrievedAllWorkflows(telemetryData);
|
||||
});
|
||||
|
||||
return res.json({
|
||||
data: workflows,
|
||||
@@ -187,18 +178,13 @@ export = {
|
||||
if (!sharedWorkflow) {
|
||||
// user trying to access a workflow he does not own
|
||||
// or workflow does not exist
|
||||
return res.status(404).json({
|
||||
message: 'Not Found',
|
||||
});
|
||||
return res.status(404).json({ message: 'Not Found' });
|
||||
}
|
||||
|
||||
// if the workflow does not have a start node, add it.
|
||||
// else there is nothing you can do in IU
|
||||
if (!hasStartNode(updateData)) {
|
||||
updateData.nodes.push(getStartNode());
|
||||
}
|
||||
|
||||
// check credentials for old format
|
||||
await replaceInvalidCredentials(updateData);
|
||||
|
||||
const workflowRunner = ActiveWorkflowRunner.getInstance();
|
||||
@@ -215,10 +201,9 @@ export = {
|
||||
try {
|
||||
await workflowRunner.add(sharedWorkflow.workflowId.toString(), 'update');
|
||||
} catch (error) {
|
||||
// todo
|
||||
// remove the type assertion
|
||||
const errorObject = error as unknown as { message: string };
|
||||
return res.status(400).json({ error: errorObject.message });
|
||||
if (error instanceof Error) {
|
||||
return res.status(400).json({ message: error.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,21 +225,19 @@ export = {
|
||||
if (!sharedWorkflow) {
|
||||
// user trying to access a workflow he does not own
|
||||
// or workflow does not exist
|
||||
return res.status(404).json({
|
||||
message: 'Not Found',
|
||||
});
|
||||
return res.status(404).json({ message: 'Not Found' });
|
||||
}
|
||||
|
||||
const workflowRunner = ActiveWorkflowRunner.getInstance();
|
||||
|
||||
if (!sharedWorkflow.workflow.active) {
|
||||
try {
|
||||
await workflowRunner.add(sharedWorkflow.workflowId.toString(), 'activate');
|
||||
await ActiveWorkflowRunner.getInstance().add(
|
||||
sharedWorkflow.workflowId.toString(),
|
||||
'activate',
|
||||
);
|
||||
} catch (error) {
|
||||
// todo
|
||||
// remove the type assertion
|
||||
const errorObject = error as unknown as { message: string };
|
||||
return res.status(400).json({ error: errorObject.message });
|
||||
if (error instanceof Error) {
|
||||
return res.status(400).json({ message: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
// change the status to active in the DB
|
||||
@@ -279,9 +262,7 @@ export = {
|
||||
if (!sharedWorkflow) {
|
||||
// user trying to access a workflow he does not own
|
||||
// or workflow does not exist
|
||||
return res.status(404).json({
|
||||
message: 'Not Found',
|
||||
});
|
||||
return res.status(404).json({ message: 'Not Found' });
|
||||
}
|
||||
|
||||
const workflowRunner = ActiveWorkflowRunner.getInstance();
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { FindManyOptions, In, UpdateResult } from 'typeorm';
|
||||
import { intersection } from 'lodash';
|
||||
import type { INode } from 'n8n-workflow';
|
||||
import { FindManyOptions, In, UpdateResult } from 'typeorm';
|
||||
|
||||
import { Db } from '../../../..';
|
||||
import { User } from '../../../../databases/entities/User';
|
||||
import { WorkflowEntity } from '../../../../databases/entities/WorkflowEntity';
|
||||
import { Db } from '../../../..';
|
||||
import { SharedWorkflow } from '../../../../databases/entities/SharedWorkflow';
|
||||
import { isInstanceOwner } from '../users/users.service';
|
||||
import { Role } from '../../../../databases/entities/Role';
|
||||
|
||||
export async function getSharedWorkflowIds(user: User): Promise<number[]> {
|
||||
const sharedWorkflows = await Db.collections.SharedWorkflow.find({
|
||||
where: {
|
||||
user,
|
||||
},
|
||||
where: { user },
|
||||
});
|
||||
|
||||
return sharedWorkflows.map((workflow) => workflow.workflowId);
|
||||
}
|
||||
|
||||
@@ -21,14 +21,13 @@ export async function getSharedWorkflow(
|
||||
user: User,
|
||||
workflowId?: string | undefined,
|
||||
): Promise<SharedWorkflow | undefined> {
|
||||
const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({
|
||||
return Db.collections.SharedWorkflow.findOne({
|
||||
where: {
|
||||
...(!isInstanceOwner(user) && { user }),
|
||||
...(workflowId && { workflow: { id: workflowId } }),
|
||||
},
|
||||
relations: ['workflow'],
|
||||
});
|
||||
return sharedWorkflow;
|
||||
}
|
||||
|
||||
export async function getSharedWorkflows(
|
||||
@@ -38,23 +37,19 @@ export async function getSharedWorkflows(
|
||||
workflowIds?: number[];
|
||||
},
|
||||
): Promise<SharedWorkflow[]> {
|
||||
const sharedWorkflows = await Db.collections.SharedWorkflow.find({
|
||||
return Db.collections.SharedWorkflow.find({
|
||||
where: {
|
||||
...(!isInstanceOwner(user) && { user }),
|
||||
...(options.workflowIds && { workflow: { id: In(options.workflowIds) } }),
|
||||
},
|
||||
...(options.relations && { relations: options.relations }),
|
||||
});
|
||||
return sharedWorkflows;
|
||||
}
|
||||
|
||||
export async function getWorkflowById(id: number): Promise<WorkflowEntity | undefined> {
|
||||
const workflow = await Db.collections.Workflow.findOne({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
return Db.collections.Workflow.findOne({
|
||||
where: { id },
|
||||
});
|
||||
return workflow;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,9 +58,7 @@ export async function getWorkflowById(id: number): Promise<WorkflowEntity | unde
|
||||
*/
|
||||
export async function getWorkflowIdsViaTags(tags: string[]): Promise<number[]> {
|
||||
const dbTags = await Db.collections.Tag.find({
|
||||
where: {
|
||||
name: In(tags),
|
||||
},
|
||||
where: { name: In(tags) },
|
||||
relations: ['workflows'],
|
||||
});
|
||||
|
||||
@@ -79,11 +72,11 @@ export async function createWorkflow(
|
||||
user: User,
|
||||
role: Role,
|
||||
): Promise<WorkflowEntity> {
|
||||
let savedWorkflow: unknown;
|
||||
const newWorkflow = new WorkflowEntity();
|
||||
Object.assign(newWorkflow, workflow);
|
||||
await Db.transaction(async (transactionManager) => {
|
||||
savedWorkflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
|
||||
return Db.transaction(async (transactionManager) => {
|
||||
const newWorkflow = new WorkflowEntity();
|
||||
Object.assign(newWorkflow, workflow);
|
||||
const savedWorkflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
|
||||
|
||||
const newSharedWorkflow = new SharedWorkflow();
|
||||
Object.assign(newSharedWorkflow, {
|
||||
role,
|
||||
@@ -91,8 +84,9 @@ export async function createWorkflow(
|
||||
workflow: savedWorkflow,
|
||||
});
|
||||
await transactionManager.save<SharedWorkflow>(newSharedWorkflow);
|
||||
|
||||
return savedWorkflow;
|
||||
});
|
||||
return savedWorkflow as WorkflowEntity;
|
||||
}
|
||||
|
||||
export async function setWorkflowAsActive(workflow: WorkflowEntity): Promise<UpdateResult> {
|
||||
@@ -110,13 +104,11 @@ export async function deleteWorkflow(workflow: WorkflowEntity): Promise<Workflow
|
||||
export async function getWorkflows(
|
||||
options: FindManyOptions<WorkflowEntity>,
|
||||
): Promise<WorkflowEntity[]> {
|
||||
const workflows = await Db.collections.Workflow.find(options);
|
||||
return workflows;
|
||||
return Db.collections.Workflow.find(options);
|
||||
}
|
||||
|
||||
export async function getWorkflowsCount(options: FindManyOptions<WorkflowEntity>): Promise<number> {
|
||||
const count = await Db.collections.Workflow.count(options);
|
||||
return count;
|
||||
return Db.collections.Workflow.count(options);
|
||||
}
|
||||
|
||||
export async function updateWorkflow(
|
||||
@@ -127,9 +119,11 @@ export async function updateWorkflow(
|
||||
}
|
||||
|
||||
export function hasStartNode(workflow: WorkflowEntity): boolean {
|
||||
return !(
|
||||
!workflow.nodes.length || !workflow.nodes.find((node) => node.type === 'n8n-nodes-base.start')
|
||||
);
|
||||
if (!workflow.nodes.length) return false;
|
||||
|
||||
const found = workflow.nodes.find((node) => node.type === 'n8n-nodes-base.start');
|
||||
|
||||
return Boolean(found);
|
||||
}
|
||||
|
||||
export function getStartNode(): INode {
|
||||
|
||||
@@ -1,24 +1,31 @@
|
||||
/* eslint-disable consistent-return */
|
||||
import { RequestHandler } from 'express';
|
||||
import { PaginatatedRequest } from '../../../types';
|
||||
/* eslint-disable @typescript-eslint/no-invalid-void-type */
|
||||
|
||||
import express from 'express';
|
||||
|
||||
import { AuthenticatedRequest, PaginatatedRequest } from '../../../types';
|
||||
import { decodeCursor } from '../services/pagination.service';
|
||||
|
||||
type Role = 'member' | 'owner';
|
||||
export const authorize =
|
||||
(authorizedRoles: readonly string[]) =>
|
||||
(
|
||||
req: AuthenticatedRequest,
|
||||
res: express.Response,
|
||||
next: express.NextFunction,
|
||||
): express.Response | void => {
|
||||
const { name } = req.user.globalRole;
|
||||
|
||||
if (!authorizedRoles.includes(name)) {
|
||||
return res.status(403).json({ message: 'Forbidden' });
|
||||
}
|
||||
|
||||
export const authorize: (role: Role[]) => RequestHandler = (role: Role[]) => (req, res, next) => {
|
||||
const {
|
||||
globalRole: { name: userRole },
|
||||
} = req.user as { globalRole: { name: Role } };
|
||||
if (role.includes(userRole)) {
|
||||
return next();
|
||||
}
|
||||
return res.status(403).json({
|
||||
message: 'Forbidden',
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
export const validCursor: RequestHandler = (req: PaginatatedRequest, res, next) => {
|
||||
export const validCursor = (
|
||||
req: PaginatatedRequest,
|
||||
res: express.Response,
|
||||
next: express.NextFunction,
|
||||
): express.Response | void => {
|
||||
if (req.query.cursor) {
|
||||
const { cursor } = req.query;
|
||||
try {
|
||||
@@ -36,5 +43,6 @@ export const validCursor: RequestHandler = (req: PaginatatedRequest, res, next)
|
||||
});
|
||||
}
|
||||
}
|
||||
next();
|
||||
|
||||
return next();
|
||||
};
|
||||
|
||||
@@ -12,7 +12,6 @@ export const decodeCursor = (cursor: string): PaginationOffsetDecoded | Paginati
|
||||
};
|
||||
|
||||
const encodeOffSetPagination = (pagination: OffsetPagination): string | null => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (pagination.numberOfTotalRecords > pagination.offset + pagination.limit) {
|
||||
return Buffer.from(
|
||||
JSON.stringify({
|
||||
|
||||
6
packages/cli/src/requests.d.ts
vendored
6
packages/cli/src/requests.d.ts
vendored
@@ -82,7 +82,7 @@ export declare namespace WorkflowRequest {
|
||||
// ----------------------------------
|
||||
|
||||
export declare namespace CredentialRequest {
|
||||
type RequestBody = Partial<{
|
||||
type CredentialProperties = Partial<{
|
||||
id: string; // delete if sent
|
||||
name: string;
|
||||
type: string;
|
||||
@@ -90,7 +90,7 @@ export declare namespace CredentialRequest {
|
||||
data: ICredentialDataDecryptedObject;
|
||||
}>;
|
||||
|
||||
type Create = AuthenticatedRequest<{}, {}, RequestBody>;
|
||||
type Create = AuthenticatedRequest<{}, {}, CredentialProperties>;
|
||||
|
||||
type Get = AuthenticatedRequest<{ id: string }, {}, {}, Record<string, string>>;
|
||||
|
||||
@@ -98,7 +98,7 @@ export declare namespace CredentialRequest {
|
||||
|
||||
type GetAll = AuthenticatedRequest<{}, {}, {}, { filter: string }>;
|
||||
|
||||
type Update = AuthenticatedRequest<{ id: string }, {}, RequestBody>;
|
||||
type Update = AuthenticatedRequest<{ id: string }, {}, CredentialProperties>;
|
||||
|
||||
type NewName = WorkflowRequest.NewName;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user