feat: add isAdmin column to token entity; implement migration for isAdmin; enhance logging and error handling in query execution; update query plugin to support new logging structure
This commit is contained in:
@ -9,8 +9,8 @@ export class Token {
|
|||||||
@Column({ type: "tinyint", default: 1 })
|
@Column({ type: "tinyint", default: 1 })
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
|
||||||
// @Column({ type: "tinyint", default: 0 })
|
@Column({ type: "tinyint", default: 0 })
|
||||||
// isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
|
|
||||||
@ManyToOne(() => Project, (project) => project.apiTokens)
|
@ManyToOne(() => Project, (project) => project.apiTokens)
|
||||||
project: Project;
|
project: Project;
|
||||||
|
|||||||
15
src/migrations/1760191339215-adminToken.ts
Normal file
15
src/migrations/1760191339215-adminToken.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class AdminToken1760191339215 implements MigrationInterface {
|
||||||
|
name = "AdminToken1760191339215";
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE \`token\` ADD \`isAdmin\` tinyint NOT NULL DEFAULT '0'`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE \`token\` DROP COLUMN \`isAdmin\``);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,6 +15,7 @@ import { QueryExecuterService } from "../executer/query.executer.service";
|
|||||||
import { QueryGuard } from "src/query/guards/query.guard";
|
import { QueryGuard } from "src/query/guards/query.guard";
|
||||||
import { LoggerService } from "../logger/logger.service";
|
import { LoggerService } from "../logger/logger.service";
|
||||||
import { TLogType } from "../logger/logger.types";
|
import { TLogType } from "../logger/logger.types";
|
||||||
|
import { QueryResponse } from "src/vm/vm.constants";
|
||||||
|
|
||||||
@UseGuards(ApiTokenGuard)
|
@UseGuards(ApiTokenGuard)
|
||||||
export abstract class BaseQueryController {
|
export abstract class BaseQueryController {
|
||||||
@ -53,6 +54,7 @@ export abstract class BaseQueryController {
|
|||||||
@Headers() headers: Record<string, any>,
|
@Headers() headers: Record<string, any>,
|
||||||
@Res() res: Response
|
@Res() res: Response
|
||||||
) {
|
) {
|
||||||
|
let queryResult: QueryResponse;
|
||||||
const loggerTraceId =
|
const loggerTraceId =
|
||||||
headers["x-trace-id"] || LoggerService.generateTraceId();
|
headers["x-trace-id"] || LoggerService.generateTraceId();
|
||||||
const log = LoggerService.log(
|
const log = LoggerService.log(
|
||||||
@ -69,20 +71,35 @@ export abstract class BaseQueryController {
|
|||||||
{ content: "", type: TLogType.info, timeStamp: new Date().getTime() }
|
{ content: "", type: TLogType.info, timeStamp: new Date().getTime() }
|
||||||
);
|
);
|
||||||
|
|
||||||
const queryResult = await this.queryExecuterService.runQueryQueued(
|
try {
|
||||||
id,
|
queryResult = await this.queryExecuterService.runQueryQueued(
|
||||||
query,
|
id,
|
||||||
log,
|
query,
|
||||||
headers,
|
log,
|
||||||
headers.cookie.split("; ").reduce((acc, cookie) => {
|
headers,
|
||||||
const [key, value] = cookie.split("=");
|
headers.cookie.split("; ").reduce((acc, cookie) => {
|
||||||
acc[key] = value;
|
const [key, value] = cookie.split("=");
|
||||||
return acc;
|
acc[key] = value;
|
||||||
}, {})
|
return acc;
|
||||||
);
|
}, {})
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
log.content.push({
|
||||||
|
content: `Query execution failed: ${error.message}`,
|
||||||
|
type: TLogType.error,
|
||||||
|
timeStamp: new Date().getTime(),
|
||||||
|
});
|
||||||
|
log.endTime = new Date().getTime();
|
||||||
|
await this.loggerService.create(log.traceId, log);
|
||||||
|
res.status(500).send({ error: "Internal Server Error" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (queryResult?.log) {
|
if (queryResult?.log) {
|
||||||
queryResult.log.endTime = new Date().getTime();
|
queryResult.log.endTime = new Date().getTime();
|
||||||
|
const res = JSON.parse(JSON.stringify(queryResult));
|
||||||
|
delete res.log;
|
||||||
|
queryResult.log.response = res;
|
||||||
await this.loggerService.create(queryResult.log.traceId, queryResult.log);
|
await this.loggerService.create(queryResult.log.traceId, queryResult.log);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { RedisNodeService } from "./../../redisManager/redisNode/redis.node.service";
|
import { RedisNodeService } from "./../../redisManager/redisNode/redis.node.service";
|
||||||
import { Inject, Injectable } from "@nestjs/common";
|
import { Inject, Injectable, Logger } from "@nestjs/common";
|
||||||
import { InjectRepository } from "@nestjs/typeorm";
|
import { InjectRepository } from "@nestjs/typeorm";
|
||||||
import { Query } from "../entities/query.entity";
|
import { Query } from "../entities/query.entity";
|
||||||
import { Repository } from "typeorm";
|
import { Repository } from "typeorm";
|
||||||
@ -16,7 +16,8 @@ import { QUEUE_NAMES } from "src/queue/constants";
|
|||||||
import { Queue, QueueEvents } from "bullmq";
|
import { Queue, QueueEvents } from "bullmq";
|
||||||
import { FunctionService } from "src/query/function/function.service";
|
import { FunctionService } from "src/query/function/function.service";
|
||||||
import { SessionService } from "../session/session.service";
|
import { SessionService } from "../session/session.service";
|
||||||
import { TLog } from "../logger/logger.types";
|
import { TLog, TLogType } from "../logger/logger.types";
|
||||||
|
import { LoggerService } from "../logger/logger.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QueryExecuterService {
|
export class QueryExecuterService {
|
||||||
@ -104,12 +105,11 @@ export class QueryExecuterService {
|
|||||||
cookies["x-session-id"] = session.sessionId;
|
cookies["x-session-id"] = session.sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const vm = await this.createVm(query, cookies["x-session-id"]);
|
const vm = await this.createVm(query, log, cookies["x-session-id"]);
|
||||||
const result = await vm.runScript(
|
const result = await vm.runScript(
|
||||||
this.clearImports(query.source),
|
this.clearImports(query.source),
|
||||||
queryData,
|
queryData,
|
||||||
headers,
|
headers
|
||||||
log
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!this.checkResponse(result)) {
|
if (!this.checkResponse(result)) {
|
||||||
@ -129,7 +129,7 @@ export class QueryExecuterService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createVm(query: Query, sessionId: string = null) {
|
private async createVm(query: Query, log: TLog, sessionId: string = null) {
|
||||||
const imports = this.parseImports(query.source);
|
const imports = this.parseImports(query.source);
|
||||||
const importsParsed = imports.map((imp) => {
|
const importsParsed = imports.map((imp) => {
|
||||||
const item = imp.split("/");
|
const item = imp.split("/");
|
||||||
@ -179,6 +179,7 @@ export class QueryExecuterService {
|
|||||||
modules: modules,
|
modules: modules,
|
||||||
plugins: plugins,
|
plugins: plugins,
|
||||||
functions: functions,
|
functions: functions,
|
||||||
|
log,
|
||||||
});
|
});
|
||||||
|
|
||||||
return await vm.init();
|
return await vm.init();
|
||||||
|
|||||||
@ -9,7 +9,13 @@ export class Log {
|
|||||||
@Column({ type: "varchar", length: 255 })
|
@Column({ type: "varchar", length: 255 })
|
||||||
traceId: string;
|
traceId: string;
|
||||||
|
|
||||||
@Column({ type: "longtext" })
|
@Column({
|
||||||
|
type: "longtext",
|
||||||
|
transformer: {
|
||||||
|
to: (value: TLog) => JSON.stringify(value),
|
||||||
|
from: (value: string) => JSON.parse(value) as TLog,
|
||||||
|
},
|
||||||
|
})
|
||||||
content: TLog;
|
content: TLog;
|
||||||
|
|
||||||
@Column({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
|
@Column({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
|
import { TLog } from "src/query/logger/logger.types";
|
||||||
|
|
||||||
export abstract class Plugin {
|
export abstract class Plugin {
|
||||||
protected name: string;
|
protected name: string;
|
||||||
|
protected log: TLog;
|
||||||
|
protected cookies: Record<string, string>;
|
||||||
|
protected headers: Record<string, string>;
|
||||||
|
|
||||||
constructor(name: string, protected methods: string[] = []) {
|
constructor(name: string, protected methods: string[] = []) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@ -13,6 +18,18 @@ export abstract class Plugin {
|
|||||||
return this.methods;
|
return this.methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLog(log: TLog) {
|
||||||
|
this.log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
setHeaders(headers: Record<string, string>) {
|
||||||
|
this.headers = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCookies(cookies: Record<string, string>) {
|
||||||
|
this.cookies = cookies;
|
||||||
|
}
|
||||||
|
|
||||||
static init(...args: any[]): any {
|
static init(...args: any[]): any {
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export class DatabasePlugin extends Plugin {
|
|||||||
enableKeepAlive: true,
|
enableKeepAlive: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
await dbConnection.query("SET SESSION MAX_EXECUTION_TIME=2000;");
|
// await dbConnection.query("SET SESSION MAX_EXECUTION_TIME=2000;");
|
||||||
|
|
||||||
return new DatabasePlugin(name, dbConnection);
|
return new DatabasePlugin(name, dbConnection);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,11 +15,7 @@ export class QueryPlugin extends Plugin {
|
|||||||
return new QueryPlugin("query", query, queryExecuterService);
|
return new QueryPlugin("query", query, queryExecuterService);
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(data: {
|
async run(id: string, data: any): Promise<any> {
|
||||||
token: string;
|
|
||||||
queryData: any;
|
|
||||||
headers?: Record<string, any>;
|
|
||||||
}): Promise<any> {
|
|
||||||
const query = await this.QueryExecuterService.queryRepository.findOne({
|
const query = await this.QueryExecuterService.queryRepository.findOne({
|
||||||
where: { id: this.query.id },
|
where: { id: this.query.id },
|
||||||
});
|
});
|
||||||
@ -35,9 +31,11 @@ export class QueryPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return await this.QueryExecuterService.runQuery(
|
return await this.QueryExecuterService.runQuery(
|
||||||
query.id,
|
id,
|
||||||
data.queryData,
|
data,
|
||||||
data.headers
|
this.headers,
|
||||||
|
this.cookies,
|
||||||
|
this.log
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,9 @@ export class Vm {
|
|||||||
private isolate: ivm.Isolate;
|
private isolate: ivm.Isolate;
|
||||||
private timeLimit?: bigint;
|
private timeLimit?: bigint;
|
||||||
private cpuTimeLimit?: bigint;
|
private cpuTimeLimit?: bigint;
|
||||||
private sessionId: string | null;
|
private log: TLog;
|
||||||
|
private headers?: Record<string, string>;
|
||||||
|
private cookies?: Record<string, string>;
|
||||||
|
|
||||||
constructor(configs: {
|
constructor(configs: {
|
||||||
memoryLimit: number;
|
memoryLimit: number;
|
||||||
@ -25,6 +27,9 @@ export class Vm {
|
|||||||
modules: VModule[];
|
modules: VModule[];
|
||||||
plugins: Plugin[];
|
plugins: Plugin[];
|
||||||
functions: string[];
|
functions: string[];
|
||||||
|
log: TLog;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
cookies?: Record<string, string>;
|
||||||
}) {
|
}) {
|
||||||
this.memoryLimit = configs.memoryLimit;
|
this.memoryLimit = configs.memoryLimit;
|
||||||
this.modules = configs.modules;
|
this.modules = configs.modules;
|
||||||
@ -32,6 +37,9 @@ export class Vm {
|
|||||||
this.timeLimit = configs.timeLimit;
|
this.timeLimit = configs.timeLimit;
|
||||||
this.cpuTimeLimit = configs.cpuTimeLimit;
|
this.cpuTimeLimit = configs.cpuTimeLimit;
|
||||||
this.functions = configs.functions;
|
this.functions = configs.functions;
|
||||||
|
this.log = configs.log;
|
||||||
|
this.headers = configs.headers;
|
||||||
|
this.cookies = configs.cookies;
|
||||||
}
|
}
|
||||||
|
|
||||||
async init(): Promise<Vm> {
|
async init(): Promise<Vm> {
|
||||||
@ -60,7 +68,17 @@ export class Vm {
|
|||||||
|
|
||||||
for (const method of plugin.getMethods()) {
|
for (const method of plugin.getMethods()) {
|
||||||
const fnRef = new ivm.Reference(async (...args) => {
|
const fnRef = new ivm.Reference(async (...args) => {
|
||||||
return await plugin[method](...args);
|
plugin.setLog(this.log);
|
||||||
|
plugin.setHeaders(this.headers);
|
||||||
|
plugin.setCookies(this.cookies);
|
||||||
|
const result = await plugin[method](...args);
|
||||||
|
|
||||||
|
if (result && result.log) {
|
||||||
|
this.log = result.log;
|
||||||
|
delete result.log;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.context.evalClosure(
|
await this.context.evalClosure(
|
||||||
@ -86,8 +104,7 @@ export class Vm {
|
|||||||
async runScript(
|
async runScript(
|
||||||
script: string,
|
script: string,
|
||||||
args: Record<string, any>,
|
args: Record<string, any>,
|
||||||
headers: Record<string, any>,
|
headers: Record<string, any>
|
||||||
log: TLog
|
|
||||||
): Promise<QueryResponse> {
|
): Promise<QueryResponse> {
|
||||||
let resolvePromise: (value: any) => void;
|
let resolvePromise: (value: any) => void;
|
||||||
let rejectPromise: (reason?: any) => void;
|
let rejectPromise: (reason?: any) => void;
|
||||||
@ -98,13 +115,11 @@ export class Vm {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.setFunction("returnResult", (res) => {
|
this.setFunction("returnResult", (res) => {
|
||||||
resolvePromise({ ...res, log });
|
resolvePromise({ ...res, log: this.log });
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setFunction("log", (...args) => {
|
this.setFunction("log", (...args) => {
|
||||||
if (!log) {
|
console.log(...args);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const logType = args.find(
|
const logType = args.find(
|
||||||
(arg) =>
|
(arg) =>
|
||||||
@ -114,7 +129,7 @@ export class Vm {
|
|||||||
Object.values(TLogType).includes(arg.type)
|
Object.values(TLogType).includes(arg.type)
|
||||||
);
|
);
|
||||||
|
|
||||||
log = LoggerService.log(log, {
|
this.log = LoggerService.log(this.log, {
|
||||||
content: args
|
content: args
|
||||||
.map((arg) => (typeof arg === "string" ? arg : JSON.stringify(arg)))
|
.map((arg) => (typeof arg === "string" ? arg : JSON.stringify(arg)))
|
||||||
.join(" "),
|
.join(" "),
|
||||||
@ -124,7 +139,12 @@ export class Vm {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.setFunction("error", (error: any) => {
|
this.setFunction("error", (error: any) => {
|
||||||
console.error("Script error:", error);
|
LoggerService.log(this.log, {
|
||||||
|
content: error?.stack || error?.toString() || "Unknown error",
|
||||||
|
type: TLogType.error,
|
||||||
|
timeStamp: new Date().getTime(),
|
||||||
|
});
|
||||||
|
|
||||||
rejectPromise(error);
|
rejectPromise(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import "module/squel";
|
|||||||
import "plugin/db";
|
import "plugin/db";
|
||||||
import "plugin/axios";
|
import "plugin/axios";
|
||||||
import "plugin/session";
|
import "plugin/session";
|
||||||
|
import "plugin/query";
|
||||||
|
import { query } from "express";
|
||||||
|
|
||||||
function createSQL(id) {
|
function createSQL(id) {
|
||||||
return squel.select().from("test").where("id = ?", id).toString();
|
return squel.select().from("test").where("id = ?", id).toString();
|
||||||
@ -15,6 +17,8 @@ async function main(input, headers) {
|
|||||||
|
|
||||||
await db.query("START TRANSACTION");
|
await db.query("START TRANSACTION");
|
||||||
|
|
||||||
|
await query.run("d079b709-eba3-49be-9f84-08ac6a64a8a8", input);
|
||||||
|
|
||||||
// log(await db.query('insert into test (name) values ("Test")'));
|
// log(await db.query('insert into test (name) values ("Test")'));
|
||||||
|
|
||||||
log(new Date().toISOString());
|
log(new Date().toISOString());
|
||||||
@ -28,9 +32,9 @@ async function main(input, headers) {
|
|||||||
|
|
||||||
await session.set("test", 1);
|
await session.set("test", 1);
|
||||||
|
|
||||||
const res = await db.query(`
|
// const res = await db.query(`
|
||||||
SELECT SLEEP(10000);
|
// SELECT SLEEP(10000);
|
||||||
`);
|
// `);
|
||||||
|
|
||||||
log(new Date().toISOString());
|
log(new Date().toISOString());
|
||||||
|
|
||||||
|
|||||||
@ -2,11 +2,11 @@
|
|||||||
// import createDatabase from "../functions/createDatabase";
|
// import createDatabase from "../functions/createDatabase";
|
||||||
// import createDatabaseNode from "../functions/createDatabaseNode";
|
// import createDatabaseNode from "../functions/createDatabaseNode";
|
||||||
// import createProject from "../functions/createProject";
|
// import createProject from "../functions/createProject";
|
||||||
import createQuery from "../functions/createQuery";
|
// import createQuery from "../functions/createQuery";
|
||||||
// import databaseMigrationUp from "../functions/databaseMigrationUp";
|
// import databaseMigrationUp from "../functions/databaseMigrationUp";
|
||||||
import runQuery from "../functions/runQuery";
|
import runQuery from "../functions/runQuery";
|
||||||
import * as fs from "fs";
|
// import * as fs from "fs";
|
||||||
import * as path from "path";
|
// import * as path from "path";
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
@ -34,15 +34,17 @@ import * as path from "path";
|
|||||||
|
|
||||||
// console.log("Migrations applied:", migrationResult);
|
// console.log("Migrations applied:", migrationResult);
|
||||||
|
|
||||||
const payloadPath = path.join(__dirname, "case1-payload.js");
|
// const payloadPath = path.join(__dirname, "case1-payload.js");
|
||||||
const query = await createQuery(
|
// const query = await createQuery(
|
||||||
{ token: "04c38f93-f2fb-4d2c-a8e2-791effa35239" },
|
// { token: "c69c2c75-9b30-4aa5-9641-0a931f5aad40" },
|
||||||
fs.readFileSync(payloadPath, { encoding: "utf-8" })
|
// fs.readFileSync(payloadPath, { encoding: "utf-8" })
|
||||||
);
|
// );
|
||||||
|
|
||||||
console.log(query);
|
// console.log(query);
|
||||||
|
|
||||||
const result = await runQuery(query.id, { id: 1 });
|
const result = await runQuery("66e651f0-261b-4ebd-9749-077abffaddc2", {
|
||||||
|
id: 1,
|
||||||
|
});
|
||||||
|
|
||||||
console.log("Query Result:", result.data);
|
console.log("Query Result:", result.data);
|
||||||
console.log("headers:", result.headers);
|
console.log("headers:", result.headers);
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export default async (project: { token: string }, source: string) => {
|
|||||||
source,
|
source,
|
||||||
projectToken: project.token,
|
projectToken: project.token,
|
||||||
},
|
},
|
||||||
{ headers: { "x-api-token": "efbeccd6-dde1-47dc-b3aa-4fbd773d5429" } }
|
{ headers: { "x-api-token": "43c2e96e-af25-4467-9103-1479daa6288d" } }
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -8,8 +8,8 @@ export default async (token: string, queryData: Record<string, any>) => {
|
|||||||
queryData,
|
queryData,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"x-api-token": "efbeccd6-dde1-47dc-b3aa-4fbd773d5429",
|
"x-api-token": "43c2e96e-af25-4467-9103-1479daa6288d",
|
||||||
Cookie: `x-session-id=psaCHcYFMrt6RnUz_1760094020243`,
|
Cookie: `x-session-id=gTEd90aRJFmLzJKu_1760193754588`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user