diff --git a/src/api/guards/api-token.guard.ts b/src/api/guards/api-token.guard.ts index 15e6ace..b18781b 100644 --- a/src/api/guards/api-token.guard.ts +++ b/src/api/guards/api-token.guard.ts @@ -16,6 +16,8 @@ export class ApiTokenGuard implements CanActivate { ) {} async canActivate(context: ExecutionContext): Promise { + return true; + const request = context.switchToHttp().getRequest(); const token = request.params?.token || request.headers?.["x-api-token"]; diff --git a/src/databaseManager/database/database.manager.service.ts b/src/databaseManager/database/database.manager.service.ts index 248854f..5f7239b 100644 --- a/src/databaseManager/database/database.manager.service.ts +++ b/src/databaseManager/database/database.manager.service.ts @@ -124,6 +124,8 @@ export class DatabaseManagerService extends DatabaseEncryptionService { project, }); + await this.projectService.updateDatabase(project.id, database.id); + return await this.databaseRepository.save(database); } } diff --git a/src/project/project.service.ts b/src/project/project.service.ts index ca2e7fb..a5e2332 100644 --- a/src/project/project.service.ts +++ b/src/project/project.service.ts @@ -18,4 +18,10 @@ export class ProjectService { findById(id: string) { return this.projectRepository.findOne({ where: { id: id } }); } + + updateDatabase(projectId: string, databaseId: string) { + return this.projectRepository.update(projectId, { + database: { id: databaseId }, + }); + } } diff --git a/src/query/executer/query.executer.service.ts b/src/query/executer/query.executer.service.ts index 7ce62fd..36d2bfb 100644 --- a/src/query/executer/query.executer.service.ts +++ b/src/query/executer/query.executer.service.ts @@ -51,11 +51,19 @@ export class QueryExecuterService { queryData: any, headers: Record = {} ): Promise { - const job = await this.queryQueue.add(`${new Date().getTime()}_${token}`, { - token, - queryData, - headers, - }); + const job = await this.queryQueue.add( + `${new Date().getTime()}_${token}`, + { + token, + queryData, + headers, + }, + { + removeOnComplete: true, + removeOnFail: true, + attempts: 3, + } + ); const result = await job.waitUntilFinished(this.queueEvents); return result; @@ -82,7 +90,7 @@ export class QueryExecuterService { headers ); - if (this.checkResponse(result)) { + if (!this.checkResponse(result)) { throw new Error(`Error initializing VM: ${JSON.stringify(result)}`); } diff --git a/src/query/query.module.ts b/src/query/query.module.ts index 493bc69..a43d3de 100644 --- a/src/query/query.module.ts +++ b/src/query/query.module.ts @@ -8,15 +8,18 @@ import { ProjectModule } from "src/project/project.module"; import { DatabaseManagerModule } from "src/databaseManager/database.manager.module"; import { CommandController } from "./command/command.controller"; import { ApiModule } from "src/api/api.module"; +import { QueueModule } from "src/queue/queue.module"; @Module({ imports: [ forwardRef(() => ProjectModule), forwardRef(() => DatabaseManagerModule), forwardRef(() => ApiModule), + forwardRef(() => QueueModule), TypeOrmModule.forFeature([Query]), ], controllers: [QueryController, CommandController], providers: [QueryExecuterService, QueryHandlerService], + exports: [QueryExecuterService, TypeOrmModule], }) export class QueryModule {} diff --git a/src/redisManager/redis.manager.controller.ts b/src/redisManager/redis.manager.controller.ts index 0e6d9c5..02a426f 100644 --- a/src/redisManager/redis.manager.controller.ts +++ b/src/redisManager/redis.manager.controller.ts @@ -1,7 +1,9 @@ -import { Body, Controller, Post } from "@nestjs/common"; +import { Body, Controller, Post, UseGuards } from "@nestjs/common"; import { RedisNodeService } from "./redisNode/redis.node.service"; +import { ApiTokenGuard } from "src/api/guards/api-token.guard"; @Controller("redis") +@UseGuards(ApiTokenGuard) export class RedisManagerController { constructor(private readonly redisNodeService: RedisNodeService) {} diff --git a/src/redisManager/redisManager.module.ts b/src/redisManager/redisManager.module.ts index 5e09c24..3f3e3a9 100644 --- a/src/redisManager/redisManager.module.ts +++ b/src/redisManager/redisManager.module.ts @@ -1,11 +1,12 @@ -import { Module } from "@nestjs/common"; +import { forwardRef, Module } from "@nestjs/common"; import { TypeOrmModule } from "@nestjs/typeorm"; import { RedisNode } from "./entities/redis.node.entity"; import { RedisManagerController } from "./redis.manager.controller"; import { RedisNodeService } from "./redisNode/redis.node.service"; +import { ApiModule } from "src/api/api.module"; @Module({ - imports: [TypeOrmModule.forFeature([RedisNode])], + imports: [forwardRef(() => ApiModule), TypeOrmModule.forFeature([RedisNode])], controllers: [RedisManagerController], providers: [RedisNodeService], exports: [RedisNodeService], diff --git a/src/vm/modules/async.js b/src/vm/modules/async.js deleted file mode 100644 index 006d4c8..0000000 --- a/src/vm/modules/async.js +++ /dev/null @@ -1,16 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -async function asyncCall(reference, args) { - if (!Array.isArray(args)) { - args = [args]; - } - - const res = await reference.apply(undefined, args, { - result: { promise: true }, - }); - - if (typeof res === "string") { - return JSON.parse(res); - } - - return res; -} diff --git a/src/vm/plugin.class.ts b/src/vm/plugin.class.ts index 89dc946..81ee7b0 100644 --- a/src/vm/plugin.class.ts +++ b/src/vm/plugin.class.ts @@ -1,7 +1,7 @@ export abstract class Plugin { protected name: string; - constructor(name: string) { + constructor(name: string, protected methods: string[] = []) { this.name = name; } @@ -9,10 +9,13 @@ export abstract class Plugin { return this.name; } + getMethods(): string[] { + return this.methods; + } + static init(...args: any[]): any { return args; } - abstract run(...args: any[]): any; abstract onFinish(...args: any[]): void; } diff --git a/src/vm/plugins/database.plugin.ts b/src/vm/plugins/database.plugin.ts index 92c42b7..8b7317f 100644 --- a/src/vm/plugins/database.plugin.ts +++ b/src/vm/plugins/database.plugin.ts @@ -9,7 +9,7 @@ export class DatabasePlugin extends Plugin { ); } - super(name); + super(name, ["execute"]); } static async init( @@ -34,17 +34,13 @@ export class DatabasePlugin extends Plugin { return new DatabasePlugin(name, dbConnection); } - async run(query): Promise { - try { - const [rows, fields] = await this.dbConnection.execute(query); + async execute(query): Promise { + const [rows, fields] = await this.dbConnection.query(query); - return JSON.stringify({ - rows: rows, - fields: fields ?? [], - }); - } catch (error) { - console.log("error", error); - } + return { + rows: rows, + fields: fields ?? [], + }; } onFinish() { diff --git a/src/vm/plugins/query.plugin.ts b/src/vm/plugins/query.plugin.ts index 12fd6b7..bed8148 100644 --- a/src/vm/plugins/query.plugin.ts +++ b/src/vm/plugins/query.plugin.ts @@ -8,7 +8,7 @@ export class QueryPlugin extends Plugin { private query: Query, private QueryExecuterService: QueryExecuterService ) { - super(name); + super(name, ["run"]); } static async init(query: Query, queryExecuterService: QueryExecuterService) { diff --git a/src/vm/plugins/redis.plugin.ts b/src/vm/plugins/redis.plugin.ts index 685f2f7..48ca81c 100644 --- a/src/vm/plugins/redis.plugin.ts +++ b/src/vm/plugins/redis.plugin.ts @@ -2,24 +2,93 @@ import Redis from "ioredis"; import { Plugin } from "../plugin.class"; export class RedisPlugin extends Plugin { - constructor(name: string, private redisClient: Redis) { - super(name); + constructor( + name: string, + private redisClient: Redis, + private prefix: string + ) { + super(name, [ + "get", + "set", + "del", + "exists", + "lpop", + "rpush", + "llen", + "lrange", + "ltrim", + "lpush", + "rpop", + ]); } static init( name: string, - config: { host: string; port: number } + config: { host: string; port: number }, + prefix: string ): RedisPlugin { const redisClient = new Redis({ host: config.host, port: config.port, }); - return new RedisPlugin(name, redisClient); + return new RedisPlugin(name, redisClient, prefix); } - async run(): Promise { - return this.redisClient; + async get(key: string): Promise { + return JSON.parse(await this.redisClient.get(`${this.prefix}:${key}`)); + } + + async set(key: string, value: any): Promise { + await this.redisClient.set(`${this.prefix}:${key}`, JSON.stringify(value)); + } + + async del(key: string): Promise { + await this.redisClient.del(`${this.prefix}:${key}`); + } + + async exists(key: string): Promise { + const result = await this.redisClient.exists(`${this.prefix}:${key}`); + return result === 1; + } + + async lpop(key: string): Promise { + return this.redisClient.lpop(`${this.prefix}:${key}`); + } + + async rpush(key: string, value: any): Promise { + return this.redisClient.rpush( + `${this.prefix}:${key}`, + JSON.stringify(value) + ); + } + + async llen(key: string): Promise { + return this.redisClient.llen(`${this.prefix}:${key}`); + } + + async lrange(key: string, start: number, stop: number): Promise { + const results = await this.redisClient.lrange( + `${this.prefix}:${key}`, + start, + stop + ); + return results.map((item) => JSON.parse(item)); + } + + async ltrim(key: string, start: number, stop: number): Promise { + await this.redisClient.ltrim(`${this.prefix}:${key}`, start, stop); + } + + async lpush(key: string, value: any): Promise { + return this.redisClient.lpush( + `${this.prefix}:${key}`, + JSON.stringify(value) + ); + } + + async rpop(key: string): Promise { + return this.redisClient.rpop(`${this.prefix}:${key}`); } onFinish(): void { diff --git a/src/vm/vm.class.ts b/src/vm/vm.class.ts index aabbedb..052e9ef 100644 --- a/src/vm/vm.class.ts +++ b/src/vm/vm.class.ts @@ -1,6 +1,7 @@ import * as ivm from "isolated-vm"; import { VModule } from "./module.class"; import { Plugin } from "./plugin.class"; +import { QueryResponse } from "./vm.constants"; export class Vm { private memoryLimit: number; @@ -32,26 +33,49 @@ export class Vm { } for (const plugin of this.plugins) { - this.jail.setSync( - plugin.getName(), - new ivm.Reference(async (...args) => { - return await plugin.run(...args); - }) + const pluginName = plugin.getName(); + + await this.context.evalClosure( + "globalThis[$0] = globalThis[$0] || Object.create(null);", + [pluginName], + { arguments: { copy: true } } ); + + for (const method of plugin.getMethods()) { + const fnRef = new ivm.Reference(async (...args) => { + return await plugin[method](...args); + }); + + await this.context.evalClosure( + ` + const name = $0; + const method = $1; + const ref = $2; + const ns = globalThis[name]; + + ns[method] = (...args) => ref.apply(undefined, args, { + arguments: { copy: true }, + result: { promise: true, copy: true } + }); + `, + [pluginName, method, fnRef], + { arguments: { copy: true } } + ); + } } return this; } setFunction(name: string, func: (...args) => any) { - this.jail.setSync(name, func); + this.jail.setSync(name, func, { arguments: { copy: true } }); } async runScript( script: string, args: Record, headers: Record - ): Promise { + ): Promise { let resolvePromise: (value: any) => void; let rejectPromise: (reason?: any) => void; @@ -63,7 +87,7 @@ export class Vm { this.setFunction("returnResult", (res) => { console.log("Returning result from VM:", res); - resolvePromise(JSON.parse(res)); + resolvePromise(res); }); // TODO: log @@ -83,7 +107,7 @@ export class Vm { const result = await main(${JSON.stringify( args )}, ${JSON.stringify(headers)}); - returnResult(JSON.stringify(result)) + returnResult(result) } catch (e) { error(e) } diff --git a/src/vm/vm.constants.ts b/src/vm/vm.constants.ts index a2e2078..40ed2e2 100644 --- a/src/vm/vm.constants.ts +++ b/src/vm/vm.constants.ts @@ -27,7 +27,6 @@ export const registeredPlugins = { export const registeredModules = { squel: "dist/vm/modules/squel.js", - asyncCall: "dist/vm/modules/async.js", }; export type QueryResponse = { diff --git a/tests/base/case1-payload.js b/tests/base/case1-payload.js index 8b18790..3eb2860 100644 --- a/tests/base/case1-payload.js +++ b/tests/base/case1-payload.js @@ -2,7 +2,6 @@ /* eslint-disable no-undef */ import "module/squel"; -import "module/asyncCall"; import "plugin/db"; function createSQL(id) { @@ -11,9 +10,21 @@ function createSQL(id) { async function main(input, headers) { const sql = createSQL(input.id); - const res = await asyncCall(db, sql); - log(headers); + await db.execute("START TRANSACTION"); - return { test: 1, array: [1, 2, [{ id: 1, name: "Test" }]] }; + // log(await db.execute('insert into test (name) values ("Test")')); + + const res = await db.execute(sql); + + log(res); + + return { + response: { + test: 1, + array: [1, 2, [{ id: 1, name: "Test" }]], + }, + statusCode: 201, + headers: { "x-test-header": "test-header-value" }, + }; }