Refactor code structure for improved readability and maintainability
This commit is contained in:
20
src/api/api.controller.ts
Normal file
20
src/api/api.controller.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Body, Controller, Delete, Inject, Post } from "@nestjs/common";
|
||||
import { ApiService } from "./api.service";
|
||||
|
||||
@Controller("api")
|
||||
export class ApiController {
|
||||
constructor(
|
||||
@Inject(ApiService)
|
||||
private readonly apiService: ApiService
|
||||
) {}
|
||||
|
||||
@Post("token/generate")
|
||||
generateToken(@Body() body: { token: string }) {
|
||||
return this.apiService.generateToken(body.token);
|
||||
}
|
||||
|
||||
@Delete("token/revoke")
|
||||
revokeToken(@Body() body: { token: string }) {
|
||||
return this.apiService.revokeToken(body.token);
|
||||
}
|
||||
}
|
||||
14
src/api/api.module.ts
Normal file
14
src/api/api.module.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||
import { Token } from "./entities/token.entity";
|
||||
import { ProjectModule } from "src/project/project.module";
|
||||
import { ApiService } from "./api.service";
|
||||
import { ApiController } from "./api.controller";
|
||||
import { Project } from "src/project/entities/project.entity";
|
||||
|
||||
@Module({
|
||||
imports: [ProjectModule, TypeOrmModule.forFeature([Token, Project])],
|
||||
controllers: [ApiController],
|
||||
providers: [ApiService],
|
||||
})
|
||||
export class ApiModule {}
|
||||
42
src/api/api.service.ts
Normal file
42
src/api/api.service.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Token } from "./entities/token.entity";
|
||||
import { Repository } from "typeorm";
|
||||
import { Project } from "src/project/entities/project.entity";
|
||||
|
||||
@Injectable()
|
||||
export class ApiService {
|
||||
constructor(
|
||||
@InjectRepository(Token)
|
||||
private readonly tokenRepository: Repository<Token>,
|
||||
|
||||
@InjectRepository(Project)
|
||||
private readonly projectRepository: Repository<Project>
|
||||
) {}
|
||||
|
||||
async generateToken(projectToken: string) {
|
||||
const project = await this.projectRepository.findOne({
|
||||
where: { token: projectToken },
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
throw new Error("Project not found");
|
||||
}
|
||||
|
||||
const token = this.tokenRepository.create({ project });
|
||||
return this.tokenRepository.save(token);
|
||||
}
|
||||
|
||||
async revokeToken(tokenString: string) {
|
||||
const token = await this.tokenRepository.findOne({
|
||||
where: { token: tokenString },
|
||||
});
|
||||
|
||||
if (!token) {
|
||||
throw new Error("Token not found");
|
||||
}
|
||||
|
||||
token.isActive = false;
|
||||
return this.tokenRepository.save(token);
|
||||
}
|
||||
}
|
||||
14
src/api/entities/token.entity.ts
Normal file
14
src/api/entities/token.entity.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Project } from "src/project/entities/project.entity";
|
||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity("token")
|
||||
export class Token {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
token: string;
|
||||
|
||||
@Column({ type: "tinyint", default: 0 })
|
||||
isActive: boolean;
|
||||
|
||||
@ManyToOne(() => Project, (project) => project.apiTokens)
|
||||
project: Project;
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||
import { ConfigModule } from "@nestjs/config";
|
||||
import { User } from "../entities/user.entity";
|
||||
import { UserService } from "../services/user.service";
|
||||
import { TestModule } from "src/test/test.module";
|
||||
import { ApiModule } from "src/api/api.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -19,13 +19,14 @@ import { UserService } from "../services/user.service";
|
||||
database: process.env.DB_DATABASE || "low_code_engine",
|
||||
entities: [__dirname + "/../**/*.entity{.ts,.js}"],
|
||||
migrations: [__dirname + "/../migrations/*{.ts,.js}"],
|
||||
synchronize: process.env.NODE_ENV === "development",
|
||||
synchronize: false,
|
||||
migrationsRun: process.env.NODE_ENV === "production",
|
||||
autoLoadEntities: true,
|
||||
}),
|
||||
TypeOrmModule.forFeature([User]),
|
||||
ApiModule,
|
||||
TestModule,
|
||||
],
|
||||
controllers: [],
|
||||
providers: [UserService],
|
||||
providers: [],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
} from "typeorm";
|
||||
|
||||
@Entity("users")
|
||||
export class User {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ unique: true })
|
||||
email: string;
|
||||
|
||||
@Column()
|
||||
firstName: string;
|
||||
|
||||
@Column()
|
||||
lastName: string;
|
||||
|
||||
@Column({ default: true })
|
||||
isActive: boolean;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
}
|
||||
@ -3,7 +3,9 @@ import { AppModule } from "./app/app.module";
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
await app.listen(3000);
|
||||
|
||||
console.log(`Application is running on: ${process.env.PORT}`);
|
||||
await app.listen(process.env.PORT);
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class Init1757413588655 implements MigrationInterface {
|
||||
name = 'Init1757413588655'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE \`users\` (\`id\` int NOT NULL AUTO_INCREMENT, \`email\` varchar(255) NOT NULL, \`firstName\` varchar(255) NOT NULL, \`lastName\` varchar(255) NOT NULL, \`isActive\` tinyint NOT NULL DEFAULT 1, \`createdAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updatedAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), UNIQUE INDEX \`IDX_97672ac88f789774dd47f7c8be\` (\`email\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX \`IDX_97672ac88f789774dd47f7c8be\` ON \`users\``);
|
||||
await queryRunner.query(`DROP TABLE \`users\``);
|
||||
}
|
||||
|
||||
}
|
||||
15
src/migrations/1758026710027-apiTokensInit.ts
Normal file
15
src/migrations/1758026710027-apiTokensInit.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class ApiTokensInit1758026710027 implements MigrationInterface {
|
||||
name = "ApiTokensInit1758026710027";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE \`token\` (\`id\` varchar(36) NOT NULL, \`isActive\` tinyint NOT NULL DEFAULT '0', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE \`token\``);
|
||||
}
|
||||
}
|
||||
18
src/project/entities/project.entity.ts
Normal file
18
src/project/entities/project.entity.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Token } from "src/api/entities/token.entity";
|
||||
import { Query } from "src/query/entities/query.enitity";
|
||||
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity("project")
|
||||
export class Project {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
token: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255 })
|
||||
name: string;
|
||||
|
||||
@OneToMany(() => Token, (token) => token.project)
|
||||
apiTokens: Token[];
|
||||
|
||||
@OneToMany(() => Query, (query) => query.project)
|
||||
queries: Query[];
|
||||
}
|
||||
15
src/project/project.controller.ts
Normal file
15
src/project/project.controller.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Body, Controller, Inject, Put } from "@nestjs/common";
|
||||
import { ProjectService } from "./project.service";
|
||||
|
||||
@Controller("")
|
||||
export class ProjectController {
|
||||
constructor(
|
||||
@Inject(ProjectService)
|
||||
private readonly projectService: ProjectService
|
||||
) {}
|
||||
|
||||
@Put("project/create")
|
||||
createProject(@Body() body: { name: string }) {
|
||||
return this.projectService.create(body.name);
|
||||
}
|
||||
}
|
||||
11
src/project/project.module.ts
Normal file
11
src/project/project.module.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||
import { Project } from "./entities/project.entity";
|
||||
import { ProjectService } from "./project.service";
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Project])],
|
||||
controllers: [],
|
||||
providers: [ProjectService],
|
||||
})
|
||||
export class ProjectModule {}
|
||||
17
src/project/project.service.ts
Normal file
17
src/project/project.service.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { Project } from "./entities/project.entity";
|
||||
|
||||
@Injectable()
|
||||
export class ProjectService {
|
||||
constructor(
|
||||
@InjectRepository(Project)
|
||||
private readonly projectRepository: Repository<Project>
|
||||
) {}
|
||||
|
||||
create(name: string) {
|
||||
const project = this.projectRepository.create({ name });
|
||||
return this.projectRepository.save(project);
|
||||
}
|
||||
}
|
||||
20
src/query/entities/module.entity.ts
Normal file
20
src/query/entities/module.entity.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { Query } from "./query.enitity";
|
||||
|
||||
@Entity("module")
|
||||
export class Module {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255, nullable: false })
|
||||
sourcePath: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255 })
|
||||
name: string;
|
||||
|
||||
@Column({ type: "tinyint", default: 1 })
|
||||
isInjectable: number;
|
||||
|
||||
@ManyToMany(() => Query, (query) => query.modules)
|
||||
queries: Query[];
|
||||
}
|
||||
27
src/query/entities/query.enitity.ts
Normal file
27
src/query/entities/query.enitity.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { Project } from "src/project/entities/project.entity";
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
} from "typeorm";
|
||||
import { Module } from "./module.entity";
|
||||
|
||||
@Entity("query")
|
||||
export class Query {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id: string;
|
||||
|
||||
@ManyToOne(() => Project, (project) => project.queries)
|
||||
project: Project;
|
||||
|
||||
@Column({ type: "longtext" })
|
||||
source: string;
|
||||
|
||||
@Column({ type: "tinyint", default: 1 })
|
||||
isActive: number;
|
||||
|
||||
@ManyToMany(() => Module, (module) => module.queries)
|
||||
modules: Module[];
|
||||
}
|
||||
15
src/query/executer/query.executer.controller.ts
Normal file
15
src/query/executer/query.executer.controller.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Body, Controller, Inject, Param, Post } from "@nestjs/common";
|
||||
import { QueryExecuterService } from "./query.executer.service";
|
||||
|
||||
@Controller("query")
|
||||
export class QueryExecuterController {
|
||||
constructor(
|
||||
@Inject(QueryExecuterService)
|
||||
private readonly queryExecuterService: QueryExecuterService
|
||||
) {}
|
||||
|
||||
@Post("/run/:token")
|
||||
async runQuery(@Param("token") token: string, @Body() query: any) {
|
||||
return this.queryExecuterService.runQuery(token, query);
|
||||
}
|
||||
}
|
||||
41
src/query/executer/query.executer.service.ts
Normal file
41
src/query/executer/query.executer.service.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Query } from "../entities/query.enitity";
|
||||
import { Repository } from "typeorm";
|
||||
import { Vm } from "src/vm/vm.class";
|
||||
import { Module } from "src/vm/module.class";
|
||||
|
||||
@Injectable()
|
||||
export class QueryExecuterService {
|
||||
constructor(
|
||||
@InjectRepository(Query)
|
||||
private readonly queryRepository: Repository<Query>
|
||||
) {}
|
||||
|
||||
async runQuery(token: string, queryData: any) {
|
||||
const query = await this.queryRepository.findOne({
|
||||
where: { id: token },
|
||||
});
|
||||
|
||||
if (!query) {
|
||||
throw new Error("Query not found");
|
||||
}
|
||||
|
||||
const vm = this.createVm(query);
|
||||
|
||||
await vm.runScript(query.source);
|
||||
|
||||
// Here you would add the logic to actually execute the query
|
||||
// against your database or data source. This is a placeholder.
|
||||
return { message: "Query executed", query: queryData };
|
||||
}
|
||||
|
||||
private createVm(query: Query) {
|
||||
return new Vm({
|
||||
memoryLimit: 5,
|
||||
modules: query.modules.map((module) => {
|
||||
return new Module(module.name, module.sourcePath);
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
10
src/query/handler/query.handler.controller.ts
Normal file
10
src/query/handler/query.handler.controller.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Controller, Inject } from "@nestjs/common";
|
||||
import { QueryHandlerService } from "./query.handler.service";
|
||||
|
||||
@Controller("query")
|
||||
export class QueryHandlerController {
|
||||
constructor(
|
||||
@Inject(QueryHandlerService)
|
||||
private readonly queryHandlerService: QueryHandlerService
|
||||
) {}
|
||||
}
|
||||
4
src/query/handler/query.handler.service.ts
Normal file
4
src/query/handler/query.handler.service.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class QueryHandlerService {}
|
||||
8
src/query/query.module.ts
Normal file
8
src/query/query.module.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
controllers: [],
|
||||
providers: [],
|
||||
})
|
||||
export class QueryModule {}
|
||||
@ -1,34 +0,0 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { User } from "../entities/user.entity";
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
constructor(
|
||||
@InjectRepository(User)
|
||||
private userRepository: Repository<User>
|
||||
) {}
|
||||
|
||||
async findAll(): Promise<User[]> {
|
||||
return this.userRepository.find();
|
||||
}
|
||||
|
||||
async findOne(id: number): Promise<User> {
|
||||
return this.userRepository.findOne({ where: { id } });
|
||||
}
|
||||
|
||||
async create(userData: Partial<User>): Promise<User> {
|
||||
const user = this.userRepository.create(userData);
|
||||
return this.userRepository.save(user);
|
||||
}
|
||||
|
||||
async update(id: number, userData: Partial<User>): Promise<User> {
|
||||
await this.userRepository.update(id, userData);
|
||||
return this.findOne(id);
|
||||
}
|
||||
|
||||
async remove(id: number): Promise<void> {
|
||||
await this.userRepository.delete(id);
|
||||
}
|
||||
}
|
||||
81
src/test/test.controller.ts
Normal file
81
src/test/test.controller.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { Controller, Get } from "@nestjs/common";
|
||||
import * as ivm from "isolated-vm";
|
||||
import * as fs from "fs";
|
||||
|
||||
@Controller("test")
|
||||
export class TestController {
|
||||
@Get()
|
||||
async test() {
|
||||
const isolate = new ivm.Isolate({ memoryLimit: 128 });
|
||||
|
||||
const context = isolate.createContextSync();
|
||||
const jail = context.global;
|
||||
|
||||
await jail.set("global", jail.derefInto());
|
||||
|
||||
jail.setSync("log", function (...args) {
|
||||
console.log(...args);
|
||||
});
|
||||
|
||||
const squelSource = fs.readFileSync(
|
||||
"node_modules/squel/dist/squel.min.js",
|
||||
"utf8"
|
||||
);
|
||||
|
||||
await context.eval(squelSource);
|
||||
|
||||
jail.setSync(
|
||||
"testAsync",
|
||||
new ivm.Reference(async (arg) => {
|
||||
const result = await this.mockAsync(arg);
|
||||
return result;
|
||||
})
|
||||
);
|
||||
|
||||
jail.setSync("result", (query) => {
|
||||
console.log("final", query);
|
||||
});
|
||||
|
||||
const hostile = isolate.compileScriptSync(`
|
||||
async function main() {
|
||||
async function asyncCall(reference, arg) {
|
||||
return await reference.apply(undefined, [arg], { result: { promise: true } });
|
||||
}
|
||||
|
||||
const a = await asyncCall(testAsync, 'testArg');
|
||||
|
||||
const query = squel.select().from("users").where("id = ?", a);
|
||||
|
||||
log(a);
|
||||
log('testMe');
|
||||
return query.toString();
|
||||
};
|
||||
|
||||
(async () => {
|
||||
const resultQuery = await main();
|
||||
result(resultQuery);
|
||||
})();
|
||||
`);
|
||||
|
||||
hostile
|
||||
.run(context)
|
||||
.catch((err) => console.error(err))
|
||||
.then((result) => {
|
||||
console.log("Script executed successfully", result);
|
||||
});
|
||||
|
||||
return "Test successful!";
|
||||
}
|
||||
|
||||
mockAsync(arg: any) {
|
||||
console.log(arg);
|
||||
|
||||
const result = new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve("Mock async result");
|
||||
}, 1);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
9
src/test/test.module.ts
Normal file
9
src/test/test.module.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { TestController } from "./test.controller";
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
controllers: [TestController],
|
||||
providers: [],
|
||||
})
|
||||
export class TestModule {}
|
||||
19
src/vm/module.class.ts
Normal file
19
src/vm/module.class.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import * as fs from "fs";
|
||||
|
||||
export class Module {
|
||||
private name: string;
|
||||
private source: string;
|
||||
|
||||
constructor(name: string, sourcePath: string) {
|
||||
this.name = name;
|
||||
this.source = fs.readFileSync(sourcePath, "utf-8");
|
||||
}
|
||||
|
||||
getSource() {
|
||||
return this.source;
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
4
src/vm/modules/async.js
Normal file
4
src/vm/modules/async.js
Normal file
@ -0,0 +1,4 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async function asyncCall(reference, args) {
|
||||
return await reference.apply(undefined, args, { result: { promise: true } });
|
||||
}
|
||||
31
src/vm/vm.class.ts
Normal file
31
src/vm/vm.class.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import * as ivm from "isolated-vm";
|
||||
import { Module } from "./module.class";
|
||||
|
||||
export class Vm {
|
||||
private memoryLimit: number;
|
||||
private modules: Module[];
|
||||
private context: any;
|
||||
private jail: any;
|
||||
|
||||
constructor(configs: { memoryLimit: number; modules: Module[] }) {
|
||||
this.memoryLimit = configs.memoryLimit;
|
||||
this.modules = configs.modules;
|
||||
}
|
||||
|
||||
async init() {
|
||||
const isolate = new ivm.Isolate({ memoryLimit: this.memoryLimit });
|
||||
this.context = isolate.createContext();
|
||||
this.jail = this.context.global;
|
||||
|
||||
this.jail.set("global", this.jail.derefInto());
|
||||
|
||||
for (const mod of this.modules) {
|
||||
this.jail.setSync(mod.getName(), mod.getSource());
|
||||
}
|
||||
}
|
||||
|
||||
async runScript(script: string) {
|
||||
const compiledScript = await this.context.isolate.compileScript(script);
|
||||
return compiledScript.run(this.context);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user