Реализовать загрузку 3D-моделей в Scene manager и выгрузку файлов для запуска симуляции

This commit is contained in:
IDONTSUDO 2024-02-04 13:12:53 +00:00 committed by Igor Brylyov
parent 11ca9cdb5e
commit 3fefd60b72
109 changed files with 2726 additions and 1190 deletions

1
server/.gitignore vendored
View file

@ -9,3 +9,4 @@ package-lock.json
build/
model_create.ts
public
p.ts

3
server/.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"cSpell.words": ["fileupload", "Metadatas", "readir"]
}

View file

@ -4,7 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
"pretest": "tsc",
"test:dev": "NODE_ENV=test_dev tsc-watch --onSuccess 'ts-node ./build/test/test.js'",
"test:unit": "NODE_ENV=unit tsc-watch --onSuccess 'ts-node ./build/test/test.js'",
"test:e2e": "NODE_ENV=e2e tsc-watch --onSuccess 'ts-node ./build/test/test.js'",
"dev": "NODE_ENV=dev tsc-watch --onSuccess 'ts-node ./build/src/main.js'"

View file

@ -1,11 +1,11 @@
import express from "express";
import { Routes } from "../interfaces/router";
import cors from "cors";
import fileUpload from "express-fileupload";
import { Routes } from "../interfaces/router";
import { Server } from "socket.io";
import { createServer } from "http";
import { SocketSubscriber } from "./socket_controller";
import { dirname } from "path";
import fileUpload from "express-fileupload";
import { SetLastActivePipelineToRealTimeServiceScenario } from "../scenarios/set_active_pipeline_to_realtime_service_scenario";
import { CheckAndCreateStaticFilesFolderUseCase } from "../usecases/check_and_create_static_files_folder_usecase";
import { DataBaseConnectUseCase } from "../usecases/database_connect_usecase";
@ -13,7 +13,7 @@ import { TypedEvent } from "../helpers/typed_event";
export enum ServerStatus {
init = "init",
finished = "finshed",
finished = "finished",
error = "error",
}
export enum Environment {
@ -85,7 +85,7 @@ export class App extends TypedEvent<ServerStatus> {
this.app.use(cors());
this.app.use(express.json());
this.app.use(express.urlencoded({ extended: true }));
this.app.use(express.static("public"));
this.app.use(express.static(App.staticFilesStoreDir()));
this.app.use(
fileUpload({
@ -118,6 +118,7 @@ export class App extends TypedEvent<ServerStatus> {
static staticFilesStoreDir = () => {
const dir = dirname(__filename);
const rootDir = dir.slice(0, dir.length - 20);
return rootDir + "public/";
};
}

View file

@ -2,20 +2,20 @@ import { validationModelMiddleware } from "../middlewares/validation_model";
import { Result } from "../helpers/result";
import { Router, Request, Response } from "express";
import { IRouteModel, Routes } from "../interfaces/router";
import { CoreValidation } from "../validations/core_validation";
export type HttpMethodType = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "PATCH" | "HEAD";
export type ResponseBase = Promise<Result<any, any>>;
export abstract class CallbackStrategyWithEmpty {
abstract call(): ResponseBase;
}
export abstract class CallbackStrategyWithValidationModel<V> {
abstract validationModel: V;
abstract call(a: V): ResponseBase;
abstract call(model: V): ResponseBase;
}
export abstract class CallbackStrategyWithIdQuery {
abstract idValidationExpression: RegExp | null;
abstract idValidationExpression: CoreValidation;
abstract call(id: string): ResponseBase;
}
export abstract class CallBackStrategyWithQueryPage {
@ -25,7 +25,8 @@ export abstract class CallBackStrategyWithQueryPage {
export abstract class CallbackStrategyWithFileUpload {
abstract checkingFileExpression: RegExp;
abstract call(file: File): ResponseBase;
abstract idValidationExpression: CoreValidation;
abstract call(file: File, id: string, description: string): ResponseBase;
}
interface ISubSetFeatureRouter<T> {
@ -84,7 +85,24 @@ export class CoreHttpController<V> implements ICoreHttpController {
throw Error("needs to be implimed");
}
if (el.fn instanceof CallbackStrategyWithIdQuery) {
throw Error("needs to be implimed");
if (req.query.id === undefined) {
res.status(400).json("request query id is null, need query id ?id={id:String}");
return;
}
if (el.fn.idValidationExpression !== undefined) {
if (!el.fn.idValidationExpression.regExp.test(req.query.id)) {
res
.status(400)
.json(
`request query id must fall under the pattern: ${el.fn.idValidationExpression.regExp} message: ${el.fn.idValidationExpression.message} `
);
return;
} else {
await this.responseHelper(res, el.fn.call(req.query.id));
}
} else {
await this.responseHelper(res, el.fn.call(req["files"]["file"]));
}
}
if (el.fn instanceof CallBackStrategyWithQueryPage) {
throw Error("needs to be implimed");
@ -99,17 +117,32 @@ export class CoreHttpController<V> implements ICoreHttpController {
res.status(400).json("need files to form-data request");
return;
}
if (req["files"]["file"] === undefined) {
res.status(400).json("need file to form data request");
return;
}
if (req.query.description === undefined) {
res
.status(400)
.json("request query description is null, need query description &description={description:String}");
return;
}
if (req.query.id === undefined) {
res.status(400).json("request query id is null, need query id ?id={id:String}");
return;
}
if (!el.fn.idValidationExpression.regExp.test(req.query.id)) {
res.status(400).json(el.fn.idValidationExpression.message);
return;
}
if (el.fn instanceof CallbackStrategyWithFileUpload) {
if (!el.fn.checkingFileExpression.test(req["files"]["file"]["name"])) {
res.status(400).json("a file with this extension is expected: " + String(el.fn.checkingFileExpression));
return;
}
}
await this.responseHelper(res, el.fn.call(req["files"]["file"]));
await this.responseHelper(res, el.fn.call(req["files"]["file"], req.query.id, req.query.description));
}
});
});

View file

@ -1,7 +1,10 @@
import { NixStoreManagerPresentation } from "../../features/nix_store_manager/nix_store_manager";
import { PipelinePresentation } from "../../features/pipelines/pipeline_presentation";
import { ProcessPresentation } from "../../features/process/process_presentation";
import { ProjectInstancePresentation } from "../../features/project_instance/project_instance_presentation";
import {
ProjectInstancePresentation,
RobossemblerAssetsPresentation,
} from "../../features/project_instance/project_instance_presentation";
import { ProjectsPresentation } from "../../features/projects/projects_presentation";
import { RealTimePresentation } from "../../features/realtime/realtime_presentation";
import { TriggerPresentation } from "../../features/triggers/triggers_presentation";
@ -21,6 +24,7 @@ export const httpRoutes: Routes[] = [
new RealTimePresentation(),
new ProjectInstancePresentation(),
new NixStoreManagerPresentation(),
new RobossemblerAssetsPresentation(),
]
.concat(routersImplementPureCrud)
.map((el) => el.call());

View file

@ -0,0 +1,8 @@
export const BufferExtensions = () => {
if (Buffer.joinBuffers === undefined) {
Buffer.prototype.joinBuffers = function (buffers: Array<Buffer>, delimiter = " ") {
const d = Buffer.from(delimiter);
return buffers.reduce((prev, b) => Buffer.concat([prev, d, b]));
};
}
};

View file

@ -1,4 +1,5 @@
import { ArrayExtensions } from "./array";
import { BufferExtensions } from "./buffer";
import { StringExtensions } from "./string";
declare global {
@ -10,15 +11,20 @@ declare global {
isEmpty(): boolean;
isNotEmpty(): boolean;
}
interface BufferConstructor {
joinBuffers(buffers: Array<Buffer>, delimiter?: string);
}
interface String {
isEmpty(): boolean;
isNotEmpty(): boolean;
lastElement(): string;
hasPattern(pattern: string): boolean;
hasNoPattern(pattern: string): boolean;
pathNormalize(): string;
}
}
export const extensions = () => {
ArrayExtensions();
StringExtensions();
BufferExtensions();
};

View file

@ -5,6 +5,11 @@ export const StringExtensions = () => {
return this.length === 0;
};
}
if ("".pathNormalize === undefined) {
String.prototype.pathNormalize = function () {
return this.replace("//", "/");
};
}
if ("".isNotEmpty === undefined) {
// eslint-disable-next-line no-extend-native
String.prototype.isNotEmpty = function () {
@ -27,3 +32,6 @@ export const StringExtensions = () => {
};
}
};
// python3 /Users/idontsudo/framework/path.py --path /Users/idontsudo/webservice/server/build/public/0a3422cc-f2e3-4abc-87d8-ae13b8b6d26d/ --env /Users/idontsudo/framework/cad_generation/env.json
// python3 /Users/idontsudo/framework/path.py --path /Users/idontsudo/webservice/server/build/public/0a3422cc-f2e3-4abc-87d8-ae13b8b6d26d/ --env /Users/idontsudo/framework/cad_generation/env.json
// /Users/idontsudo/Desktop/FreeCAD.app/Contents/MacOS/FreeCAD /Users/idontsudo/framework/cad_generation/main.py

View file

@ -8,8 +8,19 @@ export interface Disposable {
export class TypedEvent<T> {
private listeners: Listener<T>[] = [];
public listenersOnces: Listener<T>[] = [];
waitedEvent(predicate: (e: T) => boolean) {
return new Promise<T>((resolve, _reject) => {
this.on((e) => {
const isContinueWatching = predicate(e);
if (!isContinueWatching) {
resolve(e);
}
});
});
}
on = (listener: Listener<T>): Disposable => {
this.listeners.push(listener);
return {

View file

@ -17,7 +17,9 @@ export interface WorkerDataExec {
process.on("message", async (message) => {
const workerData = message as WorkerDataExec;
if (workerData.type == WorkerType.SPAWN) {
const subprocess = cp.spawn(workerData.command, workerData.cliArgs, {
// Maybe error
// const subprocess = cp.spawn(workerData.command, workerData.cliArgs, {
const subprocess = cp.spawn(workerData.command, {
cwd: workerData.execPath,
});

View file

@ -1,13 +0,0 @@
// export class Payload<T>{
// model: T | undefined
// query:string | undefined
// setModel(model:T){
// this.model = model
// }
// setQuery(query:string){
// this.query = query
// }
// isEmpty(){
// return this.model != undefined || this.query != undefined
// }
// }

View file

@ -1,12 +1,6 @@
import { RequestHandler } from "express";
export const validationMiddleware = (
type: any,
value = "body",
skipMissingProperties = false,
whitelist = true,
forbidNonWhitelisted = true
): RequestHandler => {
export const validationMiddleware = (): RequestHandler => {
// TODO:(IDONTSUDO) need TOKEN
// return nextTick
return (req, res, next) => {};

View file

@ -6,7 +6,7 @@ export const validationModelMiddleware = (
type: any,
value = "body",
skipMissingProperties = false,
whitelist = true,
whitelist = false,
forbidNonWhitelisted = true
): RequestHandler => {
return (req, res, next) => {

View file

@ -1,24 +1,27 @@
export class ActivePipeline {
pipelineIsRunning: boolean;
projectUUID?: string | null;
projectId?: string | null;
lastProcessCompleteCount: number | null;
error: any;
rootDir: string;
path: string;
constructor(
pipelineIsRunning: boolean,
lastProcessCompleteCount: number | null,
error: any,
path: string | null,
projectUUID?: string | null
projectId: string | null,
rootDir: string | null
) {
this.pipelineIsRunning = pipelineIsRunning;
this.projectUUID = projectUUID;
this.projectId = projectId;
this.lastProcessCompleteCount = lastProcessCompleteCount;
this.error = error;
this.path = path;
this.rootDir = rootDir;
}
static empty() {
return new ActivePipeline(false, null, null, null, null);
return new ActivePipeline(false, null, null, null, null, null);
}
}

View file

@ -4,10 +4,15 @@ extensions();
export class ExecError extends Error {
static isExecError(e: any): ExecError | void {
if ("type" in e && "script" in e && "unixTime" in e && "error" in e) {
return new ExecError(e.type, e.event, e.data);
try {
if (e) {
if ("type" in e && "script" in e && "unixTime" in e && "error" in e) {
return new ExecError(e.type, e.event, e.data);
}
}
} catch (error) {
console.log(error);
}
return;
}
script: string;
unixTime: number;
@ -34,10 +39,15 @@ export class SpawnError extends Error {
this.unixTime = Date.now();
}
static isError(errorType: any): SpawnError | void {
if ("command" in errorType && "error" in errorType && "execPath" in errorType) {
return new SpawnError(errorType.command, errorType.execPath, errorType.error);
try {
if (errorType) {
if ("command" in errorType && "error" in errorType && "execPath" in errorType) {
return new SpawnError(errorType.command, errorType.execPath, errorType.error);
}
}
} catch (error) {
console.log(error);
}
return;
}
}

View file

@ -10,9 +10,14 @@ export class ExecutorResult {
this.data = data;
}
static isExecutorResult(value: any): void | ExecutorResult {
if ("type" in value && "event" in value && "data" in value) {
return new ExecutorResult(value.type, value.event, value.data);
try {
if (value) {
if ("type" in value && "event" in value && "data" in value) {
return new ExecutorResult(value.type, value.event, value.data);
}
}
} catch (error) {
console.log(error);
}
return;
}
}

View file

@ -0,0 +1,160 @@
import { IsArray, IsEnum, IsNumber, IsOptional, IsString, ValidateNested } from "class-validator";
import { Type } from "class-transformer";
export class Gravity {
@IsNumber()
x: number;
@IsNumber()
y: number;
@IsNumber()
z: number;
}
export class Pose {
@IsNumber()
x: number;
@IsNumber()
y: number;
@IsNumber()
z: number;
@IsNumber()
roll: number;
@IsNumber()
pitch: number;
@IsNumber()
yaw: number;
}
export class Position {
@IsNumber()
x: number;
@IsNumber()
y: number;
@IsNumber()
z: number;
}
export enum InstanceType {
RGB_CAMERA = "rgb_camera",
SCENE_SIMPLE_OBJECT = "scene_simple_object",
}
abstract class CoreInstances {}
export class Instance extends CoreInstances {
@IsEnum(InstanceType)
instanceType: InstanceType;
@Type(() => Position)
position: Position;
@IsArray()
quaternion: number[];
@IsOptional()
@IsString()
instanceAt: null | string = null;
}
export class SceneSimpleObject extends Instance {}
export class InstanceRgbCamera extends Instance {
@IsString()
cameraLink: string;
@IsString()
topicCameraInfo: string;
@IsOptional()
@IsString()
topicDepth: string | null;
@IsString()
topicImage: string;
}
export class Asset {
@IsString()
name: string;
@IsString()
ixx: string;
@IsString()
ixy: string;
@IsString()
ixz: string;
@IsString()
iyy: string;
@IsString()
izz: string;
@IsString()
mass: string;
@IsString()
posX: string;
@IsString()
posY: string;
@IsString()
posZ: string;
@IsString()
eulerX: string;
@IsString()
eulerY: string;
@IsString()
eulerZ: string;
@IsString()
iyz: string;
@IsString()
meshPath: string;
@IsString()
friction: string;
@IsString()
centerMassX: string;
@IsString()
centerMassY: string;
@IsString()
centerMassZ: string;
}
export class Physics {
@IsString()
engine_name: string;
@Type(() => Gravity)
gravity: Gravity;
}
export class RobossemblerAssets {
@ValidateNested()
@Type(() => Asset)
assets: Asset[];
@IsArray()
@Type(() => Instance, {
discriminator: {
property: "type",
subTypes: [
{ value: InstanceRgbCamera, name: InstanceType.RGB_CAMERA },
{ value: SceneSimpleObject, name: InstanceType.SCENE_SIMPLE_OBJECT },
],
},
keepDiscriminatorProperty: true,
})
instances: Instance[];
@IsOptional()
@ValidateNested()
@Type(() => Physics)
physics: Physics;
convertLocalPathsToServerPaths(server_address: string): RobossemblerAssets {
this.assets = this.assets.map((el) => {
el.meshPath = server_address + el.meshPath;
return el;
});
return this;
}
getAssetPath(assetName: string): string {
const findElement = this.assets.find((el) => el.name === assetName);
if (findElement === undefined) {
throw new Error("RobossemblerAssets.getAssetPath not found asset by name:" + assetName);
}
return findElement.meshPath;
}
getAssetAtInstance(instanceAt: string): Asset {
return this.assets.filter((el) => el.name === instanceAt)[0];
}
}

View file

@ -0,0 +1,3 @@
export enum StaticFiles {
robossembler_assets = "robossembler_assets.json",
}

View file

@ -0,0 +1,43 @@
import * as fs from "fs";
import { promisify } from "node:util";
export class FileSystemRepository {
public createDir = promisify(fs.mkdir);
public lsStat = promisify(fs.lstat);
public writeFileAsync = promisify(fs.writeFile);
public dirIsExists = promisify(fs.exists);
public stat = promisify(fs.stat);
public readFileAsync = promisify(fs.readFile);
public readdir = promisify(fs.readdir);
async readFileAtBuffer(path: string): Promise<Buffer> {
if ((await this.lsStat(path)).isDirectory()) {
return (
await this.readdir(path, {
encoding: "buffer",
})
).reduce((accumulator, currentValue) => Buffer.joinBuffers([accumulator, currentValue]), Buffer.from(""));
}
return await this.readFileAsync(path);
}
readDirRecursive(path: string, filesToDir: string[] = []): string[] {
const files = fs.readdirSync(path);
files.forEach((file) => {
let filePath = "";
if (path[path.length - 1] !== "/") {
filePath = `${path}/${file}`;
} else {
filePath = `${path}${file}`;
}
const stats = fs.statSync(filePath);
if (stats.isDirectory()) {
this.readDirRecursive(filePath, filesToDir);
} else {
filesToDir.push(file);
}
});
return filesToDir;
}
}

View file

@ -1,31 +0,0 @@
import * as fs from "fs";
import { promisify } from "node:util";
export const readFileAsync = promisify(fs.readFile);
export const readdir = promisify(fs.readdir);
export const stat = promisify(fs.stat);
export const lsStat = promisify(fs.lstat);
export const createDir = promisify(fs.mkdir);
export const dirIsExists = promisify(fs.exists);
export const writeFileAsync = promisify(fs.writeFile);
export function readDirRecursive(path: string, filesToDir: string[] = []) {
const files = fs.readdirSync(path);
files.forEach((file) => {
let filePath = "";
if (path[path.length - 1] !== "/") {
filePath = `${path}/${file}`;
} else {
filePath = `${path}${file}`;
}
const stats = fs.statSync(filePath);
if (stats.isDirectory()) {
readDirRecursive(filePath, filesToDir);
} else {
filesToDir.push(file);
}
});
return filesToDir;
}

View file

@ -0,0 +1,33 @@
import { ClassConstructor, plainToInstance } from "class-transformer";
import { ReadFileAndParseJsonUseCase } from "../usecases/read_file_and_parse_json";
import { Result } from "../helpers/result";
import { validate, ValidationError } from "class-validator";
const skipMissingProperties = false,
whitelist = false,
forbidNonWhitelisted = true;
export class ReadingJsonFileAndConvertingToInstanceClassScenario<T> {
model: ClassConstructor<T>;
constructor(cls: ClassConstructor<T>) {
this.model = cls;
}
call = async (path: string): Promise<Result<string, T>> => {
try {
const result = await new ReadFileAndParseJsonUseCase().call(path);
if (result.isFailure()) {
return result.forward();
}
const json = result.value;
const model = plainToInstance(this.model, json);
const errors = await validate(model as object, { skipMissingProperties, whitelist, forbidNonWhitelisted });
if (errors.length > 0) {
const message = errors.map((error: ValidationError) => Object.values(error.constraints)).join(", ");
return Result.error("ReadingJsonFileAndConvertingToInstanceClassScenario:" + message);
} else {
return Result.ok(model as T);
}
} catch (error) {
return Result.error("ReadingJsonFileAndConvertingToInstanceClassScenario" + String(error));
}
};
}

View file

@ -4,20 +4,29 @@ import {
} from "../../features/project_instance/models/project_instance_database_model";
import { pipelineRealTimeService } from "../../features/realtime/realtime_presentation";
import { App } from "../controllers/app";
import { CreateFolderUseCase } from "../usecases/crete_folder_usecase";
import { CreateFolderUseCase } from "../usecases/create_folder_usecase";
import { SearchDataBaseModelUseCase } from "../usecases/search_database_model_usecase";
export class SetLastActivePipelineToRealTimeServiceScenario {
call = async (): Promise<void> => {
const result = await new SearchDataBaseModelUseCase<IProjectInstanceModel>(ProjectInstanceDbModel).call({
isActive: true,
});
if (result.isSuccess()) {
const projectModel = result.value;
const projectPath = App.staticFilesStoreDir() + result.value.rootDir + "/";
await new CreateFolderUseCase().call(projectPath);
pipelineRealTimeService.setPipelineDependency(projectModel.project.pipelines, projectPath, projectModel._id);
}
return (
await new SearchDataBaseModelUseCase<IProjectInstanceModel>(ProjectInstanceDbModel).call({
isActive: true,
})
).fold(
async (projectModel) => {
const projectPath = App.staticFilesStoreDir() + projectModel.rootDir + "/";
await new CreateFolderUseCase().call(projectPath);
pipelineRealTimeService.setPipelineDependency(
projectModel.project.pipelines,
projectPath,
projectModel._id,
projectModel.rootDir
);
},
async (_e) => {
console.log("not found active pipeline");
}
);
};
}

View file

@ -21,53 +21,61 @@ export class ExecutorProgramService
constructor(execPath: string, maxTime: number | null = null) {
super();
this.execPath = execPath;
this.execPath = execPath.pathNormalize();
this.maxTime = maxTime;
}
private async workerExecuted(command: string, workerType: WorkerType, args: Array<string> | undefined = undefined) {
cluster.setupPrimary({
exec: "/Users/idontsudo/Desktop/testdeck-mocha-seed/server/build/src/core/helpers/worker_computed.js",
});
try {
cluster.setupPrimary({
exec: __dirname + "/../helpers/worker_computed.js",
});
const worker = cluster.fork();
const worker = cluster.fork();
await delay(300);
await delay(300);
this.worker = worker;
this.worker = worker;
const workerDataExec: WorkerDataExec = {
command: command,
execPath: this.execPath,
type: workerType,
cliArgs: args,
};
worker.send(workerDataExec);
worker.on("message", (e) => {
const spawnError = SpawnError.isError(e);
if (spawnError instanceof SpawnError) {
this.emit(Result.error(spawnError));
return;
const workerDataExec: WorkerDataExec = {
command: command,
execPath: this.execPath,
type: workerType,
cliArgs: args,
};
worker.send(workerDataExec);
worker.on("message", (e) => {
const spawnError = SpawnError.isError(e);
if (spawnError instanceof SpawnError) {
this.emit(Result.error(spawnError));
return;
}
const execError = ExecError.isExecError(e);
if (execError instanceof ExecError) {
this.emit(Result.error(execError));
return;
}
const executorResult = ExecutorResult.isExecutorResult(e);
if (executorResult instanceof ExecutorResult) {
this.emit(Result.ok(executorResult));
return;
}
});
if (this.maxTime != null) {
setTimeout(() => {
this.worker.kill();
this.emit(
Result.error(
WorkerType.EXEC ? new ExecError(command, "timeout err") : new SpawnError(command, "timeout err")
)
);
}, this.maxTime!);
}
const executorResult = ExecutorResult.isExecutorResult(e);
if (executorResult instanceof ExecutorResult) {
this.emit(Result.ok(executorResult));
return;
}
const execError = ExecError.isExecError(e);
if (execError instanceof ExecError) {
this.emit(Result.error(execError));
return;
}
});
if (this.maxTime != null) {
setTimeout(() => {
this.worker.kill();
this.emit(
Result.error(WorkerType.EXEC ? new ExecError(command, "timeout err") : new SpawnError(command, "timeout err"))
);
}, this.maxTime!);
} catch (error) {
console.log(error);
}
}
@ -78,6 +86,7 @@ export class ExecutorProgramService
return;
}
this.workerExecuted(command, WorkerType.SPAWN, args);
return;
}
}

View file

@ -5,23 +5,7 @@ import { BinaryLike } from "crypto";
import { EventsFileChanger, MetaDataFileManagerModel } from "../models/meta_data_file_manager_model";
import { Result } from "../helpers/result";
import { TypedEvent } from "../helpers/typed_event";
import { lsStat, readFileAsync, readdir, stat } from "../repository/fs";
function joinBuffers(buffers: Array<Buffer>, delimiter = " ") {
const d = Buffer.from(delimiter);
return buffers.reduce((prev, b) => Buffer.concat([prev, d, b]));
}
async function readFileAtBuffer(path: string): Promise<Buffer> {
if ((await lsStat(path)).isDirectory()) {
return (
await readdir(path, {
encoding: "buffer",
})
).reduce((accumulator, currentValue) => joinBuffers([accumulator, currentValue]), Buffer.from(""));
}
return await readFileAsync(path);
}
import { FileSystemRepository } from "../repository/file_system_repository";
function md5(content: Buffer | BinaryLike): Promise<string> {
return new Promise((resolve, _reject) => {
@ -41,12 +25,14 @@ export class FilesChangeNotifierService
extends TypedEvent<Result<Error, IHashesCache>>
implements IFilesChangeNotifierService
{
fileSystemRepository: FileSystemRepository;
watcher!: fs.FSWatcher;
directory: string;
constructor(directory: string) {
super();
this.directory = directory;
this.init();
this.fileSystemRepository = new FileSystemRepository();
}
hashes: IHashesCache = {};
async init() {
@ -56,18 +42,18 @@ export class FilesChangeNotifierService
}
}
async setHash(file: string) {
const data = await readFileAsync(file);
const data = await this.fileSystemRepository.readFileAsync(file);
const md5Current = await md5(data);
this.hashes[file] = new MetaDataFileManagerModel(file, md5Current, EventsFileChanger.static);
this.emit(Result.ok(this.hashes));
}
async getFiles(dir: string): Promise<string | any[]> {
const subdirs = await readdir(dir);
const subdirs = await new FileSystemRepository().readdir(dir);
const files = await Promise.all(
subdirs.map(async (subdir) => {
const res = resolve(dir, subdir);
return (await stat(res)).isDirectory() ? this.getFiles(res) : res;
return (await this.fileSystemRepository.stat(res)).isDirectory() ? this.getFiles(res) : res;
})
);
return files.reduce((a: string | any[], f: any) => a.concat(f), []);
@ -85,7 +71,7 @@ export class FilesChangeNotifierService
fsWait = false;
}, 100);
try {
const file = await readFileAtBuffer(filePath);
const file = await this.fileSystemRepository.readFileAtBuffer(filePath);
const md5Current = await md5(file);
if (md5Current === md5Previous) {
return;

View file

@ -3,7 +3,7 @@ import { ExecError } from "../models/exec_error_model";
import { ExecutorResult } from "../models/executor_result";
import { ActivePipeline } from "../models/active_pipeline_model";
import { IPipeline } from "../models/process_model";
import { Iteration } from "./stack_service";
import { Iteration, StackService } from "./stack_service";
export class PipelineRealTimeService extends TypedEvent<ActivePipeline> {
status: ActivePipeline;
@ -32,9 +32,8 @@ export class PipelineRealTimeService extends TypedEvent<ActivePipeline> {
this.iterationLogSaver(iteration);
}
iterationLogSaver(iteration: Iteration): void {
iterationLogSaver(_iteration: Iteration): void {
// throw new Error("Method not implemented.");
// citeration.result.data
}
iterationErrorObserver(iteration: Iteration): void {
@ -59,15 +58,17 @@ export class PipelineRealTimeService extends TypedEvent<ActivePipeline> {
this.status.pipelineIsRunning = false;
}
}
setPipelineDependency(pipelineModels: IPipeline[], path: string, projectUUID: string) {
setPipelineDependency(pipelineModels: IPipeline[], path: string, projectId: string, rootDir: string) {
this.pipelineModels = pipelineModels;
this.status["projectUUID"] = projectUUID;
this.status["projectId"] = projectId;
this.status["path"] = path;
this.status["rootDir"] = rootDir;
}
runPipeline(): void {
// const stack = new StackService(this.pipelineModels, this.path);
// this.status["pipelineIsRunning"] = true;
// stack.on(this.pipelineSubscriber);
// stack.call();
const stack = new StackService(this.pipelineModels, this.status.path);
this.status["pipelineIsRunning"] = true;
stack.on(this.pipelineSubscriber);
stack.call();
}
}

View file

@ -11,7 +11,7 @@ import { Trigger } from "../../features/triggers/models/trigger_database_model";
export interface Iteration {
hashes: IHashesCache | null;
process: IPipeline;
pipeline: IPipeline;
result?: ExecError | SpawnError | ExecutorResult;
}
@ -36,80 +36,61 @@ export class StackService extends TypedEvent<Iteration[]> implements IStackServi
el = this.commandHandler(el);
this.callStack.push({
hashes: null,
process: el,
pipeline: el,
});
}
}
private commandHandler(processMetaData: IPipeline) {
processMetaData.process.command = processMetaData.process.command.replace("$PATH", this.path);
processMetaData.process.command = processMetaData.process.command.replace("$PATH", this.path).pathNormalize();
return processMetaData;
}
public async call() {
let inc = 0;
for await (const el of this.callStack!) {
await this.execStack(inc, el);
inc += 1;
this.callStack.map(async (el, index) => {
await this.execStack(index, el);
this.emit(this.callStack);
}
});
}
async execStack(stackNumber: number, stackLayer: Iteration): Promise<void | boolean> {
const executorService = new ExecutorProgramService(this.path);
executorService.call(stackLayer.process.process.type, stackLayer.process.process.command);
executorService.call(stackLayer.pipeline.process.type, stackLayer.pipeline.process.command);
const filesChangeNotifierService = new FilesChangeNotifierService(this.path);
filesChangeNotifierService.call();
const result = await this.waitEvent<Result<ExecError | SpawnError, ExecutorResult>>(executorService);
await delay(100);
if (result.isSuccess()) {
this.callStack[stackNumber].result = result.value;
this.callStack[stackNumber].hashes = filesChangeNotifierService.hashes;
const triggerResult = await this.triggerExec(stackLayer.process.trigger, stackNumber);
triggerResult.fold(
(s) => {
s;
},
(e) => {
e;
const result = await executorService.waitedEvent((event: Result<ExecError | SpawnError, ExecutorResult>) => {
event.map((value) => {
if (value.event === EXEC_EVENT.END) {
return true;
}
);
}
});
return false;
});
await delay(100);
result.map(async (el) => {
this.callStack.at(stackNumber).result = el;
this.callStack.at(stackNumber).hashes = filesChangeNotifierService.hashes;
(await this.triggerExec(stackLayer.pipeline.trigger, stackNumber)).map(() => {
filesChangeNotifierService.cancel();
});
});
filesChangeNotifierService.cancel();
return;
}
public waitEvent<T>(stream: TypedEvent<T>): Promise<T> {
const promise = new Promise<T>((resolve, reject) => {
const addListener = () => {
stream.on((e) => {
const event = e as Result<ExecError | SpawnError, ExecutorResult>;
event.fold(
(s) => {
if (s.event === EXEC_EVENT.END) {
resolve(e);
}
},
(e) => {
reject(e);
}
);
});
};
addListener();
});
return promise;
}
private async triggerExec(trigger: Trigger | null, stackNumber: number): Promise<Result<boolean, boolean>> {
if (trigger !== null) {
const hashes = this.callStack[stackNumber].hashes;
if (hashes != null) {
return await new TriggerService(trigger, hashes, this.path).call();
private async triggerExec(trigger: Trigger | null, stackNumber: number): Promise<Result<Error, void>> {
try {
if (trigger !== null) {
const hashes = this.callStack[stackNumber].hashes;
if (hashes != null) {
await new TriggerService(trigger, hashes, this.path).call();
}
throw new Error("Hashes is null");
}
throw new Error("Hashes is null");
return Result.ok();
} catch (error) {
return Result.error(error);
}
return Result.ok();
}
}

View file

@ -1,10 +1,15 @@
import { App } from "../controllers/app";
import { dirIsExists } from "../repository/fs";
import { CreateFolderUseCase } from "./crete_folder_usecase";
import { FileSystemRepository } from "../repository/file_system_repository";
import { CreateFolderUseCase } from "./create_folder_usecase";
export class CheckAndCreateStaticFilesFolderUseCase {
fileSystemRepository: FileSystemRepository;
constructor() {
this.fileSystemRepository = new FileSystemRepository();
}
call = async (): Promise<void> => {
if (await dirIsExists(App.staticFilesStoreDir())) {
if (await this.fileSystemRepository.dirIsExists(App.staticFilesStoreDir())) {
return;
}
const createFolderUseCase = await new CreateFolderUseCase().call(App.staticFilesStoreDir());

View file

@ -1,10 +1,15 @@
import { Result } from "../helpers/result";
import { writeFileAsync } from "../repository/fs";
import { FileSystemRepository } from "../repository/file_system_repository";
export class CreateFileUseCase {
fileSystemRepository: FileSystemRepository;
constructor() {
this.fileSystemRepository = new FileSystemRepository();
}
async call(path: string, buffer: Buffer): Promise<Result<Error, void>> {
try {
await writeFileAsync(path, buffer);
await this.fileSystemRepository.writeFileAsync(path.pathNormalize(), buffer);
return Result.ok();
} catch (err) {
return Result.error(err as Error);

View file

@ -0,0 +1,21 @@
import { Result } from "../helpers/result";
import { FileSystemRepository } from "../repository/file_system_repository";
export class CreateFolderUseCase {
fileSystemRepository: FileSystemRepository;
constructor() {
this.fileSystemRepository = new FileSystemRepository();
}
call = async (path: string): Promise<Result<Error, string>> => {
try {
if (await this.fileSystemRepository.dirIsExists(path)) {
return Result.ok("ok");
}
await this.fileSystemRepository.createDir(path);
return Result.ok("ok");
} catch (error) {
return Result.error(error as Error);
}
};
}

View file

@ -1,17 +0,0 @@
import { Result } from "../helpers/result";
import { dirIsExists, createDir } from "../repository/fs";
export class CreateFolderUseCase {
call = async (path: string): Promise<Result<Error, string>> => {
try {
if (await dirIsExists(path)) {
return Result.error(new Error("createFolderUseCase create dir "));
}
await createDir(path);
return Result.ok("ok");
} catch (error) {
return Result.error(error as Error);
}
};
}

View file

@ -0,0 +1,11 @@
export class FindPredicateModelAndUpdateDatabaseModelUseCase<D> {
databaseModel: D;
constructor(model) {
this.databaseModel = model;
}
async call(conditions: Partial<D>, update: Partial<D>) {
const dbModel = this.databaseModel as any;
dbModel.findOneAndUpdate(conditions, update);
}
}

View file

@ -0,0 +1,7 @@
import { Result } from "../helpers/result";
export class GetServerAddressUseCase {
call = (): Result<string, string> => {
return Result.ok("http://localhost:4001");
};
}

View file

@ -0,0 +1,25 @@
import { Result } from "../helpers/result";
import { FileSystemRepository } from "../repository/file_system_repository";
export class ReadFileAndParseJsonUseCase {
fileSystemRepository: FileSystemRepository;
constructor() {
this.fileSystemRepository = new FileSystemRepository();
}
async call<T>(path: string): Promise<Result<string, T>> {
try {
if (RegExp(path).test("^(.+)/([^/]+)$")) {
return Result.error(`ReadFileAndParseJsonUseCase got the bad way: ${path}`);
}
const file = await this.fileSystemRepository.readFileAsync(path);
try {
return Result.ok(JSON.parse(file.toString()));
} catch {
return Result.error(`ReadFileAndParseJsonUseCase is not json type file parse path: ${path}`);
}
} catch (error) {
return Result.error(`ReadFileAndParseJsonUseCase error:${error}`);
}
}
}

View file

@ -0,0 +1,13 @@
import { Result } from "../helpers/result";
import { FileSystemRepository } from "../repository/file_system_repository";
export class WriteFileSystemFileUseCase {
call = async (path: string, fileData: string): Promise<Result<string, void>> => {
try {
await new FileSystemRepository().writeFileAsync(path, fileData);
return Result.ok(undefined);
} catch (error) {
return Result.error(`WriteFileSystemFileUseCase error: ${error}`);
}
};
}

View file

@ -0,0 +1,8 @@
interface IMessage {
message: string;
}
export abstract class CoreValidation {
abstract regExp: RegExp;
abstract message: IMessage;
}

View file

@ -0,0 +1,6 @@
import { CoreValidation } from "./core_validation";
export class MongoIdValidation extends CoreValidation {
regExp = RegExp("^[0-9a-fA-F]{24}$");
message = { message: "is do not mongo db object uuid" };
}

View file

@ -1,41 +0,0 @@
import { IsMongoId, IsEnum } from "class-validator";
import { Schema, model } from "mongoose";
import { TriggerModel, triggerSchema } from "../triggers/trigger_model";
import { schemaProcess } from "../process/process_model";
import { StackGenerateType } from "../../core/models/process_model";
export const PipelineSchema = new Schema({
process: {
type: Schema.Types.ObjectId,
ref: schemaProcess,
autopopulate: true,
default: null,
},
trigger: {
type: Schema.Types.ObjectId,
ref: triggerSchema,
autopopulate: true,
default: null,
},
command: {
type: String,
},
}).plugin(require("mongoose-autopopulate"));
export const schemaPipeline = "Pipeline";
export const PipelineDBModel = model<PipelineModel>(schemaPipeline, PipelineSchema);
export class PipelineModel {
@IsMongoId()
public process: PipelineModel;
@IsMongoId()
//TODO(IDONTSUDO):NEED OPTION DECORATOR??
public trigger: TriggerModel;
public env = null;
@IsEnum(StackGenerateType)
public stackGenerateType: StackGenerateType;
}

View file

@ -1,59 +0,0 @@
import { IsString, IsOptional, IsEnum, IsNumber, IsBoolean } from "class-validator";
import { Schema, model } from "mongoose";
import { IProcess, IssueType } from "../../core/models/process_model";
import { EXEC_TYPE } from "../../core/models/exec_error_model";
export const ProcessSchema = new Schema({
type: {
type: String,
},
command: {
type: String,
},
isGenerating: {
type: String,
},
isLocaleCode: {
type: String,
},
issueType: {
type: String,
},
timeout: {
type: Number,
default: null,
},
commit: {
type: String,
default: null,
},
});
export const schemaProcess = "Process";
export const ProcessDBModel = model<IProcess>(schemaProcess, ProcessSchema);
export class ProcessModel implements IProcess {
@IsEnum(EXEC_TYPE)
public type: EXEC_TYPE;
@IsString()
public command: string;
@IsBoolean()
public isGenerating: boolean;
@IsBoolean()
public isLocaleCode: boolean;
@IsEnum(IssueType)
public issueType: IssueType;
@IsOptional()
@IsNumber()
public timeout?: number;
@IsOptional()
@IsString()
public commit?: string;
}

View file

@ -1,29 +1,21 @@
import { App } from "../../../core/controllers/app";
import { Result } from "../../../core/helpers/result";
import { CreateDataBaseModelUseCase } from "../../../core/usecases/create_database_model_usecase";
import { CreateFolderUseCase } from "../../../core/usecases/crete_folder_usecase";
import { CreateFolderUseCase } from "../../../core/usecases/create_folder_usecase";
import { pipelineRealTimeService } from "../../realtime/realtime_presentation";
import { ProjectInstanceDbModel } from "../models/project_instance_database_model";
import { ProjectInstanceValidationModel } from "../models/project_instance_validation_model";
import { v4 as uuidv4 } from "uuid";
import { SetActiveProjectScenario } from "./set_active_project_use_scenario";
export class CreateNewProjectInstanceScenario {
call = async (model: ProjectInstanceValidationModel): Promise<Result<Error, any>> => {
call = async (): Promise<Result<Error, any>> => {
try {
const folderName = uuidv4() + "/";
const createFolderUseCase = await new CreateFolderUseCase().call(App.staticFilesStoreDir() + folderName);
if (createFolderUseCase.isFailure()) {
return createFolderUseCase.forward();
}
model.rootDir = folderName;
const createDataBaseModelUseCase = await new CreateDataBaseModelUseCase(ProjectInstanceDbModel).call(model);
if (createDataBaseModelUseCase.isFailure()) {
return createDataBaseModelUseCase.forward();
}
return Result.ok({ status: "ok" });
// (await new SetActiveProjectScenario().call(id)).map(() => {
// return Result.ok({ status: "ok" });
// });
} catch (error) {
console.log(error);
return Result.error(error as Error);
}
};

View file

@ -0,0 +1,33 @@
import { CallbackStrategyWithEmpty, ResponseBase } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result";
import { RobossemblerAssets } from "../../../core/models/robossembler_assets";
import { StaticFiles } from "../../../core/models/static_files";
import { ReadingJsonFileAndConvertingToInstanceClassScenario } from "../../../core/scenarios/read_file_and_json_to_plain_instance_class_scenario";
import { GetServerAddressUseCase } from "../../../core/usecases/get_server_address_usecase";
import { PipelineStatusUseCase } from "../../realtime/domain/pipeline_status_usecase";
export class RobossemblerAssetsNetworkMapperScenario extends CallbackStrategyWithEmpty {
async call(): ResponseBase {
try {
const result = await new PipelineStatusUseCase().call();
return await result.map(async (activeInstanceModel) => {
return (
await new ReadingJsonFileAndConvertingToInstanceClassScenario(RobossemblerAssets).call(
`${activeInstanceModel.path}${StaticFiles.robossembler_assets}`
)
).map((robossemblerAssets) => {
return new GetServerAddressUseCase().call().map((address) => {
return Result.ok(
robossemblerAssets.convertLocalPathsToServerPaths(
`${address}/${activeInstanceModel.rootDir.pathNormalize()}`
)
);
});
});
});
} catch (error) {
return Result.error(error);
}
}
}

View file

@ -0,0 +1,28 @@
import { CallbackStrategyWithValidationModel, ResponseBase } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result";
import { RobossemblerAssets } from "../../../core/models/robossembler_assets";
import { StaticFiles } from "../../../core/models/static_files";
import { ReadingJsonFileAndConvertingToInstanceClassScenario } from "../../../core/scenarios/read_file_and_json_to_plain_instance_class_scenario";
import { WriteFileSystemFileUseCase } from "../../../core/usecases/write_file_system_file_usecase";
import { PipelineStatusUseCase } from "../../realtime/domain/pipeline_status_usecase";
export class SaveActiveSceneScenario extends CallbackStrategyWithValidationModel<RobossemblerAssets> {
validationModel: RobossemblerAssets = new RobossemblerAssets();
async call(model: RobossemblerAssets): ResponseBase {
return (await new PipelineStatusUseCase().call()).map(async (activeInstanceModel) =>
(
await new ReadingJsonFileAndConvertingToInstanceClassScenario(RobossemblerAssets).call(
`${activeInstanceModel.path}${StaticFiles.robossembler_assets}`.pathNormalize()
)
).map(async (prevModel) => {
model.assets = prevModel.assets;
return (
await new WriteFileSystemFileUseCase().call(
`${activeInstanceModel.path}${StaticFiles.robossembler_assets}`.pathNormalize(),
JSON.stringify(model)
)
).map(() => Result.ok("assets is rewrite"));
})
);
}
}

View file

@ -0,0 +1,35 @@
import { App } from "../../../core/controllers/app";
import { CallbackStrategyWithIdQuery, ResponseBase } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result";
import { SetLastActivePipelineToRealTimeServiceScenario } from "../../../core/scenarios/set_active_pipeline_to_realtime_service_scenario";
import { CreateFolderUseCase } from "../../../core/usecases/create_folder_usecase";
import { ReadByIdDataBaseModelUseCase } from "../../../core/usecases/read_by_id_database_model_usecase";
import { UpdateDataBaseModelUseCase } from "../../../core/usecases/update_database_model_usecase";
import { MongoIdValidation } from "../../../core/validations/mongo_id_validation";
import { IProjectInstanceModel, ProjectInstanceDbModel } from "../models/project_instance_database_model";
export class SetActiveProjectScenario extends CallbackStrategyWithIdQuery {
idValidationExpression = new MongoIdValidation();
async call(id: string): ResponseBase {
const result = await new ReadByIdDataBaseModelUseCase<IProjectInstanceModel>(ProjectInstanceDbModel).call(id);
// id
if (result.isFailure()) {
return result.forward();
}
const model = result.value;
return await (
await new CreateFolderUseCase().call(App.staticFilesStoreDir() + model.rootDir)
).map(async () => {
model.isActive = true;
return (await new UpdateDataBaseModelUseCase(ProjectInstanceDbModel).call(model)).map(async (el) => {
// TODO(IDONTSUDO): move it to a separate UseCase
await ProjectInstanceDbModel.updateMany({ _id: { $ne: el._id }, isActive: { $eq: true } }, { isActive: false });
await new SetLastActivePipelineToRealTimeServiceScenario().call();
return Result.ok(`project ${id} is active`);
});
});
}
}

View file

@ -1,23 +1,37 @@
import { App } from "../../../core/controllers/app";
import { CallbackStrategyWithFileUpload, ResponseBase } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result";
import { IFile } from "../../../core/interfaces/file";
import { CreateDataBaseModelUseCase } from "../../../core/usecases/create_database_model_usecase";
import { CreateFileUseCase } from "../../../core/usecases/create_file_usecase";
import { PipelineStatusUseCase } from "../../realtime/domain/pipeline_status_usecase";
import { CreateFolderUseCase } from "../../../core/usecases/create_folder_usecase";
import { MongoIdValidation } from "../../../core/validations/mongo_id_validation";
import { ProjectInstanceDbModel } from "../models/project_instance_database_model";
import { ProjectInstanceValidationModel } from "../models/project_instance_validation_model";
import { v4 as uuidv4 } from "uuid";
import { SetActiveProjectScenario } from "./set_active_project_use_scenario";
export class UploadCadFileToProjectScenario extends CallbackStrategyWithFileUpload {
checkingFileExpression: RegExp = RegExp(".FCStd");
idValidationExpression = new MongoIdValidation();
async call(file: IFile): ResponseBase {
const pipelineStatusUseCase = await new PipelineStatusUseCase().call();
if (pipelineStatusUseCase.isFailure()) {
return pipelineStatusUseCase.forward();
}
const projectFolder = pipelineStatusUseCase.value.path;
const createFileUseCase = await new CreateFileUseCase().call(projectFolder + file.name, file.data);
if (createFileUseCase.isFailure()) {
return createFileUseCase.forward();
}
return Result.ok("ok");
async call(file: IFile, id: string, description: string): ResponseBase {
const folderName = uuidv4() + "/";
const model = new ProjectInstanceValidationModel();
model["project"] = id;
model["description"] = description;
model["rootDir"] = folderName;
model["isActive"] = true;
return (await new CreateFolderUseCase().call(App.staticFilesStoreDir() + folderName)).map(async () =>
(await new CreateDataBaseModelUseCase(ProjectInstanceDbModel).call(model)).map(async (databaseModel) =>
(await new SetActiveProjectScenario().call(databaseModel.id)).map(async () =>
(await new CreateFileUseCase().call(App.staticFilesStoreDir() + folderName + file.name, file.data)).map(
() => {
return Result.ok("ok");
}
)
)
)
);
}
}

View file

@ -9,4 +9,7 @@ export class ProjectInstanceValidationModel {
@IsOptional()
public rootDir: string;
@IsOptional()
public isActive: boolean;
}

View file

@ -1,5 +1,11 @@
import { CrudController } from "../../core/controllers/crud_controller";
import { CoreHttpController } from "../../core/controllers/http_controller";
import { RobossemblerAssets } from "../../core/models/robossembler_assets";
import { CreateNewProjectInstanceScenario } from "./domain/create_new_project_scenario";
import { RobossemblerAssetsNetworkMapperScenario } from "./domain/robossembler_assets_network_mapper_scenario";
import { SaveActiveSceneScenario } from "./domain/save_active_scene_scenario";
import { SetActiveProjectScenario } from "./domain/set_active_project_use_scenario";
import { UploadCadFileToProjectScenario } from "./domain/upload_file_to_to_project_scenario";
import { ProjectInstanceDbModel } from "./models/project_instance_database_model";
import { ProjectInstanceValidationModel } from "./models/project_instance_validation_model";
@ -14,14 +20,30 @@ export class ProjectInstancePresentation extends CrudController<
url: "project_instance",
databaseModel: ProjectInstanceDbModel,
});
super.post(new CreateNewProjectInstanceScenario().call);
super.subRoutes = [
{
method: "POST",
subUrl: "upload",
fn: new UploadCadFileToProjectScenario(),
},
];
this.subRoutes.push({
method: "POST",
subUrl: "set/active/project",
fn: new SetActiveProjectScenario(),
});
this.subRoutes.push({
method: "POST",
subUrl: "upload",
fn: new UploadCadFileToProjectScenario(),
});
}
}
export class RobossemblerAssetsPresentation extends CoreHttpController<RobossemblerAssets> {
constructor() {
super({
url: "robossembler_assets",
validationModel: RobossemblerAssets,
});
super.get(new RobossemblerAssetsNetworkMapperScenario().call);
super.post(new SaveActiveSceneScenario().call);
}
}

View file

@ -1,31 +0,0 @@
import { Schema, model } from "mongoose";
import { PipelineModel, schemaPipeline } from "../pipelines/pipeline_model";
import { IsMongoId, IsString } from "class-validator";
export interface IProjectModel {
pipelines: [PipelineModel];
rootDir: string;
}
export const ProjectSchema = new Schema({
pipelines: {
type: Array<Schema.Types.ObjectId>,
ref: schemaPipeline,
autopopulate: true,
default: null,
},
rootDir: {
type: String,
},
}).plugin(require("mongoose-autopopulate"));
const schema = "Projects";
export const ProjectDBModel = model<IProjectModel>(schema, ProjectSchema);
export class ProjectModel implements IProjectModel {
@IsMongoId()
pipelines: [PipelineModel];
@IsString()
rootDir: string;
}

View file

@ -6,10 +6,11 @@ export class PipelineStatusUseCase {
async call(): Promise<Result<Error, ActivePipeline>> {
try {
const status = pipelineRealTimeService.status;
if (status.projectUUID !== null) {
if (status.projectId !== null) {
return Result.ok(status);
}
if (status.projectUUID === null) {
if (status.projectId === null) {
return Result.error(new Error("pipelineRealTimeService does not have an active project instance"));
}
} catch (error) {

View file

@ -1,41 +1,60 @@
import { App } from "../../../core/controllers/app";
import { CallbackStrategyWithEmpty } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result";
import { IPipeline } from "../../../core/models/process_model";
import { ReadByIdDataBaseModelUseCase } from "../../../core/usecases/read_by_id_database_model_usecase";
import { UpdateDataBaseModelUseCase } from "../../../core/usecases/update_database_model_usecase";
import { PipelineValidationModel } from "../../pipelines/models/pipeline_validation_model";
import {
IProjectInstanceModel,
ProjectInstanceDbModel,
} from "../../project_instance/models/project_instance_database_model";
import { RealTimeValidationModel, pipelineRealTimeService } from "../realtime_presentation";
import { pipelineRealTimeService } from "../realtime_presentation";
import { PipelineStatusUseCase } from "./pipeline_status_usecase";
export class RunInstancePipelineUseCase {
async call(model: RealTimeValidationModel): Promise<Result<Error, any>> {
const { id } = model;
const readByIdDataBaseModelUseCase = await new ReadByIdDataBaseModelUseCase<IProjectInstanceModel>(
ProjectInstanceDbModel
).call(id);
const mongoPipelineModelMapper = (el: PipelineValidationModel): IPipeline => {
const mapObj: IPipeline = {
process: {
type: el.process.type,
command: el.process.command,
isGenerating: Boolean(el.process.isGenerating),
isLocaleCode: Boolean(el.process.isLocaleCode),
issueType: el.process.issueType,
},
trigger: {
type: el.trigger.type,
value: el.trigger.value.map((el) => String(el)),
},
env: null,
stackGenerateType: el.stackGenerateType,
};
return mapObj;
};
if (readByIdDataBaseModelUseCase.isFailure()) {
return readByIdDataBaseModelUseCase.forward();
}
export class RunInstancePipelineUseCase extends CallbackStrategyWithEmpty {
async call(): Promise<Result<any, string>> {
return (await new PipelineStatusUseCase().call()).map(async (activePipelineModel) => {
if (activePipelineModel.pipelineIsRunning) {
return Result.error("pipeline is running");
}
const readByIdDataBaseModelUseCase = await new ReadByIdDataBaseModelUseCase<IProjectInstanceModel>(
ProjectInstanceDbModel
).call(activePipelineModel.projectId);
const projectModel = readByIdDataBaseModelUseCase.value;
projectModel.isActive = true;
if (readByIdDataBaseModelUseCase.isFailure()) {
return readByIdDataBaseModelUseCase.forward();
}
const projectModel = readByIdDataBaseModelUseCase.value;
const resultMapper = projectModel.project.pipelines.map((el) => mongoPipelineModelMapper(el));
const updateDataBaseModelUseCase = await new UpdateDataBaseModelUseCase<IProjectInstanceModel, any>(
ProjectInstanceDbModel
).call(projectModel);
pipelineRealTimeService.setPipelineDependency(
resultMapper,
App.staticFilesStoreDir() + projectModel.rootDir + "/",
projectModel._id,
projectModel.rootDir
);
if (updateDataBaseModelUseCase.isFailure()) {
return updateDataBaseModelUseCase.forward();
}
pipelineRealTimeService.setPipelineDependency(
projectModel.project.pipelines,
App.staticFilesStoreDir() + projectModel.rootDir + "/",
projectModel._id
);
pipelineRealTimeService.runPipeline();
return Result.ok({ status: "ok" });
pipelineRealTimeService.runPipeline();
return Result.ok("ok");
});
}
}

View file

@ -18,7 +18,11 @@ export class RealTimePresentation extends CoreHttpController<RealTimeValidationM
url: "realtime",
databaseModel: null,
});
super.post(new RunInstancePipelineUseCase().call);
super.get(new PipelineStatusUseCase().call);
this.subRoutes.push({
method: "POST",
subUrl: "run",
fn: new RunInstancePipelineUseCase(),
});
}
}

View file

@ -1,43 +0,0 @@
import { IsArray, IsOptional, IsEnum} from "class-validator";
import { Schema, model } from "mongoose";
export interface ITriggerModel {
_id?: string;
type: string;
value: string[];
}
export const TriggerSchema = new Schema({
type: {
type: String,
require: true,
},
value: {
type: Array,
require: true,
},
});
export const triggerSchema = "Trigger";
export const TriggerDBModel = model<ITriggerModel>(triggerSchema, TriggerSchema);
export enum TriggerType {
PROCESS = "PROCESS",
FILE = "FILE",
}
export class TriggerModel implements ITriggerModel {
@IsOptional()
public _id: string;
@IsEnum(TriggerType)
public type: TriggerType;
@IsArray()
public value: string[];
}
export interface Trigger {
type: TriggerType;
value: string[];
}

View file

@ -1,13 +1,14 @@
import { CrudController } from "../../core/controllers/crud_controller";
import { TriggerDBModel } from "./models/trigger_database_model";
import { TriggerModelValidationModel as TriggerValidationMode } from "./models/trigger_validation_model";
import { TriggerModelValidationModel } from "./models/trigger_validation_model";
export class TriggerPresentation extends CrudController<TriggerValidationMode, typeof TriggerDBModel> {
export class TriggerPresentation extends CrudController<TriggerModelValidationModel, typeof TriggerDBModel> {
constructor() {
super({
url: "trigger",
validationModel: TriggerValidationMode,
validationModel: TriggerModelValidationModel,
databaseModel: TriggerDBModel,
});
}
}
"".isEmpty();

View file

@ -4,9 +4,11 @@ import { SocketSubscriber } from "./core/controllers/socket_controller";
import { extensions } from "./core/extensions/extensions";
import { httpRoutes } from "./core/controllers/routes";
import { pipelineRealTimeService } from "./features/realtime/realtime_presentation";
import { main } from "./p";
extensions();
const socketSubscribers = [new SocketSubscriber(pipelineRealTimeService, "realtime")];
new App(httpRoutes, socketSubscribers).listen();
main();

View file

@ -3,19 +3,23 @@ import { StackService } from "../../src/core/services/stack_service";
import { delay } from "../../src/core/helpers/delay";
import { assert, dirname__ } from "../test";
import { mockSimplePipeline } from "../model/mock_pipelines";
import { readDirRecursive } from "../../src/core/repository/fs";
import { FileSystemRepository } from "../../src/core/repository/file_system_repository";
import { CreateFolderUseCase } from "../../src/core/usecases/create_folder_usecase";
abstract class IStackServiceTest {
abstract test(): Promise<boolean>;
}
class SimpleTestStackServiceTest extends StackService implements IStackServiceTest {
fileSystemRepository: FileSystemRepository;
constructor() {
super(mockSimplePipeline, dirname__ + "/context/");
this.fileSystemRepository = new FileSystemRepository();
}
async test(): Promise<boolean> {
await this.call();
const testResult = readDirRecursive(this.path).equals(["1.txt", "test.txt"], true);
console.log(this.path);
const testResult = this.fileSystemRepository.readDirRecursive(this.path).equals(["1.txt", "test.txt"], true);
await delay(100);
rmSync(this.path + "example/", { recursive: true });
return testResult;
@ -24,12 +28,15 @@ class SimpleTestStackServiceTest extends StackService implements IStackServiceTe
export class StackServiceTest {
dirName: string;
fileSystemRepository: FileSystemRepository;
constructor(dirName: string) {
this.dirName = dirName;
this.fileSystemRepository = new FileSystemRepository();
}
public async test() {
const tests = [new SimpleTestStackServiceTest()];
await new CreateFolderUseCase().call(this.dirName + "/context/");
for await (const el of tests) {
assert((await el.test()) === true, el.constructor.name);
await delay(3000);

View file

@ -39,14 +39,14 @@ const unitTest = async () => {
await init();
await new ExecutorProgramServiceTest(dirname__).test();
await new FilesChangerTest(dirname__).test();
await new StackServiceTest(dirname__ + "/context/").test();
await new StackServiceTest(dirname__).test();
await new TriggerServiceTest().test();
await new CreateDataBaseModelUseCaseTest().test();
await new CreateDataBaseModelUseCaseTest().test();
await new DeleteDataBaseModelUseCaseTest().test();
await new ReadDataBaseModelUseCaseTest().test();
await new UpdateDataBaseModelUseCaseTest().test();
// await new PipelineRealTimeServiceTest().test()
for await (const usecase of tests) {
testCore.assert(await new usecase().test(), usecase.name);
}
@ -54,7 +54,6 @@ const unitTest = async () => {
const presentationCrudControllers = [new TriggerPresentation()];
const e2eTest = async () => {
const app = new App(httpRoutes, [], Environment.E2E_TEST);
app.listen();
await new Promise((resolve, reject) => {
app.on(async (e) => {
if (e === ServerStatus.finished) {