@modular-rest/server 1.8.0 → 1.10.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modular-rest/server",
3
- "version": "1.8.0",
3
+ "version": "1.10.1",
4
4
  "description": "a nodejs module based on KOAJS for developing Rest-APIs in a modular solution.",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -38,7 +38,9 @@
38
38
  "nested-property": "^4.0.0"
39
39
  },
40
40
  "devDependencies": {
41
+ "@types/bson": "4.2.0",
41
42
  "@types/koa": "^2.14.0",
43
+ "@types/koa__cors": "^5.0.0",
42
44
  "typescript": "^5.3.3"
43
45
  }
44
- }
46
+ }
@@ -7,15 +7,22 @@ const Combination = require("./class/combinator");
7
7
  const DataProvider = require("./services/data_provider/service");
8
8
  const UserService = require("./services/user_manager/service");
9
9
 
10
- let defaultServiceRoot = __dirname + "/services";
10
+ const defaultServiceRoot = __dirname + "/services";
11
11
 
12
12
  /**
13
13
  * @typedef {import('koa')} Koa
14
+ * @typedef {import('http').Server} server
15
+ * @typedef {import('@koa/cors').Options} Cors
16
+ * @typedef {import('./class/security').PermissionGroup} PermissionGroup
17
+ * @typedef {import('./class/database_trigger.js')} DatabaseTrigger
14
18
  */
15
19
 
20
+ const { config, setConfig } = require("./config");
21
+
16
22
  /**
23
+ * Create a modular REST instance with Koa and MongoDB support.
17
24
  * @param {{
18
- * cors?: any; // Options for @koa/cors middleware.
25
+ * cors?: Cors; // CORS options.
19
26
  * modulesPath?: string; // Root directory of your router.js/db.js files.
20
27
  * staticPath?: {
21
28
  * rootDir?: string; // Root directory of your static files.
@@ -48,10 +55,13 @@ let defaultServiceRoot = __dirname + "/services";
48
55
  * };
49
56
  * verificationCodeGeneratorMethod: () => string; // A method to return a verification code when registering a new user.
50
57
  * collectionDefinitions?: CollectionDefinition[]; // An array of additional collection definitions.
58
+ * permissionGroups?: PermissionGroup[]; // An array of additional permission groups.
59
+ * authTriggers?: DatabaseTrigger[]; // An array of additional database triggers for the auth collection.
51
60
  * }} options
61
+ * @returns {Promise<{app: Koa, server: Server}>}
52
62
  */
53
- module.exports = async function createRest(options) {
54
- options = {
63
+ async function createRest(options) {
64
+ setConfig({
55
65
  port: 3000,
56
66
  dontListen: false,
57
67
  mongo: {
@@ -65,19 +75,19 @@ module.exports = async function createRest(options) {
65
75
  },
66
76
 
67
77
  ...options,
68
- };
78
+ });
69
79
 
70
- let app = new koa();
80
+ const app = new koa();
71
81
 
72
82
  /**
73
83
  * Plug in Cors
74
84
  */
75
- app.use(cors(options.cors || {}));
85
+ app.use(cors(config.cors || {}));
76
86
 
77
87
  /**
78
88
  * Plug in BodyParser
79
89
  */
80
- let bodyParserOptions = {
90
+ const bodyParserOptions = {
81
91
  multipart: true,
82
92
  };
83
93
  app.use(koaBody(bodyParserOptions));
@@ -85,14 +95,14 @@ module.exports = async function createRest(options) {
85
95
  /**
86
96
  * Plug In KoaStatic
87
97
  */
88
- if (options.staticPath) {
89
- app.use(koaStatic(options.staticPath));
98
+ if (config.staticPath) {
99
+ app.use(koaStatic(config.staticPath));
90
100
  }
91
101
 
92
102
  /**
93
103
  * Run before hook
94
104
  */
95
- if (options.onBeforeInit) options.onBeforeInit(app);
105
+ if (config.onBeforeInit) config.onBeforeInit(app);
96
106
 
97
107
  /**
98
108
  * Setup default services
@@ -118,25 +128,29 @@ module.exports = async function createRest(options) {
118
128
  // Plug in default databaseDefinitions
119
129
  await DataProvider.addCollectionDefinitionByList({
120
130
  list: defaultDatabaseDefinitionList,
121
- mongoOption: options.mongo,
131
+ mongoOption: config.mongo,
122
132
  });
123
133
 
124
134
  // Setting up default services
125
- await require("./helper/presetup_services").setup(options);
135
+ try {
136
+ await require("./helper/presetup_services").setup(options);
137
+ } catch (e) {
138
+ return Promise.reject(e);
139
+ }
126
140
 
127
141
  /**
128
142
  * User Services
129
143
  *
130
144
  * Plug in routes and database
131
145
  */
132
- if (options.modulesPath) {
146
+ if (config.modulesPath) {
133
147
  // Plug in user routes
134
- await Combination.combineRoutesByFilePath(options.modulesPath, app);
148
+ await Combination.combineRoutesByFilePath(config.modulesPath, app);
135
149
 
136
150
  // Collect user CollectionDefinitions (db.js files)
137
151
  let userDatabaseDetail = [];
138
152
  userDatabaseDetail = await Combination.combineModulesByFilePath({
139
- rootDirectory: options.modulesPath,
153
+ rootDirectory: config.modulesPath,
140
154
  filename: {
141
155
  name: "db",
142
156
  extension: ".js",
@@ -144,21 +158,21 @@ module.exports = async function createRest(options) {
144
158
  combineWithRoot: true,
145
159
  });
146
160
 
147
- // combine additional CollectionDefinitions
148
- if (options.collectionDefinitions) {
149
- userDatabaseDetail.concat(options.collectionDefinitions);
161
+ // Combine additional CollectionDefinitions
162
+ if (config.collectionDefinitions) {
163
+ userDatabaseDetail.concat(config.collectionDefinitions);
150
164
  }
151
165
 
152
166
  // Plug in user CollectionDefinitions
153
167
  await DataProvider.addCollectionDefinitionByList({
154
168
  list: userDatabaseDetail || [],
155
- mongoOption: options.mongo,
169
+ mongoOption: config.mongo,
156
170
  });
157
171
 
158
172
  // Plug in Verification method
159
- if (typeof options.verificationCodeGeneratorMethod == "function") {
173
+ if (typeof config.verificationCodeGeneratorMethod == "function") {
160
174
  UserService.main.setCustomVerificationCodeGeneratorMethod(
161
- options.verificationCodeGeneratorMethod
175
+ config.verificationCodeGeneratorMethod
162
176
  );
163
177
  }
164
178
  }
@@ -169,23 +183,29 @@ module.exports = async function createRest(options) {
169
183
  * return KOA app object
170
184
  */
171
185
  return new Promise((done, reject) => {
172
- let server;
173
-
174
- if (!options.dontListen) {
175
- server = app.listen(options.port);
176
- console.log(
177
- "\x1b[35m",
178
- `KOAS has been launched on: localhost:${options.port}`
179
- );
186
+ try {
187
+ let server;
188
+
189
+ if (!config.dontListen) {
190
+ server = app.listen(config.port);
191
+
192
+ console.log(
193
+ "\x1b[35m",
194
+ `KOAS has been launched on: localhost:${config.port}`
195
+ );
196
+ }
197
+
198
+ // on after init
199
+ if (config.onAfterInit) config.onAfterInit(app);
200
+
201
+ done({
202
+ app,
203
+ server,
204
+ });
205
+ } catch (err) {
206
+ reject(err);
180
207
  }
181
-
182
- // on after init
183
- if (options.onAfterInit) options.onAfterInit(app);
184
-
185
- //done
186
- done({
187
- app,
188
- server,
189
- });
190
208
  });
191
- };
209
+ }
210
+
211
+ module.exports = createRest;
@@ -14,9 +14,9 @@ class CollectionDefinition {
14
14
  * @param {string} option.collection - Collection name
15
15
  * @param {Object} option.schema - Mongoose schema
16
16
  * @param {Array<Permission>} option.permissions - A list of permissions for this collection
17
- * @param {Array<DatabaseTrigger>=} option.trigger - A database trigger
17
+ * @param {Array<DatabaseTrigger>=} option.triggers - A database trigger
18
18
  */
19
- constructor({ db, collection, schema, permissions, trigger }) {
19
+ constructor({ db, collection, schema, permissions, triggers }) {
20
20
  // string
21
21
  this.database = db;
22
22
  // string
@@ -26,7 +26,7 @@ class CollectionDefinition {
26
26
  // a list of Permission for this collection
27
27
  this.permissions = permissions;
28
28
 
29
- this.trigger = trigger;
29
+ this.triggers = triggers;
30
30
  }
31
31
  }
32
32
 
@@ -7,10 +7,11 @@ class DatabaseTrigger {
7
7
  /**
8
8
  * Creates a new instance of `DatabaseTrigger`.
9
9
  *
10
- * @param {'find' | 'find-one' | 'count' | 'update-one' | 'insert-one' | 'remove-one' | 'aggregate'} operation - The operation to be triggered. Supported operations are:
11
- * @param {function(query, queryResult)} callback - The callback to be called when the operation is executed.
10
+ * @param {'find' | 'find-one' | 'count' | 'update-one' | 'insert-one' | 'remove-one' | 'aggregate'} operation - The operation to be triggered. Supported operations are: 'find', 'find-one', 'count', 'update-one', 'insert-one', 'remove-one', 'aggregate'.
11
+ * @param {function({query: any, queryResult: any}): void} [callback=(context) => {}] - The callback to be called when the operation is executed. The callback function takes an object as parameter with two properties: 'query' and 'queryResult'.
12
+ * @constructor
12
13
  */
13
- constructor(operation, callback = (query, queryResult) => {}) {
14
+ constructor(operation, callback = (context) => {}) {
14
15
  this.operation = operation;
15
16
  this.callback = callback;
16
17
  }
@@ -1,66 +1,65 @@
1
- const fs = require('fs');
2
- const path = require('path');
1
+ const fs = require("fs");
2
+ const path = require("path");
3
3
 
4
- function walk(dir, settings, done)
5
- {
6
- let results = [];
4
+ function walk(dir, settings, done) {
5
+ let results = [];
7
6
 
8
- //read director fild and folders
9
- fs.readdir(dir, function (err, list) {
10
- if (err) return done(err, results);
7
+ // Read director file and folders
8
+ fs.readdir(dir, function (err, list) {
9
+ if (err) return done(err, results);
11
10
 
12
- var pending = list.length;
13
- if (!pending) return done(null, results);
11
+ var pending = list.length;
12
+ if (!pending) return done(null, results);
14
13
 
15
- list.forEach(function (file) {
16
- file = path.join(dir, file);
17
- fs.stat(file, function (err, stat) {
18
- // If directory, execute a recursive call
19
- if (stat && stat.isDirectory()) {
20
- // Add directory to array [comment if you need to remove the directories from the array]
21
- //results.push(file);
22
- walk(file, settings, function (err, res) {
23
- results = results.concat(res);
24
- if (!--pending) done(null, results);
25
- });
26
- }
27
- else {
28
- //file filter
29
- var extension = path.extname(file);
30
- var fileName = path.basename(file).split('.')[0];
31
- var fileNameKey = true;
14
+ list.forEach(function (file) {
15
+ file = path.join(dir, file);
16
+ fs.stat(file, function (err, stat) {
17
+ // If directory, execute a recursive call
18
+ if (stat && stat.isDirectory()) {
19
+ // Add directory to array [comment if you need to remove the directories from the array]
20
+ // results.push(file);
21
+ walk(file, settings, function (err, res) {
22
+ results = results.concat(res);
23
+ if (!--pending) done(null, results);
24
+ });
25
+ } else {
26
+ // file filter
27
+ var extension = path.extname(file);
28
+ var fileName = path.basename(file).split(".")[0];
29
+ var fileNameKey = true;
32
30
 
33
- //name filter
34
- if (settings.name && settings.name === fileName) fileNameKey = true;
35
- else fileNameKey = false;
31
+ // name filter
32
+ if (settings.name && settings.name === fileName) fileNameKey = true;
33
+ else fileNameKey = false;
36
34
 
37
- //extention filter
38
- if (settings.filter && fileNameKey) {
39
- settings.filter.forEach(function (element) {
40
- if (element.toLowerCase() === extension.toLowerCase())
41
- results.push(file);
42
- }, this);
43
- }
35
+ // extension filter
36
+ if (settings.filter && fileNameKey) {
37
+ settings.filter.forEach(function (element) {
38
+ if (element.toLowerCase() === extension.toLowerCase())
39
+ results.push(file);
40
+ }, this);
41
+ }
44
42
 
45
- //push any file if no option
46
- else if (fileNameKey) results.push(file);
43
+ // push any file if no option
44
+ else if (fileNameKey) results.push(file);
47
45
 
48
- if (!--pending) done(null, results);
49
- }
50
- });
51
- });
46
+ if (!--pending) done(null, results);
47
+ }
48
+ });
52
49
  });
53
- };
50
+ });
51
+ }
54
52
 
55
53
  function find(dir, settings) {
56
- return new Promise((don, reject) => {
57
- walk(dir, settings, (err, result) => {
58
- if (err) reject(err);
59
- else don(result);
60
- });
54
+ return new Promise((don, reject) => {
55
+ walk(dir, settings, (err, result) => {
56
+ if (err) reject(err);
57
+ else don(result);
61
58
  });
59
+ });
62
60
  }
63
61
 
64
62
  module.exports = {
65
- walk, find,
66
- }
63
+ walk,
64
+ find,
65
+ };
@@ -17,7 +17,7 @@ class AccessDefinition {
17
17
  }
18
18
 
19
19
  /**
20
- * @typedef {('god_access'|'user_access'|'upload_file_access'|'remove_file_access'|'anonymous_access')} PermissionType
20
+ * @typedef {('user_access'|'upload_file_access'|'remove_file_access'|'anonymous_access'|'advanced_settings')} PermissionType
21
21
  */
22
22
 
23
23
  /**
@@ -53,18 +53,6 @@ class Permission {
53
53
  * Each static getter returns a string that represents a specific type of permission.
54
54
  */
55
55
  class PermissionTypes {
56
- /**
57
- * Create permission types.
58
- * Each property represents a specific type of permission.
59
- */
60
- constructor() {
61
- this.god_access = "god_access"; // Represents god access permission type
62
- this.user_access = "user_access"; // Represents user access permission type
63
- this.upload_file_access = "upload_file_access"; // Represents upload file access permission type
64
- this.remove_file_access = "remove_file_access"; // Represents remove file access permission type
65
- this.anonymous_access = "anonymous_access"; // Represents anonymous access permission type
66
- }
67
-
68
56
  /**
69
57
  * Get the string representing god access permission type.
70
58
  * @return {string} The god access permission type.
@@ -73,6 +61,14 @@ class PermissionTypes {
73
61
  return "god_access";
74
62
  }
75
63
 
64
+ /**
65
+ * Get the string representing advanced settings permission type.
66
+ * @return {string} The advanced settings permission type.
67
+ */
68
+ static get advanced_settings() {
69
+ return "advanced_settings";
70
+ }
71
+
76
72
  /**
77
73
  * Get the string representing user access permission type.
78
74
  * @return {string} The user access permission type.
@@ -96,13 +92,31 @@ class PermissionTypes {
96
92
  static get remove_file_access() {
97
93
  return "remove_file_access";
98
94
  }
95
+ }
99
96
 
97
+ class PermissionGroup {
100
98
  /**
101
- * Get the string representing anonymous access permission type.
102
- * @return {string} The anonymous access permission type.
99
+ * Create a permission group.
100
+ * @param {Object} options - The options for the permission group.
101
+ * @param {string} options.title - The title of the permission group.
102
+ * @param {boolean} [options.isDefault=false] - If true, the permission group is the default permission group.
103
+ * @param {boolean} [options.isAnonymous=false] - If true, the permission group is the anonymous permission group.
104
+ * @param {Array.<PermissionType>} [options.validPermissionTypes=[]] - The valid permission types of the permission group.
105
+ * @return {PermissionGroup} The created permission group.
103
106
  */
104
- static get anonymous_access() {
105
- return "anonymous_access";
107
+ constructor({
108
+ title,
109
+ isDefault = false,
110
+ isAnonymous = false,
111
+ validPermissionTypes = [],
112
+ }) {
113
+ //
114
+ this.title = title;
115
+
116
+ this.isDefault = isDefault;
117
+ this.isAnonymous = isAnonymous;
118
+
119
+ this.validPermissionTypes = validPermissionTypes;
106
120
  }
107
121
  }
108
122
 
@@ -122,5 +136,6 @@ module.exports = {
122
136
  AccessDefinition,
123
137
  Permission,
124
138
  PermissionTypes,
139
+ PermissionGroup,
125
140
  AccessTypes,
126
141
  };
@@ -13,24 +13,21 @@ class TriggerOperator {
13
13
 
14
14
  /**
15
15
  * Call a trigger
16
- * @param {string} operation operation name
16
+ * @param {'find' | 'find-one' | 'count' | 'update-one' | 'insert-one' | 'remove-one' | 'aggregate'} operation operation name
17
17
  * @param {string} database database name
18
18
  * @param {string} collection collection name
19
19
  * @param {string} data
20
20
  */
21
21
  call(operation, database, collection, data) {
22
- let result;
23
-
24
22
  this.triggers.forEach((trigger) => {
25
23
  if (
26
24
  operation == trigger.operation &&
27
25
  database == trigger.database &&
28
- collection == trigger.collection
26
+ collection == trigger.collection &&
27
+ trigger.callback
29
28
  )
30
- result = trigger.callback(data.input, data.output);
29
+ trigger.callback(data);
31
30
  });
32
-
33
- return result;
34
31
  }
35
32
 
36
33
  static get instance() {
package/src/class/user.js CHANGED
@@ -1,97 +1,112 @@
1
- let validateObject = require('./validator')
1
+ const { config } = require("../config");
2
+ let validateObject = require("./validator");
2
3
 
3
4
  module.exports = class User {
5
+ constructor(id, permissionGroup, phone, email, password, type, model) {
6
+ this.id = id;
7
+ this.permissionGroup = permissionGroup;
8
+ this.email = email;
9
+ this.phone = phone;
10
+ this.password = password;
11
+ this.type = type;
12
+ this.dbModel = model;
13
+ }
4
14
 
5
- constructor(id, permission, phone, email, password, type, model)
6
- {
7
- this.id = id;
8
- this.permission = permission;
9
- this.email = email;
10
- this.phone = phone;
11
- this.password = password;
12
- this.type = type;
13
- this.dbModel = model;
14
- }
15
+ getBrief() {
16
+ const permissionGroup = config.permissionGroups.find(
17
+ (group) => group.title == this.permissionGroup
18
+ );
15
19
 
16
- getBrief()
17
- {
18
- let brief = {
19
- id : this.id,
20
- permission : this.permission,
21
- phone : this.phone,
22
- email : this.email,
23
- type : this.type,
24
- }
25
-
26
- return brief;
27
- }
20
+ const brief = {
21
+ id: this.id,
22
+ permissionGroup: permissionGroup,
23
+ phone: this.phone,
24
+ email: this.email,
25
+ type: this.type,
26
+ };
28
27
 
29
- setNewDetail(detail)
30
- {
31
- if(detail.phone) this.phone = detail.phone;
32
- if(detail.email) this.email = detail.email;
33
- if(detail.password) this.password = detail.password;
34
- }
28
+ return brief;
29
+ }
35
30
 
36
- hasPermission(permissionField)
37
- {
38
- let key = false;
31
+ setNewDetail(detail) {
32
+ if (detail.phone) this.phone = detail.phone;
33
+ if (detail.email) this.email = detail.email;
34
+ if (detail.password) this.password = detail.password;
35
+ }
39
36
 
40
- if(this.permission[permissionField] == null)
41
- return key;
37
+ hasPermission(permissionField) {
38
+ const permissionGroup = config.permissionGroups.find(
39
+ (group) => group.title == this.permissionGroup
40
+ );
42
41
 
43
- key = this.permission[permissionField];
44
- return key;
45
- }
42
+ if (permissionGroup == null) return false;
46
43
 
47
- async save()
48
- {
49
- this.mode['permission'] = this.permission;
50
- this.mode['phone'] = this.phone;
51
- this.mode['email'] = this.email;
52
- this.mode['password'] = this.password;
44
+ let key = false;
53
45
 
54
- await mode.save();
55
- }
46
+ for (let i = 0; i < permissionGroup.validPermissionTypes.length; i++) {
47
+ const userPermissionType = permissionGroup.validPermissionTypes[i];
56
48
 
57
- static loadFromModel(model)
58
- {
59
- return new Promise((done, reject) =>
60
- {
61
- // check required fields
62
- let isValidData = validateObject(model, 'fullname email password permission');
63
- if(!isValidData)
64
- reject(User.notValid(detail));
65
-
66
- let id = model.id;
67
- let permission = model.permission;
68
- let phone = model.phone;
69
- let email = model.email;
70
- let password = model.password;
71
- let type = model.type;
72
-
73
- //create user
74
- let newUser = new User(id, permission, phone, email, password, type, model);
75
- done(newUser);
76
- });
49
+ if (userPermissionType == permissionField) {
50
+ key = true;
51
+ break;
52
+ }
77
53
  }
78
54
 
79
- static createFromModel(model, detail)
80
- {
81
- return new Promise( async (done, reject) =>
82
- {
83
- //create user
84
- await new model(detail).save()
85
- .then(newUser =>
86
- done(User.loadFromModel(newUser)))
87
- .catch(reject);
88
- });
89
- }
55
+ return key;
56
+ }
90
57
 
91
- static notValid(object)
92
- {
93
- let error = `user detail are not valid ${object}`;
94
- console.error(error);
95
- return error;
96
- }
97
- }
58
+ async save() {
59
+ this.mode["permissionGroup"] = this.permissionGroup;
60
+ this.mode["phone"] = this.phone;
61
+ this.mode["email"] = this.email;
62
+ this.mode["password"] = this.password;
63
+
64
+ await mode.save();
65
+ }
66
+
67
+ static loadFromModel(model) {
68
+ return new Promise((done, reject) => {
69
+ // check required fields
70
+ let isValidData = validateObject(
71
+ model,
72
+ "fullname email password permission"
73
+ );
74
+ if (!isValidData) reject(User.notValid(detail));
75
+
76
+ let id = model.id;
77
+ let permissionGroup = model.permissionGroup;
78
+ let phone = model.phone;
79
+ let email = model.email;
80
+ let password = model.password;
81
+ let type = model.type;
82
+
83
+ //create user
84
+ let newUser = new User(
85
+ id,
86
+ permissionGroup,
87
+ phone,
88
+ email,
89
+ password,
90
+ type,
91
+ model
92
+ );
93
+ done(newUser);
94
+ });
95
+ }
96
+
97
+ static createFromModel(model, detail) {
98
+ return new Promise(async (done, reject) => {
99
+ //create user
100
+ await new model(detail)
101
+ .save()
102
+ .then((newUser) => done(User.loadFromModel(newUser)))
103
+ .catch(reject);
104
+ });
105
+ }
106
+
107
+ static notValid(object) {
108
+ let error = `user detail are not valid ${object}`;
109
+ console.error(error);
110
+ return error;
111
+ }
112
+ };