towers-express
Version:
Fast and simple express api creation with custom user's functions, built with TypeScript.
210 lines (205 loc) • 6.84 kB
JavaScript
// src/expressUtils.ts
import express from "express";
// src/routes.ts
import { Router } from "express";
import multer from "multer";
// src/functionsController.ts
var TowersFunctionsController = class {
/**
* Adds a middleware function that will be applied when a function is called.
* The middleware receives the function and its name as arguments and can modify it or perform actions before the function is executed.
* @param middleware Middleware function to add.
*/
static addFunctionMiddleware(middleware) {
this.middlewares.push(middleware);
}
/**
*
* @param func Function to check user rights.
* This function should return a string with error message if rights are not sufficient, or undefined if rights are sufficient.
*/
static setCheckRightsFunction(func) {
this.checkRights = func;
this.overridedCheckRights = true;
}
/**
*
* @param func Function to authenticate user.
* This function should return a user object or null/undefined if authentication fails.
*/
static setAuthUserFunction(func) {
this.authUser = func;
this.overridedAuthUser = true;
}
static registerFunction(name, func) {
if (this.functions[name]) {
throw new Error(`Function ${name} is already registered.`);
}
this.functions[name] = func;
}
static getFunction(name) {
if (!this.functions[name]) {
throw new Error(`Function ${name} not found.`);
}
return this.functions[name];
}
static listFunctions() {
return Object.keys(this.functions);
}
static async callFunction(name, req, res) {
let func;
try {
func = this.getFunction(name);
} catch (error) {
console.error(`Error retrieving function ${name}:`, error);
res.status(404).send({ error: `Function not found: ${name}` });
return;
}
let user;
if (!this.overridedAuthUser) {
console.warn(`Using default auth user function for function ${name}. Consider overriding it for custom behavior.`);
}
user = await this.authUser(req);
for (const middleware of this.middlewares) {
middleware(name, func, user);
}
if (func.auth) {
if (!user) {
res.status(401).send({ error: "Unauthorized" });
return;
}
if (func.rights) {
if (!this.overridedCheckRights) {
console.warn(`Using default rights check for function ${name}. Consider overriding it for custom behavior.`);
}
const checkRightsResult = await this.checkRights(user, func.rights, req);
if (checkRightsResult) {
res.status(403).send({ error: checkRightsResult });
console.warn(`User ${user.id} does not have rights for function ${name}: ${checkRightsResult}`);
return;
}
}
}
await func.method(req, res, user);
}
};
TowersFunctionsController.functions = {};
TowersFunctionsController.overridedCheckRights = false;
TowersFunctionsController.overridedAuthUser = false;
TowersFunctionsController.middlewares = [];
TowersFunctionsController.checkRights = async (user, rights, req) => {
return void 0;
};
TowersFunctionsController.authUser = async (req) => {
return void 0;
};
// src/routes.ts
var router = Router();
var upload = multer({ storage: multer.memoryStorage() });
router.post("/:functionName", (req, res, next) => {
const functionName = req.params.functionName;
let func;
try {
func = TowersFunctionsController.getFunction(functionName);
} catch (error) {
return;
}
if (func.maxFiles && func.maxFiles > 1) {
upload.array("files", func.maxFiles)(req, res, next);
} else {
upload.single("file")(req, res, next);
}
}, async (req, res) => {
TowersFunctionsController.callFunction(req.params.functionName, req, res);
});
router.get("/:functionName", async (req, res) => {
req.body = req.query;
TowersFunctionsController.callFunction(req.params.functionName, req, res);
});
var routes_default = router;
// src/expressUtils.ts
import { createServer as createHttps } from "https";
import { createServer as createHttp } from "http";
import fs from "fs";
var TowersExpress = class {
constructor(functionsEndpoint, port, options = {
allowOrigin: "*"
}) {
this.openHttpServer = (onStart) => {
this.httpServer = createHttp(this.app);
if (onStart) {
onStart(this.httpServer);
}
this.httpServer.listen(this.port, () => {
return console.log(`Express is listening at http://localhost:${this.port}`);
});
};
this.openSSLServer = (onStart) => {
if (!this.sslFiles || !this.sslPort) {
console.error("SSL is not configured, call configureSSL first");
return;
}
try {
var options = {
key: fs.readFileSync(this.sslFiles.keyPath),
cert: fs.readFileSync(this.sslFiles.certPath)
};
const serverHttps = createHttps(options, this.app);
if (onStart) {
onStart(serverHttps);
}
serverHttps.listen(this.sslPort, () => {
return console.log(`Express is listening at https://localhost:${this.sslPort}`);
});
this.httpsServer = serverHttps;
} catch (error) {
console.error("Error reading SSL certificates: ", error);
}
};
this.app = express();
this.functionsEndpoint = functionsEndpoint.startsWith("/") ? functionsEndpoint : `/${functionsEndpoint}`;
this.port = port;
this.applyMiddleware(express.json({
limit: options.bodySizeLimit || "100kb"
}));
this.applyMiddleware(express.urlencoded({ extended: true }));
this.applyMiddleware((req, res, next) => {
res.header("Access-Control-Allow-Origin", options.allowOrigin || "*");
res.header("Access-Control-Allow-Headers", "Authorization, X-API-KEY, Origin, X-Requested-With, Content-Type, Accept, Access-Control-Allow-Request-Method");
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
res.header("Allow", "GET, POST, PUT, DELETE, OPTIONS");
if (req.method === "OPTIONS") {
return res.sendStatus(200);
}
next();
});
this.app.use(this.functionsEndpoint, routes_default);
console.log("functionsEndpoint: ", this.functionsEndpoint);
}
configureSSL(sslPort, sslFiles) {
this.sslPort = sslPort;
this.sslFiles = sslFiles;
}
/**
* Starts the Express server.
* @param allowOrigin - The allowed origin for CORS. Default is '*'.
*/
start({
onHttpsStart,
onHttpStart
}) {
this.app.get("*", (req, res) => {
res.sendStatus(404);
});
this.openSSLServer(onHttpsStart);
this.openHttpServer(onHttpStart);
console.log(`SERVER_PID=${process.pid}`);
}
applyMiddleware(...args) {
this.app.use(...args);
}
};
export {
TowersExpress,
TowersFunctionsController
};