feat: integrate RedisNode management into Project and Query services; enhance RedisNodeService with optimal node selection and connection options; update vm.constants to include RedisPlugin

This commit is contained in:
lborv
2025-10-09 19:54:08 +03:00
parent c3189bb2df
commit 174dbbcdba
9 changed files with 97 additions and 12 deletions

View File

@ -27,11 +27,9 @@ export class DatabaseNodeService extends DatabaseEncryptionService {
return null; return null;
} }
return nodes.reduce((optimalNode, currentNode) => { nodes.sort((a, b) => a.databases.length - b.databases.length);
const currentCount = currentNode.databases?.length || 0;
const optimalCount = optimalNode.databases?.length || 0; return nodes[0];
return currentCount < optimalCount ? currentNode : optimalNode;
});
} }
async initDatabase( async initDatabase(

View File

@ -4,12 +4,14 @@ import {
Column, Column,
Entity, Entity,
JoinColumn, JoinColumn,
ManyToMany,
OneToMany, OneToMany,
OneToOne, OneToOne,
PrimaryGeneratedColumn, PrimaryGeneratedColumn,
} from "typeorm"; } from "typeorm";
import { Database } from "../../databaseManager/entities/database.entity"; import { Database } from "../../databaseManager/entities/database.entity";
import { FunctionEntity } from "../../query/entities/function.entity"; import { FunctionEntity } from "../../query/entities/function.entity";
import { RedisNode } from "../../redisManager/entities/redis.node.entity";
@Entity("project") @Entity("project")
export class Project { export class Project {
@ -31,4 +33,8 @@ export class Project {
@OneToMany(() => FunctionEntity, (functionEntity) => functionEntity.project) @OneToMany(() => FunctionEntity, (functionEntity) => functionEntity.project)
functions: FunctionEntity[]; functions: FunctionEntity[];
@ManyToMany(() => RedisNode, (redisNode) => redisNode.projects)
@JoinColumn()
redisNodes: RedisNode[];
} }

View File

@ -24,4 +24,10 @@ export class ProjectService {
database: { id: databaseId }, database: { id: databaseId },
}); });
} }
updateRedisNode(projectId: string, redisNodeId: { id: string }[]) {
return this.projectRepository.update(projectId, {
redisNodes: redisNodeId,
});
}
} }

View File

@ -1,3 +1,4 @@
import { RedisNodeService } from "./../../redisManager/redisNode/redis.node.service";
import { Inject, Injectable } from "@nestjs/common"; import { Inject, Injectable } 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";
@ -25,6 +26,7 @@ export class QueryExecuterService {
@Inject(FunctionService) @Inject(FunctionService)
private readonly functionService: FunctionService, private readonly functionService: FunctionService,
readonly databaseManagerService: DatabaseManagerService, readonly databaseManagerService: DatabaseManagerService,
readonly redisNodeService: RedisNodeService,
@InjectQueue(QUEUE_NAMES.QUERY) private queryQueue: Queue @InjectQueue(QUEUE_NAMES.QUERY) private queryQueue: Queue
) { ) {
this.queueEvents = new QueueEvents(this.queryQueue.name); this.queueEvents = new QueueEvents(this.queryQueue.name);

View File

@ -12,6 +12,7 @@ import { QueueModule } from "src/queue/queue.module";
import { FunctionEntity } from "./entities/function.entity"; import { FunctionEntity } from "./entities/function.entity";
import { FunctionService } from "src/project/function/function.service"; import { FunctionService } from "src/project/function/function.service";
import { FunctionController } from "src/project/function/function.controller"; import { FunctionController } from "src/project/function/function.controller";
import { RedisManagerModule } from "src/redisManager/redisManager.module";
@Module({ @Module({
imports: [ imports: [
@ -19,6 +20,7 @@ import { FunctionController } from "src/project/function/function.controller";
forwardRef(() => DatabaseManagerModule), forwardRef(() => DatabaseManagerModule),
forwardRef(() => ApiModule), forwardRef(() => ApiModule),
forwardRef(() => QueueModule), forwardRef(() => QueueModule),
forwardRef(() => RedisManagerModule),
TypeOrmModule.forFeature([Query, FunctionEntity]), TypeOrmModule.forFeature([Query, FunctionEntity]),
], ],
controllers: [QueryController, CommandController, FunctionController], controllers: [QueryController, CommandController, FunctionController],

View File

@ -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") @Entity("redisNode")
export class RedisNode { export class RedisNode {
@ -16,4 +23,8 @@ export class RedisNode {
@Column({ type: "varchar", length: 255 }) @Column({ type: "varchar", length: 255 })
password: string | null; password: string | null;
@ManyToMany(() => Project, (project) => project.redisNodes)
@JoinColumn()
projects: Project[];
} }

View File

@ -4,9 +4,14 @@ 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"; import { ApiModule } from "src/api/api.module";
import { ProjectModule } from "src/project/project.module";
@Module({ @Module({
imports: [forwardRef(() => ApiModule), TypeOrmModule.forFeature([RedisNode])], imports: [
forwardRef(() => ApiModule),
forwardRef(() => ProjectModule),
TypeOrmModule.forFeature([RedisNode]),
],
controllers: [RedisManagerController], controllers: [RedisManagerController],
providers: [RedisNodeService], providers: [RedisNodeService],
exports: [RedisNodeService], exports: [RedisNodeService],

View File

@ -1,13 +1,16 @@
import { Injectable } from "@nestjs/common"; import { Inject, Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm"; import { InjectRepository } from "@nestjs/typeorm";
import { RedisNode } from "../entities/redis.node.entity"; import { RedisNode } from "../entities/redis.node.entity";
import { Repository } from "typeorm"; import { Repository } from "typeorm";
import { ProjectService } from "src/project/project.service";
@Injectable() @Injectable()
export class RedisNodeService { export class RedisNodeService {
constructor( constructor(
@InjectRepository(RedisNode) @InjectRepository(RedisNode)
private readonly redisNodeRepository: Repository<RedisNode> private readonly redisNodeRepository: Repository<RedisNode>,
@Inject(ProjectService)
private readonly projectService: ProjectService
) {} ) {}
async create( async create(
@ -36,15 +39,55 @@ export class RedisNodeService {
return this.redisNodeRepository.save(redisNode); return this.redisNodeRepository.save(redisNode);
} }
async getConnectionOptions(id: string): Promise<{ async findOptimalNode(): Promise<RedisNode | null> {
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; host: string;
port: number; port: number;
username: string; username: string;
password: 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) { 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 { return {

View File

@ -3,6 +3,7 @@ import { DatabasePlugin } from "./plugins/database.plugin";
import { Query } from "src/query/entities/query.entity"; import { Query } from "src/query/entities/query.entity";
import { QueryPlugin } from "./plugins/query.plugin"; import { QueryPlugin } from "./plugins/query.plugin";
import { AxiosPlugin } from "./plugins/axios.plugin"; import { AxiosPlugin } from "./plugins/axios.plugin";
import { RedisPlugin } from "./plugins/redis.plugin";
export const registeredPlugins = { export const registeredPlugins = {
db: async (service: QueryExecuterService, query: Query) => { db: async (service: QueryExecuterService, query: Query) => {
@ -18,6 +19,17 @@ export const registeredPlugins = {
return DatabasePlugin.init("db", databaseConnection); 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 () => { axios: async () => {
return AxiosPlugin.init("axios"); return AxiosPlugin.init("axios");
}, },