diff --git a/.env example.docker b/.env example.docker deleted file mode 100644 index cf6422e..0000000 --- a/.env example.docker +++ /dev/null @@ -1,15 +0,0 @@ -# Docker Environment Configuration -NODE_ENV=development - -# Application Configuration -APP_PORT=3000 - -# Database Configuration -DB_HOST=mariadb -DB_PORT=3306 -DB_USERNAME=app_user -DB_PASSWORD=app_password -DB_DATABASE=low_code_engine -DB_ROOT_PASSWORD=rootpassword - -DB_PORT=3309 diff --git a/.env.example b/.env.example index 5831193..af91919 100644 --- a/.env.example +++ b/.env.example @@ -5,6 +5,9 @@ DB_USERNAME=root DB_PASSWORD=your_password_here DB_DATABASE=low_code_engine +REDIS_HOST=localhost +REDIS_PORT=6379 + # Application Configuration NODE_ENV=development PORT=3054 diff --git a/.gitea/workflows/deploy-testing.yml b/.gitea/workflows/deploy-testing.yml new file mode 100644 index 0000000..d8fa6c3 --- /dev/null +++ b/.gitea/workflows/deploy-testing.yml @@ -0,0 +1,53 @@ +name: Deploy to Testing Server + +on: + push: + branches: + - develop + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + name: Deploy to Testing Environment + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Deploy via SSH + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.SSH_HOST }} + username: ${{ secrets.SSH_USERNAME }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + port: ${{ secrets.SSH_PORT || 22 }} + script: | + # Navigate to project directory + cd ${{ secrets.PROJECT_PATH }} + + # Pull latest code + echo "πŸ”„ Pulling latest code from repository..." + git pull origin develop + + # Install dependencies + echo "πŸ“¦ Installing dependencies with yarn..." + yarn install --frozen-lockfile + + # Run database migrations + echo "πŸ—„οΈ Running database migrations..." + yarn migration:run + + # Build the project + echo "πŸ—οΈ Building the project..." + yarn build + + # Restart PM2 process + echo "πŸ”„ Restarting PM2 application..." + pm2 restart ${{ secrets.PM2_APP_NAME || 'low-code-engine' }} + + # Show PM2 status + echo "βœ… Deployment completed! PM2 status:" + pm2 status + + echo "πŸš€ Application deployed successfully!" diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml deleted file mode 100644 index a0008fe..0000000 --- a/.gitea/workflows/test.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Test Runner - -on: - push: - branches: - - main - - develop - -jobs: - hello-world: - runs-on: [ubuntu-latest] - steps: - - name: Test multiple commands - run: | - echo "Step 1 complete βœ…" - echo "Step 2 complete βœ…" - echo "All good!" - echo "Tests passed! πŸŽ‰" diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 9f3ce36..0000000 --- a/Dockerfile +++ /dev/null @@ -1,64 +0,0 @@ -# Multi-stage build for production optimization -FROM node:18-alpine AS development - -# Set working directory -WORKDIR /usr/src/app - -# Copy package files -COPY package*.json yarn.lock ./ - -# Install dependencies -RUN yarn install --frozen-lockfile - -# Copy source code -COPY . . - -# Expose port -EXPOSE 3000 - -# Development command -CMD ["yarn", "start:dev"] - -# Production build stage -FROM node:18-alpine AS build - -WORKDIR /usr/src/app - -# Copy package files -COPY package*.json yarn.lock ./ - -# Install dependencies -RUN yarn install --frozen-lockfile - -# Copy source code -COPY . . - -# Build the application -RUN yarn build - -# Production stage -FROM node:18-alpine AS production - -WORKDIR /usr/src/app - -# Copy package files -COPY package*.json yarn.lock ./ - -# Install only production dependencies -RUN yarn install --frozen-lockfile --production && yarn cache clean - -# Copy built application -COPY --from=build /usr/src/app/dist ./dist - -# Create non-root user -RUN addgroup -g 1001 -S nodejs -RUN adduser -S nestjs -u 1001 - -# Change ownership of the app directory -USER nestjs - -# Expose port -EXPOSE 3000 - -# Production command -CMD ["node", "dist/main"] diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index ed74915..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,70 +0,0 @@ -version: "3.8" - -services: - # MariaDB Database - mariadb: - image: mariadb:10.11 - container_name: low-code-engine-mariadb - restart: unless-stopped - environment: - MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-rootpassword} - MYSQL_DATABASE: ${DB_DATABASE:-low_code_engine} - MYSQL_USER: ${DB_USERNAME:-app_user} - MYSQL_PASSWORD: ${DB_PASSWORD:-app_password} - ports: - - "${DB_PORT:-3306}:3306" - volumes: - - mariadb_data:/var/lib/mysql - - ./docker/mariadb/init:/docker-entrypoint-initdb.d - networks: - - app-network - healthcheck: - test: - [ - "CMD", - "mysqladmin", - "ping", - "-h", - "localhost", - "-u", - "root", - "-p${DB_ROOT_PASSWORD:-rootpassword}", - ] - timeout: 5s - retries: 10 - interval: 10s - - # NestJS Application - app: - build: - context: . - dockerfile: Dockerfile - target: development - container_name: low-code-engine-app - restart: unless-stopped - environment: - NODE_ENV: ${NODE_ENV:-development} - DB_HOST: mariadb - DB_PORT: 3306 - DB_USERNAME: ${DB_USERNAME:-app_user} - DB_PASSWORD: ${DB_PASSWORD:-app_password} - DB_DATABASE: ${DB_DATABASE:-low_code_engine} - ports: - - "${APP_PORT:-3000}:3000" - volumes: - - .:/usr/src/app - - /usr/src/app/node_modules - networks: - - app-network - depends_on: - mariadb: - condition: service_healthy - command: yarn start:dev - -volumes: - mariadb_data: - driver: local - -networks: - app-network: - driver: bridge diff --git a/scripts/setup-docker.sh b/scripts/setup-docker.sh new file mode 100644 index 0000000..60f7d3e --- /dev/null +++ b/scripts/setup-docker.sh @@ -0,0 +1,37 @@ +#!/bin/bash +set -e + +echo "πŸ“¦ Updating system packages..." +apt update -y +apt upgrade -y + +echo "πŸ”§ Installing dependencies..." +apt install -y apt-transport-https ca-certificates curl gnupg lsb-release + +echo "πŸ”‘ Adding Docker’s official GPG key..." +curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg + +echo "πŸ“‚ Adding Docker repository..." +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \ + https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") \ + $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null + +echo "πŸ“¦ Installing Docker Engine and Compose..." +apt update -y +apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin + +echo "βš™οΈ Enabling and starting Docker service..." +systemctl enable docker +systemctl start docker + +echo "πŸ‘€ Adding current user to docker group..." +usermod -aG docker $USER + +echo "βœ… Docker and Docker Compose installation complete!" +echo "➑️ Log out and log back in (or run 'newgrp docker') to use Docker without sudo." +echo +echo "πŸ’‘ Docker version:" +docker --version || echo "Docker not yet available in current shell" +echo "πŸ’‘ Docker Compose version:" +docker compose version || echo "Docker Compose not yet available in current shell" diff --git a/src/migrations/1760377023483-projectSettings.ts b/src/migrations/1760377023483-projectSettings.ts new file mode 100644 index 0000000..2211087 --- /dev/null +++ b/src/migrations/1760377023483-projectSettings.ts @@ -0,0 +1,21 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class ProjectSettings1760377023483 implements MigrationInterface { + name = "ProjectSettings1760377023483"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE \`projectSetting\` (\`id\` varchar(36) NOT NULL, \`key\` varchar(255) NOT NULL, \`value\` text NOT NULL, \`projectId\` varchar(36) NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB` + ); + await queryRunner.query( + `ALTER TABLE \`projectSetting\` ADD CONSTRAINT \`FK_8dfaf9c1ebbadb7af024e72e871\` FOREIGN KEY (\`projectId\`) REFERENCES \`project\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION` + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE \`projectSetting\` DROP FOREIGN KEY \`FK_8dfaf9c1ebbadb7af024e72e871\`` + ); + await queryRunner.query(`DROP TABLE \`projectSetting\``); + } +} diff --git a/src/project/entities/project.entity.ts b/src/project/entities/project.entity.ts index 0d094a5..4d445bf 100644 --- a/src/project/entities/project.entity.ts +++ b/src/project/entities/project.entity.ts @@ -14,6 +14,7 @@ import { Database } from "../../databaseManager/entities/database.entity"; import { FunctionEntity } from "../../query/entities/function.entity"; import { RedisNode } from "../../redisManager/entities/redis.node.entity"; import { Log } from "../../query/logger/entities/log.entity"; +import { ProjectSetting } from "../settings/entities/project.setting.entity"; @Entity("project") export class Project { @@ -39,6 +40,9 @@ export class Project { @OneToMany(() => FunctionEntity, (functionEntity) => functionEntity.project) functions: FunctionEntity[]; + @OneToMany(() => ProjectSetting, (setting) => setting.project) + settings: ProjectSetting[]; + @ManyToMany(() => RedisNode, (redisNode) => redisNode.projects) @JoinTable() redisNodes: RedisNode[]; diff --git a/src/project/project.controller.ts b/src/project/project.controller.ts index a269d63..0ea01ef 100644 --- a/src/project/project.controller.ts +++ b/src/project/project.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, + Delete, Get, Inject, Put, @@ -11,13 +12,16 @@ import { ProjectService } from "./project.service"; import { ApiTokenGuard } from "src/api/guards/api-token.guard"; import { AdminGuard } from "src/api/guards/admin.guard"; import { Request } from "express"; +import { ProjectSettingService } from "./settings/project.setting.service"; @Controller("project") @UseGuards(ApiTokenGuard) export class ProjectController { constructor( @Inject(ProjectService) - private readonly projectService: ProjectService + private readonly projectService: ProjectService, + @Inject(ProjectSettingService) + private readonly projectSettingService: ProjectSettingService ) {} @Put("create") @@ -37,4 +41,35 @@ export class ProjectController { createProjectWithoutDB(@Body() body: { name: string }) { return this.projectService.create(body.name, false); } + + @Put("settings/create") + createSetting( + @Body() body: { key: string; value: string }, + @Req() req: Request & { apiToken: { id: string } } + ) { + return this.projectSettingService.create( + req.apiToken.id, + body.key, + body.value + ); + } + + @Delete("settings/delete") + deleteSetting( + @Body() body: { key: string }, + @Req() req: Request & { apiToken: { id: string } } + ) { + return this.projectSettingService.delete(req.apiToken.id, body.key); + } + + @Get("settings") + getAllSettings(@Req() req: Request & { apiToken: { id: string } }) { + return this.projectSettingService.getAll(req.apiToken.id); + } + + @Get("api-tokens") + @UseGuards(AdminGuard) + getAllApiTokens(@Req() req: Request & { apiToken: { id: string } }) { + return this.projectService.getAllApiTokens(req.apiToken.id); + } } diff --git a/src/project/project.module.ts b/src/project/project.module.ts index 2b9e302..deed0c2 100644 --- a/src/project/project.module.ts +++ b/src/project/project.module.ts @@ -6,16 +6,18 @@ import { ProjectController } from "./project.controller"; import { ApiModule } from "src/api/api.module"; import { RedisModule } from "src/redis/redis.module"; import { DatabaseManagerModule } from "src/databaseManager/database.manager.module"; +import { ProjectSetting } from "./settings/entities/project.setting.entity"; +import { ProjectSettingService } from "./settings/project.setting.service"; @Module({ imports: [ forwardRef(() => ApiModule), forwardRef(() => RedisModule), forwardRef(() => DatabaseManagerModule), - TypeOrmModule.forFeature([Project]), + TypeOrmModule.forFeature([Project, ProjectSetting]), ], controllers: [ProjectController], - providers: [ProjectService], - exports: [ProjectService], + providers: [ProjectService, ProjectSettingService], + exports: [ProjectService, ProjectSettingService], }) export class ProjectModule {} diff --git a/src/project/project.service.ts b/src/project/project.service.ts index ab33b41..a7a572a 100644 --- a/src/project/project.service.ts +++ b/src/project/project.service.ts @@ -4,6 +4,7 @@ import { Repository } from "typeorm"; import { Project } from "./entities/project.entity"; import { RedisClient } from "src/redis/redis.service"; import { DatabaseManagerService } from "src/databaseManager/database/database.manager.service"; +import { ProjectSettingService } from "./settings/project.setting.service"; @Injectable() export class ProjectService { @@ -13,9 +14,23 @@ export class ProjectService { @Inject(RedisClient) private readonly redisClient: RedisClient, @Inject(forwardRef(() => DatabaseManagerService)) - private readonly databaseManagerService: DatabaseManagerService + private readonly databaseManagerService: DatabaseManagerService, + @Inject(ProjectSettingService) + private readonly projectSettingService: ProjectSettingService ) {} + async createDefaultSettings(projectId: string) { + const defaultSettings = [{ key: "sessionTTL", value: "3600" }]; + + await Promise.all( + defaultSettings.map((setting) => + this.projectSettingService.create(projectId, setting.key, setting.value) + ) + ); + + return true; + } + async create(name: string, createDatabase: boolean = true) { const project = this.projectRepository.create({ name }); const projectSaved = await this.projectRepository.save(project); @@ -24,6 +39,9 @@ export class ProjectService { await this.databaseManagerService.createDatabase(projectSaved.id); } + await this.createDefaultSettings(projectSaved.id); + await this.redisClient.set(`project_${projectSaved.id}`, projectSaved, 300); + return projectSaved; } @@ -71,4 +89,12 @@ export class ProjectService { redisNodes: redisNodeId, }); } + + async getAllApiTokens(projectId: string) { + const project = await this.projectRepository.findOne({ + where: { id: projectId }, + relations: ["apiTokens"], + }); + return project?.apiTokens || []; + } } diff --git a/src/project/settings/entities/project.setting.entity.ts b/src/project/settings/entities/project.setting.entity.ts new file mode 100644 index 0000000..bac70aa --- /dev/null +++ b/src/project/settings/entities/project.setting.entity.ts @@ -0,0 +1,19 @@ +import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; +import { Project } from "../../entities/project.entity"; + +@Entity("projectSetting") +export class ProjectSetting { + @PrimaryGeneratedColumn("uuid") + id: string; + + @ManyToOne(() => Project, (project) => project.settings, { + onDelete: "CASCADE", + }) + project: Project; + + @Column({ type: "varchar", length: 255, nullable: false }) + key: string; + + @Column({ type: "text", nullable: false }) + value: string; +} diff --git a/src/project/settings/project.setting.service.ts b/src/project/settings/project.setting.service.ts new file mode 100644 index 0000000..457047f --- /dev/null +++ b/src/project/settings/project.setting.service.ts @@ -0,0 +1,112 @@ +import { Inject, Injectable } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { Repository } from "typeorm"; +import { ProjectSetting } from "./entities/project.setting.entity"; +import { RedisClient } from "src/redis/redis.service"; + +@Injectable() +export class ProjectSettingService { + constructor( + @InjectRepository(ProjectSetting) + private readonly projectSettingRepository: Repository, + @Inject(RedisClient) + private readonly redisClient: RedisClient + ) {} + + async updateCache(projectId: string) { + const settings = await this.projectSettingRepository.find({ + where: { project: { id: projectId } }, + }); + + const settingsObject = settings.reduce((obj, setting) => { + obj[setting.key] = setting.value; + return obj; + }, {}); + + await this.redisClient.set( + `project_settings_${projectId}`, + settingsObject, + 300 + ); + + return settingsObject; + } + + async get(key: string, projectId: string) { + const cached = await this.redisClient.get(`project_settings_${projectId}`); + + if (cached && key in cached) { + return cached[key]; + } + + const setting = await this.projectSettingRepository.findOne({ + where: { project: { id: projectId }, key }, + }); + + if (setting) { + return setting.value; + } + + return null; + } + + async getAll(projectId: string) { + const cached = await this.redisClient.get(`project_settings_${projectId}`); + + if (cached) { + return cached; + } + + const settings = await this.projectSettingRepository.find({ + where: { project: { id: projectId } }, + }); + + const settingsObject = settings.reduce((obj, setting) => { + obj[setting.key] = setting.value; + return obj; + }, {}); + + await this.redisClient.set( + `project_settings_${projectId}`, + settingsObject, + 300 + ); + + return settingsObject; + } + + async create(projectId: string, key: string, value: string) { + const existingSetting = await this.projectSettingRepository.findOne({ + where: { project: { id: projectId }, key }, + }); + + if (existingSetting) { + existingSetting.value = value; + await this.projectSettingRepository.save(existingSetting); + + return await this.updateCache(projectId); + } + + const newSetting = this.projectSettingRepository.create({ + key, + value, + project: { id: projectId }, + }); + + await this.projectSettingRepository.save(newSetting); + + return await this.updateCache(projectId); + } + + async delete(projectId: string, key: string) { + const setting = await this.projectSettingRepository.findOne({ + where: { project: { id: projectId }, key }, + }); + + if (setting) { + await this.projectSettingRepository.remove(setting); + } + + return await this.updateCache(projectId); + } +} diff --git a/src/query/base/base-query.controller.ts b/src/query/base/base-query.controller.ts index 031da44..23c2296 100644 --- a/src/query/base/base-query.controller.ts +++ b/src/query/base/base-query.controller.ts @@ -18,6 +18,7 @@ import { LoggerService } from "../logger/logger.service"; import { TLogType } from "../logger/logger.types"; import { QueryResponse } from "src/vm/vm.constants"; import { Query } from "../entities/query.entity"; +import { Token } from "src/api/entities/token.entity"; @UseGuards(ApiTokenGuard) export abstract class BaseQueryController { @@ -34,9 +35,16 @@ export abstract class BaseQueryController { @Post("create") async createQuery( - @Body() queryData: { projectToken: string; source: string } + @Req() req: Request & { apiToken: Token }, + @Body() queryData: { source: string } ) { - return this.queryHandlerService.createQuery(queryData, this.getIsCommand()); + return this.queryHandlerService.createQuery( + { + ...queryData, + projectToken: req?.apiToken.project.id, + }, + this.getIsCommand() + ); } @Post("update/:id") diff --git a/src/query/executer/query.executer.service.ts b/src/query/executer/query.executer.service.ts index e3cfc9e..46a0929 100644 --- a/src/query/executer/query.executer.service.ts +++ b/src/query/executer/query.executer.service.ts @@ -18,6 +18,7 @@ import { FunctionService } from "src/query/function/function.service"; import { SessionService } from "../session/session.service"; import { TLog, TLogType } from "../logger/logger.types"; import { LoggerService } from "../logger/logger.service"; +import { ProjectSettingService } from "src/project/settings/project.setting.service"; @Injectable() export class QueryExecuterService { @@ -31,6 +32,7 @@ export class QueryExecuterService { readonly databaseManagerService: DatabaseManagerService, readonly redisNodeService: RedisNodeService, readonly sessionService: SessionService, + readonly projectSettingService: ProjectSettingService, @InjectQueue(QUEUE_NAMES.QUERY) private queryQueue: Queue ) { this.queueEvents = new QueueEvents(this.queryQueue.name); diff --git a/src/query/function/function.controller.ts b/src/query/function/function.controller.ts index 1eb93fe..fce239d 100644 --- a/src/query/function/function.controller.ts +++ b/src/query/function/function.controller.ts @@ -1,6 +1,7 @@ -import { Controller, Inject, Post, UseGuards } from "@nestjs/common"; +import { Controller, Inject, Post, Req, UseGuards } from "@nestjs/common"; import { ApiTokenGuard } from "src/api/guards/api-token.guard"; import { FunctionService } from "./function.service"; +import { Token } from "src/api/entities/token.entity"; @Controller("functions") @UseGuards(ApiTokenGuard) @@ -11,12 +12,19 @@ export class FunctionController { ) {} @Post("create") - async createFunction(projectId: string, name: string, source: string) { - return this.functionService.create(projectId, name, source); + async createFunction( + @Req() req: Request & { apiToken: Token }, + name: string, + source: string + ) { + return this.functionService.create(req.apiToken.project.id, name, source); } @Post("delete") - async deleteFunction(projectId: string, name: string) { - return this.functionService.deleteFunction(projectId, name); + async deleteFunction( + @Req() req: Request & { apiToken: Token }, + name: string + ) { + return this.functionService.deleteFunction(req.apiToken.project.id, name); } } diff --git a/src/query/query.module.ts b/src/query/query.module.ts index 53c19cd..88ca4c4 100644 --- a/src/query/query.module.ts +++ b/src/query/query.module.ts @@ -18,6 +18,8 @@ import { SessionService } from "./session/session.service"; import { Log } from "./logger/entities/log.entity"; import { LoggerService } from "./logger/logger.service"; import { LoggerController } from "./logger/logger.controller"; +import { ProjectSettingService } from "src/project/settings/project.setting.service"; +import { ProjectSetting } from "src/project/settings/entities/project.setting.entity"; @Module({ imports: [ @@ -27,7 +29,7 @@ import { LoggerController } from "./logger/logger.controller"; forwardRef(() => QueueModule), forwardRef(() => RedisModule), forwardRef(() => RedisManagerModule), - TypeOrmModule.forFeature([Query, FunctionEntity, Log]), + TypeOrmModule.forFeature([Query, FunctionEntity, Log, ProjectSetting]), ], controllers: [ QueryController, @@ -41,12 +43,14 @@ import { LoggerController } from "./logger/logger.controller"; LoggerService, QueryHandlerService, FunctionService, + ProjectSettingService, ], exports: [ QueryExecuterService, TypeOrmModule, QueryHandlerService, LoggerService, + ProjectSettingService, ], }) export class QueryModule {} diff --git a/src/query/session/session.service.ts b/src/query/session/session.service.ts index f680182..1f3674a 100644 --- a/src/query/session/session.service.ts +++ b/src/query/session/session.service.ts @@ -1,9 +1,15 @@ import { Inject, Injectable } from "@nestjs/common"; +import { ProjectSettingService } from "src/project/settings/project.setting.service"; import { RedisClient } from "src/redis/redis.service"; @Injectable() export class SessionService { - constructor(@Inject(RedisClient) private readonly redisClient: RedisClient) {} + constructor( + @Inject(RedisClient) + private readonly redisClient: RedisClient, + @Inject(ProjectSettingService) + private readonly projectSettingService: ProjectSettingService + ) {} private generateSessionId(length: number = 16): string { const characters = @@ -16,6 +22,11 @@ export class SessionService { return `${result}_${new Date().getTime()}`; } + async getSessionTTL(projectId: string): Promise { + const ttl = await this.projectSettingService.get("sessionTTL", projectId); + return ttl ? parseInt(ttl) : 3600; + } + async create(prefix: string): Promise<{ sessionId: string }> { const sessionId = this.generateSessionId(); await this.set(sessionId, prefix, { @@ -33,7 +44,11 @@ export class SessionService { const data = await this.redisClient.get(`${prefix}:${sessionId}`); if (data) { - await this.redisClient.set(`${prefix}:${sessionId}`, data, 3600); + await this.redisClient.set( + `${prefix}:${sessionId}`, + data, + await this.getSessionTTL(prefix) + ); return data; } @@ -41,7 +56,11 @@ export class SessionService { } async set(sessionId: string, prefix: string, data: any): Promise { - await this.redisClient.set(`${prefix}:${sessionId}`, data, 3600); + await this.redisClient.set( + `${prefix}:${sessionId}`, + data, + await this.getSessionTTL(prefix) + ); } async delete(sessionId: string, prefix: string): Promise { diff --git a/src/vm/plugins/settings.plugin.ts b/src/vm/plugins/settings.plugin.ts new file mode 100644 index 0000000..57e6e0a --- /dev/null +++ b/src/vm/plugins/settings.plugin.ts @@ -0,0 +1,29 @@ +import { Query } from "src/query/entities/query.entity"; +import { Plugin } from "../plugin.class"; +import { QueryExecuterService } from "src/query/executer/query.executer.service"; + +export class SettingsPlugin extends Plugin { + constructor( + name: string, + private query: Query, + private queryExecuterService: QueryExecuterService + ) { + super(name, ["get"]); + } + + static async init(query: Query, queryExecuterService: QueryExecuterService) { + return new SettingsPlugin("settings", query, queryExecuterService); + } + + async get(property: string): Promise { + const settings = + await this.queryExecuterService.projectSettingService.getAll( + this.query.project.id + ); + return settings[property]; + } + + onFinish() { + // No resources to clean up + } +} diff --git a/src/vm/vm.class.ts b/src/vm/vm.class.ts index fd7feed..b5b4f24 100644 --- a/src/vm/vm.class.ts +++ b/src/vm/vm.class.ts @@ -172,16 +172,20 @@ export class Vm { `; const compiledScript = await this.isolate.compileScript(scriptWithResult); + let timer = 0n; const interval = setInterval(() => { if ( this.isolate.cpuTime > this.cpuTimeLimit || - this.isolate.wallTime > this.timeLimit + this.isolate.wallTime > this.timeLimit || + timer > this.timeLimit ) { this.isolate.dispose(); rejectPromise(new Error("Script execution timed out")); } + + timer += 500000n; }, 500); compiledScript.run(this.context); diff --git a/src/vm/vm.constants.ts b/src/vm/vm.constants.ts index 8d2c2c9..f75590e 100644 --- a/src/vm/vm.constants.ts +++ b/src/vm/vm.constants.ts @@ -6,6 +6,7 @@ import { AxiosPlugin } from "./plugins/axios.plugin"; import { RedisPlugin } from "./plugins/redis.plugin"; import { SessionPlugin } from "./plugins/session.plugin"; import { TLog } from "src/query/logger/logger.types"; +import { SettingsPlugin } from "./plugins/settings.plugin"; export const registeredPlugins = { db: async (service: QueryExecuterService, query: Query) => { @@ -32,6 +33,9 @@ export const registeredPlugins = { return RedisPlugin.init("redis", redisConnection, query.project.id); }, + settings: async (service: QueryExecuterService, query: Query) => { + return SettingsPlugin.init(query, service); + }, session: async ( service: QueryExecuterService, query: Query,