Merge branch '4-fix/mvp-ui' into 'main'
Resolve "Реализовать запуск команд через UI с обратной связью о результате исполнения" Closes #4 See merge request robossembler/webservice!4
This commit is contained in:
commit
1754c24d79
175 changed files with 4686 additions and 851 deletions
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
10
.vscode/launch.json
vendored
10
.vscode/launch.json
vendored
|
@ -25,6 +25,16 @@
|
||||||
"run-script", "test:watch"
|
"run-script", "test:watch"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}/server/",
|
"cwd": "${workspaceRoot}/server/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "ui-dev",
|
||||||
|
"runtimeExecutable": "npm",
|
||||||
|
"runtimeArgs": [
|
||||||
|
"run-script", "dev"
|
||||||
|
],
|
||||||
|
"cwd": "${workspaceRoot}/ui/",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
40
.vscode/settings.json
vendored
40
.vscode/settings.json
vendored
|
@ -1,24 +1,20 @@
|
||||||
{
|
{
|
||||||
"files.exclude": {
|
"files.exclude": {
|
||||||
"server/build/src/core/*controllers.*": true,
|
"**/.git": false,
|
||||||
"server/build/src/core/*di": true,
|
"**/.svn": false,
|
||||||
"server/build/src/core/*di.*": true,
|
"**/.hg": false,
|
||||||
"server/build/src/core/*extensions": true,
|
"**/CVS": false,
|
||||||
"server/build/src/core/*extensions.*": true,
|
"**/__pycache__": false,
|
||||||
"server/build/src/core/*helper": true,
|
"*.DS_Store": false,
|
||||||
"server/build/src/core/*helper.*": true,
|
"*.DS_Store.*": false,
|
||||||
"server/build/src/core/*interfaces": true,
|
"*.git": false,
|
||||||
"server/build/src/core/*interfaces.*": true,
|
"*.git.*": false,
|
||||||
"server/build/src/core/*middlewares": true,
|
"*.vscode": false,
|
||||||
"server/build/src/core/*middlewares.*": true,
|
"*.vscode.*": false,
|
||||||
"server/build/src/core/*model": true,
|
"*server.*": false,
|
||||||
"server/build/src/core/*model.*": true,
|
"*ui": false,
|
||||||
"server/build/src/core/*services": true,
|
"*ui.*": false
|
||||||
"server/build/src/core/*services.*": true,
|
},
|
||||||
"server/build/src/core/*usecases": true,
|
"cSpell.words": ["antd", "fileupload", "metadatas", "undici", "uuidv"],
|
||||||
"server/build/src/core/*usecases.*": true,
|
"editor.rulers": [100]
|
||||||
"server/src/core/model/exec_error_model.js": true,
|
}
|
||||||
"**/*.map": true,
|
|
||||||
"**/*.js": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
3
server/.gitignore
vendored
3
server/.gitignore
vendored
|
@ -7,4 +7,5 @@ coverage
|
||||||
package-lock.json
|
package-lock.json
|
||||||
.*.swp
|
.*.swp
|
||||||
build/
|
build/
|
||||||
model_create.ts
|
model_create.ts
|
||||||
|
public
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"pretest": "tsc",
|
"pretest": "tsc",
|
||||||
"test": "ts-node ./build/test/test.js",
|
"test:unit": "NODE_ENV=unit tsc-watch --onSuccess 'ts-node ./build/test/test.js'",
|
||||||
"test:watch": "tsc-watch --onSuccess 'ts-node ./build/test/test.js'",
|
"test:e2e": "NODE_ENV=e2e tsc-watch --onSuccess 'ts-node ./build/test/test.js'",
|
||||||
"dev": "tsc-watch --onSuccess 'ts-node ./build/src/main.js'"
|
"dev": "NODE_ENV=dev tsc-watch --onSuccess 'ts-node ./build/src/main.js'"
|
||||||
},
|
},
|
||||||
"author": "IDONTSUDO",
|
"author": "IDONTSUDO",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -31,12 +31,13 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grpc/grpc-js": "^1.9.0",
|
"@grpc/grpc-js": "^1.9.0",
|
||||||
|
"axios": "^1.6.2",
|
||||||
"babel-register": "^6.26.0",
|
"babel-register": "^6.26.0",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.0",
|
"class-validator": "^0.14.0",
|
||||||
"concurrently": "^8.2.0",
|
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"express-fileupload": "^1.4.2",
|
||||||
"first-di": "^1.0.11",
|
"first-di": "^1.0.11",
|
||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
"mongoose": "^7.6.2",
|
"mongoose": "^7.6.2",
|
||||||
|
@ -49,6 +50,6 @@
|
||||||
"spark-md5": "^3.0.2",
|
"spark-md5": "^3.0.2",
|
||||||
"ts-md5": "^1.3.1",
|
"ts-md5": "^1.3.1",
|
||||||
"tsc-watch": "^6.0.4",
|
"tsc-watch": "^6.0.4",
|
||||||
"typedi": "^0.10.0"
|
"uuid": "^9.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,67 @@
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { Routes } from "../interfaces/router";
|
import { Routes } from "../interfaces/router";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import locator from "../di/register_di";
|
import { Server } from "socket.io";
|
||||||
import { DevEnv, UnitTestEnv } from "../di/env";
|
import { createServer } from "http";
|
||||||
import mongoose from "mongoose";
|
import { SocketSubscriber } from "./socket_controller";
|
||||||
import http from "http";
|
import { dirname } from "path";
|
||||||
// import { Server } from "socket.io";
|
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";
|
||||||
|
import { TypedEvent } from "../helpers/typed_event";
|
||||||
|
|
||||||
export class App {
|
export enum ServerStatus {
|
||||||
|
init = "init",
|
||||||
|
finished = "finshed",
|
||||||
|
error = "error",
|
||||||
|
}
|
||||||
|
export enum Environment {
|
||||||
|
DEV = "DEV",
|
||||||
|
E2E_TEST = "E2E_TEST",
|
||||||
|
}
|
||||||
|
|
||||||
|
export class App extends TypedEvent<ServerStatus> {
|
||||||
public app: express.Application;
|
public app: express.Application;
|
||||||
public port: number;
|
public port: number;
|
||||||
public env: string;
|
public env: Environment;
|
||||||
public computedFolder: string;
|
public socketSubscribers: SocketSubscriber<any>[];
|
||||||
// public io:
|
public io: Server;
|
||||||
constructor(routes: Routes[], computedFolder: string) {
|
status: ServerStatus;
|
||||||
this.port = 3000;
|
|
||||||
this.env = "dev";
|
constructor(routes: Routes[] = [], socketSubscribers: SocketSubscriber<any>[] = [], env = Environment.DEV) {
|
||||||
this.loadAppDependencies().then(() => {
|
super();
|
||||||
this.app = express();
|
this.init(routes, socketSubscribers, env);
|
||||||
this.initializeMiddlewares();
|
|
||||||
this.initializeRoutes(routes);
|
|
||||||
this.computedFolder = computedFolder;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public init(routes: Routes[], socketSubscribers: SocketSubscriber<any>[], env: Environment) {
|
||||||
|
this.port = 4001;
|
||||||
|
this.socketSubscribers = socketSubscribers;
|
||||||
|
this.env = env;
|
||||||
|
this.app = express();
|
||||||
|
this.setServerStatus(ServerStatus.init);
|
||||||
|
|
||||||
|
this.loadAppDependencies().then(() => {
|
||||||
|
this.initializeMiddlewares();
|
||||||
|
this.initializeRoutes(routes);
|
||||||
|
if (this.status !== ServerStatus.error) {
|
||||||
|
this.setServerStatus(ServerStatus.finished);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
public listen() {
|
public listen() {
|
||||||
const httpServer = new http.Server(this.app);
|
const httpServer = createServer(this.app);
|
||||||
// const io = new Server(httpServer);
|
const io = new Server(httpServer, {
|
||||||
|
cors: { origin: "*" },
|
||||||
|
});
|
||||||
|
|
||||||
|
io.on("connection", (socket) => {
|
||||||
|
this.socketSubscribers.map((el) => {
|
||||||
|
el.emitter.on((e) => {
|
||||||
|
socket.emit(el.event, e);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
httpServer.listen(this.port, () => {
|
httpServer.listen(this.port, () => {
|
||||||
console.info(`=================================`);
|
console.info(`=================================`);
|
||||||
|
@ -35,18 +70,13 @@ export class App {
|
||||||
console.info(`🚀 WS ws://localhost:${this.port}`);
|
console.info(`🚀 WS ws://localhost:${this.port}`);
|
||||||
console.info(`=================================`);
|
console.info(`=================================`);
|
||||||
});
|
});
|
||||||
// io.on("connection", (socket) => {
|
|
||||||
// socket.on("disconnect", function (msg) {
|
|
||||||
// console.log("Disconnected");
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// setInterval(function () {
|
this.io = io;
|
||||||
// io.emit("goodbye");
|
}
|
||||||
// console.log(200);
|
setServerStatus(status: ServerStatus) {
|
||||||
// }, 1000);
|
this.emit(status);
|
||||||
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getServer() {
|
public getServer() {
|
||||||
return this.app;
|
return this.app;
|
||||||
}
|
}
|
||||||
|
@ -55,6 +85,13 @@ export class App {
|
||||||
this.app.use(cors());
|
this.app.use(cors());
|
||||||
this.app.use(express.json());
|
this.app.use(express.json());
|
||||||
this.app.use(express.urlencoded({ extended: true }));
|
this.app.use(express.urlencoded({ extended: true }));
|
||||||
|
this.app.use(express.static("public"));
|
||||||
|
|
||||||
|
this.app.use(
|
||||||
|
fileUpload({
|
||||||
|
createParentPath: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeRoutes(routes: Routes[]) {
|
private initializeRoutes(routes: Routes[]) {
|
||||||
|
@ -62,21 +99,25 @@ export class App {
|
||||||
this.app.use("/", route.router);
|
this.app.use("/", route.router);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async loadAppDependencies() {
|
|
||||||
await locator(
|
async loadAppDependencies(): Promise<void> {
|
||||||
this.env == "development"
|
const dataBaseName = this.env === Environment.E2E_TEST ? "e2e_test" : "dev";
|
||||||
? new DevEnv(this.computedFolder)
|
// TODO(IDONTSUDO):maybe convert it to a class and map it there
|
||||||
: new UnitTestEnv(this.computedFolder)
|
const result = await new DataBaseConnectUseCase().call(dataBaseName);
|
||||||
|
await result.fold(
|
||||||
|
async (_s) => {
|
||||||
|
await new CheckAndCreateStaticFilesFolderUseCase().call();
|
||||||
|
await new SetLastActivePipelineToRealTimeServiceScenario().call();
|
||||||
|
},
|
||||||
|
async (_e) => {
|
||||||
|
this.setServerStatus(ServerStatus.error);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
mongoose
|
|
||||||
.connect("mongodb://127.0.0.1:27017/test")
|
|
||||||
.then(() => console.log("Connected!"))
|
|
||||||
.catch((e) => {
|
|
||||||
console.log("ERROR:", e);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static staticFilesStoreDir = () => {
|
||||||
|
const dir = dirname(__filename);
|
||||||
|
const rootDir = dir.slice(0, dir.length - 20);
|
||||||
|
return rootDir + "public/";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,23 +12,23 @@ export class CrudController<V, D> extends CoreHttpController<V> {
|
||||||
|
|
||||||
constructor(routerModel: IRouteModel) {
|
constructor(routerModel: IRouteModel) {
|
||||||
super(routerModel);
|
super(routerModel);
|
||||||
this.url = "/" + routerModel.url;
|
this.mainURL = "/" + routerModel.url;
|
||||||
this.validationModel = routerModel.validationModel;
|
this.validationModel = routerModel.validationModel;
|
||||||
this.dataBaseModel = routerModel.databaseModel;
|
this.dataBaseModel = routerModel.databaseModel;
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
init() {
|
init() {
|
||||||
this.routes["POST"] = new CreateDataBaseModelUseCase<D>(
|
if (this.routes["POST"] === null) {
|
||||||
this.dataBaseModel
|
this.routes["POST"] = new CreateDataBaseModelUseCase<D>(this.dataBaseModel).call;
|
||||||
).call;
|
}
|
||||||
this.routes["GET"] = new PaginationDataBaseModelUseCase<D>(
|
if (this.routes["GET"] === null) {
|
||||||
this.dataBaseModel
|
this.routes["GET"] = new PaginationDataBaseModelUseCase<D>(this.dataBaseModel).call;
|
||||||
).call;
|
}
|
||||||
this.routes["DELETE"] = new DeleteDataBaseModelUseCase<D>(
|
if (this.routes["DELETE"] === null) {
|
||||||
this.dataBaseModel
|
this.routes["DELETE"] = new DeleteDataBaseModelUseCase<D>(this.dataBaseModel).call;
|
||||||
).call;
|
}
|
||||||
this.routes["PUT"] = new UpdateDataBaseModelUseCase<V, D>(
|
if (this.routes["PUT"] === null) {
|
||||||
this.dataBaseModel
|
this.routes["PUT"] = new UpdateDataBaseModelUseCase<V, D>(this.dataBaseModel).call;
|
||||||
).call;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,54 @@
|
||||||
import { validationModelMiddleware } from "../middlewares/validation_model";
|
import { validationModelMiddleware } from "../middlewares/validation_model";
|
||||||
import { Result } from "../helper/result";
|
import { Result } from "../helpers/result";
|
||||||
import { Router, Request, Response } from "express";
|
import { Router, Request, Response } from "express";
|
||||||
import { IRouteModel, Routes } from "../interfaces/router";
|
import { IRouteModel, Routes } from "../interfaces/router";
|
||||||
|
|
||||||
export type CallBackFunction<T> = (a: T) => Promise<Result<any, any>>;
|
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;
|
||||||
|
}
|
||||||
|
export abstract class CallbackStrategyWithIdQuery {
|
||||||
|
abstract idValidationExpression: RegExp | null;
|
||||||
|
abstract call(id: string): ResponseBase;
|
||||||
|
}
|
||||||
|
export abstract class CallBackStrategyWithQueryPage {
|
||||||
|
abstract validationPageExpression: RegExp | null;
|
||||||
|
abstract call(page: string): ResponseBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class CallbackStrategyWithFileUpload {
|
||||||
|
abstract checkingFileExpression: RegExp;
|
||||||
|
abstract call(file: File): ResponseBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ISubSetFeatureRouter<T> {
|
||||||
|
method: HttpMethodType;
|
||||||
|
subUrl: string;
|
||||||
|
fn:
|
||||||
|
| CallbackStrategyWithValidationModel<T>
|
||||||
|
| CallbackStrategyWithEmpty
|
||||||
|
| CallbackStrategyWithIdQuery
|
||||||
|
| CallBackStrategyWithQueryPage
|
||||||
|
| CallbackStrategyWithFileUpload;
|
||||||
|
}
|
||||||
|
|
||||||
abstract class ICoreHttpController {
|
abstract class ICoreHttpController {
|
||||||
abstract url: string;
|
abstract mainURL: string;
|
||||||
public router = Router();
|
public router = Router();
|
||||||
abstract call(): Routes;
|
abstract call(): Routes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CoreHttpController<V> implements ICoreHttpController {
|
export class CoreHttpController<V> implements ICoreHttpController {
|
||||||
url: string;
|
mainURL: string;
|
||||||
validationModel: any;
|
validationModel: any;
|
||||||
|
subRoutes: ISubSetFeatureRouter<V>[] = [];
|
||||||
|
|
||||||
routes = {
|
routes = {
|
||||||
POST: null,
|
POST: null,
|
||||||
|
@ -25,55 +60,97 @@ export class CoreHttpController<V> implements ICoreHttpController {
|
||||||
public router = Router();
|
public router = Router();
|
||||||
|
|
||||||
constructor(routerModel: IRouteModel) {
|
constructor(routerModel: IRouteModel) {
|
||||||
this.url = "/" + routerModel.url;
|
this.mainURL = "/" + routerModel.url;
|
||||||
this.validationModel = routerModel.validationModel;
|
this.validationModel = routerModel.validationModel;
|
||||||
}
|
}
|
||||||
|
async responseHelper(res: Response, fn: ResponseBase) {
|
||||||
|
(await fn).fold(
|
||||||
|
(ok) => {
|
||||||
|
res.json(ok);
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
res.status(400).json({ error: String(err) });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
call(): Routes {
|
call(): Routes {
|
||||||
|
if (this.subRoutes.isNotEmpty()) {
|
||||||
|
this.subRoutes.map((el) => {
|
||||||
|
this.router[el.method.toLowerCase()](this.mainURL + "/" + el.subUrl, async (req, res) => {
|
||||||
|
if (el.fn instanceof CallbackStrategyWithValidationModel) {
|
||||||
|
// TODO(IDONTSUDO):
|
||||||
|
throw Error("needs to be implimed");
|
||||||
|
}
|
||||||
|
if (el.fn instanceof CallbackStrategyWithIdQuery) {
|
||||||
|
throw Error("needs to be implimed");
|
||||||
|
}
|
||||||
|
if (el.fn instanceof CallBackStrategyWithQueryPage) {
|
||||||
|
throw Error("needs to be implimed");
|
||||||
|
}
|
||||||
|
if (el.fn instanceof CallbackStrategyWithEmpty) {
|
||||||
|
await this.responseHelper(res, el.fn.call());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.fn instanceof CallbackStrategyWithFileUpload) {
|
||||||
|
if (req["files"] === undefined) {
|
||||||
|
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 (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"]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (this.routes["POST"] != null) {
|
if (this.routes["POST"] != null) {
|
||||||
this.router.post(
|
this.router.post(this.mainURL, validationModelMiddleware(this.validationModel), (req, res) =>
|
||||||
this.url,
|
this.requestResponseController<V>(req, res, this.routes["POST"])
|
||||||
validationModelMiddleware(this.validationModel),
|
|
||||||
(req, res) =>
|
|
||||||
this.requestResponseController<V>(req, res, this.routes["POST"])
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (this.routes["DELETE"] != null) {
|
if (this.routes["DELETE"] != null) {
|
||||||
this.router.delete(this.url, (req, res) =>
|
this.router.delete(this.mainURL, (req, res) =>
|
||||||
this.requestResponseController<V>(req, res, this.routes["DELETE"])
|
this.requestResponseController<V>(req, res, this.routes["DELETE"])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (this.routes["PUT"] != null) {
|
if (this.routes["PUT"] != null) {
|
||||||
this.router.put(
|
this.router.put(this.mainURL, validationModelMiddleware(this.validationModel), (req, res) =>
|
||||||
this.url,
|
this.requestResponseController<V>(req, res, this.routes["PUT"])
|
||||||
validationModelMiddleware(this.validationModel),
|
|
||||||
(req, res) =>
|
|
||||||
this.requestResponseController<V>(req, res, this.routes["PUT"])
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (this.routes["GET"] != null) {
|
if (this.routes["GET"] != null) {
|
||||||
this.router.get(this.url, (req, res) =>
|
this.router.get(this.mainURL, (req, res) => this.requestResponseController<V>(req, res, this.routes["GET"]));
|
||||||
this.requestResponseController<V>(req, res, this.routes["GET"])
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
router: this.router,
|
router: this.router,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public put(usecase: CallBackFunction<V>) {
|
public put(usecase: CallbackStrategyWithValidationModel<V>) {
|
||||||
this.routes["PUT"] = usecase;
|
this.routes["PUT"] = usecase;
|
||||||
}
|
}
|
||||||
public delete(usecase: CallBackFunction<V>) {
|
public delete(usecase: CallbackStrategyWithValidationModel<V>) {
|
||||||
this.routes["DELETE"] = usecase;
|
this.routes["DELETE"] = usecase;
|
||||||
}
|
}
|
||||||
private async requestResponseController<T>(
|
public async requestResponseController<T>(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
usecase: CallBackFunction<T>
|
usecase: CallbackStrategyWithValidationModel<T>
|
||||||
) {
|
) {
|
||||||
let payload = null;
|
let payload = null;
|
||||||
|
const useCase = usecase as any;
|
||||||
if (req["model"] != undefined) {
|
if (req["model"] != undefined) {
|
||||||
payload = req.body as T;
|
payload = req.body as T;
|
||||||
}
|
}
|
||||||
|
@ -85,23 +162,23 @@ export class CoreHttpController<V> implements ICoreHttpController {
|
||||||
if (req.query.id !== undefined) {
|
if (req.query.id !== undefined) {
|
||||||
payload = String(req.query.id);
|
payload = String(req.query.id);
|
||||||
}
|
}
|
||||||
|
(await useCase(payload)).fold(
|
||||||
(await usecase(payload)).fold(
|
|
||||||
(ok) => {
|
(ok) => {
|
||||||
res.json(ok);
|
res.json(ok);
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
res.status(400).json(err);
|
res.status(400).json({ error: String(err) });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
public post(usecase: CallBackFunction<V>) {
|
// TODO(IDONTSUDO):need fix to CallbackStrategyWithValidationModel<V>
|
||||||
|
public post(usecase: any) {
|
||||||
this.routes["POST"] = usecase;
|
this.routes["POST"] = usecase;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get(usecase: CallBackFunction<V>) {
|
public get(usecase: any) {
|
||||||
this.routes["GET"] = usecase;
|
this.routes["GET"] = usecase;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
26
server/src/core/controllers/routes.ts
Normal file
26
server/src/core/controllers/routes.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
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 { ProjectsPresentation } from "../../features/projects/projects_presentation";
|
||||||
|
import { RealTimePresentation } from "../../features/realtime/realtime_presentation";
|
||||||
|
import { TriggerPresentation } from "../../features/triggers/triggers_presentation";
|
||||||
|
import { extensions } from "../extensions/extensions";
|
||||||
|
import { Routes } from "../interfaces/router";
|
||||||
|
|
||||||
|
extensions();
|
||||||
|
|
||||||
|
export const routersImplementPureCrud = [
|
||||||
|
new TriggerPresentation(),
|
||||||
|
new ProjectsPresentation(),
|
||||||
|
new ProcessPresentation(),
|
||||||
|
new PipelinePresentation(),
|
||||||
|
];
|
||||||
|
|
||||||
|
export const httpRoutes: Routes[] = [
|
||||||
|
new RealTimePresentation(),
|
||||||
|
new ProjectInstancePresentation(),
|
||||||
|
new NixStoreManagerPresentation(),
|
||||||
|
]
|
||||||
|
.concat(routersImplementPureCrud)
|
||||||
|
.map((el) => el.call());
|
|
@ -1,15 +1,10 @@
|
||||||
// import path from "path";
|
import { TypedEvent } from "../helpers/typed_event";
|
||||||
// import { TypedEvent } from "../helper/typed_event";
|
|
||||||
// import { StackService } from "../services/stack_service";
|
|
||||||
// // TODO(IDONTSUDO): up to do
|
|
||||||
|
|
||||||
// class SocketController<T>{
|
export class SocketSubscriber<T> {
|
||||||
// emitter:TypedEvent<T>;
|
emitter: TypedEvent<T>;
|
||||||
// constructor(emitter:TypedEvent<T>, ){
|
event: string;
|
||||||
// this.emitter = emitter
|
constructor(emitter: TypedEvent<T>, event: string) {
|
||||||
// }
|
this.emitter = emitter;
|
||||||
// call = () =>{
|
this.event = event;
|
||||||
|
}
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
|
@ -1,44 +1,44 @@
|
||||||
import { Service } from "typedi";
|
// import { Service } from "typedi";
|
||||||
|
|
||||||
@Service()
|
// @Service()
|
||||||
export class IEnv{
|
// export class IEnv{
|
||||||
rootFolder!: string;
|
// rootFolder!: string;
|
||||||
constructor(){
|
// constructor(){
|
||||||
|
|
||||||
}
|
// }
|
||||||
toStringEnv(){
|
// toStringEnv(){
|
||||||
return ''
|
// return ''
|
||||||
}
|
// }
|
||||||
static env(){
|
// static env(){
|
||||||
return ''
|
// return ''
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Service()
|
// @Service()
|
||||||
export class DevEnv implements IEnv {
|
// export class DevEnv implements IEnv {
|
||||||
rootFolder:string;
|
// rootFolder:string;
|
||||||
constructor(rootFolder:string){
|
// constructor(rootFolder:string){
|
||||||
this.rootFolder = rootFolder
|
// this.rootFolder = rootFolder
|
||||||
}
|
// }
|
||||||
toStringEnv(): string {
|
// toStringEnv(): string {
|
||||||
return DevEnv.env()
|
// return DevEnv.env()
|
||||||
}
|
// }
|
||||||
static env(){
|
// static env(){
|
||||||
return 'DevEnv'
|
// return 'DevEnv'
|
||||||
|
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
@Service()
|
// @Service()
|
||||||
export class UnitTestEnv implements IEnv{
|
// export class UnitTestEnv implements IEnv{
|
||||||
rootFolder:string;
|
// rootFolder:string;
|
||||||
constructor(rootFolder:string){
|
// constructor(rootFolder:string){
|
||||||
this.rootFolder = rootFolder
|
// this.rootFolder = rootFolder
|
||||||
}
|
// }
|
||||||
toStringEnv(): string {
|
// toStringEnv(): string {
|
||||||
return UnitTestEnv.env()
|
// return UnitTestEnv.env()
|
||||||
}
|
// }
|
||||||
static env(){
|
// static env(){
|
||||||
return 'UnitTestEnv'
|
// return 'UnitTestEnv'
|
||||||
|
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
|
@ -1,53 +1,53 @@
|
||||||
import { DevEnv, IEnv, UnitTestEnv } from "./env";
|
// import { DevEnv, IEnv, UnitTestEnv } from "./env";
|
||||||
import { extensions } from "../extensions/extensions";
|
// import { extensions } from "../extensions/extensions";
|
||||||
// import { Container, Service } from 'typedi';
|
// // import { Container, Service } from 'typedi';
|
||||||
|
|
||||||
export default function locator(env: IEnv) {
|
// export default function locator(env: IEnv) {
|
||||||
extensions();
|
// extensions();
|
||||||
envRegister(env);
|
// envRegister(env);
|
||||||
registerRepository(env);
|
// registerRepository(env);
|
||||||
registerController(env);
|
// registerController(env);
|
||||||
registerService(env);
|
// registerService(env);
|
||||||
// override(MetaDataFileManagerModel, MetaDataFileManagerModel);
|
// // override(MetaDataFileManagerModel, MetaDataFileManagerModel);
|
||||||
}
|
// }
|
||||||
|
|
||||||
const envRegister = (env: IEnv) => {
|
// const envRegister = (env: IEnv) => {
|
||||||
switch (env.toStringEnv()) {
|
// switch (env.toStringEnv()) {
|
||||||
case UnitTestEnv.env():
|
// case UnitTestEnv.env():
|
||||||
// override(IEnv, UnitTestEnv);
|
// // override(IEnv, UnitTestEnv);
|
||||||
return;
|
// return;
|
||||||
case "DevEnv":
|
// case "DevEnv":
|
||||||
// override(IEnv, DevEnv);
|
// // override(IEnv, DevEnv);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
const registerRepository = (env: IEnv) => {
|
// const registerRepository = (env: IEnv) => {
|
||||||
switch (env.toStringEnv()) {
|
// switch (env.toStringEnv()) {
|
||||||
case UnitTestEnv.env():
|
// case UnitTestEnv.env():
|
||||||
// override(IEnv, UnitTestEnv);
|
// // override(IEnv, UnitTestEnv);
|
||||||
|
|
||||||
return;
|
// return;
|
||||||
case DevEnv.env():
|
// case DevEnv.env():
|
||||||
// override(IEnv, DevEnv);
|
// // override(IEnv, DevEnv);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
const registerController = (env: IEnv) => {
|
// const registerController = (env: IEnv) => {
|
||||||
switch (env.toStringEnv()) {
|
// switch (env.toStringEnv()) {
|
||||||
case UnitTestEnv.env():
|
// case UnitTestEnv.env():
|
||||||
return;
|
// return;
|
||||||
case DevEnv.env():
|
// case DevEnv.env():
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
const registerService = (env: IEnv) => {
|
// const registerService = (env: IEnv) => {
|
||||||
switch (env.toStringEnv()) {
|
// switch (env.toStringEnv()) {
|
||||||
case UnitTestEnv.env():
|
// case UnitTestEnv.env():
|
||||||
return;
|
// return;
|
||||||
case DevEnv.env():
|
// case DevEnv.env():
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
|
@ -1,25 +1,23 @@
|
||||||
export {};
|
/* eslint-disable @typescript-eslint/no-this-alias */
|
||||||
|
export const ArrayExtensions = () => {
|
||||||
declare global {
|
if ([].firstElement === undefined) {
|
||||||
interface Array<T> {
|
Array.prototype.firstElement = function () {
|
||||||
// @strict: The parameter is determined whether the arrays must be exactly the same in content and order of this relationship or simply follow the same requirements.
|
return this[0];
|
||||||
equals(array: Array<T>, strict: boolean): boolean;
|
};
|
||||||
}
|
}
|
||||||
}
|
if ([].equals === undefined) {
|
||||||
|
// eslint-disable-next-line no-extend-native
|
||||||
export const ArrayEquals = () => {
|
|
||||||
if ([].equals == undefined) {
|
|
||||||
Array.prototype.equals = function (array, strict = true) {
|
Array.prototype.equals = function (array, strict = true) {
|
||||||
if (!array) return false;
|
if (!array) return false;
|
||||||
|
|
||||||
if (arguments.length == 1) strict = true;
|
if (arguments.length === 1) strict = true;
|
||||||
|
|
||||||
if (this.length != array.length) return false;
|
if (this.length !== array.length) return false;
|
||||||
|
|
||||||
for (let i = 0; i < this.length; i++) {
|
for (let i = 0; i < this.length; i++) {
|
||||||
if (this[i] instanceof Array && array[i] instanceof Array) {
|
if (this[i] instanceof Array && array[i] instanceof Array) {
|
||||||
if (!this[i].equals(array[i], strict)) return false;
|
if (!this[i].equals(array[i], strict)) return false;
|
||||||
} else if (strict && this[i] != array[i]) {
|
} else if (strict && this[i] !== array[i]) {
|
||||||
return false;
|
return false;
|
||||||
} else if (!strict) {
|
} else if (!strict) {
|
||||||
return this.sort().equals(array.sort(), true);
|
return this.sort().equals(array.sort(), true);
|
||||||
|
@ -28,4 +26,27 @@ export const ArrayEquals = () => {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if ([].lastElement === undefined) {
|
||||||
|
// eslint-disable-next-line no-extend-native
|
||||||
|
Array.prototype.lastElement = function () {
|
||||||
|
const instanceCheck = this;
|
||||||
|
if (instanceCheck === undefined) {
|
||||||
|
return undefined;
|
||||||
|
} else {
|
||||||
|
const instance = instanceCheck as [];
|
||||||
|
return instance[instance.length - 1];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ([].isEmpty === undefined) {
|
||||||
|
// eslint-disable-next-line no-extend-native
|
||||||
|
Array.prototype.isEmpty = function () {
|
||||||
|
return this.length === 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ([].isNotEmpty === undefined) {
|
||||||
|
Array.prototype.isNotEmpty = function () {
|
||||||
|
return this.length !== 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,24 @@
|
||||||
import { ArrayEquals } from "./array";
|
import { ArrayExtensions } from "./array";
|
||||||
|
import { StringExtensions } from "./string";
|
||||||
|
|
||||||
export const extensions = () =>{
|
declare global {
|
||||||
ArrayEquals()
|
interface Array<T> {
|
||||||
|
// @strict: The parameter is determined whether the arrays must be exactly the same in content and order of this relationship or simply follow the same requirements.
|
||||||
|
equals(array: Array<T>, strict: boolean): boolean;
|
||||||
|
lastElement(): T | undefined;
|
||||||
|
firstElement(): T | undefined;
|
||||||
|
isEmpty(): boolean;
|
||||||
|
isNotEmpty(): boolean;
|
||||||
|
}
|
||||||
|
interface String {
|
||||||
|
isEmpty(): boolean;
|
||||||
|
isNotEmpty(): boolean;
|
||||||
|
lastElement(): string;
|
||||||
|
hasPattern(pattern: string): boolean;
|
||||||
|
hasNoPattern(pattern: string): boolean;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
export const extensions = () => {
|
||||||
|
ArrayExtensions();
|
||||||
|
StringExtensions();
|
||||||
|
};
|
||||||
|
|
29
server/src/core/extensions/string.ts
Normal file
29
server/src/core/extensions/string.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
export const StringExtensions = () => {
|
||||||
|
if ("".isEmpty === undefined) {
|
||||||
|
// eslint-disable-next-line no-extend-native
|
||||||
|
String.prototype.isEmpty = function () {
|
||||||
|
return this.length === 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ("".isNotEmpty === undefined) {
|
||||||
|
// eslint-disable-next-line no-extend-native
|
||||||
|
String.prototype.isNotEmpty = function () {
|
||||||
|
return this.length !== 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ("".lastElement === undefined) {
|
||||||
|
String.prototype.lastElement = function () {
|
||||||
|
return this[this.length - 1];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ("".hasPattern === undefined) {
|
||||||
|
String.prototype.hasPattern = function (pattern) {
|
||||||
|
return new RegExp(pattern).test(this);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ("".hasNoPattern === undefined) {
|
||||||
|
String.prototype.hasNoPattern = function (pattern) {
|
||||||
|
return !this.hasPattern(pattern);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
export function delay(ms: number) {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
export interface Listener<T> {
|
|
||||||
(event: T): any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Disposable {
|
|
||||||
dispose():void;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class TypedEvent<T> {
|
|
||||||
private listeners: Listener<T>[] = [];
|
|
||||||
public listenersOnces: Listener<T>[] = [];
|
|
||||||
|
|
||||||
on = (listener: Listener<T>): Disposable => {
|
|
||||||
this.listeners.push(listener);
|
|
||||||
return {
|
|
||||||
dispose: () => this.off(listener),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
once = (listener: Listener<T>): void => {
|
|
||||||
this.listenersOnces.push(listener);
|
|
||||||
};
|
|
||||||
|
|
||||||
off = (listener: Listener<T>) => {
|
|
||||||
const callbackIndex = this.listeners.indexOf(
|
|
||||||
listener
|
|
||||||
);
|
|
||||||
if (callbackIndex > -1)
|
|
||||||
this.listeners.splice(callbackIndex, 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
emit = (event: T) => {
|
|
||||||
|
|
||||||
this.listeners.forEach((listener) =>
|
|
||||||
listener(event)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.listenersOnces.length > 0) {
|
|
||||||
const toCall = this.listenersOnces;
|
|
||||||
this.listenersOnces = [];
|
|
||||||
toCall.forEach((listener) => listener(event));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pipe = (te: TypedEvent<T>): Disposable => {
|
|
||||||
return this.on((e) => te.emit(e));
|
|
||||||
};
|
|
||||||
}
|
|
85
server/src/core/helpers/class_validator_mocker.ts
Normal file
85
server/src/core/helpers/class_validator_mocker.ts
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import { randomBytes, randomInt, randomUUID } from "crypto";
|
||||||
|
import { getMetadataStorage, IS_ARRAY, IS_BOOLEAN, IS_MONGO_ID, IS_NUMBER, IS_STRING, IS_UUID } from "class-validator";
|
||||||
|
import { ValidationMetadata } from "class-validator/types/metadata/ValidationMetadata";
|
||||||
|
|
||||||
|
type AvailableTypes = string | number | boolean | undefined | [];
|
||||||
|
|
||||||
|
export class ClassValidatorMocker {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
public static create<T>(constructor: Function, partial: Partial<T> = {}): T {
|
||||||
|
return new ClassValidatorMocker().create(constructor, partial);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
public create<T>(constructor: Function, partial: Partial<T> = {}): T {
|
||||||
|
const metadataStorage = getMetadataStorage();
|
||||||
|
const targetMetadatas = metadataStorage.getTargetValidationMetadatas(constructor, "", false, false);
|
||||||
|
const groupedMetadatas = metadataStorage.groupByPropertyName(targetMetadatas);
|
||||||
|
// nestedValidation
|
||||||
|
let randomFixture = {} as T;
|
||||||
|
|
||||||
|
for (const propertyName of Object.keys(groupedMetadatas)) {
|
||||||
|
const metadatas = groupedMetadatas[propertyName];
|
||||||
|
const value = this.generatePropertyValueFromMetadatas(metadatas);
|
||||||
|
|
||||||
|
if (value !== undefined) {
|
||||||
|
randomFixture = {
|
||||||
|
...randomFixture,
|
||||||
|
[propertyName]: value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...randomFixture, ...partial };
|
||||||
|
}
|
||||||
|
|
||||||
|
private generatePropertyValueFromMetadatas(metadatas: ValidationMetadata[]): AvailableTypes {
|
||||||
|
for (const metadata of metadatas) {
|
||||||
|
const constraints = getMetadataStorage().getTargetValidatorConstraints(metadata.constraintCls);
|
||||||
|
|
||||||
|
for (const constraint of constraints) {
|
||||||
|
switch (constraint.name) {
|
||||||
|
case IS_ARRAY:
|
||||||
|
return [];
|
||||||
|
case "isEnum":
|
||||||
|
return Object.keys(metadata.constraints.firstElement()).firstElement();
|
||||||
|
case IS_MONGO_ID:
|
||||||
|
return this.randomUUID();
|
||||||
|
case IS_STRING:
|
||||||
|
return this.randomString();
|
||||||
|
case IS_NUMBER:
|
||||||
|
return this.randomNumber();
|
||||||
|
case IS_BOOLEAN:
|
||||||
|
return this.randomBoolean();
|
||||||
|
case IS_UUID:
|
||||||
|
return this.randomUUID();
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private randomString(): string {
|
||||||
|
return randomBytes(randomInt(1, 10)).toString("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
private randomNumber(): number {
|
||||||
|
return randomInt(0, 99_999);
|
||||||
|
}
|
||||||
|
|
||||||
|
private randomBoolean(): boolean {
|
||||||
|
return randomInt(0, 1) === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private randomUUID(): string {
|
||||||
|
if (randomUUID != null) {
|
||||||
|
return randomUUID();
|
||||||
|
}
|
||||||
|
|
||||||
|
return randomBytes(16).toString("hex");
|
||||||
|
}
|
||||||
|
}
|
3
server/src/core/helpers/delay.ts
Normal file
3
server/src/core/helpers/delay.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export function delay(ms: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
444
server/src/core/helpers/result.ts
Normal file
444
server/src/core/helpers/result.ts
Normal file
|
@ -0,0 +1,444 @@
|
||||||
|
/* eslint-disable @typescript-eslint/ban-types */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
|
||||||
|
/* eslint-disable @typescript-eslint/no-namespace */
|
||||||
|
|
||||||
|
function isAsyncFn(fn: Function) {
|
||||||
|
return fn.constructor.name === "AsyncFunction";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isResult(value: unknown): value is Result<any, any, any> {
|
||||||
|
return value instanceof Ok || value instanceof Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SyncThenable {
|
||||||
|
isSync: true;
|
||||||
|
then<Fn extends () => Promise<any>>(cb: Fn): ReturnType<Fn>;
|
||||||
|
then<Fn extends () => any>(cb: Fn): SyncThenable;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncThenable(): SyncThenable {
|
||||||
|
function then<Fn extends () => Promise<any>>(cb: Fn): ReturnType<Fn>;
|
||||||
|
function then<Fn extends () => any>(cb: Fn): SyncThenable;
|
||||||
|
function then(cb: any) {
|
||||||
|
const result = cb();
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return syncThenable();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isSync: true,
|
||||||
|
then,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function forEachValueThunkOrPromise<T>(items: unknown[], execFn: (value: T) => boolean, foldFn: () => unknown) {
|
||||||
|
let shouldBreak = false;
|
||||||
|
|
||||||
|
const result: any = items.reduce((prev: { then: Function }, valueOrThunk) => {
|
||||||
|
return prev.then(() => {
|
||||||
|
if (shouldBreak) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function run(value: T) {
|
||||||
|
const isSuccess = execFn(value);
|
||||||
|
if (!isSuccess) {
|
||||||
|
shouldBreak = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const valueOrPromise = typeof valueOrThunk === "function" ? valueOrThunk() : valueOrThunk;
|
||||||
|
|
||||||
|
if (valueOrPromise instanceof Promise) {
|
||||||
|
return valueOrPromise.then(run);
|
||||||
|
}
|
||||||
|
|
||||||
|
return run(valueOrPromise);
|
||||||
|
});
|
||||||
|
}, syncThenable());
|
||||||
|
|
||||||
|
if ((result as SyncThenable).isSync) {
|
||||||
|
return foldFn();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.then(() => {
|
||||||
|
return foldFn();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Result<ErrorType, OkType, RollbackFn extends RollbackFunction = any> =
|
||||||
|
| Ok<ErrorType, OkType, RollbackFn>
|
||||||
|
| Err<ErrorType, OkType, RollbackFn>;
|
||||||
|
|
||||||
|
interface IResult<ErrorType, OkType> {
|
||||||
|
isSuccess(): this is Ok<ErrorType, OkType, any>;
|
||||||
|
|
||||||
|
isFailure(): this is Err<ErrorType, OkType, any>;
|
||||||
|
|
||||||
|
getOrNull(): OkType | null;
|
||||||
|
|
||||||
|
toString(): string;
|
||||||
|
|
||||||
|
inspect(): string;
|
||||||
|
|
||||||
|
fold<R>(onSuccess: (value: OkType) => R, onFailure: (error: ErrorType) => R): R;
|
||||||
|
fold<R>(onSuccess: (value: OkType) => Promise<R>, onFailure: (error: ErrorType) => Promise<R>): Promise<R>;
|
||||||
|
|
||||||
|
getOrDefault(defaultValue: OkType): OkType;
|
||||||
|
|
||||||
|
getOrElse(onFailure: (error: ErrorType) => OkType): OkType;
|
||||||
|
getOrElse(onFailure: (error: ErrorType) => Promise<OkType>): Promise<OkType>;
|
||||||
|
|
||||||
|
getOrThrow(): OkType;
|
||||||
|
|
||||||
|
map<T>(
|
||||||
|
fn: (value: OkType) => Promise<T>
|
||||||
|
): Promise<JoinErrorTypes<ErrorType, T extends Result<any, any, any> ? T : Result<Error, T, any>>>;
|
||||||
|
map<T>(
|
||||||
|
fn: (value: OkType) => T
|
||||||
|
): JoinErrorTypes<ErrorType, T extends Result<any, any, any> ? T : Result<Error, T, any>>;
|
||||||
|
|
||||||
|
rollback(): Result<Error, void> | Promise<Result<Error, void>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type InferErrorType<T extends Result<any, any, any>> = T extends Result<infer Errortype, any, any> ? Errortype : never;
|
||||||
|
|
||||||
|
type InferOkType<T extends Result<any, any, any>> = T extends Result<any, infer OkType, any> ? OkType : never;
|
||||||
|
|
||||||
|
type JoinErrorTypes<ErrorType, B extends Result<any, any, any>> = Result<
|
||||||
|
ErrorType | InferErrorType<B>,
|
||||||
|
InferOkType<B>,
|
||||||
|
any
|
||||||
|
>;
|
||||||
|
|
||||||
|
type ExtractErrorTypes<Tuple extends any[]> = {
|
||||||
|
[Index in keyof Tuple]: Tuple[Index] extends Result<any, any, any> ? InferErrorType<Tuple[Index]> : never;
|
||||||
|
}[number];
|
||||||
|
|
||||||
|
type MapResultTupleToOkTypeTuple<Tuple extends any[]> = {
|
||||||
|
[Index in keyof Tuple]: Tuple[Index] extends Result<any, any, any> ? InferOkType<Tuple[Index]> : never;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RollbackFunction = (() => void) | (() => Promise<void>);
|
||||||
|
|
||||||
|
type HasAsyncRollbackFunction<T extends any[]> = {
|
||||||
|
[Index in keyof T]: T[Index] extends () => Promise<infer U> | infer U
|
||||||
|
? U extends Result<any, any, () => Promise<void>>
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
: false;
|
||||||
|
}[number] extends false
|
||||||
|
? false
|
||||||
|
: true;
|
||||||
|
|
||||||
|
type UnwrapThunks<T extends any[]> = {
|
||||||
|
[Index in keyof T]: T[Index] extends () => Promise<infer U> ? U : T[Index] extends () => infer U ? U : T[Index];
|
||||||
|
};
|
||||||
|
|
||||||
|
type HasAsyncThunk<T extends any[]> = {
|
||||||
|
[Index in keyof T]: T[Index] extends () => Promise<any> ? true : false;
|
||||||
|
}[number] extends false
|
||||||
|
? false
|
||||||
|
: true;
|
||||||
|
|
||||||
|
type PromiseReturnType<T extends (...args: any) => any> = T extends (...args: any) => Promise<infer U> ? U : never;
|
||||||
|
|
||||||
|
export namespace Result {
|
||||||
|
export function ok<ErrorType extends unknown, OkType, RollbackFn extends RollbackFunction = any>(
|
||||||
|
value?: OkType,
|
||||||
|
rollbackFn?: RollbackFn
|
||||||
|
): Result<ErrorType, OkType, RollbackFn> {
|
||||||
|
return new Ok<ErrorType, OkType, RollbackFn>(value || null!, rollbackFn) as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function error<ErrorType extends unknown, OkType extends unknown, RollbackFn extends RollbackFunction = any>(
|
||||||
|
error: ErrorType,
|
||||||
|
rollbackFn?: RollbackFn
|
||||||
|
): Result<ErrorType, OkType, RollbackFn> {
|
||||||
|
return new Err<ErrorType, OkType, RollbackFn>(error, rollbackFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
type SafeReturnType<E, T> = T extends Result<any, any, any>
|
||||||
|
? Result<E | InferErrorType<T>, InferOkType<T>, never>
|
||||||
|
: Result<E, T, never>;
|
||||||
|
|
||||||
|
export function safe<T>(fn: () => Promise<T>): Promise<SafeReturnType<Error, T>>;
|
||||||
|
export function safe<T>(fn: () => T): SafeReturnType<Error, T>;
|
||||||
|
export function safe<ErrorType, T>(
|
||||||
|
err: ErrorType | (new (...args: any[]) => ErrorType),
|
||||||
|
fn: () => Promise<T>
|
||||||
|
): Promise<SafeReturnType<ErrorType, T>>;
|
||||||
|
export function safe<ErrorType, T>(
|
||||||
|
err: ErrorType | (new (...args: any[]) => ErrorType),
|
||||||
|
fn: () => T
|
||||||
|
): SafeReturnType<ErrorType, T>;
|
||||||
|
export function safe(errOrFn: any, fn?: any) {
|
||||||
|
const hasCustomError = fn !== undefined;
|
||||||
|
|
||||||
|
const execute = hasCustomError ? fn : errOrFn;
|
||||||
|
|
||||||
|
function getError(caughtError: Error) {
|
||||||
|
if (!hasCustomError) {
|
||||||
|
return caughtError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof errOrFn === "function") {
|
||||||
|
return new errOrFn(caughtError);
|
||||||
|
}
|
||||||
|
|
||||||
|
return errOrFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resultOrPromise = execute();
|
||||||
|
|
||||||
|
if (resultOrPromise instanceof Promise) {
|
||||||
|
return resultOrPromise
|
||||||
|
.then((okValue) => {
|
||||||
|
return isResult(okValue) ? okValue : Result.ok(okValue);
|
||||||
|
})
|
||||||
|
.catch((caughtError) => error(getError(caughtError)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return isResult(resultOrPromise) ? resultOrPromise : Result.ok(resultOrPromise);
|
||||||
|
} catch (caughtError) {
|
||||||
|
return error(getError(caughtError as Error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CombineResult<T extends (unknown | (() => unknown) | (() => Promise<unknown>))[]> = Result<
|
||||||
|
ExtractErrorTypes<UnwrapThunks<T>>,
|
||||||
|
MapResultTupleToOkTypeTuple<UnwrapThunks<T>>,
|
||||||
|
HasAsyncRollbackFunction<T> extends true ? () => Promise<void> : () => void
|
||||||
|
>;
|
||||||
|
|
||||||
|
export function combine<T extends (unknown | (() => unknown) | (() => Promise<unknown>))[]>(
|
||||||
|
...items: T
|
||||||
|
): HasAsyncThunk<T> extends true ? Promise<CombineResult<T>> : CombineResult<T> {
|
||||||
|
if (!items.length) {
|
||||||
|
throw new Error("Expected at least 1 argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
const values: unknown[] = [];
|
||||||
|
const rollbacks: RollbackFunction[] = [];
|
||||||
|
let error: Err<unknown, unknown, any> | null = null;
|
||||||
|
|
||||||
|
function rollback() {
|
||||||
|
const reversedRollbacks = rollbacks.reverse();
|
||||||
|
const wrappedRollbackFns = reversedRollbacks.map((fn) => Result.wrap(fn));
|
||||||
|
|
||||||
|
let error: Err<unknown, unknown, any> | null = null;
|
||||||
|
|
||||||
|
return forEachValueThunkOrPromise(
|
||||||
|
wrappedRollbackFns,
|
||||||
|
(result: Result<unknown, unknown>) => {
|
||||||
|
if (result.isFailure()) {
|
||||||
|
error = Result.error<unknown, unknown, any>(result.error) as any;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
() => error || ok()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return forEachValueThunkOrPromise(
|
||||||
|
items,
|
||||||
|
(result: Result<unknown, unknown>) => {
|
||||||
|
if (result.isFailure()) {
|
||||||
|
error = Result.error<unknown, unknown>(result.error, rollback) as any;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
values.push(result.value);
|
||||||
|
rollbacks.push(() => result.rollback());
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
() => error || ok(values, rollback)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function wrap<Fn extends (...args: any) => Promise<any>>(
|
||||||
|
fn: Fn
|
||||||
|
): (...args: Parameters<Fn>) => Promise<Result<Error, PromiseReturnType<Fn>, never>>;
|
||||||
|
export function wrap<Fn extends (...args: any) => any>(
|
||||||
|
fn: Fn
|
||||||
|
): (...args: Parameters<Fn>) => Result<Error, ReturnType<Fn>, never>;
|
||||||
|
export function wrap(fn: any) {
|
||||||
|
return function wrapped(...args: any) {
|
||||||
|
try {
|
||||||
|
const resultOrPromise = fn(...args);
|
||||||
|
|
||||||
|
if (resultOrPromise instanceof Promise) {
|
||||||
|
return resultOrPromise.then((okValue) => Result.ok(okValue)).catch((err) => error(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok(resultOrPromise);
|
||||||
|
} catch (err) {
|
||||||
|
return error(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Base<ErrorType extends unknown, OkType extends unknown, RollbackFn extends RollbackFunction>
|
||||||
|
implements IResult<ErrorType, OkType>
|
||||||
|
{
|
||||||
|
constructor(protected readonly rollbackFn?: RollbackFn) {}
|
||||||
|
|
||||||
|
errorOrNull(): ErrorType | null {
|
||||||
|
if (this.isSuccess()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (this as any).error as ErrorType;
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrNull(): OkType | null {
|
||||||
|
if (this.isFailure()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (this as any).value as OkType;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
inspect(): string {
|
||||||
|
return this.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
fold<R>(onSuccess: (value: OkType) => R, onFailure: (error: ErrorType) => R): R;
|
||||||
|
fold<R>(onSuccess: (value: OkType) => Promise<R>, onFailure: (error: ErrorType) => Promise<R>): Promise<R>;
|
||||||
|
fold(onSuccess: any, onFailure: any) {
|
||||||
|
if (this.isFailure()) {
|
||||||
|
return onFailure(this.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return onSuccess((this as any).value as OkType);
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrDefault(defaultValue: OkType): OkType {
|
||||||
|
if (this.isSuccess()) {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrElse(onFailure: (error: ErrorType) => OkType): OkType;
|
||||||
|
getOrElse(onFailure: (error: ErrorType) => Promise<OkType>): Promise<OkType>;
|
||||||
|
getOrElse(onFailure: any) {
|
||||||
|
if (this.isSuccess()) {
|
||||||
|
return isAsyncFn(onFailure) ? Promise.resolve(this.value) : this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return onFailure((this as any).error as ErrorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrThrow(): OkType {
|
||||||
|
if (this.isFailure()) {
|
||||||
|
throw this.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (this as any).value as OkType;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSuccess(): this is Ok<ErrorType, OkType, RollbackFn> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
isFailure(): this is Err<ErrorType, OkType, RollbackFn> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
map<T>(
|
||||||
|
fn: (value: OkType) => Promise<T>
|
||||||
|
): Promise<JoinErrorTypes<ErrorType, T extends Result<any, any, any> ? T : Result<Error, T, any>>>;
|
||||||
|
map<T>(
|
||||||
|
fn: (value: OkType) => T
|
||||||
|
): JoinErrorTypes<ErrorType, T extends Result<any, any, any> ? T : Result<Error, T, any>>;
|
||||||
|
map(fn: any) {
|
||||||
|
if (this.isFailure()) {
|
||||||
|
return isAsyncFn(fn) ? Promise.resolve(this) : this;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = Result.safe(() => fn((this as any).value) as any);
|
||||||
|
|
||||||
|
return result as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
rollback(): RollbackFn extends RollbackFunction
|
||||||
|
? RollbackFn extends () => Promise<void>
|
||||||
|
? Promise<Result<Error, void>>
|
||||||
|
: Result<Error, void>
|
||||||
|
: void {
|
||||||
|
if (this.rollbackFn) {
|
||||||
|
return this.rollbackFn() as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null as any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Ok<ErrorType extends unknown, OkType extends unknown, RollbackFn extends RollbackFunction> extends Base<
|
||||||
|
ErrorType,
|
||||||
|
OkType,
|
||||||
|
RollbackFn
|
||||||
|
> {
|
||||||
|
public readonly value: OkType;
|
||||||
|
|
||||||
|
constructor(val: OkType, rollbackFn?: RollbackFn) {
|
||||||
|
super(rollbackFn);
|
||||||
|
this.value = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSuccess(): this is Ok<ErrorType, OkType, RollbackFn> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
isFailure(): this is Err<ErrorType, OkType, RollbackFn> {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return `Result.Ok(${this.value})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
forward(): Result<any, OkType, RollbackFn> {
|
||||||
|
return Result.ok(this.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Err<ErrorType extends unknown, OkType extends unknown, RollbackFn extends RollbackFunction> extends Base<
|
||||||
|
ErrorType,
|
||||||
|
OkType,
|
||||||
|
RollbackFn
|
||||||
|
> {
|
||||||
|
public readonly error: ErrorType;
|
||||||
|
|
||||||
|
constructor(err: ErrorType, rollbackFn?: RollbackFn) {
|
||||||
|
super(rollbackFn);
|
||||||
|
this.error = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSuccess(): this is Ok<ErrorType, OkType, RollbackFn> {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isFailure(): this is Err<ErrorType, OkType, RollbackFn> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return `Result.Error(${this.error})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
forward(): Result<ErrorType, any, RollbackFn> {
|
||||||
|
return Result.error(this.error);
|
||||||
|
}
|
||||||
|
}
|
42
server/src/core/helpers/typed_event.ts
Normal file
42
server/src/core/helpers/typed_event.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
export interface Listener<T> {
|
||||||
|
(event: T): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Disposable {
|
||||||
|
dispose(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TypedEvent<T> {
|
||||||
|
private listeners: Listener<T>[] = [];
|
||||||
|
public listenersOnces: Listener<T>[] = [];
|
||||||
|
|
||||||
|
on = (listener: Listener<T>): Disposable => {
|
||||||
|
this.listeners.push(listener);
|
||||||
|
return {
|
||||||
|
dispose: () => this.off(listener),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
once = (listener: Listener<T>): void => {
|
||||||
|
this.listenersOnces.push(listener);
|
||||||
|
};
|
||||||
|
|
||||||
|
off = (listener: Listener<T>) => {
|
||||||
|
const callbackIndex = this.listeners.indexOf(listener);
|
||||||
|
if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
emit = (event: T) => {
|
||||||
|
this.listeners.forEach((listener) => listener(event));
|
||||||
|
|
||||||
|
if (this.listenersOnces.length > 0) {
|
||||||
|
const toCall = this.listenersOnces;
|
||||||
|
this.listenersOnces = [];
|
||||||
|
toCall.forEach((listener) => listener(event));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pipe = (te: TypedEvent<T>): Disposable => {
|
||||||
|
return this.on((e) => te.emit(e));
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { EXEC_EVENT, EXEC_TYPE, ExecError } from "../model/exec_error_model";
|
import { EXEC_EVENT, EXEC_TYPE, ExecError } from "../models/exec_error_model";
|
||||||
import * as cp from "child_process";
|
import * as cp from "child_process";
|
||||||
import { ExecutorResult } from "../model/executor_result";
|
import { ExecutorResult } from "../models/executor_result";
|
||||||
|
|
||||||
export enum WorkerType {
|
export enum WorkerType {
|
||||||
EXEC = "EXEC",
|
EXEC = "EXEC",
|
||||||
|
@ -11,7 +11,7 @@ export interface WorkerDataExec {
|
||||||
command: string;
|
command: string;
|
||||||
execPath: string;
|
execPath: string;
|
||||||
type: WorkerType;
|
type: WorkerType;
|
||||||
cliArgs:Array<string> | undefined
|
cliArgs: Array<string> | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
process.on("message", async (message) => {
|
process.on("message", async (message) => {
|
||||||
|
@ -30,15 +30,15 @@ process.on("message", async (message) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
subprocess.on('close', (_code) =>{
|
subprocess.on("close", (_code) => {
|
||||||
if (process.send) {
|
if (process.send) {
|
||||||
process.send({
|
process.send({
|
||||||
type: EXEC_TYPE.SPAWN.toString(),
|
type: EXEC_TYPE.SPAWN.toString(),
|
||||||
event: EXEC_EVENT.END.toString(),
|
event: EXEC_EVENT.END.toString(),
|
||||||
data:null
|
data: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
process.on("uncaughtException", (error) => {
|
process.on("uncaughtException", (error) => {
|
||||||
if (process.send) {
|
if (process.send) {
|
||||||
process.send({
|
process.send({
|
||||||
|
@ -50,26 +50,21 @@ process.on("message", async (message) => {
|
||||||
});
|
});
|
||||||
} else if (workerData.type == WorkerType.EXEC) {
|
} else if (workerData.type == WorkerType.EXEC) {
|
||||||
try {
|
try {
|
||||||
const result = await exec(workerData.command, {
|
const result = await exec(workerData.command, {
|
||||||
cwd: workerData.execPath,
|
cwd: workerData.execPath,
|
||||||
});
|
});
|
||||||
if (process.send) {
|
if (process.send) {
|
||||||
process.send(
|
process.send(new ExecutorResult(EXEC_TYPE.EXEC, EXEC_EVENT.END, result));
|
||||||
new ExecutorResult(EXEC_TYPE.EXEC, EXEC_EVENT.END, result)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (process.send) {
|
if (process.send) {
|
||||||
process.send(new ExecError(workerData.command, error ));
|
process.send(new ExecError(workerData.command, error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function exec(
|
async function exec(cmd: string, opts: cp.ExecOptions & { trim?: boolean } = {}): Promise<string> {
|
||||||
cmd: string,
|
|
||||||
opts: cp.ExecOptions & { trim?: boolean } = {}
|
|
||||||
): Promise<string> {
|
|
||||||
return new Promise((c, e) => {
|
return new Promise((c, e) => {
|
||||||
cp.exec(cmd, { env: process.env, ...opts }, (err, stdout) => {
|
cp.exec(cmd, { env: process.env, ...opts }, (err, stdout) => {
|
||||||
return err ? e(err) : c(opts.trim ? stdout.trim() : stdout);
|
return err ? e(err) : c(opts.trim ? stdout.trim() : stdout);
|
3
server/src/core/interfaces/file.ts
Normal file
3
server/src/core/interfaces/file.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export interface IFile extends File {
|
||||||
|
data: Buffer;
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
// export class Payload<T>{
|
// export class Payload<T>{
|
||||||
// model: T | undefined
|
// model: T | undefined
|
||||||
// query:string | undefined
|
// query:string | undefined
|
||||||
|
@ -11,4 +10,4 @@
|
||||||
// isEmpty(){
|
// isEmpty(){
|
||||||
// return this.model != undefined || this.query != undefined
|
// return this.model != undefined || this.query != undefined
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
|
@ -5,10 +5,7 @@ export interface Routes {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRouteModel {
|
export interface IRouteModel {
|
||||||
validationModel: any;
|
validationModel?: any;
|
||||||
url: string;
|
url: string;
|
||||||
databaseModel: any;
|
databaseModel?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
// import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
// export const validationMiddleware = (
|
export const validationMiddleware = (
|
||||||
// type: any,
|
type: any,
|
||||||
// value = 'body',
|
value = "body",
|
||||||
// skipMissingProperties = false,
|
skipMissingProperties = false,
|
||||||
// whitelist = true,
|
whitelist = true,
|
||||||
// forbidNonWhitelisted = true,
|
forbidNonWhitelisted = true
|
||||||
// ): RequestHandler => {
|
): RequestHandler => {
|
||||||
|
// TODO:(IDONTSUDO) need TOKEN
|
||||||
|
// return nextTick
|
||||||
// }
|
return (req, res, next) => {};
|
||||||
|
};
|
||||||
|
|
|
@ -1,30 +1,28 @@
|
||||||
import { plainToInstance } from 'class-transformer';
|
import { plainToInstance } from "class-transformer";
|
||||||
import { validate, ValidationError } from 'class-validator';
|
import { validate, ValidationError } from "class-validator";
|
||||||
import { RequestHandler } from 'express';
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const validationModelMiddleware = (
|
export const validationModelMiddleware = (
|
||||||
type: any,
|
type: any,
|
||||||
value = 'body',
|
value = "body",
|
||||||
skipMissingProperties = false,
|
skipMissingProperties = false,
|
||||||
whitelist = true,
|
whitelist = true,
|
||||||
forbidNonWhitelisted = true,
|
forbidNonWhitelisted = true
|
||||||
): RequestHandler => {
|
): RequestHandler => {
|
||||||
return (req, res, next) => {
|
return (req, res, next) => {
|
||||||
if(type === null && type == undefined){
|
if (type === null && type == undefined) {
|
||||||
next()
|
next();
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
const model = plainToInstance(type, req[value]);
|
const model = plainToInstance(type, req[value]);
|
||||||
validate(model, { skipMissingProperties, whitelist, forbidNonWhitelisted }).then((errors: ValidationError[]) => {
|
validate(model, { skipMissingProperties, whitelist, forbidNonWhitelisted }).then((errors: ValidationError[]) => {
|
||||||
console.log(errors)
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
const message = errors.map((error: ValidationError) => Object.values(error.constraints)).join(', ');
|
const message = errors.map((error: ValidationError) => Object.values(error.constraints)).join(", ");
|
||||||
return res.status(400).json(message)
|
return res.status(400).json(message);
|
||||||
} else {
|
} else {
|
||||||
req['model'] = model
|
req["model"] = model;
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
24
server/src/core/models/active_pipeline_model.ts
Normal file
24
server/src/core/models/active_pipeline_model.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
export class ActivePipeline {
|
||||||
|
pipelineIsRunning: boolean;
|
||||||
|
projectUUID?: string | null;
|
||||||
|
lastProcessCompleteCount: number | null;
|
||||||
|
error: any;
|
||||||
|
path: string;
|
||||||
|
constructor(
|
||||||
|
pipelineIsRunning: boolean,
|
||||||
|
lastProcessCompleteCount: number | null,
|
||||||
|
error: any,
|
||||||
|
path: string | null,
|
||||||
|
projectUUID?: string | null
|
||||||
|
) {
|
||||||
|
this.pipelineIsRunning = pipelineIsRunning;
|
||||||
|
this.projectUUID = projectUUID;
|
||||||
|
this.lastProcessCompleteCount = lastProcessCompleteCount;
|
||||||
|
this.error = error;
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
static empty() {
|
||||||
|
return new ActivePipeline(false, null, null, null, null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +1,23 @@
|
||||||
|
import { extensions } from "../extensions/extensions";
|
||||||
|
|
||||||
|
extensions();
|
||||||
|
|
||||||
export class ExecError extends Error {
|
export class ExecError extends Error {
|
||||||
static isExecError(e: any) {
|
static isExecError(e: any): ExecError | void {
|
||||||
if ("type" in e && "script" in e && "unixTime" in e && 'error' in e) {
|
if ("type" in e && "script" in e && "unixTime" in e && "error" in e) {
|
||||||
return new ExecError(e.type, e.event, e.data);
|
return new ExecError(e.type, e.event, e.data);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
script: string;
|
script: string;
|
||||||
unixTime: number;
|
unixTime: number;
|
||||||
type = EXEC_TYPE.EXEC;
|
type = EXEC_TYPE.EXEC;
|
||||||
error:any;
|
error: any;
|
||||||
constructor(
|
constructor(script: string, ...args: any) {
|
||||||
script: string,
|
|
||||||
...args: any
|
|
||||||
) {
|
|
||||||
super(...args);
|
super(...args);
|
||||||
this.script = script;
|
this.script = script;
|
||||||
this.unixTime = Date.now();
|
this.unixTime = Date.now();
|
||||||
this.error = args[0]
|
this.error = args.firstElement();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,27 +27,15 @@ export class SpawnError extends Error {
|
||||||
unixTime: number;
|
unixTime: number;
|
||||||
type = EXEC_TYPE.SPAWN;
|
type = EXEC_TYPE.SPAWN;
|
||||||
|
|
||||||
constructor(
|
constructor(environment: string, script: string, ...args: any) {
|
||||||
environment: string,
|
|
||||||
script: string,
|
|
||||||
...args: any
|
|
||||||
) {
|
|
||||||
super(...args);
|
super(...args);
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
this.script = script;
|
this.script = script;
|
||||||
this.unixTime = Date.now();
|
this.unixTime = Date.now();
|
||||||
}
|
}
|
||||||
static isError(errorType: any): void | SpawnError {
|
static isError(errorType: any): SpawnError | void {
|
||||||
if (
|
if ("command" in errorType && "error" in errorType && "execPath" in errorType) {
|
||||||
"command" in errorType &&
|
return new SpawnError(errorType.command, errorType.execPath, errorType.error);
|
||||||
"error" in errorType &&
|
|
||||||
"execPath" in errorType
|
|
||||||
) {
|
|
||||||
return new SpawnError(
|
|
||||||
errorType.command,
|
|
||||||
errorType.execPath,
|
|
||||||
errorType.error
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
export enum EventsFileChanger {
|
export enum EventsFileChanger {
|
||||||
remove = "remove",
|
|
||||||
update = "update",
|
update = "update",
|
||||||
delete = "delete",
|
delete = "delete",
|
||||||
create = "create",
|
create = "create",
|
||||||
|
@ -18,6 +17,7 @@ export class MetaDataFileManagerModel {
|
||||||
this.event = event;
|
this.event = event;
|
||||||
this.unixTime = Date.now();
|
this.unixTime = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
public get timeString(): string {
|
public get timeString(): string {
|
||||||
const date = new Date(this.unixTime * 1000);
|
const date = new Date(this.unixTime * 1000);
|
||||||
const hours = date.getHours();
|
const hours = date.getHours();
|
|
@ -1,9 +1,9 @@
|
||||||
import { Trigger } from "../../features/triggers/trigger_model";
|
import { Trigger } from "../../features/triggers/models/trigger_database_model";
|
||||||
import { EXEC_TYPE } from "./exec_error_model";
|
import { EXEC_TYPE } from "./exec_error_model";
|
||||||
|
|
||||||
export interface IPipeline {
|
export interface IPipeline {
|
||||||
process: IProcess;
|
process: IProcess;
|
||||||
trigger: Trigger;
|
trigger?: Trigger;
|
||||||
env: Env | null;
|
env: Env | null;
|
||||||
stackGenerateType: StackGenerateType;
|
stackGenerateType: StackGenerateType;
|
||||||
}
|
}
|
31
server/src/core/repository/fs.ts
Normal file
31
server/src/core/repository/fs.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
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;
|
||||||
|
}
|
24
server/src/core/repository/http_repository.ts
Normal file
24
server/src/core/repository/http_repository.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import axios from "axios";
|
||||||
|
import { Result } from "../helpers/result";
|
||||||
|
import { HttpMethodType } from "../controllers/http_controller";
|
||||||
|
|
||||||
|
export class HttpRepository {
|
||||||
|
serverUrl = "http://localhost:4001";
|
||||||
|
|
||||||
|
constructor(serverURL: string) {
|
||||||
|
this.serverUrl = serverURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
async jsonRequest<T>(url: string, method: HttpMethodType, reqBody?: any): Promise<Result<Error, T>> {
|
||||||
|
try {
|
||||||
|
const result = await axios(this.serverUrl + url, { method: method, data: reqBody });
|
||||||
|
if (result.status !== 200) {
|
||||||
|
return Result.error(Error("status code" + String(result.status)));
|
||||||
|
}
|
||||||
|
return Result.ok(result.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return Result.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import {
|
||||||
|
IProjectInstanceModel,
|
||||||
|
ProjectInstanceDbModel,
|
||||||
|
} 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 { 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
import cluster, { Worker } from "node:cluster";
|
import cluster, { Worker } from "node:cluster";
|
||||||
import { TypedEvent } from "../helper/typed_event";
|
import { TypedEvent } from "../helpers/typed_event";
|
||||||
import { Result } from "../helper/result";
|
import { Result } from "../helpers/result";
|
||||||
import { WorkerDataExec, WorkerType } from "../helper/worker_computed";
|
import { WorkerDataExec, WorkerType } from "../helpers/worker_computed";
|
||||||
import { delay } from "../helper/delay";
|
import { delay } from "../helpers/delay";
|
||||||
import { ExecutorResult } from "../model/executor_result";
|
import { ExecutorResult } from "../models/executor_result";
|
||||||
import { EXEC_TYPE, ExecError, SpawnError } from "../model/exec_error_model";
|
import { EXEC_TYPE, ExecError, SpawnError } from "../models/exec_error_model";
|
||||||
|
|
||||||
abstract class IExecutorProgramService {
|
abstract class IExecutorProgramService {
|
||||||
abstract execPath: string;
|
abstract execPath: string;
|
||||||
|
@ -25,13 +25,9 @@ export class ExecutorProgramService
|
||||||
this.maxTime = maxTime;
|
this.maxTime = maxTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async workerExecuted(
|
private async workerExecuted(command: string, workerType: WorkerType, args: Array<string> | undefined = undefined) {
|
||||||
command: string,
|
|
||||||
workerType: WorkerType,
|
|
||||||
args: Array<string> | undefined = undefined
|
|
||||||
) {
|
|
||||||
cluster.setupPrimary({
|
cluster.setupPrimary({
|
||||||
exec: "./src/core/helper/worker_computed",
|
exec: "/Users/idontsudo/Desktop/testdeck-mocha-seed/server/build/src/core/helpers/worker_computed.js",
|
||||||
});
|
});
|
||||||
|
|
||||||
const worker = cluster.fork();
|
const worker = cluster.fork();
|
||||||
|
@ -69,21 +65,13 @@ export class ExecutorProgramService
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.worker.kill();
|
this.worker.kill();
|
||||||
this.emit(
|
this.emit(
|
||||||
Result.error(
|
Result.error(WorkerType.EXEC ? new ExecError(command, "timeout err") : new SpawnError(command, "timeout err"))
|
||||||
WorkerType.EXEC
|
|
||||||
? new ExecError(command, "timeout err")
|
|
||||||
: new SpawnError(command, "timeout err")
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}, this.maxTime!);
|
}, this.maxTime!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async call(
|
public async call(type: EXEC_TYPE, command: string, args: Array<string> | undefined = undefined): Promise<void> {
|
||||||
type: EXEC_TYPE,
|
|
||||||
command: string,
|
|
||||||
args: Array<string> | undefined = undefined
|
|
||||||
): Promise<void> {
|
|
||||||
if (type == EXEC_TYPE.EXEC) {
|
if (type == EXEC_TYPE.EXEC) {
|
||||||
this.workerExecuted(command, WorkerType.EXEC);
|
this.workerExecuted(command, WorkerType.EXEC);
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,11 @@
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { resolve } from "node:path";
|
import { resolve } from "node:path";
|
||||||
import { promisify } from "node:util";
|
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
import "reflect-metadata";
|
|
||||||
import { BinaryLike } from "crypto";
|
import { BinaryLike } from "crypto";
|
||||||
import {
|
import { EventsFileChanger, MetaDataFileManagerModel } from "../models/meta_data_file_manager_model";
|
||||||
EventsFileChanger,
|
import { Result } from "../helpers/result";
|
||||||
MetaDataFileManagerModel,
|
import { TypedEvent } from "../helpers/typed_event";
|
||||||
} from "../model/meta_data_file_manager_model";
|
import { lsStat, readFileAsync, readdir, stat } from "../repository/fs";
|
||||||
import { Result } from "../helper/result";
|
|
||||||
import { TypedEvent } from "../helper/typed_event";
|
|
||||||
|
|
||||||
const readFileAsync = promisify(fs.readFile);
|
|
||||||
const readdir = promisify(fs.readdir);
|
|
||||||
const stat = promisify(fs.stat);
|
|
||||||
const lsStat = promisify(fs.lstat);
|
|
||||||
|
|
||||||
function joinBuffers(buffers: Array<Buffer>, delimiter = " ") {
|
function joinBuffers(buffers: Array<Buffer>, delimiter = " ") {
|
||||||
const d = Buffer.from(delimiter);
|
const d = Buffer.from(delimiter);
|
||||||
|
@ -27,13 +18,11 @@ async function readFileAtBuffer(path: string): Promise<Buffer> {
|
||||||
await readdir(path, {
|
await readdir(path, {
|
||||||
encoding: "buffer",
|
encoding: "buffer",
|
||||||
})
|
})
|
||||||
).reduce(
|
).reduce((accumulator, currentValue) => joinBuffers([accumulator, currentValue]), Buffer.from(""));
|
||||||
(accumulator, currentValue) => joinBuffers([accumulator, currentValue]),
|
|
||||||
Buffer.from("")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return await readFileAsync(path);
|
return await readFileAsync(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
function md5(content: Buffer | BinaryLike): Promise<string> {
|
function md5(content: Buffer | BinaryLike): Promise<string> {
|
||||||
return new Promise((resolve, _reject) => {
|
return new Promise((resolve, _reject) => {
|
||||||
return resolve(createHash("md5").update(content).digest("hex"));
|
return resolve(createHash("md5").update(content).digest("hex"));
|
||||||
|
@ -47,6 +36,7 @@ export interface IHashesCache {
|
||||||
export abstract class IFilesChangeNotifierService {
|
export abstract class IFilesChangeNotifierService {
|
||||||
abstract directory: string;
|
abstract directory: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FilesChangeNotifierService
|
export class FilesChangeNotifierService
|
||||||
extends TypedEvent<Result<Error, IHashesCache>>
|
extends TypedEvent<Result<Error, IHashesCache>>
|
||||||
implements IFilesChangeNotifierService
|
implements IFilesChangeNotifierService
|
||||||
|
@ -69,11 +59,7 @@ export class FilesChangeNotifierService
|
||||||
const data = await readFileAsync(file);
|
const data = await readFileAsync(file);
|
||||||
const md5Current = await md5(data);
|
const md5Current = await md5(data);
|
||||||
|
|
||||||
this.hashes[file] = new MetaDataFileManagerModel(
|
this.hashes[file] = new MetaDataFileManagerModel(file, md5Current, EventsFileChanger.static);
|
||||||
file,
|
|
||||||
md5Current,
|
|
||||||
EventsFileChanger.static
|
|
||||||
);
|
|
||||||
this.emit(Result.ok(this.hashes));
|
this.emit(Result.ok(this.hashes));
|
||||||
}
|
}
|
||||||
async getFiles(dir: string): Promise<string | any[]> {
|
async getFiles(dir: string): Promise<string | any[]> {
|
||||||
|
@ -91,46 +77,31 @@ export class FilesChangeNotifierService
|
||||||
try {
|
try {
|
||||||
let md5Previous: string | null = null;
|
let md5Previous: string | null = null;
|
||||||
let fsWait: NodeJS.Timeout | boolean = false;
|
let fsWait: NodeJS.Timeout | boolean = false;
|
||||||
const watcher = fs.watch(
|
const watcher = fs.watch(this.directory, { recursive: true }, async (_e, filename) => {
|
||||||
this.directory,
|
const filePath = this.directory + filename;
|
||||||
{ recursive: true },
|
if (filename) {
|
||||||
async (_e, filename) => {
|
if (fsWait) return;
|
||||||
const filePath = this.directory + filename;
|
fsWait = setTimeout(() => {
|
||||||
if (filename) {
|
fsWait = false;
|
||||||
if (fsWait) return;
|
}, 100);
|
||||||
fsWait = setTimeout(() => {
|
try {
|
||||||
fsWait = false;
|
const file = await readFileAtBuffer(filePath);
|
||||||
}, 100);
|
const md5Current = await md5(file);
|
||||||
try {
|
if (md5Current === md5Previous) {
|
||||||
const file = await readFileAtBuffer(filePath);
|
return;
|
||||||
const md5Current = await md5(file);
|
|
||||||
if (md5Current === md5Previous) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const status =
|
|
||||||
this.hashes[filePath] === undefined
|
|
||||||
? EventsFileChanger.create
|
|
||||||
: EventsFileChanger.update;
|
|
||||||
|
|
||||||
const model = new MetaDataFileManagerModel(
|
|
||||||
filePath,
|
|
||||||
md5Current,
|
|
||||||
status
|
|
||||||
);
|
|
||||||
this.hashes[filePath] = model;
|
|
||||||
md5Previous = md5Current;
|
|
||||||
this.emit(Result.ok(this.hashes));
|
|
||||||
} catch (error) {
|
|
||||||
this.emit(Result.ok(this.hashes));
|
|
||||||
this.hashes[filePath] = new MetaDataFileManagerModel(
|
|
||||||
filePath,
|
|
||||||
null,
|
|
||||||
EventsFileChanger.delete
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
const status = this.hashes[filePath] === undefined ? EventsFileChanger.create : EventsFileChanger.update;
|
||||||
|
|
||||||
|
const model = new MetaDataFileManagerModel(filePath, md5Current, status);
|
||||||
|
this.hashes[filePath] = model;
|
||||||
|
md5Previous = md5Current;
|
||||||
|
this.emit(Result.ok(this.hashes));
|
||||||
|
} catch (error) {
|
||||||
|
this.emit(Result.ok(this.hashes));
|
||||||
|
this.hashes[filePath] = new MetaDataFileManagerModel(filePath, null, EventsFileChanger.delete);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
this.watcher = watcher;
|
this.watcher = watcher;
|
||||||
return Result.ok(true);
|
return Result.ok(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
73
server/src/core/services/pipeline_real_time_service.ts
Normal file
73
server/src/core/services/pipeline_real_time_service.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import { TypedEvent } from "../helpers/typed_event";
|
||||||
|
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";
|
||||||
|
|
||||||
|
export class PipelineRealTimeService extends TypedEvent<ActivePipeline> {
|
||||||
|
status: ActivePipeline;
|
||||||
|
pipelineModels?: IPipeline[];
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
private init(): void {
|
||||||
|
this.status = ActivePipeline.empty();
|
||||||
|
}
|
||||||
|
pipelineSubscriber = (iterations: Iteration[]): void => {
|
||||||
|
if (this.status["lastProcessCompleteCount"] === 0) {
|
||||||
|
this.status["lastProcessCompleteCount"] = 0;
|
||||||
|
}
|
||||||
|
this.status.lastProcessCompleteCount += 1;
|
||||||
|
this.pipelineCompleted();
|
||||||
|
this.iterationHelper(iterations[iterations.length - 1]);
|
||||||
|
this.emit(this.status);
|
||||||
|
};
|
||||||
|
|
||||||
|
iterationHelper(iteration: Iteration): void {
|
||||||
|
this.iterationErrorObserver(iteration);
|
||||||
|
|
||||||
|
// TODO(IDONTSUDO): implements
|
||||||
|
this.iterationLogSaver(iteration);
|
||||||
|
}
|
||||||
|
|
||||||
|
iterationLogSaver(iteration: Iteration): void {
|
||||||
|
// throw new Error("Method not implemented.");
|
||||||
|
// citeration.result.data
|
||||||
|
}
|
||||||
|
|
||||||
|
iterationErrorObserver(iteration: Iteration): void {
|
||||||
|
if (this.status.lastProcessCompleteCount === 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = iteration?.result;
|
||||||
|
const executorResult = ExecutorResult.isExecutorResult(result);
|
||||||
|
const execError = ExecError.isExecError(result);
|
||||||
|
|
||||||
|
if (executorResult instanceof ExecutorResult) {
|
||||||
|
this.status.error = executorResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (execError instanceof ExecError) {
|
||||||
|
this.status.error = execError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pipelineCompleted() {
|
||||||
|
const pipelineIsCompleted = this.status.lastProcessCompleteCount === this.pipelineModels?.length;
|
||||||
|
if (pipelineIsCompleted) {
|
||||||
|
this.status.pipelineIsRunning = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setPipelineDependency(pipelineModels: IPipeline[], path: string, projectUUID: string) {
|
||||||
|
this.pipelineModels = pipelineModels;
|
||||||
|
this.status["projectUUID"] = projectUUID;
|
||||||
|
this.status["path"] = path;
|
||||||
|
}
|
||||||
|
runPipeline(): void {
|
||||||
|
// const stack = new StackService(this.pipelineModels, this.path);
|
||||||
|
// this.status["pipelineIsRunning"] = true;
|
||||||
|
// stack.on(this.pipelineSubscriber);
|
||||||
|
// stack.call();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,13 @@
|
||||||
import {
|
import { FilesChangeNotifierService, IHashesCache } from "./files_change_notifier_service";
|
||||||
FilesChangeNotifierService,
|
import { IPipeline } from "../models/process_model";
|
||||||
IHashesCache,
|
|
||||||
} from "./files_change_notifier_service";
|
|
||||||
import { IPipeline } from "../model/process_model";
|
|
||||||
import { ExecutorProgramService } from "./executor_program_service";
|
import { ExecutorProgramService } from "./executor_program_service";
|
||||||
import { EXEC_EVENT, ExecError, SpawnError } from "../model/exec_error_model";
|
import { EXEC_EVENT, ExecError, SpawnError } from "../models/exec_error_model";
|
||||||
import { TypedEvent } from "../helper/typed_event";
|
import { TypedEvent } from "../helpers/typed_event";
|
||||||
import { Result } from "../helper/result";
|
import { Result } from "../helpers/result";
|
||||||
import { ExecutorResult } from "../model/executor_result";
|
import { ExecutorResult } from "../models/executor_result";
|
||||||
import { delay } from "../helper/delay";
|
import { delay } from "../helpers/delay";
|
||||||
import { TriggerService } from "./trigger_service";
|
import { TriggerService } from "./trigger_service";
|
||||||
import { Trigger } from "../../features/triggers/trigger_model";
|
import { Trigger } from "../../features/triggers/models/trigger_database_model";
|
||||||
|
|
||||||
export interface Iteration {
|
export interface Iteration {
|
||||||
hashes: IHashesCache | null;
|
hashes: IHashesCache | null;
|
||||||
|
@ -24,7 +21,7 @@ export abstract class IStackService {
|
||||||
abstract init(processed: IPipeline[], path: string): void;
|
abstract init(processed: IPipeline[], path: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class StackService extends TypedEvent<string> implements IStackService {
|
export class StackService extends TypedEvent<Iteration[]> implements IStackService {
|
||||||
callStack: Iteration[];
|
callStack: Iteration[];
|
||||||
path: string;
|
path: string;
|
||||||
constructor(processed: IPipeline[], path: string) {
|
constructor(processed: IPipeline[], path: string) {
|
||||||
|
@ -44,10 +41,7 @@ export class StackService extends TypedEvent<string> implements IStackService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private commandHandler(processMetaData: IPipeline) {
|
private commandHandler(processMetaData: IPipeline) {
|
||||||
processMetaData.process.command = processMetaData.process.command.replace(
|
processMetaData.process.command = processMetaData.process.command.replace("$PATH", this.path);
|
||||||
"$PATH",
|
|
||||||
this.path
|
|
||||||
);
|
|
||||||
return processMetaData;
|
return processMetaData;
|
||||||
}
|
}
|
||||||
public async call() {
|
public async call() {
|
||||||
|
@ -56,36 +50,23 @@ export class StackService extends TypedEvent<string> implements IStackService {
|
||||||
for await (const el of this.callStack!) {
|
for await (const el of this.callStack!) {
|
||||||
await this.execStack(inc, el);
|
await this.execStack(inc, el);
|
||||||
inc += 1;
|
inc += 1;
|
||||||
|
this.emit(this.callStack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async execStack(
|
async execStack(stackNumber: number, stackLayer: Iteration): Promise<void | boolean> {
|
||||||
stackNumber: number,
|
|
||||||
stackLayer: Iteration
|
|
||||||
): Promise<void | boolean> {
|
|
||||||
const executorService = new ExecutorProgramService(this.path);
|
const executorService = new ExecutorProgramService(this.path);
|
||||||
|
executorService.call(stackLayer.process.process.type, stackLayer.process.process.command);
|
||||||
|
|
||||||
executorService.call(
|
const filesChangeNotifierService = new FilesChangeNotifierService(this.path);
|
||||||
stackLayer.process.process.type,
|
|
||||||
stackLayer.process.process.command
|
|
||||||
);
|
|
||||||
|
|
||||||
const filesChangeNotifierService = new FilesChangeNotifierService(
|
|
||||||
this.path
|
|
||||||
);
|
|
||||||
|
|
||||||
filesChangeNotifierService.call();
|
filesChangeNotifierService.call();
|
||||||
const result = await this.waitEvent<
|
const result = await this.waitEvent<Result<ExecError | SpawnError, ExecutorResult>>(executorService);
|
||||||
Result<ExecError | SpawnError, ExecutorResult>
|
|
||||||
>(executorService);
|
|
||||||
await delay(100);
|
await delay(100);
|
||||||
if (result.isSuccess()) {
|
if (result.isSuccess()) {
|
||||||
this.callStack[stackNumber].result = result.value;
|
this.callStack[stackNumber].result = result.value;
|
||||||
this.callStack[stackNumber].hashes = filesChangeNotifierService.hashes;
|
this.callStack[stackNumber].hashes = filesChangeNotifierService.hashes;
|
||||||
|
|
||||||
const triggerResult = await this.triggerExec(
|
const triggerResult = await this.triggerExec(stackLayer.process.trigger, stackNumber);
|
||||||
stackLayer.process.trigger,
|
|
||||||
stackNumber
|
|
||||||
);
|
|
||||||
triggerResult.fold(
|
triggerResult.fold(
|
||||||
(s) => {
|
(s) => {
|
||||||
s;
|
s;
|
||||||
|
@ -120,15 +101,15 @@ export class StackService extends TypedEvent<string> implements IStackService {
|
||||||
});
|
});
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
private async triggerExec(
|
private async triggerExec(trigger: Trigger | null, stackNumber: number): Promise<Result<boolean, boolean>> {
|
||||||
trigger: Trigger,
|
if (trigger !== null) {
|
||||||
stackNumber: number
|
const hashes = this.callStack[stackNumber].hashes;
|
||||||
): Promise<Result<boolean, boolean>> {
|
|
||||||
const hashes = this.callStack[stackNumber].hashes;
|
|
||||||
|
|
||||||
if (hashes != null) {
|
if (hashes != null) {
|
||||||
return await new TriggerService(trigger, hashes, this.path).call();
|
return await new TriggerService(trigger, hashes, this.path).call();
|
||||||
|
}
|
||||||
|
throw new Error("Hashes is null");
|
||||||
}
|
}
|
||||||
throw new Error("Hashes is null");
|
return Result.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import * as vm from "node:vm";
|
import * as vm from "node:vm";
|
||||||
import { IHashesCache } from "./files_change_notifier_service";
|
import { IHashesCache } from "./files_change_notifier_service";
|
||||||
import { EventsFileChanger } from "../model/meta_data_file_manager_model";
|
import { EventsFileChanger } from "../models/meta_data_file_manager_model";
|
||||||
import { Result } from "../helper/result";
|
import { Result } from "../helpers/result";
|
||||||
import { TypedEvent } from "../helper/typed_event";
|
import { TypedEvent } from "../helpers/typed_event";
|
||||||
import { Trigger, TriggerType } from "../../features/triggers/trigger_model";
|
import { Trigger, TriggerType } from "../../features/triggers/models/trigger_database_model";
|
||||||
|
|
||||||
export class TriggerCallResult {
|
export class TriggerCallResult {
|
||||||
results: Array<TriggerSuccessResult | TriggerErrorReport>;
|
results: Array<TriggerSuccessResult | TriggerErrorReport>;
|
||||||
|
@ -35,11 +35,7 @@ export class TriggerErrorReport extends Error {
|
||||||
hashes: IHashesCache;
|
hashes: IHashesCache;
|
||||||
trigger: string | Trigger;
|
trigger: string | Trigger;
|
||||||
processOutput: any;
|
processOutput: any;
|
||||||
constructor(
|
constructor(hashes: IHashesCache, trigger: string | Trigger, processOutput?: any) {
|
||||||
hashes: IHashesCache,
|
|
||||||
trigger: string | Trigger,
|
|
||||||
processOutput?: any
|
|
||||||
) {
|
|
||||||
super();
|
super();
|
||||||
this.hashes = hashes;
|
this.hashes = hashes;
|
||||||
this.trigger = trigger;
|
this.trigger = trigger;
|
||||||
|
@ -123,9 +119,7 @@ export class TriggerService extends TypedEvent<TriggerCallResult> {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
private reportTriggerTypeProcess(
|
private reportTriggerTypeProcess(triggerResult: Array<TriggerSuccessResult>): TriggerCallResult {
|
||||||
triggerResult: Array<TriggerSuccessResult>
|
|
||||||
): TriggerCallResult {
|
|
||||||
return new TriggerCallResult(
|
return new TriggerCallResult(
|
||||||
triggerResult.map((el) => {
|
triggerResult.map((el) => {
|
||||||
if (el.status) {
|
if (el.status) {
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { App } from "../controllers/app";
|
||||||
|
import { dirIsExists } from "../repository/fs";
|
||||||
|
import { CreateFolderUseCase } from "./crete_folder_usecase";
|
||||||
|
|
||||||
|
export class CheckAndCreateStaticFilesFolderUseCase {
|
||||||
|
call = async (): Promise<void> => {
|
||||||
|
if (await dirIsExists(App.staticFilesStoreDir())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const createFolderUseCase = await new CreateFolderUseCase().call(App.staticFilesStoreDir());
|
||||||
|
|
||||||
|
createFolderUseCase.fold(
|
||||||
|
(_s) => {},
|
||||||
|
(e) => {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { Result } from "../helper/result";
|
import { Result } from "../helpers/result";
|
||||||
import { ICreateObjectDataBase } from "../interfaces/response";
|
import { ICreateObjectDataBase } from "../interfaces/response";
|
||||||
|
|
||||||
export class CreateDataBaseModelUseCase<V> {
|
export class CreateDataBaseModelUseCase<V> {
|
||||||
|
@ -8,9 +8,7 @@ export class CreateDataBaseModelUseCase<V> {
|
||||||
this.databaseModel = model;
|
this.databaseModel = model;
|
||||||
}
|
}
|
||||||
|
|
||||||
call = async (
|
call = async (validationModel: V): Promise<Result<Error, ICreateObjectDataBase>> => {
|
||||||
validationModel: V
|
|
||||||
): Promise<Result<Error, ICreateObjectDataBase>> => {
|
|
||||||
try {
|
try {
|
||||||
const result = new this.databaseModel(validationModel);
|
const result = new this.databaseModel(validationModel);
|
||||||
|
|
||||||
|
|
13
server/src/core/usecases/create_file_usecase.ts
Normal file
13
server/src/core/usecases/create_file_usecase.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { Result } from "../helpers/result";
|
||||||
|
import { writeFileAsync } from "../repository/fs";
|
||||||
|
|
||||||
|
export class CreateFileUseCase {
|
||||||
|
async call(path: string, buffer: Buffer): Promise<Result<Error, void>> {
|
||||||
|
try {
|
||||||
|
await writeFileAsync(path, buffer);
|
||||||
|
return Result.ok();
|
||||||
|
} catch (err) {
|
||||||
|
return Result.error(err as Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
server/src/core/usecases/crete_folder_usecase.ts
Normal file
17
server/src/core/usecases/crete_folder_usecase.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
15
server/src/core/usecases/database_connect_usecase.ts
Normal file
15
server/src/core/usecases/database_connect_usecase.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import mongoose from "mongoose";
|
||||||
|
import { Result } from "../helpers/result";
|
||||||
|
|
||||||
|
export class DataBaseConnectUseCase {
|
||||||
|
call = async (dataBaseName: string = "test"): Promise<Result<Error, void>> => {
|
||||||
|
try {
|
||||||
|
await mongoose.connect(`mongodb://127.0.0.1:27017/${dataBaseName}`);
|
||||||
|
return Result.ok();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
console.log("database connect error");
|
||||||
|
return Result.error(error as Error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,16 +1,21 @@
|
||||||
import { Result } from "../helper/result";
|
import { Result } from "../helpers/result";
|
||||||
|
|
||||||
export class DeleteDataBaseModelUseCase<D> {
|
export class DeleteDataBaseModelUseCase<D> {
|
||||||
databaseModel: D | any;
|
databaseModel: D | any;
|
||||||
constructor(model) {
|
constructor(model) {
|
||||||
this.databaseModel = model;
|
this.databaseModel = model;
|
||||||
}
|
}
|
||||||
call = async (id: string): Promise<Result<Error, boolean>> => {
|
call = async (id: string): Promise<Result<Error, any>> => {
|
||||||
try {
|
try {
|
||||||
const model = new this.databaseModel({ _id: id });
|
const model = this.databaseModel as any;
|
||||||
await model.deleteOne();
|
|
||||||
|
|
||||||
return Result.ok(true);
|
const result = await model.deleteOne({ _id: id });
|
||||||
|
|
||||||
|
if (result.deletedCount === 0) {
|
||||||
|
return Result.error(Error(`the database does not have a collection with this ID:${id}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok({ ok: "model delete" });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return Result.error(error);
|
return Result.error(error);
|
||||||
}
|
}
|
||||||
|
|
7
server/src/core/usecases/drop_database_usecase.ts
Normal file
7
server/src/core/usecases/drop_database_usecase.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import mongoose from "mongoose";
|
||||||
|
|
||||||
|
export class DropDataBaseUseCase {
|
||||||
|
call = async () => {
|
||||||
|
await mongoose.connection.dropDatabase();
|
||||||
|
};
|
||||||
|
}
|
5
server/src/core/usecases/exit_app_usecase.ts
Normal file
5
server/src/core/usecases/exit_app_usecase.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export class ExitAppUseCase {
|
||||||
|
call = () => {
|
||||||
|
process.exit();
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { Result } from "../helper/result";
|
import { Result } from "../helpers/result";
|
||||||
|
|
||||||
export class PaginationDataBaseModelUseCase<D> {
|
export class PaginationDataBaseModelUseCase<D> {
|
||||||
databaseModel: D;
|
databaseModel: D;
|
||||||
|
@ -9,9 +9,7 @@ export class PaginationDataBaseModelUseCase<D> {
|
||||||
this.perPage = perPage;
|
this.perPage = perPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
call = async (
|
call = async (pageNumber: number): Promise<Result<Error, [D]>> => {
|
||||||
pageNumber: number
|
|
||||||
): Promise<Result<Error, [D]>> => {
|
|
||||||
try {
|
try {
|
||||||
const page = Math.max(0, pageNumber);
|
const page = Math.max(0, pageNumber);
|
||||||
const model = this.databaseModel as any;
|
const model = this.databaseModel as any;
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Result } from "../helpers/result";
|
||||||
|
|
||||||
|
export class ReadByIdDataBaseModelUseCase<D> {
|
||||||
|
databaseModel: D;
|
||||||
|
|
||||||
|
constructor(model) {
|
||||||
|
this.databaseModel = model;
|
||||||
|
}
|
||||||
|
call = async (id: string): Promise<Result<Error, D>> => {
|
||||||
|
try {
|
||||||
|
const dbModel = this.databaseModel as any;
|
||||||
|
return Result.ok(await dbModel.findById(id));
|
||||||
|
} catch (error) {
|
||||||
|
return Result.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
|
import { Result } from "../helpers/result";
|
||||||
import { Result } from "../helper/result";
|
|
||||||
|
|
||||||
export class ReadByIdDataBaseModelUseCase<D> {
|
export class ReadByIdDataBaseModelUseCase<D> {
|
||||||
databaseModel: D;
|
databaseModel: D;
|
||||||
|
|
18
server/src/core/usecases/search_database_model_usecase.ts
Normal file
18
server/src/core/usecases/search_database_model_usecase.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { Result } from "../helpers/result";
|
||||||
|
|
||||||
|
export class SearchDataBaseModelUseCase<T> {
|
||||||
|
model: any;
|
||||||
|
|
||||||
|
constructor(model: any) {
|
||||||
|
this.model = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
call = async (findFilter: Partial<T>): Promise<Result<null, T>> => {
|
||||||
|
const result = await this.model.findOne(findFilter);
|
||||||
|
if (result === null) {
|
||||||
|
return Result.error(null);
|
||||||
|
} else {
|
||||||
|
return Result.ok(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { Result } from "../helper/result";
|
import { Result } from "../helpers/result";
|
||||||
|
|
||||||
interface uuid {
|
interface uuid {
|
||||||
_id?: string;
|
_id?: string;
|
||||||
|
@ -17,7 +17,14 @@ export class UpdateDataBaseModelUseCase<D, T extends uuid> {
|
||||||
}
|
}
|
||||||
const databaseModel = this.databaseModel as any;
|
const databaseModel = this.databaseModel as any;
|
||||||
const model = await databaseModel.findById(updateModel._id);
|
const model = await databaseModel.findById(updateModel._id);
|
||||||
|
if (model === null) {
|
||||||
|
throw new Error(
|
||||||
|
`we can’t find the model in the database with ID:${updateModel._id} collection: ${databaseModel.modelName}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Object.assign(model, updateModel);
|
Object.assign(model, updateModel);
|
||||||
|
|
||||||
await model.save();
|
await model.save();
|
||||||
return Result.ok(model as T);
|
return Result.ok(model as T);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
58
server/src/features/nix_store_manager/nix_store_manager.ts
Normal file
58
server/src/features/nix_store_manager/nix_store_manager.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import { CallbackStrategyWithEmpty, CoreHttpController } from "../../core/controllers/http_controller";
|
||||||
|
import { Result } from "../../core/helpers/result";
|
||||||
|
import { EXEC_TYPE } from "../../core/models/exec_error_model";
|
||||||
|
import { ExecutorResult } from "../../core/models/executor_result";
|
||||||
|
import { IPipeline, IssueType, StackGenerateType } from "../../core/models/process_model";
|
||||||
|
import { StackService } from "../../core/services/stack_service";
|
||||||
|
import { TriggerType } from "../triggers/models/trigger_database_model";
|
||||||
|
|
||||||
|
class NixStoreModel {}
|
||||||
|
|
||||||
|
const getNixStoreFolderCommand: IPipeline[] = [
|
||||||
|
{
|
||||||
|
process: {
|
||||||
|
type: EXEC_TYPE.EXEC,
|
||||||
|
command: `ls /nix/store`,
|
||||||
|
isGenerating: true,
|
||||||
|
isLocaleCode: false,
|
||||||
|
issueType: IssueType.WARNING,
|
||||||
|
},
|
||||||
|
trigger: {
|
||||||
|
type: TriggerType.FILE,
|
||||||
|
value: ["context"],
|
||||||
|
},
|
||||||
|
env: null,
|
||||||
|
stackGenerateType: StackGenerateType.SINGLETON,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
class GetNixStorePackagesUseCase extends CallbackStrategyWithEmpty {
|
||||||
|
call = async () => {
|
||||||
|
const stackService = new StackService(
|
||||||
|
getNixStoreFolderCommand,
|
||||||
|
"/Users/idontsudo/Desktop/testdeck-mocha-seed/server/build/test/"
|
||||||
|
);
|
||||||
|
stackService.call();
|
||||||
|
|
||||||
|
const promise = new Promise((resolve, _reject) => {
|
||||||
|
stackService.on((e) => {
|
||||||
|
const iteration = e.firstElement();
|
||||||
|
if (iteration.result instanceof ExecutorResult) {
|
||||||
|
const nixPackage = iteration.result.data;
|
||||||
|
resolve(nixPackage.split("\n").filter((e) => e.hasNoPattern(".drv")));
|
||||||
|
} else {
|
||||||
|
return "GetNixStorePackagesUseCase unknown Error";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return Result.ok(await promise);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NixStoreManagerPresentation extends CoreHttpController<NixStoreModel> {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
url: "nix_store_api",
|
||||||
|
});
|
||||||
|
super.get(new GetNixStorePackagesUseCase().call);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { Schema, model } from "mongoose";
|
||||||
|
import { IPipeline } from "../../../core/models/process_model";
|
||||||
|
import { schemaProcess } from "../../process/models/process_database_model";
|
||||||
|
import { triggerSchema } from "../../triggers/models/trigger_database_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,
|
||||||
|
},
|
||||||
|
stackGenerateType: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
}).plugin(require("mongoose-autopopulate"));
|
||||||
|
|
||||||
|
export const schemaPipeline = "Pipeline";
|
||||||
|
|
||||||
|
export const PipelineDBModel = model<IPipeline>(schemaPipeline, PipelineSchema);
|
21
server/src/features/pipelines/models/pipeline_model.ts
Normal file
21
server/src/features/pipelines/models/pipeline_model.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { IsOptional, ValidateNested } from "class-validator";
|
||||||
|
import { IPipeline, IProcess, StackGenerateType } from "../../../core/models/process_model";
|
||||||
|
import { Type } from "class-transformer";
|
||||||
|
import { ProcessModel } from "../../process/models/process_validation_model";
|
||||||
|
import { TriggerModelValidationModel } from "../../triggers/models/trigger_validation_model";
|
||||||
|
|
||||||
|
export class PipelineModel implements IPipeline {
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => ProcessModel)
|
||||||
|
public process: IProcess;
|
||||||
|
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => TriggerModelValidationModel)
|
||||||
|
public trigger: TriggerModelValidationModel;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
public env = null;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
public stackGenerateType: StackGenerateType;
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { IsMongoId, IsOptional } from "class-validator";
|
||||||
|
import { IProcess, StackGenerateType } from "../../../core/models/process_model";
|
||||||
|
import { TriggerModelValidationModel } from "../../triggers/models/trigger_validation_model";
|
||||||
|
|
||||||
|
export class PipelineValidationModel {
|
||||||
|
@IsMongoId()
|
||||||
|
public process: IProcess;
|
||||||
|
|
||||||
|
@IsMongoId()
|
||||||
|
public trigger: TriggerModelValidationModel;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
public env = null;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
public stackGenerateType: StackGenerateType;
|
||||||
|
}
|
|
@ -1,11 +1,8 @@
|
||||||
import { IsMongoId, IsEnum } from "class-validator";
|
import { IsMongoId, IsEnum } from "class-validator";
|
||||||
import { Schema, model } from "mongoose";
|
import { Schema, model } from "mongoose";
|
||||||
import { StackGenerateType } from "../../core/model/process_model";
|
import { TriggerModel, triggerSchema } from "../triggers/trigger_model";
|
||||||
import {
|
|
||||||
TriggerModel,
|
|
||||||
triggerSchema,
|
|
||||||
} from "../triggers/trigger_model";
|
|
||||||
import { schemaProcess } from "../process/process_model";
|
import { schemaProcess } from "../process/process_model";
|
||||||
|
import { StackGenerateType } from "../../core/models/process_model";
|
||||||
|
|
||||||
export const PipelineSchema = new Schema({
|
export const PipelineSchema = new Schema({
|
||||||
process: {
|
process: {
|
||||||
|
@ -27,10 +24,7 @@ export const PipelineSchema = new Schema({
|
||||||
|
|
||||||
export const schemaPipeline = "Pipeline";
|
export const schemaPipeline = "Pipeline";
|
||||||
|
|
||||||
export const PipelineDBModel = model<PipelineModel>(
|
export const PipelineDBModel = model<PipelineModel>(schemaPipeline, PipelineSchema);
|
||||||
schemaPipeline,
|
|
||||||
PipelineSchema
|
|
||||||
);
|
|
||||||
|
|
||||||
export class PipelineModel {
|
export class PipelineModel {
|
||||||
@IsMongoId()
|
@IsMongoId()
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
import { CrudController } from "../../core/controllers/crud_controller";
|
import { CrudController } from "../../core/controllers/crud_controller";
|
||||||
import { PipelineDBModel, PipelineModel } from "./pipeline_model";
|
import { PipelineDBModel } from "./models/pipeline_database_model";
|
||||||
|
import { PipelineValidationModel } from "./models/pipeline_validation_model";
|
||||||
|
|
||||||
export class PipelinePresentation extends CrudController<
|
export class PipelinePresentation extends CrudController<PipelineValidationModel, typeof PipelineDBModel> {
|
||||||
PipelineModel,
|
|
||||||
typeof PipelineDBModel
|
|
||||||
> {
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
url: "pipeline",
|
url: "pipeline",
|
||||||
validationModel: PipelineModel,
|
validationModel: PipelineValidationModel,
|
||||||
databaseModel: PipelineDBModel,
|
databaseModel: PipelineDBModel,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
35
server/src/features/process/models/process_database_model.ts
Normal file
35
server/src/features/process/models/process_database_model.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { Schema, model } from "mongoose";
|
||||||
|
import { IProcess } from "../../../core/models/process_model";
|
||||||
|
|
||||||
|
export const ProcessSchema = new Schema({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
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);
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { IsBoolean, IsEnum, IsNumber, IsOptional, IsString } from "class-validator";
|
||||||
|
import { EXEC_TYPE } from "../../../core/models/exec_error_model";
|
||||||
|
import { IProcess, IssueType } from "../../../core/models/process_model";
|
||||||
|
|
||||||
|
export class ProcessModel implements IProcess {
|
||||||
|
@IsEnum(EXEC_TYPE)
|
||||||
|
public type: EXEC_TYPE;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
public description: string;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
|
@ -1,16 +1,7 @@
|
||||||
import {
|
import { IsString, IsOptional, IsEnum, IsNumber, IsBoolean } from "class-validator";
|
||||||
IsString,
|
|
||||||
IsOptional,
|
|
||||||
IsEnum,
|
|
||||||
IsNumber,
|
|
||||||
IsBoolean,
|
|
||||||
} from "class-validator";
|
|
||||||
import { Schema, model } from "mongoose";
|
import { Schema, model } from "mongoose";
|
||||||
import {
|
import { IProcess, IssueType } from "../../core/models/process_model";
|
||||||
IProcess,
|
import { EXEC_TYPE } from "../../core/models/exec_error_model";
|
||||||
IssueType,
|
|
||||||
} from "../../core/model/process_model";
|
|
||||||
import { EXEC_TYPE } from "../../core/model/exec_error_model";
|
|
||||||
|
|
||||||
export const ProcessSchema = new Schema({
|
export const ProcessSchema = new Schema({
|
||||||
type: {
|
type: {
|
||||||
|
@ -61,9 +52,8 @@ export class ProcessModel implements IProcess {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
public timeout?: number;
|
public timeout?: number;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
public commit?: string;
|
public commit?: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import { CrudController } from "../../core/controllers/crud_controller";
|
import { CrudController } from "../../core/controllers/crud_controller";
|
||||||
import { ProcessDBModel, ProcessModel } from "./process_model";
|
import { ProcessDBModel } from "./models/process_database_model";
|
||||||
|
import { ProcessModel } from "./models/process_validation_model";
|
||||||
|
|
||||||
export class ProcessPresentation extends CrudController<
|
export class ProcessPresentation extends CrudController<ProcessModel, typeof ProcessDBModel> {
|
||||||
ProcessModel,
|
|
||||||
typeof ProcessDBModel
|
|
||||||
> {
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
url: "process",
|
url: "process",
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
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 { ProjectInstanceDbModel } from "../models/project_instance_database_model";
|
||||||
|
import { ProjectInstanceValidationModel } from "../models/project_instance_validation_model";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
|
export class CreateNewProjectInstanceScenario {
|
||||||
|
call = async (model: ProjectInstanceValidationModel): 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" });
|
||||||
|
} catch (error) {
|
||||||
|
return Result.error(error as Error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { CallbackStrategyWithFileUpload, ResponseBase } from "../../../core/controllers/http_controller";
|
||||||
|
import { Result } from "../../../core/helpers/result";
|
||||||
|
import { IFile } from "../../../core/interfaces/file";
|
||||||
|
import { CreateFileUseCase } from "../../../core/usecases/create_file_usecase";
|
||||||
|
import { PipelineStatusUseCase } from "../../realtime/domain/pipeline_status_usecase";
|
||||||
|
|
||||||
|
export class UploadCadFileToProjectScenario extends CallbackStrategyWithFileUpload {
|
||||||
|
checkingFileExpression: RegExp = RegExp(".FCStd");
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { Schema, model } from "mongoose";
|
||||||
|
import { IProjectModel, projectSchema } from "../../projects/models/project_database_model";
|
||||||
|
|
||||||
|
export interface IProjectInstanceModel {
|
||||||
|
_id: string;
|
||||||
|
project: IProjectModel;
|
||||||
|
description: string;
|
||||||
|
rootDir: string;
|
||||||
|
isActive: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectInstanceSchema = new Schema({
|
||||||
|
project: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: projectSchema,
|
||||||
|
autopopulate: true,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
rootDir: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
}).plugin(require("mongoose-autopopulate"));
|
||||||
|
|
||||||
|
export const schemaProjectInstance = "instance_project";
|
||||||
|
|
||||||
|
export const ProjectInstanceDbModel = model<IProjectInstanceModel>(schemaProjectInstance, ProjectInstanceSchema);
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { IsMongoId, IsOptional, IsString } from "class-validator";
|
||||||
|
|
||||||
|
export class ProjectInstanceValidationModel {
|
||||||
|
@IsMongoId()
|
||||||
|
public project: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
public description: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
public rootDir: string;
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { CrudController } from "../../core/controllers/crud_controller";
|
||||||
|
import { CreateNewProjectInstanceScenario } from "./domain/create_new_project_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";
|
||||||
|
|
||||||
|
export class ProjectInstancePresentation extends CrudController<
|
||||||
|
ProjectInstanceValidationModel,
|
||||||
|
typeof ProjectInstanceDbModel
|
||||||
|
> {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
validationModel: ProjectInstanceValidationModel,
|
||||||
|
url: "project_instance",
|
||||||
|
databaseModel: ProjectInstanceDbModel,
|
||||||
|
});
|
||||||
|
super.post(new CreateNewProjectInstanceScenario().call);
|
||||||
|
|
||||||
|
super.subRoutes = [
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
subUrl: "upload",
|
||||||
|
fn: new UploadCadFileToProjectScenario(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { Schema, model } from "mongoose";
|
||||||
|
import { schemaPipeline } from "../../pipelines/models/pipeline_database_model";
|
||||||
|
import { PipelineValidationModel } from "../../pipelines/models/pipeline_validation_model";
|
||||||
|
|
||||||
|
export interface IProjectModel {
|
||||||
|
_id?: string;
|
||||||
|
pipelines: [PipelineValidationModel];
|
||||||
|
rootDir: string;
|
||||||
|
description: string;
|
||||||
|
isActive: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectSchema = new Schema({
|
||||||
|
pipelines: {
|
||||||
|
type: Array<Schema.Types.ObjectId>,
|
||||||
|
ref: schemaPipeline,
|
||||||
|
autopopulate: true,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
}).plugin(require("mongoose-autopopulate"));
|
||||||
|
|
||||||
|
export const projectSchema = "Projects";
|
||||||
|
|
||||||
|
export const ProjectDBModel = model<IProjectModel>(projectSchema, ProjectSchema);
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { IsArray, IsString } from "class-validator";
|
||||||
|
|
||||||
|
export class ProjectValidationModel {
|
||||||
|
@IsArray()
|
||||||
|
public pipelines: [string];
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
public description: string;
|
||||||
|
}
|
|
@ -1,15 +1,12 @@
|
||||||
// import { TriggerDBModel, TriggerModel } from "./trigger_model";
|
|
||||||
import { CrudController } from "../../core/controllers/crud_controller";
|
import { CrudController } from "../../core/controllers/crud_controller";
|
||||||
import { ProjectDBModel, ProjectModel } from "./projects_model";
|
import { ProjectDBModel } from "./models/project_database_model";
|
||||||
|
import { ProjectValidationModel } from "./models/project_validation_model";
|
||||||
|
|
||||||
export class ProjectsPresentation extends CrudController<
|
export class ProjectsPresentation extends CrudController<ProjectValidationModel, typeof ProjectDBModel> {
|
||||||
ProjectModel,
|
|
||||||
typeof ProjectDBModel
|
|
||||||
> {
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
url: "project",
|
url: "project",
|
||||||
validationModel: ProjectModel,
|
validationModel: ProjectValidationModel,
|
||||||
databaseModel: ProjectDBModel,
|
databaseModel: ProjectDBModel,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { Result } from "../../../core/helpers/result";
|
||||||
|
import { ActivePipeline } from "../../../core/models/active_pipeline_model";
|
||||||
|
import { pipelineRealTimeService } from "../realtime_presentation";
|
||||||
|
|
||||||
|
export class PipelineStatusUseCase {
|
||||||
|
async call(): Promise<Result<Error, ActivePipeline>> {
|
||||||
|
try {
|
||||||
|
const status = pipelineRealTimeService.status;
|
||||||
|
if (status.projectUUID !== null) {
|
||||||
|
return Result.ok(status);
|
||||||
|
}
|
||||||
|
if (status.projectUUID === null) {
|
||||||
|
return Result.error(new Error("pipelineRealTimeService does not have an active project instance"));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return Result.error(error as Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { App } from "../../../core/controllers/app";
|
||||||
|
import { Result } from "../../../core/helpers/result";
|
||||||
|
import { ReadByIdDataBaseModelUseCase } from "../../../core/usecases/read_by_id_database_model_usecase";
|
||||||
|
import { UpdateDataBaseModelUseCase } from "../../../core/usecases/update_database_model_usecase";
|
||||||
|
import {
|
||||||
|
IProjectInstanceModel,
|
||||||
|
ProjectInstanceDbModel,
|
||||||
|
} from "../../project_instance/models/project_instance_database_model";
|
||||||
|
import { RealTimeValidationModel, pipelineRealTimeService } from "../realtime_presentation";
|
||||||
|
|
||||||
|
export class RunInstancePipelineUseCase {
|
||||||
|
async call(model: RealTimeValidationModel): Promise<Result<Error, any>> {
|
||||||
|
const { id } = model;
|
||||||
|
const readByIdDataBaseModelUseCase = await new ReadByIdDataBaseModelUseCase<IProjectInstanceModel>(
|
||||||
|
ProjectInstanceDbModel
|
||||||
|
).call(id);
|
||||||
|
|
||||||
|
if (readByIdDataBaseModelUseCase.isFailure()) {
|
||||||
|
return readByIdDataBaseModelUseCase.forward();
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectModel = readByIdDataBaseModelUseCase.value;
|
||||||
|
projectModel.isActive = true;
|
||||||
|
|
||||||
|
const updateDataBaseModelUseCase = await new UpdateDataBaseModelUseCase<IProjectInstanceModel, any>(
|
||||||
|
ProjectInstanceDbModel
|
||||||
|
).call(projectModel);
|
||||||
|
|
||||||
|
if (updateDataBaseModelUseCase.isFailure()) {
|
||||||
|
return updateDataBaseModelUseCase.forward();
|
||||||
|
}
|
||||||
|
pipelineRealTimeService.setPipelineDependency(
|
||||||
|
projectModel.project.pipelines,
|
||||||
|
App.staticFilesStoreDir() + projectModel.rootDir + "/",
|
||||||
|
projectModel._id
|
||||||
|
);
|
||||||
|
pipelineRealTimeService.runPipeline();
|
||||||
|
|
||||||
|
return Result.ok({ status: "ok" });
|
||||||
|
}
|
||||||
|
}
|
24
server/src/features/realtime/realtime_presentation.ts
Normal file
24
server/src/features/realtime/realtime_presentation.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { IsString } from "class-validator";
|
||||||
|
import { CoreHttpController } from "../../core/controllers/http_controller";
|
||||||
|
import { PipelineRealTimeService } from "../../core/services/pipeline_real_time_service";
|
||||||
|
import { RunInstancePipelineUseCase } from "./domain/run_instance_pipeline_usecase";
|
||||||
|
import { PipelineStatusUseCase } from "./domain/pipeline_status_usecase";
|
||||||
|
|
||||||
|
export const pipelineRealTimeService = new PipelineRealTimeService();
|
||||||
|
|
||||||
|
export class RealTimeValidationModel {
|
||||||
|
@IsString()
|
||||||
|
public id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RealTimePresentation extends CoreHttpController<RealTimeValidationModel> {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
validationModel: RealTimeValidationModel,
|
||||||
|
url: "realtime",
|
||||||
|
databaseModel: null,
|
||||||
|
});
|
||||||
|
super.post(new RunInstancePipelineUseCase().call);
|
||||||
|
super.get(new PipelineStatusUseCase().call);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { Schema, model } from "mongoose";
|
||||||
|
|
||||||
|
export interface ITriggerModel {
|
||||||
|
_id?: string;
|
||||||
|
type: string;
|
||||||
|
description: string;
|
||||||
|
value: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TriggerSchema = new Schema({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
require: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: Array,
|
||||||
|
require: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const triggerSchema = "Trigger";
|
||||||
|
|
||||||
|
export const TriggerDBModel = model<ITriggerModel>(triggerSchema, TriggerSchema);
|
||||||
|
|
||||||
|
export enum TriggerType {
|
||||||
|
PROCESS = "PROCESS",
|
||||||
|
FILE = "FILE",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Trigger {
|
||||||
|
type: TriggerType;
|
||||||
|
value: string[];
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { IsArray, IsOptional, IsEnum, IsString } from "class-validator";
|
||||||
|
import { ITriggerModel, TriggerType } from "./trigger_database_model";
|
||||||
|
|
||||||
|
export class TriggerModelValidationModel implements ITriggerModel {
|
||||||
|
@IsOptional()
|
||||||
|
public _id: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
public description;
|
||||||
|
|
||||||
|
@IsEnum(TriggerType)
|
||||||
|
public type: TriggerType;
|
||||||
|
|
||||||
|
@IsArray()
|
||||||
|
public value: string[];
|
||||||
|
}
|
|
@ -1,14 +1,12 @@
|
||||||
import { TriggerDBModel, TriggerModel } from "./trigger_model";
|
|
||||||
import { CrudController } from "../../core/controllers/crud_controller";
|
import { CrudController } from "../../core/controllers/crud_controller";
|
||||||
|
import { TriggerDBModel } from "./models/trigger_database_model";
|
||||||
|
import { TriggerModelValidationModel as TriggerValidationMode } from "./models/trigger_validation_model";
|
||||||
|
|
||||||
export class TriggerPresentation extends CrudController<
|
export class TriggerPresentation extends CrudController<TriggerValidationMode, typeof TriggerDBModel> {
|
||||||
TriggerModel,
|
|
||||||
typeof TriggerDBModel
|
|
||||||
> {
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
url: "trigger",
|
url: "trigger",
|
||||||
validationModel: TriggerModel,
|
validationModel: TriggerValidationMode,
|
||||||
databaseModel: TriggerDBModel,
|
databaseModel: TriggerDBModel,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,12 @@
|
||||||
import "reflect-metadata";
|
import "reflect-metadata";
|
||||||
import { App } from "./core/controllers/app";
|
import { App } from "./core/controllers/app";
|
||||||
import { Routes } from "./core/interfaces/router";
|
import { SocketSubscriber } from "./core/controllers/socket_controller";
|
||||||
import { TriggerPresentation } from "./features/triggers/triggers_presentation";
|
import { extensions } from "./core/extensions/extensions";
|
||||||
import { ProjectsPresentation } from "./features/projects/projects_presentation";
|
import { httpRoutes } from "./core/controllers/routes";
|
||||||
import { PipelinePresentation } from "./features/pipelines/pipeline_presentation";
|
import { pipelineRealTimeService } from "./features/realtime/realtime_presentation";
|
||||||
import { ProcessPresentation } from "./features/process/process_presentation";
|
|
||||||
|
|
||||||
|
extensions();
|
||||||
|
|
||||||
const httpRoutes: Routes[] = [
|
const socketSubscribers = [new SocketSubscriber(pipelineRealTimeService, "realtime")];
|
||||||
new TriggerPresentation(),
|
|
||||||
new ProjectsPresentation(),
|
|
||||||
new ProcessPresentation(),
|
|
||||||
new PipelinePresentation(),
|
|
||||||
].map((el) => el.call());
|
|
||||||
|
|
||||||
const computedFolder = "";
|
new App(httpRoutes, socketSubscribers).listen();
|
||||||
|
|
||||||
new App(httpRoutes, computedFolder).listen();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
49
server/test/controllers/crud_controller_test.ts
Normal file
49
server/test/controllers/crud_controller_test.ts
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import { CrudController } from "../../src/core/controllers/crud_controller";
|
||||||
|
import { ClassValidatorMocker } from "../../src/core/helpers/class_validator_mocker";
|
||||||
|
import { HttpRepository } from "../../src/core/repository/http_repository";
|
||||||
|
|
||||||
|
function instanceOfObjectAndHaveId(s: any): string {
|
||||||
|
if (s instanceof Object && "id" in s) {
|
||||||
|
return s.id;
|
||||||
|
}
|
||||||
|
if (s instanceof Object && "_id" in s) {
|
||||||
|
return s._id;
|
||||||
|
}
|
||||||
|
throw Error(`${s} is not instance object or not have property _id`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CrudControllerTest {
|
||||||
|
controllerTest: CrudController<any, any>;
|
||||||
|
httpRepository: HttpRepository;
|
||||||
|
|
||||||
|
constructor(port: number, controller: CrudController<any, any>) {
|
||||||
|
this.controllerTest = controller;
|
||||||
|
this.httpRepository = new HttpRepository(`http://localhost:${port}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async call() {
|
||||||
|
let result = false;
|
||||||
|
const mockModel = ClassValidatorMocker.create<any>(this.controllerTest.validationModel);
|
||||||
|
const postRequestBody = await this.httpRepository.jsonRequest(this.controllerTest.mainURL, "POST", mockModel);
|
||||||
|
|
||||||
|
await postRequestBody.map(async (s) => {
|
||||||
|
const id = instanceOfObjectAndHaveId(s);
|
||||||
|
const getRequestBody = await this.httpRepository.jsonRequest(this.controllerTest.mainURL, "GET");
|
||||||
|
await getRequestBody.map(async (el) => {
|
||||||
|
if (el instanceof Array) {
|
||||||
|
const firstElement = el.firstElement();
|
||||||
|
const mockModelUpdate = ClassValidatorMocker.create<any>(this.controllerTest.validationModel);
|
||||||
|
Object.assign(firstElement, mockModelUpdate);
|
||||||
|
delete firstElement.__v;
|
||||||
|
const putReqBody = await this.httpRepository.jsonRequest(this.controllerTest.mainURL, "PUT", firstElement);
|
||||||
|
await putReqBody.map(async () => {
|
||||||
|
(await this.httpRepository.jsonRequest(this.controllerTest.mainURL + "?id=" + id, "DELETE")).map(() => {
|
||||||
|
result = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,14 @@
|
||||||
import mongoose from "mongoose";
|
import mongoose from "mongoose";
|
||||||
import { delay } from "../../src/core/helper/delay";
|
import { delay } from "../../src/core/helpers/delay";
|
||||||
import { Result } from "../../src/core/helper/result";
|
import { Result } from "../../src/core/helpers/result";
|
||||||
import { TypedEvent } from "../../src/core/helper/typed_event";
|
import { TypedEvent } from "../../src/core/helpers/typed_event";
|
||||||
|
import { DropDataBaseUseCase } from "../../src/core/usecases/drop_database_usecase";
|
||||||
|
import { ExitAppUseCase } from "../../src/core/usecases/exit_app_usecase";
|
||||||
|
|
||||||
|
export const before = async () => {
|
||||||
export const before = async () =>{
|
new DropDataBaseUseCase().call();
|
||||||
await mongoose.connection.dropDatabase()
|
new ExitAppUseCase().call();
|
||||||
}
|
};
|
||||||
|
|
||||||
export class TestCore {
|
export class TestCore {
|
||||||
allTests = 0;
|
allTests = 0;
|
||||||
|
@ -33,17 +35,14 @@ export class TestCore {
|
||||||
console.log("\x1b[32m", "=============");
|
console.log("\x1b[32m", "=============");
|
||||||
|
|
||||||
if (this.allTests - this.testOk === 0) {
|
if (this.allTests - this.testOk === 0) {
|
||||||
console.log(
|
console.log("\x1b[32m", `✅ All test success! ${this.allTests}/${this.testOk}`);
|
||||||
"\x1b[32m",
|
|
||||||
`✅ All test success! ${this.allTests}/${this.testOk}`
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.testErr !== 0) {
|
if (this.testErr !== 0) {
|
||||||
console.log("\x1b[31m", "❌ test error:" + String(this.testErr));
|
console.log("\x1b[31m", "❌ test error:" + String(this.testErr));
|
||||||
console.log("\x1b[32m", `✅ test success! ${this.testOk}`);
|
console.log("\x1b[32m", `✅ test success! ${this.testOk}`);
|
||||||
}
|
}
|
||||||
await before()
|
await before();
|
||||||
};
|
};
|
||||||
resultTest = async (
|
resultTest = async (
|
||||||
eventClass: TypedEvent<Result<any, any>> | any,
|
eventClass: TypedEvent<Result<any, any>> | any,
|
||||||
|
@ -54,24 +53,20 @@ export class TestCore {
|
||||||
) => {
|
) => {
|
||||||
let testIsOk = false;
|
let testIsOk = false;
|
||||||
eventClass.call(...args);
|
eventClass.call(...args);
|
||||||
const listener = eventClass.on(
|
const listener = eventClass.on((e: { fold: (arg0: (_s: any) => void, arg1: (_e: any) => void) => void }) => {
|
||||||
(e: {
|
e.fold(
|
||||||
fold: (arg0: (_s: any) => void, arg1: (_e: any) => void) => void;
|
() => {
|
||||||
}) => {
|
if (isOk) {
|
||||||
e.fold(
|
testIsOk = true;
|
||||||
() => {
|
|
||||||
if (isOk) {
|
|
||||||
testIsOk = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
if (!isOk) {
|
|
||||||
testIsOk = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
},
|
||||||
}
|
() => {
|
||||||
);
|
if (!isOk) {
|
||||||
|
testIsOk = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
await delay(delayTime);
|
await delay(delayTime);
|
||||||
this.assert(testIsOk, testName);
|
this.assert(testIsOk, testName);
|
||||||
listener.dispose();
|
listener.dispose();
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log("log");
|
||||||
setTimeout(() =>{
|
console.log("log");
|
||||||
console.log('log')
|
console.log("log");
|
||||||
console.log('log')
|
console.log("log");
|
||||||
console.log('log')
|
console.log("log");
|
||||||
console.log('log')
|
console.log("log");
|
||||||
console.log('log')
|
console.log("log");
|
||||||
console.log('log')
|
console.log("log");
|
||||||
console.log('log')
|
console.log("log");
|
||||||
console.log('log')
|
console.log("log");
|
||||||
console.log('log')
|
}, 2000);
|
||||||
console.log('log')
|
setTimeout(() => {
|
||||||
},2000)
|
console.log("log end");
|
||||||
setTimeout(() =>{
|
}, 5000);
|
||||||
console.log('log end')
|
|
||||||
}, 5000)
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const seconds = 1000 * 10
|
const seconds = 1000 * 10;
|
||||||
setTimeout(()=>{
|
setTimeout(() => {
|
||||||
console.log(200)
|
console.log("long code");
|
||||||
}, seconds)
|
}, seconds);
|
||||||
|
|
43
server/test/model/mock_pipelines.ts
Normal file
43
server/test/model/mock_pipelines.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import { EXEC_TYPE } from "../../src/core/models/exec_error_model";
|
||||||
|
import { IPipeline, IssueType, StackGenerateType } from "../../src/core/models/process_model";
|
||||||
|
import { TriggerType } from "../../src/features/triggers/models/trigger_database_model";
|
||||||
|
|
||||||
|
export const mockSimplePipeline: IPipeline[] = [
|
||||||
|
{
|
||||||
|
process: {
|
||||||
|
type: EXEC_TYPE.EXEC,
|
||||||
|
command: `nix run gitlab:robossembler/nix-robossembler-overlay#test-script '{
|
||||||
|
"filesMeta":[
|
||||||
|
{"type":"folder","name":"example", "path": null,"rewrite":true}
|
||||||
|
],
|
||||||
|
"path":"$PATH"
|
||||||
|
}'`,
|
||||||
|
isGenerating: true,
|
||||||
|
isLocaleCode: false,
|
||||||
|
issueType: IssueType.WARNING,
|
||||||
|
},
|
||||||
|
trigger: {
|
||||||
|
type: TriggerType.PROCESS,
|
||||||
|
value: [""],
|
||||||
|
},
|
||||||
|
env: null,
|
||||||
|
stackGenerateType: StackGenerateType.SINGLETON,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
process: {
|
||||||
|
type: EXEC_TYPE.EXEC,
|
||||||
|
command: `nix run gitlab:robossembler/nix-robossembler-overlay#test-script '{
|
||||||
|
"filesMeta":[
|
||||||
|
{"type":"file","name":"1.txt", "path":"example","rewrite":true}
|
||||||
|
],
|
||||||
|
"path":"$PATH"
|
||||||
|
}'`,
|
||||||
|
isGenerating: true,
|
||||||
|
isLocaleCode: false,
|
||||||
|
issueType: IssueType.WARNING,
|
||||||
|
},
|
||||||
|
trigger: null,
|
||||||
|
env: null,
|
||||||
|
stackGenerateType: StackGenerateType.SINGLETON,
|
||||||
|
},
|
||||||
|
];
|
|
@ -1,6 +1,6 @@
|
||||||
import { Schema, model } from "mongoose";
|
import { Schema, model } from "mongoose";
|
||||||
|
|
||||||
export interface ITestModel{
|
export interface ITestModel {
|
||||||
_id?: string;
|
_id?: string;
|
||||||
result: string;
|
result: string;
|
||||||
}
|
}
|
||||||
|
@ -12,4 +12,3 @@ export const TestSchema = new Schema({
|
||||||
const schema = "Test";
|
const schema = "Test";
|
||||||
|
|
||||||
export const TestDBModel = model<ITestModel>(schema, TestSchema);
|
export const TestDBModel = model<ITestModel>(schema, TestSchema);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { delay } from "../../src/core/helper/delay";
|
import { delay } from "../../src/core/helpers/delay";
|
||||||
import { EXEC_TYPE } from "../../src/core/model/exec_error_model";
|
import { EXEC_TYPE } from "../../src/core/models/exec_error_model";
|
||||||
import { ExecutorResult } from "../../src/core/model/executor_result";
|
import { ExecutorResult } from "../../src/core/models/executor_result";
|
||||||
import { ExecutorProgramService } from "../../src/core/services/executor_program_service";
|
import { ExecutorProgramService } from "../../src/core/services/executor_program_service";
|
||||||
import { TestCore } from "../core/test_core";
|
import { TestCore } from "../core/test_core";
|
||||||
import { resultTest as resultTest, dirname__ } from "../test";
|
import { resultTest as resultTest, dirname__ } from "../test";
|
||||||
|
@ -15,12 +15,8 @@ export class ExecutorProgramServiceTest extends ExecutorProgramService {
|
||||||
await this.logWriteAndEventEndTypeSpawn();
|
await this.logWriteAndEventEndTypeSpawn();
|
||||||
};
|
};
|
||||||
private async logWriteAndEventEndTypeSpawn() {
|
private async logWriteAndEventEndTypeSpawn() {
|
||||||
const executorProgramService = await new ExecutorProgramService(
|
const executorProgramService = await new ExecutorProgramService(dirname__ + "/");
|
||||||
dirname__ + "/"
|
executorProgramService.call(EXEC_TYPE.SPAWN, "node", ["./mocks/log_code"]);
|
||||||
);
|
|
||||||
executorProgramService.call(EXEC_TYPE.SPAWN, "node", [
|
|
||||||
"./mocks/log_code",
|
|
||||||
]);
|
|
||||||
const test = TestCore.instance;
|
const test = TestCore.instance;
|
||||||
let testIsOk = false;
|
let testIsOk = false;
|
||||||
let logEvent = false;
|
let logEvent = false;
|
||||||
|
@ -29,24 +25,17 @@ export class ExecutorProgramServiceTest extends ExecutorProgramService {
|
||||||
if (e.isSuccess()) {
|
if (e.isSuccess()) {
|
||||||
const executorResult = e.value as ExecutorResult;
|
const executorResult = e.value as ExecutorResult;
|
||||||
if (logEvent == false) {
|
if (logEvent == false) {
|
||||||
logEvent =
|
logEvent = executorResult.data != null && executorResult.data != undefined;
|
||||||
executorResult.data != null && executorResult.data != undefined;
|
|
||||||
}
|
}
|
||||||
testIsOk = executorResult.event == "END" && logEvent;
|
testIsOk = executorResult.event == "END" && logEvent;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await delay(8000);
|
await delay(8000);
|
||||||
test.assert(
|
test.assert(testIsOk, "ExecutorProgramService EXEC_TYPE.SPAWN end event and log write");
|
||||||
testIsOk,
|
|
||||||
"ExecutorProgramService EXEC_TYPE.SPAWN end event and log write"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
private async logWriteAndEventEndTestTypeExec() {
|
private async logWriteAndEventEndTestTypeExec() {
|
||||||
const executorProgramService = await new ExecutorProgramService(dirname__);
|
const executorProgramService = await new ExecutorProgramService(dirname__);
|
||||||
executorProgramService.call(
|
executorProgramService.call(EXEC_TYPE.EXEC, "node ./test/mocks/log_code");
|
||||||
EXEC_TYPE.EXEC,
|
|
||||||
"node ./test/mocks/log_code"
|
|
||||||
);
|
|
||||||
const test = TestCore.instance;
|
const test = TestCore.instance;
|
||||||
executorProgramService.on((e) => {
|
executorProgramService.on((e) => {
|
||||||
if (e.isSuccess()) {
|
if (e.isSuccess()) {
|
||||||
|
@ -61,10 +50,7 @@ export class ExecutorProgramServiceTest extends ExecutorProgramService {
|
||||||
}
|
}
|
||||||
private async longTimeCancelTest() {
|
private async longTimeCancelTest() {
|
||||||
const executorProgramService = await new ExecutorProgramService("", 1000);
|
const executorProgramService = await new ExecutorProgramService("", 1000);
|
||||||
executorProgramService.call(
|
executorProgramService.call(EXEC_TYPE.EXEC, "node ./test/mocks/long_code");
|
||||||
EXEC_TYPE.EXEC,
|
|
||||||
"node ./test/mocks/long_code"
|
|
||||||
);
|
|
||||||
await delay(1500);
|
await delay(1500);
|
||||||
const worker = executorProgramService.worker as Worker;
|
const worker = executorProgramService.worker as Worker;
|
||||||
const test = TestCore.instance;
|
const test = TestCore.instance;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { FilesChangeNotifierService } from "../../src/core/services/files_change_notifier_service";
|
import { FilesChangeNotifierService } from "../../src/core/services/files_change_notifier_service";
|
||||||
import { EventsFileChanger } from "../../src/core/model/meta_data_file_manager_model";
|
import { EventsFileChanger } from "../../src/core/models/meta_data_file_manager_model";
|
||||||
import { assert, dirname__ } from "../test";
|
import { assert, dirname__ } from "../test";
|
||||||
import { delay } from "../../src/core/helper/delay";
|
import { delay } from "../../src/core/helpers/delay";
|
||||||
|
|
||||||
export class FilesChangerTest extends FilesChangeNotifierService {
|
export class FilesChangerTest extends FilesChangeNotifierService {
|
||||||
directory = dirname__ + "/context/";
|
directory = dirname__ + "/context/";
|
||||||
|
@ -26,10 +26,7 @@ export class FilesChangerTest extends FilesChangeNotifierService {
|
||||||
await delay(2000);
|
await delay(2000);
|
||||||
fs.writeFileSync(this.filePath, this.data());
|
fs.writeFileSync(this.filePath, this.data());
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
this.hashUnitEqualTo(
|
this.hashUnitEqualTo(EventsFileChanger.create, "FilesChangeNotifierService create file");
|
||||||
EventsFileChanger.create,
|
|
||||||
"FilesChangeNotifierService create file"
|
|
||||||
);
|
|
||||||
|
|
||||||
this.cancel();
|
this.cancel();
|
||||||
}
|
}
|
||||||
|
@ -39,28 +36,19 @@ export class FilesChangerTest extends FilesChangeNotifierService {
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
fs.writeFileSync(this.filePath, this.data() + "132");
|
fs.writeFileSync(this.filePath, this.data() + "132");
|
||||||
await delay(500);
|
await delay(500);
|
||||||
this.hashUnitEqualTo(
|
this.hashUnitEqualTo(EventsFileChanger.update, "FilesChangeNotifierService update file");
|
||||||
EventsFileChanger.update,
|
|
||||||
"FilesChangeNotifierService update file"
|
|
||||||
);
|
|
||||||
this.cancel();
|
this.cancel();
|
||||||
}
|
}
|
||||||
public async initFile() {
|
public async initFile() {
|
||||||
this.init();
|
this.init();
|
||||||
await delay(500);
|
await delay(500);
|
||||||
this.hashUnitEqualTo(
|
this.hashUnitEqualTo(EventsFileChanger.static, "FilesChangeNotifierService init file");
|
||||||
EventsFileChanger.static,
|
|
||||||
"FilesChangeNotifierService init file"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
public async deleteFile() {
|
public async deleteFile() {
|
||||||
this.call();
|
this.call();
|
||||||
fs.unlinkSync(this.filePath);
|
fs.unlinkSync(this.filePath);
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
this.hashUnitEqualTo(
|
this.hashUnitEqualTo(EventsFileChanger.delete, "FilesChangeNotifierService delete file");
|
||||||
EventsFileChanger.delete,
|
|
||||||
"FilesChangeNotifierService delete file"
|
|
||||||
);
|
|
||||||
this.cancel();
|
this.cancel();
|
||||||
}
|
}
|
||||||
public async notExistsDirectory() {
|
public async notExistsDirectory() {
|
||||||
|
|
12
server/test/services/pipeline_real_time_service_test.ts
Normal file
12
server/test/services/pipeline_real_time_service_test.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { PipelineRealTimeService } from "../../src/core/services/pipeline_real_time_service";
|
||||||
|
import { mockSimplePipeline } from "../model/mock_pipelines";
|
||||||
|
import { dirname__ } from "../test";
|
||||||
|
|
||||||
|
export class PipelineRealTimeServiceTest extends PipelineRealTimeService {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
async test() {
|
||||||
|
// this.runPipeline(mockSimplePipeline, dirname__, "");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,97 +1,21 @@
|
||||||
import { rmSync } from "fs";
|
import { rmSync } from "fs";
|
||||||
import * as fs from "fs";
|
|
||||||
|
|
||||||
import {
|
|
||||||
IssueType,
|
|
||||||
StackGenerateType,
|
|
||||||
} from "../../src/core/model/process_model";
|
|
||||||
import { EXEC_TYPE } from "../../src/core/model/exec_error_model";
|
|
||||||
import { StackService } from "../../src/core/services/stack_service";
|
import { StackService } from "../../src/core/services/stack_service";
|
||||||
import { delay } from "../../src/core/helper/delay";
|
import { delay } from "../../src/core/helpers/delay";
|
||||||
import { assert, dirname__ } from "../test";
|
import { assert, dirname__ } from "../test";
|
||||||
import { TriggerType } from "../../src/features/triggers/trigger_model";
|
import { mockSimplePipeline } from "../model/mock_pipelines";
|
||||||
|
import { readDirRecursive } from "../../src/core/repository/fs";
|
||||||
|
|
||||||
abstract class IStackServiceTest {
|
abstract class IStackServiceTest {
|
||||||
abstract test(): Promise<boolean>;
|
abstract test(): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readDirRecursive(path: string, filesToDir: string[] = []) {
|
class SimpleTestStackServiceTest extends StackService implements IStackServiceTest {
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SimpleTestStackServiceTest
|
|
||||||
extends StackService
|
|
||||||
implements IStackServiceTest
|
|
||||||
{
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(
|
super(mockSimplePipeline, dirname__ + "/context/");
|
||||||
[
|
|
||||||
{
|
|
||||||
process: {
|
|
||||||
type: EXEC_TYPE.EXEC,
|
|
||||||
command: `nix run gitlab:robossembler/nix-robossembler-overlay#test-script '{
|
|
||||||
"filesMeta":[
|
|
||||||
{"type":"folder","name":"example", "path": null,"rewrite":true}
|
|
||||||
],
|
|
||||||
"path":"$PATH"
|
|
||||||
}'`,
|
|
||||||
isGenerating: true,
|
|
||||||
isLocaleCode: false,
|
|
||||||
issueType: IssueType.WARNING,
|
|
||||||
},
|
|
||||||
trigger: {
|
|
||||||
type: TriggerType.FILE,
|
|
||||||
value: ["context"],
|
|
||||||
},
|
|
||||||
env: null,
|
|
||||||
stackGenerateType: StackGenerateType.SINGLETON,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
process: {
|
|
||||||
type: EXEC_TYPE.EXEC,
|
|
||||||
command: `nix run gitlab:robossembler/nix-robossembler-overlay#test-script '{
|
|
||||||
"filesMeta":[
|
|
||||||
{"type":"file","name":"1.txt", "path":"example","rewrite":true}
|
|
||||||
],
|
|
||||||
"path":"$PATH"
|
|
||||||
}'`,
|
|
||||||
isGenerating: true,
|
|
||||||
isLocaleCode: false,
|
|
||||||
issueType: IssueType.WARNING,
|
|
||||||
},
|
|
||||||
trigger: {
|
|
||||||
type: TriggerType.FILE,
|
|
||||||
value: ["1.txt"],
|
|
||||||
},
|
|
||||||
env: null,
|
|
||||||
stackGenerateType: StackGenerateType.SINGLETON,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
dirname__ + "/context/"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
async test(): Promise<boolean> {
|
async test(): Promise<boolean> {
|
||||||
await this.call();
|
await this.call();
|
||||||
const testResult = readDirRecursive(this.path).equals(
|
const testResult = readDirRecursive(this.path).equals(["1.txt", "test.txt"], true);
|
||||||
["1.txt", "test.txt"],
|
|
||||||
true
|
|
||||||
);
|
|
||||||
await delay(100);
|
await delay(100);
|
||||||
rmSync(this.path + "example/", { recursive: true });
|
rmSync(this.path + "example/", { recursive: true });
|
||||||
return testResult;
|
return testResult;
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
import {
|
import { EventsFileChanger, MetaDataFileManagerModel } from "../../src/core/models/meta_data_file_manager_model";
|
||||||
EventsFileChanger,
|
|
||||||
MetaDataFileManagerModel,
|
|
||||||
} from "../../src/core/model/meta_data_file_manager_model";
|
|
||||||
|
|
||||||
import { TriggerService } from "../../src/core/services/trigger_service";
|
import { TriggerService } from "../../src/core/services/trigger_service";
|
||||||
import { TriggerType } from "../../src/features/triggers/trigger_model";
|
import { TriggerType } from "../../src/features/triggers/models/trigger_database_model";
|
||||||
import { assert } from "../test";
|
import { assert } from "../test";
|
||||||
abstract class TriggerTest {
|
abstract class TriggerTest {
|
||||||
abstract test(): Promise<boolean>;
|
abstract test(): Promise<boolean>;
|
||||||
|
@ -17,11 +14,7 @@ class TriggerServiceFileOkTest extends TriggerService implements TriggerTest {
|
||||||
value: ["context"],
|
value: ["context"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/context/": new MetaDataFileManagerModel(
|
"/context/": new MetaDataFileManagerModel("", "", EventsFileChanger.create),
|
||||||
"",
|
|
||||||
"",
|
|
||||||
EventsFileChanger.create
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
|
@ -32,10 +25,7 @@ class TriggerServiceFileOkTest extends TriggerService implements TriggerTest {
|
||||||
return r.isSuccess();
|
return r.isSuccess();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class TriggerServiceFileErrorTest
|
class TriggerServiceFileErrorTest extends TriggerService implements TriggerTest {
|
||||||
extends TriggerService
|
|
||||||
implements TriggerTest
|
|
||||||
{
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(
|
super(
|
||||||
{
|
{
|
||||||
|
@ -44,11 +34,7 @@ class TriggerServiceFileErrorTest
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/ctx/": new MetaDataFileManagerModel("", "", EventsFileChanger.create),
|
"/ctx/": new MetaDataFileManagerModel("", "", EventsFileChanger.create),
|
||||||
"/context/": new MetaDataFileManagerModel(
|
"/context/": new MetaDataFileManagerModel("", "", EventsFileChanger.create),
|
||||||
"",
|
|
||||||
"",
|
|
||||||
EventsFileChanger.create
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
""
|
""
|
||||||
|
@ -60,10 +46,7 @@ class TriggerServiceFileErrorTest
|
||||||
return r.isFailure();
|
return r.isFailure();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class TriggerServiceProcessOkTest
|
class TriggerServiceProcessOkTest extends TriggerService implements TriggerTest {
|
||||||
extends TriggerService
|
|
||||||
implements TriggerTest
|
|
||||||
{
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(
|
super(
|
||||||
{
|
{
|
||||||
|
@ -77,11 +60,7 @@ class TriggerServiceProcessOkTest
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/context/": new MetaDataFileManagerModel(
|
"/context/": new MetaDataFileManagerModel("", "", EventsFileChanger.create),
|
||||||
"",
|
|
||||||
"",
|
|
||||||
EventsFileChanger.create
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
|
@ -92,10 +71,7 @@ class TriggerServiceProcessOkTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TriggerServiceProcessErrorTest
|
class TriggerServiceProcessErrorTest extends TriggerService implements TriggerTest {
|
||||||
extends TriggerService
|
|
||||||
implements TriggerTest
|
|
||||||
{
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(
|
super(
|
||||||
{
|
{
|
||||||
|
@ -109,11 +85,7 @@ class TriggerServiceProcessErrorTest
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/context/": new MetaDataFileManagerModel(
|
"/context/": new MetaDataFileManagerModel("", "", EventsFileChanger.create),
|
||||||
"",
|
|
||||||
"",
|
|
||||||
EventsFileChanger.create
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,51 +1,84 @@
|
||||||
|
import "reflect-metadata";
|
||||||
import { TestCore } from "./core/test_core";
|
import { TestCore } from "./core/test_core";
|
||||||
import { UnitTestEnv } from "../src/core/di/env";
|
// import { UnitTestEnv } from "../src/core/di/env";
|
||||||
import { dirname } from "path";
|
import { dirname } from "path";
|
||||||
import locator from "../src/core/di/register_di";
|
// import locator from "../src/core/di/register_di";
|
||||||
import { ExecutorProgramServiceTest } from "./services/executor_program_service_test";
|
import { ExecutorProgramServiceTest } from "./services/executor_program_service_test";
|
||||||
import { FilesChangerTest } from "./services/files_change_notifier_service_test";
|
import { FilesChangerTest } from "./services/files_change_notifier_service_test";
|
||||||
import { TriggerServiceTest } from "./services/trigger_service_test";
|
import { TriggerServiceTest } from "./services/trigger_service_test";
|
||||||
import { StackServiceTest } from "./services/stack_service_test";
|
import { StackServiceTest } from "./services/stack_service_test";
|
||||||
import mongoose from "mongoose";
|
|
||||||
import { CreateDataBaseModelUseCaseTest } from "./usecases/create_database_model_usecase_test";
|
import { CreateDataBaseModelUseCaseTest } from "./usecases/create_database_model_usecase_test";
|
||||||
import { DeleteDataBaseModelUseCaseTest } from "./usecases/delete_database_model_usecase_test";
|
import { DeleteDataBaseModelUseCaseTest } from "./usecases/delete_database_model_usecase_test";
|
||||||
import { ReadDataBaseModelUseCaseTest } from "./usecases/read_database_model_usecase_test";
|
import { ReadDataBaseModelUseCaseTest } from "./usecases/read_database_model_usecase_test";
|
||||||
import { UpdateDataBaseModelUseCaseTest } from "./usecases/update_database_model_usecase";
|
import { UpdateDataBaseModelUseCaseTest } from "./usecases/update_database_model_usecase";
|
||||||
import { PaginationDataBaseModelUseCaseTest } from "./usecases/pagination_database_model_usecase_test";
|
import { PaginationDataBaseModelUseCaseTest } from "./usecases/pagination_database_model_usecase_test";
|
||||||
|
import { extensions } from "../src/core/extensions/extensions";
|
||||||
|
import { CrudControllerTest } from "./controllers/crud_controller_test";
|
||||||
|
import { TriggerPresentation } from "../src/features/triggers/triggers_presentation";
|
||||||
|
import { App, Environment, ServerStatus } from "../src/core/controllers/app";
|
||||||
|
import { httpRoutes } from "../src/core/controllers/routes";
|
||||||
|
import { DataBaseConnectUseCase } from "../src/core/usecases/database_connect_usecase";
|
||||||
|
|
||||||
|
|
||||||
const testCore = TestCore.instance;
|
const testCore = TestCore.instance;
|
||||||
|
|
||||||
export const dirname__: string = dirname(__filename);
|
export const dirname__: string = dirname(__filename);
|
||||||
export const assert = testCore.assert;
|
export const assert = testCore.assert;
|
||||||
export const resultTest = testCore.resultTest;
|
export const resultTest = testCore.resultTest;
|
||||||
const env = new UnitTestEnv(dirname__);
|
|
||||||
|
|
||||||
locator(env);
|
const tests = [
|
||||||
|
CreateDataBaseModelUseCaseTest,
|
||||||
|
DeleteDataBaseModelUseCaseTest,
|
||||||
|
ReadDataBaseModelUseCaseTest,
|
||||||
|
UpdateDataBaseModelUseCaseTest,
|
||||||
|
PaginationDataBaseModelUseCaseTest,
|
||||||
|
];
|
||||||
|
const init = async () => {
|
||||||
|
await new DataBaseConnectUseCase().call();
|
||||||
|
};
|
||||||
|
|
||||||
const tests = [CreateDataBaseModelUseCaseTest, DeleteDataBaseModelUseCaseTest,ReadDataBaseModelUseCaseTest,UpdateDataBaseModelUseCaseTest, PaginationDataBaseModelUseCaseTest]
|
const unitTest = async () => {
|
||||||
const init = async () =>{
|
await init();
|
||||||
await mongoose.connect('mongodb://127.0.0.1:27017/test')
|
|
||||||
}
|
|
||||||
|
|
||||||
const test = async () =>{
|
|
||||||
await new ExecutorProgramServiceTest(dirname__).test();
|
await new ExecutorProgramServiceTest(dirname__).test();
|
||||||
await new FilesChangerTest(dirname__).test();
|
await new FilesChangerTest(dirname__).test();
|
||||||
await new StackServiceTest(dirname__ + "/context/").test();
|
await new StackServiceTest(dirname__ + "/context/").test();
|
||||||
await new TriggerServiceTest().test();
|
await new TriggerServiceTest().test();
|
||||||
await new CreateDataBaseModelUseCaseTest().test()
|
await new CreateDataBaseModelUseCaseTest().test();
|
||||||
|
await new CreateDataBaseModelUseCaseTest().test();
|
||||||
await new CreateDataBaseModelUseCaseTest().test()
|
await new DeleteDataBaseModelUseCaseTest().test();
|
||||||
await new DeleteDataBaseModelUseCaseTest().test()
|
await new ReadDataBaseModelUseCaseTest().test();
|
||||||
await new ReadDataBaseModelUseCaseTest().test()
|
await new UpdateDataBaseModelUseCaseTest().test();
|
||||||
await new UpdateDataBaseModelUseCaseTest().test()
|
// await new PipelineRealTimeServiceTest().test()
|
||||||
for await (const usecase of tests) {
|
for await (const usecase of tests) {
|
||||||
testCore.assert(await new usecase().test(), usecase.name)
|
testCore.assert(await new usecase().test(), usecase.name);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
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) {
|
||||||
|
for await (const el of presentationCrudControllers) {
|
||||||
|
testCore.assert(await new CrudControllerTest(app.port, el).call(), el.constructor.name);
|
||||||
|
}
|
||||||
|
resolve(e);
|
||||||
|
}
|
||||||
|
if (e === ServerStatus.error) {
|
||||||
|
console.log(e);
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
await init()
|
extensions();
|
||||||
await test()
|
if (process.env.NODE_ENV === "unit") {
|
||||||
|
await unitTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === "e2e") {
|
||||||
|
await e2eTest();
|
||||||
|
}
|
||||||
await testCore.testResult();
|
await testCore.testResult();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,7 @@ export class PaginationDataBaseModelUseCaseTest {
|
||||||
async test() {
|
async test() {
|
||||||
let testIsSuccess = false;
|
let testIsSuccess = false;
|
||||||
await (
|
await (
|
||||||
await new PaginationDataBaseModelUseCase<ITestModel>(TestDBModel, 1).call(
|
await new PaginationDataBaseModelUseCase<ITestModel>(TestDBModel, 1).call(1)
|
||||||
1
|
|
||||||
)
|
|
||||||
).fold(
|
).fold(
|
||||||
(s) => {
|
(s) => {
|
||||||
testIsSuccess = s.length === 1;
|
testIsSuccess = s.length === 1;
|
||||||
|
|
|
@ -1,25 +1,20 @@
|
||||||
import { CreateDataBaseModelUseCase } from "../../src/core/usecases/create_database_model_usecase";
|
import { CreateDataBaseModelUseCase } from "../../src/core/usecases/create_database_model_usecase";
|
||||||
import { ReadByIdDataBaseModelUseCase } from "../../src/core/usecases/read_database_model_usecase";
|
import { ReadByIdDataBaseModelUseCase } from "../../src/core/usecases/read_by_id_database_model_usecase";
|
||||||
import { ITestModel, TestDBModel } from "../model/test_db_mongo_model";
|
import { ITestModel, TestDBModel } from "../model/test_db_mongo_model";
|
||||||
|
|
||||||
export class ReadDataBaseModelUseCaseTest {
|
export class ReadDataBaseModelUseCaseTest {
|
||||||
async test() {
|
async test() {
|
||||||
let testIsSuccess = false;
|
let testIsSuccess = false;
|
||||||
|
|
||||||
const result = await new CreateDataBaseModelUseCase<ITestModel>(
|
const result = await new CreateDataBaseModelUseCase<ITestModel>(TestDBModel).call({
|
||||||
TestDBModel
|
|
||||||
).call({
|
|
||||||
result: "test",
|
result: "test",
|
||||||
});
|
});
|
||||||
await result.fold(
|
await result.fold(
|
||||||
async (s) => {
|
async (s) => {
|
||||||
const r = await new ReadByIdDataBaseModelUseCase<ITestModel>(
|
const r = await new ReadByIdDataBaseModelUseCase<ITestModel>(TestDBModel).call(s.id);
|
||||||
TestDBModel
|
|
||||||
).call(s.id);
|
|
||||||
await r.fold(
|
await r.fold(
|
||||||
(_s1) => {
|
(_s1) => {
|
||||||
testIsSuccess = true;
|
testIsSuccess = true;
|
||||||
|
|
||||||
},
|
},
|
||||||
(_e) => {}
|
(_e) => {}
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,18 +6,14 @@ export class UpdateDataBaseModelUseCaseTest {
|
||||||
async test() {
|
async test() {
|
||||||
let testIsSuccess = false;
|
let testIsSuccess = false;
|
||||||
|
|
||||||
const model = await new CreateDataBaseModelUseCase<ITestModel>(
|
const model = await new CreateDataBaseModelUseCase<ITestModel>(TestDBModel).call({
|
||||||
TestDBModel
|
|
||||||
).call({
|
|
||||||
result: "test",
|
result: "test",
|
||||||
});
|
});
|
||||||
await model.fold(
|
await model.fold(
|
||||||
async (s) => {
|
async (s) => {
|
||||||
(
|
(
|
||||||
await new UpdateDataBaseModelUseCase<any, ITestModel>(
|
await new UpdateDataBaseModelUseCase<any, ITestModel>(TestDBModel).call({
|
||||||
TestDBModel
|
_id: s.id,
|
||||||
).call({
|
|
||||||
_id:s.id,
|
|
||||||
result: "complete",
|
result: "complete",
|
||||||
})
|
})
|
||||||
).fold(
|
).fold(
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
{
|
{
|
||||||
"compileOnSave": false,
|
"compileOnSave": false,
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2017",
|
"target": "es2017",
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"pretty": true,
|
"pretty": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"outDir": "./build",
|
"outDir": "./build",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"noEmit": false,
|
"noEmit": false,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"importHelpers": true,
|
"importHelpers": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
25
ui/.gitignore
vendored
Normal file
25
ui/.gitignore
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
package-lock.json
|
||||||
|
todo.md
|
62
ui/package.json
Normal file
62
ui/package.json
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"name": "ui-robossembler",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@monaco-editor/react": "^4.6.0",
|
||||||
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
|
"@testing-library/react": "^13.4.0",
|
||||||
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"@types/jest": "^27.5.2",
|
||||||
|
"@types/node": "^16.18.46",
|
||||||
|
"@types/react": "^18.2.21",
|
||||||
|
"@types/react-dom": "^18.2.7",
|
||||||
|
"@types/socket.io-client": "^3.0.0",
|
||||||
|
"@types/uuid": "^9.0.2",
|
||||||
|
"formik-antd": "^2.0.4",
|
||||||
|
"i18next": "^23.6.0",
|
||||||
|
"mobx": "^6.10.0",
|
||||||
|
"mobx-react-lite": "^4.0.4",
|
||||||
|
"mobx-store-inheritance": "^1.0.6",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-i18next": "^13.3.1",
|
||||||
|
"react-infinite-scroll-component": "^6.1.0",
|
||||||
|
"react-router-dom": "^6.18.0",
|
||||||
|
"react-scripts": "5.0.1",
|
||||||
|
"sass": "^1.66.1",
|
||||||
|
"socket.io-client": "^4.7.2",
|
||||||
|
"three": "^0.159.0",
|
||||||
|
"typescript": "^4.9.5",
|
||||||
|
"urdf-loader": "^0.12.1",
|
||||||
|
"uuid": "^9.0.1",
|
||||||
|
"web-vitals": "^2.1.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/three": "^0.158.3"
|
||||||
|
}
|
||||||
|
}
|
BIN
ui/public/favicon.ico
Normal file
BIN
ui/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
21
ui/public/index.html
Normal file
21
ui/public/index.html
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta name="description" content="Web site created using create-react-app" />
|
||||||
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<title>robossembler: pipeline </title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
BIN
ui/public/logo192.png
Normal file
BIN
ui/public/logo192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue