@modular-rest/server 1.11.13 → 1.12.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.
Files changed (181) hide show
  1. package/.nvmrc +1 -0
  2. package/.prettierrc.json +9 -0
  3. package/.releaserc.json +24 -0
  4. package/README.md +79 -94
  5. package/dist/application.d.ts +29 -0
  6. package/dist/application.js +217 -0
  7. package/dist/class/cms_trigger.d.ts +61 -0
  8. package/dist/class/cms_trigger.js +47 -0
  9. package/dist/class/collection_definition.d.ts +112 -0
  10. package/dist/class/collection_definition.js +87 -0
  11. package/dist/class/combinator.d.ts +43 -0
  12. package/dist/class/combinator.js +174 -0
  13. package/dist/class/database_trigger.d.ts +84 -0
  14. package/dist/class/database_trigger.js +64 -0
  15. package/dist/class/db_schemas.d.ts +25 -0
  16. package/dist/class/db_schemas.js +28 -0
  17. package/dist/class/directory.d.ts +20 -0
  18. package/dist/class/directory.js +87 -0
  19. package/dist/class/paginator.d.ts +31 -0
  20. package/dist/class/paginator.js +43 -0
  21. package/dist/class/reply.d.ts +29 -0
  22. package/dist/class/reply.js +44 -0
  23. package/dist/class/security.d.ts +186 -0
  24. package/dist/class/security.js +178 -0
  25. package/dist/class/trigger_operator.d.ts +92 -0
  26. package/dist/class/trigger_operator.js +99 -0
  27. package/dist/class/user.d.ts +81 -0
  28. package/dist/class/user.js +151 -0
  29. package/dist/class/validator.d.ts +19 -0
  30. package/dist/class/validator.js +101 -0
  31. package/dist/config.d.ts +112 -0
  32. package/dist/config.js +26 -0
  33. package/dist/defult-permissions.d.ts +2 -0
  34. package/dist/defult-permissions.js +31 -0
  35. package/dist/events.d.ts +23 -0
  36. package/dist/events.js +47 -0
  37. package/dist/helper/data_insertion.d.ts +38 -0
  38. package/dist/helper/data_insertion.js +110 -0
  39. package/dist/helper/presetup_services.d.ts +60 -0
  40. package/dist/helper/presetup_services.js +108 -0
  41. package/dist/index.d.ts +118 -0
  42. package/dist/index.js +79 -0
  43. package/dist/middlewares.d.ts +53 -0
  44. package/dist/middlewares.js +106 -0
  45. package/dist/play-test.d.ts +1 -0
  46. package/dist/play-test.js +9 -0
  47. package/dist/services/data_provider/router.d.ts +4 -0
  48. package/dist/services/data_provider/router.js +187 -0
  49. package/dist/services/data_provider/service.d.ts +131 -0
  50. package/dist/services/data_provider/service.js +252 -0
  51. package/dist/services/data_provider/typeCasters.d.ts +9 -0
  52. package/dist/services/data_provider/typeCasters.js +18 -0
  53. package/dist/services/file/db.d.ts +1 -0
  54. package/dist/services/file/db.js +31 -0
  55. package/dist/services/file/router.d.ts +4 -0
  56. package/dist/services/file/router.js +115 -0
  57. package/dist/services/file/service.d.ts +204 -0
  58. package/dist/services/file/service.js +341 -0
  59. package/dist/services/functions/router.d.ts +4 -0
  60. package/dist/services/functions/router.js +67 -0
  61. package/dist/services/functions/service.d.ts +132 -0
  62. package/dist/services/functions/service.js +159 -0
  63. package/dist/services/jwt/router.d.ts +4 -0
  64. package/dist/services/jwt/router.js +99 -0
  65. package/dist/services/jwt/service.d.ts +97 -0
  66. package/dist/services/jwt/service.js +135 -0
  67. package/dist/services/user_manager/db.d.ts +1 -0
  68. package/dist/services/user_manager/db.js +75 -0
  69. package/dist/services/user_manager/permissionManager.d.ts +19 -0
  70. package/dist/services/user_manager/permissionManager.js +42 -0
  71. package/dist/services/user_manager/router.d.ts +4 -0
  72. package/dist/services/user_manager/router.js +195 -0
  73. package/dist/services/user_manager/service.d.ts +317 -0
  74. package/dist/services/user_manager/service.js +628 -0
  75. package/docs/.keep +0 -0
  76. package/docs/system-access-type.md +26 -0
  77. package/package.json +58 -45
  78. package/src/application.ts +206 -0
  79. package/src/class/cms_trigger.ts +68 -0
  80. package/src/class/collection_definition.ts +134 -0
  81. package/src/class/combinator.ts +176 -0
  82. package/src/class/database_trigger.ts +99 -0
  83. package/src/class/db_schemas.ts +44 -0
  84. package/src/class/{directory.js → directory.ts} +40 -18
  85. package/src/class/paginator.ts +51 -0
  86. package/src/class/reply.ts +59 -0
  87. package/src/class/security.ts +250 -0
  88. package/src/class/trigger_operator.ts +142 -0
  89. package/src/class/user.ts +199 -0
  90. package/src/class/validator.ts +123 -0
  91. package/src/config.ts +121 -0
  92. package/src/defult-permissions.ts +31 -0
  93. package/src/events.ts +59 -0
  94. package/src/helper/data_insertion.ts +94 -0
  95. package/src/helper/presetup_services.ts +96 -0
  96. package/src/index.ts +146 -0
  97. package/src/middlewares.ts +75 -0
  98. package/src/play-test.ts +8 -0
  99. package/src/services/data_provider/router.ts +191 -0
  100. package/src/services/data_provider/service.ts +305 -0
  101. package/src/services/data_provider/typeCasters.ts +15 -0
  102. package/src/services/file/db.ts +29 -0
  103. package/src/services/file/router.ts +88 -0
  104. package/src/services/file/service.ts +387 -0
  105. package/src/services/functions/router.ts +34 -0
  106. package/src/services/functions/service.ts +203 -0
  107. package/src/services/jwt/router.ts +73 -0
  108. package/src/services/jwt/service.ts +139 -0
  109. package/src/services/user_manager/db.ts +87 -0
  110. package/src/services/user_manager/permissionManager.ts +49 -0
  111. package/src/services/user_manager/router.ts +193 -0
  112. package/src/services/user_manager/service.ts +698 -0
  113. package/tsconfig.json +16 -9
  114. package/typedoc.mjs +41 -0
  115. package/LICENSE +0 -21
  116. package/package-lock.json +0 -1373
  117. package/src/application.js +0 -239
  118. package/src/class/cms_trigger.js +0 -20
  119. package/src/class/collection_definition.js +0 -33
  120. package/src/class/combinator.js +0 -133
  121. package/src/class/database_trigger.js +0 -20
  122. package/src/class/db_schemas.js +0 -18
  123. package/src/class/paginator.js +0 -31
  124. package/src/class/reply.js +0 -37
  125. package/src/class/security.js +0 -141
  126. package/src/class/trigger_operator.js +0 -39
  127. package/src/class/user.js +0 -112
  128. package/src/class/validator.js +0 -91
  129. package/src/config.js +0 -67
  130. package/src/events.js +0 -15
  131. package/src/helper/data_insertion.js +0 -64
  132. package/src/helper/presetup_services.js +0 -31
  133. package/src/index.js +0 -66
  134. package/src/middlewares.js +0 -44
  135. package/src/services/data_provider/router.js +0 -552
  136. package/src/services/data_provider/service.js +0 -262
  137. package/src/services/data_provider/typeCasters.js +0 -10
  138. package/src/services/file/db.js +0 -29
  139. package/src/services/file/router.js +0 -92
  140. package/src/services/file/service.js +0 -231
  141. package/src/services/functions/router.js +0 -37
  142. package/src/services/functions/service.js +0 -74
  143. package/src/services/jwt/router.js +0 -82
  144. package/src/services/jwt/service.js +0 -37
  145. package/src/services/user_manager/db.js +0 -83
  146. package/src/services/user_manager/permissionManager.js +0 -43
  147. package/src/services/user_manager/router.js +0 -176
  148. package/src/services/user_manager/service.js +0 -377
  149. package/types/application.d.ts +0 -97
  150. package/types/class/cms_trigger.d.ts +0 -24
  151. package/types/class/collection_definition.d.ts +0 -36
  152. package/types/class/combinator.d.ts +0 -30
  153. package/types/class/database_trigger.d.ts +0 -28
  154. package/types/class/db_schemas.d.ts +0 -2
  155. package/types/class/directory.d.ts +0 -2
  156. package/types/class/paginator.d.ts +0 -8
  157. package/types/class/reply.d.ts +0 -8
  158. package/types/class/security.d.ts +0 -109
  159. package/types/class/trigger_operator.d.ts +0 -19
  160. package/types/class/user.d.ts +0 -24
  161. package/types/class/validator.d.ts +0 -9
  162. package/types/config.d.ts +0 -101
  163. package/types/events.d.ts +0 -7
  164. package/types/helper/data_insertion.d.ts +0 -4
  165. package/types/helper/presetup_services.d.ts +0 -5
  166. package/types/index.d.ts +0 -72
  167. package/types/middlewares.d.ts +0 -10
  168. package/types/services/data_provider/router.d.ts +0 -3
  169. package/types/services/data_provider/service.d.ts +0 -40
  170. package/types/services/data_provider/typeCasters.d.ts +0 -3
  171. package/types/services/file/db.d.ts +0 -3
  172. package/types/services/file/router.d.ts +0 -3
  173. package/types/services/file/service.d.ts +0 -81
  174. package/types/services/functions/router.d.ts +0 -3
  175. package/types/services/functions/service.d.ts +0 -23
  176. package/types/services/jwt/router.d.ts +0 -3
  177. package/types/services/jwt/service.d.ts +0 -10
  178. package/types/services/user_manager/db.d.ts +0 -3
  179. package/types/services/user_manager/permissionManager.d.ts +0 -3
  180. package/types/services/user_manager/router.d.ts +0 -3
  181. package/types/services/user_manager/service.d.ts +0 -131
@@ -0,0 +1,387 @@
1
+ import fs from 'fs';
2
+ import pathModule from 'path';
3
+ import * as DataProvider from '../data_provider/service';
4
+ import triggerService from '../../class/trigger_operator';
5
+ import { config } from '../../config';
6
+ import { IFile } from '../../class/db_schemas';
7
+
8
+ /**
9
+ * Service name constant
10
+ * @constant {string}
11
+ */
12
+ export const name = 'file';
13
+
14
+ /**
15
+ * File storage detail interface
16
+ * @interface StoredFileDetail
17
+ * @property {string} fileName - Generated unique filename
18
+ * @property {string} fullPath - Full path to the stored file
19
+ * @property {string} fileFormat - File format/extension
20
+ */
21
+ interface StoredFileDetail {
22
+ fileName: string;
23
+ fullPath: string;
24
+ fileFormat: string;
25
+ }
26
+
27
+ /**
28
+ * File upload options interface
29
+ * @interface StoreFileOptions
30
+ * @property {Object} file - File details
31
+ * @property {string} file.path - Temporary file path
32
+ * @property {string} file.type - MIME type of the file
33
+ * @property {string} file.name - Original filename
34
+ * @property {number} file.size - File size in bytes
35
+ * @property {string} ownerId - ID of the file owner
36
+ * @property {string} tag - Tag for file organization
37
+ * @property {boolean} [removeFileAfterStore=true] - Whether to remove the temporary file after storage
38
+ */
39
+ interface StoreFileOptions {
40
+ file: {
41
+ path: string;
42
+ type: string;
43
+ name: string;
44
+ size: number;
45
+ };
46
+ ownerId: string;
47
+ tag: string;
48
+ removeFileAfterStore?: boolean;
49
+ }
50
+
51
+ /**
52
+ * File service for handling file storage and retrieval.
53
+ *
54
+ * This service provides functionality for storing, retrieving, and managing files.
55
+ * It handles file storage on disk and maintains file metadata in the database.
56
+ * Files are organized by format and tag in the upload directory.
57
+ */
58
+ class FileService {
59
+ /**
60
+ * @hidden
61
+ */
62
+ private directory: string | null = null;
63
+
64
+ /**
65
+ * @hidden
66
+ */
67
+ static instance: FileService;
68
+
69
+ /**
70
+ * @hidden
71
+ */
72
+ constructor() {}
73
+
74
+ /**
75
+ * @hidden
76
+ *
77
+ * Sets the upload directory for file storage
78
+ * @param {string} directory - Directory path for file storage
79
+ * @throws {Error} If directory is invalid or not writable
80
+ * @example
81
+ * ```typescript
82
+ * import { fileService } from '@modular-rest/server';
83
+ *
84
+ * fileService.setUploadDirectory('/path/to/uploads');
85
+ * ```
86
+ */
87
+ setUploadDirectory(directory: string): void {
88
+ if (!fs.existsSync(directory)) {
89
+ fs.mkdirSync(directory, { recursive: true });
90
+ }
91
+ this.directory = directory;
92
+ }
93
+
94
+ /**
95
+ * @hidden
96
+ *
97
+ * Creates stored file details with unique filename
98
+ * @param {string} fileType - MIME type of the file
99
+ * @param {string} tag - Tag for file organization
100
+ * @returns {StoredFileDetail} Storage details including filename and path
101
+ * @throws {Error} If upload directory is not set
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * import { fileService } from '@modular-rest/server';
106
+ *
107
+ * const details = fileService.createStoredDetail('image/jpeg', 'profile');
108
+ * // Returns: { fileName: '1234567890.jpeg', fullPath: '/uploads/jpeg/profile/1234567890.jpeg', fileFormat: 'jpeg' }
109
+ * ```
110
+ */
111
+ createStoredDetail(fileType: string, tag: string): StoredFileDetail {
112
+ const typeParts = fileType.split('/');
113
+ const fileFormat = typeParts[1] || typeParts[0] || 'unknown';
114
+
115
+ const time = new Date().getTime();
116
+ const fileName = `${time}.${fileFormat}`;
117
+
118
+ if (!FileService.instance.directory) {
119
+ throw new Error('Upload directory has not been set');
120
+ }
121
+
122
+ const fullPath = pathModule.join(FileService.instance.directory, fileFormat, tag, fileName);
123
+
124
+ return { fileName, fullPath, fileFormat };
125
+ }
126
+
127
+ /**
128
+ * @hidden
129
+ *
130
+ * Stores a file, removes the temporary file, and saves metadata to database
131
+ * @param {StoreFileOptions} options - File storage options
132
+ * @returns {Promise<IFile>} Promise resolving to stored file document
133
+ * @throws {Error} If upload directory is not set or storage fails
134
+ * @example
135
+ * ```typescript
136
+ * import { fileService } from '@modular-rest/server';
137
+ *
138
+ * const file = await fileService.storeFile({
139
+ * file: {
140
+ * path: '/tmp/upload.jpg',
141
+ * type: 'image/jpeg',
142
+ * name: 'profile.jpg',
143
+ * size: 1024
144
+ * },
145
+ * ownerId: 'user123',
146
+ * tag: 'profile',
147
+ * removeFileAfterStore: true
148
+ * });
149
+ * ```
150
+ */
151
+ storeFile({ file, ownerId, tag, removeFileAfterStore = true }: StoreFileOptions): Promise<IFile> {
152
+ if (!FileService.instance.directory) {
153
+ throw new Error('Upload directory has not been set');
154
+ }
155
+
156
+ let storedFile: StoredFileDetail;
157
+
158
+ return new Promise(async (done, reject) => {
159
+ storedFile = FileService.instance.createStoredDetail(file.type, tag);
160
+
161
+ fs.copyFile(file.path, storedFile.fullPath, (err: Error | null) => {
162
+ if (err) {
163
+ reject(err);
164
+ } else {
165
+ done(null);
166
+ }
167
+
168
+ // remove temp file
169
+ if (removeFileAfterStore) {
170
+ fs.unlinkSync(file.path);
171
+ }
172
+ });
173
+ })
174
+ .then(() => {
175
+ // Get collection model for access to relative collection
176
+ const CollectionModel = DataProvider.getCollection<IFile>('cms', 'file');
177
+
178
+ if (!CollectionModel) {
179
+ throw new Error('Collection model not found');
180
+ }
181
+
182
+ const data = {
183
+ owner: ownerId,
184
+ fileName: storedFile.fileName,
185
+ originalName: file.name,
186
+ format: storedFile.fileFormat,
187
+ tag,
188
+ size: file.size,
189
+ };
190
+
191
+ // Create new document
192
+ const doc = new CollectionModel(data);
193
+
194
+ return doc.save().then(savedDoc => {
195
+ triggerService.call('insert-one', 'cms', 'file', {
196
+ query: null,
197
+ queryResult: savedDoc,
198
+ });
199
+
200
+ return savedDoc;
201
+ });
202
+ })
203
+ .catch(err => {
204
+ // remove stored file
205
+ fs.unlinkSync(storedFile.fullPath);
206
+
207
+ throw err;
208
+ });
209
+ }
210
+
211
+ /**
212
+ * @hidden
213
+ *
214
+ * Removes a file from the disk
215
+ * @param {string} path - File path to remove
216
+ * @returns {Promise<void>} Promise resolving when file is removed
217
+ * @throws {Error} If file removal fails
218
+ * @example
219
+ * ```typescript
220
+ * import { fileService } from '@modular-rest/server';
221
+ *
222
+ * await fileService.removeFromDisc('/uploads/jpeg/profile/1234567890.jpeg');
223
+ * ```
224
+ */
225
+ removeFromDisc(path: string): Promise<void> {
226
+ return new Promise((done, reject) => {
227
+ fs.unlink(path, (err: Error | null) => {
228
+ if (err) reject(err);
229
+ else done();
230
+ });
231
+ });
232
+ }
233
+
234
+ /**
235
+ * Removes a file from both database and disk
236
+ *
237
+ * @param {string} fileId - File ID to remove
238
+ * @returns {Promise<void>} Promise resolving when file is removed
239
+ * @throws {Error} If file is not found or removal fails
240
+ * @example
241
+ * ```typescript
242
+ * import { fileService } from '@modular-rest/server';
243
+ *
244
+ * try {
245
+ * await fileService.removeFile('file123');
246
+ * console.log('File removed successfully');
247
+ * } catch (error) {
248
+ * console.error('Failed to remove file:', error);
249
+ * }
250
+ * ```
251
+ */
252
+ removeFile(fileId: string): Promise<void> {
253
+ if (!FileService.instance.directory) {
254
+ throw new Error('Upload directory has not been set');
255
+ }
256
+
257
+ return new Promise(async (done, reject) => {
258
+ const CollectionModel = DataProvider.getCollection<IFile>('cms', 'file');
259
+
260
+ if (!CollectionModel) {
261
+ return reject(new Error('Collection model not found'));
262
+ }
263
+
264
+ const fileDoc = await CollectionModel.findOne({ _id: fileId }).exec();
265
+
266
+ if (!fileDoc) {
267
+ return reject(new Error('File not found'));
268
+ }
269
+
270
+ await CollectionModel.deleteOne({ _id: fileId })
271
+ .exec()
272
+ .then(() => {
273
+ // create file path
274
+ const filePath = pathModule.join(
275
+ FileService.instance.directory as string,
276
+ fileDoc.format,
277
+ fileDoc.tag,
278
+ fileDoc.fileName
279
+ );
280
+
281
+ // Remove file from disc
282
+ return FileService.instance.removeFromDisc(filePath).catch(async err => {
283
+ // Recreate fileDoc if removing file operation has error
284
+ await new CollectionModel(fileDoc).save();
285
+
286
+ throw err;
287
+ });
288
+ })
289
+ .then(() => {
290
+ triggerService.call('remove-one', 'cms', 'file', {
291
+ query: { _id: fileId },
292
+ queryResult: null,
293
+ });
294
+ })
295
+ .then(done)
296
+ .catch(reject);
297
+ });
298
+ }
299
+
300
+ /**
301
+ * Retrieves a file document from the database
302
+ *
303
+ * @param {string} fileId - File ID to retrieve
304
+ * @returns {Promise<IFile>} Promise resolving to file document
305
+ * @throws {Error} If collection model is not found or file is not found
306
+ * @example
307
+ * ```typescript
308
+ * import { fileService } from '@modular-rest/server';
309
+ *
310
+ * const fileDoc = await fileService.getFile('file123');
311
+ * console.log('File details:', fileDoc);
312
+ * ```
313
+ */
314
+ getFile(fileId: string): Promise<IFile> {
315
+ const CollectionModel = DataProvider.getCollection<IFile>('cms', 'file');
316
+
317
+ if (!CollectionModel) {
318
+ throw new Error('Collection model not found');
319
+ }
320
+
321
+ return CollectionModel.findOne({ _id: fileId })
322
+ .exec()
323
+ .then(doc => {
324
+ if (!doc) {
325
+ throw new Error('File not found');
326
+ }
327
+ return doc;
328
+ });
329
+ }
330
+
331
+ /**
332
+ * Retrieves the public URL link for a file
333
+ *
334
+ * @param {string} fileId - File ID to get link for
335
+ * @returns {Promise<string>} Promise resolving to file URL
336
+ * @throws {Error} If static path root is not defined or file is not found
337
+ * @example
338
+ * ```typescript
339
+ * import { fileService } from '@modular-rest/server';
340
+ *
341
+ * const link = await fileService.getFileLink('file123');
342
+ * // Returns: '/static/jpeg/profile/1234567890.jpeg'
343
+ * ```
344
+ */
345
+ async getFileLink(fileId: string): Promise<string> {
346
+ const fileDoc = await FileService.instance.getFile(fileId);
347
+
348
+ if (!config.staticPath?.actualPath) {
349
+ throw new Error('Static path root is not defined');
350
+ }
351
+
352
+ const link =
353
+ config.staticPath.actualPath + `/${fileDoc.format}/${fileDoc.tag}/` + fileDoc.fileName;
354
+
355
+ return link;
356
+ }
357
+
358
+ /**
359
+ * Gets the full filesystem path for a file
360
+ *
361
+ * @param {string} fileId - File ID to get path for
362
+ * @returns {Promise<string>} Promise resolving to full file path
363
+ * @throws {Error} If upload directory is not set or file is not found
364
+ * @example
365
+ * ```typescript
366
+ * import { fileService } from '@modular-rest/server';
367
+ *
368
+ * const path = await fileService.getFilePath('file123');
369
+ * // Returns: '/uploads/jpeg/profile/1234567890.jpeg'
370
+ * ```
371
+ */
372
+ async getFilePath(fileId: string): Promise<string> {
373
+ const { fileName, format, tag } = await FileService.instance.getFile(fileId);
374
+
375
+ if (!FileService.instance.directory) {
376
+ throw new Error('Upload directory has not been set');
377
+ }
378
+
379
+ return pathModule.join(FileService.instance.directory, format, tag, fileName);
380
+ }
381
+ }
382
+
383
+ /**
384
+ * Main file service instance
385
+ * @constant {FileService}
386
+ */
387
+ export const main = new FileService();
@@ -0,0 +1,34 @@
1
+ import Router from 'koa-router';
2
+ import * as service from './service';
3
+ import * as middleware from '../../middlewares';
4
+ import { validateObject } from '../../class/validator';
5
+ import { create as reply } from '../../class/reply';
6
+ import { Context, Next } from 'koa';
7
+
8
+ const functionRouter = new Router();
9
+ const name = 'function';
10
+
11
+ functionRouter.use('/', middleware.auth, async (ctx: Context, next: Next) => {
12
+ const body = ctx.request.body;
13
+ const bodyValidated = validateObject(body, 'name args');
14
+
15
+ // fields validation
16
+ if (!bodyValidated.isValid) {
17
+ ctx.throw(412, JSON.stringify(reply('e', { error: bodyValidated.requires })));
18
+ }
19
+
20
+ await next();
21
+ });
22
+
23
+ functionRouter.post('/run', middleware.auth, async (ctx: Context) => {
24
+ const { name, args } = ctx.request.body;
25
+
26
+ try {
27
+ const result = await service.runFunction(name, args, ctx.state.user);
28
+ ctx.body = JSON.stringify(reply('s', { data: result }));
29
+ } catch (e) {
30
+ ctx.throw(400, JSON.stringify(reply('e', { error: (e as Error).message })));
31
+ }
32
+ });
33
+
34
+ export { name, functionRouter as main };
@@ -0,0 +1,203 @@
1
+ import { AccessType } from '../../class/security';
2
+ import { User } from '../../class/user';
3
+
4
+ /**
5
+ * Service name constant
6
+ * @constant {string}
7
+ */
8
+ export const name = 'functions';
9
+
10
+ /**
11
+ * Storage for registered functions
12
+ * @private
13
+ */
14
+ const functions: DefinedFunction[] = [];
15
+
16
+ /**
17
+ * Interface for defined functions.
18
+ *
19
+ * @interface DefinedFunction
20
+ * @inline
21
+ * @example
22
+ * ```typescript
23
+ * const myFunction: DefinedFunction = {
24
+ * name: 'calculateTotal',
25
+ * permissionTypes: ['user_access'],
26
+ * callback: (args) => args.items.reduce((sum, item) => sum + item.price, 0)
27
+ * };
28
+ * ```
29
+ */
30
+ export interface DefinedFunction {
31
+ /**
32
+ * Unique name of the function
33
+ * @type {string}
34
+ */
35
+ name: string;
36
+ /**
37
+ * List of permission types required to run the function
38
+ * @type {AccessType[]}
39
+ */
40
+ permissionTypes: AccessType[];
41
+
42
+ /**
43
+ * The actual function implementation
44
+ * @type {(args: any) => any}
45
+ */
46
+ callback: (args: any) => any;
47
+ }
48
+
49
+ /**
50
+ * To define a function you need to create a `functions.[js|ts]` in each module of your app and return am array called `functions`, and then define all your functions with calling the `defineFunction` method.
51
+ *
52
+ * The `defineFunction` method serves as a core utility for creating custom functions dynamically. This method allows you to specify various parameters, including the name of the function, the permissions required for access, and the corresponding logic that should be executed when the function is invoked.
53
+ *
54
+ * @summary
55
+ * Define a server function to be called by clients.
56
+ *
57
+ * @param {DefinedFunction} options - The function definition options. See {@link DefinedFunction} for detailed parameter descriptions.
58
+ * @expandType DefinedFunction
59
+ *
60
+ * @returns {Object} The defined function object which system will use to generate a router for the function, generall the client library will use the router to call the function.
61
+ * @throws {Error} If function name already exists, permission types are missing, or callback is invalid
62
+ *
63
+ * @example
64
+ * Here is an example illustrating how to use the `defineFunction` method effectively:
65
+ * ```typescript
66
+ * // /modules/myModule/functions.ts
67
+ *
68
+ * import { defineFunction } from "@modular-rest/server";
69
+ *
70
+ * const getServerTime = defineFunction({
71
+ * name: "getServerTime",
72
+ * permissionTypes: ["anonymous_access"],
73
+ * callback: (params) => {
74
+ * // return your data only
75
+ * return `
76
+ * Welcome, ${params.username}!
77
+ * The current server time is ${new Date().toLocaleString()}.
78
+ * `;
79
+ *
80
+ * // error handling,
81
+ * // client gets error code 400, and the message
82
+ * // throw new Error('An error occurred');
83
+ * },
84
+ * });
85
+ *
86
+ * module.exports.functions = [getServerTime];
87
+ * ```
88
+ * In this example, we define a function named `getServerTime` that requires the `user` permission type to access. When the function is called, it will return a message containing the current server time and the username of the user who invoked the function.
89
+ *
90
+ * ---
91
+ *
92
+ * By utilizing the `defineFunction` method, developers are empowered to create custom functionality effortlessly within the Modular REST framework, enhancing both the versatility and security of their applications.
93
+ */
94
+ export function defineFunction(options: DefinedFunction): DefinedFunction {
95
+ // Check if the function already exists
96
+ const existingFunction = functions.find(f => f.name === name);
97
+ if (existingFunction) {
98
+ throw new Error(`Function with name ${name} already exists`);
99
+ }
100
+
101
+ // Check if the permission types provided
102
+ if (!options.permissionTypes || !options.permissionTypes.length) {
103
+ throw new Error(`Permission types not provided for function ${name}`);
104
+ }
105
+
106
+ // Check if the callback is a function
107
+ if (typeof options.callback !== 'function') {
108
+ throw new Error(`Callback is not a function for function ${name}`);
109
+ }
110
+
111
+ // Add the function to the list of functions
112
+ return options;
113
+ }
114
+
115
+ /**
116
+ * Runs a function by name with arguments and user context
117
+ *
118
+ * @param {string} name - Name of the function to run
119
+ * @param {any} args - Arguments to pass to the function
120
+ * @param {User} user - User attempting to run the function
121
+ * @returns {Promise<any>} Promise resolving to function result
122
+ * @throws {Error} If function not found or user lacks required permissions
123
+ *
124
+ * @private
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * try {
129
+ * const result = await runFunction('calculateTotal', {
130
+ * items: [
131
+ * { price: 10 },
132
+ * { price: 20 }
133
+ * ]
134
+ * }, currentUser);
135
+ * console.log('Total:', result); // 30
136
+ * } catch (error) {
137
+ * console.error('Function execution failed:', error);
138
+ * }
139
+ * ```
140
+ */
141
+ export function runFunction(name: string, args: any, user: User): Promise<any> {
142
+ return new Promise((resolve, reject) => {
143
+ const func = functions.find(f => f.name === name);
144
+ if (!func) {
145
+ return reject(new Error(`Function with name ${name} not found`));
146
+ }
147
+
148
+ const hasPermission = func.permissionTypes.some(permissionType =>
149
+ user.hasPermission(permissionType)
150
+ );
151
+
152
+ if (!hasPermission) {
153
+ const userBrief = user.getBrief();
154
+ const userPermissions =
155
+ typeof userBrief.permissionGroup === 'object' && userBrief.permissionGroup
156
+ ? userBrief.permissionGroup.allowedAccessTypes
157
+ : 'none';
158
+
159
+ reject(
160
+ new Error(`User does not have permission to run function ${name}:
161
+ Function permissions: ${func.permissionTypes}
162
+ User permissions: ${userPermissions}
163
+ `)
164
+ );
165
+ } else {
166
+ try {
167
+ resolve(func.callback(args));
168
+ } catch (e) {
169
+ reject(e);
170
+ }
171
+ }
172
+ });
173
+ }
174
+
175
+ /**
176
+ * Adds a function to the registry, this method is used for internal use only,
177
+ * it will add all defined functions to the registry.
178
+ *
179
+ * @param {DefinedFunction} func - Function to add
180
+ * @throws {Error} If function name already exists
181
+ *
182
+ * @private
183
+ *
184
+ * @example
185
+ * ```typescript
186
+ * const myFunction = defineFunction({
187
+ * name: 'myFunction',
188
+ * permissionTypes: ['user_access'],
189
+ * callback: (args) => args.value * 2
190
+ * });
191
+ *
192
+ * addFunction(myFunction);
193
+ * ```
194
+ */
195
+ export function addFunction(func: DefinedFunction): void {
196
+ // Check if the function already exists
197
+ const existingFunction = functions.find(f => f.name === func.name);
198
+ if (existingFunction) {
199
+ throw new Error(`Function with name ${func.name} already exists`);
200
+ }
201
+
202
+ functions.push(func);
203
+ }
@@ -0,0 +1,73 @@
1
+ import Router from 'koa-router';
2
+ import { validateObject } from '../../class/validator';
3
+ import { create as reply } from '../../class/reply';
4
+ import { Context } from 'koa';
5
+ import * as service from './service';
6
+
7
+ const name = 'verify';
8
+ const verify = new Router();
9
+
10
+ verify.post('/token', async (ctx: Context) => {
11
+ const body = ctx.request.body;
12
+
13
+ // validate result
14
+ const bodyValidate = validateObject(body, 'token');
15
+
16
+ // fields validation
17
+ if (!bodyValidate.isValid) {
18
+ ctx.status = 412;
19
+ ctx.body = reply('e', {
20
+ e: bodyValidate.requires,
21
+ });
22
+ return;
23
+ }
24
+
25
+ await service.main
26
+ .verify(body.token)
27
+ .then(payload => (ctx.body = reply('s', { user: payload })))
28
+ .catch(err => {
29
+ ctx.status = 412;
30
+ ctx.body = reply('e', { e: err });
31
+ });
32
+ });
33
+
34
+ verify.post('/checkAccess', async (ctx: Context) => {
35
+ const body = ctx.request.body;
36
+
37
+ // validate result
38
+ const bodyValidate = validateObject(body, 'token permissionField');
39
+
40
+ // fields validation
41
+ if (!bodyValidate.isValid) {
42
+ ctx.status = 412;
43
+ ctx.body = reply('e', {
44
+ e: bodyValidate.requires,
45
+ });
46
+ return;
47
+ }
48
+
49
+ const payload = await service.main.verify(body.token).catch(err => {
50
+ console.log(err);
51
+ ctx.throw(412, err.message);
52
+ });
53
+
54
+ const userid = payload.id;
55
+
56
+ await (global as any).services.userManager.main
57
+ .getUserById(userid)
58
+ .then((user: any) => {
59
+ const key = user.hasPermission(body.permissionField);
60
+ ctx.body = reply('s', { access: key });
61
+ })
62
+ .catch((err: any) => {
63
+ ctx.status = 412;
64
+ ctx.body = reply('e', { e: err });
65
+ });
66
+ });
67
+
68
+ verify.get('/ready', async (ctx: Context) => {
69
+ // it's health check, so return success
70
+ ctx.body = reply('s', {});
71
+ });
72
+
73
+ export { name, verify as main };