@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.
- package/README.md +419 -0
- package/bin/lib/init.js +103 -0
- package/bin/lib/nails.c +81 -0
- package/bin/lib/nails.js +23 -0
- package/bin/lib/test.js +4 -0
- package/index.js +6 -0
- package/lib/application.js +24 -0
- package/lib/collection.js +6 -0
- package/lib/controller.js +182 -0
- package/lib/database_connector.js +12 -0
- package/lib/firebase_connector.js +94 -0
- package/lib/model_v2.js +24 -0
- package/lib/mongoose_connector.js +49 -0
- package/lib/mongoose_mem_connector.js +21 -0
- package/lib/nails.js +244 -0
- package/lib/router.js +202 -0
- package/lib/sequelize_connector.js +31 -0
- package/lib/server.js +1 -0
- package/package.json +60 -0
- package/scripts/install.js +57 -0
- package/scripts/uninstall.js +23 -0
- package/spec/bin/init.spec.js +0 -0
- package/spec/controller.spec.js +105 -0
- package/spec/model_v2.spec.js +75 -0
- package/spec/mongodb_connector.util.js +30 -0
- package/spec/mongoose_connector.util.js +20 -0
- package/spec/nails.spec.js +0 -0
- package/spec/router.spec.js +101 -0
- package/spec/sequelize_connector.spec.js +91 -0
- package/spec/sequelize_connector.util.js +18 -0
- package/spec/services/integration/README.md +5 -0
- package/spec/services/integration/client/css/styles.css +0 -0
- package/spec/services/integration/client/download.jpg +0 -0
- package/spec/services/integration/client/favicon.ico +0 -0
- package/spec/services/integration/client/index.html +9 -0
- package/spec/services/integration/client/js/client.js +0 -0
- package/spec/services/integration/client/js/components/app.jsx +15 -0
- package/spec/services/integration/config/db.js +14 -0
- package/spec/services/integration/config/mimes.js +61 -0
- package/spec/services/integration/config/routes.js +41 -0
- package/spec/services/integration/config/service.js +48 -0
- package/spec/services/integration/config/ssl/certificate.pem +22 -0
- package/spec/services/integration/config/ssl/csr.csr +17 -0
- package/spec/services/integration/config/ssl/key.pem +28 -0
- package/spec/services/integration/config/ssl/private_key.pem +30 -0
- package/spec/services/integration/config/ssl/public_key.pem +9 -0
- package/spec/services/integration/package.json +23 -0
- package/spec/services/integration/server/controllers/classbased_controller.js +33 -0
- package/spec/services/integration/server/controllers/default_json_controller.js +20 -0
- package/spec/services/integration/server/controllers/error_controller.js +27 -0
- package/spec/services/integration/server/controllers/home_controller.js +39 -0
- package/spec/services/integration/server/controllers/json_controller.js +15 -0
- package/spec/services/integration/server/controllers/manualrenderasync_controller.js +14 -0
- package/spec/services/integration/server/controllers/mjs_controller.mjs +13 -0
- package/spec/services/integration/server/controllers/modeltest_controller.js +18 -0
- package/spec/services/integration/server/controllers/websocket_controller.js +13 -0
- package/spec/services/integration/server/models/dog.js +6 -0
- package/spec/services/integration/server/views/defaultjson/testnojson.ejs +1 -0
- package/spec/services/integration/server/views/testreact/testreact.ejs +15 -0
- package/spec/services/integration/server.js +9 -0
- package/spec/services/integration_sequelize/README.md +5 -0
- package/spec/services/integration_sequelize/client/css/styles.css +0 -0
- package/spec/services/integration_sequelize/client/download.jpg +0 -0
- package/spec/services/integration_sequelize/client/favicon.ico +0 -0
- package/spec/services/integration_sequelize/client/index.html +9 -0
- package/spec/services/integration_sequelize/client/js/client.js +0 -0
- package/spec/services/integration_sequelize/client/js/components/app.jsx +15 -0
- package/spec/services/integration_sequelize/config/db.js +4 -0
- package/spec/services/integration_sequelize/config/mimes.js +61 -0
- package/spec/services/integration_sequelize/config/routes.js +25 -0
- package/spec/services/integration_sequelize/config/service.js +48 -0
- package/spec/services/integration_sequelize/config/ssl/certificate.pem +22 -0
- package/spec/services/integration_sequelize/config/ssl/csr.csr +17 -0
- package/spec/services/integration_sequelize/config/ssl/key.pem +28 -0
- package/spec/services/integration_sequelize/config/ssl/private_key.pem +30 -0
- package/spec/services/integration_sequelize/config/ssl/public_key.pem +9 -0
- package/spec/services/integration_sequelize/package.json +23 -0
- package/spec/services/integration_sequelize/server/controllers/default_json_controller.js +21 -0
- package/spec/services/integration_sequelize/server/controllers/home_controller.js +39 -0
- package/spec/services/integration_sequelize/server/models/dog.js +7 -0
- package/spec/services/integration_sequelize/server/models/owner.js +10 -0
- package/spec/services/integration_sequelize/server/views/defaultjson/testnojson.ejs +1 -0
- package/spec/services/integration_sequelize/server/views/testreact/testreact.ejs +15 -0
- package/spec/services/integration_sequelize/server.js +9 -0
- package/spec/services.integration.spec.js +296 -0
- package/spec/services.integration_sequelize.spec.js +60 -0
- package/templates/bin/promote.sh +20 -0
- package/templates/bin/rollout.sh +74 -0
- package/templates/bin/server.js +6 -0
- package/templates/bin/start.sh +16 -0
- package/templates/common/readme_fetcher.js +4 -0
- package/templates/config/db.js +19 -0
- package/templates/config/mimes.js +59 -0
- package/templates/config/routes.js +38 -0
- package/templates/config/service.js +45 -0
- package/templates/config/ssl/certificate.pem +22 -0
- package/templates/config/ssl/csr.csr +17 -0
- package/templates/config/ssl/key.pem +28 -0
- package/templates/config/ssl/private_key.pem +30 -0
- package/templates/config/ssl/public_key.pem +9 -0
- package/templates/default/bin/promote.sh +20 -0
- package/templates/default/bin/rollout.sh +74 -0
- package/templates/default/bin/server.js +6 -0
- package/templates/default/bin/start.sh +16 -0
- package/templates/default/common/readme_fetcher.js +4 -0
- package/templates/default/config/db.js +19 -0
- package/templates/default/config/mimes.js +59 -0
- package/templates/default/config/routes.js +38 -0
- package/templates/default/config/service.js +45 -0
- package/templates/default/config/ssl/certificate.pem +22 -0
- package/templates/default/config/ssl/csr.csr +17 -0
- package/templates/default/config/ssl/key.pem +28 -0
- package/templates/default/config/ssl/private_key.pem +30 -0
- package/templates/default/config/ssl/public_key.pem +9 -0
- package/templates/default/package-lock.json +9048 -0
- package/templates/default/package.json +43 -0
- package/templates/default/public/README.xml +332 -0
- package/templates/default/public/css/styles.css +17 -0
- package/templates/default/public/download.jpg +0 -0
- package/templates/default/public/favicon.ico +0 -0
- package/templates/default/public/index.html +9 -0
- package/templates/default/public/js/client.js +1 -0
- package/templates/default/server/controllers/home_controller.js +34 -0
- package/templates/default/server/models/User.js +18 -0
- package/templates/default/server/views/home/index.ejs +14 -0
- package/templates/default/server/views/partials/javascripts.ejs +1 -0
- package/templates/default/server/views/partials/reactapp.ejs +1 -0
- package/templates/default/server/views/partials/styles.ejs +3 -0
- package/templates/default/spec/User.test.js +20 -0
- package/templates/default/spec/home_controller.test.js +28 -0
- package/templates/default/spec/setupTests.js +0 -0
- package/templates/default/src/AboutPage.jsx +9 -0
- package/templates/default/src/HomePage.jsx +9 -0
- package/templates/default/src/Layout.jsx +78 -0
- package/templates/default/src/ReadmePage.jsx +7 -0
- package/templates/default/src/app.jsx +29 -0
- package/templates/default/src/components/ReadmeLoader.jsx +13 -0
- package/templates/default/src/styles/appstyles.css +3 -0
- package/templates/default/vite.config.ts +42 -0
- package/templates/package-lock.json +9048 -0
- package/templates/package.json +43 -0
- package/templates/public/README.xml +332 -0
- package/templates/public/css/styles.css +17 -0
- package/templates/public/download.jpg +0 -0
- package/templates/public/favicon.ico +0 -0
- package/templates/public/index.html +9 -0
- package/templates/public/js/client.js +1 -0
- package/templates/server/controllers/home_controller.js +34 -0
- package/templates/server/models/User.js +18 -0
- package/templates/server/views/home/index.ejs +14 -0
- package/templates/server/views/partials/javascripts.ejs +1 -0
- package/templates/server/views/partials/reactapp.ejs +1 -0
- package/templates/server/views/partials/styles.ejs +3 -0
- package/templates/spec/User.test.js +20 -0
- package/templates/spec/home_controller.test.js +28 -0
- package/templates/spec/setupTests.js +0 -0
- package/templates/src/AboutPage.jsx +9 -0
- package/templates/src/HomePage.jsx +9 -0
- package/templates/src/Layout.jsx +78 -0
- package/templates/src/ReadmePage.jsx +7 -0
- package/templates/src/app.jsx +29 -0
- package/templates/src/components/ReadmeLoader.jsx +13 -0
- package/templates/src/styles/appstyles.css +3 -0
- 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
|
+
}
|
package/lib/model_v2.js
ADDED
|
@@ -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
|
+
};
|