16 Commits

Author SHA1 Message Date
0f0d082a17 Merge pull request 'ja sdeljal' (#15) from api_token into develop
All checks were successful
Deploy to Testing Server / Deploy to Testing Environment (push) Successful in 23s
Reviewed-on: http://192.168.0.16:3000/lborv/few-line-engine/pulls/15
Reviewed-by: lborv <boris.djumajev@gmail.com>
2025-10-14 17:20:48 +00:00
f046999828 ja sdeljal 2025-10-14 20:17:18 +03:00
eb65eec9c0 Merge pull request 'feat: implement project settings management with CRUD operations and caching' (#11) from projectSettings into develop
All checks were successful
Deploy to Testing Server / Deploy to Testing Environment (push) Successful in 32s
Reviewed-on: http://192.168.0.16:3000/lborv/few-line-engine/pulls/11
Reviewed-by: jana <comite11@inbox.lv>
2025-10-14 16:48:34 +00:00
a139e957b1 Merge pull request 'fix: add timeout handling for script execution in Vm class' (#14) from limitingQueryExrcutionTime into develop
All checks were successful
Deploy to Testing Server / Deploy to Testing Environment (push) Successful in 27s
Reviewed-on: http://192.168.0.16:3000/lborv/few-line-engine/pulls/14
Reviewed-by: jana <comite11@inbox.lv>
2025-10-14 16:47:36 +00:00
96c560a691 fix: add timeout handling for script execution in Vm class 2025-10-14 16:04:54 +03:00
9c924c525b Merge pull request 'chore: remove deprecated environment and Docker configuration files' (#13) from ci/cd into develop
All checks were successful
Deploy to Testing Server / Deploy to Testing Environment (push) Successful in 44s
Reviewed-on: http://192.168.0.16:3000/lborv/low-code-engine/pulls/13
2025-10-14 12:08:03 +00:00
fce94e7ffd chore: remove deprecated environment and Docker configuration files 2025-10-14 15:07:24 +03:00
70f6fd68bf Merge pull request 'ci/cd' (#12) from ci/cd into develop
All checks were successful
Deploy to Testing Server / Deploy to Testing Environment (push) Successful in 30s
Reviewed-on: http://192.168.0.16:3000/lborv/low-code-engine/pulls/12
2025-10-14 11:13:44 +00:00
6e95a0c1a9 chore: Refactor deployment scripts and workflows for improved clarity and efficiency 2025-10-14 14:13:10 +03:00
ff664c2086 fix: update comments and documentation to English for consistency 2025-10-13 21:25:36 +03:00
aaa8680421 chore: Remove outdated deployment documentation files 2025-10-13 21:00:50 +03:00
41f1c402ed feat: Add test deployment workflow and setup documentation
- Implemented a new GitHub Actions workflow for testing deployments (`test-deployment.yml`).
- Created detailed deployment documentation (`DEPLOYMENT-README.md`, `DEPLOYMENT.md`, `QUICK-START.md`) for setting up the testing server and configuring GitHub secrets.
- Added a setup script (`setup-testing-server.sh`) for automating the environment setup on the testing server, including Docker, Nginx, and user configurations.
- Included monitoring and cleanup scripts for managing deployments on the server.
2025-10-13 20:59:12 +03:00
93f12cd1d8 feat: implement project settings management with CRUD operations and caching 2025-10-13 20:40:01 +03:00
2671665e25 Merge pull request 'fix: add confirmation message for test completion' (#10) from test into develop
All checks were successful
Test Runner / hello-world (push) Successful in 12s
Reviewed-on: http://192.168.0.16:3000/lborv/low-code-engine/pulls/10
2025-10-13 14:33:47 +00:00
2885f0d74e fix: add confirmation message for test completion 2025-10-13 17:30:41 +03:00
aab9ffa253 Merge pull request 'main' (#9) from main into develop
All checks were successful
Test Runner / hello-world (push) Successful in 3s
Reviewed-on: http://192.168.0.16:3000/lborv/low-code-engine/pulls/9
2025-10-13 14:29:00 +00:00
22 changed files with 418 additions and 185 deletions

View File

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

View File

@ -5,6 +5,9 @@ DB_USERNAME=root
DB_PASSWORD=your_password_here DB_PASSWORD=your_password_here
DB_DATABASE=low_code_engine DB_DATABASE=low_code_engine
REDIS_HOST=localhost
REDIS_PORT=6379
# Application Configuration # Application Configuration
NODE_ENV=development NODE_ENV=development
PORT=3054 PORT=3054

View File

@ -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!"

View File

@ -1,17 +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!"

View File

@ -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"]

View File

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

37
scripts/setup-docker.sh Normal file
View File

@ -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 Dockers 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"

View File

@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class ProjectSettings1760377023483 implements MigrationInterface {
name = "ProjectSettings1760377023483";
public async up(queryRunner: QueryRunner): Promise<void> {
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<void> {
await queryRunner.query(
`ALTER TABLE \`projectSetting\` DROP FOREIGN KEY \`FK_8dfaf9c1ebbadb7af024e72e871\``
);
await queryRunner.query(`DROP TABLE \`projectSetting\``);
}
}

View File

@ -14,6 +14,7 @@ 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"; import { RedisNode } from "../../redisManager/entities/redis.node.entity";
import { Log } from "../../query/logger/entities/log.entity"; import { Log } from "../../query/logger/entities/log.entity";
import { ProjectSetting } from "../settings/entities/project.setting.entity";
@Entity("project") @Entity("project")
export class Project { export class Project {
@ -39,6 +40,9 @@ export class Project {
@OneToMany(() => FunctionEntity, (functionEntity) => functionEntity.project) @OneToMany(() => FunctionEntity, (functionEntity) => functionEntity.project)
functions: FunctionEntity[]; functions: FunctionEntity[];
@OneToMany(() => ProjectSetting, (setting) => setting.project)
settings: ProjectSetting[];
@ManyToMany(() => RedisNode, (redisNode) => redisNode.projects) @ManyToMany(() => RedisNode, (redisNode) => redisNode.projects)
@JoinTable() @JoinTable()
redisNodes: RedisNode[]; redisNodes: RedisNode[];

View File

@ -1,14 +1,26 @@
import { Body, Controller, Inject, Put, UseGuards } from "@nestjs/common"; import {
Body,
Controller,
Delete,
Get,
Inject,
Put,
Req,
UseGuards,
} from "@nestjs/common";
import { ProjectService } from "./project.service"; import { ProjectService } from "./project.service";
import { ApiTokenGuard } from "src/api/guards/api-token.guard"; import { ApiTokenGuard } from "src/api/guards/api-token.guard";
import { AdminGuard } from "src/api/guards/admin.guard"; import { AdminGuard } from "src/api/guards/admin.guard";
import { ProjectSettingService } from "./settings/project.setting.service";
@Controller("project") @Controller("project")
@UseGuards(ApiTokenGuard) @UseGuards(ApiTokenGuard)
export class ProjectController { export class ProjectController {
constructor( constructor(
@Inject(ProjectService) @Inject(ProjectService)
private readonly projectService: ProjectService private readonly projectService: ProjectService,
@Inject(ProjectSettingService)
private readonly projectSettingService: ProjectSettingService
) {} ) {}
@Put("create") @Put("create")
@ -21,4 +33,36 @@ export class ProjectController {
createProjectWithoutDB(@Body() body: { name: string }) { createProjectWithoutDB(@Body() body: { name: string }) {
return this.projectService.create(body.name, false); 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);
}
}

View File

@ -6,16 +6,18 @@ 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"; import { RedisModule } from "src/redis/redis.module";
import { DatabaseManagerModule } from "src/databaseManager/database.manager.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({ @Module({
imports: [ imports: [
forwardRef(() => ApiModule), forwardRef(() => ApiModule),
forwardRef(() => RedisModule), forwardRef(() => RedisModule),
forwardRef(() => DatabaseManagerModule), forwardRef(() => DatabaseManagerModule),
TypeOrmModule.forFeature([Project]), TypeOrmModule.forFeature([Project, ProjectSetting]),
], ],
controllers: [ProjectController], controllers: [ProjectController],
providers: [ProjectService], providers: [ProjectService, ProjectSettingService],
exports: [ProjectService], exports: [ProjectService, ProjectSettingService],
}) })
export class ProjectModule {} export class ProjectModule {}

View File

@ -4,6 +4,7 @@ import { Repository } from "typeorm";
import { Project } from "./entities/project.entity"; import { Project } from "./entities/project.entity";
import { RedisClient } from "src/redis/redis.service"; import { RedisClient } from "src/redis/redis.service";
import { DatabaseManagerService } from "src/databaseManager/database/database.manager.service"; import { DatabaseManagerService } from "src/databaseManager/database/database.manager.service";
import { ProjectSettingService } from "./settings/project.setting.service";
@Injectable() @Injectable()
export class ProjectService { export class ProjectService {
@ -13,9 +14,23 @@ export class ProjectService {
@Inject(RedisClient) @Inject(RedisClient)
private readonly redisClient: RedisClient, private readonly redisClient: RedisClient,
@Inject(forwardRef(() => DatabaseManagerService)) @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) { async create(name: string, createDatabase: boolean = true) {
const project = this.projectRepository.create({ name }); const project = this.projectRepository.create({ name });
const projectSaved = await this.projectRepository.save(project); const projectSaved = await this.projectRepository.save(project);
@ -24,6 +39,9 @@ export class ProjectService {
await this.databaseManagerService.createDatabase(projectSaved.id); await this.databaseManagerService.createDatabase(projectSaved.id);
} }
await this.createDefaultSettings(projectSaved.id);
await this.redisClient.set(`project_${projectSaved.id}`, projectSaved, 300);
return projectSaved; return projectSaved;
} }
@ -58,4 +76,12 @@ export class ProjectService {
redisNodes: redisNodeId, redisNodes: redisNodeId,
}); });
} }
async getAllApiTokens(projectId: string) {
const project = await this.projectRepository.findOne({
where: { id: projectId },
relations: ["apiTokens"],
});
return project?.apiTokens || [];
}
} }

View File

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

View File

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

View File

@ -18,6 +18,7 @@ import { LoggerService } from "../logger/logger.service";
import { TLogType } from "../logger/logger.types"; import { TLogType } from "../logger/logger.types";
import { QueryResponse } from "src/vm/vm.constants"; import { QueryResponse } from "src/vm/vm.constants";
import { Query } from "../entities/query.entity"; import { Query } from "../entities/query.entity";
import { Token } from "src/api/entities/token.entity";
@UseGuards(ApiTokenGuard) @UseGuards(ApiTokenGuard)
export abstract class BaseQueryController { export abstract class BaseQueryController {
@ -34,9 +35,16 @@ export abstract class BaseQueryController {
@Post("create") @Post("create")
async createQuery( 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") @Post("update/:id")

View File

@ -18,6 +18,7 @@ import { FunctionService } from "src/query/function/function.service";
import { SessionService } from "../session/session.service"; import { SessionService } from "../session/session.service";
import { TLog, TLogType } from "../logger/logger.types"; import { TLog, TLogType } from "../logger/logger.types";
import { LoggerService } from "../logger/logger.service"; import { LoggerService } from "../logger/logger.service";
import { ProjectSettingService } from "src/project/settings/project.setting.service";
@Injectable() @Injectable()
export class QueryExecuterService { export class QueryExecuterService {
@ -31,6 +32,7 @@ export class QueryExecuterService {
readonly databaseManagerService: DatabaseManagerService, readonly databaseManagerService: DatabaseManagerService,
readonly redisNodeService: RedisNodeService, readonly redisNodeService: RedisNodeService,
readonly sessionService: SessionService, readonly sessionService: SessionService,
readonly projectSettingService: ProjectSettingService,
@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

@ -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 { ApiTokenGuard } from "src/api/guards/api-token.guard";
import { FunctionService } from "./function.service"; import { FunctionService } from "./function.service";
import { Token } from "src/api/entities/token.entity";
@Controller("functions") @Controller("functions")
@UseGuards(ApiTokenGuard) @UseGuards(ApiTokenGuard)
@ -11,12 +12,19 @@ export class FunctionController {
) {} ) {}
@Post("create") @Post("create")
async createFunction(projectId: string, name: string, source: string) { async createFunction(
return this.functionService.create(projectId, name, source); @Req() req: Request & { apiToken: Token },
name: string,
source: string
) {
return this.functionService.create(req.apiToken.project.id, name, source);
} }
@Post("delete") @Post("delete")
async deleteFunction(projectId: string, name: string) { async deleteFunction(
return this.functionService.deleteFunction(projectId, name); @Req() req: Request & { apiToken: Token },
name: string
) {
return this.functionService.deleteFunction(req.apiToken.project.id, name);
} }
} }

View File

@ -18,6 +18,8 @@ import { SessionService } from "./session/session.service";
import { Log } from "./logger/entities/log.entity"; import { Log } from "./logger/entities/log.entity";
import { LoggerService } from "./logger/logger.service"; import { LoggerService } from "./logger/logger.service";
import { LoggerController } from "./logger/logger.controller"; 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({ @Module({
imports: [ imports: [
@ -27,7 +29,7 @@ import { LoggerController } from "./logger/logger.controller";
forwardRef(() => QueueModule), forwardRef(() => QueueModule),
forwardRef(() => RedisModule), forwardRef(() => RedisModule),
forwardRef(() => RedisManagerModule), forwardRef(() => RedisManagerModule),
TypeOrmModule.forFeature([Query, FunctionEntity, Log]), TypeOrmModule.forFeature([Query, FunctionEntity, Log, ProjectSetting]),
], ],
controllers: [ controllers: [
QueryController, QueryController,
@ -41,12 +43,14 @@ import { LoggerController } from "./logger/logger.controller";
LoggerService, LoggerService,
QueryHandlerService, QueryHandlerService,
FunctionService, FunctionService,
ProjectSettingService,
], ],
exports: [ exports: [
QueryExecuterService, QueryExecuterService,
TypeOrmModule, TypeOrmModule,
QueryHandlerService, QueryHandlerService,
LoggerService, LoggerService,
ProjectSettingService,
], ],
}) })
export class QueryModule {} export class QueryModule {}

View File

@ -1,9 +1,15 @@
import { Inject, Injectable } from "@nestjs/common"; import { Inject, Injectable } from "@nestjs/common";
import { ProjectSettingService } from "src/project/settings/project.setting.service";
import { RedisClient } from "src/redis/redis.service"; import { RedisClient } from "src/redis/redis.service";
@Injectable() @Injectable()
export class SessionService { 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 { private generateSessionId(length: number = 16): string {
const characters = const characters =
@ -16,6 +22,11 @@ export class SessionService {
return `${result}_${new Date().getTime()}`; return `${result}_${new Date().getTime()}`;
} }
async getSessionTTL(projectId: string): Promise<number> {
const ttl = await this.projectSettingService.get("sessionTTL", projectId);
return ttl ? parseInt(ttl) : 3600;
}
async create(prefix: string): Promise<{ sessionId: string }> { async create(prefix: string): Promise<{ sessionId: string }> {
const sessionId = this.generateSessionId(); const sessionId = this.generateSessionId();
await this.set(sessionId, prefix, { await this.set(sessionId, prefix, {
@ -33,7 +44,11 @@ export class SessionService {
const data = await this.redisClient.get(`${prefix}:${sessionId}`); const data = await this.redisClient.get(`${prefix}:${sessionId}`);
if (data) { if (data) {
await this.redisClient.set(`${prefix}:${sessionId}`, data, 3600); await this.redisClient.set(
`${prefix}:${sessionId}`,
data,
await this.getSessionTTL(prefix)
);
return data; return data;
} }
@ -41,7 +56,11 @@ export class SessionService {
} }
async set(sessionId: string, prefix: string, data: any): Promise<void> { async set(sessionId: string, prefix: string, data: any): Promise<void> {
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<void> { async delete(sessionId: string, prefix: string): Promise<void> {

View File

@ -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<any> {
const settings =
await this.queryExecuterService.projectSettingService.getAll(
this.query.project.id
);
return settings[property];
}
onFinish() {
// No resources to clean up
}
}

View File

@ -172,16 +172,20 @@ export class Vm {
`; `;
const compiledScript = await this.isolate.compileScript(scriptWithResult); const compiledScript = await this.isolate.compileScript(scriptWithResult);
let timer = 0n;
const interval = setInterval(() => { const interval = setInterval(() => {
if ( if (
this.isolate.cpuTime > this.cpuTimeLimit || this.isolate.cpuTime > this.cpuTimeLimit ||
this.isolate.wallTime > this.timeLimit this.isolate.wallTime > this.timeLimit ||
timer > this.timeLimit
) { ) {
this.isolate.dispose(); this.isolate.dispose();
rejectPromise(new Error("Script execution timed out")); rejectPromise(new Error("Script execution timed out"));
} }
timer += 500000n;
}, 500); }, 500);
compiledScript.run(this.context); compiledScript.run(this.context);

View File

@ -6,6 +6,7 @@ import { AxiosPlugin } from "./plugins/axios.plugin";
import { RedisPlugin } from "./plugins/redis.plugin"; import { RedisPlugin } from "./plugins/redis.plugin";
import { SessionPlugin } from "./plugins/session.plugin"; import { SessionPlugin } from "./plugins/session.plugin";
import { TLog } from "src/query/logger/logger.types"; import { TLog } from "src/query/logger/logger.types";
import { SettingsPlugin } from "./plugins/settings.plugin";
export const registeredPlugins = { export const registeredPlugins = {
db: async (service: QueryExecuterService, query: Query) => { db: async (service: QueryExecuterService, query: Query) => {
@ -32,6 +33,9 @@ export const registeredPlugins = {
return RedisPlugin.init("redis", redisConnection, query.project.id); return RedisPlugin.init("redis", redisConnection, query.project.id);
}, },
settings: async (service: QueryExecuterService, query: Query) => {
return SettingsPlugin.init(query, service);
},
session: async ( session: async (
service: QueryExecuterService, service: QueryExecuterService,
query: Query, query: Query,