Moved joinPaths to BaseController as a protected static function + refactored large chunks of Router to work with the new stuff
All checks were successful
Publish to Private NPM Registry / publish (push) Successful in 33s
All checks were successful
Publish to Private NPM Registry / publish (push) Successful in 33s
This commit is contained in:
parent
21b5e7e873
commit
621222d69d
3 changed files with 139 additions and 58 deletions
145
src/Router.ts
145
src/Router.ts
|
|
@ -7,6 +7,12 @@ import { logMessage, LogLevel } from '@BridgemanAccessible/ba-logging';
|
||||||
import { BaseController } from './controllers/BaseController.js';
|
import { BaseController } from './controllers/BaseController.js';
|
||||||
import { ErrorController } from './controllers/ErrorController.js';
|
import { ErrorController } from './controllers/ErrorController.js';
|
||||||
|
|
||||||
|
import { GET_METADATA_KEY, GET_FORCE_PREFIX_METADATA_KEY } from './decorators/GET.js';
|
||||||
|
import { POST_METADATA_KEY, POST_FORCE_PREFIX_METADATA_KEY } from './decorators/POST.js';
|
||||||
|
import { PUT_METADATA_KEY, PUT_FORCE_PREFIX_METADATA_KEY } from './decorators/PUT.js';
|
||||||
|
import { DELETE_METADATA_KEY, DELETE_FORCE_PREFIX_METADATA_KEY } from './decorators/DELETE.js';
|
||||||
|
import { CHILD_CONTROLLER_METADATA_KEY } from './decorators/ChildController.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Router sets up the routes/endpoints for the App.
|
* The Router sets up the routes/endpoints for the App.
|
||||||
*
|
*
|
||||||
|
|
@ -60,7 +66,7 @@ export class Router {
|
||||||
* @param folder The folder that the file is in
|
* @param folder The folder that the file is in
|
||||||
* @returns An object with 2 properties: a `controllers` property which holds the controller class (or list of controller classes) in the "file" or null if the file is not a controller and `errorControllers` property which holds the error controller class (or list of error controller classes) in the "file" or null if the file is not an error controller.
|
* @returns An object with 2 properties: a `controllers` property which holds the controller class (or list of controller classes) in the "file" or null if the file is not a controller and `errorControllers` property which holds the error controller class (or list of error controller classes) in the "file" or null if the file is not an error controller.
|
||||||
*/
|
*/
|
||||||
private async checkIfControllerFile(file: string, folder: string): Promise<{ controllers: BaseController | BaseController[] | null, errorControllers: (new (...args: any[]) => ErrorController) | (new (...args: any[]) => ErrorController)[] | null }> {
|
private async checkIfControllerFile(file: string, folder: string): Promise<{ controllers: ( new (...args: any[]) => BaseController) | ( new (...args: any[]) => BaseController)[] | null, errorControllers: (new (...args: any[]) => ErrorController) | (new (...args: any[]) => ErrorController)[] | null }> {
|
||||||
// Get the path to the specific file
|
// Get the path to the specific file
|
||||||
const controllerPath = path.join(folder, file);
|
const controllerPath = path.join(folder, file);
|
||||||
|
|
||||||
|
|
@ -68,15 +74,26 @@ export class Router {
|
||||||
const stats = await fse.stat(controllerPath);
|
const stats = await fse.stat(controllerPath);
|
||||||
if(stats.isDirectory()) {
|
if(stats.isDirectory()) {
|
||||||
// Recurse into the directory
|
// Recurse into the directory
|
||||||
const controllersFoundInDir = await Promise.all((await fse.readdir(controllerPath)).map(file => this.checkIfControllerFile(file, controllerPath)));
|
const controllersFoundInDir = await Promise.all(
|
||||||
|
(
|
||||||
|
await fse.readdir(controllerPath)
|
||||||
|
)
|
||||||
|
.map(file => this.checkIfControllerFile(file, controllerPath))
|
||||||
|
);
|
||||||
const controllersInDir = controllersFoundInDir.filter(controller => controller !== null);
|
const controllersInDir = controllersFoundInDir.filter(controller => controller !== null);
|
||||||
if(controllersInDir.length === 0) {
|
if(controllersInDir.length === 0) {
|
||||||
return { controllers: null, errorControllers: null };
|
return { controllers: null, errorControllers: null };
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return {
|
return {
|
||||||
controllers: controllersInDir.map(controllerInDir => controllerInDir.controllers).length > 1 ? controllersInDir.map(controllerInDir => controllerInDir.controllers).filter(ctlr => ctlr !== null).flat() : controllersInDir.map(controllerInDir => controllerInDir.controllers)[0],
|
controllers: controllersInDir.map(controllerInDir => controllerInDir.controllers).length > 1
|
||||||
errorControllers: controllersInDir.map(controllerInDir => controllerInDir.errorControllers).length > 1 ? controllersInDir.map(controllerInDir => controllerInDir.errorControllers).filter(ctlr => ctlr !== null).flat() : controllersInDir.map(controllerInDir => controllerInDir.errorControllers)[0]
|
? controllersInDir.map(controllerInDir => controllerInDir.controllers)
|
||||||
|
.filter(ctlr => ctlr !== null).flat()
|
||||||
|
: controllersInDir.map(controllerInDir => controllerInDir.controllers)[0],
|
||||||
|
errorControllers: controllersInDir.map(controllerInDir => controllerInDir.errorControllers).length > 1
|
||||||
|
? controllersInDir.map(controllerInDir => controllerInDir.errorControllers)
|
||||||
|
.filter(ctlr => ctlr !== null).flat()
|
||||||
|
: controllersInDir.map(controllerInDir => controllerInDir.errorControllers)[0]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -106,7 +123,7 @@ export class Router {
|
||||||
// This is because native JavaScript doesn't have decorators (which are used to mark a class as a controller).
|
// This is because native JavaScript doesn't have decorators (which are used to mark a class as a controller).
|
||||||
// Consequently, we enforce that all controllers in addition to using the decorator must extend the `BaseController` class so that we can identify them.
|
// Consequently, we enforce that all controllers in addition to using the decorator must extend the `BaseController` class so that we can identify them.
|
||||||
// Further, note we can't do this as part of the decorator because of the way decorators get transpiled to JavaScript.
|
// Further, note we can't do this as part of the decorator because of the way decorators get transpiled to JavaScript.
|
||||||
const controllers: BaseController[] = classes
|
const controllers: (new (...args: any[]) => BaseController)[] = classes
|
||||||
.filter(exportedClassName => controllerModule[exportedClassName].prototype instanceof BaseController)
|
.filter(exportedClassName => controllerModule[exportedClassName].prototype instanceof BaseController)
|
||||||
.map(controllerClassName => {
|
.map(controllerClassName => {
|
||||||
return controllerModule[controllerClassName];
|
return controllerModule[controllerClassName];
|
||||||
|
|
@ -120,7 +137,7 @@ export class Router {
|
||||||
// This is because native JavaScript doesn't have decorators (which are used to mark a class as a error controller).
|
// This is because native JavaScript doesn't have decorators (which are used to mark a class as a error controller).
|
||||||
// Consequently, we enforce that all error controllers in addition to using the decorator must extend the `ErrorController` class so that we can identify them.
|
// Consequently, we enforce that all error controllers in addition to using the decorator must extend the `ErrorController` class so that we can identify them.
|
||||||
// Further, note we can't do this as part of the decorator because of the way decorators get transpiled to JavaScript.
|
// Further, note we can't do this as part of the decorator because of the way decorators get transpiled to JavaScript.
|
||||||
const errorControllers: ErrorController[] = classes
|
const errorControllers: (new (...args: any[]) => ErrorController)[] = classes
|
||||||
.filter(exportedClassName => controllerModule[exportedClassName].prototype instanceof ErrorController)
|
.filter(exportedClassName => controllerModule[exportedClassName].prototype instanceof ErrorController)
|
||||||
.map(controllerClassName => {
|
.map(controllerClassName => {
|
||||||
return controllerModule[controllerClassName];
|
return controllerModule[controllerClassName];
|
||||||
|
|
@ -149,6 +166,20 @@ export class Router {
|
||||||
return { controllers: foundControllers, errorControllers: foundErrorControllers };
|
return { controllers: foundControllers, errorControllers: foundErrorControllers };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Helper function to resolve the full path for a route */
|
||||||
|
private resolvePath(controller: (new (...args: any[]) => BaseController), path: string, forcePrefix: boolean, inheritedPrefix: string = '') {
|
||||||
|
const controllerBasePath = Reflect.getMetadata('basePath', controller);
|
||||||
|
const currentFullBase = (controller as any).joinPaths(inheritedPrefix, controllerBasePath);
|
||||||
|
|
||||||
|
// DEFAULT LOGIC: forcePrefix defaults to true if undefined
|
||||||
|
// ACCESS TRICK: Cast 'controller' to 'any' to access the protected static 'joinPaths'
|
||||||
|
if (forcePrefix !== false) {
|
||||||
|
return (controller as any).joinPaths(currentFullBase, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the routes in a controller
|
* Get the routes in a controller
|
||||||
*
|
*
|
||||||
|
|
@ -157,7 +188,7 @@ export class Router {
|
||||||
* @param controller The controller to get the routes from
|
* @param controller The controller to get the routes from
|
||||||
* @returns The routes in the controller
|
* @returns The routes in the controller
|
||||||
*/
|
*/
|
||||||
private getRoutesInController(controller: any) {
|
private getRoutesInController(controller: (new (...args: any[]) => BaseController), inheritedPrefix: string = '') {
|
||||||
if (typeof controller === 'undefined') {
|
if (typeof controller === 'undefined') {
|
||||||
logMessage(`Something went wrong while processing ${JSON.stringify(controller)}`, LogLevel.ERROR);
|
logMessage(`Something went wrong while processing ${JSON.stringify(controller)}`, LogLevel.ERROR);
|
||||||
throw new Error('The controller must be a class');
|
throw new Error('The controller must be a class');
|
||||||
|
|
@ -166,13 +197,13 @@ export class Router {
|
||||||
// Loop over all the methods in the provided class looking for methods that use the GET decorator
|
// Loop over all the methods in the provided class looking for methods that use the GET decorator
|
||||||
const pathsGET = Object.getOwnPropertyNames(controller.prototype)
|
const pathsGET = Object.getOwnPropertyNames(controller.prototype)
|
||||||
// Find all methods that have a Get metadata key (GET decorator)
|
// Find all methods that have a Get metadata key (GET decorator)
|
||||||
.filter((method) => Reflect.getMetadata('Get', controller.prototype, method))
|
.filter((method) => typeof Reflect.getMetadata(GET_METADATA_KEY, controller.prototype, method) !== 'undefined')
|
||||||
.map((method) => {
|
.map((method) => {
|
||||||
// Get the method
|
// Get the method
|
||||||
const fn = controller.prototype[method];
|
const fn = controller.prototype[method];
|
||||||
|
|
||||||
// Get the path
|
// Get the path
|
||||||
const path = Reflect.getMetadata('Get', controller.prototype, method) as string;
|
const path = this.resolvePath(controller, Reflect.getMetadata(GET_METADATA_KEY, controller.prototype, method), Reflect.getMetadata(GET_FORCE_PREFIX_METADATA_KEY, controller.prototype, method), inheritedPrefix);
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
});
|
});
|
||||||
|
|
@ -180,29 +211,29 @@ export class Router {
|
||||||
// Loop over all the methods in the provided class looking for methods that use the POST decorator
|
// Loop over all the methods in the provided class looking for methods that use the POST decorator
|
||||||
const pathsPOST = Object.getOwnPropertyNames(controller.prototype)
|
const pathsPOST = Object.getOwnPropertyNames(controller.prototype)
|
||||||
// Find all methods that have a Post metadata key (POST decorator)
|
// Find all methods that have a Post metadata key (POST decorator)
|
||||||
.filter((method) => Reflect.getMetadata('Post', controller.prototype, method))
|
.filter((method) => typeof Reflect.getMetadata(POST_METADATA_KEY, controller.prototype, method) !== 'undefined')
|
||||||
.map((method) => {
|
.map((method) => {
|
||||||
// Get the method
|
// Get the method
|
||||||
const fn = controller.prototype[method];
|
const fn = controller.prototype[method];
|
||||||
|
|
||||||
// Get the metadata object (which contains a path and middleware)
|
// Get the metadata object (which contains a path and middleware)
|
||||||
const postRoute = Reflect.getMetadata('Post', controller.prototype, method);
|
const postRoute = Reflect.getMetadata(POST_METADATA_KEY, controller.prototype, method);
|
||||||
const path = postRoute.path;
|
const path = this.resolvePath(controller, postRoute.path, Reflect.getMetadata(POST_FORCE_PREFIX_METADATA_KEY, controller.prototype, method), inheritedPrefix);
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Loop over all the methods in the provided class looking for methods that use the `@PUT` decorator
|
// Loop over all the methods in the provided class looking for methods that use the `@PUT` decorator
|
||||||
const pathsPUT = Object.getOwnPropertyNames(controller.prototype)
|
const pathsPUT = Object.getOwnPropertyNames(controller.prototype)
|
||||||
// Find all methods that have a `Put` metadata key (`@PUT` decorator)
|
// Find all methods that have a `Put` metadata key (`@PUT` decorator)
|
||||||
.filter((method) => Reflect.getMetadata('Put', controller.prototype, method))
|
.filter((method) => typeof Reflect.getMetadata(PUT_METADATA_KEY, controller.prototype, method) !== 'undefined')
|
||||||
.map((method) => {
|
.map((method) => {
|
||||||
// Get the method
|
// Get the method
|
||||||
const fn = controller.prototype[method];
|
const fn = controller.prototype[method];
|
||||||
|
|
||||||
// Get the metadata object (which contains a path and middleware)
|
// Get the metadata object (which contains a path and middleware)
|
||||||
const putRoute = Reflect.getMetadata('Put', controller.prototype, method);
|
const putRoute = Reflect.getMetadata(PUT_METADATA_KEY, controller.prototype, method);
|
||||||
const path = putRoute.path;
|
const path = this.resolvePath(controller, putRoute.path, Reflect.getMetadata(PUT_FORCE_PREFIX_METADATA_KEY, controller.prototype, method), inheritedPrefix);
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
});
|
});
|
||||||
|
|
@ -210,13 +241,15 @@ export class Router {
|
||||||
// Loop over all the methods in the provided class looking for methods that use the `@DELETE` decorator
|
// Loop over all the methods in the provided class looking for methods that use the `@DELETE` decorator
|
||||||
const pathsDELETE = Object.getOwnPropertyNames(controller.prototype)
|
const pathsDELETE = Object.getOwnPropertyNames(controller.prototype)
|
||||||
// Find all methods that have a `Delete` metadata key (`@DELETE` decorator)
|
// Find all methods that have a `Delete` metadata key (`@DELETE` decorator)
|
||||||
.filter((method) => Reflect.getMetadata('Delete', controller.prototype, method))
|
.filter((method) => typeof Reflect.getMetadata(DELETE_METADATA_KEY, controller.prototype, method) !== 'undefined')
|
||||||
.map((method) => {
|
.map((method) => {
|
||||||
// Get the method
|
// Get the method
|
||||||
const fn = controller.prototype[method];
|
const fn = controller.prototype[method];
|
||||||
|
|
||||||
// Get the metadata object (which contains a path and middleware)
|
// Get the metadata object (which contains a path and middleware)
|
||||||
const deleteRoute = Reflect.getMetadata('Delete', controller.prototype, method);
|
const deleteRoute = Reflect.getMetadata(DELETE_METADATA_KEY, controller.prototype, method);
|
||||||
const path = deleteRoute.path;
|
const path = this.resolvePath(controller, deleteRoute.path, Reflect.getMetadata(DELETE_FORCE_PREFIX_METADATA_KEY, controller.prototype, method), inheritedPrefix);
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -228,22 +261,30 @@ export class Router {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async processControllerRoutes(app: Application, decoratedController: any | any[], callSetup: boolean = true) {
|
async processControllerRoutes(app: Application, decoratedController: (new (...args: any[]) => BaseController) | (new (...args: any[]) => BaseController)[], callSetup: boolean = true, inheritedPrefix: string = '') {
|
||||||
let addedRoutes: string[] = [];
|
let addedRoutes: string[] = [];
|
||||||
|
|
||||||
// If the input is an array, recurse over the array
|
// If the input is an array, recurse over the array
|
||||||
if (Array.isArray(decoratedController)) {
|
if (Array.isArray(decoratedController)) {
|
||||||
//console.log('Is an array - recursing...')
|
//console.log('Is an array - recursing...')
|
||||||
addedRoutes = addedRoutes.concat((await Promise.all(decoratedController.map(async (decoratedControllerCls) => await this.processControllerRoutes(app, decoratedControllerCls, callSetup)))).flat());
|
|
||||||
|
addedRoutes = addedRoutes.concat(
|
||||||
|
(
|
||||||
|
await Promise.all(
|
||||||
|
decoratedController.map(
|
||||||
|
async (decoratedControllerCls) => await this.processControllerRoutes(app, decoratedControllerCls, callSetup, inheritedPrefix)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).flat());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const controller = Reflect.getMetadata('originalClass', decoratedController);
|
const controller = Reflect.getMetadata('originalClass', decoratedController) as (new (...args: any[]) => BaseController);
|
||||||
|
|
||||||
//console.log(`Processing ${(typeof controller).toString()} - ${typeof controller !== 'undefined' && controller !== null && typeof controller.name !== 'undefined' ? controller.name : 'unknown'} (${(typeof decoratedController).toString()} - ${typeof decoratedController !== 'undefined' && decoratedController !== null && typeof decoratedController.name !== 'undefined' ? decoratedController.name : 'unknown'})...`);
|
//console.log(`Processing ${(typeof controller).toString()} - ${typeof controller !== 'undefined' && controller !== null && typeof controller.name !== 'undefined' ? controller.name : 'unknown'} (${(typeof decoratedController).toString()} - ${typeof decoratedController !== 'undefined' && decoratedController !== null && typeof decoratedController.name !== 'undefined' ? decoratedController.name : 'unknown'})...`);
|
||||||
|
|
||||||
let routes: { GET: string[], POST: string[], PUT: string[], DELETE: string[] } = { GET: [], POST: [], PUT: [], DELETE: [] };
|
let routes: { GET: string[], POST: string[], PUT: string[], DELETE: string[] } = { GET: [], POST: [], PUT: [], DELETE: [] };
|
||||||
try {
|
try {
|
||||||
routes = this.getRoutesInController(controller);
|
routes = this.getRoutesInController(controller, inheritedPrefix);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
logMessage(`Something went wrong while processing ${JSON.stringify(controller)} (${JSON.stringify(decoratedController)})`, LogLevel.ERROR);
|
logMessage(`Something went wrong while processing ${JSON.stringify(controller)} (${JSON.stringify(decoratedController)})`, LogLevel.ERROR);
|
||||||
|
|
@ -264,11 +305,31 @@ export class Router {
|
||||||
routes.DELETE.forEach((path) => {
|
routes.DELETE.forEach((path) => {
|
||||||
addedRoutes.push(`DELETE ${path} from ${(new controller()).constructor.name}`);
|
addedRoutes.push(`DELETE ${path} from ${(new controller()).constructor.name}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const children = Reflect.getMetadata(CHILD_CONTROLLER_METADATA_KEY, controller);
|
||||||
|
if (typeof children !== 'undefined' && (children instanceof BaseController || (Array.isArray(children) && children.length > 0))) {
|
||||||
|
const childArray = Array.isArray(children) ? children : [children];
|
||||||
|
|
||||||
|
const controllerBasePath = Reflect.getMetadata('basePath', controller);
|
||||||
|
const currentFullBase = (controller as any).joinPaths(inheritedPrefix, controllerBasePath);
|
||||||
|
|
||||||
|
// RECURSE: We pass 'currentFullBase' as the new prefix
|
||||||
|
// We pass 'false' for isRoot because the parent's setup() handles their registration
|
||||||
|
const childRoutes = (
|
||||||
|
await Promise.all(
|
||||||
|
childArray.map(child =>
|
||||||
|
this.processControllerRoutes(app, child, false, currentFullBase)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).flat();
|
||||||
|
|
||||||
|
addedRoutes = addedRoutes.concat(childRoutes);
|
||||||
|
}
|
||||||
|
|
||||||
// Because we reuse this method for adding information about the child routes but we don't want to call the setup method (because it's called on the parent controller)
|
// Because we reuse this method for adding information about the child routes but we don't want to call the setup method (because it's called on the parent controller)
|
||||||
// We have a switch to determine if we should call the setup method or not.
|
// We have a switch to determine if we should call the setup method or not.
|
||||||
if (callSetup) {
|
if (callSetup) {
|
||||||
decoratedController.setup(app);
|
(decoratedController as any).setup(app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -292,7 +353,7 @@ export class Router {
|
||||||
// Get the controller classes from the files
|
// Get the controller classes from the files
|
||||||
const loadedControllerObjects = (await Promise.all(files.map(async (file) => (await this.checkIfControllerFile(file, this.controllersPath /*, app*/)))));
|
const loadedControllerObjects = (await Promise.all(files.map(async (file) => (await this.checkIfControllerFile(file, this.controllersPath /*, app*/)))));
|
||||||
|
|
||||||
const loadedControllers: BaseController[] = loadedControllerObjects
|
const loadedControllers: (new (...args: any[]) => BaseController)[] = loadedControllerObjects
|
||||||
.map(loadedControllerObjects => loadedControllerObjects.controllers)
|
.map(loadedControllerObjects => loadedControllerObjects.controllers)
|
||||||
.filter(controller => controller !== null)
|
.filter(controller => controller !== null)
|
||||||
.flat();
|
.flat();
|
||||||
|
|
@ -300,27 +361,47 @@ export class Router {
|
||||||
let addedRoutes: string[] = [];
|
let addedRoutes: string[] = [];
|
||||||
|
|
||||||
// Get all controllers that have the child controller decorator
|
// Get all controllers that have the child controller decorator
|
||||||
const controllersWithChildControllers = loadedControllers.filter(controller => Reflect.getMetadata('ChildController', controller) !== undefined);
|
const controllersWithChildControllers = loadedControllers.filter(controller => Reflect.getMetadata(CHILD_CONTROLLER_METADATA_KEY, controller) !== undefined);
|
||||||
|
|
||||||
// Get all those controllers that are designated as children controllers by parent controllers
|
// Get all those controllers that are designated as children controllers by parent controllers
|
||||||
const childrenControllers = controllersWithChildControllers.map(controller => Reflect.getMetadata('ChildController', controller));
|
const childrenControllers = controllersWithChildControllers.map(controller => {
|
||||||
|
const children = Reflect.getMetadata(CHILD_CONTROLLER_METADATA_KEY, controller) as ((new (...args: any[]) => BaseController) | (new (...args: any[]) => BaseController)[]);
|
||||||
|
|
||||||
|
return Array.isArray(children) ? children : [children];
|
||||||
|
}).flat();
|
||||||
|
|
||||||
// Get the exclusive set of controllers that don't fit in the group of controllers with child controllers OR a child controller themselves
|
// Get the exclusive set of controllers that don't fit in the group of controllers with child controllers OR a child controller themselves
|
||||||
const controllersWithoutChildren = loadedControllers.filter(controller => !controllersWithChildControllers.includes(controller) && !childrenControllers.includes(controller));
|
//const controllersWithoutChildren = loadedControllers.filter(controller => !controllersWithChildControllers.includes(controller) && !childrenControllers.includes(controller));
|
||||||
|
|
||||||
// Take the list of controllers with child controllers and the list without children (that excludes children themselves)
|
// Take the list of controllers with child controllers and the list without children (that excludes children themselves)
|
||||||
// And forma a list that has all child controllers remove
|
// And forma a list that has all child controllers remove
|
||||||
const topLevelControllers = [...controllersWithChildControllers, ...controllersWithoutChildren];
|
const topLevelControllers = loadedControllers.filter(controller => !childrenControllers.includes(controller)) //[...controllersWithChildControllers, ...controllersWithoutChildren];
|
||||||
|
|
||||||
//console.log(`Child controllers: ${childrenControllers.map(controller => (typeof controller).toString() + ' - ' + (typeof controller !== 'undefined' && controller !== null ? controller.name : 'unknown')).join(', ')}`);
|
//console.log(`Child controllers: ${childrenControllers.map(controller => (typeof controller).toString() + ' - ' + (typeof controller !== 'undefined' && controller !== null ? controller.name : 'unknown')).join(', ')}`);
|
||||||
|
|
||||||
// Add the routes for the child controllers to the list of added routes WITHOUT calling the setup method (as this is called in the parent controller's setup method)
|
// Add the routes for the child controllers to the list of added routes WITHOUT calling the setup method (as this is called in the parent controller's setup method)
|
||||||
addedRoutes = addedRoutes.concat((await Promise.all(childrenControllers.map(async (decoratedController) => await this.processControllerRoutes(app, decoratedController, false)))).flat());
|
/*addedRoutes = addedRoutes.concat(
|
||||||
|
(
|
||||||
|
await Promise.all(
|
||||||
|
childrenControllers.map(
|
||||||
|
async (decoratedController) => await this.processControllerRoutes(app, decoratedController, false)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).flat()
|
||||||
|
);*/
|
||||||
|
|
||||||
//console.log(`Top level controllers: ${topLevelControllers.map(controller => (typeof controller).toString() + ' - ' + (typeof controller !== 'undefined' && controller !== null ? controller.name : 'unknown')).join(', ')}`);
|
//console.log(`Top level controllers: ${topLevelControllers.map(controller => (typeof controller).toString() + ' - ' + (typeof controller !== 'undefined' && controller !== null ? controller.name : 'unknown')).join(', ')}`);
|
||||||
|
|
||||||
// Add the routes for the top level controllers to the list of added routes and call the setup method
|
// Add the routes for the top level controllers to the list of added routes and call the setup method
|
||||||
addedRoutes = addedRoutes.concat((await Promise.all(topLevelControllers.map(async (decoratedController) => await this.processControllerRoutes(app, decoratedController)))).flat());
|
addedRoutes = addedRoutes.concat(
|
||||||
|
(
|
||||||
|
await Promise.all(
|
||||||
|
topLevelControllers.map(
|
||||||
|
async (decoratedController) => await this.processControllerRoutes(app, decoratedController)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).flat()
|
||||||
|
);
|
||||||
|
|
||||||
logMessage('Routes added:', LogLevel.DEBUG);
|
logMessage('Routes added:', LogLevel.DEBUG);
|
||||||
addedRoutes.forEach(route => logMessage('\t' + route, LogLevel.DEBUG));
|
addedRoutes.forEach(route => logMessage('\t' + route, LogLevel.DEBUG));
|
||||||
|
|
@ -356,7 +437,7 @@ export class Router {
|
||||||
});
|
});
|
||||||
|
|
||||||
logMessage('Error controllers added:', LogLevel.DEBUG);
|
logMessage('Error controllers added:', LogLevel.DEBUG);
|
||||||
handledErrors.forEach(errorControllers => logMessage('\t' + errorControllers, LogLevel.DEBUG));
|
handledErrors.forEach(errorControllers => logMessage('\t' + errorControllers.constructor.name, LogLevel.DEBUG));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,26 @@
|
||||||
import type { Application } from 'express';
|
import type { Application } from 'express';
|
||||||
|
|
||||||
export abstract class BaseController {
|
export abstract class BaseController {
|
||||||
|
/** Helper function to join paths cleanly */
|
||||||
|
protected static joinPaths(base: string, path: string): string {
|
||||||
|
// Combine the base path and the specific route path
|
||||||
|
let fullPath = base + path;
|
||||||
|
|
||||||
|
// Remove double slashes (e.g., //user -> /user)
|
||||||
|
fullPath = fullPath.replace(/\/\//g, '/');
|
||||||
|
|
||||||
|
// Ensure it starts with / (in case base was empty and path was empty)
|
||||||
|
if (!fullPath.startsWith('/')) {
|
||||||
|
fullPath = '/' + fullPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove trailing slash (optional, unless it's just root '/')
|
||||||
|
if (fullPath.length > 1 && fullPath.endsWith('/')) {
|
||||||
|
fullPath = fullPath.slice(0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullPath;
|
||||||
|
}
|
||||||
|
|
||||||
static setup(app: Application) {}
|
static setup(app: Application) {}
|
||||||
}
|
}
|
||||||
|
|
@ -70,27 +70,6 @@ export function Controller<T extends { new (...args: any[]): BaseController }>(b
|
||||||
|
|
||||||
// We extend the class that is decorated and override the setup method to automatically setup the routes
|
// We extend the class that is decorated and override the setup method to automatically setup the routes
|
||||||
return class extends target {
|
return class extends target {
|
||||||
// Helper function to join paths cleanly
|
|
||||||
static joinPaths(base: string, path: string): string {
|
|
||||||
// Combine the base path and the specific route path
|
|
||||||
let fullPath = base + path;
|
|
||||||
|
|
||||||
// Remove double slashes (e.g., //user -> /user)
|
|
||||||
fullPath = fullPath.replace(/\/\//g, '/');
|
|
||||||
|
|
||||||
// Ensure it starts with / (in case base was empty and path was empty)
|
|
||||||
if (!fullPath.startsWith('/')) {
|
|
||||||
fullPath = '/' + fullPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove trailing slash (optional, unless it's just root '/')
|
|
||||||
if (fullPath.length > 1 && fullPath.endsWith('/')) {
|
|
||||||
fullPath = fullPath.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return fullPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup the routes for the controller.
|
* Setup the routes for the controller.
|
||||||
*
|
*
|
||||||
|
|
@ -101,7 +80,7 @@ export function Controller<T extends { new (...args: any[]): BaseController }>(b
|
||||||
// Order matters: Incoming Prefix + Current Controller Base Path
|
// Order matters: Incoming Prefix + Current Controller Base Path
|
||||||
// Ex: "" + "/api" = "/api"
|
// Ex: "" + "/api" = "/api"
|
||||||
// Ex: "/api" + "/v1" = "/api/v1"
|
// Ex: "/api" + "/v1" = "/api/v1"
|
||||||
const currentPrefix = this.joinPaths(pathPrefix, Reflect.getMetadata('basePath', target) as string);
|
const currentPrefix = BaseController.joinPaths(pathPrefix, Reflect.getMetadata('basePath', target) as string);
|
||||||
|
|
||||||
// If the decorated class is also decorated with the `@ChildController` decorator,
|
// If the decorated class is also decorated with the `@ChildController` decorator,
|
||||||
// then we call the child controller's setup method as well.
|
// then we call the child controller's setup method as well.
|
||||||
|
|
@ -133,7 +112,7 @@ export function Controller<T extends { new (...args: any[]): BaseController }>(b
|
||||||
// Get the path
|
// Get the path
|
||||||
const path = Reflect.getMetadata(GET_METADATA_KEY, target.prototype, method);
|
const path = Reflect.getMetadata(GET_METADATA_KEY, target.prototype, method);
|
||||||
|
|
||||||
const fullPath = Reflect.getMetadata(GET_FORCE_PREFIX_METADATA_KEY, target.prototype, method) ? this.joinPaths(currentPrefix, path) : path;
|
const fullPath = Reflect.getMetadata(GET_FORCE_PREFIX_METADATA_KEY, target.prototype, method) ? BaseController.joinPaths(currentPrefix, path) : path;
|
||||||
|
|
||||||
// Bind the method to the class instance
|
// Bind the method to the class instance
|
||||||
app.get(fullPath, ...[
|
app.get(fullPath, ...[
|
||||||
|
|
@ -196,7 +175,7 @@ export function Controller<T extends { new (...args: any[]): BaseController }>(b
|
||||||
const path = postRoute.path;
|
const path = postRoute.path;
|
||||||
const middleware: NextFunction[] = postRoute.middleware;
|
const middleware: NextFunction[] = postRoute.middleware;
|
||||||
|
|
||||||
const fullPath = Reflect.getMetadata(POST_FORCE_PREFIX_METADATA_KEY, target.prototype, method) ? this.joinPaths(currentPrefix, path) : path;
|
const fullPath = Reflect.getMetadata(POST_FORCE_PREFIX_METADATA_KEY, target.prototype, method) ? BaseController.joinPaths(currentPrefix, path) : path;
|
||||||
|
|
||||||
// Bind the method to the class instance
|
// Bind the method to the class instance
|
||||||
app.post(fullPath, ...[
|
app.post(fullPath, ...[
|
||||||
|
|
@ -248,7 +227,7 @@ export function Controller<T extends { new (...args: any[]): BaseController }>(b
|
||||||
const path = putRoute.path;
|
const path = putRoute.path;
|
||||||
const middleware = putRoute.middleware;
|
const middleware = putRoute.middleware;
|
||||||
|
|
||||||
const fullPath = Reflect.getMetadata(PUT_FORCE_PREFIX_METADATA_KEY, target.prototype, method) ? this.joinPaths(currentPrefix, path) : path;
|
const fullPath = Reflect.getMetadata(PUT_FORCE_PREFIX_METADATA_KEY, target.prototype, method) ? BaseController.joinPaths(currentPrefix, path) : path;
|
||||||
|
|
||||||
// Bind the method to the class instance
|
// Bind the method to the class instance
|
||||||
app.put(fullPath, ...[
|
app.put(fullPath, ...[
|
||||||
|
|
@ -295,7 +274,7 @@ export function Controller<T extends { new (...args: any[]): BaseController }>(b
|
||||||
const path = deleteRoute.path;
|
const path = deleteRoute.path;
|
||||||
const middleware = deleteRoute.middleware;
|
const middleware = deleteRoute.middleware;
|
||||||
|
|
||||||
const fullPath = Reflect.getMetadata(DELETE_FORCE_PREFIX_METADATA_KEY, target.prototype, method) ? this.joinPaths(currentPrefix, path) : path;
|
const fullPath = Reflect.getMetadata(DELETE_FORCE_PREFIX_METADATA_KEY, target.prototype, method) ? BaseController.joinPaths(currentPrefix, path) : path;
|
||||||
|
|
||||||
// Bind the method to the class instance
|
// Bind the method to the class instance
|
||||||
app.delete(fullPath, ...[
|
app.delete(fullPath, ...[
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue