feat: enhance query handling with modules and plugins

- Added ManyToMany relationship for plugins in Query entity.
- Updated QueryExecuterController to accept structured query data.
- Enhanced QueryExecuterService to support plugin initialization and execution.
- Implemented QueryHandlerService methods for creating and updating queries, modules, and plugins.
- Introduced new endpoints for creating and adding modules and plugins to queries.
- Created Plugin base class and DatabasePlugin implementation for database interactions.
- Updated VM class to integrate plugin functionality during script execution.
- Added test cases for project, query, module, and plugin operations.
This commit is contained in:
lborv
2025-09-21 01:07:51 +03:00
parent d90c85d66f
commit 8eba1d1344
26 changed files with 620 additions and 25 deletions

View File

@ -0,0 +1,20 @@
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from "typeorm";
import { Query } from "./query.entity";
@Entity("plugin")
export class VmPlugin {
@PrimaryGeneratedColumn("uuid")
id: string;
@Column({ type: "varchar", length: 255, nullable: false })
class: string;
@Column({ type: "varchar", length: 255, nullable: false })
name: string;
@ManyToMany(() => Query, (query) => query.plugins)
queries: Query[];
@Column({ type: "varchar", length: 255 })
config: string;
}

View File

@ -7,6 +7,7 @@ import {
PrimaryGeneratedColumn,
} from "typeorm";
import { VMModule } from "./module.entity";
import { VmPlugin } from "./plugin.entity";
@Entity("query")
export class Query {
@ -24,4 +25,7 @@ export class Query {
@ManyToMany(() => VMModule, (module) => module.queries)
modules: VMModule[];
@ManyToMany(() => VmPlugin, (plugin) => plugin.queries)
plugins: VmPlugin[];
}

View File

@ -9,7 +9,10 @@ export class QueryExecuterController {
) {}
@Post("/run/:token")
async runQuery(@Param("token") token: string, @Body() query: any) {
async runQuery(
@Param("token") token: string,
@Body() query: Record<string, any>
) {
return this.queryExecuterService.runQuery(token, query);
}
}

View File

@ -4,6 +4,7 @@ import { Query } from "../entities/query.entity";
import { Repository } from "typeorm";
import { Vm } from "../../vm/vm.class";
import { VModule } from "../../vm/module.class";
import { DatabasePlugin } from "src/vm/plugins/database.plugin";
@Injectable()
export class QueryExecuterService {
@ -21,18 +22,38 @@ export class QueryExecuterService {
throw new Error("Query not found");
}
const vm = this.createVm(query);
const result = await vm.runScript(query.source);
const vm = await this.createVm(query);
const result = await vm.runScript(query.source, queryData);
return { message: "Query executed", result, query: queryData };
}
private createVm(query: Query) {
return new Vm({
memoryLimit: 5,
private async createVm(query: Query) {
if (query.modules === undefined) {
query.modules = [];
}
if (query.plugins === undefined) {
query.plugins = [];
}
const vm = new Vm({
memoryLimit: 128,
modules: query.modules.map((module) => {
return new VModule(module.name, module.sourcePath);
}),
plugins: query.plugins.map((plugin) => {
switch (plugin.class) {
case "DATABASE": {
const config = JSON.parse(plugin.config);
return DatabasePlugin.init(plugin.name, config);
}
default:
throw new Error(`Unknown plugin class: ${plugin.class}`);
}
}),
});
return await vm.init();
}
}

View File

@ -1,4 +1,4 @@
import { Controller, Inject } from "@nestjs/common";
import { Body, Controller, Inject, Post } from "@nestjs/common";
import { QueryHandlerService } from "./query.handler.service";
@Controller("query")
@ -7,4 +7,47 @@ export class QueryHandlerController {
@Inject(QueryHandlerService)
private readonly queryHandlerService: QueryHandlerService
) {}
@Post("create")
async createQuery(
@Body() queryData: { projectToken: string; source: string }
) {
return this.queryHandlerService.createQuery(queryData);
}
@Post("update/:id")
async updateQuery(
@Body() updateData: Partial<{ source: string }>,
@Inject("id") id: string
) {
return this.queryHandlerService.updateQuery(id, updateData);
}
@Post("module/create")
async createModule(@Body() moduleData: { name: string; sourcePath: string }) {
return this.queryHandlerService.createModule(moduleData);
}
@Post("plugin/create")
async createPlugin(
@Body() pluginData: { name: string; class: string; config: string }
) {
return this.queryHandlerService.createPlugin(pluginData);
}
@Post("module/add")
async addModuleToQuery(@Body() data: { queryId: string; moduleId: string }) {
return this.queryHandlerService.addModuleToQuery(
data.queryId,
data.moduleId
);
}
@Post("plugin/add")
async addPluginToQuery(@Body() data: { queryId: string; pluginId: string }) {
return this.queryHandlerService.addPluginToQuery(
data.queryId,
data.pluginId
);
}
}

View File

@ -1,4 +1,106 @@
import { Injectable } from "@nestjs/common";
import { Inject, Injectable } from "@nestjs/common";
import { Repository } from "typeorm";
import { Query } from "../entities/query.entity";
import { VMModule } from "../entities/module.entity";
import { VmPlugin } from "../entities/plugin.entity";
import { InjectRepository } from "@nestjs/typeorm";
import { ProjectService } from "src/project/project.service";
@Injectable()
export class QueryHandlerService {}
export class QueryHandlerService {
constructor(
@InjectRepository(Query)
private readonly queryRepository: Repository<Query>,
@InjectRepository(VMModule)
private readonly moduleRepository: Repository<VMModule>,
@InjectRepository(VmPlugin)
private readonly pluginRepository: Repository<VmPlugin>,
@Inject(ProjectService)
private readonly projectService: ProjectService
) {}
async createQuery(queryData: { projectToken: string; source: string }) {
const project = await this.projectService.findByToken(
queryData.projectToken
);
if (!project) {
throw new Error("Project not found");
}
queryData["project"] = project;
delete queryData.projectToken;
const query = this.queryRepository.create(queryData);
return this.queryRepository.save(query);
}
async updateQuery(id: string, updateData: Partial<Query>) {
const query = await this.queryRepository.findOne({ where: { id } });
if (!query) {
throw new Error("Query not found");
}
Object.assign(query, updateData);
return this.queryRepository.save(query);
}
async createModule(moduleData: { name: string; sourcePath: string }) {
const module = this.moduleRepository.create(moduleData);
return this.moduleRepository.save(module);
}
async createPlugin(pluginData: {
name: string;
class: string;
config: string;
}) {
const plugin = this.pluginRepository.create(pluginData);
return this.pluginRepository.save(plugin);
}
async addModuleToQuery(queryId: string, moduleId: string) {
const query = await this.queryRepository.findOne({
where: { id: queryId },
relations: ["modules"],
});
if (!query) {
throw new Error("Query not found");
}
const module = await this.moduleRepository.findOne({
where: { id: moduleId },
});
if (!module) {
throw new Error("Module not found");
}
query.modules.push(module);
return this.queryRepository.save(query);
}
async addPluginToQuery(queryId: string, pluginId: string) {
const query = await this.queryRepository.findOne({
where: { id: queryId },
relations: ["plugins"],
});
if (!query) {
throw new Error("Query not found");
}
const plugin = await this.pluginRepository.findOne({
where: { id: pluginId },
});
if (!plugin) {
throw new Error("Plugin not found");
}
query.plugins.push(plugin);
return this.queryRepository.save(query);
}
}

View File

@ -1,4 +1,4 @@
import { Module } from "@nestjs/common";
import { forwardRef, Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { Query } from "./entities/query.entity";
import { VMModule } from "./entities/module.entity";
@ -6,9 +6,14 @@ import { QueryExecuterController } from "./executer/query.executer.controller";
import { QueryHandlerController } from "./handler/query.handler.controller";
import { QueryExecuterService } from "./executer/query.executer.service";
import { QueryHandlerService } from "./handler/query.handler.service";
import { VmPlugin } from "./entities/plugin.entity";
import { ProjectModule } from "src/project/project.module";
@Module({
imports: [TypeOrmModule.forFeature([Query, VMModule])],
imports: [
forwardRef(() => ProjectModule),
TypeOrmModule.forFeature([Query, VMModule, VmPlugin]),
],
controllers: [QueryExecuterController, QueryHandlerController],
providers: [QueryExecuterService, QueryHandlerService],
})