From d850fedba9e5d809ab996be9e86114998a438a54 Mon Sep 17 00:00:00 2001 From: Igor Brylyov Date: Thu, 31 Aug 2023 12:41:12 +0000 Subject: [PATCH 1/2] Initial commit --- README.md | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..5356805 --- /dev/null +++ b/README.md @@ -0,0 +1,92 @@ +# Веб-сервис для отладки Robossembler Framework + + + +## Getting started + +To make it easy for you to get started with GitLab, here's a list of recommended next steps. + +Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! + +## Add your files + +- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files +- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: + +``` +cd existing_repo +git remote add origin https://gitlab.com/robossembler/webservice.git +git branch -M main +git push -uf origin main +``` + +## Integrate with your tools + +- [ ] [Set up project integrations](https://gitlab.com/robossembler/webservice/-/settings/integrations) + +## Collaborate with your team + +- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) +- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) +- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) +- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) +- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) + +## Test and Deploy + +Use the built-in continuous integration in GitLab. + +- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) +- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) +- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) +- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) +- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) + +*** + +# Editing this README + +When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template. + +## Suggestions for a good README +Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. + +## Name +Choose a self-explaining name for your project. + +## Description +Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. + +## Badges +On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. + +## Visuals +Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. + +## Installation +Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. + +## Usage +Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. + +## Support +Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. + +## Roadmap +If you have ideas for releases in the future, it is a good idea to list them in the README. + +## Contributing +State if you are open to contributions and what your requirements are for accepting them. + +For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. + +You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. + +## Authors and acknowledgment +Show your appreciation to those who have contributed to the project. + +## License +For open source projects, say how it is licensed. + +## Project status +If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. From 889fc95c3d1c496c61ff57cd4026e3354bbe91f1 Mon Sep 17 00:00:00 2001 From: IDONTSUDO Date: Tue, 31 Oct 2023 09:03:39 +0000 Subject: [PATCH 2/2] MVP back end --- .vscode/settings.json | 36 +-- server/.gitignore | 2 + server/package.json | 20 +- server/src/core/controllers/app.ts | 82 ++++++ .../src/core/controllers/crud_controller.ts | 34 +++ .../src/core/controllers/http_controller.ts | 107 ++++++++ .../src/core/controllers/socket_controller.ts | 15 ++ server/src/core/di/env.ts | 9 +- server/src/core/di/register_di.ts | 17 +- server/src/core/extensions/extensions.ts | 5 +- server/src/core/helper/cancelable_promise.ts | 249 +----------------- server/src/core/helper/worker_computed.ts | 4 +- server/src/core/interfaces/payload.ts | 14 + server/src/core/interfaces/response.ts | 3 + server/src/core/interfaces/router.ts | 14 + .../src/core/middlewares/validation_auth.ts | 12 + .../src/core/middlewares/validation_model.ts | 30 +++ server/src/core/model/executor_result.ts | 31 ++- server/src/core/model/process_model.ts | 31 +-- .../core/services/executor_program_service.ts | 14 +- .../services/files_change_notifier_service.ts | 10 +- server/src/core/services/stack_service.ts | 37 ++- server/src/core/services/trigger_service.ts | 17 +- .../usecases/create_database_model_usecase.ts | 22 ++ .../usecases/delete_database_model_usecase.ts | 18 ++ .../pagination_database_model_usecase.ts | 28 ++ .../usecases/read_database_model_usecase.ts | 20 ++ .../usecases/update_database_model_usecase.ts | 27 ++ .../src/features/pipelines/pipeline_model.ts | 47 ++++ .../pipelines/pipeline_presentation.ts | 16 ++ server/src/features/process/process_model.ts | 69 +++++ .../features/process/process_presentation.ts | 15 ++ .../src/features/projects/projects_model.ts | 31 +++ .../projects/projects_presentation.ts | 16 ++ server/src/features/triggers/trigger_model.ts | 43 +++ .../triggers/triggers_presentation.ts | 15 ++ server/src/index.ts | 1 - server/src/main.ts | 21 ++ server/test/core/test_core.ts | 13 +- server/test/model/test_db_mongo_model.ts | 15 ++ .../executor_program_service_test.ts | 22 +- .../files_change_notifier_service_test.ts | 8 +- .../stack_service_test.ts | 12 +- .../trigger_service_test.ts | 9 +- server/test/test.ts | 46 +++- .../create_database_model_usecase_test.ts | 12 + .../delete_database_model_usecase_test.ts | 29 ++ .../pagination_database_model_usecase_test.ts | 19 ++ .../read_database_model_usecase_test.ts | 32 +++ .../usecases/update_database_model_usecase.ts | 35 +++ server/tsconfig.json | 40 ++- 51 files changed, 1048 insertions(+), 426 deletions(-) create mode 100644 server/src/core/controllers/app.ts create mode 100644 server/src/core/controllers/crud_controller.ts create mode 100644 server/src/core/controllers/http_controller.ts create mode 100644 server/src/core/controllers/socket_controller.ts create mode 100644 server/src/core/interfaces/payload.ts create mode 100644 server/src/core/interfaces/response.ts create mode 100644 server/src/core/interfaces/router.ts create mode 100644 server/src/core/middlewares/validation_auth.ts create mode 100644 server/src/core/middlewares/validation_model.ts create mode 100644 server/src/core/usecases/create_database_model_usecase.ts create mode 100644 server/src/core/usecases/delete_database_model_usecase.ts create mode 100644 server/src/core/usecases/pagination_database_model_usecase.ts create mode 100644 server/src/core/usecases/read_database_model_usecase.ts create mode 100644 server/src/core/usecases/update_database_model_usecase.ts create mode 100644 server/src/features/pipelines/pipeline_model.ts create mode 100644 server/src/features/pipelines/pipeline_presentation.ts create mode 100644 server/src/features/process/process_model.ts create mode 100644 server/src/features/process/process_presentation.ts create mode 100644 server/src/features/projects/projects_model.ts create mode 100644 server/src/features/projects/projects_presentation.ts create mode 100644 server/src/features/triggers/trigger_model.ts create mode 100644 server/src/features/triggers/triggers_presentation.ts delete mode 100644 server/src/index.ts create mode 100644 server/src/main.ts create mode 100644 server/test/model/test_db_mongo_model.ts rename server/test/{features => services}/executor_program_service_test.ts (88%) rename server/test/{features => services}/files_change_notifier_service_test.ts (93%) rename server/test/{features => services}/stack_service_test.ts (92%) rename server/test/{features => services}/trigger_service_test.ts (93%) create mode 100644 server/test/usecases/create_database_model_usecase_test.ts create mode 100644 server/test/usecases/delete_database_model_usecase_test.ts create mode 100644 server/test/usecases/pagination_database_model_usecase_test.ts create mode 100644 server/test/usecases/read_database_model_usecase_test.ts create mode 100644 server/test/usecases/update_database_model_usecase.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index e2c0be0..97c3e4c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,20 +1,24 @@ { "files.exclude": { - "**/.git": false, - "**/.svn": false, - "**/.hg": false, - "**/CVS": false, - "**/__pycache__": false, - "test/*context.*": false, - "test/*mocks": false, - "test/*mocks.*": false, - "test/*test": false, - "test/*test.*": false, - "test/*test.js": false, - "test/*test.js.*": false, - "test/*todo": false, - "test/*todo.*": false, - "**/*.js": true, - "**/*.map": true + "server/build/src/core/*controllers.*": true, + "server/build/src/core/*di": true, + "server/build/src/core/*di.*": true, + "server/build/src/core/*extensions": true, + "server/build/src/core/*extensions.*": true, + "server/build/src/core/*helper": true, + "server/build/src/core/*helper.*": true, + "server/build/src/core/*interfaces": true, + "server/build/src/core/*interfaces.*": true, + "server/build/src/core/*middlewares": true, + "server/build/src/core/*middlewares.*": true, + "server/build/src/core/*model": true, + "server/build/src/core/*model.*": true, + "server/build/src/core/*services": true, + "server/build/src/core/*services.*": true, + "server/build/src/core/*usecases": true, + "server/build/src/core/*usecases.*": true, + "server/src/core/model/exec_error_model.js": true, + "**/*.map": true, + "**/*.js": true } } \ No newline at end of file diff --git a/server/.gitignore b/server/.gitignore index 4d4a8bd..64a820b 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -6,3 +6,5 @@ node_modules/ coverage package-lock.json .*.swp +build/ +model_create.ts \ No newline at end of file diff --git a/server/package.json b/server/package.json index 3a35aae..868174c 100644 --- a/server/package.json +++ b/server/package.json @@ -1,18 +1,20 @@ { "name": "step-by-step-server", "version": "0.0.1", - "type": "module", "description": "", "main": "index.js", "scripts": { "pretest": "tsc", - "test": "node ./test/test.js", - "test:watch": "tsc-watch --onSuccess 'node ./test/test.js'" + "test": "ts-node ./build/test/test.js", + "test:watch": "tsc-watch --onSuccess 'ts-node ./build/test/test.js'", + "dev": "tsc-watch --onSuccess 'ts-node ./build/src/main.js'" }, "author": "IDONTSUDO", "devDependencies": { "@testdeck/mocha": "latest", "@types/chai": "latest", + "@types/cors": "^2.8.14", + "@types/express": "^4.17.18", "@types/md5": "^2.3.2", "@types/mocha": "latest", "@types/node": "^20.4.8", @@ -23,20 +25,30 @@ "mocha": "latest", "nyc": "latest", "source-map-support": "latest", + "ts-node": "^10.9.1", "tslint": "latest", "typescript": "^5.1.6" }, "dependencies": { "@grpc/grpc-js": "^1.9.0", "babel-register": "^6.26.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0", "concurrently": "^8.2.0", + "cors": "^2.8.5", + "express": "^4.18.2", "first-di": "^1.0.11", "md5": "^2.3.0", + "mongoose": "^7.6.2", + "mongoose-autopopulate": "^1.1.0", "node-watch": "^0.7.4", "nodemon": "^3.0.1", "reflect-metadata": "^0.1.13", + "socket.io": "^4.7.2", + "socket.io-client": "^4.7.2", "spark-md5": "^3.0.2", "ts-md5": "^1.3.1", - "tsc-watch": "^6.0.4" + "tsc-watch": "^6.0.4", + "typedi": "^0.10.0" } } diff --git a/server/src/core/controllers/app.ts b/server/src/core/controllers/app.ts new file mode 100644 index 0000000..d868669 --- /dev/null +++ b/server/src/core/controllers/app.ts @@ -0,0 +1,82 @@ +import express from "express"; +import { Routes } from "../interfaces/router"; +import cors from "cors"; +import locator from "../di/register_di"; +import { DevEnv, UnitTestEnv } from "../di/env"; +import mongoose from "mongoose"; +import http from "http"; +// import { Server } from "socket.io"; + +export class App { + public app: express.Application; + public port: number; + public env: string; + public computedFolder: string; + // public io: + constructor(routes: Routes[], computedFolder: string) { + this.port = 3000; + this.env = "dev"; + this.loadAppDependencies().then(() => { + this.app = express(); + this.initializeMiddlewares(); + this.initializeRoutes(routes); + this.computedFolder = computedFolder; + }); + } + + public listen() { + const httpServer = new http.Server(this.app); + // const io = new Server(httpServer); + + httpServer.listen(this.port, () => { + console.info(`=================================`); + console.info(`======= ENV: ${this.env} =======`); + console.info(`🚀 HTTP http://localhost:${this.port}`); + console.info(`🚀 WS ws://localhost:${this.port}`); + console.info(`=================================`); + }); + // io.on("connection", (socket) => { + // socket.on("disconnect", function (msg) { + // console.log("Disconnected"); + // }); + // }); + + // setInterval(function () { + // io.emit("goodbye"); + // console.log(200); + // }, 1000); + } + + public getServer() { + return this.app; + } + + private initializeMiddlewares() { + this.app.use(cors()); + this.app.use(express.json()); + this.app.use(express.urlencoded({ extended: true })); + } + + private initializeRoutes(routes: Routes[]) { + routes.forEach((route) => { + this.app.use("/", route.router); + }); + } + async loadAppDependencies() { + await locator( + this.env == "development" + ? new DevEnv(this.computedFolder) + : new UnitTestEnv(this.computedFolder) + ); + + mongoose + .connect("mongodb://127.0.0.1:27017/test") + .then(() => console.log("Connected!")) + .catch((e) => { + console.log("ERROR:", e); + }); + } +} + + + \ No newline at end of file diff --git a/server/src/core/controllers/crud_controller.ts b/server/src/core/controllers/crud_controller.ts new file mode 100644 index 0000000..d2ebacc --- /dev/null +++ b/server/src/core/controllers/crud_controller.ts @@ -0,0 +1,34 @@ +import { IRouteModel } from "../interfaces/router"; +import { CreateDataBaseModelUseCase } from "../usecases/create_database_model_usecase"; +import { DeleteDataBaseModelUseCase } from "../usecases/delete_database_model_usecase"; +import { PaginationDataBaseModelUseCase } from "../usecases/pagination_database_model_usecase"; +import { UpdateDataBaseModelUseCase } from "../usecases/update_database_model_usecase"; + +import { CoreHttpController } from "./http_controller"; +import mongoose from "mongoose"; + +export class CrudController extends CoreHttpController { + dataBaseModel: mongoose.Model; + + constructor(routerModel: IRouteModel) { + super(routerModel); + this.url = "/" + routerModel.url; + this.validationModel = routerModel.validationModel; + this.dataBaseModel = routerModel.databaseModel; + this.init(); + } + init() { + this.routes["POST"] = new CreateDataBaseModelUseCase( + this.dataBaseModel + ).call; + this.routes["GET"] = new PaginationDataBaseModelUseCase( + this.dataBaseModel + ).call; + this.routes["DELETE"] = new DeleteDataBaseModelUseCase( + this.dataBaseModel + ).call; + this.routes["PUT"] = new UpdateDataBaseModelUseCase( + this.dataBaseModel + ).call; + } +} diff --git a/server/src/core/controllers/http_controller.ts b/server/src/core/controllers/http_controller.ts new file mode 100644 index 0000000..55fbce9 --- /dev/null +++ b/server/src/core/controllers/http_controller.ts @@ -0,0 +1,107 @@ +import { validationModelMiddleware } from "../middlewares/validation_model"; +import { Result } from "../helper/result"; +import { Router, Request, Response } from "express"; +import { IRouteModel, Routes } from "../interfaces/router"; + +export type CallBackFunction = (a: T) => Promise>; + +abstract class ICoreHttpController { + abstract url: string; + public router = Router(); + abstract call(): Routes; +} + +export class CoreHttpController implements ICoreHttpController { + url: string; + validationModel: any; + + routes = { + POST: null, + GET: null, + DELETE: null, + PUT: null, + }; + + public router = Router(); + + constructor(routerModel: IRouteModel) { + this.url = "/" + routerModel.url; + this.validationModel = routerModel.validationModel; + } + + call(): Routes { + if (this.routes["POST"] != null) { + this.router.post( + this.url, + validationModelMiddleware(this.validationModel), + (req, res) => + this.requestResponseController(req, res, this.routes["POST"]) + ); + } + if (this.routes["DELETE"] != null) { + this.router.delete(this.url, (req, res) => + this.requestResponseController(req, res, this.routes["DELETE"]) + ); + } + if (this.routes["PUT"] != null) { + this.router.put( + this.url, + validationModelMiddleware(this.validationModel), + (req, res) => + this.requestResponseController(req, res, this.routes["PUT"]) + ); + } + if (this.routes["GET"] != null) { + this.router.get(this.url, (req, res) => + this.requestResponseController(req, res, this.routes["GET"]) + ); + } + + return { + router: this.router, + }; + } + public put(usecase: CallBackFunction) { + this.routes["PUT"] = usecase; + } + public delete(usecase: CallBackFunction) { + this.routes["DELETE"] = usecase; + } + private async requestResponseController( + req: Request, + res: Response, + usecase: CallBackFunction + ) { + let payload = null; + + if (req["model"] != undefined) { + payload = req.body as T; + } + + if (req.query.page !== undefined) { + payload = String(req.query.page); + } + + if (req.query.id !== undefined) { + payload = String(req.query.id); + } + + (await usecase(payload)).fold( + (ok) => { + res.json(ok); + return; + }, + (err) => { + res.status(400).json(err); + return; + } + ); + } + public post(usecase: CallBackFunction) { + this.routes["POST"] = usecase; + } + + public get(usecase: CallBackFunction) { + this.routes["GET"] = usecase; + } +} diff --git a/server/src/core/controllers/socket_controller.ts b/server/src/core/controllers/socket_controller.ts new file mode 100644 index 0000000..9e20248 --- /dev/null +++ b/server/src/core/controllers/socket_controller.ts @@ -0,0 +1,15 @@ +// import path from "path"; +// import { TypedEvent } from "../helper/typed_event"; +// import { StackService } from "../services/stack_service"; +// // TODO(IDONTSUDO): up to do + +// class SocketController{ +// emitter:TypedEvent; +// constructor(emitter:TypedEvent, ){ +// this.emitter = emitter +// } +// call = () =>{ + +// } +// } + \ No newline at end of file diff --git a/server/src/core/di/env.ts b/server/src/core/di/env.ts index 8082916..55841e6 100644 --- a/server/src/core/di/env.ts +++ b/server/src/core/di/env.ts @@ -1,6 +1,6 @@ -import { reflection } from "first-di"; +import { Service } from "typedi"; -@reflection +@Service() export class IEnv{ rootFolder!: string; constructor(){ @@ -14,7 +14,7 @@ export class IEnv{ } } -@reflection +@Service() export class DevEnv implements IEnv { rootFolder:string; constructor(rootFolder:string){ @@ -28,8 +28,7 @@ export class DevEnv implements IEnv { } } - -@reflection +@Service() export class UnitTestEnv implements IEnv{ rootFolder:string; constructor(rootFolder:string){ diff --git a/server/src/core/di/register_di.ts b/server/src/core/di/register_di.ts index b08da3a..77c6b6d 100644 --- a/server/src/core/di/register_di.ts +++ b/server/src/core/di/register_di.ts @@ -1,7 +1,6 @@ -import { override } from "first-di"; -import { DevEnv, IEnv, UnitTestEnv } from "./env.js"; -import { MetaDataFileManagerModel } from "../model/meta_data_file_manager_model.js"; -import { extensions } from "../extensions/extensions.js"; +import { DevEnv, IEnv, UnitTestEnv } from "./env"; +import { extensions } from "../extensions/extensions"; +// import { Container, Service } from 'typedi'; export default function locator(env: IEnv) { extensions(); @@ -9,16 +8,16 @@ export default function locator(env: IEnv) { registerRepository(env); registerController(env); registerService(env); - override(MetaDataFileManagerModel, MetaDataFileManagerModel); + // override(MetaDataFileManagerModel, MetaDataFileManagerModel); } const envRegister = (env: IEnv) => { switch (env.toStringEnv()) { case UnitTestEnv.env(): - override(IEnv, UnitTestEnv); + // override(IEnv, UnitTestEnv); return; case "DevEnv": - override(IEnv, DevEnv); + // override(IEnv, DevEnv); return; } }; @@ -26,11 +25,11 @@ const envRegister = (env: IEnv) => { const registerRepository = (env: IEnv) => { switch (env.toStringEnv()) { case UnitTestEnv.env(): - override(IEnv, UnitTestEnv); + // override(IEnv, UnitTestEnv); return; case DevEnv.env(): - override(IEnv, DevEnv); + // override(IEnv, DevEnv); return; } }; diff --git a/server/src/core/extensions/extensions.ts b/server/src/core/extensions/extensions.ts index 0a7d97a..c02556b 100644 --- a/server/src/core/extensions/extensions.ts +++ b/server/src/core/extensions/extensions.ts @@ -1,5 +1,6 @@ -import { ArrayEquals } from "./array.js"; +import { ArrayEquals } from "./array"; export const extensions = () =>{ ArrayEquals() -} \ No newline at end of file +} + \ No newline at end of file diff --git a/server/src/core/helper/cancelable_promise.ts b/server/src/core/helper/cancelable_promise.ts index 86d59be..0519ecb 100644 --- a/server/src/core/helper/cancelable_promise.ts +++ b/server/src/core/helper/cancelable_promise.ts @@ -1,248 +1 @@ -const toStringTag: typeof Symbol.toStringTag = - typeof Symbol !== 'undefined' ? Symbol.toStringTag : ('@@toStringTag' as any); - -class CancelablePromiseInternal { - #internals: Internals; - #promise: Promise; - - [toStringTag] = 'CancelablePromise'; - - constructor({ - executor = () => {}, - internals = defaultInternals(), - promise = new Promise((resolve, reject) => - executor(resolve, reject, (onCancel) => { - internals.onCancelList.push(onCancel); - }) - ), - }: { - executor?: ( - resolve: (value: T | PromiseLike) => void, - reject: (reason?: any) => void, - onCancel: (cancelHandler: () => void) => void - ) => void; - internals?: Internals; - promise?: Promise; - }) { - this.cancel = this.cancel.bind(this); - this.#internals = internals; - this.#promise = - promise || - new Promise((resolve, reject) => - executor(resolve, reject, (onCancel) => { - internals.onCancelList.push(onCancel); - }) - ); - } - - then( - onfulfilled?: - | (( - value: T - ) => TResult1 | PromiseLike | CancelablePromise) - | undefined - | null, - onrejected?: - | (( - reason: any - ) => TResult2 | PromiseLike | CancelablePromise) - | undefined - | null - ): CancelablePromise { - return makeCancelable( - this.#promise.then( - createCallback(onfulfilled, this.#internals), - createCallback(onrejected, this.#internals) - ), - this.#internals - ); - } - - catch( - onrejected?: - | (( - reason: any - ) => TResult | PromiseLike | CancelablePromise) - | undefined - | null - ): CancelablePromise { - return makeCancelable( - this.#promise.catch(createCallback(onrejected, this.#internals)), - this.#internals - ); - } - - finally( - onfinally?: (() => void) | undefined | null, - runWhenCanceled?: boolean - ): CancelablePromise { - if (runWhenCanceled) { - this.#internals.onCancelList.push(onfinally); - } - return makeCancelable( - this.#promise.finally( - createCallback(() => { - if (onfinally) { - if (runWhenCanceled) { - this.#internals.onCancelList = - this.#internals.onCancelList.filter( - (callback) => callback !== onfinally - ); - } - return onfinally(); - } - }, this.#internals) - ), - this.#internals - ); - } - - cancel(): void { - this.#internals.isCanceled = true; - const callbacks = this.#internals.onCancelList; - this.#internals.onCancelList = []; - for (const callback of callbacks) { - if (typeof callback === 'function') { - try { - callback(); - } catch (err) { - console.error(err); - } - } - } - } - - isCanceled(): boolean { - return this.#internals.isCanceled === true; - } -} - -export class CancelablePromise extends CancelablePromiseInternal { - static all = function all(iterable: any) { - return makeAllCancelable(iterable, Promise.all(iterable)); - } as CancelablePromiseOverloads['all']; - - static allSettled = function allSettled(iterable: any) { - return makeAllCancelable(iterable, Promise.allSettled(iterable)); - } as CancelablePromiseOverloads['allSettled']; - - static any = function any(iterable: any) { - return makeAllCancelable(iterable, Promise.any(iterable)); - } as CancelablePromiseOverloads['any']; - - static race = function race(iterable) { - return makeAllCancelable(iterable, Promise.race(iterable)); - } as CancelablePromiseOverloads['race']; - - static resolve = function resolve(value) { - return cancelable(Promise.resolve(value)); - } as CancelablePromiseOverloads['resolve']; - - static reject = function reject(reason) { - return cancelable(Promise.reject(reason)); - } as CancelablePromiseOverloads['reject']; - - static isCancelable = isCancelablePromise; - - constructor( - executor: ( - resolve: (value: T | PromiseLike) => void, - reject: (reason?: any) => void, - onCancel: (cancelHandler: () => void) => void - ) => void - ) { - super({ executor }); - } -} - -export default CancelablePromise; - -export function cancelable(promise: Promise): CancelablePromise { - return makeCancelable(promise, defaultInternals()); -} - -export function isCancelablePromise(promise: any): boolean { - return ( - promise instanceof CancelablePromise || - promise instanceof CancelablePromiseInternal - ); -} - -function createCallback(onResult: any, internals: Internals):any { - if (onResult) { - return (arg?: any) => { - if (!internals.isCanceled) { - const result = onResult(arg); - if (isCancelablePromise(result)) { - internals.onCancelList.push(result.cancel); - } - return result; - } - return arg; - }; - } -} - -function makeCancelable(promise: Promise, internals: Internals) { - return new CancelablePromiseInternal({ - internals, - promise, - }) as CancelablePromise; -} - -function makeAllCancelable(iterable: any, promise: Promise) { - const internals = defaultInternals(); - internals.onCancelList.push(() => { - for (const resolvable of iterable) { - if (isCancelablePromise(resolvable)) { - resolvable.cancel(); - } - } - }); - return new CancelablePromiseInternal({ internals, promise }); -} - -function defaultInternals(): Internals { - return { isCanceled: false, onCancelList: [] }; -} - -interface Internals { - isCanceled: boolean; - onCancelList: any[]; -} - -interface CancelablePromiseOverloads { - all( - values: T - ): CancelablePromise<{ -readonly [P in keyof T]: Awaited }>; - - allSettled( - values: T - ): CancelablePromise<{ - -readonly [P in keyof T]: PromiseSettledResult>; - }>; - - allSettled( - values: Iterable | CancelablePromise> - ): CancelablePromise>[]>; - - any( - values: T - ): CancelablePromise>; - - any( - values: Iterable | CancelablePromise> - ): CancelablePromise>; - - race( - values: T - ): CancelablePromise>; - - resolve(): CancelablePromise; - - resolve( - value: T | PromiseLike | CancelablePromise - ): CancelablePromise; - - reject(reason?: any): CancelablePromise; -} \ No newline at end of file + \ No newline at end of file diff --git a/server/src/core/helper/worker_computed.ts b/server/src/core/helper/worker_computed.ts index 3d19686..5d1a04e 100644 --- a/server/src/core/helper/worker_computed.ts +++ b/server/src/core/helper/worker_computed.ts @@ -1,6 +1,6 @@ -import { EXEC_EVENT, EXEC_TYPE, ExecError } from "../model/exec_error_model.js"; +import { EXEC_EVENT, EXEC_TYPE, ExecError } from "../model/exec_error_model"; import * as cp from "child_process"; -import { ExecutorResult } from "../model/executor_result.js"; +import { ExecutorResult } from "../model/executor_result"; export enum WorkerType { EXEC = "EXEC", diff --git a/server/src/core/interfaces/payload.ts b/server/src/core/interfaces/payload.ts new file mode 100644 index 0000000..f99acbc --- /dev/null +++ b/server/src/core/interfaces/payload.ts @@ -0,0 +1,14 @@ + +// export class Payload{ +// 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 +// } +// } \ No newline at end of file diff --git a/server/src/core/interfaces/response.ts b/server/src/core/interfaces/response.ts new file mode 100644 index 0000000..1efee99 --- /dev/null +++ b/server/src/core/interfaces/response.ts @@ -0,0 +1,3 @@ +export interface ICreateObjectDataBase { + id: string; +} diff --git a/server/src/core/interfaces/router.ts b/server/src/core/interfaces/router.ts new file mode 100644 index 0000000..ad4dbd4 --- /dev/null +++ b/server/src/core/interfaces/router.ts @@ -0,0 +1,14 @@ +import { Router } from "express"; + +export interface Routes { + router: Router; +} + +export interface IRouteModel { + validationModel: any; + url: string; + databaseModel: any; +} + + + \ No newline at end of file diff --git a/server/src/core/middlewares/validation_auth.ts b/server/src/core/middlewares/validation_auth.ts new file mode 100644 index 0000000..d044c30 --- /dev/null +++ b/server/src/core/middlewares/validation_auth.ts @@ -0,0 +1,12 @@ +// import { RequestHandler } from "express"; + +// export const validationMiddleware = ( +// type: any, +// value = 'body', +// skipMissingProperties = false, +// whitelist = true, +// forbidNonWhitelisted = true, +// ): RequestHandler => { + + +// } \ No newline at end of file diff --git a/server/src/core/middlewares/validation_model.ts b/server/src/core/middlewares/validation_model.ts new file mode 100644 index 0000000..5a58d72 --- /dev/null +++ b/server/src/core/middlewares/validation_model.ts @@ -0,0 +1,30 @@ +import { plainToInstance } from 'class-transformer'; +import { validate, ValidationError } from 'class-validator'; +import { RequestHandler } from 'express'; + +export const validationModelMiddleware = ( + type: any, + value = 'body', + skipMissingProperties = false, + whitelist = true, + forbidNonWhitelisted = true, +): RequestHandler => { + return (req, res, next) => { + if(type === null && type == undefined){ + next() + return + } + const model = plainToInstance(type, req[value]); + validate(model, { skipMissingProperties, whitelist, forbidNonWhitelisted }).then((errors: ValidationError[]) => { + console.log(errors) + if (errors.length > 0) { + const message = errors.map((error: ValidationError) => Object.values(error.constraints)).join(', '); + return res.status(400).json(message) + } else { + req['model'] = model + next(); + } + }); + }; +}; + diff --git a/server/src/core/model/executor_result.ts b/server/src/core/model/executor_result.ts index d3c632f..8df94ab 100644 --- a/server/src/core/model/executor_result.ts +++ b/server/src/core/model/executor_result.ts @@ -1,19 +1,18 @@ -import { EXEC_EVENT, EXEC_TYPE } from "./exec_error_model.js"; +import { EXEC_EVENT, EXEC_TYPE } from "./exec_error_model"; - export class ExecutorResult { - type: EXEC_TYPE; - event: EXEC_EVENT; - data: any; - constructor(type: EXEC_TYPE, event: EXEC_EVENT, data: any) { - this.type = type; - this.event = event; - this.data = data; + type: EXEC_TYPE; + event: EXEC_EVENT; + data: any; + constructor(type: EXEC_TYPE, event: EXEC_EVENT, data: any) { + this.type = type; + this.event = event; + this.data = data; + } + static isExecutorResult(value: any): void | ExecutorResult { + if ("type" in value && "event" in value && "data" in value) { + return new ExecutorResult(value.type, value.event, value.data); } - static isExecutorResult(value: any): void | ExecutorResult { - if ("type" in value && "event" in value && "data" in value) { - return new ExecutorResult(value.type, value.event, value.data); - } - return; - } - } \ No newline at end of file + return; + } +} diff --git a/server/src/core/model/process_model.ts b/server/src/core/model/process_model.ts index da2239b..e5fa05f 100644 --- a/server/src/core/model/process_model.ts +++ b/server/src/core/model/process_model.ts @@ -1,16 +1,16 @@ -import { EXEC_TYPE } from "./exec_error_model.js"; +import { Trigger } from "../../features/triggers/trigger_model"; +import { EXEC_TYPE } from "./exec_error_model"; - -export interface ProcessMetaData { - process: Process; +export interface IPipeline { + process: IProcess; trigger: Trigger; env: Env | null; - stackGenerateType:StackGenerateType; + stackGenerateType: StackGenerateType; } -export enum StackGenerateType{ - MAP = 'MAP', - SINGLETON = 'SINGLETON' +export enum StackGenerateType { + MAP = "MAP", + SINGLETON = "SINGLETON", } export interface Env { @@ -19,26 +19,17 @@ export interface Env { isExtends: string; } -export interface Process { - type: EXEC_TYPE; +export interface IProcess { + type: EXEC_TYPE; command: string; isGenerating: boolean; isLocaleCode: boolean; issueType: IssueType; timeout?: number; - commit?:string | undefined; + commit?: string | undefined; } export enum IssueType { WARNING = "WARNING", ERROR = "ERROR", } - -export enum TriggerType { - PROCESS = "PROCESS", - FILE = "FILE", -} -export interface Trigger { - type: TriggerType; - value: string[]; -} diff --git a/server/src/core/services/executor_program_service.ts b/server/src/core/services/executor_program_service.ts index a6f89e2..6107d7d 100644 --- a/server/src/core/services/executor_program_service.ts +++ b/server/src/core/services/executor_program_service.ts @@ -1,10 +1,10 @@ import cluster, { Worker } from "node:cluster"; -import { TypedEvent } from "../helper/typed_event.js"; -import { Result } from "../helper/result.js"; -import { WorkerDataExec, WorkerType } from "../helper/worker_computed.js"; -import { delay } from "../helper/delay.js"; -import { ExecutorResult } from "../model/executor_result.js"; -import { EXEC_TYPE, ExecError, SpawnError } from "../model/exec_error_model.js"; +import { TypedEvent } from "../helper/typed_event"; +import { Result } from "../helper/result"; +import { WorkerDataExec, WorkerType } from "../helper/worker_computed"; +import { delay } from "../helper/delay"; +import { ExecutorResult } from "../model/executor_result"; +import { EXEC_TYPE, ExecError, SpawnError } from "../model/exec_error_model"; abstract class IExecutorProgramService { abstract execPath: string; @@ -31,7 +31,7 @@ export class ExecutorProgramService args: Array | undefined = undefined ) { cluster.setupPrimary({ - exec: "./src/core/helper/worker_computed.js", + exec: "./src/core/helper/worker_computed", }); const worker = cluster.fork(); diff --git a/server/src/core/services/files_change_notifier_service.ts b/server/src/core/services/files_change_notifier_service.ts index 33a3615..af0d397 100644 --- a/server/src/core/services/files_change_notifier_service.ts +++ b/server/src/core/services/files_change_notifier_service.ts @@ -7,16 +7,16 @@ import { BinaryLike } from "crypto"; import { EventsFileChanger, MetaDataFileManagerModel, -} from "../model/meta_data_file_manager_model.js"; -import { Result } from "../helper/result.js"; -import { TypedEvent } from "../helper/typed_event.js"; +} from "../model/meta_data_file_manager_model"; +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, delimiter = " ") { +function joinBuffers(buffers: Array, delimiter = " ") { const d = Buffer.from(delimiter); return buffers.reduce((prev, b) => Buffer.concat([prev, d, b])); } @@ -44,8 +44,6 @@ export interface IHashesCache { [key: string]: MetaDataFileManagerModel; } - - export abstract class IFilesChangeNotifierService { abstract directory: string; } diff --git a/server/src/core/services/stack_service.ts b/server/src/core/services/stack_service.ts index a5874c3..ceb6fbf 100644 --- a/server/src/core/services/stack_service.ts +++ b/server/src/core/services/stack_service.ts @@ -1,43 +1,40 @@ import { FilesChangeNotifierService, IHashesCache, -} from "./files_change_notifier_service.js"; -import { ProcessMetaData, Trigger } from "../model/process_model.js"; -import { ExecutorProgramService } from "./executor_program_service.js"; -import { - EXEC_EVENT, - ExecError, - SpawnError, -} from "../model/exec_error_model.js"; -import { TypedEvent } from "../helper/typed_event.js"; -import { Result } from "../helper/result.js"; -import { ExecutorResult } from "../model/executor_result.js"; -import { delay } from "../helper/delay.js"; -import { TriggerErrorReport, TriggerService } from "./trigger_service.js"; +} from "./files_change_notifier_service"; +import { IPipeline } from "../model/process_model"; +import { ExecutorProgramService } from "./executor_program_service"; +import { EXEC_EVENT, ExecError, SpawnError } from "../model/exec_error_model"; +import { TypedEvent } from "../helper/typed_event"; +import { Result } from "../helper/result"; +import { ExecutorResult } from "../model/executor_result"; +import { delay } from "../helper/delay"; +import { TriggerService } from "./trigger_service"; +import { Trigger } from "../../features/triggers/trigger_model"; export interface Iteration { hashes: IHashesCache | null; - process: ProcessMetaData; + process: IPipeline; result?: ExecError | SpawnError | ExecutorResult; } export abstract class IStackService { abstract callStack: Iteration[]; abstract path: string; - abstract init(processed: ProcessMetaData[], path: string): void; + abstract init(processed: IPipeline[], path: string): void; } export class StackService extends TypedEvent implements IStackService { callStack: Iteration[]; path: string; - constructor(processed: ProcessMetaData[], path: string) { + constructor(processed: IPipeline[], path: string) { super(); this.path = path; this.callStack = []; this.init(processed); } - public init(processed: ProcessMetaData[]) { + public init(processed: IPipeline[]) { for (let el of processed) { el = this.commandHandler(el); this.callStack.push({ @@ -46,7 +43,7 @@ export class StackService extends TypedEvent implements IStackService { }); } } - private commandHandler(processMetaData: ProcessMetaData) { + private commandHandler(processMetaData: IPipeline) { processMetaData.process.command = processMetaData.process.command.replace( "$PATH", this.path @@ -91,10 +88,10 @@ export class StackService extends TypedEvent implements IStackService { ); triggerResult.fold( (s) => { - s + s; }, (e) => { - e; + e; } ); } diff --git a/server/src/core/services/trigger_service.ts b/server/src/core/services/trigger_service.ts index 581d96d..0c7b990 100644 --- a/server/src/core/services/trigger_service.ts +++ b/server/src/core/services/trigger_service.ts @@ -1,9 +1,9 @@ -import { Trigger, TriggerType } from "../model/process_model.js"; import * as vm from "node:vm"; -import { IHashesCache } from "./files_change_notifier_service.js"; -import { EventsFileChanger } from "../model/meta_data_file_manager_model.js"; -import { Result } from "../helper/result.js"; -import { TypedEvent } from "../helper/typed_event.js"; +import { IHashesCache } from "./files_change_notifier_service"; +import { EventsFileChanger } from "../model/meta_data_file_manager_model"; +import { Result } from "../helper/result"; +import { TypedEvent } from "../helper/typed_event"; +import { Trigger, TriggerType } from "../../features/triggers/trigger_model"; export class TriggerCallResult { results: Array; @@ -47,7 +47,7 @@ export class TriggerErrorReport extends Error { } } export class TriggerService extends TypedEvent { - context = {}; + context: any = {}; constructor(trigger: Trigger, hashes: IHashesCache, path: string) { super(); @@ -61,8 +61,11 @@ export class TriggerService extends TypedEvent { path: string; hashes: IHashesCache; trigger: Trigger; + private init(): void { - this.context["hashes"] = this.hashes; + if (this.context["hashes"] != undefined) { + this.context["hashes"] = this.hashes; + } } private getAllHashesDeleteWithouts(): string[] { return Object.entries(this.hashes).map(([k, v]) => { diff --git a/server/src/core/usecases/create_database_model_usecase.ts b/server/src/core/usecases/create_database_model_usecase.ts new file mode 100644 index 0000000..a73d071 --- /dev/null +++ b/server/src/core/usecases/create_database_model_usecase.ts @@ -0,0 +1,22 @@ +import { Result } from "../helper/result"; +import { ICreateObjectDataBase } from "../interfaces/response"; + +export class CreateDataBaseModelUseCase { + databaseModel: any; + + constructor(model) { + this.databaseModel = model; + } + + call = async ( + validationModel: V + ): Promise> => { + try { + const result = new this.databaseModel(validationModel); + + return Result.ok({ id: String((await result.save())._id) }); + } catch (error) { + return Result.error(error); + } + }; +} diff --git a/server/src/core/usecases/delete_database_model_usecase.ts b/server/src/core/usecases/delete_database_model_usecase.ts new file mode 100644 index 0000000..abc97a6 --- /dev/null +++ b/server/src/core/usecases/delete_database_model_usecase.ts @@ -0,0 +1,18 @@ + import { Result } from "../helper/result"; + +export class DeleteDataBaseModelUseCase { + databaseModel: D | any; + constructor(model) { + this.databaseModel = model; + } + call = async (id: string): Promise> => { + try { + const model = new this.databaseModel({ _id: id }); + await model.deleteOne(); + + return Result.ok(true); + } catch (error) { + return Result.error(error); + } + }; +} diff --git a/server/src/core/usecases/pagination_database_model_usecase.ts b/server/src/core/usecases/pagination_database_model_usecase.ts new file mode 100644 index 0000000..ada4d6a --- /dev/null +++ b/server/src/core/usecases/pagination_database_model_usecase.ts @@ -0,0 +1,28 @@ +import { Result } from "../helper/result"; + +export class PaginationDataBaseModelUseCase { + databaseModel: D; + perPage: number; + + constructor(model: any, perPage = 10) { + this.databaseModel = model; + this.perPage = perPage; + } + + call = async ( + pageNumber: number + ): Promise> => { + try { + const page = Math.max(0, pageNumber); + const model = this.databaseModel as any; + return Result.ok( + await model + .find() + .limit(this.perPage) + .skip(this.perPage * page) + ); + } catch (error) { + return Result.error(error); + } + }; +} diff --git a/server/src/core/usecases/read_database_model_usecase.ts b/server/src/core/usecases/read_database_model_usecase.ts new file mode 100644 index 0000000..6786b80 --- /dev/null +++ b/server/src/core/usecases/read_database_model_usecase.ts @@ -0,0 +1,20 @@ + +import { Result } from "../helper/result"; + +export class ReadByIdDataBaseModelUseCase { + databaseModel: D; + + constructor(model) { + this.databaseModel = model; + } + call = async (id: string): Promise> => { + try { + const r = this.databaseModel as any; + + const model = await r.findById(id); + return Result.ok(model); + } catch (error) { + return Result.error(error); + } + }; +} diff --git a/server/src/core/usecases/update_database_model_usecase.ts b/server/src/core/usecases/update_database_model_usecase.ts new file mode 100644 index 0000000..a35b788 --- /dev/null +++ b/server/src/core/usecases/update_database_model_usecase.ts @@ -0,0 +1,27 @@ +import { Result } from "../helper/result"; + +interface uuid { + _id?: string; +} + +export class UpdateDataBaseModelUseCase { + databaseModel: D; + constructor(databaseModel) { + this.databaseModel = databaseModel; + } + + call = async (updateModel: T): Promise> => { + try { + if (updateModel["_id"] === undefined) { + return Result.error(new Error("need _id at model body")); + } + const databaseModel = this.databaseModel as any; + const model = await databaseModel.findById(updateModel._id); + Object.assign(model, updateModel); + await model.save(); + return Result.ok(model as T); + } catch (error) { + return Result.error(error); + } + }; +} diff --git a/server/src/features/pipelines/pipeline_model.ts b/server/src/features/pipelines/pipeline_model.ts new file mode 100644 index 0000000..3409dd0 --- /dev/null +++ b/server/src/features/pipelines/pipeline_model.ts @@ -0,0 +1,47 @@ +import { IsMongoId, IsEnum } from "class-validator"; +import { Schema, model } from "mongoose"; +import { StackGenerateType } from "../../core/model/process_model"; +import { + TriggerModel, + triggerSchema, +} from "../triggers/trigger_model"; +import { schemaProcess } from "../process/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( + 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; +} diff --git a/server/src/features/pipelines/pipeline_presentation.ts b/server/src/features/pipelines/pipeline_presentation.ts new file mode 100644 index 0000000..6476392 --- /dev/null +++ b/server/src/features/pipelines/pipeline_presentation.ts @@ -0,0 +1,16 @@ +import { CrudController } from "../../core/controllers/crud_controller"; +import { PipelineDBModel, PipelineModel } from "./pipeline_model"; + +export class PipelinePresentation extends CrudController< + PipelineModel, + typeof PipelineDBModel +> { + constructor() { + super({ + url: "pipeline", + validationModel: PipelineModel, + databaseModel: PipelineDBModel, + }); + } + +} diff --git a/server/src/features/process/process_model.ts b/server/src/features/process/process_model.ts new file mode 100644 index 0000000..df48590 --- /dev/null +++ b/server/src/features/process/process_model.ts @@ -0,0 +1,69 @@ +import { + IsString, + IsOptional, + IsEnum, + IsNumber, + IsBoolean, +} from "class-validator"; +import { Schema, model } from "mongoose"; +import { + IProcess, + IssueType, +} from "../../core/model/process_model"; +import { EXEC_TYPE } from "../../core/model/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(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; +} + \ No newline at end of file diff --git a/server/src/features/process/process_presentation.ts b/server/src/features/process/process_presentation.ts new file mode 100644 index 0000000..b9ec912 --- /dev/null +++ b/server/src/features/process/process_presentation.ts @@ -0,0 +1,15 @@ +import { CrudController } from "../../core/controllers/crud_controller"; +import { ProcessDBModel, ProcessModel } from "./process_model"; + +export class ProcessPresentation extends CrudController< + ProcessModel, + typeof ProcessDBModel +> { + constructor() { + super({ + url: "process", + validationModel: ProcessModel, + databaseModel: ProcessDBModel, + }); + } +} diff --git a/server/src/features/projects/projects_model.ts b/server/src/features/projects/projects_model.ts new file mode 100644 index 0000000..dfbf035 --- /dev/null +++ b/server/src/features/projects/projects_model.ts @@ -0,0 +1,31 @@ +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, + ref: schemaPipeline, + autopopulate: true, + default: null, + }, + rootDir: { + type: String, + }, +}).plugin(require("mongoose-autopopulate")); + +const schema = "Projects"; + +export const ProjectDBModel = model(schema, ProjectSchema); + +export class ProjectModel implements IProjectModel { + @IsMongoId() + pipelines: [PipelineModel]; + @IsString() + rootDir: string; +} diff --git a/server/src/features/projects/projects_presentation.ts b/server/src/features/projects/projects_presentation.ts new file mode 100644 index 0000000..e9414ad --- /dev/null +++ b/server/src/features/projects/projects_presentation.ts @@ -0,0 +1,16 @@ +// import { TriggerDBModel, TriggerModel } from "./trigger_model"; +import { CrudController } from "../../core/controllers/crud_controller"; +import { ProjectDBModel, ProjectModel } from "./projects_model"; + +export class ProjectsPresentation extends CrudController< + ProjectModel, + typeof ProjectDBModel +> { + constructor() { + super({ + url: "project", + validationModel: ProjectModel, + databaseModel: ProjectDBModel, + }); + } +} diff --git a/server/src/features/triggers/trigger_model.ts b/server/src/features/triggers/trigger_model.ts new file mode 100644 index 0000000..5e25257 --- /dev/null +++ b/server/src/features/triggers/trigger_model.ts @@ -0,0 +1,43 @@ +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(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[]; +} + \ No newline at end of file diff --git a/server/src/features/triggers/triggers_presentation.ts b/server/src/features/triggers/triggers_presentation.ts new file mode 100644 index 0000000..77b135f --- /dev/null +++ b/server/src/features/triggers/triggers_presentation.ts @@ -0,0 +1,15 @@ +import { TriggerDBModel, TriggerModel } from "./trigger_model"; +import { CrudController } from "../../core/controllers/crud_controller"; + +export class TriggerPresentation extends CrudController< + TriggerModel, + typeof TriggerDBModel +> { + constructor() { + super({ + url: "trigger", + validationModel: TriggerModel, + databaseModel: TriggerDBModel, + }); + } +} diff --git a/server/src/index.ts b/server/src/index.ts deleted file mode 100644 index 5f247c2..0000000 --- a/server/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {} \ No newline at end of file diff --git a/server/src/main.ts b/server/src/main.ts new file mode 100644 index 0000000..3d844be --- /dev/null +++ b/server/src/main.ts @@ -0,0 +1,21 @@ +import "reflect-metadata"; +import { App } from "./core/controllers/app"; +import { Routes } from "./core/interfaces/router"; +import { TriggerPresentation } from "./features/triggers/triggers_presentation"; +import { ProjectsPresentation } from "./features/projects/projects_presentation"; +import { PipelinePresentation } from "./features/pipelines/pipeline_presentation"; +import { ProcessPresentation } from "./features/process/process_presentation"; + + +const httpRoutes: Routes[] = [ + new TriggerPresentation(), + new ProjectsPresentation(), + new ProcessPresentation(), + new PipelinePresentation(), +].map((el) => el.call()); + +const computedFolder = ""; + +new App(httpRoutes, computedFolder).listen(); + + \ No newline at end of file diff --git a/server/test/core/test_core.ts b/server/test/core/test_core.ts index a20f87c..8ca7373 100644 --- a/server/test/core/test_core.ts +++ b/server/test/core/test_core.ts @@ -1,6 +1,12 @@ -import { delay } from "../../src/core/helper/delay.js"; +import mongoose from "mongoose"; +import { delay } from "../../src/core/helper/delay"; import { Result } from "../../src/core/helper/result"; -import { TypedEvent } from "../../src/core/helper/typed_event.js"; +import { TypedEvent } from "../../src/core/helper/typed_event"; + + +export const before = async () =>{ + await mongoose.connection.dropDatabase() +} export class TestCore { allTests = 0; @@ -23,7 +29,7 @@ export class TestCore { console.log("\x1b[31m", "❌ - " + testName); }; - testResult = () => { + testResult = async () => { console.log("\x1b[32m", "============="); if (this.allTests - this.testOk === 0) { @@ -37,6 +43,7 @@ export class TestCore { console.log("\x1b[31m", "❌ test error:" + String(this.testErr)); console.log("\x1b[32m", `✅ test success! ${this.testOk}`); } + await before() }; resultTest = async ( eventClass: TypedEvent> | any, diff --git a/server/test/model/test_db_mongo_model.ts b/server/test/model/test_db_mongo_model.ts new file mode 100644 index 0000000..5e2f708 --- /dev/null +++ b/server/test/model/test_db_mongo_model.ts @@ -0,0 +1,15 @@ +import { Schema, model } from "mongoose"; + +export interface ITestModel{ + _id?: string; + result: string; +} + +export const TestSchema = new Schema({ + result: String, +}); + +const schema = "Test"; + +export const TestDBModel = model(schema, TestSchema); + \ No newline at end of file diff --git a/server/test/features/executor_program_service_test.ts b/server/test/services/executor_program_service_test.ts similarity index 88% rename from server/test/features/executor_program_service_test.ts rename to server/test/services/executor_program_service_test.ts index 9e4d106..8cf3849 100644 --- a/server/test/features/executor_program_service_test.ts +++ b/server/test/services/executor_program_service_test.ts @@ -1,9 +1,9 @@ -import { delay } from "../../src/core/helper/delay.js"; -import { EXEC_TYPE } from "../../src/core/model/exec_error_model.js"; -import { ExecutorResult } from "../../src/core/model/executor_result.js"; -import { ExecutorProgramService } from "../../src/core/services/executor_program_service.js"; -import { TestCore } from "../core/test_core.js"; -import { resultTest as resultTest, dirname__ } from "../test.js"; +import { delay } from "../../src/core/helper/delay"; +import { EXEC_TYPE } from "../../src/core/model/exec_error_model"; +import { ExecutorResult } from "../../src/core/model/executor_result"; +import { ExecutorProgramService } from "../../src/core/services/executor_program_service"; +import { TestCore } from "../core/test_core"; +import { resultTest as resultTest, dirname__ } from "../test"; import { Worker } from "node:cluster"; export class ExecutorProgramServiceTest extends ExecutorProgramService { @@ -19,7 +19,7 @@ export class ExecutorProgramServiceTest extends ExecutorProgramService { dirname__ + "/" ); executorProgramService.call(EXEC_TYPE.SPAWN, "node", [ - "./mocks/log_code.js", + "./mocks/log_code", ]); const test = TestCore.instance; let testIsOk = false; @@ -45,7 +45,7 @@ export class ExecutorProgramServiceTest extends ExecutorProgramService { const executorProgramService = await new ExecutorProgramService(dirname__); executorProgramService.call( EXEC_TYPE.EXEC, - "node ./test/mocks/log_code.js" + "node ./test/mocks/log_code" ); const test = TestCore.instance; executorProgramService.on((e) => { @@ -63,7 +63,7 @@ export class ExecutorProgramServiceTest extends ExecutorProgramService { const executorProgramService = await new ExecutorProgramService("", 1000); executorProgramService.call( EXEC_TYPE.EXEC, - "node ./test/mocks/long_code.js" + "node ./test/mocks/long_code" ); await delay(1500); const worker = executorProgramService.worker as Worker; @@ -73,7 +73,7 @@ export class ExecutorProgramServiceTest extends ExecutorProgramService { private resultsTests = async () => { await resultTest( new ExecutorProgramService(dirname__), - [EXEC_TYPE.EXEC, "node ./mocks/error.js"], + [EXEC_TYPE.EXEC, "node ./mocks/error"], "ExecutorProgramService EXEC_TYPE.EXEC on Result.error", false, 4000 @@ -94,7 +94,7 @@ export class ExecutorProgramServiceTest extends ExecutorProgramService { ); await resultTest( new ExecutorProgramService(dirname__), - [EXEC_TYPE.SPAWN, "python3 ./mocks/s.js"], + [EXEC_TYPE.SPAWN, "python3 ./mocks/s"], "ExecutorProgramService EXEC_TYPE.SPAWN on Result.error", false, 2000 diff --git a/server/test/features/files_change_notifier_service_test.ts b/server/test/services/files_change_notifier_service_test.ts similarity index 93% rename from server/test/features/files_change_notifier_service_test.ts rename to server/test/services/files_change_notifier_service_test.ts index e47598f..8246820 100644 --- a/server/test/features/files_change_notifier_service_test.ts +++ b/server/test/services/files_change_notifier_service_test.ts @@ -1,8 +1,8 @@ import * as fs from "fs"; -import { FilesChangeNotifierService } from "../../src/core/services/files_change_notifier_service.js"; -import { EventsFileChanger } from "../../src/core/model/meta_data_file_manager_model.js"; -import { assert, dirname__ } from "../test.js"; -import { delay } from "../../src/core/helper/delay.js"; +import { FilesChangeNotifierService } from "../../src/core/services/files_change_notifier_service"; +import { EventsFileChanger } from "../../src/core/model/meta_data_file_manager_model"; +import { assert, dirname__ } from "../test"; +import { delay } from "../../src/core/helper/delay"; export class FilesChangerTest extends FilesChangeNotifierService { directory = dirname__ + "/context/"; diff --git a/server/test/features/stack_service_test.ts b/server/test/services/stack_service_test.ts similarity index 92% rename from server/test/features/stack_service_test.ts rename to server/test/services/stack_service_test.ts index 1cf468f..12a25f8 100644 --- a/server/test/features/stack_service_test.ts +++ b/server/test/services/stack_service_test.ts @@ -4,12 +4,12 @@ import * as fs from "fs"; import { IssueType, StackGenerateType, - TriggerType, -} from "../../src/core/model/process_model.js"; -import { EXEC_TYPE } from "../../src/core/model/exec_error_model.js"; -import { StackService } from "../../src/core/services/stack_service.js"; -import { delay } from "../../src/core/helper/delay.js"; -import { assert, dirname__ } from "../test.js"; +} 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 { delay } from "../../src/core/helper/delay"; +import { assert, dirname__ } from "../test"; +import { TriggerType } from "../../src/features/triggers/trigger_model"; abstract class IStackServiceTest { abstract test(): Promise; diff --git a/server/test/features/trigger_service_test.ts b/server/test/services/trigger_service_test.ts similarity index 93% rename from server/test/features/trigger_service_test.ts rename to server/test/services/trigger_service_test.ts index 1c12595..d919256 100644 --- a/server/test/features/trigger_service_test.ts +++ b/server/test/services/trigger_service_test.ts @@ -1,10 +1,11 @@ import { EventsFileChanger, MetaDataFileManagerModel, -} from "../../src/core/model/meta_data_file_manager_model.js"; -import { TriggerType } from "../../src/core/model/process_model.js"; -import { TriggerService } from "../../src/core/services/trigger_service.js"; -import { assert } from "../test.js"; +} from "../../src/core/model/meta_data_file_manager_model"; + +import { TriggerService } from "../../src/core/services/trigger_service"; +import { TriggerType } from "../../src/features/triggers/trigger_model"; +import { assert } from "../test"; abstract class TriggerTest { abstract test(): Promise; } diff --git a/server/test/test.ts b/server/test/test.ts index 0d5f165..f29630a 100644 --- a/server/test/test.ts +++ b/server/test/test.ts @@ -1,17 +1,21 @@ -import locator from "../src/core/di/register_di.js"; -import { UnitTestEnv } from "../src/core/di/env.js"; -import { fileURLToPath } from "url"; +import { TestCore } from "./core/test_core"; +import { UnitTestEnv } from "../src/core/di/env"; import { dirname } from "path"; -import { ExecutorProgramServiceTest } from "./features/executor_program_service_test.js"; -import { FilesChangerTest } from "./features/files_change_notifier_service_test.js"; -import { TestCore } from "./core/test_core.js"; -import "reflect-metadata"; -import { StackServiceTest } from "./features/stack_service_test.js"; -import { TriggerServiceTest } from "./features/trigger_service_test.js"; +import locator from "../src/core/di/register_di"; +import { ExecutorProgramServiceTest } from "./services/executor_program_service_test"; +import { FilesChangerTest } from "./services/files_change_notifier_service_test"; +import { TriggerServiceTest } from "./services/trigger_service_test"; +import { StackServiceTest } from "./services/stack_service_test"; +import mongoose from "mongoose"; +import { CreateDataBaseModelUseCaseTest } from "./usecases/create_database_model_usecase_test"; +import { DeleteDataBaseModelUseCaseTest } from "./usecases/delete_database_model_usecase_test"; +import { ReadDataBaseModelUseCaseTest } from "./usecases/read_database_model_usecase_test"; +import { UpdateDataBaseModelUseCaseTest } from "./usecases/update_database_model_usecase"; +import { PaginationDataBaseModelUseCaseTest } from "./usecases/pagination_database_model_usecase_test"; + const testCore = TestCore.instance; -const __filename: string = fileURLToPath(import.meta.url); - + export const dirname__: string = dirname(__filename); export const assert = testCore.assert; export const resultTest = testCore.resultTest; @@ -19,11 +23,29 @@ const env = new UnitTestEnv(dirname__); locator(env); -const main = async () => { +const tests = [CreateDataBaseModelUseCaseTest, DeleteDataBaseModelUseCaseTest,ReadDataBaseModelUseCaseTest,UpdateDataBaseModelUseCaseTest, PaginationDataBaseModelUseCaseTest] +const init = async () =>{ + await mongoose.connect('mongodb://127.0.0.1:27017/test') +} + +const test = async () =>{ 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() + for await (const usecase of tests) { + testCore.assert(await new usecase().test(), usecase.name) + } +} +const main = async () => { + await init() + await test() await testCore.testResult(); }; diff --git a/server/test/usecases/create_database_model_usecase_test.ts b/server/test/usecases/create_database_model_usecase_test.ts new file mode 100644 index 0000000..735d31f --- /dev/null +++ b/server/test/usecases/create_database_model_usecase_test.ts @@ -0,0 +1,12 @@ +import { CreateDataBaseModelUseCase } from "../../src/core/usecases/create_database_model_usecase"; +import { ITestModel, TestDBModel } from "../model/test_db_mongo_model"; + +export class CreateDataBaseModelUseCaseTest { + async test() { + return ( + await new CreateDataBaseModelUseCase(TestDBModel).call({ + result: "test", + }) + ).isSuccess(); + } +} diff --git a/server/test/usecases/delete_database_model_usecase_test.ts b/server/test/usecases/delete_database_model_usecase_test.ts new file mode 100644 index 0000000..e9809cf --- /dev/null +++ b/server/test/usecases/delete_database_model_usecase_test.ts @@ -0,0 +1,29 @@ +import { CreateDataBaseModelUseCase } from "../../src/core/usecases/create_database_model_usecase"; +import { DeleteDataBaseModelUseCase } from "../../src/core/usecases/delete_database_model_usecase"; +import { ITestModel, TestDBModel } from "../model/test_db_mongo_model"; + +export class DeleteDataBaseModelUseCaseTest { + async test() { + let testIsSuccess = false; + + const result = await new CreateDataBaseModelUseCase(TestDBModel).call({ + result: "test", + }); + + await result.fold( + async (s) => { + (await new DeleteDataBaseModelUseCase(TestDBModel).call(s.id)).fold( + (_s1) => { + testIsSuccess = true; + }, + (_e1) => {} + ); + }, + async (_e) => { + return; + } + ); + + return testIsSuccess; + } +} diff --git a/server/test/usecases/pagination_database_model_usecase_test.ts b/server/test/usecases/pagination_database_model_usecase_test.ts new file mode 100644 index 0000000..48f3433 --- /dev/null +++ b/server/test/usecases/pagination_database_model_usecase_test.ts @@ -0,0 +1,19 @@ +import { PaginationDataBaseModelUseCase } from "../../src/core/usecases/pagination_database_model_usecase"; +import { ITestModel, TestDBModel } from "../model/test_db_mongo_model"; + +export class PaginationDataBaseModelUseCaseTest { + async test() { + let testIsSuccess = false; + await ( + await new PaginationDataBaseModelUseCase(TestDBModel, 1).call( + 1 + ) + ).fold( + (s) => { + testIsSuccess = s.length === 1; + }, + (_e) => {} + ); + return testIsSuccess; + } +} diff --git a/server/test/usecases/read_database_model_usecase_test.ts b/server/test/usecases/read_database_model_usecase_test.ts new file mode 100644 index 0000000..94fae6e --- /dev/null +++ b/server/test/usecases/read_database_model_usecase_test.ts @@ -0,0 +1,32 @@ +import { CreateDataBaseModelUseCase } from "../../src/core/usecases/create_database_model_usecase"; +import { ReadByIdDataBaseModelUseCase } from "../../src/core/usecases/read_database_model_usecase"; +import { ITestModel, TestDBModel } from "../model/test_db_mongo_model"; + +export class ReadDataBaseModelUseCaseTest { + async test() { + let testIsSuccess = false; + + const result = await new CreateDataBaseModelUseCase( + TestDBModel + ).call({ + result: "test", + }); + await result.fold( + async (s) => { + const r = await new ReadByIdDataBaseModelUseCase( + TestDBModel + ).call(s.id); + await r.fold( + (_s1) => { + testIsSuccess = true; + + }, + (_e) => {} + ); + }, + async (_e) => {} + ); + + return testIsSuccess; + } +} diff --git a/server/test/usecases/update_database_model_usecase.ts b/server/test/usecases/update_database_model_usecase.ts new file mode 100644 index 0000000..868fec4 --- /dev/null +++ b/server/test/usecases/update_database_model_usecase.ts @@ -0,0 +1,35 @@ +import { CreateDataBaseModelUseCase } from "../../src/core/usecases/create_database_model_usecase"; +import { UpdateDataBaseModelUseCase } from "../../src/core/usecases/update_database_model_usecase"; +import { ITestModel, TestDBModel } from "../model/test_db_mongo_model"; + +export class UpdateDataBaseModelUseCaseTest { + async test() { + let testIsSuccess = false; + + const model = await new CreateDataBaseModelUseCase( + TestDBModel + ).call({ + result: "test", + }); + await model.fold( + async (s) => { + ( + await new UpdateDataBaseModelUseCase( + TestDBModel + ).call({ + _id:s.id, + result: "complete", + }) + ).fold( + (s1) => { + testIsSuccess = s1.result === "complete"; + }, + (_e1) => {} + ); + }, + async (_e) => {} + ); + + return testIsSuccess; + } +} diff --git a/server/tsconfig.json b/server/tsconfig.json index 33bce8f..f5e853d 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -1,25 +1,19 @@ { + "compileOnSave": false, "compilerOptions": { - "target": "ES2020", - "module": "ESNext", - "removeComments": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "lib": [ - "esnext", - "dom" - ], - "moduleResolution":"node", - "sourceMap": true, - "noImplicitReturns": true, - "noUnusedParameters": true, - "pretty": true, - "strict": true, - "skipLibCheck": true, - "noImplicitAny": false - // "moduleResolution": "node" - }, - // "include": ["src/**/*.ts", "src/**/*.json", ".env"], - // "exclude": ["node_modules"] - -} \ No newline at end of file + "target": "es2017", + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "pretty": true, + "declaration": true, + "outDir": "./build", + "allowJs": true, + "noEmit": false, + "esModuleInterop": true, + "resolveJsonModule": true, + "importHelpers": true, + } + } \ No newline at end of file