Refactor code structure for improved readability and maintainability

This commit is contained in:
lborv
2025-09-17 17:02:03 +03:00
parent afb1f343e0
commit db58d6ecb1
28 changed files with 674 additions and 132 deletions

20
src/api/api.controller.ts Normal file
View 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
View 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
View 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);
}
}

View 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;
}

View File

@ -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 {}

View File

@ -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;
}

View File

@ -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();

View File

@ -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\``);
}
}

View 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\``);
}
}

View 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[];
}

View 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);
}
}

View 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 {}

View 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);
}
}

View 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[];
}

View 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[];
}

View 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);
}
}

View 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);
}),
});
}
}

View 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
) {}
}

View File

@ -0,0 +1,4 @@
import { Injectable } from "@nestjs/common";
@Injectable()
export class QueryHandlerService {}

View File

@ -0,0 +1,8 @@
import { Module } from "@nestjs/common";
@Module({
imports: [],
controllers: [],
providers: [],
})
export class QueryModule {}

View File

@ -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);
}
}

View 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
View 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
View 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
View 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
View 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);
}
}