refactor(core): Move tag collection into repository (no-changelog) (#6860)

* refactor(core): Move tag collection into repository

* Fix tests

* Address feedback

* Fix missing spot
This commit is contained in:
Iván Ovejero 2023-08-08 14:08:56 +02:00 committed by GitHub
parent 8de28fe4d0
commit 11440bfd3c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 59 additions and 61 deletions

View file

@ -35,7 +35,6 @@ import {
SettingsRepository,
SharedCredentialsRepository,
SharedWorkflowRepository,
TagRepository,
UserRepository,
VariablesRepository,
WorkflowRepository,
@ -177,7 +176,6 @@ export async function init(testConnectionOptions?: ConnectionOptions): Promise<v
collections.InstalledPackages = Container.get(InstalledPackagesRepository);
collections.SharedCredentials = Container.get(SharedCredentialsRepository);
collections.SharedWorkflow = Container.get(SharedWorkflowRepository);
collections.Tag = Container.get(TagRepository);
collections.Variables = Container.get(VariablesRepository);
collections.WorkflowStatistics = Container.get(WorkflowStatisticsRepository);
collections.WorkflowTagMapping = Container.get(WorkflowTagMappingRepository);

View file

@ -54,7 +54,6 @@ import type {
SettingsRepository,
SharedCredentialsRepository,
SharedWorkflowRepository,
TagRepository,
UserRepository,
VariablesRepository,
WorkflowRepository,
@ -100,7 +99,6 @@ export interface IDatabaseCollections extends Record<string, Repository<any>> {
Settings: SettingsRepository;
SharedCredentials: SharedCredentialsRepository;
SharedWorkflow: SharedWorkflowRepository;
Tag: TagRepository;
User: UserRepository;
Variables: VariablesRepository;
Workflow: WorkflowRepository;

View file

@ -8,6 +8,8 @@ import { WorkflowEntity } from '@db/entities/WorkflowEntity';
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
import type { Role } from '@db/entities/Role';
import config from '@/config';
import { TagRepository } from '@/databases/repositories';
import Container from 'typedi';
function insertIf(condition: boolean, elements: string[]): string[] {
return condition ? elements : [];
@ -62,7 +64,7 @@ export async function getWorkflowById(id: string): Promise<WorkflowEntity | null
* Intersection! e.g. workflow needs to have all provided tags.
*/
export async function getWorkflowIdsViaTags(tags: string[]): Promise<string[]> {
const dbTags = await Db.collections.Tag.find({
const dbTags = await Container.get(TagRepository).find({
where: { name: In(tags) },
relations: ['workflows'],
});

View file

@ -482,7 +482,7 @@ export class Server extends AbstractServer {
logger,
jwtService,
}),
new TagsController({ config, repositories, externalHooks }),
Container.get(TagsController),
new TranslationController(config, this.credentialTypes),
new UsersController({
config,

View file

@ -1,6 +1,8 @@
import type { EntityManager } from 'typeorm';
import { TagEntity } from '@db/entities/TagEntity';
import type { TagEntity } from '@db/entities/TagEntity';
import type { ITagToImport, IWorkflowToImport } from '@/Interfaces';
import { TagRepository } from './databases/repositories';
import Container from 'typedi';
// ----------------------------------
// utils
@ -26,8 +28,7 @@ export function sortByRequestOrder(
// ----------------------------------
const createTag = async (transactionManager: EntityManager, name: string): Promise<TagEntity> => {
const tag = new TagEntity();
tag.name = name;
const tag = Container.get(TagRepository).create({ name: name.trim() });
return transactionManager.save<TagEntity>(tag);
};

View file

@ -18,6 +18,7 @@ import { replaceInvalidCredentials } from '@/WorkflowHelpers';
import { BaseCommand, UM_FIX_INSTRUCTION } from '../BaseCommand';
import { generateNanoId } from '@db/utils/generators';
import { RoleService } from '@/services/role.service';
import { TagRepository } from '@/databases/repositories';
function assertHasWorkflowsToImport(workflows: unknown): asserts workflows is IWorkflowToImport[] {
if (!Array.isArray(workflows)) {
@ -92,7 +93,7 @@ export class ImportWorkflowsCommand extends BaseCommand {
const user = flags.userId ? await this.getAssignee(flags.userId) : await this.getOwner();
const credentials = await Db.collections.Credentials.find();
const tags = await Db.collections.Tag.find();
const tags = await Container.get(TagRepository).find();
let totalImported = 0;

View file

@ -1,35 +1,25 @@
import { Request, Response, NextFunction } from 'express';
import type { Config } from '@/config';
import config from '@/config';
import { Authorized, Delete, Get, Middleware, Patch, Post, RestController } from '@/decorators';
import type { IDatabaseCollections, IExternalHooksClass, ITagWithCountDb } from '@/Interfaces';
import { TagEntity } from '@db/entities/TagEntity';
import type { TagRepository } from '@db/repositories';
import { type ITagWithCountDb } from '@/Interfaces';
import type { TagEntity } from '@db/entities/TagEntity';
import { TagRepository } from '@db/repositories';
import { validateEntity } from '@/GenericHelpers';
import { BadRequestError } from '@/ResponseHelper';
import { TagsRequest } from '@/requests';
import { Service } from 'typedi';
import { ExternalHooks } from '@/ExternalHooks';
@Authorized()
@RestController('/tags')
@Service()
export class TagsController {
private config: Config;
private config = config;
private externalHooks: IExternalHooksClass;
private tagsRepository: TagRepository;
constructor({
config,
externalHooks,
repositories,
}: {
config: Config;
externalHooks: IExternalHooksClass;
repositories: Pick<IDatabaseCollections, 'Tag'>;
}) {
this.config = config;
this.externalHooks = externalHooks;
this.tagsRepository = repositories.Tag;
}
constructor(
private tagsRepository: TagRepository,
private externalHooks: ExternalHooks,
) {}
// TODO: move this into a new decorator `@IfEnabled('workflowTagsDisabled')`
@Middleware()
@ -63,8 +53,7 @@ export class TagsController {
// Creates a tag
@Post('/')
async createTag(req: TagsRequest.Create): Promise<TagEntity> {
const newTag = new TagEntity();
newTag.name = req.body.name.trim();
const newTag = this.tagsRepository.create({ name: req.body.name.trim() });
await this.externalHooks.run('tag.beforeCreate', [newTag]);
await validateEntity(newTag);
@ -77,12 +66,7 @@ export class TagsController {
// Updates a tag
@Patch('/:id(\\w+)')
async updateTag(req: TagsRequest.Update): Promise<TagEntity> {
const { name } = req.body;
const { id } = req.params;
const newTag = new TagEntity();
newTag.id = id;
newTag.name = name.trim();
const newTag = this.tagsRepository.create({ id: req.params.id, name: req.body.name.trim() });
await this.externalHooks.run('tag.beforeUpdate', [newTag]);
await validateEntity(newTag);

View file

@ -1,6 +1,5 @@
import Container, { Service } from 'typedi';
import path from 'path';
import * as Db from '@/Db';
import {
getTagsPath,
getTrackingInformationFromPostPushResult,
@ -39,6 +38,7 @@ import type { Variables } from '@db/entities/Variables';
import type { SourceControlWorkflowVersionId } from './types/sourceControlWorkflowVersionId';
import type { ExportableCredential } from './types/exportableCredential';
import { InternalHooks } from '@/InternalHooks';
import { TagRepository } from '@/databases/repositories';
@Service()
export class SourceControlService {
private sshKeyName: string;
@ -52,6 +52,7 @@ export class SourceControlService {
private sourceControlPreferencesService: SourceControlPreferencesService,
private sourceControlExportService: SourceControlExportService,
private sourceControlImportService: SourceControlImportService,
private tagRepository: TagRepository,
) {
const userFolder = UserSettings.getUserN8nFolderPath();
this.sshFolder = path.join(userFolder, SOURCE_CONTROL_SSH_FOLDER);
@ -682,7 +683,7 @@ export class SourceControlService {
options: SourceControlGetStatus,
sourceControlledFiles: SourceControlledFile[],
) {
const lastUpdatedTag = await Db.collections.Tag.find({
const lastUpdatedTag = await this.tagRepository.find({
order: { updatedAt: 'DESC' },
take: 1,
select: ['updatedAt'],

View file

@ -26,6 +26,7 @@ import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
import { In } from 'typeorm';
import type { SourceControlledFile } from './types/sourceControlledFile';
import { VariablesService } from '../variables/variables.service';
import { TagRepository } from '@/databases/repositories';
@Service()
export class SourceControlExportService {
@ -35,7 +36,10 @@ export class SourceControlExportService {
private credentialExportFolder: string;
constructor(private readonly variablesService: VariablesService) {
constructor(
private readonly variablesService: VariablesService,
private readonly tagRepository: TagRepository,
) {
const userFolder = UserSettings.getUserN8nFolderPath();
this.gitFolder = path.join(userFolder, SOURCE_CONTROL_GIT_FOLDER);
this.workflowExportFolder = path.join(this.gitFolder, SOURCE_CONTROL_WORKFLOW_EXPORT_FOLDER);
@ -167,7 +171,7 @@ export class SourceControlExportService {
async exportTagsToWorkFolder(): Promise<ExportResult> {
try {
sourceControlFoldersExistCheck([this.gitFolder]);
const tags = await Db.collections.Tag.find();
const tags = await this.tagRepository.find();
// do not export empty tags
if (tags.length === 0) {
return {

View file

@ -27,6 +27,7 @@ import { getCredentialExportPath, getWorkflowExportPath } from './sourceControlH
import type { SourceControlledFile } from './types/sourceControlledFile';
import { RoleService } from '@/services/role.service';
import { VariablesService } from '../variables/variables.service';
import { TagRepository } from '@/databases/repositories';
@Service()
export class SourceControlImportService {
@ -39,6 +40,7 @@ export class SourceControlImportService {
constructor(
private readonly variablesService: VariablesService,
private readonly activeWorkflowRunner: ActiveWorkflowRunner,
private readonly tagRepository: TagRepository,
) {
const userFolder = UserSettings.getUserN8nFolderPath();
this.gitFolder = path.join(userFolder, SOURCE_CONTROL_GIT_FOLDER);
@ -265,7 +267,7 @@ export class SourceControlImportService {
tags: TagEntity[];
mappings: WorkflowTagMapping[];
}> {
const localTags = await Db.collections.Tag.find({
const localTags = await this.tagRepository.find({
select: ['id', 'name'],
});
const localMappings = await Db.collections.WorkflowTagMapping.find({
@ -481,7 +483,7 @@ export class SourceControlImportService {
await Promise.all(
mappedTags.tags.map(async (tag) => {
const findByName = await Db.collections.Tag.findOne({
const findByName = await this.tagRepository.findOne({
where: { name: tag.name },
select: ['id'],
});
@ -490,7 +492,7 @@ export class SourceControlImportService {
`A tag with the name <strong>${tag.name}</strong> already exists locally.<br />Please either rename the local tag, or the remote one with the id <strong>${tag.id}</strong> in the tags.json file.`,
);
}
await Db.collections.Tag.upsert(
await this.tagRepository.upsert(
{
...tag,
},

View file

@ -20,6 +20,7 @@ import { In } from 'typeorm';
import { Container } from 'typedi';
import { InternalHooks } from '@/InternalHooks';
import { RoleService } from '@/services/role.service';
import { TagRepository } from '@/databases/repositories';
// eslint-disable-next-line @typescript-eslint/naming-convention
export const EEWorkflowController = express.Router();
@ -134,7 +135,7 @@ EEWorkflowController.post(
const { tags: tagIds } = req.body;
if (tagIds?.length && !config.getEnv('workflowTagsDisabled')) {
newWorkflow.tags = await Db.collections.Tag.find({
newWorkflow.tags = await Container.get(TagRepository).find({
select: ['id', 'name'],
where: {
id: In(tagIds),

View file

@ -24,6 +24,7 @@ import { In } from 'typeorm';
import { Container } from 'typedi';
import { InternalHooks } from '@/InternalHooks';
import { RoleService } from '@/services/role.service';
import { TagRepository } from '@/databases/repositories';
export const workflowsController = express.Router();
@ -62,7 +63,7 @@ workflowsController.post(
const { tags: tagIds } = req.body;
if (tagIds?.length && !config.getEnv('workflowTagsDisabled')) {
newWorkflow.tags = await Db.collections.Tag.find({
newWorkflow.tags = await Container.get(TagRepository).find({
select: ['id', 'name'],
where: {
id: In(tagIds),

View file

@ -35,6 +35,8 @@ import type { ExecutionData } from '@db/entities/ExecutionData';
import { generateNanoId } from '@db/utils/generators';
import { RoleService } from '@/services/role.service';
import { VariablesService } from '@/environments/variables/variables.service';
import { TagRepository } from '@/databases/repositories';
import { separate } from '@/utils';
export type TestDBType = 'postgres' | 'mysql';
@ -113,7 +115,13 @@ export async function terminate() {
* Truncate specific DB tables in a test DB.
*/
export async function truncate(collections: CollectionName[]) {
for (const collection of collections) {
const [tag, rest] = separate(collections, (c) => c === 'Tag');
if (tag) {
await Container.get(TagRepository).delete({});
}
for (const collection of rest) {
await Db.collections[collection].delete({});
}
}
@ -384,7 +392,7 @@ export async function createWaitingExecution(workflow: WorkflowEntity) {
export async function createTag(attributes: Partial<TagEntity> = {}) {
const { name } = attributes;
return Db.collections.Tag.save({
return Container.get(TagRepository).save({
id: generateNanoId(),
name: name ?? randomName(),
...attributes,

View file

@ -272,11 +272,7 @@ export const setupTestServer = ({
);
break;
case 'tags':
registerController(
app,
config,
new TagsController({ config, externalHooks, repositories }),
);
registerController(app, config, Container.get(TagsController));
break;
}
}

View file

@ -1,7 +1,8 @@
import * as Db from '@/Db';
import * as utils from './shared/utils/';
import * as testDb from './shared/testDb';
import type { SuperAgentTest } from 'supertest';
import { TagRepository } from '@/databases/repositories';
import Container from 'typedi';
let authOwnerAgent: SuperAgentTest;
const testServer = utils.setupTestServer({ endpointGroups: ['tags'] });
@ -21,18 +22,18 @@ describe('POST /tags', () => {
const resp = await authOwnerAgent.post('/tags').send({ name: 'test' });
expect(resp.statusCode).toBe(200);
const dbTag = await Db.collections.Tag.findBy({ name: 'test' });
const dbTag = await Container.get(TagRepository).findBy({ name: 'test' });
expect(dbTag.length === 1);
});
test('should not create duplicate tag', async () => {
const newTag = Db.collections.Tag.create({ name: 'test' });
await Db.collections.Tag.save(newTag);
const newTag = Container.get(TagRepository).create({ name: 'test' });
await Container.get(TagRepository).save(newTag);
const resp = await authOwnerAgent.post('/tags').send({ name: 'test' });
expect(resp.status).toBe(500);
const dbTag = await Db.collections.Tag.findBy({ name: 'test' });
const dbTag = await Container.get(TagRepository).findBy({ name: 'test' });
expect(dbTag.length).toBe(1);
});
});