Refactoring

This commit is contained in:
IDONTSUDO 2023-12-22 21:07:55 +03:00
parent 11ca9cdb5e
commit 7ff6165882
22 changed files with 172 additions and 327 deletions

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

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

View file

@ -1,11 +1,11 @@
import express from "express";
import { Routes } from "../interfaces/router";
import cors from "cors";
import fileUpload from "express-fileupload";
import { Routes } from "../interfaces/router";
import { Server } from "socket.io";
import { createServer } from "http";
import { SocketSubscriber } from "./socket_controller";
import { dirname } from "path";
import fileUpload from "express-fileupload";
import { SetLastActivePipelineToRealTimeServiceScenario } from "../scenarios/set_active_pipeline_to_realtime_service_scenario";
import { CheckAndCreateStaticFilesFolderUseCase } from "../usecases/check_and_create_static_files_folder_usecase";
import { DataBaseConnectUseCase } from "../usecases/database_connect_usecase";
@ -13,7 +13,7 @@ import { TypedEvent } from "../helpers/typed_event";
export enum ServerStatus {
init = "init",
finished = "finshed",
finished = "finished",
error = "error",
}
export enum Environment {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -27,7 +27,7 @@ export class ExecutorProgramService
private async workerExecuted(command: string, workerType: WorkerType, args: Array<string> | undefined = undefined) {
cluster.setupPrimary({
exec: "/Users/idontsudo/Desktop/testdeck-mocha-seed/server/build/src/core/helpers/worker_computed.js",
exec: __dirname + "/../helpers/worker_computed.js",
});
const worker = cluster.fork();
@ -45,17 +45,21 @@ export class ExecutorProgramService
worker.send(workerDataExec);
worker.on("message", (e) => {
const spawnError = SpawnError.isError(e);
if (spawnError instanceof SpawnError) {
this.emit(Result.error(spawnError));
return;
}
const executorResult = ExecutorResult.isExecutorResult(e);
if (executorResult instanceof ExecutorResult) {
this.emit(Result.ok(executorResult));
return;
}
const execError = ExecError.isExecError(e);
if (execError instanceof ExecError) {
this.emit(Result.error(execError));
return;
@ -78,6 +82,7 @@ export class ExecutorProgramService
return;
}
this.workerExecuted(command, WorkerType.SPAWN, args);
return;
}
}

View file

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

View file

@ -45,71 +45,55 @@ export class StackService extends TypedEvent<Iteration[]> implements IStackServi
return processMetaData;
}
public async call() {
let inc = 0;
for await (const el of this.callStack!) {
await this.execStack(inc, el);
inc += 1;
this.callStack.map(async (el, index) => {
await this.execStack(index, el);
this.emit(this.callStack);
}
});
}
async execStack(stackNumber: number, stackLayer: Iteration): Promise<void | boolean> {
const executorService = new ExecutorProgramService(this.path);
executorService.call(stackLayer.process.process.type, stackLayer.process.process.command);
executorService.on((e) => {
console.log(e);
});
const filesChangeNotifierService = new FilesChangeNotifierService(this.path);
filesChangeNotifierService.call();
const result = await this.waitEvent<Result<ExecError | SpawnError, ExecutorResult>>(executorService);
await delay(100);
if (result.isSuccess()) {
this.callStack[stackNumber].result = result.value;
this.callStack[stackNumber].hashes = filesChangeNotifierService.hashes;
const triggerResult = await this.triggerExec(stackLayer.process.trigger, stackNumber);
triggerResult.fold(
(s) => {
s;
},
(e) => {
e;
const result = await executorService.waitedEvent((event: Result<ExecError | SpawnError, ExecutorResult>) => {
event.map((value) => {
if (value.event === EXEC_EVENT.END) {
return true;
}
);
}
});
return false;
});
await delay(100);
result.map(async (el) => {
this.callStack.at(stackNumber).result = el;
this.callStack.at(stackNumber).hashes = filesChangeNotifierService.hashes;
(await this.triggerExec(stackLayer.process.trigger, stackNumber)).map(() => {
filesChangeNotifierService.cancel();
});
});
filesChangeNotifierService.cancel();
return;
}
public waitEvent<T>(stream: TypedEvent<T>): Promise<T> {
const promise = new Promise<T>((resolve, reject) => {
const addListener = () => {
stream.on((e) => {
const event = e as Result<ExecError | SpawnError, ExecutorResult>;
event.fold(
(s) => {
if (s.event === EXEC_EVENT.END) {
resolve(e);
}
},
(e) => {
reject(e);
}
);
});
};
addListener();
});
return promise;
}
private async triggerExec(trigger: Trigger | null, stackNumber: number): Promise<Result<boolean, boolean>> {
if (trigger !== null) {
const hashes = this.callStack[stackNumber].hashes;
if (hashes != null) {
return await new TriggerService(trigger, hashes, this.path).call();
private async triggerExec(trigger: Trigger | null, stackNumber: number): Promise<Result<Error, void>> {
try {
if (trigger !== null) {
const hashes = this.callStack[stackNumber].hashes;
if (hashes != null) {
await new TriggerService(trigger, hashes, this.path).call();
}
throw new Error("Hashes is null");
}
throw new Error("Hashes is null");
return Result.ok();
} catch (error) {
return Result.error(error);
}
return Result.ok();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -16,12 +16,10 @@ export class ProjectInstancePresentation extends CrudController<
});
super.post(new CreateNewProjectInstanceScenario().call);
super.subRoutes = [
{
method: "POST",
subUrl: "upload",
fn: new UploadCadFileToProjectScenario(),
},
];
this.subRoutes.push({
method: "POST",
subUrl: "upload",
fn: new UploadCadFileToProjectScenario(),
});
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -37,24 +37,23 @@ const init = async () => {
const unitTest = async () => {
await init();
await new ExecutorProgramServiceTest(dirname__).test();
await new FilesChangerTest(dirname__).test();
await new StackServiceTest(dirname__ + "/context/").test();
await new TriggerServiceTest().test();
await new CreateDataBaseModelUseCaseTest().test();
await new CreateDataBaseModelUseCaseTest().test();
await new DeleteDataBaseModelUseCaseTest().test();
await new ReadDataBaseModelUseCaseTest().test();
await new UpdateDataBaseModelUseCaseTest().test();
// await new PipelineRealTimeServiceTest().test()
for await (const usecase of tests) {
testCore.assert(await new usecase().test(), usecase.name);
}
// await new ExecutorProgramServiceTest(dirname__).test();
// await new FilesChangerTest(dirname__).test();
await new StackServiceTest(dirname__).test();
// await new TriggerServiceTest().test();
// await new CreateDataBaseModelUseCaseTest().test();
// await new CreateDataBaseModelUseCaseTest().test();
// await new DeleteDataBaseModelUseCaseTest().test();
// await new ReadDataBaseModelUseCaseTest().test();
// await new UpdateDataBaseModelUseCaseTest().test();
// for await (const usecase of tests) {
// 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) {