This repository has been archived by the owner on Jan 4, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
/
index.js
298 lines (282 loc) · 8.43 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
const Mongoose = require('mongoose');
const Joi = require('joi');
const Boom = require('boom');
const Hoek = require('hoek');
const pluralize = require('pluralize');
const Schema = Mongoose.Schema;
const ObjectID = Schema.ObjectId;
/**
* The model definition should have the Joi Definitions also
* But not the required() method of Joi
* because it will be added by this method
* The Joi object will be used for both POST and PUT
* only difference will be of required() in various keys among them
*
* @function
* @name separateJoiValidationObject
* @param {object} config The Schema Config object
* @return {object}
*/
function separateJoiValidationObject(config) {
const post = {};
const put = {};
const schema = Object.assign({}, config);
Object.keys(schema).forEach((prop) => {
if (schema[prop] === null) {
throw new Error('Null configs are not supported!');
} else if (schema[prop].joi) {
const itemConf = schema[prop];
if (!itemConf.joi.isJoi) {
itemConf.joi = Joi.object(itemConf.joi);
}
put[prop] = itemConf.joi;
if (itemConf.required) {
post[prop] = itemConf.joi.required();
} else {
post[prop] = itemConf.joi;
}
delete schema[prop].joi;
}
});
return {
schema,
post,
put,
};
}
/**
* @function
* @name getController
* @param {object} model The Mongoose model object
* @param {object} joiValidationObject The Joi validation objects
* @return {object} object containing controller methods
*/
function getControllers(model, joiValidationObject, singularRouteName) {
const controllers = {
getAll: {
handler(request, reply) {
model.find({}, (err, data) => {
if (!err) {
reply(data);
} else {
reply(Boom.badImplementation(err)); // 500 error
}
});
},
},
getOne: {
handler(request, reply) {
model.findOne({
_id: ObjectID(request.params.id),
}, (err, data) => {
if (!err) {
reply(data);
} else {
reply(Boom.notFound(err)); // 500 error
}
});
},
},
create: {
validate: {
payload: joiValidationObject.post,
},
handler(request, reply) {
const payload = request.payload;
const object = new model(payload);
object.save((err, data) => {
if (!err) {
reply(data).created(`/${data._id}`); // HTTP 201
} else if (err.code === 11000 || err.code === 11001) {
reply(Boom.forbidden(`please provide another ${singularRouteName} id, it already exist`));
} else {
reply(Boom.forbidden(getErrorMessageFrom(err))); // HTTP 403
}
});
},
},
update: {
validate: {
payload: joiValidationObject.put,
},
handler(request, reply) {
model.findOne({
_id: request.params.id,
}, (err, dbObject) => {
if (!err) {
Object.keys(request.payload).forEach((prop) => {
if (request.payload[prop]) {
dbObject[prop] = request.payload[prop];
}
});
dbObject.save((error, data) => {
if (!error) {
reply(data).created(`/${data._id}`); // HTTP 201
} else if (error.code === 11000 || error.code === 11001) {
reply(Boom.forbidden(`please provide another ${singularRouteName} id, it already exist`));
} else {
reply(Boom.forbidden(getErrorMessageFrom(error))); // HTTP 403
}
});
} else {
reply(Boom.badImplementation(err)); // 500 error
}
});
},
},
remove: {
handler(request, reply) {
model.findOneAndRemove({
_id: ObjectID(request.params.id),
}, (err) => {
if (!err) {
reply({
message: `${singularRouteName} deleted successfully`,
});
} else if (!err) {
// Couldn't find the object.
reply(Boom.notFound());
} else {
reply(Boom.badRequest(`Could not delete ${singularRouteName}`));
}
});
},
},
};
return controllers;
}
/**
* @function
* @name getRoutes
* @param {object} controllers The object containing controller methods
* @param {string} routeBaseName The string which should be used for routebase
* @param {string} singularRouteName The singular entity name for routes
* @return {object} The routes object which can be plugged in hapijs or can be extended more
*/
function getRoutes(controllers, routeBaseName, singularRouteName) {
const routes = [
{
method: 'GET',
path: `/${routeBaseName}`,
config: {
description: `Get all ${routeBaseName}`,
notes: `Returns a list of ${routeBaseName} ordered by addition date`,
tags: ['api', routeBaseName],
},
handler: controllers.getAll.handler,
},
{
method: 'GET',
path: `/${routeBaseName}/{id}`,
config: {
description: `Get ${singularRouteName} by DB Id`,
notes: `Returns the ${singularRouteName} object if matched with the DB id`,
tags: ['api', routeBaseName],
},
handler: controllers.getOne.handler,
},
{
method: 'PUT',
path: `/${routeBaseName}/{id}`,
config: {
validate: controllers.update.validate,
description: `Update a ${singularRouteName}`,
notes: `Returns a ${singularRouteName} by the id passed in the path`,
tags: ['api', routeBaseName],
},
handler: controllers.update.handler,
},
{
method: 'DELETE',
path: `/${routeBaseName}/{id}`,
config: {
description: `Delete ${singularRouteName}`,
notes: `Returns the ${singularRouteName} deletion status`,
tags: ['api', routeBaseName],
},
handler: controllers.remove.handler,
},
{
method: 'POST',
path: `/${routeBaseName}`,
config: {
validate: controllers.create.validate,
description: `Add a ${singularRouteName}`,
notes: `Returns a ${singularRouteName} by the id passed in the path`,
tags: ['api', routeBaseName],
},
handler: controllers.create.handler,
},
];
return routes;
}
/**
* @function
* @name getModel
* @param {string} modelName The Mongoose Model name
* @param {object} schema The Mongoose Schema object
* @param {object} db The Mongoose DB object, if pased, use this otherwise use Mongoose
* @return {object} model The Mongoose model
*/
function getModel(modelName, schema, db) {
if (db === undefined) {
db = Mongoose;
}
return db.model(modelName, schema);
}
/**
* @function
* @name getSchema
* @param {object} schema definition object
* @return {object} mongoose schema
*/
function getSchema(definitionObject) {
return new Schema(definitionObject);
}
/**
* @function
* @name decorate
* @param {object} schemaDefinitionObject The Schema definition object
* @param {string} routeBaseName Route base in plurals
* @param {string} modelName Model name
* @param {string} singularRouteName Route base in singular
* @param {object} db The Mongoose connection object
* @return {object} Collection object containing ingredients of REST
*/
function decorate(schemaDefination, routeBaseName, modelName, singularRouteName, db) {
Hoek.assert(schemaDefination, 'Schema Defination is required');
Hoek.assert(routeBaseName, 'Route Base Name is required');
Hoek.assert(modelName, 'Model Name is required');
let validations = {};
let schema = null;
let model = null;
let controllers = {};
let routes = [];
if (!singularRouteName) {
singularRouteName = pluralize.singular(routeBaseName);
}
validations = separateJoiValidationObject(schemaDefination);
schema = getSchema(validations.schema);
model = getModel(modelName, schema, db);
controllers = getControllers(model, validations, singularRouteName);
routes = getRoutes(controllers, routeBaseName, singularRouteName);
return {
validations: {
post: validations.post,
put: validations.put,
},
schema,
model,
controllers,
routes,
};
}
/**
* For Manual Control (If needed)
*/
decorate.separateJoiValidationObject = separateJoiValidationObject;
decorate.getSchema = getSchema;
decorate.getModel = getModel;
decorate.getControllers = getControllers;
decorate.getRoutes = getRoutes;
module.exports = decorate;