From c2f102f603fd2cb7ba664d486ea7f54df306a92b Mon Sep 17 00:00:00 2001 From: Eric Tuvesson Date: Thu, 25 Jan 2024 12:09:50 +0100 Subject: [PATCH] chore: Split routes and add typings (#5) --- packages/noodl-cloudservice/package-lock.json | 97 +++++++++++++++-- packages/noodl-cloudservice/package.json | 5 +- packages/noodl-cloudservice/src/index.ts | 101 ++---------------- .../src/routes/functions-admin.ts | 52 +++++++++ .../src/routes/functions.ts | 57 ++++++++++ 5 files changed, 208 insertions(+), 104 deletions(-) create mode 100644 packages/noodl-cloudservice/src/routes/functions-admin.ts create mode 100644 packages/noodl-cloudservice/src/routes/functions.ts diff --git a/packages/noodl-cloudservice/package-lock.json b/packages/noodl-cloudservice/package-lock.json index af37c42..5735e87 100644 --- a/packages/noodl-cloudservice/package-lock.json +++ b/packages/noodl-cloudservice/package-lock.json @@ -1,12 +1,12 @@ { "name": "@noodl/cloudservice", - "version": "1.0.0", + "version": "0.2.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@noodl/cloudservice", - "version": "1.0.0", + "version": "0.2.4", "license": "MIT", "dependencies": { "isolated-vm": "^4.4.2", @@ -16,6 +16,7 @@ "winston-mongodb": "^5.1.0" }, "devDependencies": { + "@types/express": "^4.17.21", "ts-node": "^10.9.2", "typescript": "^5.2.2" } @@ -937,12 +938,12 @@ } }, "node_modules/@types/express": { - "version": "4.17.7", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.7.tgz", - "integrity": "sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dependencies": { "@types/body-parser": "*", - "@types/express-serve-static-core": "*", + "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "*" } @@ -975,6 +976,17 @@ "express-unless": "*" } }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.17.41", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", + "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/fs-capacitor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/fs-capacitor/-/fs-capacitor-2.0.0.tgz", @@ -1046,6 +1058,20 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/send/node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + }, "node_modules/@types/serve-static": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", @@ -1450,6 +1476,17 @@ "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" } }, + "node_modules/apollo-server-express/node_modules/@types/express": { + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.7.tgz", + "integrity": "sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, "node_modules/apollo-server-express/node_modules/subscriptions-transport-ws": { "version": "0.9.19", "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.19.tgz", @@ -7615,14 +7652,27 @@ } }, "@types/express": { - "version": "4.17.7", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.7.tgz", - "integrity": "sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "requires": { "@types/body-parser": "*", - "@types/express-serve-static-core": "*", + "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "*" + }, + "dependencies": { + "@types/express-serve-static-core": { + "version": "4.17.41", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", + "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + } } }, "@types/express-jwt": { @@ -7723,6 +7773,22 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, + "@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "requires": { + "@types/mime": "^1", + "@types/node": "*" + }, + "dependencies": { + "@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + } + } + }, "@types/serve-static": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", @@ -8042,6 +8108,17 @@ "type-is": "^1.6.16" }, "dependencies": { + "@types/express": { + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.7.tgz", + "integrity": "sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, "subscriptions-transport-ws": { "version": "0.9.19", "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.19.tgz", diff --git a/packages/noodl-cloudservice/package.json b/packages/noodl-cloudservice/package.json index 2b557ee..9432ded 100644 --- a/packages/noodl-cloudservice/package.json +++ b/packages/noodl-cloudservice/package.json @@ -25,7 +25,8 @@ "winston-mongodb": "^5.1.0" }, "devDependencies": { - "typescript": "^5.2.2", - "ts-node": "^10.9.2" + "@types/express": "^4.17.21", + "ts-node": "^10.9.2", + "typescript": "^5.2.2" } } diff --git a/packages/noodl-cloudservice/src/index.ts b/packages/noodl-cloudservice/src/index.ts index a22978c..40d0a7d 100644 --- a/packages/noodl-cloudservice/src/index.ts +++ b/packages/noodl-cloudservice/src/index.ts @@ -1,97 +1,14 @@ -import { NoodlParseServerOptions, createNoodlParseServer } from "./parse"; -import { executeFunction } from "./function"; -import { CFVersion, deployFunctions, getLatestVersion } from "./function-deploy"; -import { Logger } from "./logger"; +import type { Request, Response, NextFunction } from "express" +import { NoodlParseServerOptions, NoodlParseServerResult, createNoodlParseServer } from "./parse"; +import { routeFunctions } from "./routes/functions"; +import { routeFunctionsAdmin } from "./routes/functions-admin"; -function createMiddleware(noodlServer) { - return async function middleware(req, res, next) { +function createMiddleware(noodlServer: NoodlParseServerResult) { + return async function middleware(req: Request, res: Response, next: NextFunction) { if (req.url.startsWith('/functions/') && req.method === 'POST') { - try { - const path = req.url; - const functionId = decodeURIComponent(path.split('/')[2]); - - if (functionId === undefined) - return next() - - console.log('Running cloud function ' + functionId); - - let requestVersion = req.headers['x-noodl-cloud-version']; - let version: CFVersion = requestVersion - ? { functionVersion: requestVersion } - : await getLatestVersion(noodlServer.options) - - // Execute the request - const cfResponse = await executeFunction({ - port: noodlServer.options.port, - appId: noodlServer.options.appId, - masterKey: noodlServer.options.masterKey, - version, - logger: new Logger(noodlServer), - headers: req.headers, - functionId, - body: req.body, - timeOut: noodlServer.functionOptions.timeOut, - memoryLimit: noodlServer.functionOptions.memoryLimit, - }) - - if (cfResponse.headers) { - res.status(cfResponse.statusCode) - .set(cfResponse.headers) - .send(cfResponse.body) - } else { - res.status(cfResponse.statusCode) - .set({ 'Content-Type': 'application/json' }) - .send(cfResponse.body) - } - } catch (e) { - console.log('Something went wrong when running function', e) - res.status(400).json({ - error: "Something when wrong..." - }) - } + routeFunctions(noodlServer, req, res, next); } else if (req.url.startsWith('/functions-admin')) { - if (req.headers['x-parse-master-key'] !== noodlServer.options.masterKey) { - return res.status(401).json({ - message: 'Not authorized' - }) - } - - if (req.headers['x-parse-application-id'] !== noodlServer.options.appId) { - return res.status(401).json({ - message: 'Not authorized' - }) - } - - // Deploy a new version - if (req.method === 'POST' && req.url === "/functions-admin/deploy") { - if (!req.body || typeof req.body.deploy !== "string" || typeof req.body.runtime !== "string") { - return res.status(400).json({ - message: 'Must supply deploy and runtime' - }) - } - - console.log('Uploading deploy...') - const { version } = await deployFunctions({ - port: noodlServer.options.port, - appId: noodlServer.options.appId, - masterKey: noodlServer.options.masterKey, - runtime: req.body.runtime, - data: req.body.deploy - }) - - console.log('Upload completed, version: ' + version) - res.json({ - status: 'success', - version - }) - } else if (req.method === 'GET' && req.url === "/functions-admin/info") { - res.json({ - version: '1.0' - }) - } else res.status(400).json({ - message: 'Function not supported' - }) - + routeFunctionsAdmin(noodlServer, req, res, next); } else { next() } @@ -104,7 +21,7 @@ export function createNoodlServer(options: NoodlParseServerOptions) { const cfMiddleware = createMiddleware(noodlServer); // Combine the Noodl Cloud Function middleware with the Parse middleware into one middleware. - const middleware = (req: Request, res: Response, next: () => void) => { + const middleware = (req: Request, res: Response, next: NextFunction) => { cfMiddleware(req, res, () => { noodlServer.server.app(req, res, next); }); diff --git a/packages/noodl-cloudservice/src/routes/functions-admin.ts b/packages/noodl-cloudservice/src/routes/functions-admin.ts new file mode 100644 index 0000000..7de58db --- /dev/null +++ b/packages/noodl-cloudservice/src/routes/functions-admin.ts @@ -0,0 +1,52 @@ +import type { Request, Response, NextFunction } from "express" +import { NoodlParseServerResult } from "../parse" +import { deployFunctions } from "../function-deploy" + +export async function routeFunctionsAdmin( + noodlServer: NoodlParseServerResult, + req: Request, + res: Response, + _next: NextFunction +) { + if (req.headers['x-parse-master-key'] !== noodlServer.options.masterKey) { + return res.status(401).json({ + message: 'Not authorized' + }) + } + + if (req.headers['x-parse-application-id'] !== noodlServer.options.appId) { + return res.status(401).json({ + message: 'Not authorized' + }) + } + + // Deploy a new version + if (req.method === 'POST' && req.url === "/functions-admin/deploy") { + if (!req.body || typeof req.body.deploy !== "string" || typeof req.body.runtime !== "string") { + return res.status(400).json({ + message: 'Must supply deploy and runtime' + }) + } + + console.log('Uploading deploy...') + const { version } = await deployFunctions({ + port: noodlServer.options.port, + appId: noodlServer.options.appId, + masterKey: noodlServer.options.masterKey, + runtime: req.body.runtime, + data: req.body.deploy + }) + + console.log('Upload completed, version: ' + version) + res.json({ + status: 'success', + version + }) + } else if (req.method === 'GET' && req.url === "/functions-admin/info") { + res.json({ + version: '1.0' + }) + } else res.status(400).json({ + message: 'Function not supported' + }) +} \ No newline at end of file diff --git a/packages/noodl-cloudservice/src/routes/functions.ts b/packages/noodl-cloudservice/src/routes/functions.ts new file mode 100644 index 0000000..6c68d09 --- /dev/null +++ b/packages/noodl-cloudservice/src/routes/functions.ts @@ -0,0 +1,57 @@ +import type { Request, Response, NextFunction } from "express" +import { executeFunction } from "../function"; +import { CFVersion, getLatestVersion } from "../function-deploy"; +import { Logger } from "../logger"; +import { NoodlParseServerResult } from "../parse"; + +export async function routeFunctions( + noodlServer: NoodlParseServerResult, + req: Request, + res: Response, + next: NextFunction +) { + try { + const path = req.url; + const functionId = decodeURIComponent(path.split("/")[2]); + + if (functionId === undefined) return next(); + + console.log("Running cloud function " + functionId); + + let requestVersion = req.headers["x-noodl-cloud-version"]; + let version: CFVersion = requestVersion + ? { functionVersion: String(requestVersion) } + : await getLatestVersion(noodlServer.options); + + // Execute the request + const cfResponse = await executeFunction({ + port: noodlServer.options.port, + appId: noodlServer.options.appId, + masterKey: noodlServer.options.masterKey, + version, + logger: new Logger(noodlServer), + headers: req.headers, + functionId, + body: req.body, + timeOut: noodlServer.functionOptions.timeOut, + memoryLimit: noodlServer.functionOptions.memoryLimit, + }); + + if (cfResponse.headers) { + res + .status(cfResponse.statusCode) + .set(cfResponse.headers) + .send(cfResponse.body); + } else { + res + .status(cfResponse.statusCode) + .set({ "Content-Type": "application/json" }) + .send(cfResponse.body); + } + } catch (e) { + console.log("Something went wrong when running function", e); + res.status(400).json({ + error: "Something when wrong...", + }); + } +}