mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
filter by credential ids in a better way
This commit is contained in:
parent
ee1b50de3e
commit
fd9435c3f1
|
@ -91,6 +91,9 @@ export class WorkflowEntity extends WithTimestampsAndStringId implements IWorkfl
|
||||||
display() {
|
display() {
|
||||||
return `"${this.name}" (ID: ${this.id})`;
|
return `"${this.name}" (ID: ${this.id})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Column({ type: 'simple-array', default: '' })
|
||||||
|
credentialIds: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import type { MigrationContext, ReversibleMigration } from '@/databases/types';
|
||||||
|
|
||||||
|
export class AddWorkflowFilterColumns1730286483664 implements ReversibleMigration {
|
||||||
|
async up({ schemaBuilder: { addColumns, column } }: MigrationContext) {
|
||||||
|
console.log('foo');
|
||||||
|
await addColumns('workflow_entity', [column('credentialIds').text]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down({ schemaBuilder: { dropColumns } }: MigrationContext) {
|
||||||
|
await dropColumns('workflow_entity', ['credentialIds']);
|
||||||
|
}
|
||||||
|
}
|
|
@ -65,6 +65,7 @@ import { CreateAnnotationTables1724753530828 } from '../common/1724753530828-Cre
|
||||||
import { CreateProcessedDataTable1726606152711 } from '../common/1726606152711-CreateProcessedDataTable';
|
import { CreateProcessedDataTable1726606152711 } from '../common/1726606152711-CreateProcessedDataTable';
|
||||||
import { SeparateExecutionCreationFromStart1727427440136 } from '../common/1727427440136-SeparateExecutionCreationFromStart';
|
import { SeparateExecutionCreationFromStart1727427440136 } from '../common/1727427440136-SeparateExecutionCreationFromStart';
|
||||||
import { UpdateProcessedDataValueColumnToText1729607673464 } from '../common/1729607673464-UpdateProcessedDataValueColumnToText';
|
import { UpdateProcessedDataValueColumnToText1729607673464 } from '../common/1729607673464-UpdateProcessedDataValueColumnToText';
|
||||||
|
import { AddWorkflowFilterColumns1730286483664 } from '../common/1730286483664-AddWorkflowFilterColumns';
|
||||||
|
|
||||||
const sqliteMigrations: Migration[] = [
|
const sqliteMigrations: Migration[] = [
|
||||||
InitialMigration1588102412422,
|
InitialMigration1588102412422,
|
||||||
|
@ -132,6 +133,7 @@ const sqliteMigrations: Migration[] = [
|
||||||
CreateProcessedDataTable1726606152711,
|
CreateProcessedDataTable1726606152711,
|
||||||
AddMissingPrimaryKeyOnAnnotationTagMapping1728659839644,
|
AddMissingPrimaryKeyOnAnnotationTagMapping1728659839644,
|
||||||
UpdateProcessedDataValueColumnToText1729607673464,
|
UpdateProcessedDataValueColumnToText1729607673464,
|
||||||
|
AddWorkflowFilterColumns1730286483664,
|
||||||
];
|
];
|
||||||
|
|
||||||
export { sqliteMigrations };
|
export { sqliteMigrations };
|
||||||
|
|
|
@ -13,11 +13,10 @@ import {
|
||||||
} from '@n8n/typeorm';
|
} from '@n8n/typeorm';
|
||||||
import { Service } from 'typedi';
|
import { Service } from 'typedi';
|
||||||
|
|
||||||
import * as a from 'assert/strict';
|
|
||||||
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import type { ListQuery } from '@/requests';
|
import type { ListQuery } from '@/requests';
|
||||||
import { isStringArray } from '@/utils';
|
import { isStringArray } from '@/utils';
|
||||||
|
import { toBase64 } from '@/workflows/utils';
|
||||||
|
|
||||||
import { WebhookEntity } from '../entities/webhook-entity';
|
import { WebhookEntity } from '../entities/webhook-entity';
|
||||||
import { WorkflowEntity } from '../entities/workflow-entity';
|
import { WorkflowEntity } from '../entities/workflow-entity';
|
||||||
|
@ -155,11 +154,11 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (credentialIds.length) {
|
if (credentialIds.length) {
|
||||||
where.nodes = Or(...credentialIds.map((id) => Like(`%{"id":"${id}"%`)));
|
where.credentialIds = Or(...credentialIds.map((id) => Like(`%${toBase64(id)}%`)));
|
||||||
}
|
}
|
||||||
|
|
||||||
const findManyOptions: FindManyOptions<WorkflowEntity> = {
|
const findManyOptions: FindManyOptions<WorkflowEntity> = {
|
||||||
select: { ...select, id: true, nodes: true },
|
select: { ...select, id: true },
|
||||||
where,
|
where,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -176,43 +175,11 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
|
||||||
findManyOptions.take = options.take;
|
findManyOptions.take = options.take;
|
||||||
}
|
}
|
||||||
|
|
||||||
let [workflows, count] = (await this.findAndCount(findManyOptions)) as [
|
const [workflows, count] = (await this.findAndCount(findManyOptions)) as [
|
||||||
ListQuery.Workflow.Plain[] | ListQuery.Workflow.WithSharing[],
|
ListQuery.Workflow.Plain[] | ListQuery.Workflow.WithSharing[],
|
||||||
number,
|
number,
|
||||||
];
|
];
|
||||||
|
|
||||||
function workflowUsesCredential(
|
|
||||||
workflow: ListQuery.Workflow.Plain,
|
|
||||||
credentialIds: string[],
|
|
||||||
): boolean {
|
|
||||||
a.ok(workflow.nodes);
|
|
||||||
|
|
||||||
return (
|
|
||||||
workflow.nodes.findIndex((node) => {
|
|
||||||
if (node.credentials) {
|
|
||||||
return (
|
|
||||||
Object.values(node.credentials).findIndex((credential) => {
|
|
||||||
a.ok(credential.id);
|
|
||||||
return credentialIds.includes(credential.id);
|
|
||||||
}) !== -1
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}) !== -1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (credentialIds.length) {
|
|
||||||
workflows = workflows.filter((wf) => workflowUsesCredential(wf, credentialIds));
|
|
||||||
|
|
||||||
count = workflows.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const wf of workflows) {
|
|
||||||
delete wf.nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { workflows, count };
|
return { workflows, count };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,14 +81,7 @@ export namespace ListQuery {
|
||||||
* Slim workflow returned from a list query operation.
|
* Slim workflow returned from a list query operation.
|
||||||
*/
|
*/
|
||||||
export namespace Workflow {
|
export namespace Workflow {
|
||||||
type OptionalBaseFields =
|
type OptionalBaseFields = 'name' | 'active' | 'versionId' | 'createdAt' | 'updatedAt' | 'tags';
|
||||||
| 'name'
|
|
||||||
| 'active'
|
|
||||||
| 'versionId'
|
|
||||||
| 'createdAt'
|
|
||||||
| 'updatedAt'
|
|
||||||
| 'tags'
|
|
||||||
| 'nodes';
|
|
||||||
|
|
||||||
type BaseFields = Pick<WorkflowEntity, 'id'> &
|
type BaseFields = Pick<WorkflowEntity, 'id'> &
|
||||||
Partial<Pick<WorkflowEntity, OptionalBaseFields>>;
|
Partial<Pick<WorkflowEntity, OptionalBaseFields>>;
|
||||||
|
|
23
packages/cli/src/workflows/utils.ts
Normal file
23
packages/cli/src/workflows/utils.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
|
||||||
|
|
||||||
|
export function toBase64(id: string): string {
|
||||||
|
return Buffer.from(id).toString('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fromBase64(encoded: string): string {
|
||||||
|
return Buffer.from(encoded, 'base64').toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEncodedCredentialIds(workflow: WorkflowEntity): string[] {
|
||||||
|
const credentialIds: string[] = [];
|
||||||
|
|
||||||
|
for (const nodes of workflow.nodes) {
|
||||||
|
for (const credential of Object.values(nodes.credentials ?? {})) {
|
||||||
|
if (credential.id) {
|
||||||
|
credentialIds.push(toBase64(credential.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return credentialIds;
|
||||||
|
}
|
|
@ -33,6 +33,7 @@ import { RoleService } from '@/services/role.service';
|
||||||
import { TagService } from '@/services/tag.service';
|
import { TagService } from '@/services/tag.service';
|
||||||
import * as WorkflowHelpers from '@/workflow-helpers';
|
import * as WorkflowHelpers from '@/workflow-helpers';
|
||||||
|
|
||||||
|
import { getEncodedCredentialIds } from './utils';
|
||||||
import { WorkflowHistoryService } from './workflow-history/workflow-history.service.ee';
|
import { WorkflowHistoryService } from './workflow-history/workflow-history.service.ee';
|
||||||
import { WorkflowSharingService } from './workflow-sharing.service';
|
import { WorkflowSharingService } from './workflow-sharing.service';
|
||||||
|
|
||||||
|
@ -84,9 +85,10 @@ export class WorkflowService {
|
||||||
}
|
}
|
||||||
|
|
||||||
workflows.forEach((w) => {
|
workflows.forEach((w) => {
|
||||||
// This is to emulate the old behavior of removing the shared field as
|
// @ts-expect-error: This is to emulate the old behavior of removing the
|
||||||
// part of `addOwnedByAndSharedWith`. We need this field in `addScopes`
|
// shared field as part of `addOwnedByAndSharedWith`. We need this field
|
||||||
// though. So to avoid leaking the information we just delete it.
|
// in `addScopes` though. So to avoid leaking the information we just
|
||||||
|
// delete it.
|
||||||
delete w.shared;
|
delete w.shared;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -183,9 +185,8 @@ export class WorkflowService {
|
||||||
await validateEntity(workflowUpdateData);
|
await validateEntity(workflowUpdateData);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.workflowRepository.update(
|
await this.workflowRepository.update(workflowId, {
|
||||||
workflowId,
|
...pick(workflowUpdateData, [
|
||||||
pick(workflowUpdateData, [
|
|
||||||
'name',
|
'name',
|
||||||
'active',
|
'active',
|
||||||
'nodes',
|
'nodes',
|
||||||
|
@ -196,7 +197,8 @@ export class WorkflowService {
|
||||||
'pinData',
|
'pinData',
|
||||||
'versionId',
|
'versionId',
|
||||||
]),
|
]),
|
||||||
);
|
credentialIds: getEncodedCredentialIds(workflow),
|
||||||
|
});
|
||||||
|
|
||||||
if (tagIds && !config.getEnv('workflowTagsDisabled')) {
|
if (tagIds && !config.getEnv('workflowTagsDisabled')) {
|
||||||
await this.workflowTagMappingRepository.overwriteTaggings(workflowId, tagIds);
|
await this.workflowTagMappingRepository.overwriteTaggings(workflowId, tagIds);
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { GlobalConfig } from '@n8n/config';
|
||||||
import { In, type FindOptionsRelations } from '@n8n/typeorm';
|
import { In, type FindOptionsRelations } from '@n8n/typeorm';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { ApplicationError } from 'n8n-workflow';
|
import { ApplicationError, createEnvProvider } from 'n8n-workflow';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ import { WorkflowRequest } from './workflow.request';
|
||||||
import { WorkflowService } from './workflow.service';
|
import { WorkflowService } from './workflow.service';
|
||||||
import { EnterpriseWorkflowService } from './workflow.service.ee';
|
import { EnterpriseWorkflowService } from './workflow.service.ee';
|
||||||
import { CredentialsService } from '../credentials/credentials.service';
|
import { CredentialsService } from '../credentials/credentials.service';
|
||||||
|
import { getEncodedCredentialIds } from './utils';
|
||||||
|
|
||||||
@RestController('/workflows')
|
@RestController('/workflows')
|
||||||
export class WorkflowsController {
|
export class WorkflowsController {
|
||||||
|
@ -115,6 +116,8 @@ export class WorkflowsController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newWorkflow.credentialIds = getEncodedCredentialIds(newWorkflow);
|
||||||
|
|
||||||
let project: Project | null;
|
let project: Project | null;
|
||||||
const savedWorkflow = await Db.transaction(async (transactionManager) => {
|
const savedWorkflow = await Db.transaction(async (transactionManager) => {
|
||||||
const workflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
|
const workflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { WorkflowRepository } from '@/databases/repositories/workflow.repository
|
||||||
import { License } from '@/license';
|
import { License } from '@/license';
|
||||||
import type { ListQuery } from '@/requests';
|
import type { ListQuery } from '@/requests';
|
||||||
import { ProjectService } from '@/services/project.service';
|
import { ProjectService } from '@/services/project.service';
|
||||||
|
import { toBase64 } from '@/workflows/utils';
|
||||||
import { EnterpriseWorkflowService } from '@/workflows/workflow.service.ee';
|
import { EnterpriseWorkflowService } from '@/workflows/workflow.service.ee';
|
||||||
|
|
||||||
import { mockInstance } from '../../shared/mocking';
|
import { mockInstance } from '../../shared/mocking';
|
||||||
|
@ -488,7 +489,10 @@ describe('GET /workflows', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const workflow1 = await createWorkflow({ name: 'First', nodes: [node1] }, owner);
|
const workflow1 = await createWorkflow(
|
||||||
|
{ name: 'First', nodes: [node1], credentialIds: [toBase64(credential1.id)] },
|
||||||
|
owner,
|
||||||
|
);
|
||||||
|
|
||||||
const credential2 = await saveCredential(randomCredentialPayload(), {
|
const credential2 = await saveCredential(randomCredentialPayload(), {
|
||||||
user: owner,
|
user: owner,
|
||||||
|
|
Loading…
Reference in a new issue