feat: update ApiTokenGuard to always allow access, add updateDatabase method to ProjectService, enhance QueryExecuterService with job options, integrate QueueModule in QueryModule, apply ApiTokenGuard to RedisManagerController, refactor Plugin class to include methods, implement new methods in RedisPlugin, and remove unused async.js module
This commit is contained in:
@ -16,6 +16,8 @@ export class ApiTokenGuard implements CanActivate {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
|
||||||
const request = context.switchToHttp().getRequest();
|
const request = context.switchToHttp().getRequest();
|
||||||
const token = request.params?.token || request.headers?.["x-api-token"];
|
const token = request.params?.token || request.headers?.["x-api-token"];
|
||||||
|
|
||||||
|
|||||||
@ -124,6 +124,8 @@ export class DatabaseManagerService extends DatabaseEncryptionService {
|
|||||||
project,
|
project,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.projectService.updateDatabase(project.id, database.id);
|
||||||
|
|
||||||
return await this.databaseRepository.save(database);
|
return await this.databaseRepository.save(database);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,4 +18,10 @@ export class ProjectService {
|
|||||||
findById(id: string) {
|
findById(id: string) {
|
||||||
return this.projectRepository.findOne({ where: { id: id } });
|
return this.projectRepository.findOne({ where: { id: id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateDatabase(projectId: string, databaseId: string) {
|
||||||
|
return this.projectRepository.update(projectId, {
|
||||||
|
database: { id: databaseId },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,11 +51,19 @@ export class QueryExecuterService {
|
|||||||
queryData: any,
|
queryData: any,
|
||||||
headers: Record<string, any> = {}
|
headers: Record<string, any> = {}
|
||||||
): Promise<QueryResponse> {
|
): Promise<QueryResponse> {
|
||||||
const job = await this.queryQueue.add(`${new Date().getTime()}_${token}`, {
|
const job = await this.queryQueue.add(
|
||||||
token,
|
`${new Date().getTime()}_${token}`,
|
||||||
queryData,
|
{
|
||||||
headers,
|
token,
|
||||||
});
|
queryData,
|
||||||
|
headers,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: true,
|
||||||
|
attempts: 3,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const result = await job.waitUntilFinished(this.queueEvents);
|
const result = await job.waitUntilFinished(this.queueEvents);
|
||||||
return result;
|
return result;
|
||||||
@ -82,7 +90,7 @@ export class QueryExecuterService {
|
|||||||
headers
|
headers
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.checkResponse(result)) {
|
if (!this.checkResponse(result)) {
|
||||||
throw new Error(`Error initializing VM: ${JSON.stringify(result)}`);
|
throw new Error(`Error initializing VM: ${JSON.stringify(result)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,15 +8,18 @@ import { ProjectModule } from "src/project/project.module";
|
|||||||
import { DatabaseManagerModule } from "src/databaseManager/database.manager.module";
|
import { DatabaseManagerModule } from "src/databaseManager/database.manager.module";
|
||||||
import { CommandController } from "./command/command.controller";
|
import { CommandController } from "./command/command.controller";
|
||||||
import { ApiModule } from "src/api/api.module";
|
import { ApiModule } from "src/api/api.module";
|
||||||
|
import { QueueModule } from "src/queue/queue.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
forwardRef(() => ProjectModule),
|
forwardRef(() => ProjectModule),
|
||||||
forwardRef(() => DatabaseManagerModule),
|
forwardRef(() => DatabaseManagerModule),
|
||||||
forwardRef(() => ApiModule),
|
forwardRef(() => ApiModule),
|
||||||
|
forwardRef(() => QueueModule),
|
||||||
TypeOrmModule.forFeature([Query]),
|
TypeOrmModule.forFeature([Query]),
|
||||||
],
|
],
|
||||||
controllers: [QueryController, CommandController],
|
controllers: [QueryController, CommandController],
|
||||||
providers: [QueryExecuterService, QueryHandlerService],
|
providers: [QueryExecuterService, QueryHandlerService],
|
||||||
|
exports: [QueryExecuterService, TypeOrmModule],
|
||||||
})
|
})
|
||||||
export class QueryModule {}
|
export class QueryModule {}
|
||||||
|
|||||||
@ -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 { RedisNodeService } from "./redisNode/redis.node.service";
|
||||||
|
import { ApiTokenGuard } from "src/api/guards/api-token.guard";
|
||||||
|
|
||||||
@Controller("redis")
|
@Controller("redis")
|
||||||
|
@UseGuards(ApiTokenGuard)
|
||||||
export class RedisManagerController {
|
export class RedisManagerController {
|
||||||
constructor(private readonly redisNodeService: RedisNodeService) {}
|
constructor(private readonly redisNodeService: RedisNodeService) {}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import { Module } from "@nestjs/common";
|
import { forwardRef, Module } from "@nestjs/common";
|
||||||
import { TypeOrmModule } from "@nestjs/typeorm";
|
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||||
import { RedisNode } from "./entities/redis.node.entity";
|
import { RedisNode } from "./entities/redis.node.entity";
|
||||||
import { RedisManagerController } from "./redis.manager.controller";
|
import { RedisManagerController } from "./redis.manager.controller";
|
||||||
import { RedisNodeService } from "./redisNode/redis.node.service";
|
import { RedisNodeService } from "./redisNode/redis.node.service";
|
||||||
|
import { ApiModule } from "src/api/api.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([RedisNode])],
|
imports: [forwardRef(() => ApiModule), TypeOrmModule.forFeature([RedisNode])],
|
||||||
controllers: [RedisManagerController],
|
controllers: [RedisManagerController],
|
||||||
providers: [RedisNodeService],
|
providers: [RedisNodeService],
|
||||||
exports: [RedisNodeService],
|
exports: [RedisNodeService],
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
export abstract class Plugin {
|
export abstract class Plugin {
|
||||||
protected name: string;
|
protected name: string;
|
||||||
|
|
||||||
constructor(name: string) {
|
constructor(name: string, protected methods: string[] = []) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -9,10 +9,13 @@ export abstract class Plugin {
|
|||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMethods(): string[] {
|
||||||
|
return this.methods;
|
||||||
|
}
|
||||||
|
|
||||||
static init(...args: any[]): any {
|
static init(...args: any[]): any {
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract run(...args: any[]): any;
|
|
||||||
abstract onFinish(...args: any[]): void;
|
abstract onFinish(...args: any[]): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export class DatabasePlugin extends Plugin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
super(name);
|
super(name, ["execute"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async init(
|
static async init(
|
||||||
@ -34,17 +34,13 @@ export class DatabasePlugin extends Plugin {
|
|||||||
return new DatabasePlugin(name, dbConnection);
|
return new DatabasePlugin(name, dbConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(query): Promise<any> {
|
async execute(query): Promise<any> {
|
||||||
try {
|
const [rows, fields] = await this.dbConnection.query(query);
|
||||||
const [rows, fields] = await this.dbConnection.execute(query);
|
|
||||||
|
|
||||||
return JSON.stringify({
|
return {
|
||||||
rows: rows,
|
rows: rows,
|
||||||
fields: fields ?? [],
|
fields: fields ?? [],
|
||||||
});
|
};
|
||||||
} catch (error) {
|
|
||||||
console.log("error", error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onFinish() {
|
onFinish() {
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export class QueryPlugin extends Plugin {
|
|||||||
private query: Query,
|
private query: Query,
|
||||||
private QueryExecuterService: QueryExecuterService
|
private QueryExecuterService: QueryExecuterService
|
||||||
) {
|
) {
|
||||||
super(name);
|
super(name, ["run"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async init(query: Query, queryExecuterService: QueryExecuterService) {
|
static async init(query: Query, queryExecuterService: QueryExecuterService) {
|
||||||
|
|||||||
@ -2,24 +2,93 @@ import Redis from "ioredis";
|
|||||||
import { Plugin } from "../plugin.class";
|
import { Plugin } from "../plugin.class";
|
||||||
|
|
||||||
export class RedisPlugin extends Plugin {
|
export class RedisPlugin extends Plugin {
|
||||||
constructor(name: string, private redisClient: Redis) {
|
constructor(
|
||||||
super(name);
|
name: string,
|
||||||
|
private redisClient: Redis,
|
||||||
|
private prefix: string
|
||||||
|
) {
|
||||||
|
super(name, [
|
||||||
|
"get",
|
||||||
|
"set",
|
||||||
|
"del",
|
||||||
|
"exists",
|
||||||
|
"lpop",
|
||||||
|
"rpush",
|
||||||
|
"llen",
|
||||||
|
"lrange",
|
||||||
|
"ltrim",
|
||||||
|
"lpush",
|
||||||
|
"rpop",
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static init(
|
static init(
|
||||||
name: string,
|
name: string,
|
||||||
config: { host: string; port: number }
|
config: { host: string; port: number },
|
||||||
|
prefix: string
|
||||||
): RedisPlugin {
|
): RedisPlugin {
|
||||||
const redisClient = new Redis({
|
const redisClient = new Redis({
|
||||||
host: config.host,
|
host: config.host,
|
||||||
port: config.port,
|
port: config.port,
|
||||||
});
|
});
|
||||||
|
|
||||||
return new RedisPlugin(name, redisClient);
|
return new RedisPlugin(name, redisClient, prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(): Promise<Redis> {
|
async get(key: string): Promise<string | null> {
|
||||||
return this.redisClient;
|
return JSON.parse(await this.redisClient.get(`${this.prefix}:${key}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
async set(key: string, value: any): Promise<void> {
|
||||||
|
await this.redisClient.set(`${this.prefix}:${key}`, JSON.stringify(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
async del(key: string): Promise<void> {
|
||||||
|
await this.redisClient.del(`${this.prefix}:${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async exists(key: string): Promise<boolean> {
|
||||||
|
const result = await this.redisClient.exists(`${this.prefix}:${key}`);
|
||||||
|
return result === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
async lpop(key: string): Promise<string | null> {
|
||||||
|
return this.redisClient.lpop(`${this.prefix}:${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async rpush(key: string, value: any): Promise<number> {
|
||||||
|
return this.redisClient.rpush(
|
||||||
|
`${this.prefix}:${key}`,
|
||||||
|
JSON.stringify(value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async llen(key: string): Promise<number> {
|
||||||
|
return this.redisClient.llen(`${this.prefix}:${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async lrange(key: string, start: number, stop: number): Promise<string[]> {
|
||||||
|
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<void> {
|
||||||
|
await this.redisClient.ltrim(`${this.prefix}:${key}`, start, stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
async lpush(key: string, value: any): Promise<number> {
|
||||||
|
return this.redisClient.lpush(
|
||||||
|
`${this.prefix}:${key}`,
|
||||||
|
JSON.stringify(value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async rpop(key: string): Promise<string | null> {
|
||||||
|
return this.redisClient.rpop(`${this.prefix}:${key}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFinish(): void {
|
onFinish(): void {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import * as ivm from "isolated-vm";
|
import * as ivm from "isolated-vm";
|
||||||
import { VModule } from "./module.class";
|
import { VModule } from "./module.class";
|
||||||
import { Plugin } from "./plugin.class";
|
import { Plugin } from "./plugin.class";
|
||||||
|
import { QueryResponse } from "./vm.constants";
|
||||||
|
|
||||||
export class Vm {
|
export class Vm {
|
||||||
private memoryLimit: number;
|
private memoryLimit: number;
|
||||||
@ -32,26 +33,49 @@ export class Vm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const plugin of this.plugins) {
|
for (const plugin of this.plugins) {
|
||||||
this.jail.setSync(
|
const pluginName = plugin.getName();
|
||||||
plugin.getName(),
|
|
||||||
new ivm.Reference(async (...args) => {
|
await this.context.evalClosure(
|
||||||
return await plugin.run(...args);
|
"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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setFunction(name: string, func: (...args) => any) {
|
setFunction(name: string, func: (...args) => any) {
|
||||||
this.jail.setSync(name, func);
|
this.jail.setSync(name, func, { arguments: { copy: true } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async runScript(
|
async runScript(
|
||||||
script: string,
|
script: string,
|
||||||
args: Record<string, any>,
|
args: Record<string, any>,
|
||||||
headers: Record<string, any>
|
headers: Record<string, any>
|
||||||
): Promise<any> {
|
): Promise<QueryResponse> {
|
||||||
let resolvePromise: (value: any) => void;
|
let resolvePromise: (value: any) => void;
|
||||||
let rejectPromise: (reason?: any) => void;
|
let rejectPromise: (reason?: any) => void;
|
||||||
|
|
||||||
@ -63,7 +87,7 @@ export class Vm {
|
|||||||
this.setFunction("returnResult", (res) => {
|
this.setFunction("returnResult", (res) => {
|
||||||
console.log("Returning result from VM:", res);
|
console.log("Returning result from VM:", res);
|
||||||
|
|
||||||
resolvePromise(JSON.parse(res));
|
resolvePromise(res);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: log
|
// TODO: log
|
||||||
@ -83,7 +107,7 @@ export class Vm {
|
|||||||
const result = await main(${JSON.stringify(
|
const result = await main(${JSON.stringify(
|
||||||
args
|
args
|
||||||
)}, ${JSON.stringify(headers)});
|
)}, ${JSON.stringify(headers)});
|
||||||
returnResult(JSON.stringify(result))
|
returnResult(result)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error(e)
|
error(e)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,6 @@ export const registeredPlugins = {
|
|||||||
|
|
||||||
export const registeredModules = {
|
export const registeredModules = {
|
||||||
squel: "dist/vm/modules/squel.js",
|
squel: "dist/vm/modules/squel.js",
|
||||||
asyncCall: "dist/vm/modules/async.js",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type QueryResponse = {
|
export type QueryResponse = {
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
/* eslint-disable no-undef */
|
/* eslint-disable no-undef */
|
||||||
|
|
||||||
import "module/squel";
|
import "module/squel";
|
||||||
import "module/asyncCall";
|
|
||||||
import "plugin/db";
|
import "plugin/db";
|
||||||
|
|
||||||
function createSQL(id) {
|
function createSQL(id) {
|
||||||
@ -11,9 +10,21 @@ function createSQL(id) {
|
|||||||
|
|
||||||
async function main(input, headers) {
|
async function main(input, headers) {
|
||||||
const sql = createSQL(input.id);
|
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" },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user