Skip to content

Commit

Permalink
refactor(core): Move some typeorm operators to repositories (no-cha…
Browse files Browse the repository at this point in the history
…ngelog) (#8139)

Moving some persistence logic to repositories to reduce circular
dependencies.
  • Loading branch information
ivov authored Dec 22, 2023
1 parent ab74bad commit c6dd935
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 118 deletions.
9 changes: 8 additions & 1 deletion packages/cli/src/databases/repositories/tag.repository.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { Service } from 'typedi';
import { DataSource, Repository } from 'typeorm';
import { DataSource, In, Repository } from 'typeorm';
import { TagEntity } from '../entities/TagEntity';

@Service()
export class TagRepository extends Repository<TagEntity> {
constructor(dataSource: DataSource) {
super(TagEntity, dataSource.manager);
}

async findMany(tagIds: string[]) {
return this.find({
select: ['id', 'name'],
where: { id: In(tagIds) },
});
}
}
113 changes: 112 additions & 1 deletion packages/cli/src/databases/repositories/workflow.repository.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
import { Service } from 'typedi';
import { DataSource, Repository, type UpdateResult, type FindOptionsWhere } from 'typeorm';
import {
DataSource,
Repository,
In,
Like,
type UpdateResult,
type FindOptionsWhere,
type FindOptionsSelect,
type FindManyOptions,
type EntityManager,
type DeleteResult,
Not,
} from 'typeorm';
import type { ListQuery } from '@/requests';
import { isStringArray } from '@/utils';
import config from '@/config';
import { WorkflowEntity } from '../entities/WorkflowEntity';
import { SharedWorkflow } from '../entities/SharedWorkflow';

@Service()
export class WorkflowRepository extends Repository<WorkflowEntity> {
Expand Down Expand Up @@ -45,6 +60,29 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
return totalTriggerCount ?? 0;
}

async getSharings(
transaction: EntityManager,
workflowId: string,
relations = ['shared'],
): Promise<SharedWorkflow[]> {
const workflow = await transaction.findOne(WorkflowEntity, {
where: { id: workflowId },
relations,
});
return workflow?.shared ?? [];
}

async pruneSharings(
transaction: EntityManager,
workflowId: string,
userIds: string[],
): Promise<DeleteResult> {
return transaction.delete(SharedWorkflow, {
workflowId,
userId: Not(In(userIds)),
});
}

async updateWorkflowTriggerCount(id: string, triggerCount: number): Promise<UpdateResult> {
const qb = this.createQueryBuilder('workflow');
return qb
Expand All @@ -61,4 +99,77 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
.where('id = :id', { id })
.execute();
}

async getMany(sharedWorkflowIds: string[], options?: ListQuery.Options) {
if (sharedWorkflowIds.length === 0) return { workflows: [], count: 0 };

const where: FindOptionsWhere<WorkflowEntity> = {
...options?.filter,
id: In(sharedWorkflowIds),
};

const reqTags = options?.filter?.tags;

if (isStringArray(reqTags)) {
where.tags = reqTags.map((tag) => ({ name: tag }));
}

type Select = FindOptionsSelect<WorkflowEntity> & { ownedBy?: true };

const select: Select = options?.select
? { ...options.select } // copy to enable field removal without affecting original
: {
name: true,
active: true,
createdAt: true,
updatedAt: true,
versionId: true,
shared: { userId: true, roleId: true },
};

delete select?.ownedBy; // remove non-entity field, handled after query

const relations: string[] = [];

const areTagsEnabled = !config.getEnv('workflowTagsDisabled');
const isDefaultSelect = options?.select === undefined;
const areTagsRequested = isDefaultSelect || options?.select?.tags === true;
const isOwnedByIncluded = isDefaultSelect || options?.select?.ownedBy === true;

if (areTagsEnabled && areTagsRequested) {
relations.push('tags');
select.tags = { id: true, name: true };
}

if (isOwnedByIncluded) relations.push('shared', 'shared.role', 'shared.user');

if (typeof where.name === 'string' && where.name !== '') {
where.name = Like(`%${where.name}%`);
}

const findManyOptions: FindManyOptions<WorkflowEntity> = {
select: { ...select, id: true },
where,
};

if (isDefaultSelect || options?.select?.updatedAt === true) {
findManyOptions.order = { updatedAt: 'ASC' };
}

if (relations.length > 0) {
findManyOptions.relations = relations;
}

if (options?.take) {
findManyOptions.skip = options.skip;
findManyOptions.take = options.take;
}

const [workflows, count] = (await this.findAndCount(findManyOptions)) as [
ListQuery.Workflow.Plain[] | ListQuery.Workflow.WithSharing[],
number,
];

return { workflows, count };
}
}
30 changes: 3 additions & 27 deletions packages/cli/src/workflows/workflow.service.ee.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { DeleteResult, EntityManager } from 'typeorm';
import { In, Not } from 'typeorm';
import type { EntityManager } from 'typeorm';
import * as WorkflowHelpers from '@/WorkflowHelpers';
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
import type { SharedWorkflow } from '@db/entities/SharedWorkflow';
import type { User } from '@db/entities/User';
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
import { UserService } from '@/services/user.service';
import { WorkflowService } from './workflow.service';
import type {
Expand Down Expand Up @@ -48,29 +47,6 @@ export class EnterpriseWorkflowService {
return { ownsWorkflow: true, workflow };
}

async getSharings(
transaction: EntityManager,
workflowId: string,
relations = ['shared'],
): Promise<SharedWorkflow[]> {
const workflow = await transaction.findOne(WorkflowEntity, {
where: { id: workflowId },
relations,
});
return workflow?.shared ?? [];
}

async pruneSharings(
transaction: EntityManager,
workflowId: string,
userIds: string[],
): Promise<DeleteResult> {
return transaction.delete(SharedWorkflow, {
workflowId,
userId: Not(In(userIds)),
});
}

async share(
transaction: EntityManager,
workflow: WorkflowEntity,
Expand Down
75 changes: 3 additions & 72 deletions packages/cli/src/workflows/workflow.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import Container, { Service } from 'typedi';
import type { INode, IPinData } from 'n8n-workflow';
import { NodeApiError, Workflow } from 'n8n-workflow';
import type { FindManyOptions, FindOptionsSelect, FindOptionsWhere } from 'typeorm';
import { In, Like } from 'typeorm';
import type { FindOptionsWhere } from 'typeorm';
import pick from 'lodash/pick';
import omit from 'lodash/omit';
import { v4 as uuid } from 'uuid';
Expand All @@ -14,7 +13,7 @@ import type { User } from '@db/entities/User';
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
import { validateEntity } from '@/GenericHelpers';
import { ExternalHooks } from '@/ExternalHooks';
import { type WorkflowRequest, type ListQuery, hasSharing } from '@/requests';
import { type WorkflowRequest, hasSharing, type ListQuery } from '@/requests';
import { TagService } from '@/services/tag.service';
import type { IWorkflowDb, IWorkflowExecutionDataProcess } from '@/Interfaces';
import { NodeTypes } from '@/NodeTypes';
Expand All @@ -25,7 +24,6 @@ import { whereClause } from '@/UserManagement/UserManagementHelper';
import { InternalHooks } from '@/InternalHooks';
import { WorkflowRepository } from '@db/repositories/workflow.repository';
import { OwnershipService } from '@/services/ownership.service';
import { isStringArray } from '@/utils';
import { WorkflowHistoryService } from './workflowHistory/workflowHistory.service.ee';
import { BinaryDataService } from 'n8n-core';
import type { Scope } from '@n8n/permissions';
Expand Down Expand Up @@ -121,74 +119,7 @@ export class WorkflowService {
}

async getMany(sharedWorkflowIds: string[], options?: ListQuery.Options) {
if (sharedWorkflowIds.length === 0) return { workflows: [], count: 0 };

const where: FindOptionsWhere<WorkflowEntity> = {
...options?.filter,
id: In(sharedWorkflowIds),
};

const reqTags = options?.filter?.tags;

if (isStringArray(reqTags)) {
where.tags = reqTags.map((tag) => ({ name: tag }));
}

type Select = FindOptionsSelect<WorkflowEntity> & { ownedBy?: true };

const select: Select = options?.select
? { ...options.select } // copy to enable field removal without affecting original
: {
name: true,
active: true,
createdAt: true,
updatedAt: true,
versionId: true,
shared: { userId: true, roleId: true },
};

delete select?.ownedBy; // remove non-entity field, handled after query

const relations: string[] = [];

const areTagsEnabled = !config.getEnv('workflowTagsDisabled');
const isDefaultSelect = options?.select === undefined;
const areTagsRequested = isDefaultSelect || options?.select?.tags === true;
const isOwnedByIncluded = isDefaultSelect || options?.select?.ownedBy === true;

if (areTagsEnabled && areTagsRequested) {
relations.push('tags');
select.tags = { id: true, name: true };
}

if (isOwnedByIncluded) relations.push('shared', 'shared.role', 'shared.user');

if (typeof where.name === 'string' && where.name !== '') {
where.name = Like(`%${where.name}%`);
}

const findManyOptions: FindManyOptions<WorkflowEntity> = {
select: { ...select, id: true },
where,
};

if (isDefaultSelect || options?.select?.updatedAt === true) {
findManyOptions.order = { updatedAt: 'ASC' };
}

if (relations.length > 0) {
findManyOptions.relations = relations;
}

if (options?.take) {
findManyOptions.skip = options.skip;
findManyOptions.take = options.take;
}

const [workflows, count] = (await this.workflowRepository.findAndCount(findManyOptions)) as [
ListQuery.Workflow.Plain[] | ListQuery.Workflow.WithSharing[],
number,
];
const { workflows, count } = await this.workflowRepository.getMany(sharedWorkflowIds, options);

return hasSharing(workflows)
? {
Expand Down
15 changes: 5 additions & 10 deletions packages/cli/src/workflows/workflows.controller.ee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { SharedWorkflow } from '@db/entities/SharedWorkflow';
import { CredentialsService } from '../credentials/credentials.service';
import type { IExecutionPushResponse } from '@/Interfaces';
import * as GenericHelpers from '@/GenericHelpers';
import { In } from 'typeorm';
import { Container } from 'typedi';
import { InternalHooks } from '@/InternalHooks';
import { RoleService } from '@/services/role.service';
Expand All @@ -29,6 +28,7 @@ import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
import { WorkflowService } from './workflow.service';
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
import { TagRepository } from '@/databases/repositories/tag.repository';

export const EEWorkflowController = express.Router();

Expand Down Expand Up @@ -81,7 +81,7 @@ EEWorkflowController.put(
}

const ownerIds = (
await Container.get(EnterpriseWorkflowService).getSharings(
await Container.get(WorkflowRepository).getSharings(
Db.getConnection().createEntityManager(),
workflowId,
['shared', 'shared.role'],
Expand All @@ -93,12 +93,12 @@ EEWorkflowController.put(
let newShareeIds: string[] = [];
await Db.transaction(async (trx) => {
// remove all sharings that are not supposed to exist anymore
await Container.get(EnterpriseWorkflowService).pruneSharings(trx, workflowId, [
await Container.get(WorkflowRepository).pruneSharings(trx, workflowId, [
...ownerIds,
...shareWithIds,
]);

const sharings = await Container.get(EnterpriseWorkflowService).getSharings(trx, workflowId);
const sharings = await Container.get(WorkflowRepository).getSharings(trx, workflowId);

// extract the new sharings that need to be added
newShareeIds = rightDiff(
Expand Down Expand Up @@ -169,12 +169,7 @@ EEWorkflowController.post(
const { tags: tagIds } = req.body;

if (tagIds?.length && !config.getEnv('workflowTagsDisabled')) {
newWorkflow.tags = await Container.get(TagService).findMany({
select: ['id', 'name'],
where: {
id: In(tagIds),
},
});
newWorkflow.tags = await Container.get(TagRepository).findMany(tagIds);
}

await WorkflowHelpers.replaceInvalidCredentials(newWorkflow);
Expand Down
9 changes: 2 additions & 7 deletions packages/cli/src/workflows/workflows.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { isBelowOnboardingThreshold } from '@/WorkflowHelpers';
import { EEWorkflowController } from './workflows.controller.ee';
import { WorkflowService } from './workflow.service';
import { whereClause } from '@/UserManagement/UserManagementHelper';
import { In } from 'typeorm';
import { Container } from 'typedi';
import { InternalHooks } from '@/InternalHooks';
import { RoleService } from '@/services/role.service';
Expand All @@ -31,6 +30,7 @@ import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
import { NamingService } from '@/services/naming.service';
import { TagRepository } from '@/databases/repositories/tag.repository';

export const workflowsController = express.Router();
workflowsController.use('/', EEWorkflowController);
Expand All @@ -56,12 +56,7 @@ workflowsController.post(
const { tags: tagIds } = req.body;

if (tagIds?.length && !config.getEnv('workflowTagsDisabled')) {
newWorkflow.tags = await Container.get(TagService).findMany({
select: ['id', 'name'],
where: {
id: In(tagIds),
},
});
newWorkflow.tags = await Container.get(TagRepository).findMany(tagIds);
}

await WorkflowHelpers.replaceInvalidCredentials(newWorkflow);
Expand Down

0 comments on commit c6dd935

Please sign in to comment.