@vulkano/core 1.20.0 → 1.20.2
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/bootstrap/express.js +6 -1
- package/examples/controllers/ExampleController.js +22 -0
- package/examples/controllers/RestExampleController.js +78 -0
- package/examples/controllers/RestScaffoldController.js +21 -0
- package/examples/models/Example.js +178 -0
- package/examples/models/ExampleWithScaffold.js +51 -0
- package/examples/views/_shared/templates/default.html +20 -0
- package/examples/views/index.html +7 -0
- package/package.json +19 -18
- package/pnpm-workspace.yaml +8 -1
package/bootstrap/express.js
CHANGED
|
@@ -51,7 +51,12 @@ module.exports = function getExpressConfiguration() {
|
|
|
51
51
|
sockets: {},
|
|
52
52
|
redis: {},
|
|
53
53
|
multer: {
|
|
54
|
-
dest: global.PUBLIC_PATH ? path.join(global.PUBLIC_PATH, 'files') : 'public/files'
|
|
54
|
+
dest: global.PUBLIC_PATH ? path.join(global.PUBLIC_PATH, 'files') : 'public/files',
|
|
55
|
+
limits: {
|
|
56
|
+
// Prevents DoS via deeply nested field names (e.g. a[b][c][d]...),
|
|
57
|
+
// which multer forwards to append-field's unbounded recursive parser.
|
|
58
|
+
fieldNestingDepth: 5
|
|
59
|
+
}
|
|
55
60
|
},
|
|
56
61
|
morgan: {
|
|
57
62
|
format: 'dev',
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// This is an example for a simple controller that renders a view.
|
|
2
|
+
// It is a good starting point for creating a simple web application.
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
|
|
6
|
+
// Method to render the home page
|
|
7
|
+
// Example: domain.com/example/
|
|
8
|
+
get(req, res) {
|
|
9
|
+
|
|
10
|
+
res.render('example/index.html', { title: 'This is an example home page' });
|
|
11
|
+
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
// Method to render the about page
|
|
15
|
+
// Example: domain.com/example/about/
|
|
16
|
+
about(req, res) {
|
|
17
|
+
|
|
18
|
+
res.render('example/about.html', { title: 'This is an example about page' });
|
|
19
|
+
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/* global Example */
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* This endpoint is protected by JWT, please disabled it to test
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// This is an example for Full API Rest controller that provides CRUD operations.
|
|
8
|
+
// It is a good starting point for creating a RESTful API.
|
|
9
|
+
// The business logic is implemented in the Example model, which is a good practice
|
|
10
|
+
// to keep the controller thin and focused on handling requests and responses.
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
|
|
14
|
+
// Method to get all records from the model
|
|
15
|
+
// Example: GET /projects/?page=1
|
|
16
|
+
get(req, res) {
|
|
17
|
+
|
|
18
|
+
// Status code: 200 (default)
|
|
19
|
+
res.vsr(Example.getAll(req.query || {}));
|
|
20
|
+
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
// Method to create a new record in the model
|
|
24
|
+
// Example: POST /projects/
|
|
25
|
+
post(req, res) {
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
body
|
|
29
|
+
} = req || {};
|
|
30
|
+
|
|
31
|
+
// Status code: 201 (created)
|
|
32
|
+
res.vsr(Example.create(body), 201);
|
|
33
|
+
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// Method to get a record by id from the model
|
|
37
|
+
// Example: GET /projects/:id
|
|
38
|
+
'get :id': (req, res) => {
|
|
39
|
+
|
|
40
|
+
const {
|
|
41
|
+
id
|
|
42
|
+
} = req.params;
|
|
43
|
+
|
|
44
|
+
// Status code: 200 (default)
|
|
45
|
+
res.vsr(Example.getExample(id));
|
|
46
|
+
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
// Method to update a record by id in the model
|
|
50
|
+
// Example: PUT /projects/:id
|
|
51
|
+
'put :id': (req, res) => {
|
|
52
|
+
|
|
53
|
+
const {
|
|
54
|
+
id
|
|
55
|
+
} = req.params;
|
|
56
|
+
|
|
57
|
+
const {
|
|
58
|
+
body
|
|
59
|
+
} = req || {};
|
|
60
|
+
|
|
61
|
+
// Status code: 202 (accepted)
|
|
62
|
+
res.vsr(Example.update(id, body), 202);
|
|
63
|
+
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
// Method to delete a record by id from the model
|
|
67
|
+
// Example: DELETE /projects/:id
|
|
68
|
+
'delete :id': (req, res) => {
|
|
69
|
+
|
|
70
|
+
const {
|
|
71
|
+
id
|
|
72
|
+
} = req.params;
|
|
73
|
+
|
|
74
|
+
res.vsr(Example.remove(id), 204);
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This endpoint is protected by JWT, please disable it to test
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Scaffold Controller is a controller that provides CRUD operations for a model.
|
|
6
|
+
// It is a generic controller that can be used to create, read, update, and delete records
|
|
7
|
+
// from a model. It is a good starting point for creating a
|
|
8
|
+
// RESTful API.
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
|
|
12
|
+
// Extend methods of Scaffold Controller and Scaffold Model
|
|
13
|
+
scaffold: true,
|
|
14
|
+
|
|
15
|
+
// Allowed methods
|
|
16
|
+
allowedMethods: ['get', 'post', 'put', 'delete'],
|
|
17
|
+
|
|
18
|
+
// Model to CRUD (create, read, update, and delete) records
|
|
19
|
+
model: 'ExampleWithScaffold'
|
|
20
|
+
|
|
21
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/* global Example */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Example.js
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// This is an example for a simple model that provides CRUD operations.
|
|
8
|
+
// Provide a flexible way to implement business logic in the model, which is a good practice
|
|
9
|
+
// to keep the controller thin and focused on handling requests and responses.
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Fields
|
|
15
|
+
*/
|
|
16
|
+
attributes: {
|
|
17
|
+
name: {
|
|
18
|
+
type: String,
|
|
19
|
+
required: true
|
|
20
|
+
},
|
|
21
|
+
age: {
|
|
22
|
+
type: Number,
|
|
23
|
+
required: false,
|
|
24
|
+
validate: {
|
|
25
|
+
validator: (value) => {
|
|
26
|
+
const isValid = (value >= 21) ? true : false;
|
|
27
|
+
return isValid;
|
|
28
|
+
},
|
|
29
|
+
message: 'Invalid Age: Must be +21.',
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// the fields:
|
|
33
|
+
// active, createdAt, updatedAt
|
|
34
|
+
// was created automatically
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
// Custom Index
|
|
38
|
+
indexes: [
|
|
39
|
+
{
|
|
40
|
+
name: 'text'
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Method to get all records by page
|
|
46
|
+
*
|
|
47
|
+
* @param {Object} props (page, perPage, search, sort)
|
|
48
|
+
* @returns {Promise}
|
|
49
|
+
*/
|
|
50
|
+
getAll(props) {
|
|
51
|
+
|
|
52
|
+
// Props to Query
|
|
53
|
+
const defaultProps = {
|
|
54
|
+
sort: 'createdAt|DESC',
|
|
55
|
+
searchBy: ['name'],
|
|
56
|
+
filter: {
|
|
57
|
+
active: true
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Populate
|
|
62
|
+
const populate = [];
|
|
63
|
+
|
|
64
|
+
// Query to Run
|
|
65
|
+
const query = Paginate.serializeQuery(defaultProps, props);
|
|
66
|
+
|
|
67
|
+
// Pagination
|
|
68
|
+
return Paginate.get(Example, query, populate);
|
|
69
|
+
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Method to get a record by id
|
|
74
|
+
*
|
|
75
|
+
* @param {ObjectID} id
|
|
76
|
+
* @returns {Promise}
|
|
77
|
+
*/
|
|
78
|
+
getExample(_id) {
|
|
79
|
+
|
|
80
|
+
// This is to prevent error while run the findOne
|
|
81
|
+
if (!(/^[a-fA-F0-9]{24}$/).test(_id)) {
|
|
82
|
+
return VSError.reject('Invalid ID. Record not found', 404);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return Example.findOne({ _id })
|
|
86
|
+
.then( (r) => {
|
|
87
|
+
|
|
88
|
+
if (!r) {
|
|
89
|
+
return VSError.notFound();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return r.toObject({ transform: true });
|
|
93
|
+
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Method to create a new record
|
|
100
|
+
* @param {Promise} data
|
|
101
|
+
*/
|
|
102
|
+
create(data) {
|
|
103
|
+
|
|
104
|
+
const obj = new Example(data);
|
|
105
|
+
return obj.save();
|
|
106
|
+
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Method to update a record
|
|
111
|
+
* @param {ObjectID} id
|
|
112
|
+
* @param {Object} data
|
|
113
|
+
* @returns {Promise}
|
|
114
|
+
*/
|
|
115
|
+
update(_id, data) {
|
|
116
|
+
|
|
117
|
+
return Example.getExample(_id)
|
|
118
|
+
.then( (record) => {
|
|
119
|
+
|
|
120
|
+
// Merge current info with incoming values
|
|
121
|
+
const merged = { ...record, ...data };
|
|
122
|
+
|
|
123
|
+
return Example
|
|
124
|
+
.findOneAndUpdate({ _id }, merged, { new: true })
|
|
125
|
+
.then( (r) => {
|
|
126
|
+
|
|
127
|
+
const tmp = r.toObject({ transform: true });
|
|
128
|
+
return tmp;
|
|
129
|
+
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Method to delete a record
|
|
138
|
+
* @param {ObjectID} id
|
|
139
|
+
* @returns {Promise}
|
|
140
|
+
*/
|
|
141
|
+
delete(id) {
|
|
142
|
+
|
|
143
|
+
// Soft delete
|
|
144
|
+
return this.update(id, { active: false });
|
|
145
|
+
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Before save callback
|
|
150
|
+
* @param {Callback} cb
|
|
151
|
+
*/
|
|
152
|
+
beforeSave(cb) {
|
|
153
|
+
|
|
154
|
+
const data = this;
|
|
155
|
+
|
|
156
|
+
console.log('Running callback before save');
|
|
157
|
+
console.log(data);
|
|
158
|
+
|
|
159
|
+
// All good!
|
|
160
|
+
cb();
|
|
161
|
+
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Callback after save
|
|
166
|
+
* @param {Callback} cb
|
|
167
|
+
*/
|
|
168
|
+
afterSave(data, cb) {
|
|
169
|
+
|
|
170
|
+
console.log('Running callback after save');
|
|
171
|
+
console.log(data);
|
|
172
|
+
|
|
173
|
+
// All good!
|
|
174
|
+
cb();
|
|
175
|
+
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExampleWithScaffold.js
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// This is an example for a simple model that provides CRUD operations with a scaffold.
|
|
6
|
+
// Provide a flexible way to implement business logic in the model, which is a good practice
|
|
7
|
+
// to keep the controller thin and focused on handling requests and responses.
|
|
8
|
+
|
|
9
|
+
// Scaffold (Model name: ExampleWithScaffold) - Allowed Methods:
|
|
10
|
+
// ExampleWidthScaffold.getAllExampleWidthScaffold(props) { page, perPage, search, sort }
|
|
11
|
+
// ExampleWidthScaffold.getAll(props) (alias to getAllModelName)
|
|
12
|
+
// ExampleWidthScaffold.create(payload) { name, age }
|
|
13
|
+
// ExampleWidthScaffold.getByField(email, 'email') (alias)
|
|
14
|
+
// ExampleWidthScaffold.getExampleWidthScaffold(id)
|
|
15
|
+
// ExampleWidthScaffold.update(id, payload)
|
|
16
|
+
// ExampleWidthScaffold.delete(id)
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Fillable fields for the update method
|
|
22
|
+
* to prevent update of non-allowed fields like createdAt, updatedAt, etc.
|
|
23
|
+
* @type Array
|
|
24
|
+
*/
|
|
25
|
+
fillable: ['name', 'age'],
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Fields
|
|
29
|
+
*/
|
|
30
|
+
attributes: {
|
|
31
|
+
name: {
|
|
32
|
+
type: String,
|
|
33
|
+
required: true
|
|
34
|
+
},
|
|
35
|
+
age: {
|
|
36
|
+
type: Number,
|
|
37
|
+
required: false,
|
|
38
|
+
validate: {
|
|
39
|
+
validator: (value) => {
|
|
40
|
+
const isValid = (value >= 21) ? true : false;
|
|
41
|
+
return isValid;
|
|
42
|
+
},
|
|
43
|
+
message: 'Invalid Age: Must be +21.',
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
// the fields:
|
|
47
|
+
// active, createdAt, updatedAt
|
|
48
|
+
// was created automatically
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!DOCTYPE HTML>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<title>{{ title }}</title>
|
|
5
|
+
<meta charset=UTF-8 >
|
|
6
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
8
|
+
<link rel="shortcut icon" type="image/png" href="/favicon.png?v={{ app.pkg.version }}" />
|
|
9
|
+
|
|
10
|
+
{{ vite({ entry: 'app', type: 'style' }) | safe }}
|
|
11
|
+
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
|
|
15
|
+
<div id="app">{% block content %}{% endblock %}</div>
|
|
16
|
+
|
|
17
|
+
{{ vite({ entry: 'app', type: 'script' }) | safe }}
|
|
18
|
+
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vulkano/core",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.2",
|
|
4
4
|
"description": "A MVC framework using Express 4",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Vulkano Team",
|
|
@@ -35,37 +35,38 @@
|
|
|
35
35
|
"url": "https://github.com/vulkanojs/vulkano-core/issues"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
38
|
+
"jest": "^30.4.2",
|
|
39
|
+
"nodemon": "^3.1.14",
|
|
40
|
+
"socket.io-client": "^4.8.3",
|
|
41
|
+
"vite-plus": "catalog:"
|
|
41
42
|
},
|
|
42
43
|
"packageManager": "pnpm@11.1.2",
|
|
43
44
|
"dependencies": {
|
|
44
45
|
"@socket.io/mongo-adapter": "^0.4.0",
|
|
45
46
|
"@socket.io/redis-adapter": "^8.3.0",
|
|
46
|
-
"compression": "^1.8.
|
|
47
|
-
"connect-timeout": "^1.9.
|
|
47
|
+
"compression": "^1.8.1",
|
|
48
|
+
"connect-timeout": "^1.9.1",
|
|
48
49
|
"cookie-parser": "^1.4.7",
|
|
49
|
-
"cron": "^4.
|
|
50
|
+
"cron": "^4.4.0",
|
|
50
51
|
"dotenv": "^17.4.2",
|
|
51
52
|
"express": "^4.21.2",
|
|
52
53
|
"express-handlebars": "^9.0.1",
|
|
53
54
|
"express-jwt": "^8.5.1",
|
|
54
|
-
"express-session": "^1.
|
|
55
|
-
"express-useragent": "^2.1
|
|
55
|
+
"express-session": "^1.19.0",
|
|
56
|
+
"express-useragent": "^2.2.1",
|
|
56
57
|
"frameguard": "^4.0.0",
|
|
57
|
-
"helmet": "^8.
|
|
58
|
-
"i18next": "^26.
|
|
58
|
+
"helmet": "^8.3.0",
|
|
59
|
+
"i18next": "^26.3.6",
|
|
59
60
|
"include-all": "^4.0.3",
|
|
60
61
|
"jwt-simple": "^0.5.6",
|
|
61
62
|
"mongoose": "^8.12.1",
|
|
62
|
-
"mongoose-paginate-v2": "^1.9.
|
|
63
|
-
"morgan": "^1.
|
|
64
|
-
"multer": "^2.
|
|
63
|
+
"mongoose-paginate-v2": "^1.9.5",
|
|
64
|
+
"morgan": "^1.11.0",
|
|
65
|
+
"multer": "^2.2.0",
|
|
65
66
|
"nunjucks": "^3.2.4",
|
|
66
|
-
"redis": "^
|
|
67
|
-
"socket.io": "^4.8.
|
|
68
|
-
"socket.io-adapter": "^2.5.
|
|
69
|
-
"undici": "^
|
|
67
|
+
"redis": "^6.1.0",
|
|
68
|
+
"socket.io": "^4.8.3",
|
|
69
|
+
"socket.io-adapter": "^2.5.8",
|
|
70
|
+
"undici": "^7.28.0"
|
|
70
71
|
}
|
|
71
72
|
}
|
package/pnpm-workspace.yaml
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
allowBuilds:
|
|
2
2
|
fsevents: set this to true or false
|
|
3
|
+
unrs-resolver: set this to true or false
|
|
3
4
|
catalog:
|
|
4
5
|
vite: npm:@voidzero-dev/vite-plus-core@latest
|
|
5
6
|
vitest: npm:@voidzero-dev/vite-plus-test@latest
|
|
6
|
-
vite-plus:
|
|
7
|
+
vite-plus: ^0.2.4
|
|
7
8
|
overrides:
|
|
8
9
|
vite: "catalog:"
|
|
9
10
|
vitest: "catalog:"
|
|
11
|
+
# Safety floor for the real "vite" package pnpm auto-installs to satisfy
|
|
12
|
+
# @voidzero-dev/vite-plus-test / @vitest/mocker's peerDependency (they
|
|
13
|
+
# require actual vite, not the vite-plus-core alias). Keeps it above the
|
|
14
|
+
# server.fs.deny bypass (GHSA-v6wh-96g9-6wx3), fixed in 8.0.16.
|
|
15
|
+
"@vitest/mocker>vite": ^8.1.4
|
|
16
|
+
"@voidzero-dev/vite-plus-test>vite": ^8.1.4
|
|
10
17
|
peerDependencyRules:
|
|
11
18
|
allowAny:
|
|
12
19
|
- vite
|