@projectinvicta/nails 2.0.15

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 (164) hide show
  1. package/README.md +419 -0
  2. package/bin/lib/init.js +103 -0
  3. package/bin/lib/nails.c +81 -0
  4. package/bin/lib/nails.js +23 -0
  5. package/bin/lib/test.js +4 -0
  6. package/index.js +6 -0
  7. package/lib/application.js +24 -0
  8. package/lib/collection.js +6 -0
  9. package/lib/controller.js +182 -0
  10. package/lib/database_connector.js +12 -0
  11. package/lib/firebase_connector.js +94 -0
  12. package/lib/model_v2.js +24 -0
  13. package/lib/mongoose_connector.js +49 -0
  14. package/lib/mongoose_mem_connector.js +21 -0
  15. package/lib/nails.js +244 -0
  16. package/lib/router.js +202 -0
  17. package/lib/sequelize_connector.js +31 -0
  18. package/lib/server.js +1 -0
  19. package/package.json +60 -0
  20. package/scripts/install.js +57 -0
  21. package/scripts/uninstall.js +23 -0
  22. package/spec/bin/init.spec.js +0 -0
  23. package/spec/controller.spec.js +105 -0
  24. package/spec/model_v2.spec.js +75 -0
  25. package/spec/mongodb_connector.util.js +30 -0
  26. package/spec/mongoose_connector.util.js +20 -0
  27. package/spec/nails.spec.js +0 -0
  28. package/spec/router.spec.js +101 -0
  29. package/spec/sequelize_connector.spec.js +91 -0
  30. package/spec/sequelize_connector.util.js +18 -0
  31. package/spec/services/integration/README.md +5 -0
  32. package/spec/services/integration/client/css/styles.css +0 -0
  33. package/spec/services/integration/client/download.jpg +0 -0
  34. package/spec/services/integration/client/favicon.ico +0 -0
  35. package/spec/services/integration/client/index.html +9 -0
  36. package/spec/services/integration/client/js/client.js +0 -0
  37. package/spec/services/integration/client/js/components/app.jsx +15 -0
  38. package/spec/services/integration/config/db.js +14 -0
  39. package/spec/services/integration/config/mimes.js +61 -0
  40. package/spec/services/integration/config/routes.js +41 -0
  41. package/spec/services/integration/config/service.js +48 -0
  42. package/spec/services/integration/config/ssl/certificate.pem +22 -0
  43. package/spec/services/integration/config/ssl/csr.csr +17 -0
  44. package/spec/services/integration/config/ssl/key.pem +28 -0
  45. package/spec/services/integration/config/ssl/private_key.pem +30 -0
  46. package/spec/services/integration/config/ssl/public_key.pem +9 -0
  47. package/spec/services/integration/package.json +23 -0
  48. package/spec/services/integration/server/controllers/classbased_controller.js +33 -0
  49. package/spec/services/integration/server/controllers/default_json_controller.js +20 -0
  50. package/spec/services/integration/server/controllers/error_controller.js +27 -0
  51. package/spec/services/integration/server/controllers/home_controller.js +39 -0
  52. package/spec/services/integration/server/controllers/json_controller.js +15 -0
  53. package/spec/services/integration/server/controllers/manualrenderasync_controller.js +14 -0
  54. package/spec/services/integration/server/controllers/mjs_controller.mjs +13 -0
  55. package/spec/services/integration/server/controllers/modeltest_controller.js +18 -0
  56. package/spec/services/integration/server/controllers/websocket_controller.js +13 -0
  57. package/spec/services/integration/server/models/dog.js +6 -0
  58. package/spec/services/integration/server/views/defaultjson/testnojson.ejs +1 -0
  59. package/spec/services/integration/server/views/testreact/testreact.ejs +15 -0
  60. package/spec/services/integration/server.js +9 -0
  61. package/spec/services/integration_sequelize/README.md +5 -0
  62. package/spec/services/integration_sequelize/client/css/styles.css +0 -0
  63. package/spec/services/integration_sequelize/client/download.jpg +0 -0
  64. package/spec/services/integration_sequelize/client/favicon.ico +0 -0
  65. package/spec/services/integration_sequelize/client/index.html +9 -0
  66. package/spec/services/integration_sequelize/client/js/client.js +0 -0
  67. package/spec/services/integration_sequelize/client/js/components/app.jsx +15 -0
  68. package/spec/services/integration_sequelize/config/db.js +4 -0
  69. package/spec/services/integration_sequelize/config/mimes.js +61 -0
  70. package/spec/services/integration_sequelize/config/routes.js +25 -0
  71. package/spec/services/integration_sequelize/config/service.js +48 -0
  72. package/spec/services/integration_sequelize/config/ssl/certificate.pem +22 -0
  73. package/spec/services/integration_sequelize/config/ssl/csr.csr +17 -0
  74. package/spec/services/integration_sequelize/config/ssl/key.pem +28 -0
  75. package/spec/services/integration_sequelize/config/ssl/private_key.pem +30 -0
  76. package/spec/services/integration_sequelize/config/ssl/public_key.pem +9 -0
  77. package/spec/services/integration_sequelize/package.json +23 -0
  78. package/spec/services/integration_sequelize/server/controllers/default_json_controller.js +21 -0
  79. package/spec/services/integration_sequelize/server/controllers/home_controller.js +39 -0
  80. package/spec/services/integration_sequelize/server/models/dog.js +7 -0
  81. package/spec/services/integration_sequelize/server/models/owner.js +10 -0
  82. package/spec/services/integration_sequelize/server/views/defaultjson/testnojson.ejs +1 -0
  83. package/spec/services/integration_sequelize/server/views/testreact/testreact.ejs +15 -0
  84. package/spec/services/integration_sequelize/server.js +9 -0
  85. package/spec/services.integration.spec.js +296 -0
  86. package/spec/services.integration_sequelize.spec.js +60 -0
  87. package/templates/bin/promote.sh +20 -0
  88. package/templates/bin/rollout.sh +74 -0
  89. package/templates/bin/server.js +6 -0
  90. package/templates/bin/start.sh +16 -0
  91. package/templates/common/readme_fetcher.js +4 -0
  92. package/templates/config/db.js +19 -0
  93. package/templates/config/mimes.js +59 -0
  94. package/templates/config/routes.js +38 -0
  95. package/templates/config/service.js +45 -0
  96. package/templates/config/ssl/certificate.pem +22 -0
  97. package/templates/config/ssl/csr.csr +17 -0
  98. package/templates/config/ssl/key.pem +28 -0
  99. package/templates/config/ssl/private_key.pem +30 -0
  100. package/templates/config/ssl/public_key.pem +9 -0
  101. package/templates/default/bin/promote.sh +20 -0
  102. package/templates/default/bin/rollout.sh +74 -0
  103. package/templates/default/bin/server.js +6 -0
  104. package/templates/default/bin/start.sh +16 -0
  105. package/templates/default/common/readme_fetcher.js +4 -0
  106. package/templates/default/config/db.js +19 -0
  107. package/templates/default/config/mimes.js +59 -0
  108. package/templates/default/config/routes.js +38 -0
  109. package/templates/default/config/service.js +45 -0
  110. package/templates/default/config/ssl/certificate.pem +22 -0
  111. package/templates/default/config/ssl/csr.csr +17 -0
  112. package/templates/default/config/ssl/key.pem +28 -0
  113. package/templates/default/config/ssl/private_key.pem +30 -0
  114. package/templates/default/config/ssl/public_key.pem +9 -0
  115. package/templates/default/package-lock.json +9048 -0
  116. package/templates/default/package.json +43 -0
  117. package/templates/default/public/README.xml +332 -0
  118. package/templates/default/public/css/styles.css +17 -0
  119. package/templates/default/public/download.jpg +0 -0
  120. package/templates/default/public/favicon.ico +0 -0
  121. package/templates/default/public/index.html +9 -0
  122. package/templates/default/public/js/client.js +1 -0
  123. package/templates/default/server/controllers/home_controller.js +34 -0
  124. package/templates/default/server/models/User.js +18 -0
  125. package/templates/default/server/views/home/index.ejs +14 -0
  126. package/templates/default/server/views/partials/javascripts.ejs +1 -0
  127. package/templates/default/server/views/partials/reactapp.ejs +1 -0
  128. package/templates/default/server/views/partials/styles.ejs +3 -0
  129. package/templates/default/spec/User.test.js +20 -0
  130. package/templates/default/spec/home_controller.test.js +28 -0
  131. package/templates/default/spec/setupTests.js +0 -0
  132. package/templates/default/src/AboutPage.jsx +9 -0
  133. package/templates/default/src/HomePage.jsx +9 -0
  134. package/templates/default/src/Layout.jsx +78 -0
  135. package/templates/default/src/ReadmePage.jsx +7 -0
  136. package/templates/default/src/app.jsx +29 -0
  137. package/templates/default/src/components/ReadmeLoader.jsx +13 -0
  138. package/templates/default/src/styles/appstyles.css +3 -0
  139. package/templates/default/vite.config.ts +42 -0
  140. package/templates/package-lock.json +9048 -0
  141. package/templates/package.json +43 -0
  142. package/templates/public/README.xml +332 -0
  143. package/templates/public/css/styles.css +17 -0
  144. package/templates/public/download.jpg +0 -0
  145. package/templates/public/favicon.ico +0 -0
  146. package/templates/public/index.html +9 -0
  147. package/templates/public/js/client.js +1 -0
  148. package/templates/server/controllers/home_controller.js +34 -0
  149. package/templates/server/models/User.js +18 -0
  150. package/templates/server/views/home/index.ejs +14 -0
  151. package/templates/server/views/partials/javascripts.ejs +1 -0
  152. package/templates/server/views/partials/reactapp.ejs +1 -0
  153. package/templates/server/views/partials/styles.ejs +3 -0
  154. package/templates/spec/User.test.js +20 -0
  155. package/templates/spec/home_controller.test.js +28 -0
  156. package/templates/spec/setupTests.js +0 -0
  157. package/templates/src/AboutPage.jsx +9 -0
  158. package/templates/src/HomePage.jsx +9 -0
  159. package/templates/src/Layout.jsx +78 -0
  160. package/templates/src/ReadmePage.jsx +7 -0
  161. package/templates/src/app.jsx +29 -0
  162. package/templates/src/components/ReadmeLoader.jsx +13 -0
  163. package/templates/src/styles/appstyles.css +3 -0
  164. package/templates/vite.config.ts +42 -0
@@ -0,0 +1,182 @@
1
+ // const fs = require('fs');
2
+ import fs from 'node:fs';
3
+ import events from 'node:events';
4
+ // const events = require('events');
5
+ import application from './application.js';
6
+ // const application = require('./application');
7
+
8
+ var router;
9
+ var controller_proto;
10
+ var models;
11
+
12
+ // TODO: refractor error generation
13
+ var NAME_REQUIRED_ERROR = function () {
14
+ return new Error(
15
+ 'FATAL ERROR::: Named function required for Controller constructor method');
16
+ }
17
+
18
+ const DISABLE_AUTORENDER = new (class DisableAutorender {});
19
+
20
+ // The base controller definition
21
+ class Controller {
22
+ constructor() {
23
+ var subclassName = this.constructor.name;
24
+ var controllerName = this._getControllerName();
25
+ // subclassName.toLowerCase().replace(/controller$/, '');
26
+ router.removeAllListeners('dispatchTo:' + controllerName);
27
+ router.on('dispatchTo:' + controllerName, this._do.bind(this));
28
+ }
29
+
30
+ static get DISABLE_AUTORENDER() {
31
+ return DISABLE_AUTORENDER;
32
+ }
33
+
34
+ static setRouter (router_singleton) {
35
+ Controller.router = router = router_singleton;
36
+ }
37
+
38
+ static setModels (models) {
39
+ Controller.prototype.models = models;
40
+ }
41
+
42
+ static extend (constructor) {
43
+ console.log('extending', constructor.name);
44
+ if (!constructor.name) throw NAME_REQUIRED_ERROR();
45
+ controller_proto = controller_proto || new Controller();
46
+ constructor.prototype.__proto__ = controller_proto;
47
+ var constructed = new constructor();
48
+
49
+ // configure event listeners on router.
50
+ var controller_name =
51
+ constructor.name.toLowerCase().replace(/controller$/, '');
52
+ router.removeAllListeners('dispatchTo:' + controller_name);
53
+ router.on('dispatchTo:' + controller_name, constructed._do.bind(constructed));
54
+
55
+ return constructed;
56
+ }
57
+
58
+ _getControllerName() {
59
+ return this.constructor.name.toLowerCase().replace(/controller$/, '');
60
+ }
61
+
62
+ /** Initializes local and global routes defined on the Controller subclass */
63
+ _registerControllerRoutes() {
64
+ const defaultToJson = !!this.json;
65
+ const controllerName = this._getControllerName();
66
+ if (this.routes && this.routes.length) {
67
+ const localizedRoutes = this.routes.map(route => {
68
+ // TODO: throw an error if :controller is present in local routes
69
+ // TODO: also account for other malformed local routes
70
+ const routePrefix = `/${controllerName}/`;
71
+ const modifiedDestination = route[1][0] == '/'
72
+ ? route[1]
73
+ : route[1].startsWith('./') ? route[1].replace('./', routePrefix) : routePrefix + route[1];
74
+ const modifiedOptions = {...route[2]};
75
+ // TODO: introduce a shorthand so action doesn't have to be redefined everywhere
76
+ if (!('json' in modifiedOptions)) modifiedOptions.json = defaultToJson;
77
+ if (!('action' in modifiedOptions)
78
+ && !modifiedDestination.includes(":action")
79
+ && !Object.values(modifiedOptions).includes('action')) {
80
+ const destinationParts = modifiedDestination.split("/");
81
+ modifiedOptions.action = destinationParts[destinationParts.length - 1];
82
+ }
83
+ modifiedOptions.controller = controllerName;
84
+ return [route[0], modifiedDestination, modifiedOptions];
85
+ });
86
+ router.addRoutes(localizedRoutes);
87
+ }
88
+ }
89
+
90
+ /**** Network Methods *****/
91
+ /**
92
+ * The main entry function of the controller.
93
+ *
94
+ * @param {string} action The action called for this controller
95
+ * @param {object} params The parameter hash for this action
96
+ * @param {object} request The http request object from the http server
97
+ * @param {object} response The http response object from the http server
98
+ * @param {object} ws The WebSocket instance
99
+ */
100
+ _do (action, params, request, response, ws) {
101
+ request.handled_by_controller = true;
102
+ var doAction = this[action];
103
+ console.log(this.constructor.name, 'doing', action);
104
+ // TODO: let express handle errors
105
+ if (typeof doAction != 'function') {
106
+ //return this['404'](params, request, response);
107
+ var error = new Error('Action Not Available: #' + action);
108
+ if (response) {
109
+ return response.status(404).send({
110
+ error: error.stack
111
+ });
112
+ }
113
+ return ws.close(1003, "Action Not available: #" + action);
114
+ }
115
+ if (ws && !response) {
116
+ return doAction.call(this, params, ws, request);
117
+ }
118
+ // Override async with Controller definition.
119
+ if (doAction.async != undefined) params._async = !!doAction.async;
120
+ let actionResponse = doAction.call(this, params, request, response);
121
+ let controller = params._controller.toString();
122
+ //let viewAction = params._action ? params._action.toString() : "index";
123
+ let viewPath = `${controller}/${action}`;
124
+ function renderView(templateVars) {
125
+ if (response.headersSent) {
126
+ return;
127
+ }
128
+ if (!!params._json) {
129
+ response.json(templateVars == undefined ? params : templateVars);
130
+ return;
131
+ }
132
+ response.render(
133
+ viewPath,
134
+ templateVars == undefined ? params : templateVars,
135
+ function(err, html) {
136
+ if (err && err.message.match(/Failed to lookup view/)) {
137
+ response.render(viewPath + ".ejs", params);
138
+ } else if (err) {
139
+ console.error(err);
140
+ response.sendStatus(400, err);
141
+ } else {
142
+ response.send(html);
143
+ }
144
+ });
145
+ }
146
+ // Default to the jsx engine. If that doesn't work, try the configured view engine.
147
+ if (!response.headersSent
148
+ && !params._async
149
+ && actionResponse != DISABLE_AUTORENDER) {
150
+ if (actionResponse instanceof Promise) {
151
+ // TODO: add a timeout so an unresolved promise doesn't preserve the
152
+ // connection indefinitely.
153
+ actionResponse
154
+ .then(renderView)
155
+ .catch(error => {
156
+ // TODO: only do one of these
157
+ console.error(error);
158
+ console.log(error);
159
+ response.sendStatus(500, error);
160
+ });
161
+ } else renderView(actionResponse);
162
+ }
163
+ }
164
+ }
165
+
166
+ const error_codes = [404, 500];
167
+ error_codes.forEach((ec) => {
168
+ Controller.prototype[ec] = function (params, request, response) {
169
+ //this._render('404');
170
+ console.error(ec.toString() + ' error with params', params);
171
+ var error = params['error'] || {};
172
+ var message = error.message || 'Error';
173
+ var stack_trace = error.stack || error || '';
174
+ response.setHeader('content-type', 'text/plain');
175
+ response.statusCode = ec;
176
+ response.end(message + '\n\n' + stack_trace);
177
+ };
178
+ Controller.prototype[ec.toString()] = Controller.prototype[ec];
179
+
180
+ });
181
+
182
+ export default Controller;
@@ -0,0 +1,12 @@
1
+ export default class DbConnector {
2
+ async connect() {
3
+ throw 'DbConnector#connect not implemented'
4
+ }
5
+ generateModelSuperclass() {
6
+ throw 'GenerateModelSuperclass not implemented'
7
+ }
8
+
9
+ async afterInitialization() {
10
+ console.warn("DbConnector#afterInitialization not implemented");
11
+ }
12
+ }
@@ -0,0 +1,94 @@
1
+ // TODO: multiple async requests here... need to
2
+ // consider how this will be used by the models
3
+ import {EventEmitter} from 'node:events';
4
+ // var EventEmitter = require('events').EventEmitter;
5
+ import { MongoClient } from 'mongodb';
6
+ // const MongoClient = require('mongodb').MongoClient;
7
+ // const test = require('assert');
8
+ import test from 'assert';
9
+
10
+ module.exports = MongoDBConnector;
11
+
12
+ // TODO: need to deal with clustered databases...
13
+
14
+ MongoDBConnector.prototype.__proto__ = EventEmitter.prototype;
15
+ function MongoDBConnector(config) {
16
+ EventEmitter.call(this);
17
+ var url = config.url || 'mongodb://localhost';
18
+ var port = config.port || '27017';
19
+ var database = config.database || 'nails';
20
+ this.exec_once_connected = [];
21
+ MongoClient.connect(url + ':' + port)
22
+ .then((err, client) => {
23
+ this._client = client;
24
+ this._db = client.db(database);
25
+ }).catch({
26
+ // TODO: handle a failed mongodb connection
27
+ });
28
+ }
29
+
30
+ /**
31
+ * Need to implement these methods for a connector to work
32
+ */
33
+ // maybe use rest methods as names for any database connector used...
34
+ MongoDBConnector.prototype.post = function(model_or_collection) {
35
+ if (model_or_collection.is_model)
36
+ this._post_one(model_or_collection._collection_name(),
37
+ model_or_collection.attributes);
38
+ }
39
+ MongoDBConnector.prototype._post_one = function(collection_name, doc_attributes, callback) {
40
+ this._db.collection(collection_name).insert(doc_attributes);
41
+ }
42
+ MongoDBConnector.prototype._post_many = function(collection) {
43
+ this._db.collection(collection.name())
44
+ .save(collection.model_attributes());
45
+ }
46
+
47
+ // update a record in the collection
48
+ MongoDBConnector.prototype.put = function(model_or_collection) {
49
+ if (model_or_collection.is_model)
50
+ this._put_one(model_or_collection._collection_name(), model_or_collection.attributes);
51
+ }
52
+ MongoDBConnector.prototype._put_one = function(collection_name, doc) {
53
+ // TODO: replacing document completely is sow
54
+ // will want to only send changed attr
55
+ // TODO: write concerns?
56
+ this._db.collection(collection_name).update({_id: doc._id}, doc);
57
+ }
58
+ /**
59
+ * _put_many will call _update on each individual object in collection
60
+ * if that object has changed
61
+ */
62
+ MongoDBConnector.prototype._put_many = function(collection) {
63
+ }
64
+
65
+ // return a single record from a collection
66
+ // or a collection of records if requested...
67
+ // if a model or collection is passed,
68
+ MongoDBConnector.prototype.get = function(model_or_collection) {
69
+ if (model_or_collection.is_model)
70
+ this._get_one(model_or_collection._collection_name(), model_or_collection.id, null,
71
+ this._on_doc_response.bind(model_or_collection));
72
+ }
73
+ MongoDBConnector.prototype._get_one = function(collection_name, id, options, callback) {
74
+ options = options || {};
75
+ this._db.collection(collection_name).findOne(id, options, callback);
76
+ }
77
+ MongoDBConnector.prototype._get_many = function(collection) {
78
+ return this._db.collection(collection.name()).find({
79
+ _id: collection.collect('_id')});
80
+ }
81
+
82
+ // delete a record from a collection
83
+ MongoDBConnector.prototype.delete = function(model) {
84
+ return this._db.collection(model.collection_name())
85
+ .remove(model.attributes._id);
86
+ }
87
+ // MongoDBConnector.prototype._delete_one
88
+ // MongoDBConnector.prototype._delete_many
89
+
90
+ MongoDBConnector.prototype._on_doc_response = function(err, doc) {
91
+ if (err) return console.log('error retrieving from', collection_name, '\n', err);
92
+ delete doc._id;
93
+ this._merge_attributes(doc);
94
+ }
@@ -0,0 +1,24 @@
1
+ let dbConnector = null;
2
+
3
+ const FINALIZATIONS = [];
4
+
5
+ export default class Model {
6
+ static finalize(extraWork) {
7
+ FINALIZATIONS.push(extraWork);
8
+ }
9
+
10
+ static async _doFinalize() {
11
+ await Promise.all(FINALIZATIONS.map(extraWork => extraWork()));
12
+ }
13
+
14
+ static setConnector(connector) {
15
+ // TODO: enforce environment using variables
16
+ if (dbConnector)
17
+ console.warn("WARNING: Model#setConnector should not be called multiple times outside of tests");
18
+ dbConnector = connector;
19
+ }
20
+
21
+ constructor(modelName, connectorOptions) {
22
+ return dbConnector.generateModelSuperclass(modelName, connectorOptions);
23
+ }
24
+ }
@@ -0,0 +1,49 @@
1
+ // const mongoose = require('mongoose');
2
+ import mongoose from 'mongoose';
3
+ import DbConnector from './database_connector.js';
4
+
5
+ class MongooseDbConnector extends DbConnector{
6
+ async connect(options) {
7
+ if (options.uri) {
8
+ this.connection = await mongoose.createConnection(options.uri/*, mongooseOptions*/).asPromise();
9
+ } else {
10
+ var url = options.url || 'mongodb://127.0.0.1';
11
+ var port = options.port || '27017';
12
+ var database = options.database || options.dbName || 'nails';
13
+ this.connection = await mongoose.createConnection(`${url}:${port}/${database}`).asPromise();
14
+ }
15
+ }
16
+
17
+ generateModelSuperclass(name, options) {
18
+ if (!this.connection) console.error("NO CONNECTION\n", this);
19
+ let schema = options.schema instanceof mongoose.Schema
20
+ ? options.schema
21
+ : new mongoose.Schema(options.schema);
22
+ if (options.indexes) {
23
+ options.indexes.forEach(index => schema.index(index));
24
+ }
25
+ return this.connection.model(name, options.schema);
26
+ }
27
+ }
28
+ export default MongooseDbConnector;
29
+ /*
30
+ module.exports.connect = function(options) {
31
+ if (options.uri) return mongoose.createConnection(options.uri, mongooseOptions);
32
+ else {
33
+ var url = options.url || 'mongodb://localhost';
34
+ var port = options.port || '27017';
35
+ var database = options.database || 'nails';
36
+ return mongoose.createConnection(`${url}:${port}/${database}`, mongooseOptions);
37
+ }
38
+ }
39
+
40
+ module.exports.generateModelSuperclass = function(name, options) {
41
+ let schema = options.schema instanceof mongoose.Schema
42
+ ? options.schema
43
+ : new mongoose.Schema(options.schema);
44
+ if (options.indexes) {
45
+ options.indexes.forEach(index => schema.index(index));
46
+ }
47
+ return mongoose.model(name, options.schema);
48
+ }
49
+ */
@@ -0,0 +1,21 @@
1
+ // const mongoose = require('mongoose');
2
+ // const {MongoMemoryServer} = require('mongodb-memory-server');
3
+ import { MongoMemoryServer } from 'mongodb-memory-server';
4
+ import MongooseDbConnector from './mongoose_connector.js';
5
+ // const MongooseDbConnector = require('./mongoose_connector');
6
+
7
+ export default class MongooseMemoryConnector extends MongooseDbConnector {
8
+
9
+ async connect() {
10
+ try {
11
+ const mongod = await MongoMemoryServer.create();
12
+ const uri = mongod.getUri();
13
+ console.error("The URI", uri);
14
+ const dbConfig = {uri: uri};
15
+ return super.connect(dbConfig);
16
+ } catch (e) {
17
+ console.error("Could not connect to MongoMemoryServer");
18
+ console.error(e);
19
+ }
20
+ }
21
+ }
package/lib/nails.js ADDED
@@ -0,0 +1,244 @@
1
+ // The file which configures the nails application
2
+ // import http from 'node:http';
3
+ import https from 'node:https';
4
+ // import URL from 'node:url';
5
+ import path from 'node:path';
6
+ import fs from 'node:fs';
7
+ import { EventEmitter } from 'node:events';
8
+
9
+ import Controller from './controller.js';
10
+ import ModelV2 from './model_v2.js';
11
+ import Router from './router.js';
12
+
13
+ import express_app from './application.js';
14
+ import { Model } from 'mongoose';
15
+
16
+ // TODO: add key value pairs to express app singleton.
17
+ var application = {};
18
+ application.config = {};
19
+
20
+ // TODO: this should return a function (the configure function).
21
+ // Calling the function should return { startServer: startServer }.
22
+ // export defulat nails;
23
+
24
+ export default async function nails(app_config) {
25
+ nails.config = app_config.config;
26
+ application._onceConfigured = configure(app_config);
27
+ await application._onceConfigured;
28
+ return {startServer: startServer};
29
+ }
30
+
31
+ nails.application = express_app;
32
+ nails.Controller = Controller;
33
+ nails.Model = ModelV2;
34
+ nails.events = new EventEmitter();
35
+ nails._dbConnector = null;
36
+ nails.MODELS = {init: initializeModels};
37
+
38
+ async function configure( app_config ) {
39
+ express_app.set('nails_config', application);
40
+ application.config = app_config.config;
41
+ // TODO: may not need mimes any more.
42
+ application.mimes = app_config.mimes;
43
+
44
+ // Init view engine. Defaults to ejs.
45
+ express_app.set('view engine', 'ejs');
46
+ if (app_config.config.VIEW_ENGINE) {
47
+ if (typeof app_config.config.VIEW_ENGINE == "string") {
48
+ console.log("using consolidate for some reason");
49
+ }
50
+ express_app.engine(
51
+ app_config.config.VIEW_ENGINE_EXT,
52
+ app_config.config.VIEW_ENGINE);
53
+ express_app.set('view engine', app_config.config.VIEW_ENGINE_EXT);
54
+ }
55
+ express_app.set('views', app_config.config.VIEWS_ROOT);
56
+
57
+ // set up router and controllers
58
+ express_app.set("public_root", app_config.config.PUBLIC_ROOT);
59
+ console.log("Initializing Router...");
60
+ // application.router = new Router( app_config.routes || [] );
61
+ application.router = new Router( [] );
62
+ console.log("Application Router initialized");
63
+
64
+ // init models
65
+ await initializeModels(app_config);
66
+
67
+ // init Controllers
68
+ Controller.setRouter(application.router);
69
+ application.controller = Controller.extend(ApplicationController);
70
+ console.log('initializing controllers: ', app_config.config.CONTROLLERS_ROOT);
71
+ await init_controllers(app_config.config.CONTROLLERS_ROOT);
72
+ application.router.addRoutes(app_config.routes);
73
+ };
74
+
75
+ export async function initializeModels( app_config ) {
76
+ // init models
77
+ console.log("Initializing DB connection...");
78
+ var DBConnector = await get_dbconnector(app_config.db.connector);
79
+
80
+ console.log("Instantiating DBConnector and Connecting to DB...");
81
+ // Try to instantiate DBConnector
82
+ const dbConnector = new DBConnector();
83
+ nails._dbConnector = dbConnector;
84
+ await dbConnector.connect(app_config.db);
85
+ console.log("Generating model superclass...");
86
+ ModelV2.setConnector(dbConnector);
87
+ await init_models_v2(app_config.config.MODELS_ROOT);
88
+ console.log("Done importing models");
89
+ await dbConnector.afterInitialization();
90
+ await ModelV2._doFinalize();
91
+ console.log("DB Connection complete");
92
+ }
93
+
94
+ function startServer(config) {
95
+ // Log the config.
96
+ console.log(config);
97
+ // await application._onceConfigured;
98
+ console.log("CONFIGURATION COMPLETE");
99
+ // TODO: Use logging middleware.
100
+ application._onceConfigured.then(() => {
101
+ // Use the router middleware.
102
+ express_app.use(application.router.express_router);
103
+ var ip = application.config.IP || 'localhost';
104
+ var port = application.config.PORT || 3000;
105
+
106
+ let startHttp = 'ENABLE_HTTP' in application.config && !!application.config.ENABLE_HTTP;
107
+ let startHttps = !!application.config.ENABLE_HTTPS;
108
+
109
+ if (!startHttp && !startHttps) {
110
+ console.error("Either ENABLE_HTTPS or ENABLE_HTTP must be set for nails to start");
111
+ }
112
+
113
+ let atLeastOneServerStarted = false;
114
+ let serverStartedCallback = () => {
115
+ console.log("Started");
116
+ if (atLeastOneServerStarted) return;
117
+ nails.events.emit("ready", null);
118
+ atLeastOneServerStarted = true;
119
+ }
120
+
121
+ if (startHttp) {
122
+ console.log("starting nails HTTP server. listening to ", ip + ':' + port);
123
+ express_app.listen(port, ip, serverStartedCallback);
124
+
125
+ }
126
+ if (startHttps) {
127
+ console.log(`starting nails HTTPS server. Listening to ${ip}:${application.config.SSL_PORT}`);
128
+ https.createServer({
129
+ key: application.config.PRIVATE_KEY,
130
+ cert: application.config.CERTIFICATE
131
+ }, express_app).listen(application.config.SSL_PORT, serverStartedCallback);
132
+ }
133
+ });
134
+ }
135
+
136
+ // TODO: create an initializer lib file.
137
+ async function init_controllers(controller_lib) {
138
+ await init_app_lib(Controller, controller_lib);
139
+ }
140
+
141
+ async function init_app_lib(superclass, abs_path) {
142
+ console.log('attempting to import:', abs_path);
143
+ if (!fs.existsSync(abs_path))
144
+ return console.log('Cannot initialize. Path not found.', abs_path);
145
+ if (fs.statSync(abs_path).isFile()) {
146
+ let subclass = (await import(abs_path)).default;
147
+ // Constructor function was provided
148
+ if (!superclass.isPrototypeOf(subclass))
149
+ return superclass.extend(subclass);
150
+ // ES6 Class was provided
151
+ const subInstance = new subclass();
152
+ if (superclass == Controller) {
153
+ subInstance._registerControllerRoutes();
154
+ }
155
+ return subInstance;
156
+ }
157
+ for (const rel_path of fs.readdirSync(abs_path)) {
158
+ await init_app_lib(superclass, path.join(abs_path, rel_path));
159
+ }
160
+ }
161
+
162
+ async function init_models_v2(abs_path) {
163
+ if (!fs.existsSync(abs_path))
164
+ return console.log('Cannot initialize. Path not found.', abs_path);
165
+ if (fs.statSync(abs_path).isFile()) {
166
+ console.log('attempting to import:', abs_path);
167
+ // We just need to import each model once so the generateSuperclass
168
+ // method is called at least once for each model.
169
+ const modelModule = await import(abs_path);
170
+ let modelClass = modelModule.default;
171
+ if (modelClass && modelClass.name) {
172
+ console.log('imported model:', modelClass.name);
173
+ nails.MODELS[modelClass.name] = modelClass;
174
+ if (modelModule.defer) {
175
+ await modelModule.defer();
176
+ }
177
+ }
178
+ else console.warn("No model found at:", abs_path);
179
+ return;
180
+ }
181
+ const directory_contents = fs.readdirSync(abs_path);
182
+ for (const rel_path of directory_contents) {
183
+ await init_models_v2(path.join(abs_path, rel_path));
184
+ };
185
+ }
186
+
187
+ // retrieves the connector object. If cannot
188
+ // require the module with the same name,
189
+ // try grabbing connector from lib
190
+ // (default connectors)
191
+ async function get_dbconnector(connector_name) {
192
+ console.log("Getting DBConnector:", connector_name);
193
+ var DBConnector;
194
+ //TODO: put mongodbconnector in its own module
195
+ try {
196
+ DBConnector = (await import(connector_name)).default;
197
+ } catch(e) {
198
+ DBConnector = (await import('./'+connector_name)).default;
199
+ }
200
+ console.log("Got DBConnector:", DBConnector.name);
201
+ return DBConnector;
202
+ }
203
+
204
+ // Define default views and controllers
205
+ // TODO: clean this up... all this logic shouldnt be here
206
+ function ApplicationController(){};
207
+
208
+ /**
209
+ * Application Views
210
+ */
211
+ function JSONView(){};
212
+ // TODO: add error template for error view
213
+ function ErrorView() {}
214
+ ErrorView.prototype.render = function(params, response) {
215
+ response.statusCode = params.code || 500;
216
+ // TODO: change this to use the error jade template
217
+ response.setHeader('content-type', 'text/plain');
218
+ var content = params.error ?
219
+ params.error.message + '\n' + params.error.stack :
220
+ params.message;
221
+ response.end(content);
222
+ }
223
+ // TODO: consider changing public to static...
224
+ function PublicView(){};
225
+ PublicView.prototype.get_full_path = function(filepath) {
226
+ var pubroot = application.config.PUBLIC_ROOT;
227
+ var partial_path = filepath;
228
+ if (filepath.substr(0, pubroot.length) != pubroot)
229
+ partial_path = path.join(pubroot, filepath);
230
+ return path.join(application.config.SERVER_ROOT, partial_path);
231
+ };
232
+ PublicView.prototype.render = function(params, response) {
233
+ var file_path = this.get_full_path(params.path);
234
+
235
+ var content = '';
236
+ try {
237
+ content = fs.readFileSync(file_path);
238
+ } catch(e) {
239
+ console.log(e.message, "\n", e.stack);
240
+ response.statusCode = 404;
241
+ } finally {
242
+ response.end(content);
243
+ }
244
+ };