@vulkano/core 1.15.6 β 1.16.1
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 +318 -51
- package/app.js +5 -7
- package/bootstrap/express.js +1 -1
- package/bootstrap/server.js +44 -15
- package/controllers/ScaffoldController.js +18 -0
- package/controllers/controllers.js +3 -3
- package/database/mongodb.js +6 -12
- package/database/scaffold.js +1 -4
- package/libs/ApiClient.js +44 -72
- package/libs/Filter.js +5 -5
- package/libs/Jwt.js +3 -4
- package/libs/Merge.js +109 -0
- package/libs/Paginate.js +49 -38
- package/libs/VSError.js +0 -2
- package/libs/i18n.js +0 -16
- package/package.json +16 -26
- package/public/.gitignore +3 -0
- package/responses/vsr.js +11 -9
- package/.eslintignore +0 -8
- package/.gitattributes +0 -108
- package/.nvmrc +0 -1
- package/bun.lockb +0 -0
package/README.md
CHANGED
|
@@ -1,80 +1,347 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://avatars.githubusercontent.com/u/42077334?s=200&v=4" alt="
|
|
2
|
+
<img src="https://avatars.githubusercontent.com/u/42077334?s=200&v=4" alt="Vulkano Logo" width="100">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<h1 align="center">@vulkano/core</h1>
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
[](#sponsors)
|
|
7
|
+
<p align="center">
|
|
8
|
+
A fast, convention-based MVC framework for Node.js β built on top of Express.
|
|
9
|
+
</p>
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://opencollective.com/vulkanojs#backer"><img src="https://opencollective.com/vulkanojs/backers/badge.svg" alt="Backers"></a>
|
|
13
|
+
<a href="https://opencollective.com/vulkanojs#sponsor"><img src="https://opencollective.com/vulkanojs/sponsors/badge.svg" alt="Sponsors"></a>
|
|
14
|
+
</p>
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
---
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
## What is Vulkano?
|
|
17
19
|
|
|
20
|
+
Vulkano is a lightweight MVC framework for building web applications and APIs with Node.js. It wires together Express, MongoDB (Mongoose), Socket.io, i18n, JWT, file uploads, cron jobs, and more β so you spend time writing features, not boilerplate.
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
Inspired by [KumbiaPHP](https://www.kumbiaphp.com).
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
---
|
|
22
25
|
|
|
23
|
-
##
|
|
26
|
+
## Requirements
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
- **Node.js** `^22`
|
|
29
|
+
- **MongoDB** (optional β only needed if you use models)
|
|
30
|
+
- **Redis** (optional β only needed for Socket.io Redis adapter or sessions)
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
- Node.js v20+
|
|
32
|
+
---
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
## Installation
|
|
31
35
|
|
|
32
36
|
```bash
|
|
33
|
-
|
|
37
|
+
npm install @vulkano/core
|
|
34
38
|
```
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
- `public/` - HTTP Public folder
|
|
39
|
-
- `vulkano/` - Vulkano App (config, controllers, models, views)
|
|
40
|
-
- `app.js` - Server entry point
|
|
40
|
+
---
|
|
41
41
|
|
|
42
|
-
##
|
|
42
|
+
## Quick Start
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* app.js
|
|
47
|
-
*
|
|
48
|
-
* To start the server, run: β β―node app.jsβ―β .
|
|
49
|
-
*
|
|
50
|
-
* For example:
|
|
51
|
-
* => β β―npm run startβ―β
|
|
52
|
-
* => β β―node app.jsβ―β
|
|
53
|
-
*/
|
|
44
|
+
### 1. Entry point β `app.js`
|
|
54
45
|
|
|
46
|
+
```js
|
|
55
47
|
const vulkano = require('@vulkano/core');
|
|
48
|
+
vulkano();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. Project structure
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
your-app/
|
|
55
|
+
βββ app.js # Entry point
|
|
56
|
+
βββ public/ # Static files served over HTTP
|
|
57
|
+
β βββ css/
|
|
58
|
+
β βββ js/
|
|
59
|
+
β βββ img/
|
|
60
|
+
β βββ files/ # Uploaded files
|
|
61
|
+
βββ vulkano/ # Your application
|
|
62
|
+
βββ config/
|
|
63
|
+
β βββ settings.js # App-wide settings (port, database, JWTβ¦)
|
|
64
|
+
β βββ routes.js # Explicit route overrides (optional)
|
|
65
|
+
β βββ env/ # Per-environment config overrides
|
|
66
|
+
β βββ express/ # Express middleware customization
|
|
67
|
+
β βββ settings.js # App-wide settings (port, database, JWTβ¦)
|
|
68
|
+
β βββ routes.js # Explicit route overrides (optional)
|
|
69
|
+
β βββ locales/ # i18n translation files (en.js, es.js, etc.)
|
|
70
|
+
βββ controllers/ # Request handlers
|
|
71
|
+
βββ models/ # Mongoose model definitions
|
|
72
|
+
βββ services/ # Shared services & libs (auto-loaded as globals)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Routing
|
|
78
|
+
|
|
79
|
+
Vulkano resolves routes **by convention** β no route file required for standard CRUD.
|
|
80
|
+
|
|
81
|
+
### Convention-based (automatic)
|
|
82
|
+
|
|
83
|
+
| HTTP method | URL | Resolves to |
|
|
84
|
+
|-------------|------------------|-------------------------------------|
|
|
85
|
+
| `GET` | `/user` | `UserController.get` |
|
|
86
|
+
| `POST` | `/user` | `UserController.post` |
|
|
87
|
+
| `PUT` | `/user/42` | `UserController['put :id']` |
|
|
88
|
+
| `PATCH` | `/user/42` | `UserController['patch :id']` |
|
|
89
|
+
| `DELETE` | `/user/42` | `UserController['delete :id']` |
|
|
90
|
+
| `POST` | `/user/save` | `UserController['post save']` |
|
|
91
|
+
| `GET` | `/user/42/info` | `UserController['get :id/info']` |
|
|
92
|
+
|
|
93
|
+
### Controller example
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
// vulkano/controllers/UserController.js
|
|
97
|
+
module.exports = {
|
|
98
|
+
|
|
99
|
+
get(req, res) {
|
|
100
|
+
res.vsr(Promise.resolve({ users: [] }));
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
'get :id': function (req, res) {
|
|
104
|
+
res.vsr(Promise.resolve({ id: req.params.id }));
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
'post save': function (req, res) {
|
|
108
|
+
res.vsr(Promise.resolve({ saved: true }));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
};
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Explicit routes β `config/routes.js`
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
module.exports = [
|
|
118
|
+
{ method: 'GET', path: '/health', controller: 'StatusController', action: 'ping' },
|
|
119
|
+
{ method: 'POST', path: '/auth/login', controller: 'AuthController', action: 'login' },
|
|
120
|
+
];
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
You can also register routes with inline handlers or using `app.vulkano.get/post/β¦`.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Responses β `res.vsr()`
|
|
128
|
+
|
|
129
|
+
Every controller action uses `res.vsr(promise)`. It wraps the resolved value in a standard envelope:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{ "success": true, "statusCode": 200, "data": { β¦ } }
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Errors are handled automatically:
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
// Custom error with status code
|
|
139
|
+
res.vsr(VSError.reject('Not allowed', 403));
|
|
140
|
+
|
|
141
|
+
// 404
|
|
142
|
+
res.vsr(VSError.notFound('User'));
|
|
143
|
+
|
|
144
|
+
// Plain rejection β 500
|
|
145
|
+
res.vsr(Promise.reject(new Error('Something went wrong')));
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Scaffold β zero-code REST API
|
|
151
|
+
|
|
152
|
+
Point a controller at a model and get a full REST API for free:
|
|
153
|
+
|
|
154
|
+
```js
|
|
155
|
+
// vulkano/controllers/api/ProductController.js
|
|
156
|
+
module.exports = {
|
|
157
|
+
scaffold: 'Product', // Mongoose model name
|
|
158
|
+
allowedMethods: ['get', 'post', 'put', 'patch', 'delete']
|
|
159
|
+
};
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
This automatically exposes:
|
|
163
|
+
|
|
164
|
+
| Method | Path | Action |
|
|
165
|
+
|----------|--------------------|------------------|
|
|
166
|
+
| `GET` | `/api/product` | List (paginated) |
|
|
167
|
+
| `GET` | `/api/product/:id` | Get by ID |
|
|
168
|
+
| `POST` | `/api/product` | Create |
|
|
169
|
+
| `PUT` | `/api/product/:id` | Replace |
|
|
170
|
+
| `PATCH` | `/api/product/:id` | Partial update |
|
|
171
|
+
| `DELETE` | `/api/product/:id` | Soft-delete |
|
|
172
|
+
|
|
173
|
+
Query string params supported on list: `page`, `per_page`, `sort`, `search`, `fields`.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Models
|
|
56
178
|
|
|
57
|
-
vulkano()
|
|
179
|
+
Models live in `vulkano/models/` and are auto-loaded as globals (e.g., `Product`).
|
|
180
|
+
|
|
181
|
+
```js
|
|
182
|
+
// vulkano/models/Product.js
|
|
183
|
+
module.exports = {
|
|
184
|
+
attributes: {
|
|
185
|
+
name: { type: String, required: true },
|
|
186
|
+
price: { type: Number, default: 0 },
|
|
187
|
+
tags: { type: [String] }
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
// Lifecycle hooks
|
|
191
|
+
beforeSave(next) {
|
|
192
|
+
this.updatedAt = new Date();
|
|
193
|
+
next();
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Every model automatically gets `active`, `createdAt`, and `updatedAt` fields.
|
|
199
|
+
Models use [`mongoose-paginate-v2`](https://github.com/aravindnc/mongoose-paginate-v2) for pagination.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Built-in Global Libs
|
|
204
|
+
|
|
205
|
+
All files in `vulkano/services/` are auto-loaded as globals. The framework also exposes:
|
|
206
|
+
|
|
207
|
+
| Global | Description |
|
|
208
|
+
|-------------|-----------------------------------------------------------------|
|
|
209
|
+
| `VSError` | Structured error factory (`reject`, `notFound`, `badRequest`) |
|
|
210
|
+
| `Jwt` | Sign / verify JWT tokens |
|
|
211
|
+
| `Paginate` | Query serialization and pagination helpers |
|
|
212
|
+
| `Merge` | Deep-merge utility (deepmerge-compatible, full option support) |
|
|
213
|
+
| `Encrypter` | Hash and compare passwords (bcrypt-based) |
|
|
214
|
+
| `Filter` | Input sanitization helpers |
|
|
215
|
+
| `Crontab` | Schedule recurring jobs with cron expressions |
|
|
216
|
+
| `ApiClient` | HTTP client for calling external APIs (native fetch + undici) |
|
|
217
|
+
| `Download` | File download helper |
|
|
218
|
+
| `i18n` | Internationalization via i18next |
|
|
219
|
+
| `mongoose` | Mongoose instance |
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## File Uploads
|
|
224
|
+
|
|
225
|
+
Vulkano uses [Multer](https://github.com/expressjs/multer) v2. Files are available on `req.files` after a `multipart/form-data` POST:
|
|
226
|
+
|
|
227
|
+
```js
|
|
228
|
+
'post upload': function (req, res) {
|
|
229
|
+
const files = (req.files || []).map((f) => ({
|
|
230
|
+
fieldname: f.fieldname,
|
|
231
|
+
originalname: f.originalname,
|
|
232
|
+
mimetype: f.mimetype,
|
|
233
|
+
size: f.size
|
|
234
|
+
}));
|
|
235
|
+
res.vsr(Promise.resolve({ uploaded: files.length, files }));
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## JWT Authentication
|
|
242
|
+
|
|
243
|
+
Configure in `vulkano/config/express/jwt.js`:
|
|
244
|
+
|
|
245
|
+
```js
|
|
246
|
+
module.exports = {
|
|
247
|
+
secret: process.env.JWT_SECRET,
|
|
248
|
+
unless: ['/auth/login', '/health'] // Public paths (no token required)
|
|
249
|
+
};
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Sign a token anywhere in your app:
|
|
253
|
+
|
|
254
|
+
```js
|
|
255
|
+
const token = Jwt.encode({ userId: user._id });
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Cron Jobs
|
|
261
|
+
|
|
262
|
+
```js
|
|
263
|
+
// vulkano/services/Jobs.js
|
|
264
|
+
module.exports = {
|
|
265
|
+
init() {
|
|
266
|
+
Crontab.add('cleanup', '0 3 * * *', async () => {
|
|
267
|
+
await Report.deleteMany({ active: false });
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
};
|
|
58
271
|
```
|
|
59
272
|
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## i18n
|
|
276
|
+
|
|
277
|
+
Translation files go in `vulkano/config/locales/`. Use `i18n.t('key')` anywhere in your app.
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Socket.io
|
|
282
|
+
|
|
283
|
+
Enabled via `vulkano/config/settings.js`. Adapters for MongoDB and Redis are included out of the box.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Configuration β `vulkano/config/settings.js`
|
|
288
|
+
|
|
289
|
+
```js
|
|
290
|
+
module.exports = {
|
|
291
|
+
|
|
292
|
+
// PORT to listen on
|
|
293
|
+
port: process.env.PORT || 3000,
|
|
294
|
+
|
|
295
|
+
// Salt for password
|
|
296
|
+
salt: process.env.SALT_KEY || '',
|
|
297
|
+
|
|
298
|
+
// Database configuration
|
|
299
|
+
database: {
|
|
300
|
+
|
|
301
|
+
// MONGO_URI connection
|
|
302
|
+
connection: process.env.MONGO_URI,
|
|
303
|
+
|
|
304
|
+
// Settings before to connect
|
|
305
|
+
settings: {
|
|
306
|
+
strictQuery: false,
|
|
307
|
+
debug: false
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
// Additional config to mongoose
|
|
311
|
+
config: {
|
|
312
|
+
useNewUrlParser: true,
|
|
313
|
+
useUnifiedTopology: true,
|
|
314
|
+
// family: 4 // 4 (IPv4), 6 (IPv6), or null (default: OS family)
|
|
315
|
+
// useFindAndModify: false,
|
|
316
|
+
// useCreateIndex: true
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Running Tests
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
npm test # all suites
|
|
329
|
+
npm run test:watch # watch mode
|
|
330
|
+
npm run test:coverage # with coverage report
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
> Tests require Node.js 22. Use `nvm use 22` if needed.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Support
|
|
60
338
|
|
|
61
|
-
|
|
339
|
+
- [Open an issue](https://github.com/vulkanojs/vulkano-core/issues)
|
|
340
|
+
- [Open Collective β Backers](https://opencollective.com/vulkanojs#backers)
|
|
341
|
+
- [Buy me a coffee](https://buymeacoffee.com/argordmel)
|
|
62
342
|
|
|
63
|
-
|
|
64
|
-
- `config` - Your config files
|
|
65
|
-
- `env` - Folder for custom environment settings
|
|
66
|
-
- `express` - Folder to customize the server (cookies, jwt, helmet, cors and settings)
|
|
67
|
-
- `locales` - i18n folder
|
|
68
|
-
- `views` - Folder to customize filters and helpers to Nunjukcs
|
|
69
|
-
- `controllers` - Your controllers
|
|
70
|
-
- `models` - Your models
|
|
71
|
-
- `services` - Your services or libs
|
|
343
|
+
---
|
|
72
344
|
|
|
73
|
-
##
|
|
345
|
+
## License
|
|
74
346
|
|
|
75
|
-
|
|
76
|
-
- `css` - Styles
|
|
77
|
-
- `fonts` - Fonts
|
|
78
|
-
- `img` - Images
|
|
79
|
-
- `js` - Javascript
|
|
80
|
-
- `files` - Files uploaded
|
|
347
|
+
MIT Β© [Vulkano Team](https://github.com/vulkanojs)
|
package/app.js
CHANGED
|
@@ -6,18 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
// Start Time for logs
|
|
9
|
-
global.START_TIME =
|
|
9
|
+
global.START_TIME = Date.now();
|
|
10
10
|
|
|
11
11
|
const dotenv = require('dotenv');
|
|
12
12
|
const path = require('path');
|
|
13
|
-
const moment = require('moment');
|
|
14
|
-
const merge = require('deepmerge');
|
|
15
|
-
const _ = require('underscore');
|
|
16
13
|
const v8 = require('v8');
|
|
17
14
|
const fs = require('fs');
|
|
18
15
|
|
|
19
16
|
global.app = {};
|
|
20
|
-
global._ = _;
|
|
21
17
|
|
|
22
18
|
const rootProject = path.resolve(process.cwd());
|
|
23
19
|
|
|
@@ -62,7 +58,7 @@ if (!fs.existsSync(PUBLIC_PATH)) {
|
|
|
62
58
|
}
|
|
63
59
|
|
|
64
60
|
// Read Dontenv config
|
|
65
|
-
dotenv.config({ path: `${ABS_PATH}/.env
|
|
61
|
+
dotenv.config({ path: `${ABS_PATH}/.env`, quiet: !process.env.DOTENV_VERBOSE });
|
|
66
62
|
|
|
67
63
|
// Include all api config
|
|
68
64
|
const config = require('include-all')({
|
|
@@ -71,6 +67,8 @@ const config = require('include-all')({
|
|
|
71
67
|
optional: true
|
|
72
68
|
});
|
|
73
69
|
|
|
70
|
+
const merge = require('./libs/Merge');
|
|
71
|
+
|
|
74
72
|
//
|
|
75
73
|
// Get package.json information
|
|
76
74
|
//
|
|
@@ -236,7 +234,7 @@ async function startVulkano() {
|
|
|
236
234
|
const startUpConfig = [];
|
|
237
235
|
startUpConfig.push(' SOCKETS: ', `${colors.fg.green}${showColumn(socketText, 10)}${colors.reset}`);
|
|
238
236
|
startUpConfig.push(' | ');
|
|
239
|
-
startUpConfig.push(` STARTUP: ${colors.fg.green}${
|
|
237
|
+
startUpConfig.push(` STARTUP: ${colors.fg.green}${((Date.now() - global.START_TIME) / 1000).toFixed(3)}s${colors.reset}`);
|
|
240
238
|
console.log(startUpConfig.join(''));
|
|
241
239
|
|
|
242
240
|
const dbConfig = [];
|
package/bootstrap/express.js
CHANGED
package/bootstrap/server.js
CHANGED
|
@@ -56,6 +56,10 @@ module.exports = function loadServer() {
|
|
|
56
56
|
|
|
57
57
|
const vulkano = express();
|
|
58
58
|
|
|
59
|
+
// Expose the Express instance early so that custom() initializers in
|
|
60
|
+
// config/routes.js can register routes via app.vulkano.get(), app.vulkano.post(), etc.
|
|
61
|
+
app.vulkano = vulkano;
|
|
62
|
+
|
|
59
63
|
// Settings
|
|
60
64
|
vulkano.enable('trust proxy');
|
|
61
65
|
|
|
@@ -173,8 +177,8 @@ module.exports = function loadServer() {
|
|
|
173
177
|
res.header('Access-Control-Allow-Origin', '*');
|
|
174
178
|
res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
|
|
175
179
|
}
|
|
176
|
-
res.header('Allow', 'GET,PUT,POST,DELETE,OPTIONS');
|
|
177
|
-
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
|
|
180
|
+
res.header('Allow', 'GET,PUT,PATCH,POST,DELETE,OPTIONS');
|
|
181
|
+
res.header('Access-Control-Allow-Methods', 'GET,PUT,PATCH,POST,DELETE,OPTIONS');
|
|
178
182
|
|
|
179
183
|
res.status(200).end();
|
|
180
184
|
|
|
@@ -230,7 +234,7 @@ module.exports = function loadServer() {
|
|
|
230
234
|
tmpCorsHeaders = tmpCorsHeaders.concat(expressConfig.cors.headers || []);
|
|
231
235
|
|
|
232
236
|
res.header('Access-Control-Allow-Origin', expressConfig.cors.origin);
|
|
233
|
-
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
|
|
237
|
+
res.header('Access-Control-Allow-Methods', 'GET,PUT,PATCH,POST,DELETE,OPTIONS');
|
|
234
238
|
res.header('Access-Control-Allow-Headers', tmpCorsHeaders.join(', '));
|
|
235
239
|
|
|
236
240
|
// Disable CACHE in API resources.
|
|
@@ -440,10 +444,15 @@ module.exports = function loadServer() {
|
|
|
440
444
|
next();
|
|
441
445
|
});
|
|
442
446
|
|
|
443
|
-
// Middleware Folder
|
|
447
|
+
// Middleware Folder β routes.js always loads first if present
|
|
444
448
|
const middlewares = app.config.middlewares || {};
|
|
445
449
|
|
|
450
|
+
if (middlewares.routes && typeof middlewares.routes === 'function') {
|
|
451
|
+
vulkano.use(middlewares.routes);
|
|
452
|
+
}
|
|
453
|
+
|
|
446
454
|
Object.keys(middlewares).forEach( (item) => {
|
|
455
|
+
if (item === 'routes') return;
|
|
447
456
|
const middlewareFunction = middlewares[item];
|
|
448
457
|
if (typeof middlewareFunction === 'function') {
|
|
449
458
|
vulkano.use(middlewareFunction);
|
|
@@ -497,13 +506,26 @@ module.exports = function loadServer() {
|
|
|
497
506
|
Object.keys(this.routes || {}).forEach((i) => {
|
|
498
507
|
|
|
499
508
|
const current = this.routes[i];
|
|
509
|
+
|
|
510
|
+
// Initializer functions: keys that are not path-based ('/...') and not
|
|
511
|
+
// 'METHOD path' pairs are treated as startup hooks that register routes
|
|
512
|
+
// directly via app.vulkano.get(), app.vulkano.post(), etc.
|
|
513
|
+
const keyParts = i.split(' ');
|
|
514
|
+
const isPathRoute = i.includes('/');
|
|
515
|
+
const isMethodRoute = ['get', 'post', 'put', 'delete', 'patch'].includes(keyParts[0].toLowerCase()) && keyParts.length > 1;
|
|
516
|
+
|
|
517
|
+
if (!isPathRoute && !isMethodRoute && typeof current === 'function') {
|
|
518
|
+
current();
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
|
|
500
522
|
const parts = i.split(' ');
|
|
501
523
|
let pathToRun = parts.pop();
|
|
502
524
|
|
|
503
525
|
// Capture the HTTP Method
|
|
504
526
|
let option = (parts[0] !== undefined) ? String(parts[0]).toLowerCase() : 'get';
|
|
505
527
|
|
|
506
|
-
if (option !== 'get' && option !== 'post' && option !== 'put' && option !== 'delete') {
|
|
528
|
+
if (option !== 'get' && option !== 'post' && option !== 'put' && option !== 'patch' && option !== 'delete') {
|
|
507
529
|
option = 'get';
|
|
508
530
|
}
|
|
509
531
|
|
|
@@ -600,7 +622,9 @@ module.exports = function loadServer() {
|
|
|
600
622
|
path: routePath
|
|
601
623
|
} = r || {};
|
|
602
624
|
|
|
603
|
-
const routerControllerToCheck =
|
|
625
|
+
const routerControllerToCheck = (typeof Filter !== 'undefined')
|
|
626
|
+
? Filter.get(req.path, 'trim', '/').split('/')[0]
|
|
627
|
+
: req.path.replace(/^\/+|\/+$/g, '').split('/')[0];
|
|
604
628
|
|
|
605
629
|
return routePath.startsWith(`/${routerControllerToCheck}`);
|
|
606
630
|
|
|
@@ -668,21 +692,26 @@ module.exports = function loadServer() {
|
|
|
668
692
|
|
|
669
693
|
}
|
|
670
694
|
|
|
671
|
-
|
|
695
|
+
const errStack = (err && err.stack) ? String(err.stack) : '';
|
|
696
|
+
const isMissingTemplate = errStack.includes('template not found');
|
|
672
697
|
|
|
673
|
-
|
|
674
|
-
|
|
698
|
+
let missingView = '';
|
|
699
|
+
if (isMissingTemplate) {
|
|
700
|
+
const afterNotFound = errStack.split('not found:')[1] || '';
|
|
701
|
+
missingView = `${afterNotFound.split('.')[0].trim()}.html`;
|
|
675
702
|
}
|
|
676
703
|
|
|
704
|
+
const errorViewToShow = isMissingTemplate
|
|
705
|
+
? `${CORE_PATH}/views/errors/no_view.html`
|
|
706
|
+
: `${CORE_PATH}/views/errors/exception.html`;
|
|
707
|
+
|
|
677
708
|
res.render(errorViewToShow, {
|
|
678
709
|
statusCode: status,
|
|
679
710
|
method: req.method,
|
|
680
711
|
controller: req.path.split('/')[1],
|
|
681
712
|
action: req.path.split('/')[2],
|
|
682
|
-
view:
|
|
683
|
-
|
|
684
|
-
: '',
|
|
685
|
-
stack: err.stack
|
|
713
|
+
view: missingView,
|
|
714
|
+
stack: errStack
|
|
686
715
|
});
|
|
687
716
|
|
|
688
717
|
});
|
|
@@ -789,7 +818,7 @@ module.exports = function loadServer() {
|
|
|
789
818
|
|
|
790
819
|
let mongoClientDB = null;
|
|
791
820
|
|
|
792
|
-
//
|
|
821
|
+
// If no active Mongoose connection, create a dedicated one
|
|
793
822
|
if (!mongoose.connection.readyState) {
|
|
794
823
|
|
|
795
824
|
if (!socketsConnection) {
|
|
@@ -911,7 +940,7 @@ module.exports = function loadServer() {
|
|
|
911
940
|
|
|
912
941
|
});
|
|
913
942
|
|
|
914
|
-
//
|
|
943
|
+
// Expose io globally so controllers can emit events
|
|
915
944
|
global.io = io;
|
|
916
945
|
vulkano.set('socketio', io);
|
|
917
946
|
|
|
@@ -93,6 +93,20 @@ module.exports = (modelName, allowedMethods) => {
|
|
|
93
93
|
|
|
94
94
|
},
|
|
95
95
|
|
|
96
|
+
'patch :id': function onPatchRecord(req, res) {
|
|
97
|
+
|
|
98
|
+
const {
|
|
99
|
+
id
|
|
100
|
+
} = req.params || {};
|
|
101
|
+
|
|
102
|
+
const {
|
|
103
|
+
body
|
|
104
|
+
} = req || {};
|
|
105
|
+
|
|
106
|
+
res.vsr(global[modelName].update(id, body), 202);
|
|
107
|
+
|
|
108
|
+
},
|
|
109
|
+
|
|
96
110
|
'delete :id': function onDeleteRecord(req, res) {
|
|
97
111
|
|
|
98
112
|
const {
|
|
@@ -124,6 +138,10 @@ module.exports = (modelName, allowedMethods) => {
|
|
|
124
138
|
delete allMethods['put :id'];
|
|
125
139
|
}
|
|
126
140
|
|
|
141
|
+
if (!tempAllowedMethods.includes('patch')) {
|
|
142
|
+
delete allMethods['patch :id'];
|
|
143
|
+
}
|
|
144
|
+
|
|
127
145
|
if (!tempAllowedMethods.includes('delete')) {
|
|
128
146
|
delete allMethods['delete :id'];
|
|
129
147
|
}
|
|
@@ -17,7 +17,7 @@ module.exports = function loadControllersApplication() {
|
|
|
17
17
|
|
|
18
18
|
Object.keys(AllControllers).forEach( (controller) => {
|
|
19
19
|
|
|
20
|
-
const methods = ['get', 'post', 'put', 'delete'];
|
|
20
|
+
const methods = ['get', 'post', 'put', 'patch', 'delete'];
|
|
21
21
|
const current = AllControllers[controller];
|
|
22
22
|
|
|
23
23
|
const {
|
|
@@ -101,7 +101,7 @@ module.exports = function loadControllersApplication() {
|
|
|
101
101
|
method = pathToRun.toLowerCase();
|
|
102
102
|
pathToRun = `/${moduleName}/${controllerName}/`;
|
|
103
103
|
} else {
|
|
104
|
-
pathToRun = `/${moduleName}/${controllerName}/${pathToRun.replace(/GET|POST|DELETE|PUT/i, '')}`;
|
|
104
|
+
pathToRun = `/${moduleName}/${controllerName}/${pathToRun.replace(/GET|POST|DELETE|PUT|PATCH/i, '')}`;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
}
|
|
@@ -132,7 +132,7 @@ module.exports = function loadControllersApplication() {
|
|
|
132
132
|
method = pathToRun.toLowerCase();
|
|
133
133
|
pathToRun = `/${controllerName}/`;
|
|
134
134
|
} else {
|
|
135
|
-
pathToRun = `/${controllerName}/${pathToRun.replace(/GET|POST|DELETE|PUT/i, '')}`;
|
|
135
|
+
pathToRun = `/${controllerName}/${pathToRun.replace(/GET|POST|DELETE|PUT|PATCH/i, '')}`;
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
|
package/database/mongodb.js
CHANGED
|
@@ -2,13 +2,9 @@
|
|
|
2
2
|
* Database connection
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
const Promise = require('bluebird');
|
|
6
|
-
const paginate = require('mongoose-paginate-v2');
|
|
7
|
-
const merge = require('deepmerge');
|
|
8
|
-
|
|
9
5
|
const mongoose = require('mongoose');
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
const paginate = require('mongoose-paginate-v2');
|
|
7
|
+
const merge = require('../libs/Merge');
|
|
12
8
|
|
|
13
9
|
global.mongoose = mongoose;
|
|
14
10
|
global.Virtual = 'Virtual';
|
|
@@ -45,15 +41,13 @@ module.exports = async function loadDatabaseApplication() {
|
|
|
45
41
|
: (connection || null);
|
|
46
42
|
|
|
47
43
|
if (!toConnect) {
|
|
48
|
-
throw `Invalid
|
|
44
|
+
throw new Error(`Invalid connection to MongoDB with source "${connection}"`);
|
|
49
45
|
}
|
|
50
46
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
};
|
|
54
|
-
|
|
47
|
+
// Build connection props from user config; family defaults to 4 (IPv4)
|
|
48
|
+
// unless explicitly set to another value in database.config
|
|
55
49
|
const connectionProps = merge.all([
|
|
56
|
-
|
|
50
|
+
{ family: 4 },
|
|
57
51
|
(database ? database.config || {} : {})
|
|
58
52
|
]);
|
|
59
53
|
|