feat: enhance API and query handling with Redis caching; add QueryGuard for query validation; refactor services to utilize RedisClient for improved performance
This commit is contained in:
@ -10,6 +10,10 @@ export class ApiController {
|
|||||||
|
|
||||||
@Post("token/generate")
|
@Post("token/generate")
|
||||||
generateToken(@Body() body: { id: string }) {
|
generateToken(@Body() body: { id: string }) {
|
||||||
|
if (!body.id) {
|
||||||
|
throw new Error("Project ID is required");
|
||||||
|
}
|
||||||
|
|
||||||
return this.apiService.generateToken(body.id);
|
return this.apiService.generateToken(body.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Module } from "@nestjs/common";
|
import { forwardRef, Module } from "@nestjs/common";
|
||||||
import { TypeOrmModule } from "@nestjs/typeorm";
|
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||||
import { Token } from "./entities/token.entity";
|
import { Token } from "./entities/token.entity";
|
||||||
import { ProjectModule } from "../project/project.module";
|
import { ProjectModule } from "../project/project.module";
|
||||||
@ -6,11 +6,19 @@ import { ApiService } from "./api.service";
|
|||||||
import { ApiController } from "./api.controller";
|
import { ApiController } from "./api.controller";
|
||||||
import { Project } from "../project/entities/project.entity";
|
import { Project } from "../project/entities/project.entity";
|
||||||
import { ApiTokenGuard } from "./guards/api-token.guard";
|
import { ApiTokenGuard } from "./guards/api-token.guard";
|
||||||
|
import { RedisModule } from "src/redis/redis.module";
|
||||||
|
import { QueryGuard } from "./guards/query.guard";
|
||||||
|
import { QueryModule } from "src/query/query.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ProjectModule, TypeOrmModule.forFeature([Token, Project])],
|
imports: [
|
||||||
|
forwardRef(() => RedisModule),
|
||||||
|
forwardRef(() => QueryModule),
|
||||||
|
ProjectModule,
|
||||||
|
TypeOrmModule.forFeature([Token, Project]),
|
||||||
|
],
|
||||||
controllers: [ApiController],
|
controllers: [ApiController],
|
||||||
providers: [ApiService, ApiTokenGuard],
|
providers: [ApiService, ApiTokenGuard, QueryGuard],
|
||||||
exports: [ApiTokenGuard, TypeOrmModule],
|
exports: [ApiTokenGuard, ApiService, TypeOrmModule],
|
||||||
})
|
})
|
||||||
export class ApiModule {}
|
export class ApiModule {}
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
import { Inject, Injectable } from "@nestjs/common";
|
||||||
import { InjectRepository } from "@nestjs/typeorm";
|
import { InjectRepository } from "@nestjs/typeorm";
|
||||||
import { Token } from "./entities/token.entity";
|
import { Token } from "./entities/token.entity";
|
||||||
import { Repository } from "typeorm";
|
import { Repository } from "typeorm";
|
||||||
import { Project } from "../project/entities/project.entity";
|
import { Project } from "../project/entities/project.entity";
|
||||||
|
import { RedisClient } from "src/redis/redis.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApiService {
|
export class ApiService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Token)
|
@InjectRepository(Token)
|
||||||
private readonly tokenRepository: Repository<Token>,
|
private readonly tokenRepository: Repository<Token>,
|
||||||
|
@Inject(RedisClient)
|
||||||
|
private readonly redisClient: RedisClient,
|
||||||
@InjectRepository(Project)
|
@InjectRepository(Project)
|
||||||
private readonly projectRepository: Repository<Project>
|
private readonly projectRepository: Repository<Project>
|
||||||
) {}
|
) {}
|
||||||
@ -27,6 +29,25 @@ export class ApiService {
|
|||||||
return this.tokenRepository.save(token);
|
return this.tokenRepository.save(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getTokenDetails(tokenString: string) {
|
||||||
|
const cached = await this.redisClient.get(`token_${tokenString}`);
|
||||||
|
|
||||||
|
if (cached) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = await this.tokenRepository.findOne({
|
||||||
|
where: { token: tokenString },
|
||||||
|
relations: ["project"],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
await this.redisClient.set(`token_${token.token}`, token, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
async revokeToken(tokenString: string) {
|
async revokeToken(tokenString: string) {
|
||||||
const token = await this.tokenRepository.findOne({
|
const token = await this.tokenRepository.findOne({
|
||||||
where: { token: tokenString },
|
where: { token: tokenString },
|
||||||
@ -36,6 +57,8 @@ export class ApiService {
|
|||||||
throw new Error("Token not found");
|
throw new Error("Token not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.redisClient.del(`token_${tokenString}`);
|
||||||
|
|
||||||
token.isActive = false;
|
token.isActive = false;
|
||||||
return this.tokenRepository.save(token);
|
return this.tokenRepository.save(token);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ export class Token {
|
|||||||
@PrimaryGeneratedColumn("uuid")
|
@PrimaryGeneratedColumn("uuid")
|
||||||
token: string;
|
token: string;
|
||||||
|
|
||||||
@Column({ type: "tinyint", default: 0 })
|
@Column({ type: "tinyint", default: 1 })
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
|
||||||
@ManyToOne(() => Project, (project) => project.apiTokens)
|
@ManyToOne(() => Project, (project) => project.apiTokens)
|
||||||
|
|||||||
@ -1,34 +1,28 @@
|
|||||||
import {
|
import {
|
||||||
CanActivate,
|
CanActivate,
|
||||||
ExecutionContext,
|
ExecutionContext,
|
||||||
|
Inject,
|
||||||
Injectable,
|
Injectable,
|
||||||
UnauthorizedException,
|
UnauthorizedException,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import { InjectRepository } from "@nestjs/typeorm";
|
import { ApiService } from "../api.service";
|
||||||
import { Repository } from "typeorm";
|
|
||||||
import { Token } from "../entities/token.entity";
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApiTokenGuard implements CanActivate {
|
export class ApiTokenGuard implements CanActivate {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Token)
|
@Inject(ApiService)
|
||||||
private readonly tokenRepository: Repository<Token>
|
private readonly apiService: ApiService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
return true;
|
|
||||||
|
|
||||||
const request = context.switchToHttp().getRequest();
|
const request = context.switchToHttp().getRequest();
|
||||||
const token = request.params?.token || request.headers?.["x-api-token"];
|
const token = request.headers?.["x-api-token"];
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new UnauthorizedException("API token is required");
|
throw new UnauthorizedException("API token is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenEntity = await this.tokenRepository.findOne({
|
const tokenEntity = await this.apiService.getTokenDetails(token);
|
||||||
where: { token },
|
|
||||||
relations: ["project"],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!tokenEntity) {
|
if (!tokenEntity) {
|
||||||
throw new UnauthorizedException("Invalid API token");
|
throw new UnauthorizedException("Invalid API token");
|
||||||
@ -38,6 +32,8 @@ export class ApiTokenGuard implements CanActivate {
|
|||||||
throw new UnauthorizedException("API token is inactive");
|
throw new UnauthorizedException("API token is inactive");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
request.apiToken = tokenEntity;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
49
src/api/guards/query.guard.ts
Normal file
49
src/api/guards/query.guard.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import {
|
||||||
|
CanActivate,
|
||||||
|
ExecutionContext,
|
||||||
|
Inject,
|
||||||
|
Injectable,
|
||||||
|
UnauthorizedException,
|
||||||
|
} from "@nestjs/common";
|
||||||
|
import { QueryHandlerService } from "src/query/handler/query.handler.service";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class QueryGuard implements CanActivate {
|
||||||
|
constructor(
|
||||||
|
@Inject(QueryHandlerService)
|
||||||
|
private readonly queryHandlerService: QueryHandlerService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
const apiToken = request.apiToken;
|
||||||
|
|
||||||
|
if (!apiToken || !apiToken.project) {
|
||||||
|
throw new UnauthorizedException("Project not found for the API token");
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryId = request.params?.id;
|
||||||
|
|
||||||
|
if (!queryId) {
|
||||||
|
throw new UnauthorizedException("Query ID is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = await this.queryHandlerService.getQueryById(queryId);
|
||||||
|
|
||||||
|
if (!query) {
|
||||||
|
throw new UnauthorizedException("Query not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!query.isActive) {
|
||||||
|
throw new UnauthorizedException("Query is inactive");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.project.id !== apiToken.project.id) {
|
||||||
|
throw new UnauthorizedException("You do not have access to this query");
|
||||||
|
}
|
||||||
|
|
||||||
|
request.query = query;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,12 +9,14 @@ import { DatabaseManagerController } from "./database/database.manager.controlle
|
|||||||
import { DatabaseManagerService } from "./database/database.manager.service";
|
import { DatabaseManagerService } from "./database/database.manager.service";
|
||||||
import { DatabaseNodeService } from "./databaseNode/database.node.service";
|
import { DatabaseNodeService } from "./databaseNode/database.node.service";
|
||||||
import { ApiModule } from "src/api/api.module";
|
import { ApiModule } from "src/api/api.module";
|
||||||
|
import { RedisModule } from "src/redis/redis.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
forwardRef(() => ProjectModule),
|
forwardRef(() => ProjectModule),
|
||||||
forwardRef(() => MigrationModule),
|
forwardRef(() => MigrationModule),
|
||||||
forwardRef(() => ApiModule),
|
forwardRef(() => ApiModule),
|
||||||
|
forwardRef(() => RedisModule),
|
||||||
TypeOrmModule.forFeature([Database, DatabaseNode, Project]),
|
TypeOrmModule.forFeature([Database, DatabaseNode, Project]),
|
||||||
],
|
],
|
||||||
controllers: [DatabaseManagerController],
|
controllers: [DatabaseManagerController],
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
import { Inject, Injectable } from "@nestjs/common";
|
||||||
import { InjectRepository } from "@nestjs/typeorm";
|
import { InjectRepository } from "@nestjs/typeorm";
|
||||||
import { Database } from "../entities/database.entity";
|
import { Database } from "../entities/database.entity";
|
||||||
import { Repository } from "typeorm";
|
import { Repository } from "typeorm";
|
||||||
@ -6,6 +6,7 @@ import { ProjectService } from "src/project/project.service";
|
|||||||
import { DatabaseEncryptionService } from "../database.encryption.service";
|
import { DatabaseEncryptionService } from "../database.encryption.service";
|
||||||
import { DatabaseNodeService } from "../databaseNode/database.node.service";
|
import { DatabaseNodeService } from "../databaseNode/database.node.service";
|
||||||
import * as mysql from "mysql2/promise";
|
import * as mysql from "mysql2/promise";
|
||||||
|
import { RedisClient } from "src/redis/redis.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DatabaseManagerService extends DatabaseEncryptionService {
|
export class DatabaseManagerService extends DatabaseEncryptionService {
|
||||||
@ -13,7 +14,9 @@ export class DatabaseManagerService extends DatabaseEncryptionService {
|
|||||||
@InjectRepository(Database)
|
@InjectRepository(Database)
|
||||||
private databaseRepository: Repository<Database>,
|
private databaseRepository: Repository<Database>,
|
||||||
private readonly projectService: ProjectService,
|
private readonly projectService: ProjectService,
|
||||||
private readonly databaseNodeService: DatabaseNodeService
|
private readonly databaseNodeService: DatabaseNodeService,
|
||||||
|
@Inject(RedisClient)
|
||||||
|
private readonly redisClient: RedisClient
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@ -64,6 +67,14 @@ export class DatabaseManagerService extends DatabaseEncryptionService {
|
|||||||
throw new Error("Project not found");
|
throw new Error("Project not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cached = await this.redisClient.get(
|
||||||
|
`db_conn_opts_${projectId}_${queryUser}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cached) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
const database = await this.databaseRepository.findOne({
|
const database = await this.databaseRepository.findOne({
|
||||||
where: { project: { id: project.id } },
|
where: { project: { id: project.id } },
|
||||||
relations: ["node"],
|
relations: ["node"],
|
||||||
@ -73,7 +84,7 @@ export class DatabaseManagerService extends DatabaseEncryptionService {
|
|||||||
throw new Error("Database not found");
|
throw new Error("Database not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const connectionOptions = {
|
||||||
host: database.node.host,
|
host: database.node.host,
|
||||||
port: database.node.port,
|
port: database.node.port,
|
||||||
user: queryUser ? database.q_username : database.c_username,
|
user: queryUser ? database.q_username : database.c_username,
|
||||||
@ -82,6 +93,14 @@ export class DatabaseManagerService extends DatabaseEncryptionService {
|
|||||||
idleTimeout: 150e3,
|
idleTimeout: 150e3,
|
||||||
connectTimeout: 2e3,
|
connectTimeout: 2e3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
await this.redisClient.set(
|
||||||
|
`db_conn_opts_${projectId}_${queryUser}`,
|
||||||
|
connectionOptions,
|
||||||
|
300
|
||||||
|
);
|
||||||
|
|
||||||
|
return connectionOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createDatabase(projectId: string): Promise<Database> {
|
async createDatabase(projectId: string): Promise<Database> {
|
||||||
|
|||||||
@ -4,9 +4,14 @@ import { Project } from "./entities/project.entity";
|
|||||||
import { ProjectService } from "./project.service";
|
import { ProjectService } from "./project.service";
|
||||||
import { ProjectController } from "./project.controller";
|
import { ProjectController } from "./project.controller";
|
||||||
import { ApiModule } from "src/api/api.module";
|
import { ApiModule } from "src/api/api.module";
|
||||||
|
import { RedisModule } from "src/redis/redis.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [forwardRef(() => ApiModule), TypeOrmModule.forFeature([Project])],
|
imports: [
|
||||||
|
forwardRef(() => ApiModule),
|
||||||
|
forwardRef(() => RedisModule),
|
||||||
|
TypeOrmModule.forFeature([Project]),
|
||||||
|
],
|
||||||
controllers: [ProjectController],
|
controllers: [ProjectController],
|
||||||
providers: [ProjectService],
|
providers: [ProjectService],
|
||||||
exports: [ProjectService],
|
exports: [ProjectService],
|
||||||
|
|||||||
@ -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 { Repository } from "typeorm";
|
import { Repository } from "typeorm";
|
||||||
import { Project } from "./entities/project.entity";
|
import { Project } from "./entities/project.entity";
|
||||||
|
import { RedisClient } from "src/redis/redis.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ProjectService {
|
export class ProjectService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Project)
|
@InjectRepository(Project)
|
||||||
private readonly projectRepository: Repository<Project>
|
private readonly projectRepository: Repository<Project>,
|
||||||
|
@Inject(RedisClient)
|
||||||
|
private readonly redisClient: RedisClient
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
create(name: string) {
|
create(name: string) {
|
||||||
@ -15,17 +18,33 @@ export class ProjectService {
|
|||||||
return this.projectRepository.save(project);
|
return this.projectRepository.save(project);
|
||||||
}
|
}
|
||||||
|
|
||||||
findById(id: string) {
|
async findById(id: string) {
|
||||||
return this.projectRepository.findOne({ where: { id: id } });
|
const cached = await this.redisClient.get(`project_${id}`);
|
||||||
|
|
||||||
|
if (cached) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
const project = await this.projectRepository.findOne({ where: { id: id } });
|
||||||
|
|
||||||
|
if (project) {
|
||||||
|
await this.redisClient.set(`project_${id}`, project, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
return project;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDatabase(projectId: string, databaseId: string) {
|
async updateDatabase(projectId: string, databaseId: string) {
|
||||||
|
await this.redisClient.del(`project_${projectId}`);
|
||||||
|
|
||||||
return this.projectRepository.update(projectId, {
|
return this.projectRepository.update(projectId, {
|
||||||
database: { id: databaseId },
|
database: { id: databaseId },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRedisNode(projectId: string, redisNodeId: { id: string }[]) {
|
async updateRedisNode(projectId: string, redisNodeId: { id: string }[]) {
|
||||||
|
await this.redisClient.del(`project_${projectId}`);
|
||||||
|
|
||||||
return this.projectRepository.update(projectId, {
|
return this.projectRepository.update(projectId, {
|
||||||
redisNodes: redisNodeId,
|
redisNodes: redisNodeId,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { Response } from "express";
|
|||||||
import { QueryHandlerService } from "../handler/query.handler.service";
|
import { QueryHandlerService } from "../handler/query.handler.service";
|
||||||
import { ApiTokenGuard } from "src/api/guards/api-token.guard";
|
import { ApiTokenGuard } from "src/api/guards/api-token.guard";
|
||||||
import { QueryExecuterService } from "../executer/query.executer.service";
|
import { QueryExecuterService } from "../executer/query.executer.service";
|
||||||
|
import { QueryGuard } from "src/api/guards/query.guard";
|
||||||
|
|
||||||
@UseGuards(ApiTokenGuard)
|
@UseGuards(ApiTokenGuard)
|
||||||
export abstract class BaseQueryController {
|
export abstract class BaseQueryController {
|
||||||
@ -31,22 +32,24 @@ export abstract class BaseQueryController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post("update/:id")
|
@Post("update/:id")
|
||||||
|
@UseGuards(QueryGuard)
|
||||||
async updateQuery(
|
async updateQuery(
|
||||||
@Body() updateData: Partial<{ source: string }>,
|
@Body() updateData: Partial<{ source: string }>,
|
||||||
@Inject("id") id: string
|
@Param("id") id: string
|
||||||
) {
|
) {
|
||||||
return this.queryHandlerService.updateQuery(id, updateData);
|
return this.queryHandlerService.updateQuery(id, updateData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("/run/:token")
|
@Post("/run/:id")
|
||||||
|
@UseGuards(QueryGuard)
|
||||||
async runQuery(
|
async runQuery(
|
||||||
@Param("token") token: string,
|
@Param("id") id: string,
|
||||||
@Body() query: Record<string, any>,
|
@Body() query: Record<string, any>,
|
||||||
@Headers() headers: Record<string, any>,
|
@Headers() headers: Record<string, any>,
|
||||||
@Res() res: Response
|
@Res() res: Response
|
||||||
) {
|
) {
|
||||||
const queryResult = await this.queryExecuterService.runQueryQueued(
|
const queryResult = await this.queryExecuterService.runQueryQueued(
|
||||||
token,
|
id,
|
||||||
query,
|
query,
|
||||||
headers
|
headers
|
||||||
);
|
);
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { DatabaseManagerService } from "src/databaseManager/database/database.ma
|
|||||||
import { InjectQueue } from "@nestjs/bullmq";
|
import { InjectQueue } from "@nestjs/bullmq";
|
||||||
import { QUEUE_NAMES } from "src/queue/constants";
|
import { QUEUE_NAMES } from "src/queue/constants";
|
||||||
import { Queue, QueueEvents } from "bullmq";
|
import { Queue, QueueEvents } from "bullmq";
|
||||||
import { FunctionService } from "src/project/function/function.service";
|
import { FunctionService } from "src/query/function/function.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QueryExecuterService {
|
export class QueryExecuterService {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { Inject, Injectable } from "@nestjs/common";
|
|||||||
import { InjectRepository } from "@nestjs/typeorm";
|
import { InjectRepository } from "@nestjs/typeorm";
|
||||||
import { FunctionEntity } from "src/query/entities/function.entity";
|
import { FunctionEntity } from "src/query/entities/function.entity";
|
||||||
import { In, Repository } from "typeorm";
|
import { In, Repository } from "typeorm";
|
||||||
import { ProjectService } from "../project.service";
|
import { ProjectService } from "../../project/project.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FunctionService {
|
export class FunctionService {
|
||||||
@ -3,6 +3,7 @@ import { Repository } from "typeorm";
|
|||||||
import { Query } from "../entities/query.entity";
|
import { Query } from "../entities/query.entity";
|
||||||
import { InjectRepository } from "@nestjs/typeorm";
|
import { InjectRepository } from "@nestjs/typeorm";
|
||||||
import { ProjectService } from "src/project/project.service";
|
import { ProjectService } from "src/project/project.service";
|
||||||
|
import { RedisClient } from "src/redis/redis.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QueryHandlerService {
|
export class QueryHandlerService {
|
||||||
@ -10,7 +11,9 @@ export class QueryHandlerService {
|
|||||||
@InjectRepository(Query)
|
@InjectRepository(Query)
|
||||||
private readonly queryRepository: Repository<Query>,
|
private readonly queryRepository: Repository<Query>,
|
||||||
@Inject(ProjectService)
|
@Inject(ProjectService)
|
||||||
private readonly projectService: ProjectService
|
private readonly projectService: ProjectService,
|
||||||
|
@Inject(RedisClient)
|
||||||
|
private readonly redisClient: RedisClient
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async createQuery(
|
async createQuery(
|
||||||
@ -34,6 +37,25 @@ export class QueryHandlerService {
|
|||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getQueryById(id: string) {
|
||||||
|
const cached = await this.redisClient.get(`query_${id}`);
|
||||||
|
|
||||||
|
if (cached) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = await this.queryRepository.findOne({
|
||||||
|
where: { id },
|
||||||
|
relations: ["project"],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (query) {
|
||||||
|
await this.redisClient.set(`query_${id}`, query, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
async updateQuery(id: string, updateData: Partial<Query>) {
|
async updateQuery(id: string, updateData: Partial<Query>) {
|
||||||
const query = await this.queryRepository.findOne({ where: { id } });
|
const query = await this.queryRepository.findOne({ where: { id } });
|
||||||
|
|
||||||
@ -41,6 +63,8 @@ export class QueryHandlerService {
|
|||||||
throw new Error("Query not found");
|
throw new Error("Query not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.redisClient.del(`query_${id}`);
|
||||||
|
|
||||||
Object.assign(query, updateData);
|
Object.assign(query, updateData);
|
||||||
return this.queryRepository.save(query);
|
return this.queryRepository.save(query);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,9 +10,10 @@ import { CommandController } from "./command/command.controller";
|
|||||||
import { ApiModule } from "src/api/api.module";
|
import { ApiModule } from "src/api/api.module";
|
||||||
import { QueueModule } from "src/queue/queue.module";
|
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/query/function/function.service";
|
||||||
import { FunctionController } from "src/project/function/function.controller";
|
import { FunctionController } from "src/query/function/function.controller";
|
||||||
import { RedisManagerModule } from "src/redisManager/redisManager.module";
|
import { RedisManagerModule } from "src/redisManager/redisManager.module";
|
||||||
|
import { RedisModule } from "src/redis/redis.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -20,11 +21,12 @@ import { RedisManagerModule } from "src/redisManager/redisManager.module";
|
|||||||
forwardRef(() => DatabaseManagerModule),
|
forwardRef(() => DatabaseManagerModule),
|
||||||
forwardRef(() => ApiModule),
|
forwardRef(() => ApiModule),
|
||||||
forwardRef(() => QueueModule),
|
forwardRef(() => QueueModule),
|
||||||
|
forwardRef(() => RedisModule),
|
||||||
forwardRef(() => RedisManagerModule),
|
forwardRef(() => RedisManagerModule),
|
||||||
TypeOrmModule.forFeature([Query, FunctionEntity]),
|
TypeOrmModule.forFeature([Query, FunctionEntity]),
|
||||||
],
|
],
|
||||||
controllers: [QueryController, CommandController, FunctionController],
|
controllers: [QueryController, CommandController, FunctionController],
|
||||||
providers: [QueryExecuterService, QueryHandlerService, FunctionService],
|
providers: [QueryExecuterService, QueryHandlerService, FunctionService],
|
||||||
exports: [QueryExecuterService, TypeOrmModule],
|
exports: [QueryExecuterService, TypeOrmModule, QueryHandlerService],
|
||||||
})
|
})
|
||||||
export class QueryModule {}
|
export class QueryModule {}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export class RedisClient {
|
|||||||
|
|
||||||
async set(
|
async set(
|
||||||
key: string,
|
key: string,
|
||||||
value: string,
|
value: any,
|
||||||
expireInSeconds?: number
|
expireInSeconds?: number
|
||||||
): Promise<"OK" | null> {
|
): Promise<"OK" | null> {
|
||||||
if (!this.redis) {
|
if (!this.redis) {
|
||||||
@ -20,18 +20,23 @@ export class RedisClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (expireInSeconds) {
|
if (expireInSeconds) {
|
||||||
return await this.redis.set(key, value, "EX", expireInSeconds);
|
return await this.redis.set(
|
||||||
|
key,
|
||||||
|
JSON.stringify(value),
|
||||||
|
"EX",
|
||||||
|
expireInSeconds
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.redis.set(key, value);
|
return await this.redis.set(key, JSON.stringify(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(key: string): Promise<string | null> {
|
async get(key: string): Promise<any | null> {
|
||||||
if (!this.redis) {
|
if (!this.redis) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.redis.get(key);
|
return JSON.parse(await this.redis.get(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
async del(key: string): Promise<number | null> {
|
async del(key: string): Promise<number | null> {
|
||||||
|
|||||||
@ -10,33 +10,33 @@ import * as path from "path";
|
|||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const node = await createDatabaseNode("localhost", 3306, "root", "root");
|
// const node = await createDatabaseNode("localhost", 3306, "root", "root");
|
||||||
|
|
||||||
console.log("Database node created:", node);
|
// console.log("Database node created:", node);
|
||||||
|
|
||||||
const project = await createProject("Test Project");
|
// const project = await createProject("Test Project");
|
||||||
|
|
||||||
console.log("Project created:", project);
|
// console.log("Project created:", project);
|
||||||
|
|
||||||
const db = await createDatabase(project.id, node.id);
|
// const db = await createDatabase(project.id, node.id);
|
||||||
|
|
||||||
console.log("Database created:", db);
|
// console.log("Database created:", db);
|
||||||
|
|
||||||
const migration = await createMigration(
|
// const migration = await createMigration(
|
||||||
db.id,
|
// db.id,
|
||||||
"CREATE TABLE `test` (id INT AUTO_INCREMENT PRIMARY KEY, col1 VARCHAR(255))",
|
// "CREATE TABLE `test` (id INT AUTO_INCREMENT PRIMARY KEY, col1 VARCHAR(255))",
|
||||||
"DROP TABLE `test`"
|
// "DROP TABLE `test`"
|
||||||
);
|
// );
|
||||||
|
|
||||||
console.log("Migration created:", migration);
|
// console.log("Migration created:", migration);
|
||||||
|
|
||||||
const migrationResult = await databaseMigrationUp(db.id);
|
// const migrationResult = await databaseMigrationUp(db.id);
|
||||||
|
|
||||||
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(
|
||||||
project,
|
{ token: "04c38f93-f2fb-4d2c-a8e2-791effa35239" },
|
||||||
fs.readFileSync(payloadPath, { encoding: "utf-8" })
|
fs.readFileSync(payloadPath, { encoding: "utf-8" })
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -3,10 +3,14 @@ import { config } from "../config";
|
|||||||
|
|
||||||
export default async (project: { token: string }, source: string) => {
|
export default async (project: { token: string }, source: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(`${config.url}/query/create`, {
|
const response = await axios.post(
|
||||||
source,
|
`${config.url}/query/create`,
|
||||||
projectToken: project.token,
|
{
|
||||||
});
|
source,
|
||||||
|
projectToken: project.token,
|
||||||
|
},
|
||||||
|
{ headers: { "x-api-token": "efbeccd6-dde1-47dc-b3aa-4fbd773d5429" } }
|
||||||
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating query:", error);
|
console.error("Error creating query:", error);
|
||||||
|
|||||||
@ -5,7 +5,8 @@ export default async (token: string, queryData: Record<string, any>) => {
|
|||||||
try {
|
try {
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
`${config.url}/query/run/${token}`,
|
`${config.url}/query/run/${token}`,
|
||||||
queryData
|
queryData,
|
||||||
|
{ headers: { "x-api-token": "efbeccd6-dde1-47dc-b3aa-4fbd773d5429" } }
|
||||||
);
|
);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
|
|||||||
Reference in New Issue
Block a user