From 174dbbcdba416097fa16459ad20e3053eb09f73d Mon Sep 17 00:00:00 2001 From: lborv Date: Thu, 9 Oct 2025 19:54:08 +0300 Subject: [PATCH] feat: integrate RedisNode management into Project and Query services; enhance RedisNodeService with optimal node selection and connection options; update vm.constants to include RedisPlugin --- .../databaseNode/database.node.service.ts | 8 ++- src/project/entities/project.entity.ts | 6 +++ src/project/project.service.ts | 6 +++ src/query/executer/query.executer.service.ts | 2 + src/query/query.module.ts | 2 + .../entities/redis.node.entity.ts | 13 ++++- src/redisManager/redisManager.module.ts | 7 ++- .../redisNode/redis.node.service.ts | 53 +++++++++++++++++-- src/vm/vm.constants.ts | 12 +++++ 9 files changed, 97 insertions(+), 12 deletions(-) diff --git a/src/databaseManager/databaseNode/database.node.service.ts b/src/databaseManager/databaseNode/database.node.service.ts index c0ec4e9..5d1835d 100644 --- a/src/databaseManager/databaseNode/database.node.service.ts +++ b/src/databaseManager/databaseNode/database.node.service.ts @@ -27,11 +27,9 @@ export class DatabaseNodeService extends DatabaseEncryptionService { return null; } - return nodes.reduce((optimalNode, currentNode) => { - const currentCount = currentNode.databases?.length || 0; - const optimalCount = optimalNode.databases?.length || 0; - return currentCount < optimalCount ? currentNode : optimalNode; - }); + nodes.sort((a, b) => a.databases.length - b.databases.length); + + return nodes[0]; } async initDatabase( diff --git a/src/project/entities/project.entity.ts b/src/project/entities/project.entity.ts index e2a9ee3..2b2893c 100644 --- a/src/project/entities/project.entity.ts +++ b/src/project/entities/project.entity.ts @@ -4,12 +4,14 @@ import { Column, Entity, JoinColumn, + ManyToMany, OneToMany, OneToOne, PrimaryGeneratedColumn, } from "typeorm"; import { Database } from "../../databaseManager/entities/database.entity"; import { FunctionEntity } from "../../query/entities/function.entity"; +import { RedisNode } from "../../redisManager/entities/redis.node.entity"; @Entity("project") export class Project { @@ -31,4 +33,8 @@ export class Project { @OneToMany(() => FunctionEntity, (functionEntity) => functionEntity.project) functions: FunctionEntity[]; + + @ManyToMany(() => RedisNode, (redisNode) => redisNode.projects) + @JoinColumn() + redisNodes: RedisNode[]; } diff --git a/src/project/project.service.ts b/src/project/project.service.ts index a5e2332..7c8a9f5 100644 --- a/src/project/project.service.ts +++ b/src/project/project.service.ts @@ -24,4 +24,10 @@ export class ProjectService { database: { id: databaseId }, }); } + + updateRedisNode(projectId: string, redisNodeId: { id: string }[]) { + return this.projectRepository.update(projectId, { + redisNodes: redisNodeId, + }); + } } diff --git a/src/query/executer/query.executer.service.ts b/src/query/executer/query.executer.service.ts index 1d0913a..c96d617 100644 --- a/src/query/executer/query.executer.service.ts +++ b/src/query/executer/query.executer.service.ts @@ -1,3 +1,4 @@ +import { RedisNodeService } from "./../../redisManager/redisNode/redis.node.service"; import { Inject, Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { Query } from "../entities/query.entity"; @@ -25,6 +26,7 @@ export class QueryExecuterService { @Inject(FunctionService) private readonly functionService: FunctionService, readonly databaseManagerService: DatabaseManagerService, + readonly redisNodeService: RedisNodeService, @InjectQueue(QUEUE_NAMES.QUERY) private queryQueue: Queue ) { this.queueEvents = new QueueEvents(this.queryQueue.name); diff --git a/src/query/query.module.ts b/src/query/query.module.ts index 632af81..28ad62a 100644 --- a/src/query/query.module.ts +++ b/src/query/query.module.ts @@ -12,6 +12,7 @@ import { QueueModule } from "src/queue/queue.module"; import { FunctionEntity } from "./entities/function.entity"; import { FunctionService } from "src/project/function/function.service"; import { FunctionController } from "src/project/function/function.controller"; +import { RedisManagerModule } from "src/redisManager/redisManager.module"; @Module({ imports: [ @@ -19,6 +20,7 @@ import { FunctionController } from "src/project/function/function.controller"; forwardRef(() => DatabaseManagerModule), forwardRef(() => ApiModule), forwardRef(() => QueueModule), + forwardRef(() => RedisManagerModule), TypeOrmModule.forFeature([Query, FunctionEntity]), ], controllers: [QueryController, CommandController, FunctionController], diff --git a/src/redisManager/entities/redis.node.entity.ts b/src/redisManager/entities/redis.node.entity.ts index f312e44..b8cf757 100644 --- a/src/redisManager/entities/redis.node.entity.ts +++ b/src/redisManager/entities/redis.node.entity.ts @@ -1,4 +1,11 @@ -import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; +import { Project } from "../../project/entities/project.entity"; +import { + Column, + Entity, + JoinColumn, + ManyToMany, + PrimaryGeneratedColumn, +} from "typeorm"; @Entity("redisNode") export class RedisNode { @@ -16,4 +23,8 @@ export class RedisNode { @Column({ type: "varchar", length: 255 }) password: string | null; + + @ManyToMany(() => Project, (project) => project.redisNodes) + @JoinColumn() + projects: Project[]; } diff --git a/src/redisManager/redisManager.module.ts b/src/redisManager/redisManager.module.ts index 3f3e3a9..e6297ac 100644 --- a/src/redisManager/redisManager.module.ts +++ b/src/redisManager/redisManager.module.ts @@ -4,9 +4,14 @@ 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"; +import { ProjectModule } from "src/project/project.module"; @Module({ - imports: [forwardRef(() => ApiModule), TypeOrmModule.forFeature([RedisNode])], + imports: [ + forwardRef(() => ApiModule), + forwardRef(() => ProjectModule), + TypeOrmModule.forFeature([RedisNode]), + ], controllers: [RedisManagerController], providers: [RedisNodeService], exports: [RedisNodeService], diff --git a/src/redisManager/redisNode/redis.node.service.ts b/src/redisManager/redisNode/redis.node.service.ts index 13b7fb7..f162596 100644 --- a/src/redisManager/redisNode/redis.node.service.ts +++ b/src/redisManager/redisNode/redis.node.service.ts @@ -1,13 +1,16 @@ -import { Injectable } from "@nestjs/common"; +import { Inject, Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { RedisNode } from "../entities/redis.node.entity"; import { Repository } from "typeorm"; +import { ProjectService } from "src/project/project.service"; @Injectable() export class RedisNodeService { constructor( @InjectRepository(RedisNode) - private readonly redisNodeRepository: Repository + private readonly redisNodeRepository: Repository, + @Inject(ProjectService) + private readonly projectService: ProjectService ) {} async create( @@ -36,15 +39,55 @@ export class RedisNodeService { return this.redisNodeRepository.save(redisNode); } - async getConnectionOptions(id: string): Promise<{ + async findOptimalNode(): Promise { + const nodes = await this.redisNodeRepository.find({ + relations: ["projects"], + }); + + if (nodes.length === 0) { + return null; + } + + nodes.sort((a, b) => a.projects.length - b.projects.length); + + return nodes[0]; + } + + async getConnectionOptions(projectId: string): Promise<{ host: string; port: number; username: string; password: string; }> { - const node = await this.redisNodeRepository.findOne({ where: { id } }); + const project = await this.projectService.findById(projectId); + + if (!project) { + throw new Error("Project not found"); + } + + const node = project.redisNodes[0]; if (!node) { - throw new Error("Redis node not found"); + const newNode = await this.findOptimalNode(); + + if (!newNode) { + throw new Error("No Redis nodes available"); + } + + project.redisNodes.push(newNode); + newNode.projects.push(project); + await this.redisNodeRepository.save(newNode); + + await this.projectService.updateRedisNode( + project.id, + project.redisNodes.map((n) => ({ id: n.id })) + ); + + return { + host: newNode.host, + port: newNode.port, + username: newNode.user, + password: newNode.password, + }; } return { diff --git a/src/vm/vm.constants.ts b/src/vm/vm.constants.ts index bb9d09a..4413103 100644 --- a/src/vm/vm.constants.ts +++ b/src/vm/vm.constants.ts @@ -3,6 +3,7 @@ import { DatabasePlugin } from "./plugins/database.plugin"; import { Query } from "src/query/entities/query.entity"; import { QueryPlugin } from "./plugins/query.plugin"; import { AxiosPlugin } from "./plugins/axios.plugin"; +import { RedisPlugin } from "./plugins/redis.plugin"; export const registeredPlugins = { db: async (service: QueryExecuterService, query: Query) => { @@ -18,6 +19,17 @@ export const registeredPlugins = { return DatabasePlugin.init("db", databaseConnection); }, + redis: async (service: QueryExecuterService, query: Query) => { + const redisConnection = await service.redisNodeService.getConnectionOptions( + query.project.id + ); + + if (!redisConnection) { + throw new Error("Redis connection not found"); + } + + return RedisPlugin.init("redis", redisConnection, query.project.id); + }, axios: async () => { return AxiosPlugin.init("axios"); },