diff --git a/package.json b/package.json index 5e90129..468289f 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,14 @@ "prebuild": "rimraf dist", "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", - "start": "nest start", - "start:dev": "nest start --watch", - "start:debug": "nest start --debug --watch", - "start:prod": "node dist/main", + "start:api": "nest start", + "start:api:dev": "nest start --watch", + "start:api:debug": "nest start --debug --watch", + "start:api:prod": "node dist/main", + "start:proc": "nest start --entryFile processor", + "start:proc:dev": "nest start --entryFile processor --watch", + "start:proc:debug": "nest start --entryFile processor --debug --watch", + "start:proc:prod": "node dist/processor", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", @@ -49,7 +53,6 @@ "@types/express": "^4.17.21", "@types/jest": "^29.5.12", "@types/node": "^16.0.0", - "@types/supertest": "^6.0.2", "@typescript-eslint/eslint-plugin": "^7.13.1", "@typescript-eslint/parser": "^7.13.1", "eslint": "^8.56.0", @@ -58,7 +61,7 @@ "jest": "^29.7.0", "prettier": "^3.3.2", "source-map-support": "^0.5.20", - "supertest": "^7.0.0", + "node-fetch": "^2.6.7", "ts-jest": "^29.1.5", "ts-node": "^10.9.2", "tsconfig-paths": "^3.10.1", diff --git a/src/api/agent/agent.controller.spec.ts b/src/api/agent/agent.controller.spec.ts new file mode 100644 index 0000000..338a2e8 --- /dev/null +++ b/src/api/agent/agent.controller.spec.ts @@ -0,0 +1,88 @@ +import { DataSource } from 'typeorm' +import { testConfig } from '../../configuration.tests' +import { ConfigService } from '@nestjs/config' +import { CreateTaskDto } from './dto/create-task-dto' +import fetch from 'node-fetch' + +describe('AppController', () => { + // let appController: AgentController + // beforeEach(async () => { + // const appModule: TestingModule = await Test.createTestingModule({ + // // imports: [TypeOrmModule.forFeature([TaskEntity, StepEntity])], + // imports: [], + // controllers: [AgentController], + // providers: [ + // { provide: AgentService, useValue: undefined }, + // { provide: getRepositoryToken(TaskEntity), useClass: TaskEntity }, + // { provide: getRepositoryToken(StepEntity), useClass: StepEntity } + // ] + // }).compile() + // await appModule.init() + // // appController = app.get(AgentController) + // }) + let dataSource: DataSource + let configService: ConfigService + const TASKS_ENDPOINT = '/api/v1/agent/tasks' + + beforeAll(async () => { + configService = new ConfigService(testConfig) + + console.log('Connecting to database') + //@ts-expect-error-next-line + dataSource = new DataSource({ + type: configService.get('database.type'), + host: configService.get('database.host'), + port: configService.get('database.port'), + username: configService.get('database.username'), + password: configService.get('database.password'), + database: configService.get('database.name'), + logging: ['query', 'log', 'error', 'warn', 'info'], + entities: [], + synchronize: true + // logging: true + }) + await dataSource.initialize() + console.log('Conection to database established') + }) + + afterAll(async () => { + try { + if (dataSource.isInitialized) { + await dataSource.query('DELETE FROM steps ') + console.log('Closing database connection ..') + dataSource.destroy() + } + } catch (error) { + console.warn('Error deleting content from db') + } + }) + + describe('root', () => { + it('Can create a task', async () => { + const task = new CreateTaskDto() + task.query = 'My test query' + task.additional_params = [{ param1: 'value1' }, { param2: 'value2' }] + task.artifacts = [{ artifact_id: 'art-0123', url: 'https://url.test' }] + createTask(task).then((response) => { + expect(response.status).toBe(201) + }) + }) + }) + + async function createTask(createTaskDto: CreateTaskDto) { + console.log(`Creating task: ${configService.get('api.url')}`) + return fetch(`${configService.get('api.url')}${TASKS_ENDPOINT}`, { + method: 'post', + body: JSON.stringify(createTaskDto), + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${configService.get('api.authToken')}` + } + }) + // return request(configService.get('api.url')) + // .post(TASKS_ENDPOINT) + // .set('Accept', 'application/json') + // .set('Authorization', `Bearer ${configService.get('api.authToken')}`) + // .send(createTaskDto) + } +}) diff --git a/src/modules/agent/agent.controller.ts b/src/api/agent/agent.controller.ts similarity index 100% rename from src/modules/agent/agent.controller.ts rename to src/api/agent/agent.controller.ts diff --git a/src/modules/agent/agent.module.ts b/src/api/agent/agent.module.ts similarity index 73% rename from src/modules/agent/agent.module.ts rename to src/api/agent/agent.module.ts index 1b7058f..801ca60 100644 --- a/src/modules/agent/agent.module.ts +++ b/src/api/agent/agent.module.ts @@ -2,8 +2,8 @@ import { Module } from '@nestjs/common' import { AgentController } from './agent.controller' import { AgentService } from './agent.service' import { TypeOrmModule } from '@nestjs/typeorm' -import { TaskEntity } from 'src/database/entities/task.entity' -import { StepEntity } from 'src/database/entities/step.entity' +import { TaskEntity } from '../../database/entities/task.entity' +import { StepEntity } from '../../database/entities/step.entity' @Module({ imports: [TypeOrmModule.forFeature([TaskEntity, StepEntity])], diff --git a/src/api/agent/agent.service.ts b/src/api/agent/agent.service.ts new file mode 100644 index 0000000..251a915 --- /dev/null +++ b/src/api/agent/agent.service.ts @@ -0,0 +1,127 @@ +import { Injectable, Logger } from '@nestjs/common' +import { CreateTaskDto } from './dto/create-task-dto' +import { AgentsFactory } from '../../common/utils/agents-factory' +import { TaskEntity } from '../../database/entities/task.entity' +import { InjectRepository } from '@nestjs/typeorm' +import { StepEntity } from '../../database/entities/step.entity' +import { MoreThan, Repository } from 'typeorm' +import { ExecutionStatus } from '../../common/models/agent-models' +import { AppDataSource } from '../../database/typeorm.config' + +@Injectable() +export class AgentService { + // The maximum number of retries for a step, after which it is marked as failed + readonly MAX_STEP_RETRIES = 3 + constructor( + @InjectRepository(TaskEntity) private taskEntity: Repository, + @InjectRepository(StepEntity) private stepEntity: Repository + ) {} + + async createAgentTask(agentTaskDto: CreateTaskDto) { + const task = AgentsFactory.getTaskFromTemplate(agentTaskDto) + const dbTaskEntity: TaskEntity = { + ...new TaskEntity(), + name: task.name, + input_query: task.input.query, + input_params: JSON.stringify(task.input.additional_params), + input_artifacts: JSON.stringify(task.input.artifacts) + } + const insertedTask = await this.taskEntity.save(dbTaskEntity) + + const step = AgentsFactory.getStepFromTemplate( + agentTaskDto, + insertedTask.task_id + ) + const dbStepEntity: StepEntity = { + ...new StepEntity(), + task_id: step.task_id, + name: task.name, + input_query: step.input.query, + is_last: step.is_last, + input_params: JSON.stringify(step.input.additional_params), + input_artifacts: JSON.stringify(step.input.artifacts) + } + await this.stepEntity.save(dbStepEntity) + + return insertedTask + } + + async getPendingTasksFromDb() { + return this.taskEntity.find({ + where: { task_status: ExecutionStatus.PENDING } + }) + } + + async getPendingStepsForTask(task: TaskEntity) { + return this.stepEntity.find({ + where: { step_status: ExecutionStatus.PENDING, task_id: task.task_id } + }) + } + + updateStep(stepId: string, stepUpdated: Partial) { + Logger.log(`Updating step ${stepId}, with ${JSON.stringify(stepUpdated)}`) + return this.stepEntity.update( + { step_id: stepId }, + { ...stepUpdated, updated_at: new Date() } + ) + } + + async completeTasksWhenStepsAreDone() { + const tasks = await AppDataSource.query( + `SELECT tasks.task_id as task_id, steps.output as output, steps.output_artifacts as output_artifacts, steps.output_additional as output_additional FROM tasks, steps WHERE tasks.task_id = steps.task_id AND steps.is_last = true AND steps.step_status = \'COMPLETED\' AND tasks.task_status = \'PENDING\' ` + ) + for await (const task of tasks) { + Logger.log(`Marking task ${task.task_id} as completed`) + this.taskEntity.update( + { task_id: task.task_id }, + { + task_status: ExecutionStatus.COMPLETED, + output: task.output, + output_artifacts: task.output_artifacts, + output_additional: task.output_additional, + updated_at: new Date() + } + ) + } + return tasks + } + + async markTasksAsFailedAfterRetries() { + this.stepEntity.update( + { retries: MoreThan(this.MAX_STEP_RETRIES) }, + { step_status: ExecutionStatus.FAILED, updated_at: new Date() } + ) + const tasks = await AppDataSource.query( + `SELECT tasks.task_id as task_id, steps.output as output, steps.output_artifacts as output_artifacts, steps.output_additional as output_additional FROM tasks, steps WHERE tasks.task_id = steps.task_id AND steps.step_status = \'FAILED\' AND tasks.task_status != \'FAILED\' ` + ) + for await (const task of tasks) { + Logger.log(`Marking task ${task.task_id} as failed`) + this.taskEntity.update( + { task_id: task.task_id }, + { + task_status: ExecutionStatus.FAILED, + output: task.output, + output_artifacts: task.output_artifacts, + output_additional: task.output_additional, + updated_at: new Date() + } + ) + } + return tasks + } + // async addFirstStepToTask(taskId: string) { + // } + + // async addLastStepToTask(taskId: string) { + // } + + async getTaskById(taskId: string) { + const steps = await this.stepEntity.find({ where: { task_id: taskId } }) + const task = await this.taskEntity.findOne({ where: { task_id: taskId } }) + return { task, steps } + } + + listTasks() { + return [] + } +} diff --git a/src/modules/agent/dto/create-task-dto.ts b/src/api/agent/dto/create-task-dto.ts similarity index 100% rename from src/modules/agent/dto/create-task-dto.ts rename to src/api/agent/dto/create-task-dto.ts diff --git a/src/modules/app.module.ts b/src/api/app.module.ts similarity index 100% rename from src/modules/app.module.ts rename to src/api/app.module.ts diff --git a/src/modules/entity.module.ts b/src/api/entity.module.ts similarity index 100% rename from src/modules/entity.module.ts rename to src/api/entity.module.ts diff --git a/src/modules/info/info.controller.spec.ts b/src/api/info/info.controller.spec.ts similarity index 100% rename from src/modules/info/info.controller.spec.ts rename to src/api/info/info.controller.spec.ts diff --git a/src/modules/info/info.controller.ts b/src/api/info/info.controller.ts similarity index 100% rename from src/modules/info/info.controller.ts rename to src/api/info/info.controller.ts diff --git a/src/modules/info/info.module.ts b/src/api/info/info.module.ts similarity index 100% rename from src/modules/info/info.module.ts rename to src/api/info/info.module.ts diff --git a/src/modules/info/info.service.ts b/src/api/info/info.service.ts similarity index 100% rename from src/modules/info/info.service.ts rename to src/api/info/info.service.ts diff --git a/src/common/models/agent-models.ts b/src/common/models/agent-models.ts index 00882ff..2d7dd5e 100644 --- a/src/common/models/agent-models.ts +++ b/src/common/models/agent-models.ts @@ -65,6 +65,11 @@ export interface ExecutionOptions { * When the execution was last updated */ updated_at?: Date + + /** + * The number of retries for the task or step + */ + retries?: number } /** diff --git a/src/common/utils/agents-factory.ts b/src/common/utils/agents-factory.ts index 9e18548..9cf0e66 100644 --- a/src/common/utils/agents-factory.ts +++ b/src/common/utils/agents-factory.ts @@ -21,7 +21,11 @@ export abstract class AgentsFactory { return task } - static getStepFromTemplate(input: ExecutionInput | string, task_id: string) { + static getStepFromTemplate( + input: ExecutionInput | string, + task_id: string, + isLast = false + ) { if (typeof input === 'string') { input = { query: input @@ -32,7 +36,8 @@ export abstract class AgentsFactory { task_id, input, status: ExecutionStatus.PENDING, - is_last: false, + is_last: isLast, + retries: 0, created_at: new Date(), updated_at: new Date() } diff --git a/src/common/utils/utils.ts b/src/common/utils/utils.ts index 347b648..cb7bd77 100644 --- a/src/common/utils/utils.ts +++ b/src/common/utils/utils.ts @@ -8,3 +8,6 @@ export function extractAuthTokenFromHeader( return undefined } } + +export const sleep = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)) diff --git a/src/configuration.tests.ts b/src/configuration.tests.ts new file mode 100644 index 0000000..72fc42a --- /dev/null +++ b/src/configuration.tests.ts @@ -0,0 +1,23 @@ +export const testConfig = { + api: { + host: process.env.API_HOST || 'localhost', + port: parseInt(process.env.API_PORT, 10) || 4100, + url: + process.env.API_URL || + `http://${process.env.API_HOST || 'localhost'}:${ + process.env.API_PORT || '4100' + }`, + authToken: process.env.API_AUTH_TOKEN, + enableHttpsRedirect: process.env.API_ENABLE_HTTPS_REDIRECT === 'true' + }, + database: { + type: process.env.DATABASE_TYPE || 'sqlite', // sqlite, mysql, postgres, etc + name: process.env.DATABASE_NAME || '/tmp/test/agent-template.db', // path to the database file if sqlite if is a different database type put the database name instead + host: process.env.DATABASE_HOST, + port: parseInt(process.env.DATABASE_PORT, 10), + username: process.env.DATABASE_USERNAME, + password: process.env.DATABASE_PASSWORD, + schena: process.env.DATABASE_SCHEMA + } +} +export default () => testConfig diff --git a/src/configuration.ts b/src/configuration.ts index 33b93f4..1d9a7b1 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -26,6 +26,9 @@ export const appConfig = { username: process.env.DATABASE_USERNAME, password: process.env.DATABASE_PASSWORD, schena: process.env.DATABASE_SCHEMA + }, + processor: { + sleepDuration: Number(process.env.AGENT_SLEEP_DURATION) || 3000 // In milliseconds } } export default () => appConfig diff --git a/src/database/1718898482722-AgentBase.ts b/src/database/1718898482722-AgentBase.ts index 735777b..d1522a1 100644 --- a/src/database/1718898482722-AgentBase.ts +++ b/src/database/1718898482722-AgentBase.ts @@ -96,6 +96,11 @@ export class AgentBase1718898482722 implements MigrationInterface { default: false, isNullable: false }, + { + name: 'retries', + type: 'integer', + default: 0 + }, { name: 'step_status', type: 'enum', diff --git a/src/database/entities/step.entity.ts b/src/database/entities/step.entity.ts index 8c6009e..b2a1285 100644 --- a/src/database/entities/step.entity.ts +++ b/src/database/entities/step.entity.ts @@ -1,6 +1,6 @@ import { Column, Entity, PrimaryColumn } from 'typeorm' import { BaseEntity } from './base.entity' -import { ExecutionStatus } from 'src/common/models/agent-models' +import { ExecutionStatus } from '../../common/models/agent-models' import { v4 as uuidv4 } from 'uuid' @Entity('steps') @@ -14,11 +14,13 @@ export class StepEntity extends BaseEntity { @Column('varchar') name: string + @Column('integer') + retries: number + @Column('boolean') is_last: boolean @Column({ - // enum: Object.values(ExecutionStatus), enum: ExecutionStatus, type: 'simple-enum' }) @@ -46,5 +48,6 @@ export class StepEntity extends BaseEntity { super() this.step_id = `step-${uuidv4()}` this.step_status = ExecutionStatus.PENDING + this.retries = 0 } } diff --git a/src/database/entities/task.entity.ts b/src/database/entities/task.entity.ts index 8e569de..d4ab59b 100644 --- a/src/database/entities/task.entity.ts +++ b/src/database/entities/task.entity.ts @@ -1,6 +1,6 @@ import { Column, Entity, PrimaryColumn } from 'typeorm' import { BaseEntity } from './base.entity' -import { ExecutionStatus } from 'src/common/models/agent-models' +import { ExecutionStatus } from '../../common/models/agent-models' import { v4 as uuidv4 } from 'uuid' @Entity('tasks') diff --git a/src/database/typeorm.config.ts b/src/database/typeorm.config.ts index fa22d31..566975d 100644 --- a/src/database/typeorm.config.ts +++ b/src/database/typeorm.config.ts @@ -15,7 +15,7 @@ export const AppDataSource = new DataSource({ username: configService.get('database.username'), password: configService.get('database.password'), database: configService.get('database.name'), - logging: ['query', 'log', 'error', 'warn', 'info'], + logging: ['log', 'error', 'warn', 'info'], entities: [], migrations: [AgentBase1718898482722] }) diff --git a/src/main.ts b/src/main.ts index 67268c1..331cc00 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,7 +4,7 @@ import { ConsoleLogger, Logger } from '@nestjs/common' import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger' import { readFileSync } from 'fs' import { join } from 'path' -import { AppModule } from './modules/app.module' +import { AppModule } from './api/app.module' import { ConfigService } from '@nestjs/config' async function bootstrap() { diff --git a/src/modules/agent/agent.controller.spec.ts b/src/modules/agent/agent.controller.spec.ts deleted file mode 100644 index 46dd88c..0000000 --- a/src/modules/agent/agent.controller.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing' -import { AgentService } from './agent.service' -import { AgentController } from './agent.controller' -import { ConfigService } from '@nestjs/config' - -describe('AppController', () => { - let appController: AgentController - - beforeEach(async () => { - const app: TestingModule = await Test.createTestingModule({ - controllers: [AgentController], - providers: [AgentService, ConfigService] - }).compile() - - appController = app.get(AgentController) - }) - - describe('root', () => { - it('should return a list of tasks"', () => { - expect(appController.getAgentTasks()).toBeInstanceOf(Array) - }) - }) -}) diff --git a/src/modules/agent/agent.service.ts b/src/modules/agent/agent.service.ts deleted file mode 100644 index 5f06be8..0000000 --- a/src/modules/agent/agent.service.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Injectable } from '@nestjs/common' -import { CreateTaskDto } from './dto/create-task-dto' -import { AgentsFactory } from '../../common/utils/agents-factory' -import { TaskEntity } from 'src/database/entities/task.entity' -import { InjectRepository } from '@nestjs/typeorm' -import { StepEntity } from 'src/database/entities/step.entity' -import { Repository } from 'typeorm' - -@Injectable() -export class AgentService { - constructor( - @InjectRepository(TaskEntity) private taskEntity: Repository, - @InjectRepository(StepEntity) private stepEntity: Repository - ) {} - - async createAgentTask(agentTaskDto: CreateTaskDto) { - const task = AgentsFactory.getTaskFromTemplate(agentTaskDto) - const dbTaskEntity: TaskEntity = { - ...new TaskEntity(), - name: task.name, - input_query: task.input.query, - input_params: JSON.stringify(task.input.additional_params), - input_artifacts: JSON.stringify(task.input.artifacts) - } - const insertedTask = await this.taskEntity.save(dbTaskEntity) - - const step = AgentsFactory.getStepFromTemplate( - agentTaskDto, - insertedTask.task_id - ) - const dbStepEntity: StepEntity = { - ...new StepEntity(), - task_id: step.task_id, - name: task.name, - input_query: step.input.query, - is_last: step.is_last, - input_params: JSON.stringify(step.input.additional_params), - input_artifacts: JSON.stringify(step.input.artifacts) - } - await this.stepEntity.save(dbStepEntity) - - return insertedTask - } - - async getTaskById(taskId: string) { - const steps = await this.stepEntity.find({ where: { task_id: taskId } }) - const task = await this.taskEntity.findOne({ where: { task_id: taskId } }) - return { task, steps } - } - - listTasks() { - return [] - } -} diff --git a/src/modules/config/config.module.ts b/src/modules/config/config.module.ts deleted file mode 100644 index 6985843..0000000 --- a/src/modules/config/config.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Module } from '@nestjs/common' -import { ConfigService } from './config.service' - -@Module({ - providers: [ - { - provide: ConfigService, - useValue: new ConfigService() - } - ], - exports: [ConfigService] -}) -export class ConfigModule {} diff --git a/src/modules/config/config.service.ts b/src/modules/config/config.service.ts deleted file mode 100644 index 9bac8e3..0000000 --- a/src/modules/config/config.service.ts +++ /dev/null @@ -1 +0,0 @@ -export class ConfigService {} diff --git a/src/processor.ts b/src/processor.ts new file mode 100644 index 0000000..8d09c83 --- /dev/null +++ b/src/processor.ts @@ -0,0 +1,30 @@ +import { NestFactory } from '@nestjs/core' +import { ConsoleLogger, Logger } from '@nestjs/common' +import { readFileSync } from 'fs' +import { join } from 'path' +import { ConfigService } from '@nestjs/config' +import { ProcessorModule } from './processor/processor.module' + +async function bootstrap() { + const packageJsonPath = join(__dirname, '../', 'package.json') + const packageJsonString = readFileSync(packageJsonPath, 'utf8') + const packageJson = JSON.parse(packageJsonString) as { + version: string + name: string + } + + const app = await NestFactory.createApplicationContext(ProcessorModule, { + logger: ['log', 'error', 'warn'] + }) + const config = app.get(ConfigService) + + app.useLogger( + new ConsoleLogger('processor', { + timestamp: true, + logLevels: config.get('log.levels') + }) + ) + Logger.log(`👾 Agent Requests Processor ${packageJson.version} is running`) +} + +bootstrap() diff --git a/src/processor/processor.controller.ts b/src/processor/processor.controller.ts new file mode 100644 index 0000000..1f7fa1e --- /dev/null +++ b/src/processor/processor.controller.ts @@ -0,0 +1,77 @@ +import { Logger, OnModuleInit } from '@nestjs/common' +import { Injectable } from '@nestjs/common' +import { ConfigService } from '@nestjs/config' +import { sleep } from '../common/utils/utils' +import { AgentService } from '../api/agent/agent.service' +import { TaskEntity } from '../database/entities/task.entity' +import { StepEntity } from '../database/entities/step.entity' +import { ExecutionStatus } from 'src/common/models/agent-models' + +@Injectable() +export class ProcessorController implements OnModuleInit { + constructor( + private readonly agentService: AgentService, + private readonly config: ConfigService + ) {} + + async onModuleInit() { + const agentSleepDuration = this.config.get('processor.sleepDuration') + while (true) { + Logger.debug(`Processor Controller is running...`) + await this.processTasks() + await sleep(agentSleepDuration) + } + } + + async processTasks() { + // 1. Get all pending tasks from the database + const pendingTasks = await this.agentService.getPendingTasksFromDb() + pendingTasks.map(async (task) => { + await this.processTask(task) + }) + + // X. All the tasks that are having all the steps finished are marked as completed + await this.agentService.completeTasksWhenStepsAreDone() + + // X. All the tasks with more than Y number of retries are marked as failed + await this.agentService.markTasksAsFailedAfterRetries() + } + + async processTask(task: TaskEntity) { + Logger.debug(`Processing task: ${task.task_id}`) + + // Get all the steps for the task that are still pending + const pendingSteps = await this.agentService.getPendingStepsForTask(task) + + pendingSteps.map(async (step) => { + await this.processStep(step, task) + }) + } + + async processStep(step: StepEntity, task: TaskEntity) { + Logger.log(`[${task.task_id}] Processing step: ${step.step_id}`) + + const randomIndex = Math.floor(Math.random() * 10) + if (randomIndex === 0) { + Logger.log(`[${task.task_id}] Simulate a step that is still in progress`) + await this.agentService.updateStep(step.step_id, { + step_status: ExecutionStatus.IN_PROGRESS, + retries: step.retries + 1 + }) + } else if (randomIndex === 1) { + Logger.log(`[${task.task_id}] Simulate a step that is failing`) + await this.agentService.updateStep(step.step_id, { + step_status: ExecutionStatus.FAILED, + output: `{"result": 500, "message": "Failed because ${randomIndex} is 1"}` + }) + } else { + // Simulate a successful step + Logger.log(`[${task.task_id}] Simulate a step that is completed`) + await this.agentService.updateStep(step.step_id, { + step_status: ExecutionStatus.COMPLETED, + is_last: true, + output: `{"result": 200, "message": "${randomIndex}"}` + }) + } + } +} diff --git a/src/processor/processor.module.ts b/src/processor/processor.module.ts new file mode 100644 index 0000000..4e239ce --- /dev/null +++ b/src/processor/processor.module.ts @@ -0,0 +1,25 @@ +import { Module } from '@nestjs/common' +import { ConfigModule, ConfigService } from '@nestjs/config' +import { appConfig } from '../configuration' +import { ProcessorController } from './processor.controller' +import { AgentService } from '../api/agent/agent.service' +import { TypeOrmModule } from '@nestjs/typeorm' +import { TaskEntity } from '../database/entities/task.entity' +import { StepEntity } from '../database/entities/step.entity' +import { EntityModule } from '../api/entity.module' + +@Module({ + imports: [ + TypeOrmModule.forFeature([TaskEntity, StepEntity]), + ConfigModule.forRoot({ + load: [() => appConfig], // define a function that returns the configuration object + ignoreEnvFile: true, // we don't need .env files because we're using environment variables + isGlobal: true, // we can access the configuration everywhere in the app + cache: true // cache the configuration + }), + EntityModule + ], + controllers: [ProcessorController], + providers: [AgentService, ConfigService] +}) +export class ProcessorModule {} diff --git a/src/routes.ts b/src/routes.ts index 7c8551f..5560a05 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -1,6 +1,6 @@ import { Routes } from '@nestjs/core' -import { InfoModule } from './modules/info/info.module' -import { AgentModule } from './modules/agent/agent.module' +import { InfoModule } from './api/info/info.module' +import { AgentModule } from './api/agent/agent.module' export const routes: Routes = [ { diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts index 6618f90..05b55d1 100644 --- a/test/app.e2e-spec.ts +++ b/test/app.e2e-spec.ts @@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing' import { INestApplication } from '@nestjs/common' import { ConfigModule } from '@nestjs/config' import * as request from 'supertest' -import { InfoModule } from '../src/modules/info/info.module' +import { InfoModule } from '../src/api/info/info.module' describe('InfoController (e2e)', () => { let app: INestApplication diff --git a/tsconfig.json b/tsconfig.json index adb614c..1d68c51 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, - "target": "es2017", + "target": "es2020", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", diff --git a/yarn.lock b/yarn.lock index 17cf652..5a737e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4277,7 +4277,7 @@ node-emoji@1.11.0: dependencies: lodash "^4.17.21" -node-fetch@^2.6.1: +node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -5743,6 +5743,7 @@ word-wrap@^1.2.5: integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + name wrap-ansi-cjs version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==