diff --git a/packages/cli/src/databases/migrations/sqlite/1690000000020-FixMissingIndicesFromStringIdMigration.ts b/packages/cli/src/databases/migrations/sqlite/1690000000020-FixMissingIndicesFromStringIdMigration.ts new file mode 100644 index 0000000000..f950ff70da --- /dev/null +++ b/packages/cli/src/databases/migrations/sqlite/1690000000020-FixMissingIndicesFromStringIdMigration.ts @@ -0,0 +1,40 @@ +import type { IrreversibleMigration, MigrationContext } from '@db/types'; + +export class FixMissingIndicesFromStringIdMigration1690000000020 implements IrreversibleMigration { + async up({ queryRunner, tablePrefix }: MigrationContext): Promise { + const toMerge = (await queryRunner.query( + `SELECT id, name, COUNT(*) c FROM ${tablePrefix}tag_entity GROUP BY name HAVING c > 1`, + )) as Array<{ id: string; name: string }>; + + for (const m of toMerge) { + const tags = (await queryRunner.query( + `SELECT id FROM ${tablePrefix}tag_entity WHERE name = ?`, + [m.name], + )) as Array<{ id: string }>; + for (const t of tags) { + if (t.id === m.id) { + continue; + } + await queryRunner.query( + `UPDATE ${tablePrefix}workflows_tags SET tagId = ? WHERE tagId = ?`, + [m.id, t.id], + ); + await queryRunner.query(`DELETE FROM ${tablePrefix}tag_entity WHERE id = ?`, [t.id]); + } + } + + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_${tablePrefix}8f949d7a3a984759044054e89b" ON "${tablePrefix}tag_entity" ("name") `, + ); + + await queryRunner.query( + `CREATE INDEX 'IDX_${tablePrefix}b94b45ce2c73ce46c54f20b5f9' ON '${tablePrefix}execution_entity' ('waitTill', 'id');`, + ); + await queryRunner.query( + `CREATE INDEX 'IDX_${tablePrefix}81fc04c8a17de15835713505e4' ON '${tablePrefix}execution_entity' ('workflowId', 'id');`, + ); + await queryRunner.query( + `CREATE INDEX 'IDX_${tablePrefix}8b6f3f9ae234f137d707b98f3bf43584' ON '${tablePrefix}execution_entity' ('status', 'workflowId');`, + ); + } +} diff --git a/packages/cli/src/databases/migrations/sqlite/index.ts b/packages/cli/src/databases/migrations/sqlite/index.ts index b0a98fa10b..f23ce87665 100644 --- a/packages/cli/src/databases/migrations/sqlite/index.ts +++ b/packages/cli/src/databases/migrations/sqlite/index.ts @@ -38,6 +38,7 @@ import { AddUserActivatedProperty1681134145996 } from './1681134145996-AddUserAc import { MigrateIntegerKeysToString1690000000002 } from './1690000000002-MigrateIntegerKeysToString'; import { SeparateExecutionData1690000000010 } from './1690000000010-SeparateExecutionData'; import { RemoveSkipOwnerSetup1681134145997 } from './1681134145997-RemoveSkipOwnerSetup'; +import { FixMissingIndicesFromStringIdMigration1690000000020 } from './1690000000020-FixMissingIndicesFromStringIdMigration'; const sqliteMigrations: Migration[] = [ InitialMigration1588102412422, @@ -79,6 +80,7 @@ const sqliteMigrations: Migration[] = [ MigrateIntegerKeysToString1690000000002, SeparateExecutionData1690000000010, RemoveSkipOwnerSetup1681134145997, + FixMissingIndicesFromStringIdMigration1690000000020, ]; export { sqliteMigrations }; diff --git a/packages/cli/test/integration/shared/types.ts b/packages/cli/test/integration/shared/types.ts index 8e25c62a5b..b507749e23 100644 --- a/packages/cli/test/integration/shared/types.ts +++ b/packages/cli/test/integration/shared/types.ts @@ -24,7 +24,8 @@ export type EndpointGroup = | 'sourceControl' | 'eventBus' | 'license' - | 'variables'; + | 'variables' + | 'tags'; export interface SetupProps { applyAuth?: boolean; diff --git a/packages/cli/test/integration/shared/utils/testServer.ts b/packages/cli/test/integration/shared/utils/testServer.ts index 8f77ee75df..ac7f8076b6 100644 --- a/packages/cli/test/integration/shared/utils/testServer.ts +++ b/packages/cli/test/integration/shared/utils/testServer.ts @@ -28,6 +28,7 @@ import { NodesController, OwnerController, PasswordResetController, + TagsController, UsersController, } from '@/controllers'; import { setupAuthMiddlewares } from '@/middlewares'; @@ -261,6 +262,14 @@ export const setupTestServer = ({ logger, }), ); + break; + case 'tags': + registerController( + app, + config, + new TagsController({ config, externalHooks, repositories }), + ); + break; } } } diff --git a/packages/cli/test/integration/tags.api.test.ts b/packages/cli/test/integration/tags.api.test.ts new file mode 100644 index 0000000000..a8aa61a8ba --- /dev/null +++ b/packages/cli/test/integration/tags.api.test.ts @@ -0,0 +1,38 @@ +import * as Db from '@/Db'; +import * as utils from './shared/utils/'; +import * as testDb from './shared/testDb'; +import type { SuperAgentTest } from 'supertest'; + +let authOwnerAgent: SuperAgentTest; +const testServer = utils.setupTestServer({ endpointGroups: ['tags'] }); + +beforeAll(async () => { + const globalOwnerRole = await testDb.getGlobalOwnerRole(); + const ownerShell = await testDb.createUserShell(globalOwnerRole); + authOwnerAgent = testServer.authAgentFor(ownerShell); +}); + +beforeEach(async () => { + await testDb.truncate(['Tag']); +}); + +describe('POST /tags', () => { + test('should create tag', async () => { + const resp = await authOwnerAgent.post('/tags').send({ name: 'test' }); + expect(resp.statusCode).toBe(200); + + const dbTag = await Db.collections.Tag.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 resp = await authOwnerAgent.post('/tags').send({ name: 'test' }); + expect(resp.status).toBe(500); + + const dbTag = await Db.collections.Tag.findBy({ name: 'test' }); + expect(dbTag.length).toBe(1); + }); +});