@modular-rest/server 1.8.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.8.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;
@@ -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
  };
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
+ };
package/src/config.js ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @typedef {import('koa')} Koa
3
+ * @typedef {import('@koa/cors').Options} Cors
4
+ * @typedef {import('./class/collection_definition')} CollectionDefinition
5
+ * @typedef {import('./class/security').PermissionGroup} PermissionGroup
6
+ */
7
+
8
+ /**
9
+ * @typedef {{
10
+ * cors?: Cors; // CORS options.
11
+ * modulesPath?: string; // Root directory of your router.js/db.js files.
12
+ * staticPath?: {
13
+ * rootDir?: string; // Root directory of your static files.
14
+ * rootPath?: string; // Root path of your static files.
15
+ * notFoundFile?: string; // Not found file.
16
+ * log?: boolean; // Log requests to console.
17
+ * last?: boolean; // Don't execute any downstream middleware.
18
+ * maxage?: number; // Browser cache max-age in milliseconds.
19
+ * hidden?: boolean; // Allow transfer of hidden files.
20
+ * gzip?: boolean; // Try to serve the gzipped version of a file automatically when gzip is supported by a client and if the requested file exists.
21
+ * brotli?: boolean; // Try to serve the brotli version of a file automatically when brotli is supported by a client and if the requested file exists.
22
+ * index?: string; // Index file.
23
+ * };
24
+ * onBeforeInit?: (koaApp:Koa) => void; // A callback called before initializing the Koa server.
25
+ * onAfterInit?: (koaApp:Koa) => void; // A callback called after server initialization.
26
+ * port?: number; // Server port.
27
+ * dontListen?: boolean; // If true, the server will not run and will only return the Koa app object.
28
+ * mongo?: {
29
+ * dbPrefix: string; // A prefix for your database name.
30
+ * mongoBaseAddress: string; // The address of your MongoDB server without any database specification.
31
+ * addressMap?: string; // Specific addresses for each database.
32
+ * };
33
+ * keypair?: {
34
+ * private: string; // Private key for RSA authentication.
35
+ * public: string; // Public key for RSA authentication.
36
+ * };
37
+ * adminUser?: {
38
+ * email: string; // Admin user email.
39
+ * password: string; // Admin user password.
40
+ * };
41
+ * verificationCodeGeneratorMethod: () => string; // A method to return a verification code when registering a new user.
42
+ * collectionDefinitions?: CollectionDefinition[]; // An array of additional collection definitions.
43
+ * permissionGroups?: PermissionGroup[]; // An array of additional permission groups.
44
+ * }} Config
45
+ * @exports Config
46
+ */
47
+
48
+ /**
49
+ * @param {Config} options
50
+ */
51
+ function setConfig(options) {
52
+ Object.assign(config, options);
53
+ }
54
+
55
+ /**
56
+ * @type {Config}
57
+ */
58
+ const config = {};
59
+
60
+ module.exports = {
61
+ setConfig,
62
+ config,
63
+ };