@modular-rest/server 1.7.0 → 1.10.0

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.7.0",
3
+ "version": "1.10.0",
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,21 @@ 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
14
17
  */
15
18
 
19
+ const { config, setConfig } = require("./config");
20
+
16
21
  /**
22
+ * Create a modular REST instance with Koa and MongoDB support.
17
23
  * @param {{
18
- * cors?: any; // Options for @koa/cors middleware.
24
+ * cors?: Cors; // CORS options.
19
25
  * modulesPath?: string; // Root directory of your router.js/db.js files.
20
26
  * staticPath?: {
21
27
  * rootDir?: string; // Root directory of your static files.
@@ -48,10 +54,12 @@ let defaultServiceRoot = __dirname + "/services";
48
54
  * };
49
55
  * verificationCodeGeneratorMethod: () => string; // A method to return a verification code when registering a new user.
50
56
  * collectionDefinitions?: CollectionDefinition[]; // An array of additional collection definitions.
57
+ * permissionGroups?: PermissionGroup[]; // An array of additional permission groups.
51
58
  * }} options
59
+ * @returns {Promise<{app: Koa, server: Server}>}
52
60
  */
53
- module.exports = async function createRest(options) {
54
- options = {
61
+ async function createRest(options) {
62
+ setConfig({
55
63
  port: 3000,
56
64
  dontListen: false,
57
65
  mongo: {
@@ -65,19 +73,19 @@ module.exports = async function createRest(options) {
65
73
  },
66
74
 
67
75
  ...options,
68
- };
76
+ });
69
77
 
70
- let app = new koa();
78
+ const app = new koa();
71
79
 
72
80
  /**
73
81
  * Plug in Cors
74
82
  */
75
- app.use(cors(options.cors || {}));
83
+ app.use(cors(config.cors || {}));
76
84
 
77
85
  /**
78
86
  * Plug in BodyParser
79
87
  */
80
- let bodyParserOptions = {
88
+ const bodyParserOptions = {
81
89
  multipart: true,
82
90
  };
83
91
  app.use(koaBody(bodyParserOptions));
@@ -85,14 +93,14 @@ module.exports = async function createRest(options) {
85
93
  /**
86
94
  * Plug In KoaStatic
87
95
  */
88
- if (options.staticPath) {
89
- app.use(koaStatic(options.staticPath));
96
+ if (config.staticPath) {
97
+ app.use(koaStatic(config.staticPath));
90
98
  }
91
99
 
92
100
  /**
93
101
  * Run before hook
94
102
  */
95
- if (options.onBeforeInit) options.onBeforeInit(app);
103
+ if (config.onBeforeInit) config.onBeforeInit(app);
96
104
 
97
105
  /**
98
106
  * Setup default services
@@ -118,25 +126,29 @@ module.exports = async function createRest(options) {
118
126
  // Plug in default databaseDefinitions
119
127
  await DataProvider.addCollectionDefinitionByList({
120
128
  list: defaultDatabaseDefinitionList,
121
- mongoOption: options.mongo,
129
+ mongoOption: config.mongo,
122
130
  });
123
131
 
124
132
  // Setting up default services
125
- await require("./helper/presetup_services").setup(options);
133
+ try {
134
+ await require("./helper/presetup_services").setup(options);
135
+ } catch (e) {
136
+ return Promise.reject(e);
137
+ }
126
138
 
127
139
  /**
128
140
  * User Services
129
141
  *
130
142
  * Plug in routes and database
131
143
  */
132
- if (options.modulesPath) {
144
+ if (config.modulesPath) {
133
145
  // Plug in user routes
134
- await Combination.combineRoutesByFilePath(options.modulesPath, app);
146
+ await Combination.combineRoutesByFilePath(config.modulesPath, app);
135
147
 
136
148
  // Collect user CollectionDefinitions (db.js files)
137
149
  let userDatabaseDetail = [];
138
150
  userDatabaseDetail = await Combination.combineModulesByFilePath({
139
- rootDirectory: options.modulesPath,
151
+ rootDirectory: config.modulesPath,
140
152
  filename: {
141
153
  name: "db",
142
154
  extension: ".js",
@@ -144,21 +156,21 @@ module.exports = async function createRest(options) {
144
156
  combineWithRoot: true,
145
157
  });
146
158
 
147
- // combine additional CollectionDefinitions
148
- if (options.collectionDefinitions) {
149
- userDatabaseDetail.concat(options.collectionDefinitions);
159
+ // Combine additional CollectionDefinitions
160
+ if (config.collectionDefinitions) {
161
+ userDatabaseDetail.concat(config.collectionDefinitions);
150
162
  }
151
163
 
152
164
  // Plug in user CollectionDefinitions
153
165
  await DataProvider.addCollectionDefinitionByList({
154
166
  list: userDatabaseDetail || [],
155
- mongoOption: options.mongo,
167
+ mongoOption: config.mongo,
156
168
  });
157
169
 
158
170
  // Plug in Verification method
159
- if (typeof options.verificationCodeGeneratorMethod == "function") {
171
+ if (typeof config.verificationCodeGeneratorMethod == "function") {
160
172
  UserService.main.setCustomVerificationCodeGeneratorMethod(
161
- options.verificationCodeGeneratorMethod
173
+ config.verificationCodeGeneratorMethod
162
174
  );
163
175
  }
164
176
  }
@@ -169,23 +181,29 @@ module.exports = async function createRest(options) {
169
181
  * return KOA app object
170
182
  */
171
183
  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
- );
184
+ try {
185
+ let server;
186
+
187
+ if (!config.dontListen) {
188
+ server = app.listen(config.port);
189
+
190
+ console.log(
191
+ "\x1b[35m",
192
+ `KOAS has been launched on: localhost:${config.port}`
193
+ );
194
+ }
195
+
196
+ // on after init
197
+ if (config.onAfterInit) config.onAfterInit(app);
198
+
199
+ done({
200
+ app,
201
+ server,
202
+ });
203
+ } catch (err) {
204
+ reject(err);
180
205
  }
181
-
182
- // on after init
183
- if (options.onAfterInit) options.onAfterInit(app);
184
-
185
- //done
186
- done({
187
- app,
188
- server,
189
- });
190
206
  });
191
- };
207
+ }
208
+
209
+ module.exports = createRest;
@@ -14,7 +14,7 @@ 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.trigger - A database trigger
18
18
  */
19
19
  constructor({ db, collection, schema, permissions, trigger }) {
20
20
  // string
@@ -7,10 +7,8 @@ class DatabaseTrigger {
7
7
  /**
8
8
  * Creates a new instance of `DatabaseTrigger`.
9
9
  *
10
- * @param {string} operation - The name of the operation on which the callback should be triggered.
11
- * @param {function} [callback=(query, queryResult) => {}] - The callback function to be triggered. It accepts two parameters:
12
- * 1. `query` - The query that is being executed.
13
- * 2. `queryResult` - The result of the query execution.
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.
14
12
  */
15
13
  constructor(operation, callback = (query, queryResult) => {}) {
16
14
  this.operation = operation;
@@ -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'|'delete'|'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
  /**
@@ -31,12 +31,20 @@ class Permission {
31
31
  * @param {boolean} [options.read=false] - The read access of the permission.
32
32
  * @param {boolean} [options.write=false] - The write access of the permission.
33
33
  * @param {boolean} [options.onlyOwnData=false] - If true, users can perform CRUD on documents that they created already.
34
+ * @param {string} [options.ownerIdField='refId'] - The name of the field that contains the owner's id of the document.
34
35
  */
35
- constructor({ type, read = false, write = false, onlyOwnData = false }) {
36
+ constructor({
37
+ type,
38
+ read = false,
39
+ write = false,
40
+ onlyOwnData = false,
41
+ ownerIdField = "refId",
42
+ }) {
36
43
  this.type = type;
37
44
  this.read = read;
38
45
  this.write = write;
39
46
  this.onlyOwnData = onlyOwnData;
47
+ this.ownerIdField = ownerIdField;
40
48
  }
41
49
  }
42
50
 
@@ -45,18 +53,6 @@ class Permission {
45
53
  * Each static getter returns a string that represents a specific type of permission.
46
54
  */
47
55
  class PermissionTypes {
48
- /**
49
- * Create permission types.
50
- * Each property represents a specific type of permission.
51
- */
52
- constructor() {
53
- this.god_access = "god_access"; // Represents god access permission type
54
- this.user_access = "user_access"; // Represents user access permission type
55
- this.upload_file_access = "upload_file_access"; // Represents upload file access permission type
56
- this.remove_file_access = "remove_file_access"; // Represents remove file access permission type
57
- this.anonymous_access = "anonymous_access"; // Represents anonymous access permission type
58
- }
59
-
60
56
  /**
61
57
  * Get the string representing god access permission type.
62
58
  * @return {string} The god access permission type.
@@ -65,6 +61,14 @@ class PermissionTypes {
65
61
  return "god_access";
66
62
  }
67
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
+
68
72
  /**
69
73
  * Get the string representing user access permission type.
70
74
  * @return {string} The user access permission type.
@@ -88,13 +92,31 @@ class PermissionTypes {
88
92
  static get remove_file_access() {
89
93
  return "remove_file_access";
90
94
  }
95
+ }
91
96
 
97
+ class PermissionGroup {
92
98
  /**
93
- * Get the string representing anonymous access permission type.
94
- * @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.
95
106
  */
96
- static get anonymous_access() {
97
- 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;
98
120
  }
99
121
  }
100
122
 
@@ -114,5 +136,6 @@ module.exports = {
114
136
  AccessDefinition,
115
137
  Permission,
116
138
  PermissionTypes,
139
+ PermissionGroup,
117
140
  AccessTypes,
118
141
  };
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
+ };