@projectinvicta/nails 2.0.15 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/README.md +70 -84
  2. package/bin/test/test_init.sh +33 -0
  3. package/index.ts +11 -0
  4. package/lib/Controller.ts +207 -0
  5. package/lib/Nails.ts +210 -0
  6. package/lib/Router.ts +180 -0
  7. package/lib/{application.js → application.ts} +8 -3
  8. package/lib/config.ts +74 -0
  9. package/package.json +14 -11
  10. package/spec/controller.spec.js +5 -51
  11. package/spec/nails.spec.js +1 -0
  12. package/spec/router.spec.js +5 -5
  13. package/spec/services/integration/config/db.ts +7 -0
  14. package/spec/services/integration/config/service.js +8 -8
  15. package/spec/services/integration/server/controllers/classbased_controller.js +2 -2
  16. package/spec/services/integration/server/controllers/default_json_controller.js +19 -7
  17. package/spec/services/integration/server/controllers/error_controller.js +2 -2
  18. package/spec/services/integration/server/controllers/home_controller.js +6 -28
  19. package/spec/services/integration/server/controllers/json_controller.js +2 -2
  20. package/spec/services/integration/server/controllers/manualrenderasync_controller.js +2 -2
  21. package/spec/services/integration/server/controllers/mjs_controller.mjs +2 -6
  22. package/spec/services/integration/server/controllers/modeltest_controller.js +6 -9
  23. package/spec/services/integration/server/controllers/websocket_controller.js +1 -1
  24. package/spec/services/integration/server/models/dog.js +7 -5
  25. package/spec/services/integration/server/models/owner.js +13 -0
  26. package/spec/services/integration/server.js +3 -4
  27. package/spec/services.integration.spec.js +45 -17
  28. package/templates/default/config/db.ts +13 -0
  29. package/{spec/services/integration_sequelize/config/mimes.js → templates/default/config/mimes.ts} +42 -61
  30. package/templates/default/config/routes.ts +22 -0
  31. package/templates/{config/service.js → default/config/service.ts} +2 -2
  32. package/templates/default/package.json +8 -2
  33. package/templates/default/public/README.xml +68 -85
  34. package/templates/default/server/controllers/home_controller.js +2 -2
  35. package/templates/{server/controllers/home_controller.js → default/server/controllers/users_controller.ts} +29 -34
  36. package/templates/default/server/models/Dog.ts +8 -0
  37. package/templates/default/server/models/User.ts +14 -0
  38. package/templates/default/spec/User.test.js +7 -5
  39. package/templates/default/spec/home_controller.test.js +3 -3
  40. package/index.js +0 -6
  41. package/lib/collection.js +0 -6
  42. package/lib/controller.js +0 -182
  43. package/lib/database_connector.js +0 -12
  44. package/lib/firebase_connector.js +0 -94
  45. package/lib/model_v2.js +0 -24
  46. package/lib/mongoose_connector.js +0 -49
  47. package/lib/mongoose_mem_connector.js +0 -21
  48. package/lib/nails.js +0 -244
  49. package/lib/router.js +0 -202
  50. package/lib/sequelize_connector.js +0 -31
  51. package/lib/server.js +0 -1
  52. package/spec/model_v2.spec.js +0 -75
  53. package/spec/mongodb_connector.util.js +0 -30
  54. package/spec/mongoose_connector.util.js +0 -20
  55. package/spec/sequelize_connector.spec.js +0 -91
  56. package/spec/sequelize_connector.util.js +0 -18
  57. package/spec/services/integration/config/db.js +0 -14
  58. package/spec/services/integration_sequelize/README.md +0 -5
  59. package/spec/services/integration_sequelize/client/css/styles.css +0 -0
  60. package/spec/services/integration_sequelize/client/download.jpg +0 -0
  61. package/spec/services/integration_sequelize/client/favicon.ico +0 -0
  62. package/spec/services/integration_sequelize/client/index.html +0 -9
  63. package/spec/services/integration_sequelize/client/js/client.js +0 -0
  64. package/spec/services/integration_sequelize/client/js/components/app.jsx +0 -15
  65. package/spec/services/integration_sequelize/config/db.js +0 -4
  66. package/spec/services/integration_sequelize/config/routes.js +0 -25
  67. package/spec/services/integration_sequelize/config/service.js +0 -48
  68. package/spec/services/integration_sequelize/config/ssl/certificate.pem +0 -22
  69. package/spec/services/integration_sequelize/config/ssl/csr.csr +0 -17
  70. package/spec/services/integration_sequelize/config/ssl/key.pem +0 -28
  71. package/spec/services/integration_sequelize/config/ssl/private_key.pem +0 -30
  72. package/spec/services/integration_sequelize/config/ssl/public_key.pem +0 -9
  73. package/spec/services/integration_sequelize/package.json +0 -23
  74. package/spec/services/integration_sequelize/server/controllers/default_json_controller.js +0 -21
  75. package/spec/services/integration_sequelize/server/controllers/home_controller.js +0 -39
  76. package/spec/services/integration_sequelize/server/models/dog.js +0 -7
  77. package/spec/services/integration_sequelize/server/models/owner.js +0 -10
  78. package/spec/services/integration_sequelize/server/views/defaultjson/testnojson.ejs +0 -1
  79. package/spec/services/integration_sequelize/server/views/testreact/testreact.ejs +0 -15
  80. package/spec/services/integration_sequelize/server.js +0 -9
  81. package/spec/services.integration_sequelize.spec.js +0 -60
  82. package/templates/bin/promote.sh +0 -20
  83. package/templates/bin/rollout.sh +0 -74
  84. package/templates/bin/server.js +0 -6
  85. package/templates/bin/start.sh +0 -16
  86. package/templates/common/readme_fetcher.js +0 -4
  87. package/templates/config/db.js +0 -19
  88. package/templates/config/mimes.js +0 -59
  89. package/templates/config/routes.js +0 -38
  90. package/templates/config/ssl/certificate.pem +0 -22
  91. package/templates/config/ssl/csr.csr +0 -17
  92. package/templates/config/ssl/key.pem +0 -28
  93. package/templates/config/ssl/private_key.pem +0 -30
  94. package/templates/config/ssl/public_key.pem +0 -9
  95. package/templates/default/config/db.js +0 -19
  96. package/templates/default/config/mimes.js +0 -59
  97. package/templates/default/config/routes.js +0 -38
  98. package/templates/default/config/service.js +0 -45
  99. package/templates/default/server/models/User.js +0 -18
  100. package/templates/package-lock.json +0 -9048
  101. package/templates/package.json +0 -43
  102. package/templates/public/README.xml +0 -332
  103. package/templates/public/css/styles.css +0 -17
  104. package/templates/public/download.jpg +0 -0
  105. package/templates/public/favicon.ico +0 -0
  106. package/templates/public/index.html +0 -9
  107. package/templates/public/js/client.js +0 -1
  108. package/templates/server/models/User.js +0 -18
  109. package/templates/server/views/home/index.ejs +0 -14
  110. package/templates/server/views/partials/javascripts.ejs +0 -1
  111. package/templates/server/views/partials/reactapp.ejs +0 -1
  112. package/templates/server/views/partials/styles.ejs +0 -3
  113. package/templates/spec/User.test.js +0 -20
  114. package/templates/spec/home_controller.test.js +0 -28
  115. package/templates/spec/setupTests.js +0 -0
  116. package/templates/src/AboutPage.jsx +0 -9
  117. package/templates/src/HomePage.jsx +0 -9
  118. package/templates/src/Layout.jsx +0 -78
  119. package/templates/src/ReadmePage.jsx +0 -7
  120. package/templates/src/app.jsx +0 -29
  121. package/templates/src/components/ReadmeLoader.jsx +0 -13
  122. package/templates/src/styles/appstyles.css +0 -3
  123. package/templates/vite.config.ts +0 -42
package/README.md CHANGED
@@ -1,33 +1,34 @@
1
- # Nails-Boilerplate: A Node Webservice Framework
1
+ # Nails: A Node Webservice Framework
2
2
 
3
- This framework is designed to provide a lightweight, configurable MVC backend
4
- for node developers. With minimal dependencies, Nails offers a greater
5
- syntactical familiarity than php alongside the creative freedom of well developed
6
- server framework solutions like Rails and Django.
3
+ Nails is a lightweight, configurable MVC-inspired backend framework for Node.js. With minimal dependencies, Nails offers a familiar syntax for developers coming from frameworks like Ruby on Rails or Django, while providing the flexibility of JavaScript.
7
4
 
8
- This boilerplate offers the basic necessities to get your MVC site off the ground.
9
- The modules used in Nails Boilerplate can be easily extended to produce the custom
10
- functionality to fit your needs, and you are encouraged to do so.
5
+ Nails provides the essential building blocks to get your MVC-style application up and running quickly. The modules used in Nails can be easily extended to produce the custom functionality to fit your needs, and you are encouraged to do so.
11
6
 
12
- ## Install
7
+ ## Getting Started
13
8
 
14
- ```
15
- sudo npm install -g nails-boilerplate
9
+ There are two recommended ways to get started with Nails.
16
10
 
17
- nails init <app_name>
11
+ ### Using NPX (Recommended)
12
+ You can create a new Nails project without a global installation using `npx`:
13
+
14
+ ```bash
15
+ npx @projectinvicta/nails init <app_name>
18
16
  ```
19
17
 
20
- This will initialize a barebones app in the directory of the same name. Take a
21
- look at the self-documented config files and example controller and view before
22
- getting started. Additional controllers and views will automatically be imported
23
- into nails. Now just hook the new controllers in with some new routes and you're
24
- off to a good start.
18
+ ### Using Global Install
19
+ Alternatively, you can install the package globally:
25
20
 
21
+ ```bash
22
+ npm install -g @projectinvicta/nails
23
+
24
+ nails init <app_name>
26
25
  ```
27
- cd app_name
28
26
 
29
- npm install
27
+ After initializing your app, navigate into the new directory and start the server:
30
28
 
29
+ ```bash
30
+ cd <app_name>
31
+ npm install
31
32
  npm start
32
33
  ```
33
34
 
@@ -36,15 +37,15 @@ npm start
36
37
  For your convenience, here is a quick outline of the main components of a nails service.
37
38
  Remember: each object comes with an example file to use for reference when building your service.
38
39
 
39
- ### Config
40
- Your configuration files are stored in app_name/config/. There are three default config files:
41
-
42
- ```
43
- service.js
44
- routes.js
45
- db.js
40
+ ### Named Exports
41
+ `nails` now provides a number of useful named exports. You can import them as follows:
42
+ ```js
43
+ import Nails, { Controller, Model, DataTypes, /* config types */ } from '@projectinvicta/nails';
46
44
  ```
47
45
 
46
+ ### Config
47
+ Your configuration files are stored in app_name/config/.
48
+
48
49
  Each default config file is annotated with comments documenting each field to
49
50
  help you tailor your service to your needs.
50
51
 
@@ -65,7 +66,7 @@ fields. The resulting config will be available to your service through the nails
65
66
  module:
66
67
 
67
68
  ```js
68
- import nails from 'nails-boilerplate';
69
+ import Nails from '@projectinvicta/nails';
69
70
 
70
71
  const service_config = nails.config
71
72
  ```
@@ -85,7 +86,7 @@ then `service_config.yourCustomField` as defined above will be equal to
85
86
 
86
87
  #### routes.js
87
88
  *routes.js* is a list defining mappings from a url path to a *Controller* and
88
- *Action*. Each entry in the list is an array with three elements:
89
+ *Action*. Routes are now strongly typed (see `lib/config.ts` and `lib/Router.ts`). Each entry in the list is an array with three elements:
89
90
  `[method, path, options]`
90
91
 
91
92
  **method** is a string defining the HTTP request method of the route. Supported
@@ -134,15 +135,9 @@ parameters work.
134
135
 
135
136
  #### db.js
136
137
 
137
- Quickly configure your database connection here. Nails comes pre-configured to
138
- use the sequelize connector, giving your models sequelize support. The initial setup
139
- uses a *sqlite3* database file `config/development.db` and an in-memory database in the test environment. Change the address to change the location and
140
- version of your desired sql database. Check out [Sequelize](https://sequelize.org)
141
- for more info.
142
-
143
- Alternatively, you can configure a connection to MongoDB using the mongoose_connector.js.
144
- If enabled, models will accept [Mongoose](https://mongoosejs.com/docs/) schemas and will
145
- be backed by the desired MongoDB. Consider using the in-memory DB during development.
138
+ Quickly configure your database connection here. The initial setup uses a *sqlite3* database file `config/development.db`
139
+ and an in-memory database in the test environment. Change the address to change the location and
140
+ version of your desired sql database. Check out [Sequelize](https://sequelize.org) for more info.
146
141
 
147
142
  ## Controller
148
143
 
@@ -153,10 +148,9 @@ actions, receiving **params**, **request**, and **response** as arguments.
153
148
 
154
149
  For Example:
155
150
  ``` js
156
- // const Controller = requre("nails-boilerplate").Controller
157
- import nails from 'nails-boilerplate';
151
+ import { Controller } from '@projectinvicta/nails';
158
152
 
159
- class HomeController extends nails.Controller {
153
+ class HomeController extends Controller {
160
154
  index(params, request, response) {
161
155
  // default action
162
156
  }
@@ -193,7 +187,9 @@ it will accept GET requests to /data instead. All local routes are
193
187
  implicitly routed to their respective parent controllers.
194
188
 
195
189
  ```js
196
- export default class UsersController extends nails.Controller {
190
+ import { Controller } from '@projectinvicta/nails';
191
+
192
+ export default class UsersController extends Controller {
197
193
  routes = [
198
194
  // Routes requests to /absolute/path
199
195
  ['get', '/absolute/path', {action: 'actionA'}],
@@ -280,12 +276,15 @@ You can configure an individual route to respond with JSON by setting the `json`
280
276
  ```
281
277
 
282
278
  ##### API Controllers
283
- By setting `json` to `true`, all actions in a controller will respond with JSON.
279
+ By setting `json` to `true`, all actions in a controller will respond with JSON by default.
280
+ This can be overridden for individual routes by setting `{json: false}` in the route options.
284
281
 
285
282
  ```js
286
- class YourApiController extends nails.Controller {
283
+ import { Controller } from '@projectinvicta/nails';
284
+
285
+ class YourApiController extends Controller {
287
286
  json = true;
288
-
287
+
289
288
  action(params, request, response) {
290
289
  return {your: 'jsonresponse'};
291
290
  }
@@ -294,27 +293,28 @@ class YourApiController extends nails.Controller {
294
293
 
295
294
  ## Model
296
295
 
297
- Models are programmatic representations of data you wish to persist in a
298
- database. The constructor for Model accepts two arguments: the `modelName` and an
299
- `options` object which is passed to the database connector module.
296
+ Models are programmatic representations of data you wish to persist in a database. Nails connects to
297
+ your database of choice using the Sequelize ORM.
300
298
 
301
299
  ### Sequelize Models
302
300
 
303
301
  Sequelize models are subclasses of
304
302
  [Sequelize Models][sequelize_model_docs], and come with the `count()`, `findAll()`,
305
303
  and `create()` methods, to name a few. You can define your own models by
306
- extending an instance of the `Model` class provided by Nails:
304
+ extending an instance of the `Model` class provided by Nails. Model files must export the Model subclass as a default export and a named `schema` export.
305
+ They can also export an `options` object to provide ModelInitializationOptions as well as `defer`, `finalize`, and `migrate` functions.
307
306
 
308
307
  ```js
309
- // const Model = require("nails-boilerplate").Model;
310
- import nails from 'nails-boilerplate';
311
- import {DataTypes} from 'sequelize';
312
- schema = {
308
+ import { Model, DataTypes } from '@projectinvicta/nails';
309
+
310
+ // REQUIRED
311
+ export const schema = {
313
312
  name: {type: DataTypes.STRING, allowNull: false},
314
313
  email: {type: DataTypes.STRING, allowNull: false}
315
314
  };
316
315
 
317
- options = {
316
+ // Optional
317
+ export const options = {
318
318
  indexes: [
319
319
  {
320
320
  unique: true,
@@ -323,38 +323,37 @@ options = {
323
323
  ],
324
324
  };
325
325
 
326
- export default class User extends new Model("User", {schema, options}) {
326
+ // REQUIRED
327
+ export default class User extends Model {
327
328
  someHelperMethod() {
328
329
  // This method will be available on all instances of User and is
329
330
  // an ideal way to simplify data manipulation.
330
331
  }
331
332
  };
332
333
 
333
- ```
334
+ // Optional
335
+ export async function defer(models) {
336
+ // define associations here
337
+ }
334
338
 
335
- ### Mongoose Models
336
- Mongoose models are subclasses of
337
- [Mongoose Models][mongoose_model_docs], and come with the `save()`, `find()`,
338
- and `where()` methods, to name a few. You can define your own models by
339
- extending an instance of the `Model` class provided by Nails:
339
+ // Optional
340
+ export async function finalize(models) {
341
+ // any final model setup
342
+ }
340
343
 
341
- ``` js
342
- // const Model = require("nails-boilerplate").Model;
343
- import nails from 'nails-boilerplate';
344
- const userSchema = {name: String, email: String};
345
- export default class User extends new Model("User", {schema: userSchema}) {
346
- // Define your helper methods here
347
- };
344
+ // Optional
345
+ export async function migrate(queryInterface) {
346
+ // migration logic
347
+ }
348
348
  ```
349
349
 
350
- The `schema` option for Mongoose Models accepts a schema field that is used
351
- to define how documents are stored in MongoDB.
352
-
353
350
  ### Model Library
354
351
  Nails will store all instantialized models in a single object called `MODELS`. By accessing these models via the library, you can avoid circular dependencies and ensure all models have been fully initialized.
355
352
 
356
353
  ```js
357
- class User extends nails.Model("User", {schema, options}) {
354
+ import Nails, { Model } from '@projectinvicta/nails';
355
+ // ...
356
+ class User extends Model {
358
357
  // A helper method which depends on anoher model using the
359
358
  // Nails Model Library rather than directly importing the model.
360
359
  async findFriends() {
@@ -364,18 +363,6 @@ class User extends nails.Model("User", {schema, options}) {
364
363
  ```
365
364
  This design pattern is not always necessary, but will help avoid circular dependencies.
366
365
 
367
- ### Database Connectors
368
-
369
- Database connectors are intermediaries which define how a Model interacts with
370
- a database. Database connector modules need to export two methods:
371
- * _connect(db_config)_ uses the db config defined in *db.js* to connect to
372
- a database. This function will be called once by Nails.
373
- * _generateModelSuperclass(name, options)_ uses the provided Model name and
374
- options to generate a Model prototype for use as an interface. A Model
375
- interface is generated for each of your models, allowing them to interact with
376
- a database. Ideally, interfaces will define save() and find() methods, but
377
- these methods and their implementations are up to the individual connector.
378
-
379
366
  ## View
380
367
  Views are dynamic templates used to render an html response for a browser.
381
368
  Nails comes prepackaged with EJS templates.
@@ -415,5 +402,4 @@ Enjoy! Feature requests, bug reports, and comments are welcome on github.
415
402
 
416
403
  [express_routing_docs]: https://expressjs.com/en/guide/routing.html
417
404
  [express_request_docs]: https://expressjs.com/en/5x/api.html#req
418
- [mongoose_model_docs]: https://mongoosejs.com/docs/api/model.html
419
405
  [sequelize_model_docs]: https://sequelize.org/docs/v6/core-concepts/model-basics/
@@ -0,0 +1,33 @@
1
+ #!/bin/bash
2
+
3
+ # Store the fully qualified location of the local nails module directory
4
+ NAILS_MODULE_DIR=$(pwd)
5
+
6
+ # Create a temporary directory for the test project with a datestring
7
+ DATE_STRING=$(date +%Y%m%d%H%M%S)
8
+ TEST_DIR="/tmp/test_project_$DATE_STRING"
9
+ mkdir -p $TEST_DIR
10
+
11
+ # Create a new nails project using the local version of nails-boilerplate
12
+ npx $NAILS_MODULE_DIR/ init $TEST_DIR
13
+
14
+ # Change directory to the test project
15
+ cd $TEST_DIR
16
+
17
+ # Link the local @projectinvicta/nails module
18
+ npm link $NAILS_MODULE_DIR
19
+
20
+ # Install other dependencies
21
+ npm install
22
+
23
+ # Run tests in the new test project
24
+ npm test
25
+
26
+ # Return to the original nails module directory
27
+ cd $NAILS_MODULE_DIR
28
+
29
+ # Unlink the local @projectinvicta/nails module
30
+ npm unlink @projectinvicta/nails
31
+
32
+ # Clean up the temporary directory
33
+ rm -rf $TEST_DIR
package/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ import Nails from './lib/Nails.ts';
2
+ import ControllerInternal from './lib/Controller.ts';
3
+
4
+ export { DataTypes } from 'sequelize';
5
+
6
+ export default Nails;
7
+
8
+ export const Controller = ControllerInternal;
9
+ export * from './lib/config.ts';
10
+
11
+ export { Model } from 'sequelize';
@@ -0,0 +1,207 @@
1
+ import { type Request, type Response } from 'express';
2
+ import { type WebSocket } from 'ws';
3
+ import { type RouteOptions, type RouteDefinition, type ControllerDoRouteParams } from './config.ts'; // Assuming RouteDefinition is exported from Config.ts
4
+
5
+ // Define a basic Router interface based on its usage
6
+ interface Router {
7
+ on(event: string, listener: Function): void;
8
+ removeAllListeners(event: string): void;
9
+ addRoutes(routes: RouteDefinition[]): void;
10
+ }
11
+
12
+ // Global router instance
13
+ let router: Router;
14
+ let controller_proto: Controller; // This will be assigned an instance of Controller
15
+
16
+ // TODO: refactor error generation
17
+ const NAME_REQUIRED_ERROR = (): Error => {
18
+ return new Error(
19
+ 'FATAL ERROR::: Named function required for Controller constructor method');
20
+ };
21
+
22
+ const DISABLE_AUTORENDER = new (class DisableAutorender {});
23
+
24
+ // The base controller definition
25
+ class Controller {
26
+ // Static properties
27
+ static router: Router; // Static reference to the router
28
+ static models: Record<string, any>; // Static reference to models, assuming key-value pairs
29
+
30
+ static get DISABLE_AUTORENDER(): typeof DISABLE_AUTORENDER {
31
+ return DISABLE_AUTORENDER;
32
+ }
33
+
34
+ static setRouter(router_singleton: Router): void {
35
+ Controller.router = router = router_singleton;
36
+ }
37
+
38
+ static setModels(models: Record<string, any>): void {
39
+ Controller.prototype.models = models;
40
+ }
41
+
42
+ // Instance properties
43
+ models: Record<string, any>; // This will be assigned via Controller.setModels
44
+ routes: RouteDefinition[]; // Assuming routes are defined on subclasses
45
+ json: boolean;
46
+
47
+ constructor() {
48
+ const controllerName = this._getControllerName();
49
+ router.removeAllListeners('dispatchTo:' + controllerName);
50
+ router.on('dispatchTo:' + controllerName, this._do.bind(this));
51
+ }
52
+
53
+ _getControllerName(): string {
54
+ return this.constructor.name.toLowerCase().replace(/controller$/, '');
55
+ }
56
+
57
+ /** Initializes local and global routes defined on the Controller subclass */
58
+ _registerControllerRoutes(): void {
59
+ // `this.json` is not explicitly defined on Controller, but expected on subclasses
60
+ // assuming it's a boolean or undefined
61
+ const defaultToJson = this.json;
62
+ const controllerName = this._getControllerName();
63
+ if (this.routes && this.routes.length) {
64
+ const localizedRoutes: RouteDefinition[] = this.routes.map(route => {
65
+ // TODO: throw an error if :controller is present in local routes
66
+ // TODO: also account for other malformed local routes
67
+ const routePrefix = `/${controllerName}/`;
68
+ const modifiedDestination = (route[1][0] == '/')
69
+ ? route[1]
70
+ : (route[1].startsWith('./') ? route[1].replace('./', routePrefix) : routePrefix + route[1]);
71
+ const modifiedOptions: RouteOptions = {...route[2]};
72
+
73
+ if (!('json' in modifiedOptions)) modifiedOptions.json = defaultToJson;
74
+ if (!('action' in modifiedOptions)
75
+ && typeof modifiedDestination === 'string' && !modifiedDestination.includes(":action")
76
+ && !Object.values(modifiedOptions).includes('action')) {
77
+ const destinationParts = modifiedDestination.split("/");
78
+ modifiedOptions.action = destinationParts[destinationParts.length - 1];
79
+ }
80
+ modifiedOptions.controller = controllerName;
81
+ return [route[0], modifiedDestination, modifiedOptions];
82
+ });
83
+ router.addRoutes(localizedRoutes);
84
+ }
85
+ }
86
+
87
+ /**** Network Methods *****/
88
+ /**
89
+ * The main entry function of the controller.
90
+ *
91
+ * @param {string} options.action The action called for this controller
92
+ * @param {object} options.params The parameter hash for this action
93
+ * @param {object} options.request The http request object from the http server
94
+ * @param {object} options.response The http response object from the http server
95
+ * @param {object} options.ws The WebSocket instance
96
+ * @param {object} options.next The Express NextFunction
97
+ */
98
+ async _do({action, params, request, response, ws, next}: ControllerDoRouteParams): Promise<void> {
99
+ (request as any).handled_by_controller = true;
100
+ const doAction: Function = (this as any)[action]; // Action method on the controller
101
+
102
+ console.log(this.constructor.name, 'doing', action);
103
+
104
+ // TODO: let express handle errors
105
+ if (!doAction || typeof doAction !== 'function') {
106
+ const error = new Error('Action Not Available: #' + action);
107
+ if (response) {
108
+ response.status(404).send({
109
+ error: error.message + '\n' + error.stack
110
+ });
111
+ return;
112
+ }
113
+ if (ws) {
114
+ ws.close(1003, "Action Not available: #" + action);
115
+ }
116
+ return;
117
+ }
118
+
119
+ if (ws && !response) { // If it's a websocket request and not an http response
120
+ await doAction.call(this, params, ws, request);
121
+ return;
122
+ }
123
+
124
+ // Override async with Controller definition.
125
+ // Not using prototypal function objects
126
+ let actionResponse;
127
+ try {
128
+ actionResponse = await doAction.call(this, params, request, response);
129
+ } catch (e) {
130
+ // console.error(e);
131
+ return next(e);
132
+ // response.write({error: e.message, stack: e.stack});
133
+ // actionResponse = 500;
134
+ }
135
+
136
+ if ((typeof actionResponse) == 'number') {
137
+ response.sendStatus(actionResponse);
138
+ return;
139
+ }
140
+
141
+ if (params._json) {
142
+ console.log("Sending JSON");
143
+ console.log("The ACTION RESPONSE", actionResponse);
144
+ response.json(actionResponse || {});
145
+ return;
146
+ }
147
+ //let viewAction = params._action ? params._action.toString() : "index";
148
+ let viewPath = `${this._getControllerName()}/${action}`;
149
+
150
+ const renderView = (templateVars: any): void => {
151
+ if (response.headersSent) {
152
+ return;
153
+ }
154
+ if (!!params._json) {
155
+ response.json(templateVars === undefined ? params : templateVars);
156
+ return;
157
+ }
158
+ response.render(
159
+ viewPath,
160
+ templateVars === undefined ? params : templateVars,
161
+ function(err: Error | null, html?: string) {
162
+ if (err && err.message.match(/Failed to lookup view/)) {
163
+ response.render(viewPath + ".ejs", params);
164
+ } else if (err) {
165
+ console.error(err);
166
+ response.sendStatus(400); // Changed from 400, err to just 400
167
+ } else {
168
+ response.send(html);
169
+ }
170
+ });
171
+ };
172
+
173
+ if (!response.headersSent
174
+ && !params._async
175
+ && actionResponse !== DISABLE_AUTORENDER) { // TODO: what does this do?
176
+ if (actionResponse instanceof Promise) {
177
+ // TODO: add a timeout so an unresolved promise doesn't preserve the
178
+ // connection indefinitely.
179
+ actionResponse
180
+ .then(renderView)
181
+ .catch((error: any) => {
182
+ console.error(error);
183
+ response.sendStatus(500); // Changed from 500, error to just 500
184
+ });
185
+ } else {
186
+ renderView(actionResponse);
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ const error_codes = [404, 500];
193
+ error_codes.forEach((ec) => {
194
+ Controller.prototype[ec] = function (params: Record<string, any>, request: Request, response: Response): void {
195
+ //this._render('404');
196
+ console.error(ec.toString() + ' error with params', params);
197
+ const error = params['error'] || {};
198
+ const message = error.message || 'Error';
199
+ const stack_trace = error.stack || error || '';
200
+ response.setHeader('content-type', 'text/plain');
201
+ response.statusCode = ec;
202
+ response.end(message + '\n\n' + stack_trace);
203
+ };
204
+ (Controller.prototype as any)[ec.toString()] = Controller.prototype[ec];
205
+ });
206
+
207
+ export default Controller;