rjweb-server
Version:
Easy and Robust Way to create a Web Server with Many Easy-to-use Features in NodeJS
306 lines (305 loc) • 9.18 kB
JavaScript
import RPath from "../path";
import { isRegExp } from "util/types";
import parsePath from "../../functions/parsePath";
import path from "path";
import fs from "fs";
import RouteWS from "./ws";
import RouteHTTP from "./http";
import RouteDefaultHeaders from "./defaultHeaders";
class RoutePath {
/** Generate Route Block */
constructor(path2, validations, headers, contexts) {
this.hasCalledGet = false;
this.httpPath = parsePath(path2);
this.routes = [];
this.statics = [];
this.webSockets = [];
this.loadPaths = [];
this.parsedHeaders = {};
this.validations = validations || [];
this.headers = headers || {};
this.externals = [];
if (Object.keys(this.headers).length > 0) {
const routeDefaultHeaders = new RouteDefaultHeaders();
this.externals.push({ object: routeDefaultHeaders });
for (const [key, value] of Object.entries(this.headers)) {
routeDefaultHeaders.add(key, value);
}
}
}
/**
* Add a Validation
* @example
* ```
* // The /api route will automatically check for correct credentials
* // Obviously still putting the prefix (in this case / from the RoutePath in front)
* const controller = new Server({ })
*
* controller.path('/api', (path) => path
* .validate(async(ctr, end) => {
* if (!ctr.headers.has('Authorization')) return end(ctr.status(401).print('Unauthorized'))
* if (ctr.headers.get('Authorization') !== 'key123 or db request ig') return end(ctr.status(401).print('Unauthorized'))
* })
* .redirect('/pics', 'https://google.com/search?q=devil')
* )
* ```
* @since 3.2.1
*/
validate(code) {
this.validations.push(code);
return this;
}
/**
* Add a HTTP Route
* @example
* ```
* controller.path('/', (path) => path
* .http('GET', '/hello', (ws) => ws
* .onRequest(async(ctr) => {
* ctr.print('Hello bro!')
* })
* )
* )
* ```
* @since 6.0.0
*/
http(method, path2, callback) {
if (this.routes.some((obj) => isRegExp(obj.path) ? false : obj.path.path === parsePath(path2)))
return this;
const routeHTTP = new RouteHTTP(path2, method, this.validations, this.parsedHeaders);
this.externals.push({ object: routeHTTP, addPrefix: this.httpPath });
callback(routeHTTP);
return this;
}
/**
* Add a Websocket Route
* @example
* ```
* controller.path('/', (path) => path
* .ws('/uptime', (ws) => ws
* .onConnect(async(ctr) => {
* console.log('Connected to ws!')
* })
* .onMessage((ctr) => {
* console.log('Received message', ctr.message)
* })
* .onClose((ctr) => {
* console.log('Disconnected from ws!')
* })
* )
* )
* ```
* @since 5.4.0
*/
ws(path2, callback) {
if (this.webSockets.some((obj) => isRegExp(obj.path) ? false : obj.path.path === parsePath(path2)))
return this;
const routeWS = new RouteWS(path2, this.validations, this.parsedHeaders);
this.externals.push({ object: routeWS, addPrefix: this.httpPath });
callback(routeWS);
return this;
}
/**
* Add Default Headers
* @example
* ```
* controller.path('/', (path) => path
* .defaultHeaders((dH) => dH
* .add('X-Api-Version', '1.0.0')
* )
* )
* ```
* @since 6.0.0
*/
defaultHeaders(callback) {
const routeDefaultHeaders = new RouteDefaultHeaders();
this.externals.push({ object: routeDefaultHeaders });
callback(routeDefaultHeaders);
this.headers = Object.assign(this.headers, routeDefaultHeaders["defaultHeaders"]);
return this;
}
/**
* Add a Redirect
* @example
* ```
* // The /devil route will automatically redirect to google.com
* // Obviously still putting the prefix (in this case / from the RoutePath in front)
* const controller = new Server({ })
*
* controller.path('/', (path) => path
* .redirect('/devil', 'https://google.com')
* .redirect('/devilpics', 'https://google.com/search?q=devil')
* )
* ```
* @since 3.1.0
*/
redirect(request, redirect) {
this.routes.push({
type: "http",
method: "GET",
path: new RPath("GET", parsePath([this.httpPath, request])),
onRequest: (ctr) => ctr.redirect(redirect),
data: {
validations: this.validations,
headers: this.parsedHeaders
},
context: { data: {}, keep: true }
});
return this;
}
/**
* Serve Static Files
* @example
* ```
* // All Files in "./static" will be served dynamically so they wont be loaded as routes by default
* // Due to the hideHTML Option being on files will be served differently, /index.html -> /; /about.html -> /about; /contributors/index.html -> /contributors
* const controller = new Server({ })
*
* controller.path('/', (path) => path
* .static('./static', {
* hideHTML: true, // If enabled will remove .html ending from files
* addTypes: true, // If enabled will automatically add content-types to some file endings (including the custom ones defined in the main config)
* })
* )
* ```
* @since 3.1.0
*/
static(folder, options = {}) {
const addTypes = options?.addTypes ?? true;
const compress = options?.compress ?? true;
const hideHTML = options?.hideHTML ?? false;
this.statics.push({
type: "static",
path: new RPath("GET", parsePath(this.httpPath)),
location: folder,
data: {
doCompress: compress,
addTypes,
hideHTML,
validations: this.validations,
headers: this.parsedHeaders
}
});
return this;
}
/**
* Load CJS Route Files
* @example
* ```
* // All Files in "./routes" ending with .js will be loaded as routes
* const controller = new Server({ })
*
* controller.path('/', (path) => path
* .loadCJS('./routes')
* )
* ```
* @since 3.1.0
*/
loadCJS(folder, options = {}) {
const fileBasedRouting = options?.fileBasedRouting ?? false;
if (!fs.existsSync(path.resolve(folder)))
throw Error("The CJS Function folder wasnt found!");
this.loadPaths.push({
path: path.resolve(folder),
prefix: this.httpPath,
type: "cjs",
validations: this.validations,
fileBasedRouting,
headers: this.parsedHeaders
});
return this;
}
/**
* Load ESM Route Files
* @example
* ```
* // All Files in "./routes" ending with .js will be loaded as routes
* const controller = new Server({ })
*
* controller.path('/', (path) => path
* .loadESM('./routes')
* )
* ```
* @since 4.0.0
*/
loadESM(folder, options = {}) {
const fileBasedRouting = options?.fileBasedRouting ?? false;
if (!fs.existsSync(path.resolve(folder)))
throw Error("The ESM Function folder wasnt found!");
this.loadPaths.push({
path: path.resolve(folder),
prefix: this.httpPath,
type: "esm",
validations: this.validations,
fileBasedRouting,
headers: this.parsedHeaders
});
return this;
}
/**
* Add a new Block of Routes with a Prefix
* @example
* ```
* const controller = new Server({ })
*
* controller.path('/', (path) => path
* .http('GET', '/cool', (http) => http
* .onRequest((ctr) => {
* ctr.print('cool!')
* })
* )
* .path('/api', (path) => path
* .context({
* version: '1.0.0'
* })
* .http('GET', '/', (http) => http
* .onRequest((ctr) => {
* ctr.print(`Welcome to the API!, Version ${ctr["@"].version}`)
* })
* )
* )
* )
* ```
* @since 5.0.0
*/
path(prefix, router) {
if ("getData" in router) {
this.externals.push({ object: router, addPrefix: parsePath([this.httpPath, prefix]) });
} else {
const routePath = new RoutePath(parsePath([this.httpPath, prefix]), [...this.validations], Object.assign({}, this.headers));
this.externals.push({ object: routePath });
router(routePath);
}
return this;
}
/**
* Internal Method for Generating Routes Object
* @since 6.0.0
*/
async getData() {
if (!this.hasCalledGet)
for (const external of this.externals) {
const result = await external.object.getData(external.addPrefix ?? "/");
if ("routes" in result && result.routes.length > 0)
this.routes.push(...result.routes);
if ("webSockets" in result && result.webSockets.length > 0)
this.webSockets.push(...result.webSockets);
if ("statics" in result && result.statics.length > 0)
this.statics.push(...result.statics);
if ("loadPaths" in result && result.loadPaths.length > 0)
this.loadPaths.push(...result.loadPaths);
if ("defaultHeaders" in result)
this.parsedHeaders = Object.assign(this.parsedHeaders, result.defaultHeaders);
}
this.hasCalledGet = true;
return {
routes: this.routes,
webSockets: this.webSockets,
statics: this.statics,
loadPaths: this.loadPaths
};
}
}
export {
RoutePath as default
};