hola-server 1.0.10 → 2.0.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 (83) hide show
  1. package/README.md +196 -1
  2. package/core/array.js +79 -142
  3. package/core/bash.js +208 -259
  4. package/core/chart.js +26 -16
  5. package/core/cron.js +14 -3
  6. package/core/date.js +15 -44
  7. package/core/encrypt.js +19 -9
  8. package/core/file.js +42 -29
  9. package/core/lhs.js +32 -6
  10. package/core/meta.js +213 -289
  11. package/core/msg.js +20 -7
  12. package/core/number.js +105 -103
  13. package/core/obj.js +15 -12
  14. package/core/random.js +9 -6
  15. package/core/role.js +69 -77
  16. package/core/thread.js +12 -2
  17. package/core/type.js +300 -261
  18. package/core/url.js +20 -12
  19. package/core/validate.js +29 -26
  20. package/db/db.js +297 -227
  21. package/db/entity.js +631 -963
  22. package/db/gridfs.js +120 -166
  23. package/design/add_default_field_attr.md +56 -0
  24. package/http/context.js +22 -8
  25. package/http/cors.js +25 -8
  26. package/http/error.js +27 -9
  27. package/http/express.js +70 -41
  28. package/http/params.js +70 -42
  29. package/http/router.js +51 -40
  30. package/http/session.js +59 -36
  31. package/index.js +85 -9
  32. package/package.json +2 -2
  33. package/router/clone.js +28 -36
  34. package/router/create.js +21 -26
  35. package/router/delete.js +24 -28
  36. package/router/read.js +137 -123
  37. package/router/update.js +38 -56
  38. package/setting.js +22 -6
  39. package/skills/array.md +155 -0
  40. package/skills/bash.md +91 -0
  41. package/skills/chart.md +54 -0
  42. package/skills/code.md +422 -0
  43. package/skills/context.md +177 -0
  44. package/skills/date.md +58 -0
  45. package/skills/express.md +255 -0
  46. package/skills/file.md +60 -0
  47. package/skills/lhs.md +54 -0
  48. package/skills/meta.md +1023 -0
  49. package/skills/msg.md +30 -0
  50. package/skills/number.md +88 -0
  51. package/skills/obj.md +36 -0
  52. package/skills/params.md +206 -0
  53. package/skills/random.md +22 -0
  54. package/skills/role.md +59 -0
  55. package/skills/session.md +281 -0
  56. package/skills/storage.md +743 -0
  57. package/skills/thread.md +22 -0
  58. package/skills/type.md +547 -0
  59. package/skills/url.md +34 -0
  60. package/skills/validate.md +48 -0
  61. package/test/cleanup/close-db.js +5 -0
  62. package/test/core/array.js +226 -0
  63. package/test/core/chart.js +51 -0
  64. package/test/core/file.js +59 -0
  65. package/test/core/lhs.js +44 -0
  66. package/test/core/number.js +167 -12
  67. package/test/core/obj.js +47 -0
  68. package/test/core/random.js +24 -0
  69. package/test/core/thread.js +20 -0
  70. package/test/core/type.js +216 -0
  71. package/test/core/validate.js +67 -0
  72. package/test/db/db-ops.js +99 -0
  73. package/test/db/pipe_test.txt +0 -0
  74. package/test/db/test_case_design.md +528 -0
  75. package/test/db/test_db_class.js +613 -0
  76. package/test/db/test_entity_class.js +414 -0
  77. package/test/db/test_gridfs_class.js +234 -0
  78. package/test/entity/create.js +1 -1
  79. package/test/entity/delete-mixed.js +156 -0
  80. package/test/entity/ref-filter.js +63 -0
  81. package/tool/gen_i18n.js +55 -21
  82. package/test/crud/router.js +0 -99
  83. package/test/router/user.js +0 -17
package/db/gridfs.js CHANGED
@@ -1,221 +1,175 @@
1
+ /**
2
+ * @fileoverview GridFS file storage utilities.
3
+ * @module db/gridfs
4
+ */
5
+
1
6
  const fs = require('fs');
2
7
  const { GridFSBucket, MongoClient } = require('mongodb');
3
8
  const { get_settings } = require('../setting');
4
9
 
10
+ const CHUNK_SIZE = 1024 * 1024; // 1MB
11
+
5
12
  let gridfs_instance;
6
13
 
14
+ /**
15
+ * Get or create GridFS singleton instance.
16
+ * @returns {Promise<GridFS>} GridFS instance
17
+ */
7
18
  const get_gridfs_instance = async () => {
8
- if (gridfs_instance) {
9
- return gridfs_instance;
10
- } else {
11
- const mongo = get_settings().mongo;
12
- gridfs_instance = new GridFS();
13
- const client = await gridfs_instance.connect(mongo.url);
14
- gridfs_instance.db = client.db();
15
- return gridfs_instance;
16
- }
17
- }
19
+ if (gridfs_instance) return gridfs_instance;
20
+
21
+ const { url } = get_settings().mongo;
22
+ const client = await MongoClient.connect(url, { useUnifiedTopology: true, useNewUrlParser: true });
23
+
24
+ gridfs_instance = new GridFS(client.db());
25
+ return gridfs_instance;
26
+ };
27
+
28
+ /**
29
+ * Wrap stream in promise.
30
+ * @param {Stream} stream - Stream to wrap
31
+ * @returns {Promise} Resolves on finish, rejects on error
32
+ */
33
+ const stream_to_promise = (stream) => new Promise((resolve, reject) => {
34
+ stream.on('error', reject).on('finish', resolve);
35
+ });
18
36
 
19
37
  class GridFS {
20
- /**
21
- * Connect to mongodb
22
- * @param {url of mongo} url
23
- * @returns
24
- */
25
- connect(url) {
26
- return new Promise((resolve, reject) => {
27
- MongoClient.connect(url, { useUnifiedTopology: true, useNewUrlParser: true }, (err, client) => {
28
- if (err) return reject(err);
29
- resolve(client);
30
- });
31
- });
38
+ constructor(db) {
39
+ this.db = db;
32
40
  }
33
41
 
34
42
  /**
35
- *
36
- * @param {mongodb bucket name} bucketname
37
- * @param {the file name} filename
38
- * @param {the file path} filepath
39
- * @returns
43
+ * Get GridFS bucket.
44
+ * @param {string} bucket_name - Bucket name
45
+ * @returns {GridFSBucket} Bucket instance
40
46
  */
41
- async save_file(bucketname, filename, filepath) {
42
- const bucket = new GridFSBucket(this.db, { chunkSizeBytes: 1024 * 1024, bucketName: bucketname });
43
- const files = await bucket.find({ filename: filename }).toArray();
44
- if (files && files.length > 0) {
45
- await delete_file(bucket, files[0]._id);
46
- }
47
- return new Promise((resolve, reject) => {
48
- if (typeof filepath === 'string') {
49
- fs.createReadStream(filepath).pipe(bucket.openUploadStream(filename))
50
- .on('error', err => reject(err))
51
- .on('finish', item => resolve(item));
52
- } else {
53
- filepath.pipe(bucket.openUploadStream(filename))
54
- .on('error', err => reject(err))
55
- .on('finish', item => resolve(item));
56
- }
57
- });
47
+ bucket(bucket_name) {
48
+ return new GridFSBucket(this.db, { chunkSizeBytes: CHUNK_SIZE, bucketName: bucket_name });
58
49
  }
59
50
 
60
51
  /**
61
- *
62
- * @param {mongodb bucket name} bucketname
63
- * @param {the file name} filename
64
- * @param {http response} response
52
+ * Save file to GridFS (replaces existing if found).
53
+ * @param {string} bucket_name - Bucket name
54
+ * @param {string} filename - File name
55
+ * @param {string|Stream} source - File path or readable stream
56
+ * @returns {Promise} Upload result
65
57
  */
66
- read_file(bucketname, filename, response) {
67
- const bucket = new GridFSBucket(this.db, { chunkSizeBytes: 1024 * 1024, bucketName: bucketname });
68
- const stream = bucket.openDownloadStreamByName(filename);
69
- stream.on('data', (chunk) => {
70
- response.write(chunk);
71
- });
72
- stream.on('error', () => {
73
- response.sendStatus(404);
74
- });
75
- stream.on('end', () => {
76
- response.end();
77
- });
58
+ async save_file(bucket_name, filename, source) {
59
+ const bucket = this.bucket(bucket_name);
60
+
61
+ // Delete existing file if present
62
+ const existing = await bucket.find({ filename }).toArray();
63
+ if (existing.length > 0) {
64
+ await bucket.delete(existing[0]._id);
65
+ }
66
+
67
+ // Create read stream from path if string
68
+ const read_stream = typeof source === 'string' ? fs.createReadStream(source) : source;
69
+ return stream_to_promise(read_stream.pipe(bucket.openUploadStream(filename)));
78
70
  }
79
71
 
80
72
  /**
81
- *
82
- * @param {mongodb bucket name} bucketname
83
- * @param {the file name} filename
84
- * @param {the dest file name} dest_file
73
+ * Stream file to HTTP response.
74
+ * @param {string} bucket_name - Bucket name
75
+ * @param {string} filename - File name
76
+ * @param {Response} response - HTTP response object
85
77
  */
86
- async pipe_file(bucketname, filename, dest_filename) {
87
- const bucket = new GridFSBucket(this.db, { chunkSizeBytes: 1024 * 1024, bucketName: bucketname });
88
- const stream = bucket.openDownloadStreamByName(filename);
89
- const write_stream = require("fs").createWriteStream(dest_filename);
90
-
91
- return new Promise((resolve, reject) => {
92
- stream.pipe(write_stream)
93
- .on('error', err => reject(err))
94
- .on('finish', item => resolve(item));
95
- });
78
+ read_file(bucket_name, filename, response) {
79
+ const stream = this.bucket(bucket_name).openDownloadStreamByName(filename);
80
+ stream.on('data', chunk => response.write(chunk));
81
+ stream.on('error', () => response.sendStatus(404));
82
+ stream.on('end', () => response.end());
96
83
  }
97
84
 
98
85
  /**
99
- * Delete the files by file name
100
- * @param {bucket name} bucketname
101
- * @param {file name} filename
86
+ * Pipe file from GridFS to disk.
87
+ * @param {string} bucket_name - Bucket name
88
+ * @param {string} filename - Source file name
89
+ * @param {string} dest_path - Destination file path
90
+ * @returns {Promise} Pipe result
102
91
  */
103
- async delete_files(bucketname, filename) {
104
- const bucket = new GridFSBucket(this.db, { chunkSizeBytes: 1024 * 1024, bucketName: bucketname });
105
- const files = await bucket.find({ filename: filename }).toArray();
106
- if (files && files.length > 0) {
107
- await delete_file(bucket, files[0]._id);
108
- }
92
+ pipe_file(bucket_name, filename, dest_path) {
93
+ const stream = this.bucket(bucket_name).openDownloadStreamByName(filename);
94
+ return stream_to_promise(stream.pipe(fs.createWriteStream(dest_path)));
109
95
  }
110
96
 
111
97
  /**
112
- * Delete the file using id
113
- * @param {mongodb bucket name} bucket
114
- * @param {id of the file} id
115
- * @returns
98
+ * Delete file by name.
99
+ * @param {string} bucket_name - Bucket name
100
+ * @param {string} filename - File name
116
101
  */
117
- delete_file(bucket, id) {
118
- return new Promise((resolve, reject) => {
119
- bucket.delete(id, (async (err) => {
120
- if (err) {
121
- reject(err);
122
- } else {
123
- resolve(true);
124
- }
125
- }));
126
- });
102
+ async delete_file(bucket_name, filename) {
103
+ const bucket = this.bucket(bucket_name);
104
+ const files = await bucket.find({ filename }).toArray();
105
+ if (files.length > 0) {
106
+ await bucket.delete(files[0]._id);
107
+ }
127
108
  }
128
109
  }
129
110
 
130
111
  /**
131
- * set file fields of the entity
132
- * @param {meta info} meta
133
- * @param {http request} req
134
- * @param {entity object} obj
112
+ * Set file field values on entity object based on uploaded files.
113
+ * @param {Object} meta - Entity meta info
114
+ * @param {Request} req - HTTP request
115
+ * @param {Object} obj - Entity object
135
116
  */
136
- const set_file_fields = function (meta, req, obj) {
137
- const file_fields = meta.file_fields;
138
-
139
- if (file_fields && file_fields.length > 0 && req.files) {
140
- const primary_key = meta.primary_keys.map(key => obj[key]).join("_");
141
- file_fields.forEach(function (field) {
142
- const files = req.files[field.name];
143
-
144
- if (files && files.length > 0) {
145
- if (file_fields.length == 1) {
146
- obj[field.name] = primary_key;
147
- } else {
148
- obj[field.name] = primary_key + "_" + field.name;
149
- }
150
- } else {
151
- delete obj[field.name];
152
- }
153
- });
117
+ const set_file_fields = (meta, req, obj) => {
118
+ const { file_fields, primary_keys } = meta;
119
+ if (!file_fields?.length || !req.files) return;
120
+
121
+ const primary_key = primary_keys.map(key => obj[key]).join('_');
122
+
123
+ for (const field of file_fields) {
124
+ const files = req.files[field.name];
125
+ if (files?.length > 0) {
126
+ obj[field.name] = file_fields.length === 1 ? primary_key : `${primary_key}_${field.name}`;
127
+ } else {
128
+ delete obj[field.name];
129
+ }
154
130
  }
155
- }
131
+ };
156
132
 
157
133
  /**
158
- *
159
- * @param {mongodb collection name} collection
160
- * @param {file fields of the entity} file_fields
161
- * @param {http request object} req
162
- * @param {entity object} obj
134
+ * Save uploaded file fields to GridFS.
135
+ * @param {string} collection - MongoDB collection name
136
+ * @param {Object[]} file_fields - File field definitions
137
+ * @param {Request} req - HTTP request
138
+ * @param {Object} obj - Entity object
163
139
  */
164
- const save_file_fields_to_db = async function (collection, file_fields, req, obj) {
165
- if (file_fields && file_fields.length > 0 && req.files) {
166
- for (let i = 0; i < file_fields.length; i++) {
167
- const field = file_fields[i];
168
- if (obj[field.name]) {
169
- const file = req.files[field.name][0];
170
- const instance = await get_gridfs_instance();
171
- await instance.save_file(collection, obj[field.name], file.path);
172
- fs.unlinkSync(file.path);
173
- }
174
- }
140
+ const save_file_fields_to_db = async (collection, file_fields, req, obj) => {
141
+ if (!file_fields?.length || !req.files) return;
142
+
143
+ const instance = await get_gridfs_instance();
144
+
145
+ for (const field of file_fields) {
146
+ if (!obj[field.name]) continue;
147
+
148
+ const [file] = req.files[field.name];
149
+ await instance.save_file(collection, obj[field.name], file.path);
150
+ fs.unlinkSync(file.path);
175
151
  }
176
- }
152
+ };
177
153
 
178
- /**
179
- * Save file to gridfs file
180
- * @param {collection name} collection
181
- * @param {file name} filename
182
- * @param {file full path} filepath
183
- */
154
+ // Wrapper functions for external API
184
155
  const save_file = async (collection, filename, filepath) => {
185
156
  const instance = await get_gridfs_instance();
186
157
  await instance.save_file(collection, filename, filepath);
187
- }
158
+ };
188
159
 
189
- /**
190
- * read file from gridfs
191
- * @param {collection name} collection
192
- * @param {file name} filename
193
- * @param {http response} response
194
- */
195
160
  const read_file = async (collection, filename, response) => {
196
161
  const instance = await get_gridfs_instance();
197
- await instance.read_file(collection, filename, response);
198
- }
162
+ instance.read_file(collection, filename, response);
163
+ };
199
164
 
200
- /**
201
- * pipe file from gridfs
202
- * @param {collection name} collection
203
- * @param {file name} filename
204
- * @param {dest file name} dest_filename
205
- */
206
165
  const pipe_file = async (collection, filename, dest_filename) => {
207
166
  const instance = await get_gridfs_instance();
208
167
  await instance.pipe_file(collection, filename, dest_filename);
209
- }
168
+ };
210
169
 
211
- /**
212
- * delete file from gridfs
213
- * @param {collection name} collection
214
- * @param {file name} filename
215
- */
216
170
  const delete_file = async (collection, filename) => {
217
171
  const instance = await get_gridfs_instance();
218
- await instance.delete_files(collection, filename);
219
- }
172
+ await instance.delete_file(collection, filename);
173
+ };
220
174
 
221
175
  module.exports = { set_file_fields, save_file_fields_to_db, save_file, read_file, pipe_file, delete_file };
@@ -0,0 +1,56 @@
1
+ # Default Field Attribute
2
+
3
+ Added `default` attribute support to meta field definitions for automatic value population during create operations.
4
+
5
+ ## Changes
6
+
7
+ ### Server: `core/meta.js`
8
+
9
+ 1. Added `default` to `FIELD_ATTRS` array
10
+ 2. Added validation in `validate_field`:
11
+ - Validates default value against field type using `type.convert()`
12
+ - Throws error if default value is invalid for the field type
13
+
14
+ ```javascript
15
+ // Validate default value against field type
16
+ if (field.default !== undefined && field.type) {
17
+ const type = get_type(field.type);
18
+ const result = type.convert(field.default);
19
+ if (result.err) throw meta_error(meta.collection, `invalid default value...`);
20
+ }
21
+ ```
22
+
23
+ ### Client: `components/BasicForm.vue`
24
+
25
+ 1. Added `apply_defaults(form)` method that applies default values to empty form fields
26
+ 2. Called during initialization and form prop changes
27
+
28
+ ```javascript
29
+ apply_defaults(form) {
30
+ const result = { ...form };
31
+ for (const field of this.fields) {
32
+ if (field.default !== undefined &&
33
+ (result[field.name] === undefined || result[field.name] === null || result[field.name] === "")) {
34
+ result[field.name] = field.default;
35
+ }
36
+ }
37
+ return result;
38
+ }
39
+ ```
40
+
41
+ ## Usage
42
+
43
+ ```javascript
44
+ {
45
+ collection: "products",
46
+ primary_keys: ["name"],
47
+ fields: [
48
+ { name: "name", type: "string", required: true },
49
+ { name: "quantity", type: "int", default: 0 },
50
+ { name: "active", type: "boolean", default: true },
51
+ { name: "price", type: "float", default: 9.99 }
52
+ ]
53
+ }
54
+ ```
55
+
56
+ When creating a new product, form fields automatically populate with their defined default values.
package/http/context.js CHANGED
@@ -1,17 +1,31 @@
1
+ /**
2
+ * @fileoverview Request context management using AsyncLocalStorage.
3
+ * @module http/context
4
+ */
5
+
1
6
  const { AsyncLocalStorage } = require('async_hooks');
2
7
  const asyncLocalStorage = new AsyncLocalStorage();
3
8
 
9
+ /**
10
+ * Set value in current request context.
11
+ * @param {string} key - Context key
12
+ * @param {*} obj - Value to store
13
+ */
4
14
  const set_context_value = (key, obj) => {
5
15
  const store = asyncLocalStorage.getStore();
6
- store && (store[key] = obj);
7
- }
16
+ if (store) {
17
+ store[key] = obj;
18
+ }
19
+ };
8
20
 
21
+ /**
22
+ * Get value from current request context.
23
+ * @param {string} key - Context key
24
+ * @returns {*} Stored value or null
25
+ */
9
26
  const get_context_value = (key) => {
10
27
  const store = asyncLocalStorage.getStore();
11
- if (!store) {
12
- return null;
13
- }
14
- return store[key];
15
- }
28
+ return store ? store[key] : null;
29
+ };
16
30
 
17
- module.exports = { asyncLocalStorage, set_context_value, get_context_value }
31
+ module.exports = { asyncLocalStorage, set_context_value, get_context_value };
package/http/cors.js CHANGED
@@ -1,15 +1,32 @@
1
+ /**
2
+ * @fileoverview CORS middleware initialization.
3
+ * @module http/cors
4
+ */
5
+
1
6
  const cors = require('cors');
2
7
  const { get_settings } = require('../setting');
3
8
 
9
+ /**
10
+ * Initialize CORS middleware with client URL whitelist.
11
+ * @param {Object} app - Express application instance
12
+ * @throws {Error} If server settings are missing
13
+ */
4
14
  const init_cors = (app) => {
5
- const server = get_settings().server;
6
- if (server && server.client_web_url) {
7
- app.use(cors({
8
- origin: server.client_web_url,
9
- methods: ['GET', 'POST'],
10
- credentials: true
11
- }));
15
+ const settings = get_settings();
16
+ if (!settings?.server) {
17
+ throw new Error('Server settings required for CORS initialization');
18
+ }
19
+
20
+ const client_urls = settings.server.client_web_url;
21
+ if (!Array.isArray(client_urls)) {
22
+ return;
12
23
  }
13
- }
24
+
25
+ app.use(cors({
26
+ origin: client_urls,
27
+ methods: ['GET', 'POST'],
28
+ credentials: true
29
+ }));
30
+ };
14
31
 
15
32
  module.exports = { init_cors };
package/http/error.js CHANGED
@@ -1,21 +1,39 @@
1
+ /**
2
+ * @fileoverview Error handling middleware and utilities.
3
+ * @module http/error
4
+ */
5
+
1
6
  const { ERROR } = require('./code');
2
7
  const { LOG_SYSTEM, is_log_error, log_error } = require('../db/db');
3
8
 
9
+ /**
10
+ * Wrap async route handler to catch errors.
11
+ * @param {Function} fn - Async route handler
12
+ * @returns {Function} Wrapped handler
13
+ */
4
14
  const wrap_http = fn => (...args) => fn(...args).catch(args[2]);
5
15
 
16
+ /**
17
+ * Global error handler middleware.
18
+ * Logs errors when enabled and returns standardized error response.
19
+ * @param {Object} app - Express application instance
20
+ */
6
21
  const handle_exception = app => {
7
22
  app.use(function (error, req, res, next) {
8
- if (is_log_error()) {
9
- if (error) {
10
- const error_msg = error.stack ? error.stack : JSON.stringify(error);
11
- log_error(LOG_SYSTEM, error_msg);
23
+ if (error) {
24
+ const error_msg = error.stack || error.message || JSON.stringify(error);
25
+
26
+ if (is_log_error()) {
27
+ log_error(LOG_SYSTEM, error_msg, { path: req.originalUrl, method: req.method });
28
+ } else {
29
+ console.error('[Error]', error_msg);
12
30
  }
13
- } else {
14
- throw new Error(error);
15
31
  }
16
32
 
17
- res.json({ code: ERROR, err: "errors occur in server" });
33
+ if (!res.headersSent) {
34
+ res.json({ code: ERROR, err: "server error" });
35
+ }
18
36
  });
19
- }
37
+ };
20
38
 
21
- module.exports = { wrap_http, handle_exception }
39
+ module.exports = { wrap_http, handle_exception };
package/http/express.js CHANGED
@@ -1,9 +1,15 @@
1
+ /**
2
+ * @fileoverview Express server initialization and configuration.
3
+ * @module http/express
4
+ */
5
+
1
6
  const express = require('express');
2
7
 
3
8
  const { init_cors } = require('./cors');
4
9
  const { NO_SESSION } = require('./code');
5
- const { init_session, get_session_userid } = require('./session');
10
+ const { init_session, get_session_user_id } = require('./session');
6
11
  const { init_router_dirs } = require('./router');
12
+ const { ensure_entity_indexes } = require('../db/db');
7
13
  const { handle_exception } = require('./error');
8
14
  const { get_settings } = require('../setting');
9
15
  const { asyncLocalStorage, set_context_value } = require('./context');
@@ -11,65 +17,88 @@ const { asyncLocalStorage, set_context_value } = require('./context');
11
17
  const app = express();
12
18
  let server_initialized = false;
13
19
 
20
+ /**
21
+ * Check if URL is in the excluded list.
22
+ * @param {Object} server - Server settings
23
+ * @param {Object} req - Express request
24
+ * @returns {boolean} True if URL should be excluded from auth
25
+ */
14
26
  const is_excluded_url = (server, req) => {
15
- const exclude_urls = server.exclude_urls;
16
- for (let i = 0; i < exclude_urls.length; i++) {
17
- const re = new RegExp(exclude_urls[i], "gim");
18
- if (re.test(req.originalUrl)) {
19
- return true;
27
+ const patterns = server.exclude_urls;
28
+ if (!Array.isArray(patterns)) {
29
+ return false;
30
+ }
31
+ return patterns.some(pattern => new RegExp(pattern, "i").test(req.originalUrl));
32
+ };
33
+
34
+ /**
35
+ * Configure body parser middleware with optional size limit.
36
+ * @param {Object} app - Express application
37
+ * @param {string|undefined} body_limit - Optional body size limit
38
+ */
39
+ const configure_body_parser = (app, body_limit) => {
40
+ const options = body_limit ? { limit: body_limit, extended: true } : { extended: true };
41
+ app.use(express.json(options));
42
+ app.use(express.urlencoded(options));
43
+ };
44
+
45
+ /**
46
+ * Create authentication middleware.
47
+ * @param {Object} server - Server settings
48
+ * @returns {Function} Express middleware
49
+ */
50
+ const create_auth_middleware = (server) => (req, res, next) => {
51
+ if (server.check_user && !is_excluded_url(server, req)) {
52
+ if (!get_session_user_id(req)) {
53
+ res.json({ code: NO_SESSION, err: "authentication required" });
54
+ return;
20
55
  }
21
56
  }
22
57
 
23
- return false;
24
- }
58
+ asyncLocalStorage.run({}, () => {
59
+ set_context_value("req", req);
60
+ next();
61
+ });
62
+ };
25
63
 
64
+ /**
65
+ * Initialize Express server with middleware and routes.
66
+ * @param {string} base_dir - Base directory for router files
67
+ * @param {string} port_attr - Port attribute name in settings
68
+ * @param {Function} [callback] - Optional callback after server starts
69
+ * @returns {Object} Express app instance
70
+ * @throws {Error} If server settings are invalid
71
+ */
26
72
  const init_express_server = (base_dir, port_attr, callback) => {
27
- if (server_initialized === true) {
73
+ if (server_initialized) {
28
74
  return app;
29
75
  }
30
76
 
31
- const server = get_settings().server;
32
- const threshold = server.threshold;
33
-
34
- init_cors(app);
77
+ const settings = get_settings();
78
+ if (!settings?.server) {
79
+ throw new Error('Server settings required for initialization');
80
+ }
35
81
 
36
- if (threshold) {
37
- app.use(express.json({ limit: threshold.body_limit, extended: true }));
38
- app.use(express.urlencoded({ limit: threshold.body_limit, extended: true }));
82
+ const { server } = settings;
83
+ const port = server[port_attr];
84
+ if (!port) {
85
+ throw new Error(`Port attribute '${port_attr}' not found in server settings`);
39
86
  }
40
87
 
88
+ init_cors(app);
89
+ configure_body_parser(app, server.threshold?.body_limit);
41
90
  init_session(app);
42
-
43
- app.use((req, res, next) => {
44
- if (server.check_user && !is_excluded_url(server, req)) {
45
- const user_id = get_session_userid(req);
46
- if (user_id == null) {
47
- res.json({ code: NO_SESSION, err: "no session found" });
48
- } else {
49
- asyncLocalStorage.run({}, () => {
50
- set_context_value("req", req);
51
- next();
52
- });
53
- }
54
- } else {
55
- asyncLocalStorage.run({}, () => {
56
- set_context_value("req", req);
57
- next();
58
- });
59
- }
60
- });
61
-
91
+ app.use(create_auth_middleware(server));
62
92
  init_router_dirs(app, base_dir);
93
+ ensure_entity_indexes();
63
94
  handle_exception(app);
64
95
 
65
- app.listen(server[port_attr], async function () {
66
- if (callback) {
67
- await callback();
68
- }
96
+ app.listen(port, async () => {
97
+ if (callback) await callback();
69
98
  });
70
99
 
71
100
  server_initialized = true;
72
101
  return app;
73
- }
102
+ };
74
103
 
75
104
  module.exports = { init_express_server };