From 38310be004b84d09420e1dbcd446d34e787bda6c Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 23 Feb 2022 17:46:07 +0100 Subject: [PATCH 01/72] The worker listen for pipelines changes --- worker/package.json | 7 +- worker/src/actions.ts | 5 +- worker/src/index.ts | 70 ++++++++---------- worker/src/models/pipeline.ts | 2 + worker/tsconfig.json | 1 + worker/yarn.lock | 132 +++++++++++++++++++--------------- 6 files changed, 114 insertions(+), 103 deletions(-) diff --git a/worker/package.json b/worker/package.json index 63c4ade..270c4e3 100644 --- a/worker/package.json +++ b/worker/package.json @@ -24,16 +24,13 @@ "discord-api-types": "^0.27.2", "discord.js": "^13.6.0", "express": "^4.17.2", - "express-ws": "^5.0.2", + "node-fetch": "^3.2.0", "rxjs": "^7.5.2", "spotify-web-api-js": "^1.5.2", - "twitter-api-v2": "^1.10.0", - "ws": "^8.4.2" + "twitter-api-v2": "^1.10.0" }, "devDependencies": { "@types/express": "^4.17.13", - "@types/express-ws": "^3.0.1", - "@types/ws": "^8.2.2", "ts-lint": "^4.5.1", "typescript": "^4.5.5" }, diff --git a/worker/src/actions.ts b/worker/src/actions.ts index daea33e..d0e54c1 100644 --- a/worker/src/actions.ts +++ b/worker/src/actions.ts @@ -1,6 +1,6 @@ import { catchError, groupBy, lastValueFrom, map, mergeMap, NEVER, Observable, switchAll, tap } from "rxjs"; import { BaseService } from "./models/base-service"; -import { Pipeline, PipelineEnv } from "./models/pipeline"; +import { Pipeline, PipelineEnv, PipelineType } from "./models/pipeline"; import { Runner } from "./runner"; @@ -31,6 +31,9 @@ export class Manager { } createPipeline(pipeline: Pipeline): Observable { + if (pipeline.type === PipelineType.Never) + return NEVER; + try { const service = BaseService.createService(pipeline.service, pipeline); return service.getAction(pipeline.type)(pipeline.params) diff --git a/worker/src/index.ts b/worker/src/index.ts index 7f2dafd..20a7546 100644 --- a/worker/src/index.ts +++ b/worker/src/index.ts @@ -1,49 +1,39 @@ -// import express from "express"; -// import expressWs, { Application } from "express-ws" -// import WebSocket from "ws" - -import { from } from "rxjs"; +import { fromEvent, merge, Observable } from "rxjs"; +import { fromFetch } from 'rxjs/fetch'; import { Manager } from "./actions"; -import { Pipeline, PipelineType, ReactionType, ServiceType } from "./models/pipeline"; import "./services"; +import fetch from 'node-fetch'; +import AbortController from 'abort-controller'; +import { Pipeline, PipelineType } from "./models/pipeline"; +import { EventEmitter } from "events" +import express from "express"; -// const app: Application = expressWs(express()).app; -// const port = process.env.PORT || 8999; +// @ts-ignore +global.fetch = fetch; +global.AbortController = AbortController; +const app = express() +const pipelineEvent = new EventEmitter(); -// app.ws("/ws-path", (ws: WebSocket) => { -// ws.on("message", (message: string) => { -// console.log("received: %s", message); -// ws.send(`Hello, you sent -> ${message}`); -// }); +app.put("/workflow", (req: Pipeline) => { + pipelineEvent.emit("event", req); +}); -// ws.send("Hi there, I am a WebSocket server"); -// }); +app.post("/workflow", (req: Pipeline) => { + pipelineEvent.emit("event", req); +}); -// app.listen(port, () => { -// console.log(`Server started on port ${port}`); -// }); +app.delete("/workflow", (req: Pipeline) => { + req.type = PipelineType.Never; + pipelineEvent.emit("event", req); +}); -const pipelines: Pipeline[] = [ - { - id: 1, - enabled: true, - lastTrigger: new Date(), - triggerCount: 0, - name: "toto", - service: ServiceType.Youtube, - type: PipelineType.OnUpload, - params: { - channel: "UCq-Fj5jknLsUf-MWSy4_brA" - }, - userData: { }, - reactions: [{ - id: 1, - service: ServiceType.Twitter, - type: ReactionType.Tweet, - params: {} - }] - } -]; -const manager: Manager = new Manager(from(pipelines)); +app.listen(5000); + +const pipelines = merge( + fromFetch(`${process.env["API_URL"]}/workflows?API_KEY=${process.env["API_KEY"]}`, {selector: x => x.json()}), + fromEvent(pipelineEvent, "event"), +) as Observable; + +const manager: Manager = new Manager(pipelines); await manager.run() diff --git a/worker/src/models/pipeline.ts b/worker/src/models/pipeline.ts index 106f081..da1da54 100644 --- a/worker/src/models/pipeline.ts +++ b/worker/src/models/pipeline.ts @@ -6,6 +6,8 @@ export enum ServiceType { }; export enum PipelineType { + // Special value that will never emit an action. It is used for deleted pipelines. + Never, OnTweet, OnUpload, }; diff --git a/worker/tsconfig.json b/worker/tsconfig.json index 2ca9cf3..b825145 100644 --- a/worker/tsconfig.json +++ b/worker/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "module": "esnext", "moduleResolution": "node", + "allowSyntheticDefaultImports": true, "target": "es2017", "noImplicitAny": true, "sourceMap": true, diff --git a/worker/yarn.lock b/worker/yarn.lock index d4f4d91..1981ad2 100644 --- a/worker/yarn.lock +++ b/worker/yarn.lock @@ -169,7 +169,7 @@ dependencies: "@types/node" "*" -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": +"@types/express-serve-static-core@^4.17.18": version "4.17.28" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" integrity sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== @@ -178,16 +178,7 @@ "@types/qs" "*" "@types/range-parser" "*" -"@types/express-ws@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/express-ws/-/express-ws-3.0.1.tgz#6fbf5dfdbeedd16479ccbeecbca63c14be26612e" - integrity sha512-VguRXzcpPBF0IggIGpUoM65cZJDfMQxoc6dKoCz1yLzcwcXW7ft60yhq3ygKhyEhEIQFtLrWjyz4AJ1qjmzCFw== - dependencies: - "@types/express" "*" - "@types/express-serve-static-core" "*" - "@types/ws" "*" - -"@types/express@*", "@types/express@^4.17.13": +"@types/express@^4.17.13": version "4.17.13" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== @@ -233,7 +224,7 @@ "@types/mime" "^1" "@types/node" "*" -"@types/ws@*", "@types/ws@^8.2.2": +"@types/ws@^8.2.2": version "8.2.2" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21" integrity sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg== @@ -247,7 +238,7 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" -accepts@~1.3.7: +accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== @@ -316,20 +307,20 @@ bignumber.js@^9.0.0: resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.2.tgz#71c6c6bed38de64e24a65ebe16cfcf23ae693673" integrity sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw== -body-parser@1.19.1: - version "1.19.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.1.tgz#1499abbaa9274af3ecc9f6f10396c995943e31d4" - integrity sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA== +body-parser@1.19.2: + version "1.19.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.2.tgz#4714ccd9c157d44797b8b5607d72c0b89952f26e" + integrity sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw== dependencies: - bytes "3.1.1" + bytes "3.1.2" content-type "~1.0.4" debug "2.6.9" depd "~1.1.2" http-errors "1.8.1" iconv-lite "0.4.24" on-finished "~2.3.0" - qs "6.9.6" - raw-body "2.4.2" + qs "6.9.7" + raw-body "2.4.3" type-is "~1.6.18" brace-expansion@^1.1.7: @@ -345,10 +336,10 @@ buffer-equal-constant-time@1.0.1: resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= -bytes@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a" - integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg== +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== call-bind@^1.0.0: version "1.0.2" @@ -403,10 +394,15 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" - integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== +cookie@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + +data-uri-to-buffer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b" + integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA== debug@2.6.9: version "2.6.9" @@ -514,24 +510,17 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -express-ws@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/express-ws/-/express-ws-5.0.2.tgz#5b02d41b937d05199c6c266d7cc931c823bda8eb" - integrity sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ== - dependencies: - ws "^7.4.6" - express@^4.17.2: - version "4.17.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.2.tgz#c18369f265297319beed4e5558753cc8c1364cb3" - integrity sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg== + version "4.17.3" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.3.tgz#f6c7302194a4fb54271b73a1fe7a06478c8f85a1" + integrity sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg== dependencies: - accepts "~1.3.7" + accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.19.1" + body-parser "1.19.2" content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.4.1" + cookie "0.4.2" cookie-signature "1.0.6" debug "2.6.9" depd "~1.1.2" @@ -546,7 +535,7 @@ express@^4.17.2: parseurl "~1.3.3" path-to-regexp "0.1.7" proxy-addr "~2.0.7" - qs "6.9.6" + qs "6.9.7" range-parser "~1.2.1" safe-buffer "5.2.1" send "0.17.2" @@ -567,6 +556,14 @@ fast-text-encoding@^1.0.0: resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.1.4.tgz#e8c6567f80ad7fc22fd302e7dcb72bafde9c1717" + integrity sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + finalhandler@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -605,6 +602,13 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -899,6 +903,11 @@ negotiator@0.6.3: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + node-fetch@^2.6.1, node-fetch@^2.6.5, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" @@ -906,6 +915,15 @@ node-fetch@^2.6.1, node-fetch@^2.6.5, node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" +node-fetch@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.0.tgz#59390db4e489184fa35d4b74caf5510e8dfbaf3b" + integrity sha512-8xeimMwMItMw8hRrOl3C9/xzU49HV/yE6ORew/l+dxWimO5A4Ra8ld2rerlJvc/O7et5Z1zrWsPX43v1QBjCxw== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + node-forge@^1.0.0: version "1.2.1" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.2.1.tgz#82794919071ef2eb5c509293325cec8afd0fd53c" @@ -966,10 +984,10 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -qs@6.9.6: - version "6.9.6" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee" - integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ== +qs@6.9.7: + version "6.9.7" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe" + integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw== qs@^6.7.0: version "6.10.3" @@ -983,12 +1001,12 @@ range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.2.tgz#baf3e9c21eebced59dd6533ac872b71f7b61cb32" - integrity sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ== +raw-body@2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.3.tgz#8f80305d11c2a0a545c2d9d89d7a0286fcead43c" + integrity sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g== dependencies: - bytes "3.1.1" + bytes "3.1.2" http-errors "1.8.1" iconv-lite "0.4.24" unpipe "1.0.0" @@ -1176,6 +1194,11 @@ vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= +web-streams-polyfill@^3.0.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz#a6b74026b38e4885869fb5c589e90b95ccfc7965" + integrity sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA== + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -1199,12 +1222,7 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@^7.4.6: - version "7.5.7" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" - integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== - -ws@^8.4.0, ws@^8.4.2: +ws@^8.4.0: version "8.5.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== From cebecfa19c95a1fe8207b3b57699c606e66e77e4 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 24 Feb 2022 17:47:19 +0100 Subject: [PATCH 02/72] Adding a method to inform the worker of changes --- api/src/Api/Pipeline.hs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/api/src/Api/Pipeline.hs b/api/src/Api/Pipeline.hs index 17efcc0..dcb7b95 100644 --- a/api/src/Api/Pipeline.hs +++ b/api/src/Api/Pipeline.hs @@ -4,6 +4,7 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeOperators #-} @@ -38,6 +39,9 @@ import Servant.Server.Generic (AsServerT) import Utils (mapInd, UserAuth, AuthRes) import Core.User (UserId(UserId), User (User)) import Servant.Auth.Server (AuthResult(Authenticated)) +import Network.HTTP.Simple (setRequestBodyJSON, httpJSONEither, setRequestMethod, addRequestHeader, parseRequest, httpBS) +import Network.HTTP.Client.Conduit (Request(requestBody), httpNoBody) +import Data.ByteString (ByteString) data PipelineData = PipelineData { name :: Text @@ -76,6 +80,17 @@ data PipelineAPI mode = PipelineAPI } deriving stock (Generic) +informWorker :: ByteString -> Pipeline Identity -> IO() +informWorker method pipeline = do + request <- parseRequest "toto" + response <- httpBS + $ setRequestMethod method + $ addRequestHeader "Accept" "application/json" + $ setRequestBodyJSON pipeline + $ request + return () + + getPipelineHandler :: AuthRes -> PipelineId -> AppM GetPipelineResponse getPipelineHandler (Authenticated user) pipelineId = do pipeline <- getPipelineById' pipelineId @@ -88,6 +103,7 @@ getPipelineHandler _ _ = throwError err401 postPipelineHandler :: AuthRes -> PostPipelineData -> AppM [ReactionId] postPipelineHandler (Authenticated (User uid uname slug)) x = do actionId <- createPipeline $ Pipeline (PipelineId 1) (name p) (pType p) (pParams p) uid + -- informWorker "POST" -- pipelinetoadd sequence $ mapInd (reactionMap actionId) r where p = action x From 284b66c43675dc006661a1ab0a5dfd705cfef03c Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 24 Feb 2022 18:11:44 +0100 Subject: [PATCH 03/72] Changing the body of the worker api --- api/src/Api/Pipeline.hs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/api/src/Api/Pipeline.hs b/api/src/Api/Pipeline.hs index dcb7b95..266cf0c 100644 --- a/api/src/Api/Pipeline.hs +++ b/api/src/Api/Pipeline.hs @@ -11,7 +11,7 @@ module Api.Pipeline where import App (AppM, State (State, dbPool)) -import Control.Monad.IO.Class (MonadIO) +import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Trans.Reader (ask) import Core.Pipeline (PipelineParams, PipelineType) import Core.Reaction (ReactionParams, ReactionType) @@ -19,7 +19,7 @@ import Data.Aeson (FromJSON, ToJSON, defaultOptions, eitherDecode) import Data.Aeson.TH (deriveJSON) import Data.Functor.Identity (Identity) import Data.Int (Int64) -import Data.Text (Text) +import Data.Text (Text, pack) import Db.Pipeline (Pipeline (Pipeline, pipelineType), PipelineId (PipelineId, toInt64), getPipelineById, insertPipeline, pipelineName, pipelineParams, pipelineSchema, pipelineId) import Db.Reaction (Reaction (Reaction, reactionOrder, reactionParams, reactionType), ReactionId (ReactionId), getReactionsByPipelineId, insertReaction) import GHC.Generics (Generic) @@ -39,9 +39,11 @@ import Servant.Server.Generic (AsServerT) import Utils (mapInd, UserAuth, AuthRes) import Core.User (UserId(UserId), User (User)) import Servant.Auth.Server (AuthResult(Authenticated)) -import Network.HTTP.Simple (setRequestBodyJSON, httpJSONEither, setRequestMethod, addRequestHeader, parseRequest, httpBS) +import Network.HTTP.Simple (setRequestBodyJSON, httpJSONEither, setRequestMethod, addRequestHeader, parseRequest, httpBS, setRequestPath) import Network.HTTP.Client.Conduit (Request(requestBody), httpNoBody) import Data.ByteString (ByteString) +import Data.Text.Encoding (encodeUtf8) +import System.Environment.MrEnv (envAsString) data PipelineData = PipelineData { name :: Text @@ -80,13 +82,14 @@ data PipelineAPI mode = PipelineAPI } deriving stock (Generic) -informWorker :: ByteString -> Pipeline Identity -> IO() -informWorker method pipeline = do - request <- parseRequest "toto" +informWorker :: ByteString -> PipelineId -> IO() +informWorker method id = do + url <- envAsString "WORKER_URL" "worker/" + request <- parseRequest url response <- httpBS $ setRequestMethod method $ addRequestHeader "Accept" "application/json" - $ setRequestBodyJSON pipeline + $ setRequestPath (encodeUtf8 (pack $ "/worker" <> show id)) $ request return () @@ -103,7 +106,7 @@ getPipelineHandler _ _ = throwError err401 postPipelineHandler :: AuthRes -> PostPipelineData -> AppM [ReactionId] postPipelineHandler (Authenticated (User uid uname slug)) x = do actionId <- createPipeline $ Pipeline (PipelineId 1) (name p) (pType p) (pParams p) uid - -- informWorker "POST" -- pipelinetoadd + liftIO $ informWorker "POST" actionId sequence $ mapInd (reactionMap actionId) r where p = action x From c5ac044fb7c0a37cfc73c182d66841041c1e2055 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 25 Feb 2022 16:50:27 +0100 Subject: [PATCH 04/72] Updating routes to send ids instead of the whole pipeline to the worker --- api/src/Api/Pipeline.hs | 4 +++- worker/src/index.ts | 22 +++++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/api/src/Api/Pipeline.hs b/api/src/Api/Pipeline.hs index 0037865..59c3ffc 100644 --- a/api/src/Api/Pipeline.hs +++ b/api/src/Api/Pipeline.hs @@ -92,7 +92,7 @@ formatGetPipelineResponse pipeline reactions = actionResult = PipelineData (pipelineName pipeline) (pipelineType pipeline) (pipelineParams pipeline) (pipelineId pipeline) (pipelineEnabled pipeline) reactionsResult = fmap (\x -> ReactionData (reactionType x) (reactionParams x)) reactions -informWorker :: ByteString -> PipelineId -> IO() +informWorker :: ByteString -> PipelineId -> IO () informWorker method id = do url <- envAsString "WORKER_URL" "worker/" request <- parseRequest url @@ -136,6 +136,7 @@ putPipelineHandler (Authenticated (User uid _ _)) pipelineId x = do if pipelineUserId oldPipeline == uid then do res <- putWorkflow pipelineId newPipeline r if res > 0 then + liftIO $ informWorker "PUT" actionId return x else throwError err500 @@ -155,6 +156,7 @@ delPipelineHandler :: AuthRes -> PipelineId -> AppM Int64 delPipelineHandler (Authenticated (User uid _ _)) pipelineId = do oldPipeline <- getPipelineById' pipelineId if pipelineUserId oldPipeline == uid then do + liftIO $ informWorker "DELETE" actionId delWorkflow pipelineId else throwError err403 delPipelineHandler _ _ = throwError err401 diff --git a/worker/src/index.ts b/worker/src/index.ts index 20a7546..3f9408e 100644 --- a/worker/src/index.ts +++ b/worker/src/index.ts @@ -15,17 +15,25 @@ global.AbortController = AbortController; const app = express() const pipelineEvent = new EventEmitter(); -app.put("/workflow", (req: Pipeline) => { - pipelineEvent.emit("event", req); +app.put("/workflow/:id", req => { + fetch(`${process.env["API_URL"]}/workflow/${req.params.id}?API_KEY=${process.env["API_KEY"]}`) + .then(res => { + pipelineEvent.emit("event", res.json()); + }); }); -app.post("/workflow", (req: Pipeline) => { - pipelineEvent.emit("event", req); +app.post("/workflow/:id", req => { + fetch(`${process.env["API_URL"]}/workflow/${req.params.id}?API_KEY=${process.env["API_KEY"]}`) + .then(res => { + pipelineEvent.emit("event", res.json()); + }); }); -app.delete("/workflow", (req: Pipeline) => { - req.type = PipelineType.Never; - pipelineEvent.emit("event", req); +app.delete("/workflow/:id", req => { + pipelineEvent.emit("event", { + id: req.params.id, + type: PipelineType.Never, + }); }); app.listen(5000); From c8a3d99cae74dcdf78ac31b0f7e2870d17201240 Mon Sep 17 00:00:00 2001 From: GitBluub Date: Fri, 25 Feb 2022 17:19:38 +0100 Subject: [PATCH 05/72] WIP github oidc --- api/src/Api/OIDC.hs | 8 ++--- api/src/Core/OIDC.hs | 74 ++++++++++++++++++++++++++++++++++-------- api/src/OIDC/Github.hs | 42 +++++++----------------- 3 files changed, 77 insertions(+), 47 deletions(-) diff --git a/api/src/Api/OIDC.hs b/api/src/Api/OIDC.hs index 1b14b42..2782b18 100644 --- a/api/src/Api/OIDC.hs +++ b/api/src/Api/OIDC.hs @@ -8,19 +8,19 @@ import App (AppM) import Control.Monad.IO.Class (liftIO) import Core.User (ExternalToken (ExternalToken, service), Service (Github), UserId (UserId), User (User)) import Data.Text (pack) -import OIDC +import Core.OIDC ( getOauthTokens ) import Repository.User (updateTokens) -import Servant (Capture, Get, GetNoContent, JSON, NoContent (NoContent), QueryParam, ServerT, err400, throwError, type (:<|>) ((:<|>)), type (:>), err401) +import Servant (Capture, Get, GetNoContent, JSON, NoContent (NoContent), QueryParam, ServerT, err400, throwError, type (:<|>) ((:<|>)), type (:>), err401, err403) import Servant.API.Generic (type (:-)) import Servant.Server.Generic (AsServerT) import Utils (UserAuth, AuthRes) import Servant.Auth.Server (AuthResult(Authenticated)) oauthHandler :: AuthRes -> Service -> Maybe String -> AppM NoContent -oauthHandler (Authenticated (User uid name slug)) service (Just code) = do +oauthHandler (Authenticated (User uid _ _)) service (Just code) = do tokens <- liftIO $ getOauthTokens service code case tokens of - Nothing -> throwError err400 + Nothing -> throwError err403 Just t -> do updateTokens uid t return NoContent diff --git a/api/src/Core/OIDC.hs b/api/src/Core/OIDC.hs index 9d85fc8..e0d47e1 100644 --- a/api/src/Core/OIDC.hs +++ b/api/src/Core/OIDC.hs @@ -2,21 +2,69 @@ module Core.OIDC where -import Data.ByteString.Lazy +import qualified Data.ByteString.Char8 as B8 +import qualified Data.HashMap.Strict as HM --- * OIDC +import App (AppM) +import Core.User (ExternalToken (ExternalToken), Service (Github)) +import Data.Aeson.Types (Object, Value (String)) +import Data.Text (Text, pack) +import Network.HTTP.Simple (JSONException, addRequestHeader, getResponseBody, httpJSONEither, parseRequest, setRequestMethod, setRequestQueryString) +import System.Environment.MrEnv (envAsBool, envAsInt, envAsInteger, envAsString) +import Utils (lookupObj) -data OIDCConf = OIDCConf - { redirectUri :: ByteString - , clientId :: ByteString - , clientPassword :: ByteString +data OAuth2Conf = OAuth2Conf + { oauthClientId :: String + , oauthClientSecret :: String + , oauthAccessTokenEndpoint :: String } deriving (Show, Eq) -oidcGoogleConf :: OIDCConf -oidcGoogleConf = - OIDCConf - { redirectUri = "http://localhost:8080/auth/login/google" - , clientId = "914790981890-qjn5qjq5qjqjqjqjqjqjqjqjqjqjqjq.apps.googleusercontent.com" - , clientPassword = "914790981890-qjn5qjq5qjqjqjqjqjqjqjqjqjqjqjqjq" - } +tokenEndpoint :: String -> OAuth2Conf -> String +tokenEndpoint code oc = + concat + [ oauthAccessTokenEndpoint oc + , "?client_id=" + , oauthClientId oc + , "&client_secret=" + , oauthClientSecret oc + , "&code=" + , code + ] + +-- GITHUB +getGithubConfig :: IO OAuth2Conf +getGithubConfig = + OAuth2Conf + <$> envAsString "GITHUB_CLIENT_ID" "" + <*> envAsString "GITHUB_SECRET" "" + <*> pure "https://github.com/login/oauth/access_token" + +getGithubTokens :: String -> IO (Maybe ExternalToken) +getGithubTokens code = do + gh <- getGithubConfig + print gh + let endpoint = tokenEndpoint code gh + request' <- parseRequest endpoint + let request = + setRequestMethod "POST" $ + addRequestHeader "Accept" "application/json" $ + setRequestQueryString + [ ("client_id", Just . B8.pack . oauthClientId $ gh) + , ("client_secret", Just . B8.pack . oauthClientSecret $ gh) + , ("code", Just . B8.pack $ code) + ] + $ request' + response <- httpJSONEither request + print response + return $ case (getResponseBody response :: Either JSONException Object) of + Left _ -> Nothing + Right obj -> do + access <- lookupObj obj "access_token" + refresh <- lookupObj obj "refresh_token" + Just $ ExternalToken (pack access) (pack refresh) 0 Github + +-- General +getOauthTokens :: Service -> String -> IO (Maybe ExternalToken) +getOauthTokens Github = getGithubTokens +getOauthTokens _ = \s -> return Nothing diff --git a/api/src/OIDC/Github.hs b/api/src/OIDC/Github.hs index 275229b..f3ac379 100644 --- a/api/src/OIDC/Github.hs +++ b/api/src/OIDC/Github.hs @@ -13,37 +13,21 @@ import Network.HTTP.Simple (JSONException, addRequestHeader, getResponseBody, ht import System.Environment.MrEnv (envAsBool, envAsInt, envAsInteger, envAsString) import Utils (lookupObj) -data GithubOAuth2 = GithubOAuth2 +data OAuth2Conf = OAuth2Conf { oauthClientId :: String , oauthClientSecret :: String - , oauthOAuthorizeEndpoint :: String , oauthAccessTokenEndpoint :: String - , oauthCallback :: String } deriving (Show, Eq) -getGithubConfig :: IO GithubOAuth2 +getGithubConfig :: IO OAuth2Conf getGithubConfig = - GithubOAuth2 + OAuth2Conf <$> envAsString "GITHUB_CLIENT_ID" "" <*> envAsString "GITHUB_SECRET" "" - <*> pure "https://github.com/login/oauth/authorize" <*> pure "https://github.com/login/oauth/access_token" - <*> pure "http://localhost:8080/auth/github/token" -githubAuthEndpoint :: GithubOAuth2 -> String -githubAuthEndpoint oa = - concat - [ oauthOAuthorizeEndpoint oa - , "?client_id=" - , oauthClientId oa - , "&response_type=" - , "code" - , "&redirect_uri=" - , oauthCallback oa - ] - -tokenEndpoint :: String -> GithubOAuth2 -> String +tokenEndpoint :: String -> OAuth2Conf -> String tokenEndpoint code oa = concat [ oauthAccessTokenEndpoint oa @@ -55,9 +39,6 @@ tokenEndpoint code oa = , code ] -getGithubAuthEndpoint :: IO String -getGithubAuthEndpoint = githubAuthEndpoint <$> getGithubConfig - -- Step 3. Exchange code for auth token getGithubTokens :: String -> IO (Maybe ExternalToken) getGithubTokens code = do @@ -66,14 +47,15 @@ getGithubTokens code = do request' <- parseRequest endpoint let request = setRequestMethod "POST" $ - addRequestHeader "Accept" "application/json" $ - setRequestQueryString - [ ("client_id", Just . B8.pack . oauthClientId $ gh) - , ("client_secret", Just . B8.pack . oauthClientSecret $ gh) - , ("code", Just . B8.pack $ code) - ] - $ request' + addRequestHeader "Accept" "application/json" $ + setRequestQueryString + [ ("client_id", Just . B8.pack . oauthClientId $ gh) + , ("client_secret", Just . B8.pack . oauthClientSecret $ gh) + , ("code", Just . B8.pack $ code) + ] + $ request' response <- httpJSONEither request + print response return $ case (getResponseBody response :: Either JSONException Object) of Left _ -> Nothing Right obj -> do From 4e527c4655033639e287ec5dd1f270ebe432a1dc Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 25 Feb 2022 17:53:51 +0100 Subject: [PATCH 06/72] fixing first error of complie --- web-app/src/utils/CRUDPipeline.tsx | 2 +- web-app/src/utils/utils.tsx | 17 ----------------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/web-app/src/utils/CRUDPipeline.tsx b/web-app/src/utils/CRUDPipeline.tsx index f298bb8..deb2b57 100644 --- a/web-app/src/utils/CRUDPipeline.tsx +++ b/web-app/src/utils/CRUDPipeline.tsx @@ -5,7 +5,7 @@ import { API_ROUTE } from ".."; export const requestCreatePipeline = async (pipelineData: AppPipelineType, creation: boolean): Promise => { const jwt = getCookie("aeris_jwt"); - const request = API_ROUTE + "/workflow/" + (creation ? pipelineData.id : "") + const request = API_ROUTE + "/workflow/" + (!creation ? pipelineData.id : "") const rawResponse = await fetch(API_ROUTE + "/workflow/", { method: creation ? "POST" : "PUT", diff --git a/web-app/src/utils/utils.tsx b/web-app/src/utils/utils.tsx index 7d73e8e..ef3adc5 100644 --- a/web-app/src/utils/utils.tsx +++ b/web-app/src/utils/utils.tsx @@ -38,23 +38,6 @@ export const PipelineParamsToApiParam = (pipelineParams: { [key: string]: Params return Object.fromEntries(Object.entries(pipelineParams).map((el) => [el[0], el[1].value])); }; -export const requestCreatePipeline = async (pipelineData: AppPipelineType, creation: boolean) => { - const jwt = getCookie("aeris_jwt"); - - const request = API_ROUTE + "/workflow/" + (!creation ? pipelineData.id : ""); - - const rawResponse = await fetch(API_ROUTE + "/workflow/", { - method: creation ? "POST" : "PUT", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - Authorization: "Bearer " + jwt, - }, - body: JSON.stringify(PipeLineHostToApi(pipelineData)), - }); - return rawResponse.ok; -}; - export const PipeLineHostToApi = (pipelineData: AppPipelineType) => { return { action: { From 794d1de53c1701df8ca132efcb09e9efd1dc819f Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 25 Feb 2022 22:11:01 +0100 Subject: [PATCH 07/72] updating typescript prototype for funtion --- web-app/src/pages/HomePage.tsx | 8 ++++---- web-app/src/pages/PipelineEdit/PipelineEditAREA.tsx | 2 +- web-app/src/pages/PipelineEdit/PipelineEditPage.tsx | 6 +++--- web-app/src/pages/PipelineEdit/PipelineEditParams.tsx | 4 ++-- web-app/src/pages/PipelineEdit/PipelineEditPipeline.tsx | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/web-app/src/pages/HomePage.tsx b/web-app/src/pages/HomePage.tsx index ccea0ad..911dfb8 100644 --- a/web-app/src/pages/HomePage.tsx +++ b/web-app/src/pages/HomePage.tsx @@ -59,7 +59,7 @@ export default function HomePage() { const [username, setUsername] = useState(""); const [modalMode, setModalMode] = useState(ModalSelection.None); const [pipelineData, setPipelineData] = useState(AppListPipelines[0]); - const [handleSavePipeline, setHandleSavePipeline] = useState(() => {}); + const [handleSavePipeline, setHandleSavePipeline] = useState<(pD: AppPipelineType) => any>(() => (t: AppPipelineType) => {}); const homePagePipeLineSave = async (pD: AppPipelineType, creation: boolean) => { if (await requestCreatePipeline(pD, creation)) { @@ -84,7 +84,7 @@ export default function HomePage() { status: "mdr", }, } as AppPipelineType); - setHandleSavePipeline((pD: AppPipelineType) => homePagePipeLineSave(pD, false)); + setHandleSavePipeline(() => (pD: AppPipelineType) => homePagePipeLineSave(pD, false)); setModalMode(ModalSelection.PipelineEdit); }, }, @@ -96,7 +96,7 @@ export default function HomePage() { service2: AppServicesLogos["twitter"], onClickCallback: () => { setPipelineData(AppListPipelines[0]); - setHandleSavePipeline((pD: AppPipelineType) => homePagePipeLineSave(pD, false)); + setHandleSavePipeline(() => (pD: AppPipelineType) => homePagePipeLineSave(pD, false)); setModalMode(ModalSelection.PipelineEdit); }, }, @@ -148,7 +148,7 @@ export default function HomePage() { { setPipelineData(AppListPipelines[1]); - setHandleSavePipeline((pD: AppPipelineType) => homePagePipeLineSave(pD, true)); + setHandleSavePipeline(() => (pD: AppPipelineType) => homePagePipeLineSave(pD, true)); setModalMode(ModalSelection.PipelineEdit); }} size="medium" diff --git a/web-app/src/pages/PipelineEdit/PipelineEditAREA.tsx b/web-app/src/pages/PipelineEdit/PipelineEditAREA.tsx index 8de1ed7..67384e7 100644 --- a/web-app/src/pages/PipelineEdit/PipelineEditAREA.tsx +++ b/web-app/src/pages/PipelineEdit/PipelineEditAREA.tsx @@ -17,7 +17,7 @@ export interface PipelineEditAREAProps { pipelineData: AppPipelineType; services: Array; AREAs: Array; - setEditMode: any; + setEditMode: (mode: PipelineEditMode) => any; setAREA: any; } diff --git a/web-app/src/pages/PipelineEdit/PipelineEditPage.tsx b/web-app/src/pages/PipelineEdit/PipelineEditPage.tsx index 779a504..da7023a 100644 --- a/web-app/src/pages/PipelineEdit/PipelineEditPage.tsx +++ b/web-app/src/pages/PipelineEdit/PipelineEditPage.tsx @@ -17,12 +17,12 @@ import PipelineEditAREA from "./PipelineEditAREA"; interface PipelineEditProps { pipelineData: AppPipelineType; - handleSave: any; - handleDelete: any; + handleSave: (pD: AppPipelineType) => any; + handleDelete: (pD: AppPipelineType) => any; services: Array; actions: Array; reactions: Array; - handleQuit: any; + handleQuit: () => void; } export enum PipelineEditMode { diff --git a/web-app/src/pages/PipelineEdit/PipelineEditParams.tsx b/web-app/src/pages/PipelineEdit/PipelineEditParams.tsx index a9b9515..51ea9cc 100644 --- a/web-app/src/pages/PipelineEdit/PipelineEditParams.tsx +++ b/web-app/src/pages/PipelineEdit/PipelineEditParams.tsx @@ -8,8 +8,8 @@ import { AppPipelineType, AppAREAType } from "../../utils/types"; interface PipelineEditParamsProps { pipelineData: AppPipelineType; AREA: AppAREAType; - setParams: any; - handleQuit: any; + setParams: (area: AppAREAType) => any; + handleQuit: () => any; } export default function PipelineEditParams({ pipelineData, AREA, setParams }: PipelineEditParamsProps) { diff --git a/web-app/src/pages/PipelineEdit/PipelineEditPipeline.tsx b/web-app/src/pages/PipelineEdit/PipelineEditPipeline.tsx index 7b7c0bd..1981b77 100644 --- a/web-app/src/pages/PipelineEdit/PipelineEditPipeline.tsx +++ b/web-app/src/pages/PipelineEdit/PipelineEditPipeline.tsx @@ -25,9 +25,9 @@ import { Keyboard } from "@mui/icons-material"; interface PipelineEditPipelineProps { pipelineData: AppPipelineType; - handleDelete: any; - handleSave: any; - setEditMode: any; + handleDelete: (pD: AppPipelineType) => any; + handleSave: (pD: AppPipelineType) => any; + setEditMode: (mode: PipelineEditMode) => any; setEditReactionIndex: any; } From 09a8285911f8ffce9fe5696ac824a64fc76a8219 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 25 Feb 2022 23:23:38 +0100 Subject: [PATCH 08/72] deserializing json and fixing some material ui errors --- web-app/src/pages/HomePage.tsx | 14 +- .../pages/PipelineEdit/PipelineEditAREA.tsx | 10 +- .../pages/PipelineEdit/PipelineEditParams.tsx | 3 +- web-app/src/utils/discord.json | 259 ++++++++++++++++++ web-app/src/utils/types.tsx | 2 +- web-app/src/utils/utils.tsx | 36 ++- 6 files changed, 312 insertions(+), 12 deletions(-) create mode 100644 web-app/src/utils/discord.json diff --git a/web-app/src/pages/HomePage.tsx b/web-app/src/pages/HomePage.tsx index 911dfb8..389232b 100644 --- a/web-app/src/pages/HomePage.tsx +++ b/web-app/src/pages/HomePage.tsx @@ -10,7 +10,7 @@ import { API_ROUTE } from "../"; import { makeStyles } from "@material-ui/core/styles"; import { useState } from "react"; -import { getCookie } from "../utils/utils"; +import { getCookie, deSerializeService } from "../utils/utils"; import { requestCreatePipeline, deletePipeline } from "../utils/CRUDPipeline"; import { AppPipelineType, ActionTypeEnum, ReactionTypeEnum, AppAREAType } from "../utils/types"; import ServiceSetupModal from "./ServiceSetup"; @@ -23,6 +23,7 @@ import { AppListPipelines, } from "../utils/globals"; import AerisAppbar from "../components/AppBar"; +import serviceDump from "../utils/discord.json"; const useStyles = makeStyles((theme) => ({ divHomePage: { @@ -59,7 +60,9 @@ export default function HomePage() { const [username, setUsername] = useState(""); const [modalMode, setModalMode] = useState(ModalSelection.None); const [pipelineData, setPipelineData] = useState(AppListPipelines[0]); - const [handleSavePipeline, setHandleSavePipeline] = useState<(pD: AppPipelineType) => any>(() => (t: AppPipelineType) => {}); + const [handleSavePipeline, setHandleSavePipeline] = useState<(pD: AppPipelineType) => any>( + () => (t: AppPipelineType) => {} + ); const homePagePipeLineSave = async (pD: AppPipelineType, creation: boolean) => { if (await requestCreatePipeline(pD, creation)) { @@ -67,6 +70,9 @@ export default function HomePage() { } }; + const AREAs = deSerializeService(serviceDump, AppServices); + console.log(AREAs); + const data: Array = [ { title: "My super action", @@ -125,8 +131,8 @@ export default function HomePage() { pipelineData={pipelineData} handleSave={handleSavePipeline} services={AppServices} - actions={AppListActions} - reactions={AppListReactions} + actions={AREAs[0]} + reactions={AREAs[1]} handleDelete={(pD: AppPipelineType) => deletePipeline(pD)} handleQuit={() => setModalMode(ModalSelection.None)} /> diff --git a/web-app/src/pages/PipelineEdit/PipelineEditAREA.tsx b/web-app/src/pages/PipelineEdit/PipelineEditAREA.tsx index 67384e7..efcfbe4 100644 --- a/web-app/src/pages/PipelineEdit/PipelineEditAREA.tsx +++ b/web-app/src/pages/PipelineEdit/PipelineEditAREA.tsx @@ -1,4 +1,4 @@ -import { InputLabel, FormHelperText, Button } from "@mui/material"; +import { InputLabel, FormHelperText, Button, SelectChangeEvent } from "@mui/material"; import Typography from "@mui/material/Typography"; import MenuItem from "@mui/material/MenuItem"; import Select from "@mui/material/Select"; @@ -57,12 +57,12 @@ export default function PipelineEditAREA({ Service - {filteredElements.length} actions disponibles + {filteredElements.length} {isActions ? "actions" : "réactions"} disponibles
-
- {}} - setParams={(AREA: AppAREAType) => setAREA(AREA)} - /> -
+ {AREAData === null ? ( + + Sélectionnez une {isActions ? "Action" : "Réaction"} + + ) : ( +
+ {}} + setParams={(AREA: AppAREAType) => setAREA(AREA)} + /> +
+ )} From 650399f9dfca7ec6d69c82f04985bfc7add72205 Mon Sep 17 00:00:00 2001 From: 0Nom4D Date: Mon, 28 Feb 2022 18:33:00 +0100 Subject: [PATCH 55/72] Aeris : react-reorganize-homepage - Fixing Tooltip + redirection on url --- web-app/src/components/AppBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web-app/src/components/AppBar.tsx b/web-app/src/components/AppBar.tsx index 55f653e..aa29946 100644 --- a/web-app/src/components/AppBar.tsx +++ b/web-app/src/components/AppBar.tsx @@ -37,10 +37,10 @@ export default function AerisAppbar({ username, onClickOnServices, onClickRefres - + { document.cookie = "aeris_jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; - navigate('/pipelines'); + navigate('/auth'); }}> From 107efe6dd7981f9aeace441a0374bb9f983e4856 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 1 Mar 2022 10:44:33 +0100 Subject: [PATCH 56/72] front is now able to deliver the mobile app apk --- web-app/.dockerignore | 3 ++- web-app/Dockerfile | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/web-app/.dockerignore b/web-app/.dockerignore index 40b878d..e3f4a47 100644 --- a/web-app/.dockerignore +++ b/web-app/.dockerignore @@ -1 +1,2 @@ -node_modules/ \ No newline at end of file +node_modules/ +Dockerfile \ No newline at end of file diff --git a/web-app/Dockerfile b/web-app/Dockerfile index 196abcd..f58dd20 100644 --- a/web-app/Dockerfile +++ b/web-app/Dockerfile @@ -27,4 +27,4 @@ RUN npm run build FROM nginx:1.21 COPY nginx.conf /etc/nginx/conf.d/default.conf COPY --from=builder /webapp/build/ /usr/share/nginx/html -RUN mv /dist/aeris_android.apk /usr/share/nginx/html/client.apk \ No newline at end of file +CMD cp /dist/aeris_android.apk /usr/share/nginx/html/client.apk; nginx -g "daemon off;" \ No newline at end of file From a08fd13a0e9803e0c4904ee1724f5edababb3fd7 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 1 Mar 2022 10:53:57 +0100 Subject: [PATCH 57/72] adding button to dl mobile app on home page --- web-app/src/App.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/web-app/src/App.tsx b/web-app/src/App.tsx index d47b423..7f353d4 100644 --- a/web-app/src/App.tsx +++ b/web-app/src/App.tsx @@ -1,5 +1,4 @@ -import { Typography, Box, Button } from "@mui/material"; -//import "./App.css"; +import { Typography, Box, Button, ButtonGroup } from "@mui/material"; import { useNavigate } from "react-router-dom"; @@ -26,14 +25,12 @@ export default function App() { Professional, personnal action-reaction manager
- + From 1af65f63dfd39c82cd5db5f02aec72e80203370d Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 1 Mar 2022 12:01:59 +0100 Subject: [PATCH 58/72] adding buttons when there's no AREA's --- .../PipelineEdit/PipelineEditPipeline.tsx | 76 +++++++++++++------ web-app/src/utils/globals.tsx | 2 +- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/web-app/src/pages/PipelineEdit/PipelineEditPipeline.tsx b/web-app/src/pages/PipelineEdit/PipelineEditPipeline.tsx index 0319756..fa6df99 100644 --- a/web-app/src/pages/PipelineEdit/PipelineEditPipeline.tsx +++ b/web-app/src/pages/PipelineEdit/PipelineEditPipeline.tsx @@ -23,6 +23,7 @@ import { getCookie, PipeLineHostToApi } from "../../utils/utils"; import { API_ROUTE } from "../.."; import { PipelineAREACard } from "../../components/PipelineAREACard"; import { Keyboard } from "@mui/icons-material"; +import { NoAREA } from "../../utils/globals"; interface PipelineEditPipelineProps { pipelineData: AppPipelineType; @@ -104,17 +105,28 @@ export default function PipelineEditPipeline({ justifyContent="flex-start" alignItems="flex-start"> - { - setEditMode(PipelineEditMode.Action); - }} - handleDelete={() => {}} - AREA={pipelineData.action} - style={{ width: "25vw" }} - order={0} - onClick={() => {}} - /> + {pipelineData.action.type === NoAREA.type ? ( + + + + ) : ( + { + setEditMode(PipelineEditMode.Action); + }} + handleDelete={() => {}} + AREA={pipelineData.action} + style={{ width: "25vw" }} + order={0} + onClick={() => {}} + /> + )} @@ -123,13 +135,25 @@ export default function PipelineEditPipeline({
+ {pipelineData.reactions.length === 0 && ( + + + + )} {pipelineData.reactions.map((el, index, arr) => (
- { - setEditMode(PipelineEditMode.Reactions); - setEditReactionIndex(pipelineData.reactions.length); - }} - startIcon={} - variant="contained"> - Ajouter une réaction - + {pipelineData.reactions.length !== 0 && ( + { + setEditMode(PipelineEditMode.Reactions); + setEditReactionIndex(pipelineData.reactions.length); + }} + startIcon={} + variant="contained"> + Ajouter une réaction + + )} } = { }; export const NoAREA: AppAREAType = { - type: "Nothing", + type: "WebFrontEndNoAREA", params: { contents: {}, }, From 41f5fd54e9cdcf441d00fb2b49bce4deaba5ada9 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 1 Mar 2022 12:02:28 +0100 Subject: [PATCH 59/72] Fixing services.json embed --- api/aeris.cabal | 1 + api/src/Api/About.hs | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/api/aeris.cabal b/api/aeris.cabal index 6d8a839..7a9bc2d 100644 --- a/api/aeris.cabal +++ b/api/aeris.cabal @@ -17,6 +17,7 @@ license-file: LICENSE build-type: Simple extra-source-files: README.md + services/*.json source-repository head type: git diff --git a/api/src/Api/About.hs b/api/src/Api/About.hs index 4bf0016..cdf06a9 100644 --- a/api/src/Api/About.hs +++ b/api/src/Api/About.hs @@ -9,7 +9,7 @@ module Api.About where import App (AppM) import Control.Monad.IO.Class (liftIO) -import Data.Aeson (defaultOptions, eitherDecode) +import Data.Aeson (defaultOptions, eitherDecode, Object) import qualified Data.Aeson.Parser import Data.Aeson.TH (deriveJSON) import Data.Time.Clock.POSIX (POSIXTime, getPOSIXTime) @@ -29,6 +29,8 @@ data ClientAbout = ClientAbout data ActionAbout = ActionAbout { name :: String , description :: String + , params :: [Object] + , returns :: [Object] } deriving (Eq, Show) @@ -58,7 +60,7 @@ $(deriveJSON defaultOptions ''ServerAbout) $(deriveJSON defaultOptions ''About) servicesDir :: [(FilePath, S.ByteString)] -servicesDir = $(makeRelativeToProject "./services/" >>= embedDir) +servicesDir = $(embedDir "./services/") about :: SockAddr -> AppM About about host = do From d19a04bf94cd80b5121f3bc34cae89df7b8fb24f Mon Sep 17 00:00:00 2001 From: GitBluub Date: Tue, 1 Mar 2022 12:04:05 +0100 Subject: [PATCH 60/72] feat: params as object WIP --- api/src/Api/Pipeline.hs | 21 ++++++++++++--------- api/src/Api/Worker.hs | 8 ++++---- api/src/Core/Pipeline.hs | 15 ++++++++------- api/src/Core/Reaction.hs | 16 ++++++++-------- api/src/Utils.hs | 14 +++++++------- docker-compose.dev.yml | 4 ++++ docker-compose.yml | 1 + worker/src/index.ts | 4 +++- 8 files changed, 47 insertions(+), 36 deletions(-) diff --git a/api/src/Api/Pipeline.hs b/api/src/Api/Pipeline.hs index cc337b0..aa7f1f5 100644 --- a/api/src/Api/Pipeline.hs +++ b/api/src/Api/Pipeline.hs @@ -46,6 +46,7 @@ import Data.Text.Encoding (encodeUtf8) import System.Environment.MrEnv (envAsString) import Data.Default (def) import Db.User (UserDB(..)) +import Control.Applicative (Alternative((<|>))) data PipelineData = PipelineData { name :: Text @@ -94,15 +95,17 @@ formatGetPipelineResponse pipeline reactions = reactionsResult = fmap (\x -> ReactionData (reactionType x) (reactionParams x)) reactions informWorker :: ByteString -> PipelineId -> IO () -informWorker method id = do - url <- envAsString "WORKER_URL" "worker/" - request <- parseRequest url - response <- httpBS - $ setRequestMethod method - $ addRequestHeader "Accept" "application/json" - $ setRequestPath (encodeUtf8 (pack $ "/worker" <> show id)) - $ request - return () +informWorker method id = + do + url <- envAsString "WORKER_URL" "worker/" + request <- parseRequest url + response <- httpBS + $ setRequestMethod method + $ addRequestHeader "Accept" "application/json" + $ setRequestPath (encodeUtf8 (pack $ "/worker" <> show id)) + $ request + return () + <|> return () getPipelineHandler :: AuthRes -> PipelineId -> AppM GetPipelineResponse diff --git a/api/src/Api/Worker.hs b/api/src/Api/Worker.hs index 784fb80..71e9ea1 100644 --- a/api/src/Api/Worker.hs +++ b/api/src/Api/Worker.hs @@ -48,13 +48,13 @@ $(deriveJSON defaultOptions ''ErrorBody) data WorkerAPI mode = WorkerAPI { get :: mode :- "workflow" :> Capture "id" PipelineId :> - QueryParam "API_KEY" String :> Get '[JSON] GetPipelineWorkerResponse + QueryParam "WORKER_API_KEY" String :> Get '[JSON] GetPipelineWorkerResponse , all :: mode :- "workflows" :> - QueryParam "API_KEY" String :> Get '[JSON] [GetPipelineWorkerResponse] + QueryParam "WORKER_API_KEY" String :> Get '[JSON] [GetPipelineWorkerResponse] , trigger :: mode :- "trigger" :> Capture "id" PipelineId :> - QueryParam "API_KEY" String :> Get '[JSON] NoContent + QueryParam "WORKER_API_KEY" String :> Get '[JSON] NoContent , error :: mode :- "error" :> Capture "id" PipelineId :> - QueryParam "API_KEY" String :> ReqBody '[JSON] ErrorBody :>Get '[JSON] NoContent + QueryParam "WORKER_API_KEY" String :> ReqBody '[JSON] ErrorBody :>Get '[JSON] NoContent } deriving stock (Generic) diff --git a/api/src/Core/Pipeline.hs b/api/src/Core/Pipeline.hs index d598d5f..ed3cfe5 100644 --- a/api/src/Core/Pipeline.hs +++ b/api/src/Core/Pipeline.hs @@ -6,14 +6,16 @@ {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} {-# HLINT ignore "Use newtype instead of data" #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} module Core.Pipeline where -import Data.Aeson (FromJSON, ToJSON, defaultOptions, eitherDecode) +import Data.Aeson (FromJSON, ToJSON, defaultOptions, eitherDecode, Object) import Data.Aeson.TH (deriveJSON) import Data.Text (Text) import GHC.Generics (Generic) -import Rel8 (DBType, JSONBEncoded (JSONBEncoded), ReadShow (ReadShow)) +import Rel8 (DBType, JSONBEncoded (JSONBEncoded), ReadShow (ReadShow), DBEq) +import Servant (FromHttpApiData) data PipelineType = TwitterNewPost | TwitterNewFollower deriving stock (Generic, Read, Show) @@ -34,9 +36,8 @@ data TwitterNewFollowerData = TwitterNewFollowerData $(deriveJSON defaultOptions ''TwitterNewFollowerData) -data PipelineParams - = TwitterNewPostP TwitterNewPostData - | TwitterNewFollowerP TwitterNewFollowerData - deriving stock (Generic, Show) - deriving anyclass (ToJSON, FromJSON) +newtype PipelineParams + = PipelineParams { toObject :: Object } + deriving stock (Generic) + deriving newtype (DBEq, Eq, Show, FromJSON, ToJSON) deriving (DBType) via JSONBEncoded PipelineParams diff --git a/api/src/Core/Reaction.hs b/api/src/Core/Reaction.hs index ad041c5..88c062e 100644 --- a/api/src/Core/Reaction.hs +++ b/api/src/Core/Reaction.hs @@ -5,14 +5,15 @@ {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} {-# HLINT ignore "Use newtype instead of data" #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} module Core.Reaction where -import Data.Aeson (FromJSON, ToJSON, defaultOptions, eitherDecode) +import Data.Aeson (FromJSON, ToJSON, defaultOptions, eitherDecode, Object) import Data.Aeson.TH (deriveJSON) import Data.Text (Text) import GHC.Generics (Generic) -import Rel8 (DBType, JSONBEncoded (JSONBEncoded), ReadShow (ReadShow)) +import Rel8 (DBType, JSONBEncoded (JSONBEncoded), ReadShow (ReadShow), DBEq) data ReactionType = TwitterTweet | TwitterFollower deriving stock (Generic, Read, Show) @@ -32,9 +33,8 @@ data TwitterFollowData = TwitterFollowData $(deriveJSON defaultOptions ''TwitterFollowData) -data ReactionParams - = TwitterTweetP TwitterTweetData - | TwitterFollowP TwitterFollowData - deriving stock (Generic, Show) - deriving anyclass (ToJSON, FromJSON) - deriving (DBType) via JSONBEncoded ReactionParams +newtype ReactionParams + = ReactionParams { toObject :: Object } + deriving stock (Generic) + deriving newtype (DBEq, Eq, Show, FromJSON, ToJSON) + deriving (DBType) via JSONBEncoded ReactionParams \ No newline at end of file diff --git a/api/src/Utils.hs b/api/src/Utils.hs index fcaae0c..c1caf12 100644 --- a/api/src/Utils.hs +++ b/api/src/Utils.hs @@ -12,13 +12,13 @@ import Db.User (User') import qualified Servant.Auth.Server import Core.User (User, UserId (UserId)) import Data.Text (Text, unpack) -import Data.HashMap.Strict (HashMap, lookup) +import Data.HashMap.Strict (HashMap, lookup, empty) import Data.Functor.Identity (Identity) import Db.Pipeline (Pipeline (Pipeline), PipelineId (PipelineId), pipelineLastTrigger, pipelineTriggerCount, pipelineError, pipelineEnabled, pipelineUserId, pipelineParams, pipelineType, pipelineName, pipelineId) -import Core.Pipeline (PipelineType(TwitterNewPost), PipelineParams (TwitterNewPostP), TwitterNewPostData (TwitterNewPostData)) +import Core.Pipeline (PipelineType(TwitterNewPost), PipelineParams (PipelineParams)) import Data.Time (UTCTime (UTCTime), fromGregorian, secondsToDiffTime) -import Data.Default (Default) -import Data.Aeson (Value(Number), decode) +import Data.Default (Default, def) +import Data.Aeson (Value(Number, Object), decode) mapInd :: (a -> Int -> b) -> [a] -> [b] mapInd f l = zipWith f l [0 ..] @@ -31,14 +31,14 @@ lookupObjString obj key = case Data.HashMap.Strict.lookup key obj of uncurry3 :: (a -> b -> c -> d) -> (a, b, c) -> d uncurry3 f (a, b, c) = f a b c -defaultPipelineParams :: PipelineParams -defaultPipelineParams = TwitterNewPostP (TwitterNewPostData "") +instance Default PipelineParams where + def = PipelineParams empty instance Default (Pipeline Identity) where def = Pipeline { pipelineId = PipelineId 1 , pipelineName = "" , pipelineType = TwitterNewPost - , pipelineParams = defaultPipelineParams + , pipelineParams = def , pipelineUserId = UserId 1 , pipelineEnabled = True , pipelineError = Nothing diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 4d1f3e5..0836a6d 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -23,6 +23,7 @@ services: - POSTGRES_DB=${POSTGRES_DB} - POSTGRES_PORT=${POSTGRES_PORT} - WORKER_API_KEY=${WORKER_API_KEY} + - WORKER_URL=${WORKER_URL} - DISCORD_CLIENT_ID=${DISCORD_CLIENT_ID} - DISCORD_SECRET=${DISCORD_SECRET} - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID} @@ -41,6 +42,9 @@ services: - "api" environment: - YOUTUBE_KEY=${YOUTUBE_KEY} + - WORKER_API_URL=${WORKER_API_URL} + - WORKER_API_KEY=${WORKER_API_KEY} + volumes: apk: diff --git a/docker-compose.yml b/docker-compose.yml index 1842427..236b1a2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,6 +33,7 @@ services: - POSTGRES_DB=${POSTGRES_DB} - POSTGRES_PORT=${POSTGRES_PORT} - WORKER_API_KEY=${WORKER_API_KEY} + - WORKER_URL=${WORKER_URL} - DISCORD_CLIENT_ID=${DISCORD_CLIENT_ID} - DISCORD_SECRET=${DISCORD_SECRET} - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID} diff --git a/worker/src/index.ts b/worker/src/index.ts index 4a73453..754fabb 100644 --- a/worker/src/index.ts +++ b/worker/src/index.ts @@ -38,8 +38,10 @@ app.delete("/workflow/:id", req => { app.listen(5000); +console.log(`${process.env["WORKER_API_URL"]}/workflows?WORKER_API_KEY=${process.env["WORKER_API_KEY"]}`) + fetch(`${process.env["WORKER_API_URL"]}/workflows?WORKER_API_KEY=${process.env["WORKER_API_KEY"]}`) - .then(async res => console.log("toto", await res.json())); + .then(async res => console.log("toto", await res.json())) const pipelines = merge( fromFetch(`${process.env["WORKER_API_URL"]}/workflows?WORKER_API_KEY=${process.env["WORKER_API_KEY"]}`, {selector: x => x.json()}), From 61bff5794c5bb74d17345f72d29cd9b6704fff7b Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 1 Mar 2022 12:19:16 +0100 Subject: [PATCH 61/72] adding some fake data --- .../pages/PipelineEdit/PipelineEditPipeline.tsx | 3 +++ web-app/src/utils/globals.tsx | 17 ++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/web-app/src/pages/PipelineEdit/PipelineEditPipeline.tsx b/web-app/src/pages/PipelineEdit/PipelineEditPipeline.tsx index fa6df99..d623feb 100644 --- a/web-app/src/pages/PipelineEdit/PipelineEditPipeline.tsx +++ b/web-app/src/pages/PipelineEdit/PipelineEditPipeline.tsx @@ -110,6 +110,7 @@ export default function PipelineEditPipeline({ @@ -146,6 +147,7 @@ export default function PipelineEditPipeline({