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.
- package/README.md +196 -1
- package/core/array.js +79 -142
- package/core/bash.js +208 -259
- package/core/chart.js +26 -16
- package/core/cron.js +14 -3
- package/core/date.js +15 -44
- package/core/encrypt.js +19 -9
- package/core/file.js +42 -29
- package/core/lhs.js +32 -6
- package/core/meta.js +213 -289
- package/core/msg.js +20 -7
- package/core/number.js +105 -103
- package/core/obj.js +15 -12
- package/core/random.js +9 -6
- package/core/role.js +69 -77
- package/core/thread.js +12 -2
- package/core/type.js +300 -261
- package/core/url.js +20 -12
- package/core/validate.js +29 -26
- package/db/db.js +297 -227
- package/db/entity.js +631 -963
- package/db/gridfs.js +120 -166
- package/design/add_default_field_attr.md +56 -0
- package/http/context.js +22 -8
- package/http/cors.js +25 -8
- package/http/error.js +27 -9
- package/http/express.js +70 -41
- package/http/params.js +70 -42
- package/http/router.js +51 -40
- package/http/session.js +59 -36
- package/index.js +85 -9
- package/package.json +2 -2
- package/router/clone.js +28 -36
- package/router/create.js +21 -26
- package/router/delete.js +24 -28
- package/router/read.js +137 -123
- package/router/update.js +38 -56
- package/setting.js +22 -6
- package/skills/array.md +155 -0
- package/skills/bash.md +91 -0
- package/skills/chart.md +54 -0
- package/skills/code.md +422 -0
- package/skills/context.md +177 -0
- package/skills/date.md +58 -0
- package/skills/express.md +255 -0
- package/skills/file.md +60 -0
- package/skills/lhs.md +54 -0
- package/skills/meta.md +1023 -0
- package/skills/msg.md +30 -0
- package/skills/number.md +88 -0
- package/skills/obj.md +36 -0
- package/skills/params.md +206 -0
- package/skills/random.md +22 -0
- package/skills/role.md +59 -0
- package/skills/session.md +281 -0
- package/skills/storage.md +743 -0
- package/skills/thread.md +22 -0
- package/skills/type.md +547 -0
- package/skills/url.md +34 -0
- package/skills/validate.md +48 -0
- package/test/cleanup/close-db.js +5 -0
- package/test/core/array.js +226 -0
- package/test/core/chart.js +51 -0
- package/test/core/file.js +59 -0
- package/test/core/lhs.js +44 -0
- package/test/core/number.js +167 -12
- package/test/core/obj.js +47 -0
- package/test/core/random.js +24 -0
- package/test/core/thread.js +20 -0
- package/test/core/type.js +216 -0
- package/test/core/validate.js +67 -0
- package/test/db/db-ops.js +99 -0
- package/test/db/pipe_test.txt +0 -0
- package/test/db/test_case_design.md +528 -0
- package/test/db/test_db_class.js +613 -0
- package/test/db/test_entity_class.js +414 -0
- package/test/db/test_gridfs_class.js +234 -0
- package/test/entity/create.js +1 -1
- package/test/entity/delete-mixed.js +156 -0
- package/test/entity/ref-filter.js +63 -0
- package/tool/gen_i18n.js +55 -21
- package/test/crud/router.js +0 -99
- package/test/router/user.js +0 -17
package/db/entity.js
CHANGED
|
@@ -1,826 +1,684 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Entity-level CRUD helpers and metadata-driven operations.
|
|
3
|
+
* @module db/entity
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
const { SUCCESS, ERROR, NO_PARAMS, INVALID_PARAMS, DUPLICATE_KEY, NOT_FOUND, REF_NOT_FOUND, REF_NOT_UNIQUE, HAS_REF } = require('../http/code');
|
|
2
7
|
const { validate_required_fields, has_value } = require('../core/validate');
|
|
3
8
|
const { required_params } = require('../http/params');
|
|
4
9
|
const { convert_type, convert_update_type, get_type } = require('../core/type');
|
|
5
10
|
const { get_entity_meta, DELETE_MODE } = require('../core/meta');
|
|
6
11
|
const { unique, map_array_to_obj } = require('../core/array');
|
|
7
|
-
const { LOG_ENTITY, get_db, oid_query, oid_queries,
|
|
12
|
+
const { LOG_ENTITY, get_db, oid_query, oid_queries, log_debug, log_error, get_session_user_id, bulk_update } = require('./db');
|
|
13
|
+
|
|
14
|
+
// Comparison operator mapping for search queries
|
|
15
|
+
const COMPARISON_OPERATORS = [
|
|
16
|
+
{ prefix: '>=', op: '$gte', len: 2 },
|
|
17
|
+
{ prefix: '<=', op: '$lte', len: 2 },
|
|
18
|
+
{ prefix: '>', op: '$gt', len: 1 },
|
|
19
|
+
{ prefix: '<', op: '$lt', len: 1 }
|
|
20
|
+
];
|
|
8
21
|
|
|
9
22
|
/**
|
|
10
|
-
* Convert search value type,
|
|
11
|
-
* @param {
|
|
12
|
-
* @param {
|
|
13
|
-
* @returns
|
|
23
|
+
* Convert search value type, keeping original on error.
|
|
24
|
+
* @param {string} type_name - Field type
|
|
25
|
+
* @param {*} search_value - Search value
|
|
26
|
+
* @returns {*} Converted value or original on error
|
|
14
27
|
*/
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return search_value;
|
|
20
|
-
} else {
|
|
21
|
-
return value;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
28
|
+
const convert_search_value = (type_name, search_value) => {
|
|
29
|
+
const { value, err } = get_type(type_name).convert(search_value);
|
|
30
|
+
return err ? search_value : value;
|
|
31
|
+
};
|
|
24
32
|
|
|
25
33
|
/**
|
|
26
|
-
* Create search object based on
|
|
27
|
-
* @param {
|
|
28
|
-
* @param {
|
|
29
|
-
* @param {
|
|
30
|
-
* @returns
|
|
34
|
+
* Create search object based on field type and value.
|
|
35
|
+
* @param {string} name - Field name
|
|
36
|
+
* @param {string} type_name - Field type name
|
|
37
|
+
* @param {*} search_value - Search value
|
|
38
|
+
* @returns {Object} MongoDB query object
|
|
31
39
|
*/
|
|
32
|
-
const parse_search_value =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
return { [name]: {
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
} else if (type_name === "array") {
|
|
47
|
-
return { [name]: { "$in": [search_value] } };
|
|
48
|
-
} else {
|
|
49
|
-
let value = convert_search_value_by_type(type_name, search_value);
|
|
50
|
-
if (typeof value === "string") {
|
|
51
|
-
value = new RegExp(value, 'i');
|
|
40
|
+
const parse_search_value = (name, type_name, search_value) => {
|
|
41
|
+
const raw = `${search_value}`;
|
|
42
|
+
|
|
43
|
+
// Handle comma-separated values
|
|
44
|
+
if (raw.includes(',')) {
|
|
45
|
+
const values = raw.split(',').map(v => convert_search_value(type_name, v));
|
|
46
|
+
const op = type_name === 'array' ? '$all' : '$in';
|
|
47
|
+
return { [name]: { [op]: values } };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Handle comparison operators
|
|
51
|
+
for (const { prefix, op, len } of COMPARISON_OPERATORS) {
|
|
52
|
+
if (raw.startsWith(prefix)) {
|
|
53
|
+
return { [name]: { [op]: convert_search_value(type_name, raw.substring(len)) } };
|
|
52
54
|
}
|
|
53
|
-
return { [name]: value }
|
|
54
55
|
}
|
|
56
|
+
|
|
57
|
+
// Handle array type
|
|
58
|
+
if (type_name === 'array') {
|
|
59
|
+
return { [name]: { $in: [raw] } };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Default: convert value, use regex for strings
|
|
63
|
+
let value = convert_search_value(type_name, raw);
|
|
64
|
+
if (typeof value === 'string') {
|
|
65
|
+
value = new RegExp(value, 'i');
|
|
66
|
+
}
|
|
67
|
+
return { [name]: value };
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Apply ref_filter to query based on entity context.
|
|
72
|
+
* @param {Object} query - Existing query
|
|
73
|
+
* @param {Object} ref_filter - Filter configuration
|
|
74
|
+
* @param {string} ref_by_entity - Referring entity name
|
|
75
|
+
* @returns {Object} Query with filter applied
|
|
76
|
+
*/
|
|
77
|
+
const apply_ref_filter = (query, ref_filter, ref_by_entity) => {
|
|
78
|
+
if (!ref_filter) return query;
|
|
79
|
+
|
|
80
|
+
const filter = (ref_by_entity && ref_filter[ref_by_entity])
|
|
81
|
+
|| ref_filter['*']
|
|
82
|
+
|| (typeof ref_filter === 'object' ? ref_filter : null);
|
|
83
|
+
|
|
84
|
+
return filter ? { ...query, ...filter } : query;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Log error with formatted message.
|
|
89
|
+
* @param {string} msg - Error message
|
|
90
|
+
* @param {Object} data - Additional data to log
|
|
91
|
+
*/
|
|
92
|
+
const log_err = (msg, data = {}) => {
|
|
93
|
+
const parts = Object.entries(data)
|
|
94
|
+
.filter(([, v]) => v !== undefined)
|
|
95
|
+
.map(([k, v]) => `${k}:${JSON.stringify(v)}`);
|
|
96
|
+
log_error(LOG_ENTITY, parts.length ? `${msg} - ${parts.join(', ')}` : msg);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Execute a lifecycle hook and return error result if failed.
|
|
101
|
+
* @param {Function} hook - Hook function to execute
|
|
102
|
+
* @param {string} hook_name - Name for logging
|
|
103
|
+
* @param {...any} args - Arguments to pass to hook
|
|
104
|
+
* @returns {Object|null} Error result or null if successful
|
|
105
|
+
*/
|
|
106
|
+
const run_hook = async (hook, hook_name, ...args) => {
|
|
107
|
+
if (!hook) return null;
|
|
108
|
+
const { code, err } = await hook(...args);
|
|
109
|
+
if (err || code !== SUCCESS) {
|
|
110
|
+
log_err(`${hook_name} error`, { err, code });
|
|
111
|
+
return { code, err };
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Validate reference and return error result if failed.
|
|
118
|
+
* @param {Entity} entity - Entity instance
|
|
119
|
+
* @param {Object} obj - Object to validate
|
|
120
|
+
* @returns {Object|null} Error result or null if successful
|
|
121
|
+
*/
|
|
122
|
+
const validate_refs = async (entity, obj) => {
|
|
123
|
+
if (!entity.meta.ref_fields) return null;
|
|
124
|
+
const { code, err } = await entity.validate_ref(obj);
|
|
125
|
+
if (err || code !== SUCCESS) {
|
|
126
|
+
log_err('validate_ref error', { err, code });
|
|
127
|
+
return { code, err };
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Parse positive integer with default.
|
|
134
|
+
* @param {*} value - Value to parse
|
|
135
|
+
* @param {number} defaultVal - Default value
|
|
136
|
+
* @returns {number} Parsed integer or default
|
|
137
|
+
*/
|
|
138
|
+
const parse_int = (value, defaultVal) => {
|
|
139
|
+
const parsed = parseInt(value);
|
|
140
|
+
return isNaN(parsed) || parsed <= 0 ? defaultVal : parsed;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Extract ref and link fields from field map based on allowed names.
|
|
145
|
+
* @param {Object} fields_map - Map of field name to field config
|
|
146
|
+
* @param {string[]} attr_names - Attribute names to check
|
|
147
|
+
* @param {string[]} allowed_names - Allowed field names
|
|
148
|
+
* @returns {Object} Object with attrs, ref_fields, and link_fields
|
|
149
|
+
*/
|
|
150
|
+
const extract_field_info = (fields_map, attr_names, allowed_names) => {
|
|
151
|
+
const attrs = { _id: 1 };
|
|
152
|
+
const ref_fields = [];
|
|
153
|
+
const link_fields = [];
|
|
154
|
+
|
|
155
|
+
attr_names.split(',').forEach(attr => {
|
|
156
|
+
if (!allowed_names.includes(attr)) return;
|
|
157
|
+
|
|
158
|
+
attrs[attr] = 1;
|
|
159
|
+
const field = fields_map[attr];
|
|
160
|
+
if (field.link) {
|
|
161
|
+
link_fields.push(field);
|
|
162
|
+
attrs[field.link] = 1;
|
|
163
|
+
} else if (field.ref) {
|
|
164
|
+
ref_fields.push(field);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return { attrs, ref_fields, link_fields };
|
|
55
169
|
};
|
|
56
170
|
|
|
57
171
|
class Entity {
|
|
58
172
|
/**
|
|
59
|
-
* @param {
|
|
173
|
+
* @param {Object|string} meta - Entity meta object or collection name
|
|
60
174
|
*/
|
|
61
175
|
constructor(meta) {
|
|
62
|
-
this.meta =
|
|
176
|
+
this.meta = typeof meta === 'string' ? get_entity_meta(meta) : meta;
|
|
63
177
|
this.db = get_db();
|
|
64
178
|
}
|
|
65
179
|
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
*/
|
|
70
|
-
get_col() {
|
|
71
|
-
return this.db.get_col(this.meta.collection);
|
|
180
|
+
/** @returns {Object} MongoDB collection */
|
|
181
|
+
col() {
|
|
182
|
+
return this.db.col(this.meta.collection);
|
|
72
183
|
}
|
|
73
184
|
|
|
74
185
|
/**
|
|
75
|
-
* Execute bulk update using the items
|
|
76
|
-
* @param {
|
|
77
|
-
* @param {
|
|
78
|
-
* @returns
|
|
186
|
+
* Execute bulk update using the items.
|
|
187
|
+
* @param {Object[]} items - Items to update
|
|
188
|
+
* @param {string[]} attrs - Attributes for search criteria
|
|
79
189
|
*/
|
|
80
190
|
async bulk_update(items, attrs) {
|
|
81
|
-
|
|
82
|
-
await bulk_update(col, items, attrs);
|
|
191
|
+
await bulk_update(this.col(), items, attrs);
|
|
83
192
|
}
|
|
84
193
|
|
|
85
194
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* @
|
|
89
|
-
* @returns code and err
|
|
195
|
+
* Validate ref value and convert ref_label to objectid.
|
|
196
|
+
* @param {Object} param_obj - Parameter object
|
|
197
|
+
* @returns {Object} Result with code and optional err
|
|
90
198
|
*/
|
|
91
199
|
async validate_ref(param_obj) {
|
|
92
200
|
const ref_fields = this.meta.ref_fields;
|
|
93
|
-
if (ref_fields) {
|
|
94
|
-
for (let i = 0; i < ref_fields.length; i++) {
|
|
95
|
-
const field = ref_fields[i];
|
|
96
|
-
const value = param_obj[field.name];
|
|
97
|
-
const ref_entity = new Entity(get_entity_meta(field.ref));
|
|
201
|
+
if (!ref_fields) return { code: SUCCESS };
|
|
98
202
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const v = value[j];
|
|
103
|
-
const ref_entities = await ref_entity.find_by_ref_value(v, { "_id": 1 }, this.meta.collection);
|
|
104
|
-
|
|
105
|
-
if (ref_entities.length == 0) {
|
|
106
|
-
return { code: REF_NOT_FOUND, err: [field.name] };
|
|
107
|
-
} else if (ref_entities.length > 1) {
|
|
108
|
-
return { code: REF_NOT_UNIQUE, err: [field.name] };
|
|
109
|
-
} else if (ref_entities.length == 1) {
|
|
110
|
-
array.push(ref_entities[0]["_id"] + "");
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
param_obj[field.name] = array;
|
|
203
|
+
for (const field of ref_fields) {
|
|
204
|
+
const value = param_obj[field.name];
|
|
205
|
+
const ref_entity = new Entity(get_entity_meta(field.ref));
|
|
114
206
|
|
|
115
|
-
|
|
116
|
-
|
|
207
|
+
const resolve_ref = async (v) => {
|
|
208
|
+
const refs = await ref_entity.find_by_ref_value(v, { _id: 1 }, this.meta.collection);
|
|
209
|
+
if (refs.length === 0) return { code: REF_NOT_FOUND, err: [field.name] };
|
|
210
|
+
if (refs.length > 1) return { code: REF_NOT_UNIQUE, err: [field.name] };
|
|
211
|
+
return { id: `${refs[0]._id}` };
|
|
212
|
+
};
|
|
117
213
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
214
|
+
if (Array.isArray(value)) {
|
|
215
|
+
const ids = [];
|
|
216
|
+
for (const v of value) {
|
|
217
|
+
const result = await resolve_ref(v);
|
|
218
|
+
if (result.code) return result;
|
|
219
|
+
ids.push(result.id);
|
|
125
220
|
}
|
|
221
|
+
param_obj[field.name] = ids;
|
|
222
|
+
} else if (has_value(value)) {
|
|
223
|
+
const result = await resolve_ref(value);
|
|
224
|
+
if (result.code) return result;
|
|
225
|
+
param_obj[field.name] = result.id;
|
|
126
226
|
}
|
|
127
227
|
}
|
|
128
228
|
return { code: SUCCESS };
|
|
129
229
|
}
|
|
130
230
|
|
|
131
231
|
/**
|
|
132
|
-
* Create search query
|
|
133
|
-
* @param {
|
|
232
|
+
* Create search query from client params.
|
|
233
|
+
* @param {Object} param_obj - Search parameters
|
|
234
|
+
* @returns {Object|null} Query object or null if no search fields
|
|
134
235
|
*/
|
|
135
236
|
async get_search_query(param_obj) {
|
|
136
|
-
const search_fields = this.meta
|
|
137
|
-
if (search_fields
|
|
138
|
-
const refer_field_names = this.meta.ref_fields.map(f => f.name);
|
|
139
|
-
const query = {};
|
|
140
|
-
const and_array = [];
|
|
141
|
-
for (let i = 0; i < search_fields.length; i++) {
|
|
142
|
-
const search_field = search_fields[i];
|
|
143
|
-
const value = param_obj[search_field.name];
|
|
144
|
-
if (has_value(value)) {
|
|
145
|
-
if (refer_field_names.includes(search_field.name)) {
|
|
146
|
-
//refer field
|
|
147
|
-
const refer_entity = new Entity(get_entity_meta(search_field.ref));
|
|
148
|
-
const oids = await refer_entity.find_by_ref_value(value, { _id: 1 }, this.meta.collection);
|
|
149
|
-
if (oids.length == 1) {
|
|
150
|
-
and_array.push({ [search_field.name]: oids.map(o => o._id + "")[0] });
|
|
151
|
-
} else if (oids.length > 1) {
|
|
152
|
-
and_array.push({ [search_field.name]: { "$in": oids.map(o => o._id + "") } });
|
|
153
|
-
}
|
|
154
|
-
} else {
|
|
155
|
-
and_array.push(parse_search_value(search_field.name, search_field.type, value));
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
237
|
+
const { search_fields } = this.meta;
|
|
238
|
+
if (!search_fields?.length) return null;
|
|
159
239
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (ids.length == 1) {
|
|
163
|
-
and_array.push(oid_query(ids[0]));
|
|
164
|
-
} else if (ids.length > 1) {
|
|
165
|
-
and_array.push(oid_queries(ids));
|
|
166
|
-
}
|
|
167
|
-
}
|
|
240
|
+
const ref_names = this.meta.ref_fields.map(f => f.name);
|
|
241
|
+
const and_array = [];
|
|
168
242
|
|
|
169
|
-
|
|
170
|
-
|
|
243
|
+
for (const field of search_fields) {
|
|
244
|
+
const value = param_obj[field.name];
|
|
245
|
+
if (!has_value(value)) continue;
|
|
171
246
|
|
|
172
|
-
|
|
173
|
-
|
|
247
|
+
if (ref_names.includes(field.name)) {
|
|
248
|
+
const ref_entity = new Entity(get_entity_meta(field.ref));
|
|
249
|
+
const oids = await ref_entity.find_by_ref_value(value, { _id: 1 }, this.meta.collection);
|
|
250
|
+
if (oids.length > 0) {
|
|
251
|
+
const ids = oids.map(o => `${o._id}`);
|
|
252
|
+
const op = oids.length === 1 ? null : (`${value}`.includes(',') ? '$all' : '$in');
|
|
253
|
+
and_array.push(op ? { [field.name]: { [op]: ids } } : { [field.name]: ids[0] });
|
|
174
254
|
}
|
|
175
|
-
|
|
176
|
-
return query;
|
|
177
255
|
} else {
|
|
178
|
-
|
|
256
|
+
and_array.push(parse_search_value(field.name, field.type, value));
|
|
179
257
|
}
|
|
180
|
-
} else {
|
|
181
|
-
return null;
|
|
182
258
|
}
|
|
259
|
+
|
|
260
|
+
// Handle _id parameter
|
|
261
|
+
const id_param = param_obj._id?.trim();
|
|
262
|
+
if (id_param) {
|
|
263
|
+
const ids = id_param.split(',');
|
|
264
|
+
and_array.push(ids.length === 1 ? oid_query(ids[0]) : oid_queries(ids));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (and_array.length > 0) {
|
|
268
|
+
const query = { $and: and_array };
|
|
269
|
+
log_debug(LOG_ENTITY, `search query:${JSON.stringify(query)}`);
|
|
270
|
+
return query;
|
|
271
|
+
}
|
|
272
|
+
return {};
|
|
183
273
|
}
|
|
184
274
|
|
|
185
275
|
/**
|
|
186
|
-
*
|
|
187
|
-
* @param {
|
|
188
|
-
* @param {
|
|
189
|
-
* @
|
|
276
|
+
* List entities with pagination.
|
|
277
|
+
* @param {Object} query_params - Query parameters
|
|
278
|
+
* @param {Object} query - Additional query filter
|
|
279
|
+
* @param {Object} param_obj - Search parameters
|
|
280
|
+
* @param {string} view - View filter
|
|
281
|
+
* @returns {Object} Result with code, total, and data
|
|
190
282
|
*/
|
|
191
283
|
async list_entity(query_params, query, param_obj, view) {
|
|
192
|
-
const
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return { code: NO_PARAMS, err: error_required_field_names };
|
|
284
|
+
const missing = validate_required_fields(query_params, ['attr_names', 'sort_by', 'desc']);
|
|
285
|
+
if (missing.length > 0) {
|
|
286
|
+
log_err('missing required fields', { fields: missing });
|
|
287
|
+
return { code: NO_PARAMS, err: missing };
|
|
199
288
|
}
|
|
200
289
|
|
|
201
290
|
const { attr_names, page, limit, sort_by, desc } = query_params;
|
|
202
|
-
const sort = {};
|
|
203
|
-
const sorts = sort_by.split(",");
|
|
204
|
-
const descs = desc.split(",");
|
|
205
|
-
sorts.forEach(function (value, index) {
|
|
206
|
-
sort[value] = descs[index] === "false" ? 1 : -1;
|
|
207
|
-
});
|
|
208
291
|
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
const link_fields = [];
|
|
214
|
-
|
|
215
|
-
const attrs = {};
|
|
216
|
-
const fields_map = this.meta.fields_map;
|
|
217
|
-
attr_names.split(",").forEach((attr) => {
|
|
218
|
-
if (list_field_names.includes(attr)) {
|
|
219
|
-
attrs[attr] = 1;
|
|
220
|
-
const field = fields_map[attr];
|
|
221
|
-
if (field.link) {
|
|
222
|
-
link_fields.push(field);
|
|
223
|
-
attrs[field.link] = 1;
|
|
224
|
-
} else if (field.ref) {
|
|
225
|
-
ref_fields.push(field);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
});
|
|
292
|
+
// Build sort object
|
|
293
|
+
const sorts = sort_by.split(',');
|
|
294
|
+
const descs = desc.split(',');
|
|
295
|
+
const sort = sorts.reduce((s, field, i) => ({ ...s, [field]: descs[i] === 'false' ? 1 : -1 }), {});
|
|
229
296
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
297
|
+
const list_fields = this.filter_fields_by_view(this.meta.list_fields, view);
|
|
298
|
+
const { attrs, ref_fields, link_fields } = extract_field_info(
|
|
299
|
+
this.meta.fields_map,
|
|
300
|
+
attr_names,
|
|
301
|
+
list_fields.map(f => f.name)
|
|
302
|
+
);
|
|
234
303
|
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
if (is_log_error()) {
|
|
238
|
-
log_error(LOG_ENTITY, "no search query is set for param:" + JSON.stringify(param_obj));
|
|
239
|
-
}
|
|
304
|
+
const page_int = parse_int(page, 1);
|
|
305
|
+
const page_limit = parse_int(limit, 10);
|
|
240
306
|
|
|
241
|
-
|
|
307
|
+
const search_query = await this.get_search_query(param_obj);
|
|
308
|
+
if (search_query === null) {
|
|
309
|
+
log_err('no search query', { param_obj });
|
|
310
|
+
return { code: INVALID_PARAMS, err: 'no search query is set' };
|
|
242
311
|
}
|
|
243
312
|
|
|
244
|
-
const
|
|
245
|
-
const
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
if (is_log_debug()) {
|
|
250
|
-
log_debug(LOG_ENTITY, "total:" + total + ",data:" + JSON.stringify(data));
|
|
251
|
-
}
|
|
313
|
+
const merged = { ...(query || {}), ...search_query };
|
|
314
|
+
const total = await this.count(merged);
|
|
315
|
+
const list = await this.find_page(merged, sort, page_int, page_limit, attrs);
|
|
316
|
+
const with_links = await this.read_link_attrs(list, link_fields);
|
|
317
|
+
const data = await this.convert_ref_attrs(with_links, ref_fields);
|
|
252
318
|
|
|
253
|
-
|
|
319
|
+
log_debug(LOG_ENTITY, `total:${total},data:${JSON.stringify(data)}`);
|
|
320
|
+
return { code: SUCCESS, total, data };
|
|
254
321
|
}
|
|
255
322
|
|
|
256
323
|
/**
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const fields =
|
|
324
|
+
* Common logic for create/clone entity operations.
|
|
325
|
+
* @private
|
|
326
|
+
*/
|
|
327
|
+
async _save_entity(param_obj, view, options) {
|
|
328
|
+
const { fields_key, before_hook, main_hook, after_hook, id_for_hook } = options;
|
|
329
|
+
|
|
330
|
+
const fields = this.filter_fields_by_view(this.meta[fields_key], view);
|
|
264
331
|
const { obj, error_field_names } = convert_type(param_obj, fields);
|
|
265
332
|
if (error_field_names.length > 0) {
|
|
266
|
-
|
|
267
|
-
log_error(LOG_ENTITY, "error fields:" + JSON.stringify(error_field_names));
|
|
268
|
-
}
|
|
269
|
-
|
|
333
|
+
log_err('invalid fields', { fields: error_field_names });
|
|
270
334
|
return { code: INVALID_PARAMS, err: error_field_names };
|
|
271
335
|
}
|
|
272
336
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
log_error(LOG_ENTITY, "before_create error:" + JSON.stringify(err) + ", with code:" + code);
|
|
278
|
-
}
|
|
279
|
-
return { code: code, err: err };
|
|
280
|
-
}
|
|
281
|
-
}
|
|
337
|
+
// Before hook
|
|
338
|
+
const before_args = id_for_hook ? [id_for_hook, this, obj] : [this, obj];
|
|
339
|
+
const before_err = await run_hook(this.meta[before_hook], before_hook, ...before_args);
|
|
340
|
+
if (before_err) return before_err;
|
|
282
341
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
return { code: NO_PARAMS, err: error_required_field_names };
|
|
342
|
+
// Validate required fields
|
|
343
|
+
const missing = validate_required_fields(obj, this.meta.required_field_names);
|
|
344
|
+
if (missing.length > 0) {
|
|
345
|
+
log_err('missing required fields', { fields: missing });
|
|
346
|
+
return { code: NO_PARAMS, err: missing };
|
|
289
347
|
}
|
|
290
348
|
|
|
291
|
-
|
|
292
|
-
if (
|
|
293
|
-
return { code: DUPLICATE_KEY, err:
|
|
349
|
+
// Check for duplicates
|
|
350
|
+
if (await this.count_by_primary_keys(obj) > 0) {
|
|
351
|
+
return { code: DUPLICATE_KEY, err: 'entity already exist in db' };
|
|
294
352
|
}
|
|
295
353
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (is_log_error()) {
|
|
300
|
-
log_error(LOG_ENTITY, "validate_ref error:" + JSON.stringify(err) + ", with code:" + code);
|
|
301
|
-
}
|
|
302
|
-
return { code: code, err: err };
|
|
303
|
-
}
|
|
304
|
-
}
|
|
354
|
+
// Validate refs
|
|
355
|
+
const ref_err = await validate_refs(this, obj);
|
|
356
|
+
if (ref_err) return ref_err;
|
|
305
357
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
return { code: code, err: err };
|
|
313
|
-
}
|
|
358
|
+
// Main operation
|
|
359
|
+
if (this.meta[main_hook]) {
|
|
360
|
+
const main_args = id_for_hook ? [id_for_hook, this, obj] : [this, obj];
|
|
361
|
+
const main_err = await run_hook(this.meta[main_hook], main_hook, ...main_args);
|
|
362
|
+
if (main_err) return main_err;
|
|
314
363
|
} else {
|
|
315
364
|
const db_obj = await this.create(obj);
|
|
316
|
-
if (!db_obj
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
return { code: ERROR, err: "creating record is failed" };
|
|
365
|
+
if (!db_obj._id) {
|
|
366
|
+
log_err('create failed');
|
|
367
|
+
return { code: ERROR, err: 'creating record is failed' };
|
|
321
368
|
}
|
|
322
369
|
}
|
|
323
370
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
log_error(LOG_ENTITY, "after_create error:" + JSON.stringify(err) + ", with code:" + code);
|
|
329
|
-
}
|
|
330
|
-
return { code: code, err: err };
|
|
331
|
-
}
|
|
332
|
-
}
|
|
371
|
+
// After hook
|
|
372
|
+
const after_args = id_for_hook ? [id_for_hook, this, obj] : [this, obj];
|
|
373
|
+
const after_err = await run_hook(this.meta[after_hook], after_hook, ...after_args);
|
|
374
|
+
if (after_err) return after_err;
|
|
333
375
|
|
|
334
376
|
return { code: SUCCESS };
|
|
335
377
|
}
|
|
336
378
|
|
|
337
379
|
/**
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
return { code: INVALID_PARAMS, err: error_field_names };
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (this.meta.before_clone) {
|
|
354
|
-
const { code, err } = await this.meta.before_clone(_id, this, obj);
|
|
355
|
-
if (err || code != SUCCESS) {
|
|
356
|
-
if (is_log_error()) {
|
|
357
|
-
log_error(LOG_ENTITY, "before_clone error:" + JSON.stringify(err) + ", with code:" + code);
|
|
358
|
-
}
|
|
359
|
-
return { code: code, err: err };
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const error_required_field_names = validate_required_fields(obj, this.meta.required_field_names);
|
|
364
|
-
if (error_required_field_names.length > 0) {
|
|
365
|
-
if (is_log_error()) {
|
|
366
|
-
log_error(LOG_ENTITY, "error required fields:" + JSON.stringify(error_required_field_names));
|
|
367
|
-
}
|
|
368
|
-
return { code: NO_PARAMS, err: error_required_field_names };
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const entity_count = await this.count_by_primary_keys(obj);
|
|
372
|
-
if (entity_count > 0) {
|
|
373
|
-
return { code: DUPLICATE_KEY, err: "entity already exist in db" };
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (this.meta.ref_fields) {
|
|
377
|
-
const { code, err } = await this.validate_ref(obj);
|
|
378
|
-
if (err || code != SUCCESS) {
|
|
379
|
-
if (is_log_error()) {
|
|
380
|
-
log_error(LOG_ENTITY, "validate_ref error:" + JSON.stringify(err) + ", with code:" + code);
|
|
381
|
-
}
|
|
382
|
-
return { code: code, err: err };
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if (this.meta.clone) {
|
|
387
|
-
const { code, err } = await this.meta.clone(_id, this, obj);
|
|
388
|
-
if (err || code != SUCCESS) {
|
|
389
|
-
if (is_log_error()) {
|
|
390
|
-
log_error(LOG_ENTITY, "clone error:" + JSON.stringify(err) + ", with code:" + code);
|
|
391
|
-
}
|
|
392
|
-
return { code: code, err: err };
|
|
393
|
-
}
|
|
394
|
-
} else {
|
|
395
|
-
const db_obj = await this.create(obj);
|
|
396
|
-
if (!db_obj["_id"]) {
|
|
397
|
-
if (is_log_error()) {
|
|
398
|
-
log_error(LOG_ENTITY, "create error:" + JSON.stringify(err) + ", with code:" + code);
|
|
399
|
-
}
|
|
400
|
-
return { code: ERROR, err: "creating record is failed" };
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
if (this.meta.after_clone) {
|
|
405
|
-
const { code, err } = await this.meta.after_clone(_id, this, obj);
|
|
406
|
-
if (err || code != SUCCESS) {
|
|
407
|
-
if (is_log_error()) {
|
|
408
|
-
log_error(LOG_ENTITY, "after_clone error:" + JSON.stringify(err) + ", with code:" + code);
|
|
409
|
-
}
|
|
410
|
-
return { code: code, err: err };
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
return { code: SUCCESS };
|
|
380
|
+
* Create a new entity.
|
|
381
|
+
* @param {Object} param_obj - Entity data
|
|
382
|
+
* @param {string} view - View filter
|
|
383
|
+
* @returns {Object} Result with code
|
|
384
|
+
*/
|
|
385
|
+
async create_entity(param_obj, view) {
|
|
386
|
+
return this._save_entity(param_obj, view, {
|
|
387
|
+
fields_key: 'create_fields',
|
|
388
|
+
before_hook: 'before_create',
|
|
389
|
+
main_hook: 'create',
|
|
390
|
+
after_hook: 'after_create'
|
|
391
|
+
});
|
|
415
392
|
}
|
|
416
393
|
|
|
394
|
+
/**
|
|
395
|
+
* Clone an existing entity.
|
|
396
|
+
* @param {string} _id - Source entity ID
|
|
397
|
+
* @param {Object} param_obj - New entity data
|
|
398
|
+
* @param {string} view - View filter
|
|
399
|
+
* @returns {Object} Result with code
|
|
400
|
+
*/
|
|
401
|
+
async clone_entity(_id, param_obj, view) {
|
|
402
|
+
return this._save_entity(param_obj, view, {
|
|
403
|
+
fields_key: 'clone_fields',
|
|
404
|
+
before_hook: 'before_clone',
|
|
405
|
+
main_hook: 'clone',
|
|
406
|
+
after_hook: 'after_clone',
|
|
407
|
+
id_for_hook: _id
|
|
408
|
+
});
|
|
409
|
+
}
|
|
417
410
|
|
|
418
411
|
/**
|
|
419
|
-
*
|
|
420
|
-
* @param {
|
|
421
|
-
* @param {
|
|
422
|
-
* @param {
|
|
423
|
-
*
|
|
412
|
+
* Update an existing entity.
|
|
413
|
+
* @param {string} _id - Entity ID (null to use primary key)
|
|
414
|
+
* @param {Object} param_obj - Update data
|
|
415
|
+
* @param {string} view - View filter
|
|
416
|
+
* @returns {Object} Result with code
|
|
424
417
|
*/
|
|
425
418
|
async update_entity(_id, param_obj, view) {
|
|
426
|
-
const fields =
|
|
427
|
-
|
|
419
|
+
const fields = this.filter_fields_by_view(this.meta.update_fields, view);
|
|
428
420
|
const { obj, error_field_names } = convert_update_type(param_obj, fields);
|
|
429
421
|
if (error_field_names.length > 0) {
|
|
430
|
-
|
|
431
|
-
log_error(LOG_ENTITY, "update_entity error fields:" + JSON.stringify(error_field_names));
|
|
432
|
-
}
|
|
422
|
+
log_err('update_entity invalid fields', { fields: error_field_names });
|
|
433
423
|
return { code: INVALID_PARAMS, err: error_field_names };
|
|
434
424
|
}
|
|
435
425
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
if (err || code != SUCCESS) {
|
|
439
|
-
if (is_log_error()) {
|
|
440
|
-
log_error(LOG_ENTITY, "before_update error:" + JSON.stringify(err) + ", with code:" + code);
|
|
441
|
-
}
|
|
442
|
-
return { code: code, err: err };
|
|
443
|
-
}
|
|
444
|
-
}
|
|
426
|
+
const before_err = await run_hook(this.meta.before_update, 'before_update', _id, this, obj);
|
|
427
|
+
if (before_err) return before_err;
|
|
445
428
|
|
|
446
429
|
const query = _id ? oid_query(_id) : this.primary_key_query(obj);
|
|
447
|
-
if (query
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}
|
|
451
|
-
return { code: INVALID_PARAMS, err: _id ? ["_id"] : this.meta.primary_keys };
|
|
430
|
+
if (!query) {
|
|
431
|
+
log_err('invalid query', { _id, obj });
|
|
432
|
+
return { code: INVALID_PARAMS, err: _id ? ['_id'] : this.meta.primary_keys };
|
|
452
433
|
}
|
|
453
434
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
log_error(LOG_ENTITY, "update_entity not found with query:" + JSON.stringify(query) + ", and total:" + total);
|
|
458
|
-
}
|
|
459
|
-
return { code: NOT_FOUND, err: _id ? ["_id"] : this.meta.primary_keys };
|
|
435
|
+
if (await this.count(query) !== 1) {
|
|
436
|
+
log_err('entity not found', { query });
|
|
437
|
+
return { code: NOT_FOUND, err: _id ? ['_id'] : this.meta.primary_keys };
|
|
460
438
|
}
|
|
461
439
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
if (err || code != SUCCESS) {
|
|
465
|
-
if (is_log_error()) {
|
|
466
|
-
log_error(LOG_ENTITY, "update_entity validate_ref error:" + JSON.stringify(err) + ", with code:" + code);
|
|
467
|
-
}
|
|
468
|
-
return { code: code, err: err };
|
|
469
|
-
}
|
|
470
|
-
}
|
|
440
|
+
const ref_err = await validate_refs(this, obj);
|
|
441
|
+
if (ref_err) return ref_err;
|
|
471
442
|
|
|
472
443
|
if (this.meta.update) {
|
|
473
|
-
const
|
|
474
|
-
if (
|
|
475
|
-
if (is_log_error()) {
|
|
476
|
-
log_error(LOG_ENTITY, "meta update error:" + JSON.stringify(err) + ", with code:" + code + ",_id:" + _id + ",obj:" + JSON.stringify(obj));
|
|
477
|
-
}
|
|
478
|
-
return { code: code, err: err };
|
|
479
|
-
}
|
|
444
|
+
const update_err = await run_hook(this.meta.update, 'update', _id, this, obj);
|
|
445
|
+
if (update_err) return update_err;
|
|
480
446
|
} else {
|
|
481
447
|
const result = await this.update(query, obj);
|
|
482
|
-
if (result.ok
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
}
|
|
486
|
-
return { code: ERROR, err: "update record is failed" };
|
|
448
|
+
if (result.ok !== 1) {
|
|
449
|
+
log_err('update failed', { query, obj, result });
|
|
450
|
+
return { code: ERROR, err: 'update record is failed' };
|
|
487
451
|
}
|
|
488
452
|
}
|
|
489
453
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
if (err || code != SUCCESS) {
|
|
493
|
-
if (is_log_error()) {
|
|
494
|
-
log_error(LOG_ENTITY, "after_update is failed with _id:" + JSON.stringify(_id) + ",obj:" + JSON.stringify(obj) + ",err:" + JSON.stringify(err) + ",code:" + code);
|
|
495
|
-
}
|
|
496
|
-
return { code: code, err: err };
|
|
497
|
-
}
|
|
498
|
-
}
|
|
454
|
+
const after_err = await run_hook(this.meta.after_update, 'after_update', _id, this, obj);
|
|
455
|
+
if (after_err) return after_err;
|
|
499
456
|
|
|
500
457
|
return { code: SUCCESS };
|
|
501
458
|
}
|
|
502
459
|
|
|
503
460
|
/**
|
|
504
|
-
*
|
|
505
|
-
* @param {
|
|
506
|
-
* @param {
|
|
507
|
-
*
|
|
461
|
+
* Batch update multiple entities.
|
|
462
|
+
* @param {string[]} _ids - Entity IDs
|
|
463
|
+
* @param {Object} param_obj - Update data
|
|
464
|
+
* @param {string} view - View filter
|
|
465
|
+
* @returns {Object} Result with code
|
|
508
466
|
*/
|
|
509
467
|
async batch_update_entity(_ids, param_obj, view) {
|
|
510
|
-
const
|
|
511
|
-
const { obj, error_field_names } = convert_update_type(param_obj,
|
|
468
|
+
const fields = this.filter_fields_by_view(this.meta.update_fields, view);
|
|
469
|
+
const { obj, error_field_names } = convert_update_type(param_obj, fields);
|
|
512
470
|
if (error_field_names.length > 0) {
|
|
513
|
-
|
|
514
|
-
log_error(LOG_ENTITY, "batch_update_entity error fields:" + JSON.stringify(error_field_names));
|
|
515
|
-
}
|
|
471
|
+
log_err('batch_update invalid fields', { fields: error_field_names });
|
|
516
472
|
return { code: INVALID_PARAMS, err: error_field_names };
|
|
517
473
|
}
|
|
518
474
|
|
|
519
475
|
const query = oid_queries(_ids);
|
|
520
|
-
if (query
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
}
|
|
524
|
-
return { code: INVALID_PARAMS, err: ["_ids"] };
|
|
476
|
+
if (!query) {
|
|
477
|
+
log_err('batch_update invalid ids', { _ids });
|
|
478
|
+
return { code: INVALID_PARAMS, err: ['_ids'] };
|
|
525
479
|
}
|
|
526
480
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
if (err || code != SUCCESS) {
|
|
530
|
-
if (is_log_error()) {
|
|
531
|
-
log_error(LOG_ENTITY, "batch_update_entity validate_ref error:" + JSON.stringify(err) + ", with code:" + code);
|
|
532
|
-
}
|
|
533
|
-
return { code: code, err: err };
|
|
534
|
-
}
|
|
535
|
-
}
|
|
481
|
+
const ref_err = await validate_refs(this, obj);
|
|
482
|
+
if (ref_err) return ref_err;
|
|
536
483
|
|
|
537
484
|
if (this.meta.batch_update) {
|
|
538
|
-
const
|
|
539
|
-
if (
|
|
540
|
-
if (is_log_error()) {
|
|
541
|
-
log_error(LOG_ENTITY, "batch_update_entity batch_update error:" + JSON.stringify(err) + ", with code:" + code);
|
|
542
|
-
}
|
|
543
|
-
return { code: code, err: err };
|
|
544
|
-
}
|
|
485
|
+
const batch_err = await run_hook(this.meta.batch_update, 'batch_update', _ids, this, obj);
|
|
486
|
+
if (batch_err) return batch_err;
|
|
545
487
|
} else {
|
|
546
488
|
const result = await this.update(query, obj);
|
|
547
|
-
if (result.ok
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
}
|
|
551
|
-
return { code: ERROR, err: "batch update record is failed" };
|
|
489
|
+
if (result.ok !== 1) {
|
|
490
|
+
log_err('batch update failed', { query, obj, result });
|
|
491
|
+
return { code: ERROR, err: 'batch update record is failed' };
|
|
552
492
|
}
|
|
553
493
|
}
|
|
554
494
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
if (err || code != SUCCESS) {
|
|
558
|
-
if (is_log_error()) {
|
|
559
|
-
log_error(LOG_ENTITY, "after_batch_update error:" + JSON.stringify(err) + ", with code:" + code);
|
|
560
|
-
}
|
|
561
|
-
return { code: code, err: err };
|
|
562
|
-
}
|
|
563
|
-
}
|
|
495
|
+
const after_err = await run_hook(this.meta.after_batch_update, 'after_batch_update', _ids, this, obj);
|
|
496
|
+
if (after_err) return after_err;
|
|
564
497
|
|
|
565
498
|
return { code: SUCCESS };
|
|
566
499
|
}
|
|
567
500
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
501
|
+
/**
|
|
502
|
+
* Filter fields by view.
|
|
503
|
+
* @param {Object[]} fields - Fields to filter
|
|
504
|
+
* @param {string} view - View name
|
|
505
|
+
* @returns {Object[]} Filtered fields
|
|
506
|
+
*/
|
|
507
|
+
filter_fields_by_view(fields, view) {
|
|
508
|
+
if (!view || view === '*') return fields;
|
|
509
|
+
|
|
510
|
+
return fields.filter(field => {
|
|
511
|
+
const fv = field.view;
|
|
512
|
+
if (Array.isArray(fv)) {
|
|
513
|
+
return fv.includes('*') || fv.some(v => view.includes(v));
|
|
572
514
|
}
|
|
573
|
-
|
|
574
|
-
|
|
515
|
+
if (typeof fv === 'string') {
|
|
516
|
+
return fv === '*' || view.includes(fv);
|
|
517
|
+
}
|
|
518
|
+
return true;
|
|
519
|
+
});
|
|
575
520
|
}
|
|
576
521
|
|
|
577
522
|
/**
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
*
|
|
581
|
-
* @param {
|
|
582
|
-
* @
|
|
583
|
-
*
|
|
523
|
+
* Read entity properties without ref conversion.
|
|
524
|
+
* @param {string} _id - Entity ID
|
|
525
|
+
* @param {string} attr_names - Comma-separated attribute names
|
|
526
|
+
* @param {string} view - View filter
|
|
527
|
+
* @returns {Object} Result with code and data
|
|
584
528
|
*/
|
|
585
529
|
async read_property(_id, attr_names, view) {
|
|
586
530
|
const query = oid_query(_id);
|
|
587
|
-
if (query
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
}
|
|
591
|
-
return { code: INVALID_PARAMS, err: ["_id"] };
|
|
531
|
+
if (!query) {
|
|
532
|
+
log_err('read_property invalid id', { _id });
|
|
533
|
+
return { code: INVALID_PARAMS, err: ['_id'] };
|
|
592
534
|
}
|
|
593
535
|
|
|
594
|
-
const property_fields =
|
|
536
|
+
const property_fields = this.filter_fields_by_view(this.meta.property_fields, view);
|
|
595
537
|
const field_names = property_fields.map(f => f.name);
|
|
596
538
|
const attrs = { _id: 1 };
|
|
597
|
-
attr_names.split(
|
|
598
|
-
if (field_names.includes(attr))
|
|
599
|
-
attrs[attr] = 1;
|
|
600
|
-
}
|
|
539
|
+
attr_names.split(',').forEach(attr => {
|
|
540
|
+
if (field_names.includes(attr)) attrs[attr] = 1;
|
|
601
541
|
});
|
|
602
542
|
|
|
603
543
|
const results = await this.find(query, attrs);
|
|
604
|
-
if (results
|
|
605
|
-
|
|
606
|
-
log_debug("read_property with query:" + JSON.stringify(query) + ",attrs:" + JSON.stringify(attrs) + ",result:" + JSON.stringify(results));
|
|
607
|
-
}
|
|
544
|
+
if (results?.length === 1) {
|
|
545
|
+
log_debug(LOG_ENTITY, `read_property query:${JSON.stringify(query)},result:${JSON.stringify(results[0])}`);
|
|
608
546
|
return { code: SUCCESS, data: results[0] };
|
|
609
|
-
} else {
|
|
610
|
-
return { code: NOT_FOUND, err: ["_id"] };
|
|
611
547
|
}
|
|
548
|
+
return { code: NOT_FOUND, err: ['_id'] };
|
|
612
549
|
}
|
|
613
550
|
|
|
614
551
|
/**
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
* @param {
|
|
618
|
-
* @param {
|
|
619
|
-
*
|
|
552
|
+
* Read entity with ref conversion and links.
|
|
553
|
+
* @param {string} _id - Entity ID
|
|
554
|
+
* @param {string} attr_names - Comma-separated attribute names
|
|
555
|
+
* @param {string} view - View filter
|
|
556
|
+
* @returns {Object} Result with code and data
|
|
620
557
|
*/
|
|
621
558
|
async read_entity(_id, attr_names, view) {
|
|
622
559
|
const query = oid_query(_id);
|
|
623
|
-
if (query
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
}
|
|
627
|
-
return { code: INVALID_PARAMS, err: ["_id"] };
|
|
560
|
+
if (!query) {
|
|
561
|
+
log_err('read_entity invalid id', { _id });
|
|
562
|
+
return { code: INVALID_PARAMS, err: ['_id'] };
|
|
628
563
|
}
|
|
629
564
|
|
|
630
565
|
if (!attr_names) {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
}
|
|
634
|
-
return { code: INVALID_PARAMS, err: ["attr_names"] };
|
|
566
|
+
log_err('read_entity invalid attr_names', { attr_names });
|
|
567
|
+
return { code: INVALID_PARAMS, err: ['attr_names'] };
|
|
635
568
|
}
|
|
636
569
|
|
|
637
|
-
const property_fields =
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
const fields_map = this.meta.fields_map;
|
|
645
|
-
attr_names.split(",").forEach((attr) => {
|
|
646
|
-
if (field_names.includes(attr)) {
|
|
647
|
-
attrs[attr] = 1;
|
|
648
|
-
const field = fields_map[attr];
|
|
649
|
-
if (field.link) {
|
|
650
|
-
link_fields.push(field);
|
|
651
|
-
attrs[field.link] = 1;
|
|
652
|
-
} else if (field.ref) {
|
|
653
|
-
ref_fields.push(field);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
});
|
|
570
|
+
const property_fields = this.filter_fields_by_view(this.meta.property_fields, view);
|
|
571
|
+
const { attrs, ref_fields, link_fields } = extract_field_info(
|
|
572
|
+
this.meta.fields_map,
|
|
573
|
+
attr_names,
|
|
574
|
+
property_fields.map(f => f.name)
|
|
575
|
+
);
|
|
657
576
|
|
|
658
577
|
const results = await this.find(query, attrs);
|
|
659
|
-
if (results
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
if (err || code != SUCCESS) {
|
|
663
|
-
if (is_log_error()) {
|
|
664
|
-
log_error(LOG_ENTITY, "after_read error:" + JSON.stringify(err) + ", with code:" + code);
|
|
665
|
-
}
|
|
666
|
-
return { code: code, err: err };
|
|
667
|
-
}
|
|
668
|
-
}
|
|
578
|
+
if (results?.length !== 1) {
|
|
579
|
+
return { code: NOT_FOUND, err: ['_id'] };
|
|
580
|
+
}
|
|
669
581
|
|
|
670
|
-
|
|
671
|
-
|
|
582
|
+
const after_err = await run_hook(this.meta.after_read, 'after_read', _id, this, attr_names, results[0]);
|
|
583
|
+
if (after_err) return after_err;
|
|
672
584
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
log_debug("read_entity with query:" + JSON.stringify(query) + ",attrs:" + JSON.stringify(attrs) + ",converted:" + JSON.stringify(converted));
|
|
676
|
-
}
|
|
677
|
-
return { code: SUCCESS, data: converted[0] };
|
|
678
|
-
}
|
|
679
|
-
}
|
|
585
|
+
const with_links = await this.read_link_attrs(results, link_fields);
|
|
586
|
+
const converted = await this.convert_ref_attrs(with_links, ref_fields);
|
|
680
587
|
|
|
681
|
-
|
|
588
|
+
if (converted?.length === 1) {
|
|
589
|
+
log_debug(LOG_ENTITY, `read_entity query:${JSON.stringify(query)},result:${JSON.stringify(converted[0])}`);
|
|
590
|
+
return { code: SUCCESS, data: converted[0] };
|
|
591
|
+
}
|
|
592
|
+
return { code: NOT_FOUND, err: ['_id'] };
|
|
682
593
|
}
|
|
683
594
|
|
|
684
595
|
/**
|
|
685
|
-
* Delete
|
|
686
|
-
* @param {
|
|
596
|
+
* Delete entities by ID array.
|
|
597
|
+
* @param {string[]} id_array - Entity IDs
|
|
598
|
+
* @returns {Object} Result with code
|
|
687
599
|
*/
|
|
688
600
|
async delete_entity(id_array) {
|
|
689
601
|
const query = oid_queries(id_array);
|
|
690
|
-
if (query
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
}
|
|
694
|
-
return { code: INVALID_PARAMS, err: ["ids"] };
|
|
602
|
+
if (!query) {
|
|
603
|
+
log_err('delete_entity invalid ids', { id_array });
|
|
604
|
+
return { code: INVALID_PARAMS, err: ['ids'] };
|
|
695
605
|
}
|
|
696
606
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
if (err || code != SUCCESS) {
|
|
700
|
-
if (is_log_error()) {
|
|
701
|
-
log_error(LOG_ENTITY, "before_delete error:" + JSON.stringify(err) + ", with code:" + code);
|
|
702
|
-
}
|
|
703
|
-
return { code: code, err: err };
|
|
704
|
-
}
|
|
705
|
-
}
|
|
607
|
+
const before_err = await run_hook(this.meta.before_delete, 'before_delete', this, id_array);
|
|
608
|
+
if (before_err) return before_err;
|
|
706
609
|
|
|
707
610
|
if (this.meta.delete) {
|
|
708
|
-
const
|
|
709
|
-
if (
|
|
710
|
-
if (is_log_error()) {
|
|
711
|
-
log_error(LOG_ENTITY, "delete error:" + JSON.stringify(err) + ", with code:" + code);
|
|
712
|
-
}
|
|
713
|
-
return { code: code, err: err };
|
|
714
|
-
}
|
|
611
|
+
const delete_err = await run_hook(this.meta.delete, 'delete', this, id_array);
|
|
612
|
+
if (delete_err) return delete_err;
|
|
715
613
|
} else {
|
|
716
|
-
//
|
|
717
|
-
const
|
|
718
|
-
if (
|
|
719
|
-
const
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
}
|
|
723
|
-
return { code: HAS_REF, err: array };
|
|
614
|
+
// Check references
|
|
615
|
+
const refs = await this.check_refer_entity(id_array);
|
|
616
|
+
if (refs.length > 0) {
|
|
617
|
+
const unique_refs = [...new Set(refs)];
|
|
618
|
+
log_err('has references', { refs: unique_refs });
|
|
619
|
+
return { code: HAS_REF, err: unique_refs };
|
|
724
620
|
}
|
|
725
621
|
|
|
726
622
|
const result = await this.delete(query);
|
|
727
|
-
if (result.ok
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
if (ref_field.delete == DELETE_MODE.cascade) {
|
|
740
|
-
const refer_by_entity = new Entity(ref_by_meta);
|
|
741
|
-
await refer_by_entity.delete_refer_entity(ref_field.name, id_array)
|
|
623
|
+
if (result.ok !== 1) {
|
|
624
|
+
log_err('delete failed', { query, result });
|
|
625
|
+
return { code: ERROR, err: 'delete record is failed' };
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Cascade delete
|
|
629
|
+
for (const ref_by_meta of this.meta.ref_by_metas) {
|
|
630
|
+
const ref_fields = ref_by_meta.ref_fields.filter(f => f.ref === this.meta.collection);
|
|
631
|
+
for (const field of ref_fields) {
|
|
632
|
+
if (field.delete === DELETE_MODE.cascade) {
|
|
633
|
+
const ref_entity = new Entity(ref_by_meta);
|
|
634
|
+
await ref_entity.delete_refer_entity(field.name, id_array);
|
|
742
635
|
}
|
|
743
636
|
}
|
|
744
637
|
}
|
|
745
638
|
}
|
|
746
639
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
if (err || code != SUCCESS) {
|
|
750
|
-
if (is_log_error()) {
|
|
751
|
-
log_error(LOG_ENTITY, "after_delete error:" + JSON.stringify(err) + ", with code:" + code);
|
|
752
|
-
}
|
|
753
|
-
return { code: code, err: err };
|
|
754
|
-
}
|
|
755
|
-
}
|
|
640
|
+
const after_err = await run_hook(this.meta.after_delete, 'after_delete', this, id_array);
|
|
641
|
+
if (after_err) return after_err;
|
|
756
642
|
|
|
757
643
|
return { code: SUCCESS };
|
|
758
644
|
}
|
|
759
645
|
|
|
760
646
|
/**
|
|
761
|
-
*
|
|
762
|
-
* @param {
|
|
763
|
-
* @returns
|
|
647
|
+
* Build primary key query.
|
|
648
|
+
* @param {Object} param_obj - Parameters
|
|
649
|
+
* @returns {Object|null} Query object or null
|
|
764
650
|
*/
|
|
765
651
|
primary_key_query(param_obj) {
|
|
766
|
-
|
|
767
|
-
if (params === null) {
|
|
768
|
-
return null;
|
|
769
|
-
}
|
|
652
|
+
if (!required_params(param_obj, this.meta.primary_keys)) return null;
|
|
770
653
|
|
|
771
654
|
const { obj, error_field_names } = convert_type(param_obj, this.meta.primary_key_fields);
|
|
772
|
-
if (error_field_names.length > 0)
|
|
773
|
-
return null;
|
|
774
|
-
}
|
|
655
|
+
if (error_field_names.length > 0) return null;
|
|
775
656
|
|
|
776
|
-
|
|
777
|
-
this.meta.primary_keys.forEach(function (key) {
|
|
778
|
-
query[key] = obj[key];
|
|
779
|
-
});
|
|
780
|
-
return query;
|
|
657
|
+
return this.meta.primary_keys.reduce((q, key) => ({ ...q, [key]: obj[key] }), {});
|
|
781
658
|
}
|
|
782
659
|
|
|
783
|
-
/**
|
|
784
|
-
* Get the count value by primary key
|
|
785
|
-
* @param {object used to create query} obj
|
|
786
|
-
* @returns the count value by primary key
|
|
787
|
-
*/
|
|
660
|
+
/** Count by primary key. */
|
|
788
661
|
count_by_primary_keys(obj) {
|
|
789
662
|
return this.count(this.primary_key_query(obj));
|
|
790
663
|
}
|
|
791
664
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
* @returns
|
|
806
|
-
*/
|
|
807
|
-
update(query, obj) {
|
|
808
|
-
return this.db.update(this.meta.collection, query, obj);
|
|
809
|
-
}
|
|
665
|
+
// Database operations (delegate to db)
|
|
666
|
+
create(obj) { return this.db.create(this.meta.collection, obj); }
|
|
667
|
+
update(query, obj) { return this.db.update(this.meta.collection, query, obj); }
|
|
668
|
+
delete(query) { return this.db.delete(this.meta.collection, query); }
|
|
669
|
+
find(query, attr) { return this.db.find(this.meta.collection, query, attr); }
|
|
670
|
+
find_one(query, attr) { return this.db.find_one(this.meta.collection, query, attr); }
|
|
671
|
+
find_sort(query, sort, attr) { return this.db.find_sort(this.meta.collection, query, sort, attr); }
|
|
672
|
+
find_page(query, sort, page, limit, attr) { return this.db.find_page(this.meta.collection, query, sort, page, limit, attr); }
|
|
673
|
+
count(query) { return this.db.count(this.meta.collection, query); }
|
|
674
|
+
sum(query, field) { return this.db.sum(this.meta.collection, query, field); }
|
|
675
|
+
pull(query, ele) { return this.db.pull(this.meta.collection, query, ele); }
|
|
676
|
+
push(query, ele) { return this.db.push(this.meta.collection, query, ele); }
|
|
677
|
+
add_to_set(query, ele) { return this.db.add_to_set(this.meta.collection, query, ele); }
|
|
810
678
|
|
|
811
679
|
/**
|
|
812
|
-
*
|
|
813
|
-
* @param {
|
|
814
|
-
* @returns
|
|
815
|
-
*/
|
|
816
|
-
delete(query) {
|
|
817
|
-
return this.db.delete(this.meta.collection, query);
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
/**
|
|
821
|
-
* Remove the records from mongodb
|
|
822
|
-
* @param {*} id can be array or string
|
|
823
|
-
* @returns
|
|
680
|
+
* Delete by ID (single or array).
|
|
681
|
+
* @param {string|string[]} id - ID or array of IDs
|
|
824
682
|
*/
|
|
825
683
|
delete_by_id(id) {
|
|
826
684
|
const query = Array.isArray(id) ? oid_queries(id) : oid_query(id);
|
|
@@ -828,242 +686,182 @@ class Entity {
|
|
|
828
686
|
}
|
|
829
687
|
|
|
830
688
|
/**
|
|
831
|
-
* Find
|
|
832
|
-
* @param {
|
|
833
|
-
* @param {
|
|
834
|
-
* @
|
|
689
|
+
* Find by ref value (objectid or ref_label).
|
|
690
|
+
* @param {*} value - Ref value
|
|
691
|
+
* @param {Object} attr - Attributes to load
|
|
692
|
+
* @param {string} ref_by_entity - Referring entity
|
|
693
|
+
* @returns {Promise<Object[]>} Found entities
|
|
835
694
|
*/
|
|
836
695
|
find_by_ref_value(value, attr, ref_by_entity) {
|
|
837
696
|
let query = Array.isArray(value) ? oid_queries(value) : oid_query(value);
|
|
838
|
-
|
|
697
|
+
|
|
698
|
+
if (!query) {
|
|
699
|
+
const ref_label = this.meta.ref_label;
|
|
839
700
|
if (Array.isArray(value)) {
|
|
840
|
-
query = { [
|
|
701
|
+
query = { [ref_label]: { $in: value } };
|
|
702
|
+
} else if (value.includes(',')) {
|
|
703
|
+
query = { [ref_label]: { $in: value.split(',') } };
|
|
841
704
|
} else {
|
|
842
|
-
|
|
843
|
-
const values = value.split(",");
|
|
844
|
-
query = { [this.meta.ref_label]: { "$in": values } };
|
|
845
|
-
} else {
|
|
846
|
-
query = { [this.meta.ref_label]: value };
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
if (this.meta.ref_filter && ref_by_entity) {
|
|
852
|
-
if (this.meta.ref_filter[ref_by_entity]) {
|
|
853
|
-
query = { ...query, ...this.meta.ref_filter[ref_by_entity] };
|
|
854
|
-
} else if (this.meta.ref_filter["*"]) {
|
|
855
|
-
query = { ...query, ...this.meta.ref_filter["*"] };
|
|
705
|
+
query = { [ref_label]: value };
|
|
856
706
|
}
|
|
857
707
|
}
|
|
858
708
|
|
|
859
|
-
return this.find(query, attr);
|
|
709
|
+
return this.find(apply_ref_filter(query, this.meta.ref_filter, ref_by_entity), attr);
|
|
860
710
|
}
|
|
861
711
|
|
|
862
712
|
/**
|
|
863
|
-
*
|
|
864
|
-
* @param {
|
|
865
|
-
* @param {
|
|
866
|
-
* @param {
|
|
867
|
-
* @returns
|
|
713
|
+
* Find one ref entity by field name and value.
|
|
714
|
+
* @param {string} field_name - Field name
|
|
715
|
+
* @param {*} value - Value
|
|
716
|
+
* @param {Object} attr - Attributes to load
|
|
717
|
+
* @returns {Promise<Object>} Found entity
|
|
868
718
|
*/
|
|
869
719
|
find_one_ref_entity(field_name, value, attr) {
|
|
870
|
-
const
|
|
871
|
-
if (
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
let query = oid_query(value);
|
|
877
|
-
if (query == null) {
|
|
878
|
-
query = { [ref_meta.ref_label]: value };
|
|
879
|
-
}
|
|
880
|
-
return ref_entity.find_one(query, attr);
|
|
881
|
-
|
|
882
|
-
} else {
|
|
883
|
-
throw new Error("the field:" + field_name + " is not ref field,in entity:" + this.meta.collection);
|
|
884
|
-
}
|
|
885
|
-
} else {
|
|
886
|
-
throw new Error("not found the field by name:" + field_name + ",in entity:" + this.meta.collection);
|
|
720
|
+
const field = this.meta.fields.find(f => f.name === field_name);
|
|
721
|
+
if (!field) {
|
|
722
|
+
throw new Error(`field not found: ${field_name} in ${this.meta.collection}`);
|
|
723
|
+
}
|
|
724
|
+
if (!field.ref) {
|
|
725
|
+
throw new Error(`field is not ref: ${field_name} in ${this.meta.collection}`);
|
|
887
726
|
}
|
|
727
|
+
|
|
728
|
+
const ref_meta = get_entity_meta(field.ref);
|
|
729
|
+
const ref_entity = new Entity(ref_meta);
|
|
730
|
+
const query = oid_query(value) || { [ref_meta.ref_label]: value };
|
|
731
|
+
return ref_entity.find_one(query, attr);
|
|
888
732
|
}
|
|
889
733
|
|
|
890
734
|
/**
|
|
891
|
-
*
|
|
892
|
-
* @param {
|
|
893
|
-
* @returns
|
|
735
|
+
* Check for referring entities.
|
|
736
|
+
* @param {string[]} id_array - Entity IDs
|
|
737
|
+
* @returns {Promise<string[]>} Reference descriptions
|
|
894
738
|
*/
|
|
895
739
|
async check_refer_entity(id_array) {
|
|
896
|
-
const
|
|
897
|
-
for (let i = 0; i < this.meta.ref_by_metas.length; i++) {
|
|
898
|
-
const ref_by_meta = this.meta.ref_by_metas[i];
|
|
899
|
-
const refer_by_entity = new Entity(ref_by_meta);
|
|
900
|
-
const ref_fields = ref_by_meta.ref_fields.filter(field => field.ref == this.meta.collection);
|
|
901
|
-
|
|
902
|
-
for (let j = 0; j < ref_fields.length; j++) {
|
|
903
|
-
const ref_field = ref_fields[j];
|
|
904
|
-
if (ref_field.delete != DELETE_MODE.keep) {
|
|
905
|
-
const attr = {};
|
|
906
|
-
if (ref_by_meta.ref_label) {
|
|
907
|
-
attr[ref_by_meta.ref_label] = 1;
|
|
908
|
-
}
|
|
740
|
+
const refs = [];
|
|
909
741
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
742
|
+
for (const ref_by_meta of this.meta.ref_by_metas) {
|
|
743
|
+
const ref_entity = new Entity(ref_by_meta);
|
|
744
|
+
const ref_fields = ref_by_meta.ref_fields.filter(f => f.ref === this.meta.collection);
|
|
745
|
+
|
|
746
|
+
for (const field of ref_fields) {
|
|
747
|
+
if (field.delete === DELETE_MODE.keep) continue;
|
|
748
|
+
|
|
749
|
+
const attr = ref_by_meta.ref_label ? { [ref_by_meta.ref_label]: 1 } : {};
|
|
750
|
+
const entities = await ref_entity.get_refer_entities(field.name, id_array, attr);
|
|
751
|
+
|
|
752
|
+
if (!entities?.length) continue;
|
|
753
|
+
|
|
754
|
+
if (field.delete === DELETE_MODE.cascade) {
|
|
755
|
+
const cascade_refs = await ref_entity.check_refer_entity(entities.map(o => `${o._id}`));
|
|
756
|
+
if (cascade_refs?.length) refs.push(...cascade_refs);
|
|
757
|
+
} else {
|
|
758
|
+
const label_key = ref_by_meta.ref_label || '_id';
|
|
759
|
+
refs.push(...entities.map(o => `${this.meta.collection}<-${ref_by_meta.collection}:${o[label_key]}`));
|
|
925
760
|
}
|
|
926
761
|
}
|
|
927
762
|
}
|
|
928
|
-
return
|
|
763
|
+
return refs;
|
|
929
764
|
}
|
|
930
765
|
|
|
931
|
-
/**
|
|
932
|
-
* check whether this entity has refered the entity_id value
|
|
933
|
-
* @param {entity in this field name} field_name
|
|
934
|
-
* @param {entity object id array} id_array
|
|
935
|
-
* @returns true if has refered
|
|
936
|
-
*/
|
|
766
|
+
/** Get entities referring to given IDs. */
|
|
937
767
|
async get_refer_entities(field_name, id_array, attr) {
|
|
938
|
-
|
|
939
|
-
return await this.find(query, attr);
|
|
768
|
+
return this.find({ [field_name]: { $in: id_array } }, attr);
|
|
940
769
|
}
|
|
941
770
|
|
|
942
|
-
/**
|
|
943
|
-
* delete refer entities
|
|
944
|
-
* @param {entity in this field name} field_name
|
|
945
|
-
* @param {entity object id array} id_array
|
|
946
|
-
*/
|
|
771
|
+
/** Delete entities referring to given IDs. */
|
|
947
772
|
async delete_refer_entity(field_name, id_array) {
|
|
948
773
|
const entities = await this.get_refer_entities(field_name, id_array, {});
|
|
949
|
-
await this.delete_entity(entities.map(o => o._id
|
|
774
|
+
await this.delete_entity(entities.map(o => `${o._id}`));
|
|
950
775
|
}
|
|
951
776
|
|
|
952
777
|
/**
|
|
953
|
-
* Convert ref
|
|
954
|
-
* @param {
|
|
955
|
-
* @param {
|
|
956
|
-
* @returns
|
|
778
|
+
* Convert ref objectids to ref_labels.
|
|
779
|
+
* @param {Object[]} elements - Elements to convert
|
|
780
|
+
* @param {Object[]} ref_fields - Ref fields configuration
|
|
781
|
+
* @returns {Promise<Object[]>} Converted elements
|
|
957
782
|
*/
|
|
958
|
-
async convert_ref_attrs(elements,
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
const
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
if (Array.isArray(value)) {
|
|
986
|
-
obj[ref_field.name] = value.map(v => label_map_obj[v]);
|
|
987
|
-
} else if (value) {
|
|
988
|
-
obj[ref_field.name] = label_map_obj[value];
|
|
989
|
-
}
|
|
990
|
-
}
|
|
783
|
+
async convert_ref_attrs(elements, ref_fields = this.meta.ref_fields) {
|
|
784
|
+
if (!elements?.length || !ref_fields?.length) return elements;
|
|
785
|
+
|
|
786
|
+
for (const field of ref_fields) {
|
|
787
|
+
// Collect all IDs
|
|
788
|
+
let ids = [];
|
|
789
|
+
for (const obj of elements) {
|
|
790
|
+
const value = obj[field.name];
|
|
791
|
+
if (Array.isArray(value)) ids.push(...value);
|
|
792
|
+
else if (value) ids.push(value);
|
|
793
|
+
}
|
|
794
|
+
ids = unique(ids);
|
|
795
|
+
|
|
796
|
+
// Get labels
|
|
797
|
+
const ref_meta = get_entity_meta(field.ref);
|
|
798
|
+
const ref_entity = new Entity(ref_meta);
|
|
799
|
+
const labels = await ref_entity.get_ref_labels(ids);
|
|
800
|
+
const label_map = map_array_to_obj(labels, '_id', ref_meta.ref_label);
|
|
801
|
+
|
|
802
|
+
// Apply labels
|
|
803
|
+
for (const obj of elements) {
|
|
804
|
+
const value = obj[field.name];
|
|
805
|
+
obj[`${field.name}_id`] = value;
|
|
806
|
+
obj[field.name] = Array.isArray(value)
|
|
807
|
+
? value.map(v => label_map[v])
|
|
808
|
+
: (value ? label_map[value] : value);
|
|
991
809
|
}
|
|
992
810
|
}
|
|
993
811
|
return elements;
|
|
994
812
|
}
|
|
995
813
|
|
|
996
814
|
/**
|
|
997
|
-
*
|
|
998
|
-
* @param {
|
|
999
|
-
* @
|
|
815
|
+
* Read link attributes.
|
|
816
|
+
* @param {Object[]} elements - Elements to process
|
|
817
|
+
* @param {Object[]} link_fields - Link field configurations
|
|
818
|
+
* @returns {Promise<Object[]>} Elements with link data
|
|
1000
819
|
*/
|
|
1001
820
|
async read_link_attrs(elements, link_fields) {
|
|
1002
|
-
if (elements
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
const
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
const
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
if (!field.link && field.ref) {
|
|
1047
|
-
ref_fields.push(field);
|
|
1048
|
-
}
|
|
1049
|
-
});
|
|
1050
|
-
|
|
1051
|
-
const ref_entity_items = await entity.find(query, attrs);
|
|
1052
|
-
if (ref_entity_items && ref_entity_items.length > 0) {
|
|
1053
|
-
await entity.convert_ref_attrs(ref_entity_items, ref_fields);
|
|
1054
|
-
|
|
1055
|
-
for (let j = 0; j < elements.length; j++) {
|
|
1056
|
-
const obj = elements[j];
|
|
1057
|
-
const linked_attrs = entity_filter_map[entities[i]];
|
|
1058
|
-
for (let k = 0; k < linked_attrs.length; k++) {
|
|
1059
|
-
const id = obj[linked_attrs[k]];
|
|
1060
|
-
const [link_obj] = ref_entity_items.filter(o => o._id + "" == id);
|
|
1061
|
-
if (link_obj) {
|
|
1062
|
-
const copy_obj = { ...link_obj };
|
|
1063
|
-
delete copy_obj["_id"];
|
|
1064
|
-
elements[j] = { ...obj, ...copy_obj };
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
821
|
+
if (!elements?.length || !link_fields?.length) return elements;
|
|
822
|
+
|
|
823
|
+
// Group by linked entity
|
|
824
|
+
const entity_info = link_fields.reduce((acc, field) => {
|
|
825
|
+
const link_field = this.meta.fields_map[field.link];
|
|
826
|
+
const entity = link_field.ref;
|
|
827
|
+
if (!acc[entity]) acc[entity] = { attrs: [], filters: [] };
|
|
828
|
+
acc[entity].attrs.push(field.name);
|
|
829
|
+
if (!acc[entity].filters.includes(link_field.name)) {
|
|
830
|
+
acc[entity].filters.push(link_field.name);
|
|
831
|
+
}
|
|
832
|
+
return acc;
|
|
833
|
+
}, {});
|
|
834
|
+
|
|
835
|
+
for (const [entity_name, { attrs, filters }] of Object.entries(entity_info)) {
|
|
836
|
+
const meta = get_entity_meta(entity_name);
|
|
837
|
+
const entity = new Entity(meta);
|
|
838
|
+
|
|
839
|
+
// Collect IDs
|
|
840
|
+
const ids = unique(elements.flatMap(o => filters.map(f => o[f])).filter(Boolean));
|
|
841
|
+
const query = oid_queries(ids);
|
|
842
|
+
|
|
843
|
+
// Build attrs and ref_fields
|
|
844
|
+
const attr_obj = {};
|
|
845
|
+
const ref_fields = [];
|
|
846
|
+
for (const attr of attrs) {
|
|
847
|
+
attr_obj[attr] = 1;
|
|
848
|
+
const field = meta.fields_map[attr];
|
|
849
|
+
if (!field.link && field.ref) ref_fields.push(field);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const items = await entity.find(query, attr_obj);
|
|
853
|
+
if (!items?.length) continue;
|
|
854
|
+
|
|
855
|
+
await entity.convert_ref_attrs(items, ref_fields);
|
|
856
|
+
|
|
857
|
+
// Merge link data
|
|
858
|
+
for (let i = 0; i < elements.length; i++) {
|
|
859
|
+
for (const filter of filters) {
|
|
860
|
+
const id = elements[i][filter];
|
|
861
|
+
const link_item = items.find(o => `${o._id}` === `${id}`);
|
|
862
|
+
if (link_item) {
|
|
863
|
+
const { _id, ...data } = link_item;
|
|
864
|
+
elements[i] = { ...elements[i], ...data };
|
|
1067
865
|
}
|
|
1068
866
|
}
|
|
1069
867
|
}
|
|
@@ -1072,181 +870,51 @@ class Entity {
|
|
|
1072
870
|
}
|
|
1073
871
|
|
|
1074
872
|
/**
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
873
|
+
* Get ref labels with filter.
|
|
874
|
+
* @param {string} ref_by_entity - Referring entity
|
|
875
|
+
* @param {string} client_query - Client query string
|
|
876
|
+
* @returns {Promise<Object[]>} Filtered labels
|
|
877
|
+
*/
|
|
1078
878
|
get_filtered_ref_labels(ref_by_entity, client_query) {
|
|
1079
879
|
let query = {};
|
|
880
|
+
|
|
1080
881
|
if (this.meta.user_field) {
|
|
1081
|
-
query[this.meta.user_field] =
|
|
882
|
+
query[this.meta.user_field] = get_session_user_id();
|
|
1082
883
|
}
|
|
1083
884
|
|
|
1084
|
-
|
|
885
|
+
// Parse client query
|
|
1085
886
|
let search_query = {};
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
search_field && (search_query = parse_search_value(field_name, search_field.type, query_values[1]))
|
|
887
|
+
if (client_query?.trim() && this.meta.search_fields?.length) {
|
|
888
|
+
for (const part of client_query.split(',')) {
|
|
889
|
+
const [field_name, value] = part.split(':');
|
|
890
|
+
if (field_name && value) {
|
|
891
|
+
const field = this.meta.search_fields.find(f => f.name === field_name);
|
|
892
|
+
if (field) {
|
|
893
|
+
search_query = parse_search_value(field_name, field.type, value);
|
|
894
|
+
}
|
|
1095
895
|
}
|
|
1096
896
|
}
|
|
1097
897
|
}
|
|
1098
898
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
} else if (this.meta.ref_filter["*"]) {
|
|
1103
|
-
query = { ...query, ...this.meta.ref_filter["*"] };
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
return this.find_sort({ ...search_query, ...query }, { [this.meta.ref_label]: 1 }, { [this.meta.ref_label]: 1 });
|
|
899
|
+
query = apply_ref_filter(query, this.meta.ref_filter, ref_by_entity);
|
|
900
|
+
const ref_label = this.meta.ref_label;
|
|
901
|
+
return this.find_sort({ ...search_query, ...query }, { [ref_label]: 1 }, { [ref_label]: 1 });
|
|
1107
902
|
}
|
|
1108
903
|
|
|
1109
|
-
/**
|
|
1110
|
-
* get ref labels of the object
|
|
1111
|
-
* @param {id array of objectid} id_array
|
|
1112
|
-
* @returns
|
|
1113
|
-
*/
|
|
904
|
+
/** Get ref labels by ID array. */
|
|
1114
905
|
get_ref_labels(id_array) {
|
|
1115
|
-
|
|
1116
|
-
return this.find(query, { [this.meta.ref_label]: 1 });
|
|
906
|
+
return this.find(oid_queries(id_array), { [this.meta.ref_label]: 1 });
|
|
1117
907
|
}
|
|
1118
908
|
|
|
1119
|
-
/**
|
|
1120
|
-
* get entity by object id
|
|
1121
|
-
* @param {object id} id
|
|
1122
|
-
* @param {the attributes to load from db} attr
|
|
1123
|
-
*/
|
|
909
|
+
/** Find by objectid. */
|
|
1124
910
|
find_by_oid(id, attr) {
|
|
1125
911
|
const query = oid_query(id);
|
|
1126
|
-
|
|
1127
|
-
return null;
|
|
1128
|
-
}
|
|
1129
|
-
return this.find_one(query, attr);
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
/**
|
|
1133
|
-
* Search the db using query
|
|
1134
|
-
* @param {search criteria} query
|
|
1135
|
-
* @param {the attributes to load from db} attr
|
|
1136
|
-
* @returns
|
|
1137
|
-
*/
|
|
1138
|
-
find(query, attr) {
|
|
1139
|
-
return this.db.find(this.meta.collection, query, attr);
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
/**
|
|
1143
|
-
* Find one record from db
|
|
1144
|
-
* @param {search criteria} query
|
|
1145
|
-
* @param {the attributes to load from db} attr
|
|
1146
|
-
* @returns
|
|
1147
|
-
*/
|
|
1148
|
-
find_one(query, attr) {
|
|
1149
|
-
return this.db.find_one(this.meta.collection, query, attr);
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
/**
|
|
1153
|
-
* Find the records from db using sort to do sorting
|
|
1154
|
-
* @param {search criteria} query
|
|
1155
|
-
* @param {sort object to sort the result} sort
|
|
1156
|
-
* @param {the attributes of the object to load from db} attr
|
|
1157
|
-
* @returns
|
|
1158
|
-
*/
|
|
1159
|
-
find_sort(query, sort, attr) {
|
|
1160
|
-
return this.db.find_sort(this.meta.collection, query, sort, attr);
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
/**
|
|
1164
|
-
* Find the page records
|
|
1165
|
-
* @param {search criteria} query
|
|
1166
|
-
* @param {sort object to sort the results} sort
|
|
1167
|
-
* @param {the page index to load} page
|
|
1168
|
-
* @param {page size } limit
|
|
1169
|
-
* @param {the attributes of the object to load from db} attr
|
|
1170
|
-
* @returns
|
|
1171
|
-
*/
|
|
1172
|
-
find_page(query, sort, page, limit, attr) {
|
|
1173
|
-
return this.db.find_page(this.meta.collection, query, sort, page, limit, attr);
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
/**
|
|
1177
|
-
* The count number of the query
|
|
1178
|
-
* @param {search criteria} query
|
|
1179
|
-
* @returns
|
|
1180
|
-
*/
|
|
1181
|
-
count(query) {
|
|
1182
|
-
return this.db.count(this.meta.collection, query);
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
/**
|
|
1186
|
-
* Calculate the sum value based on the field and query criteria
|
|
1187
|
-
* @param {search criteria} query
|
|
1188
|
-
* @param {field name to calculate sum} field
|
|
1189
|
-
* @returns
|
|
1190
|
-
*/
|
|
1191
|
-
sum(query, field) {
|
|
1192
|
-
return this.db.sum(this.meta.collection, query, field);
|
|
912
|
+
return query ? this.find_one(query, attr) : null;
|
|
1193
913
|
}
|
|
1194
914
|
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
* @param {object pulled from the array} ele
|
|
1199
|
-
* @returns
|
|
1200
|
-
*/
|
|
1201
|
-
pull(query, ele) {
|
|
1202
|
-
return this.db.pull(this.meta.collection, query, ele);
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
/**
|
|
1206
|
-
* Push the object to array
|
|
1207
|
-
* @param {search criteria} query
|
|
1208
|
-
* @param {object push to the array} ele
|
|
1209
|
-
* @returns
|
|
1210
|
-
*/
|
|
1211
|
-
push(query, ele) {
|
|
1212
|
-
return this.db.push(this.meta.collection, query, ele);
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
/**
|
|
1216
|
-
* add the object to set
|
|
1217
|
-
* @param {search criteria} query
|
|
1218
|
-
* @param {object added to the set} ele
|
|
1219
|
-
* @returns
|
|
1220
|
-
*/
|
|
1221
|
-
add_to_set(query, ele) {
|
|
1222
|
-
return this.db.add_to_set(this.meta.collection, query, ele);
|
|
1223
|
-
};
|
|
1224
|
-
|
|
1225
|
-
/**
|
|
1226
|
-
* Get the mongodb collection with this entity
|
|
1227
|
-
* @returns
|
|
1228
|
-
*/
|
|
1229
|
-
col() {
|
|
1230
|
-
return this.db.col(this.meta.collection);
|
|
1231
|
-
};
|
|
1232
|
-
|
|
1233
|
-
/**
|
|
1234
|
-
* Add oid query to entity
|
|
1235
|
-
* @param {object id} _id
|
|
1236
|
-
* @returns
|
|
1237
|
-
*/
|
|
1238
|
-
oid_query(_id) {
|
|
1239
|
-
return oid_query(_id);
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
/**
|
|
1243
|
-
* Object ID array
|
|
1244
|
-
* @param {object id array} _ids
|
|
1245
|
-
* @returns
|
|
1246
|
-
*/
|
|
1247
|
-
oid_queries(_ids) {
|
|
1248
|
-
return oid_queries(_ids);
|
|
1249
|
-
}
|
|
915
|
+
// Proxy methods for oid utilities
|
|
916
|
+
oid_query(_id) { return oid_query(_id); }
|
|
917
|
+
oid_queries(_ids) { return oid_queries(_ids); }
|
|
1250
918
|
}
|
|
1251
919
|
|
|
1252
920
|
module.exports = { Entity };
|