From 785a7bfe8eb4ac31e7b86eb5c473f6879fa63af9 Mon Sep 17 00:00:00 2001 From: lborv Date: Sat, 27 Sep 2025 23:41:32 +0300 Subject: [PATCH] feat: enhance database management with new migration and database node functionalities, including CRUD operations and test cases --- .../database/database.manager.controller.ts | 31 +++--- .../database/database.manager.module.ts | 2 +- .../database/database.manager.service.ts | 21 ++-- .../databaseNode/database.node.service.ts | 13 ++- .../entities/database.entity.ts | 4 +- .../entities/database.node.entity.ts | 9 +- .../migration/migration.module.ts | 9 +- .../migration/migration.service.ts | 4 +- src/migrations/1758534887223-init.ts | 43 -------- src/migrations/1758550333691-defaults.ts | 18 ---- .../1758551707113-query-modules-join.ts | 20 ---- src/migrations/1758912793124-Migrations.ts | 59 ----------- src/migrations/1759002866941-init.ts | 98 +++++++++++++++++++ src/migrations/1759005139426-fix.ts | 59 +++++++++++ src/project/entities/project.entity.ts | 4 +- tests/base/createDbNode.ts | 16 +++ tests/base/createMigration.ts | 43 +++++--- tests/functions/createDatabase.ts | 15 +++ tests/functions/createDatabaseNode.ts | 22 +++++ tests/functions/createMigration.ts | 18 +++- tests/functions/databaseMigrationUp.ts | 14 +++ 21 files changed, 333 insertions(+), 189 deletions(-) delete mode 100644 src/migrations/1758534887223-init.ts delete mode 100644 src/migrations/1758550333691-defaults.ts delete mode 100644 src/migrations/1758551707113-query-modules-join.ts delete mode 100644 src/migrations/1758912793124-Migrations.ts create mode 100644 src/migrations/1759002866941-init.ts create mode 100644 src/migrations/1759005139426-fix.ts create mode 100644 tests/base/createDbNode.ts create mode 100644 tests/functions/createDatabase.ts create mode 100644 tests/functions/createDatabaseNode.ts create mode 100644 tests/functions/databaseMigrationUp.ts diff --git a/src/databaseManager/database/database.manager.controller.ts b/src/databaseManager/database/database.manager.controller.ts index 94a61b2..b59e564 100644 --- a/src/databaseManager/database/database.manager.controller.ts +++ b/src/databaseManager/database/database.manager.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Inject, Post } from "@nestjs/common"; +import { Controller, Get, Post, Body, Param } from "@nestjs/common"; import { DatabaseManagerService } from "./database.manager.service"; import { DatabaseNodeService } from "../databaseNode/database.node.service"; import { MigrationService } from "../migration/migration.service"; @@ -6,27 +6,22 @@ import { MigrationService } from "../migration/migration.service"; @Controller("database") export class DatabaseManagerController { constructor( - @Inject("DatabaseService") private readonly databaseManagerService: DatabaseManagerService, - @Inject("DatabaseNodeService") private readonly databaseNodeService: DatabaseNodeService, - @Inject("MigrationService") private readonly migrationService: MigrationService ) {} @Post("create") - createDatabase( - @Inject("body") body: { projectId: string; databaseNodeId: string } - ) { + createDatabase(@Body() body: { projectId: string; databaseNodeId: string }) { return this.databaseManagerService.createDatabase( - body.projectId, - body.databaseNodeId + body.databaseNodeId, + body.projectId ); } @Post("node/create") addDatabaseNode( - @Inject("body") + @Body() body: { host: string; port: number; @@ -43,18 +38,18 @@ export class DatabaseManagerController { } @Get("migration/up/:databaseId") - migrateUp(@Inject("params") params: { databaseId: string }) { - return this.migrationService.up(params.databaseId); + migrateUp(@Param("databaseId") databaseId: string) { + return this.migrationService.up(databaseId); } @Get("migration/down/:databaseId") - migrateDown(@Inject("params") params: { databaseId: string }) { - return this.migrationService.down(params.databaseId); + migrateDown(@Param("databaseId") databaseId: string) { + return this.migrationService.down(databaseId); } @Post("migration/create") createMigration( - @Inject("body") + @Body() body: { up: string; down: string; @@ -66,9 +61,9 @@ export class DatabaseManagerController { @Post("query/:databaseId") runQuery( - @Inject("params") params: { databaseId: string }, - @Inject("body") body: { query: string } + @Param("databaseId") databaseId: string, + @Body() body: { query: string } ) { - return this.databaseManagerService.runQuery(params.databaseId, body.query); + return this.databaseManagerService.runQuery(databaseId, body.query); } } diff --git a/src/databaseManager/database/database.manager.module.ts b/src/databaseManager/database/database.manager.module.ts index 5414f6a..aae49b9 100644 --- a/src/databaseManager/database/database.manager.module.ts +++ b/src/databaseManager/database/database.manager.module.ts @@ -12,7 +12,7 @@ import { DatabaseNodeService } from "../databaseNode/database.node.service"; @Module({ imports: [ forwardRef(() => ProjectModule), - MigrationModule, + forwardRef(() => MigrationModule), TypeOrmModule.forFeature([Database, DatabaseNode, Project]), ], controllers: [DatabaseManagerController], diff --git a/src/databaseManager/database/database.manager.service.ts b/src/databaseManager/database/database.manager.service.ts index c4d386e..61f3d99 100644 --- a/src/databaseManager/database/database.manager.service.ts +++ b/src/databaseManager/database/database.manager.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from "@nestjs/common"; +import { Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { Database } from "../entities/database.entity"; import { Repository } from "typeorm"; @@ -12,9 +12,7 @@ export class DatabaseManagerService extends DatabaseEncryptionService { constructor( @InjectRepository(Database) private databaseRepository: Repository, - @Inject(ProjectService) private readonly projectService: ProjectService, - @Inject(DatabaseNodeService) private readonly databaseNodeService: DatabaseNodeService ) { super(); @@ -24,8 +22,19 @@ export class DatabaseManagerService extends DatabaseEncryptionService { return this.databaseRepository.findOne({ where: { id } }); } - async runQuery(databaseId: string, query: string) { - const database = await this.findById(databaseId); + async findByIdWithRelations(id: string): Promise { + return this.databaseRepository.findOne({ + where: { id }, + relations: ["node", "project"], + }); + } + + async runQuery( + databaseId: string, + query: string, + queryUser: boolean = false + ): Promise { + const database = await this.findByIdWithRelations(databaseId); if (!database) { throw new Error("Database not found"); @@ -34,7 +43,7 @@ export class DatabaseManagerService extends DatabaseEncryptionService { const dbConnection = await mysql.createConnection({ host: database.node.host, port: database.node.port, - user: database.c_username, + user: queryUser ? database.q_username : database.c_username, password: this.decryptPassword(database.password), database: database.database, enableKeepAlive: true, diff --git a/src/databaseManager/databaseNode/database.node.service.ts b/src/databaseManager/databaseNode/database.node.service.ts index 774617e..087bb05 100644 --- a/src/databaseManager/databaseNode/database.node.service.ts +++ b/src/databaseManager/databaseNode/database.node.service.ts @@ -61,6 +61,16 @@ export class DatabaseNodeService extends DatabaseEncryptionService { username: string, password: string ): Promise { + const existingNode = await this.databaseNodeRepository.findOne({ + where: { host, port }, + }); + + if (existingNode) { + existingNode.password = this.encryptPassword(password); + existingNode.username = username; + return this.databaseNodeRepository.save(existingNode); + } + const encryptedPassword = this.encryptPassword(password); const databaseNode = this.databaseNodeRepository.create({ host, @@ -68,6 +78,7 @@ export class DatabaseNodeService extends DatabaseEncryptionService { username, password: encryptedPassword, }); + return this.databaseNodeRepository.save(databaseNode); } @@ -81,7 +92,7 @@ export class DatabaseNodeService extends DatabaseEncryptionService { return { host: node.host, port: node.port, - username: node.username, + user: node.username, password: this.decryptPassword(node.password), }; } diff --git a/src/databaseManager/entities/database.entity.ts b/src/databaseManager/entities/database.entity.ts index d0ba5d8..0407e65 100644 --- a/src/databaseManager/entities/database.entity.ts +++ b/src/databaseManager/entities/database.entity.ts @@ -1,13 +1,14 @@ import { Column, Entity, + JoinColumn, ManyToOne, OneToMany, OneToOne, PrimaryGeneratedColumn, } from "typeorm"; import { Migration } from "./migration.entity"; -import { Project } from "src/project/entities/project.entity"; +import { Project } from "../../project/entities/project.entity"; import { DatabaseNode } from "./database.node.entity"; @Entity("database") @@ -31,6 +32,7 @@ export class Database { migrations: Migration[]; @OneToOne(() => Project, (project) => project.database) + @JoinColumn() project: Project; @ManyToOne(() => DatabaseNode, (node) => node.databases) diff --git a/src/databaseManager/entities/database.node.entity.ts b/src/databaseManager/entities/database.node.entity.ts index 68f7c30..c1ffaba 100644 --- a/src/databaseManager/entities/database.node.entity.ts +++ b/src/databaseManager/entities/database.node.entity.ts @@ -1,7 +1,14 @@ -import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; +import { + Column, + Entity, + Index, + OneToMany, + PrimaryGeneratedColumn, +} from "typeorm"; import { Database } from "./database.entity"; @Entity("databaseNode") +@Index("IDX_DATABASE_NODE_HOST_PORT", ["host", "port"], { unique: true }) export class DatabaseNode { @PrimaryGeneratedColumn("uuid") id: string; diff --git a/src/databaseManager/migration/migration.module.ts b/src/databaseManager/migration/migration.module.ts index 010063d..9278691 100644 --- a/src/databaseManager/migration/migration.module.ts +++ b/src/databaseManager/migration/migration.module.ts @@ -1,9 +1,14 @@ +import { DatabaseManagerModule } from "src/databaseManager/database/database.manager.module"; import { forwardRef, Module } from "@nestjs/common"; import { MigrationService } from "./migration.service"; -import { DatabaseModule } from "src/database/database.module"; +import { TypeOrmModule } from "@nestjs/typeorm"; +import { Migration } from "../entities/migration.entity"; @Module({ - imports: [forwardRef(() => DatabaseModule)], + imports: [ + forwardRef(() => DatabaseManagerModule), + TypeOrmModule.forFeature([Migration]), + ], controllers: [], providers: [MigrationService], exports: [MigrationService], diff --git a/src/databaseManager/migration/migration.service.ts b/src/databaseManager/migration/migration.service.ts index 55f00c0..2494efd 100644 --- a/src/databaseManager/migration/migration.service.ts +++ b/src/databaseManager/migration/migration.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from "@nestjs/common"; +import { Injectable, forwardRef, Inject } from "@nestjs/common"; import { IsNull, Not, Repository } from "typeorm"; import { Migration } from "../entities/migration.entity"; import { InjectRepository } from "@nestjs/typeorm"; @@ -9,7 +9,7 @@ export class MigrationService { constructor( @InjectRepository(Migration) private readonly migrationRepository: Repository, - @Inject(DatabaseManagerService) + @Inject(forwardRef(() => DatabaseManagerService)) private readonly databaseService: DatabaseManagerService ) {} diff --git a/src/migrations/1758534887223-init.ts b/src/migrations/1758534887223-init.ts deleted file mode 100644 index d49b872..0000000 --- a/src/migrations/1758534887223-init.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class Init1758534887223 implements MigrationInterface { - name = "Init1758534887223"; - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `CREATE TABLE \`token\` (\`token\` uuid NOT NULL, \`isActive\` tinyint NOT NULL DEFAULT '0', \`projectToken\` uuid NULL, PRIMARY KEY (\`token\`)) ENGINE=InnoDB` - ); - await queryRunner.query( - `CREATE TABLE \`project\` (\`token\` uuid NOT NULL, \`name\` varchar(255) NOT NULL, PRIMARY KEY (\`token\`)) ENGINE=InnoDB` - ); - await queryRunner.query( - `CREATE TABLE \`module\` (\`id\` uuid NOT NULL, \`sourcePath\` varchar(255) NOT NULL, \`name\` varchar(255) NOT NULL, \`isInjectable\` tinyint NOT NULL DEFAULT '1', PRIMARY KEY (\`id\`)) ENGINE=InnoDB` - ); - await queryRunner.query( - `CREATE TABLE \`query\` (\`id\` uuid NOT NULL, \`source\` longtext NOT NULL, \`isActive\` tinyint NOT NULL DEFAULT '1', \`projectToken\` uuid NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB` - ); - await queryRunner.query( - `CREATE TABLE \`plugin\` (\`id\` uuid NOT NULL, \`class\` varchar(255) NOT NULL, \`name\` varchar(255) NOT NULL, \`config\` varchar(255) NOT NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB` - ); - await queryRunner.query( - `ALTER TABLE \`token\` ADD CONSTRAINT \`FK_f0bc174c878df5e005c38fe05bd\` FOREIGN KEY (\`projectToken\`) REFERENCES \`project\`(\`token\`) ON DELETE NO ACTION ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE \`query\` ADD CONSTRAINT \`FK_f58429a7d32fbb51ead8c4daf0a\` FOREIGN KEY (\`projectToken\`) REFERENCES \`project\`(\`token\`) ON DELETE NO ACTION ON UPDATE NO ACTION` - ); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `ALTER TABLE \`query\` DROP FOREIGN KEY \`FK_f58429a7d32fbb51ead8c4daf0a\`` - ); - await queryRunner.query( - `ALTER TABLE \`token\` DROP FOREIGN KEY \`FK_f0bc174c878df5e005c38fe05bd\`` - ); - await queryRunner.query(`DROP TABLE \`plugin\``); - await queryRunner.query(`DROP TABLE \`query\``); - await queryRunner.query(`DROP TABLE \`module\``); - await queryRunner.query(`DROP TABLE \`project\``); - await queryRunner.query(`DROP TABLE \`token\``); - } -} diff --git a/src/migrations/1758550333691-defaults.ts b/src/migrations/1758550333691-defaults.ts deleted file mode 100644 index 4df87b0..0000000 --- a/src/migrations/1758550333691-defaults.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class Defaults1758550333691 implements MigrationInterface { - name = 'Defaults1758550333691' - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE \`module\` DROP COLUMN \`isInjectable\``); - await queryRunner.query(`ALTER TABLE \`plugin\` ADD \`queryId\` uuid NULL`); - await queryRunner.query(`ALTER TABLE \`plugin\` ADD CONSTRAINT \`FK_5162d18c3653d35ff4d104dd940\` FOREIGN KEY (\`queryId\`) REFERENCES \`query\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE \`plugin\` DROP FOREIGN KEY \`FK_5162d18c3653d35ff4d104dd940\``); - await queryRunner.query(`ALTER TABLE \`plugin\` DROP COLUMN \`queryId\``); - await queryRunner.query(`ALTER TABLE \`module\` ADD \`isInjectable\` tinyint NOT NULL DEFAULT 1`); - } - -} diff --git a/src/migrations/1758551707113-query-modules-join.ts b/src/migrations/1758551707113-query-modules-join.ts deleted file mode 100644 index 60ddd8c..0000000 --- a/src/migrations/1758551707113-query-modules-join.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class QueryModulesJoin1758551707113 implements MigrationInterface { - name = 'QueryModulesJoin1758551707113' - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`CREATE TABLE \`query_modules_module\` (\`queryId\` uuid NOT NULL, \`moduleId\` uuid NOT NULL, INDEX \`IDX_12121324c524e12538de4948ce\` (\`queryId\`), INDEX \`IDX_7fb42dbc85874aafbd4bfb1c10\` (\`moduleId\`), PRIMARY KEY (\`queryId\`, \`moduleId\`)) ENGINE=InnoDB`); - await queryRunner.query(`ALTER TABLE \`query_modules_module\` ADD CONSTRAINT \`FK_12121324c524e12538de4948cee\` FOREIGN KEY (\`queryId\`) REFERENCES \`query\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE \`query_modules_module\` ADD CONSTRAINT \`FK_7fb42dbc85874aafbd4bfb1c101\` FOREIGN KEY (\`moduleId\`) REFERENCES \`module\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE \`query_modules_module\` DROP FOREIGN KEY \`FK_7fb42dbc85874aafbd4bfb1c101\``); - await queryRunner.query(`ALTER TABLE \`query_modules_module\` DROP FOREIGN KEY \`FK_12121324c524e12538de4948cee\``); - await queryRunner.query(`DROP INDEX \`IDX_7fb42dbc85874aafbd4bfb1c10\` ON \`query_modules_module\``); - await queryRunner.query(`DROP INDEX \`IDX_12121324c524e12538de4948ce\` ON \`query_modules_module\``); - await queryRunner.query(`DROP TABLE \`query_modules_module\``); - } - -} diff --git a/src/migrations/1758912793124-Migrations.ts b/src/migrations/1758912793124-Migrations.ts deleted file mode 100644 index f96fefb..0000000 --- a/src/migrations/1758912793124-Migrations.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class Migrations1758912793124 implements MigrationInterface { - name = "Migrations1758912793124"; - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `CREATE TABLE \`migration\` (\`id\` varchar(36) NOT NULL, \`name\` varchar(255) NOT NULL, \`appliedAt\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP(), \`isApplied\` tinyint NOT NULL DEFAULT '1', \`isValid\` tinyint NOT NULL DEFAULT '1', \`sql\` varchar(255) NOT NULL, \`data\` json NULL, \`projectToken\` varchar(36) NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB` - ); - await queryRunner.query( - `CREATE TABLE \`query_modules_module\` (\`queryId\` varchar(36) NOT NULL, \`moduleId\` varchar(36) NOT NULL, INDEX \`IDX_12121324c524e12538de4948ce\` (\`queryId\`), INDEX \`IDX_7fb42dbc85874aafbd4bfb1c10\` (\`moduleId\`), PRIMARY KEY (\`queryId\`, \`moduleId\`)) ENGINE=InnoDB` - ); - await queryRunner.query( - `ALTER TABLE \`module\` DROP COLUMN \`isInjectable\`` - ); - await queryRunner.query( - `ALTER TABLE \`plugin\` ADD \`queryId\` varchar(36) NULL` - ); - await queryRunner.query( - `ALTER TABLE \`migration\` ADD CONSTRAINT \`FK_d3d093f32ce7c968b02fc6bce65\` FOREIGN KEY (\`projectToken\`) REFERENCES \`project\`(\`token\`) ON DELETE NO ACTION ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE \`plugin\` ADD CONSTRAINT \`FK_5162d18c3653d35ff4d104dd940\` FOREIGN KEY (\`queryId\`) REFERENCES \`query\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE \`query_modules_module\` ADD CONSTRAINT \`FK_12121324c524e12538de4948cee\` FOREIGN KEY (\`queryId\`) REFERENCES \`query\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE \`query_modules_module\` ADD CONSTRAINT \`FK_7fb42dbc85874aafbd4bfb1c101\` FOREIGN KEY (\`moduleId\`) REFERENCES \`module\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION` - ); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `ALTER TABLE \`query_modules_module\` DROP FOREIGN KEY \`FK_7fb42dbc85874aafbd4bfb1c101\`` - ); - await queryRunner.query( - `ALTER TABLE \`query_modules_module\` DROP FOREIGN KEY \`FK_12121324c524e12538de4948cee\`` - ); - await queryRunner.query( - `ALTER TABLE \`plugin\` DROP FOREIGN KEY \`FK_5162d18c3653d35ff4d104dd940\`` - ); - await queryRunner.query( - `ALTER TABLE \`migration\` DROP FOREIGN KEY \`FK_d3d093f32ce7c968b02fc6bce65\`` - ); - await queryRunner.query(`ALTER TABLE \`plugin\` DROP COLUMN \`queryId\``); - await queryRunner.query( - `ALTER TABLE \`module\` ADD \`isInjectable\` tinyint NOT NULL DEFAULT 1` - ); - await queryRunner.query( - `DROP INDEX \`IDX_7fb42dbc85874aafbd4bfb1c10\` ON \`query_modules_module\`` - ); - await queryRunner.query( - `DROP INDEX \`IDX_12121324c524e12538de4948ce\` ON \`query_modules_module\`` - ); - await queryRunner.query(`DROP TABLE \`query_modules_module\``); - await queryRunner.query(`DROP TABLE \`migration\``); - } -} diff --git a/src/migrations/1759002866941-init.ts b/src/migrations/1759002866941-init.ts new file mode 100644 index 0000000..820d93f --- /dev/null +++ b/src/migrations/1759002866941-init.ts @@ -0,0 +1,98 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Init1759002866941 implements MigrationInterface { + name = "Init1759002866941"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE \`token\` (\`token\` varchar(36) NOT NULL, \`isActive\` tinyint NOT NULL DEFAULT '0', \`projectId\` varchar(36) NULL, PRIMARY KEY (\`token\`)) ENGINE=InnoDB` + ); + await queryRunner.query( + `CREATE TABLE \`migration\` (\`id\` varchar(36) NOT NULL, \`appliedAt\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP(), \`isApplied\` tinyint NOT NULL DEFAULT '1', \`isValid\` tinyint NOT NULL DEFAULT '1', \`up\` longtext NOT NULL, \`down\` longtext NOT NULL, \`createdAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`databaseId\` varchar(36) NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB` + ); + await queryRunner.query( + `CREATE TABLE \`databaseNode\` (\`id\` varchar(36) NOT NULL, \`host\` varchar(255) NOT NULL, \`port\` int NOT NULL, \`username\` varchar(255) NOT NULL, \`password\` varchar(255) NOT NULL, UNIQUE INDEX \`IDX_DATABASE_NODE_HOST_PORT\` (\`host\`, \`port\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB` + ); + await queryRunner.query( + `CREATE TABLE \`database\` (\`id\` varchar(36) NOT NULL, \`q_username\` varchar(255) NOT NULL, \`c_username\` varchar(255) NOT NULL, \`password\` varchar(255) NOT NULL, \`database\` varchar(255) NOT NULL, \`nodeId\` varchar(36) NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB` + ); + await queryRunner.query( + `CREATE TABLE \`project\` (\`id\` varchar(36) NOT NULL, \`name\` varchar(255) NOT NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB` + ); + await queryRunner.query( + `CREATE TABLE \`module\` (\`id\` varchar(36) NOT NULL, \`sourcePath\` varchar(255) NOT NULL, \`name\` varchar(255) NOT NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB` + ); + await queryRunner.query( + `CREATE TABLE \`plugin\` (\`id\` varchar(36) NOT NULL, \`class\` varchar(255) NOT NULL, \`name\` varchar(255) NOT NULL, \`config\` varchar(255) NOT NULL, \`queryId\` varchar(36) NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB` + ); + await queryRunner.query( + `CREATE TABLE \`query\` (\`id\` varchar(36) NOT NULL, \`source\` longtext NOT NULL, \`isActive\` tinyint NOT NULL DEFAULT '1', \`projectId\` varchar(36) NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB` + ); + await queryRunner.query( + `CREATE TABLE \`query_modules_module\` (\`queryId\` varchar(36) NOT NULL, \`moduleId\` varchar(36) NOT NULL, INDEX \`IDX_12121324c524e12538de4948ce\` (\`queryId\`), INDEX \`IDX_7fb42dbc85874aafbd4bfb1c10\` (\`moduleId\`), PRIMARY KEY (\`queryId\`, \`moduleId\`)) ENGINE=InnoDB` + ); + await queryRunner.query( + `ALTER TABLE \`token\` ADD CONSTRAINT \`FK_1e287222c887ffb2c3302880588\` FOREIGN KEY (\`projectId\`) REFERENCES \`project\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION` + ); + await queryRunner.query( + `ALTER TABLE \`migration\` ADD CONSTRAINT \`FK_72832cfef0fdac2f4a0e017a6b8\` FOREIGN KEY (\`databaseId\`) REFERENCES \`database\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION` + ); + await queryRunner.query( + `ALTER TABLE \`database\` ADD CONSTRAINT \`FK_d668eb5daf53aa3eb388953db08\` FOREIGN KEY (\`nodeId\`) REFERENCES \`databaseNode\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION` + ); + await queryRunner.query( + `ALTER TABLE \`plugin\` ADD CONSTRAINT \`FK_5162d18c3653d35ff4d104dd940\` FOREIGN KEY (\`queryId\`) REFERENCES \`query\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION` + ); + await queryRunner.query( + `ALTER TABLE \`query\` ADD CONSTRAINT \`FK_a8e4452beec9f0228fb45de99fe\` FOREIGN KEY (\`projectId\`) REFERENCES \`project\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION` + ); + await queryRunner.query( + `ALTER TABLE \`query_modules_module\` ADD CONSTRAINT \`FK_12121324c524e12538de4948cee\` FOREIGN KEY (\`queryId\`) REFERENCES \`query\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE` + ); + await queryRunner.query( + `ALTER TABLE \`query_modules_module\` ADD CONSTRAINT \`FK_7fb42dbc85874aafbd4bfb1c101\` FOREIGN KEY (\`moduleId\`) REFERENCES \`module\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION` + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE \`query_modules_module\` DROP FOREIGN KEY \`FK_7fb42dbc85874aafbd4bfb1c101\`` + ); + await queryRunner.query( + `ALTER TABLE \`query_modules_module\` DROP FOREIGN KEY \`FK_12121324c524e12538de4948cee\`` + ); + await queryRunner.query( + `ALTER TABLE \`query\` DROP FOREIGN KEY \`FK_a8e4452beec9f0228fb45de99fe\`` + ); + await queryRunner.query( + `ALTER TABLE \`plugin\` DROP FOREIGN KEY \`FK_5162d18c3653d35ff4d104dd940\`` + ); + await queryRunner.query( + `ALTER TABLE \`database\` DROP FOREIGN KEY \`FK_d668eb5daf53aa3eb388953db08\`` + ); + await queryRunner.query( + `ALTER TABLE \`migration\` DROP FOREIGN KEY \`FK_72832cfef0fdac2f4a0e017a6b8\`` + ); + await queryRunner.query( + `ALTER TABLE \`token\` DROP FOREIGN KEY \`FK_1e287222c887ffb2c3302880588\`` + ); + await queryRunner.query( + `DROP INDEX \`IDX_7fb42dbc85874aafbd4bfb1c10\` ON \`query_modules_module\`` + ); + await queryRunner.query( + `DROP INDEX \`IDX_12121324c524e12538de4948ce\` ON \`query_modules_module\`` + ); + await queryRunner.query(`DROP TABLE \`query_modules_module\``); + await queryRunner.query(`DROP TABLE \`query\``); + await queryRunner.query(`DROP TABLE \`plugin\``); + await queryRunner.query(`DROP TABLE \`module\``); + await queryRunner.query(`DROP TABLE \`project\``); + await queryRunner.query(`DROP TABLE \`database\``); + await queryRunner.query( + `DROP INDEX \`IDX_DATABASE_NODE_HOST_PORT\` ON \`databaseNode\`` + ); + await queryRunner.query(`DROP TABLE \`databaseNode\``); + await queryRunner.query(`DROP TABLE \`migration\``); + await queryRunner.query(`DROP TABLE \`token\``); + } +} diff --git a/src/migrations/1759005139426-fix.ts b/src/migrations/1759005139426-fix.ts new file mode 100644 index 0000000..94bb1b8 --- /dev/null +++ b/src/migrations/1759005139426-fix.ts @@ -0,0 +1,59 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Fix1759005139426 implements MigrationInterface { + name = "Fix1759005139426"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE \`database\` ADD \`projectId\` varchar(36) NULL` + ); + await queryRunner.query( + `ALTER TABLE \`database\` ADD UNIQUE INDEX \`IDX_3b4af405edcc8df2198286c89d\` (\`projectId\`)` + ); + await queryRunner.query( + `ALTER TABLE \`project\` ADD \`databaseId\` varchar(36) NULL` + ); + await queryRunner.query( + `ALTER TABLE \`project\` ADD UNIQUE INDEX \`IDX_6212e562f65574a73b47251265\` (\`databaseId\`)` + ); + await queryRunner.query( + `CREATE UNIQUE INDEX \`REL_3b4af405edcc8df2198286c89d\` ON \`database\` (\`projectId\`)` + ); + await queryRunner.query( + `CREATE UNIQUE INDEX \`REL_6212e562f65574a73b47251265\` ON \`project\` (\`databaseId\`)` + ); + await queryRunner.query( + `ALTER TABLE \`database\` ADD CONSTRAINT \`FK_3b4af405edcc8df2198286c89d2\` FOREIGN KEY (\`projectId\`) REFERENCES \`project\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION` + ); + await queryRunner.query( + `ALTER TABLE \`project\` ADD CONSTRAINT \`FK_6212e562f65574a73b47251265b\` FOREIGN KEY (\`databaseId\`) REFERENCES \`database\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION` + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE \`project\` DROP FOREIGN KEY \`FK_6212e562f65574a73b47251265b\`` + ); + await queryRunner.query( + `ALTER TABLE \`database\` DROP FOREIGN KEY \`FK_3b4af405edcc8df2198286c89d2\`` + ); + await queryRunner.query( + `DROP INDEX \`REL_6212e562f65574a73b47251265\` ON \`project\`` + ); + await queryRunner.query( + `DROP INDEX \`REL_3b4af405edcc8df2198286c89d\` ON \`database\`` + ); + await queryRunner.query( + `ALTER TABLE \`project\` DROP INDEX \`IDX_6212e562f65574a73b47251265\`` + ); + await queryRunner.query( + `ALTER TABLE \`project\` DROP COLUMN \`databaseId\`` + ); + await queryRunner.query( + `ALTER TABLE \`database\` DROP INDEX \`IDX_3b4af405edcc8df2198286c89d\`` + ); + await queryRunner.query( + `ALTER TABLE \`database\` DROP COLUMN \`projectId\`` + ); + } +} diff --git a/src/project/entities/project.entity.ts b/src/project/entities/project.entity.ts index 2ef0328..caab56a 100644 --- a/src/project/entities/project.entity.ts +++ b/src/project/entities/project.entity.ts @@ -3,11 +3,12 @@ import { Query } from "../../query/entities/query.entity"; import { Column, Entity, + JoinColumn, OneToMany, OneToOne, PrimaryGeneratedColumn, } from "typeorm"; -import { Database } from "src/databaseManager/entities/database.entity"; +import { Database } from "../../databaseManager/entities/database.entity"; @Entity("project") export class Project { @@ -21,6 +22,7 @@ export class Project { apiTokens: Token[]; @OneToOne(() => Database, (database) => database.project) + @JoinColumn() database: Database; @OneToMany(() => Query, (query) => query.project) diff --git a/tests/base/createDbNode.ts b/tests/base/createDbNode.ts new file mode 100644 index 0000000..d7b0662 --- /dev/null +++ b/tests/base/createDbNode.ts @@ -0,0 +1,16 @@ +import createDatabaseNode from "../functions/createDatabaseNode"; + +(async () => { + try { + const node = await createDatabaseNode( + "localhost", + 3306, + "root", + "password" + ); + + console.log("node", node); + } catch (error) { + console.error("Error during test execution:", error); + } +})(); diff --git a/tests/base/createMigration.ts b/tests/base/createMigration.ts index 50471d2..228c9bf 100644 --- a/tests/base/createMigration.ts +++ b/tests/base/createMigration.ts @@ -1,21 +1,42 @@ +import createDatabase from "../functions/createDatabase"; +import createDatabaseNode from "../functions/createDatabaseNode"; import createMigration from "../functions/createMigration"; import createProject from "../functions/createProject"; +import databaseMigrationUp from "..//functions/databaseMigrationUp"; (async () => { try { - const project = await createProject("Test Project"); + const node = await createDatabaseNode("localhost", 3306, "root", "root"); - const result_1 = await createMigration(project.token, [ - { - name: "users", - fields: { - name: { type: "string", isNullable: false }, - age: { type: "int", isNullable: true }, - }, - }, - ]); + console.log("Database node created:", node); - console.log("Migration 1:", result_1.data); + const project = await createProject("Test Migrations Project"); + + console.log("Project created:", project); + + const db = await createDatabase(project.id, node.id); + + console.log("Database created:", db); + + const migration = await createMigration( + db.id, + "CREATE TABLE `test` (id INT)", + "DROP TABLE `test`" + ); + + console.log("Migration created:", migration); + + const migration2 = await createMigration( + db.id, + "ALTER TABLE `test` ADD COLUMN name VARCHAR(255)", + "ALTER TABLE `test` DROP COLUMN name" + ); + + console.log("Second Migration created:", migration2); + + const migrationsUp = await databaseMigrationUp(db.id); + + console.log("Migrations applied:", migrationsUp); } catch (error) { console.error("Error during test execution:", error); } diff --git a/tests/functions/createDatabase.ts b/tests/functions/createDatabase.ts new file mode 100644 index 0000000..a4c8272 --- /dev/null +++ b/tests/functions/createDatabase.ts @@ -0,0 +1,15 @@ +import axios from "axios"; +import { config } from "../config"; + +export default async (projectId: string, databaseNodeId: string) => { + try { + const response = await axios.post(`${config.url}/database/create`, { + projectId, + databaseNodeId, + }); + + return response.data; + } catch (error) { + console.error("Error in creating database:", error); + } +}; diff --git a/tests/functions/createDatabaseNode.ts b/tests/functions/createDatabaseNode.ts new file mode 100644 index 0000000..cff8328 --- /dev/null +++ b/tests/functions/createDatabaseNode.ts @@ -0,0 +1,22 @@ +import { config } from "../config"; +import axios from "axios"; + +export default async ( + host: string, + port: number, + username: string, + password: string +) => { + try { + const response = await axios.post(`${config.url}/database/node/create`, { + host, + port, + username, + password, + }); + + return response.data; + } catch (error) { + console.error("Error in creating database node:", error); + } +}; diff --git a/tests/functions/createMigration.ts b/tests/functions/createMigration.ts index f85db6c..00c6a52 100644 --- a/tests/functions/createMigration.ts +++ b/tests/functions/createMigration.ts @@ -1,12 +1,20 @@ import axios from "axios"; import { config } from "../config"; -export default async (token: string, tables: any) => { +export default async ( + databaseId: string, + upQuery: string, + downQuery: string +) => { try { - const response = await axios.post(`${config.url}/migrations/create`, { - token, - tables, - }); + const response = await axios.post( + `${config.url}/database/migration/create`, + { + databaseId, + up: upQuery, + down: downQuery, + } + ); return response.data; } catch (error) { diff --git a/tests/functions/databaseMigrationUp.ts b/tests/functions/databaseMigrationUp.ts new file mode 100644 index 0000000..aa3c4db --- /dev/null +++ b/tests/functions/databaseMigrationUp.ts @@ -0,0 +1,14 @@ +import { config } from "../config"; +import axios from "axios"; + +export default async (databaseId: string) => { + try { + const response = await axios.get( + `${config.url}/database/migration/up/${databaseId}` + ); + + return response.data; + } catch (error) { + console.error("Error in migrating database up:", error); + } +};