@steedos/service-rest 2.6.10-beta.9 → 2.7.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/package.service.js +145 -7
- package/translate.js +459 -0
- package/utils.js +150 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steedos/service-rest",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0-beta.10",
|
|
4
4
|
"main": "package.service.js",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"repository": {},
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@steedos/objectql": "2.
|
|
20
|
-
"@steedos/service-object-mixin": "2.
|
|
19
|
+
"@steedos/objectql": "2.7.0-beta.10",
|
|
20
|
+
"@steedos/service-object-mixin": "2.7.0-beta.10",
|
|
21
21
|
"lodash": "^4.17.21"
|
|
22
22
|
},
|
|
23
|
-
"gitHead": "
|
|
23
|
+
"gitHead": "78659952139c455e4fdd3d581c0d242219e033d0"
|
|
24
24
|
}
|
package/package.service.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* @Author: sunhaolin@hotoa.com
|
|
3
3
|
* @Date: 2023-03-23 15:12:14
|
|
4
|
-
* @LastEditors:
|
|
5
|
-
* @LastEditTime:
|
|
4
|
+
* @LastEditors: 殷亮辉 yinlianghui@hotoa.com
|
|
5
|
+
* @LastEditTime: 2024-03-14 10:39:22
|
|
6
6
|
* @Description:
|
|
7
7
|
*/
|
|
8
8
|
"use strict";
|
|
9
9
|
// @ts-check
|
|
10
10
|
const serviceObjectMixin = require('@steedos/service-object-mixin');
|
|
11
11
|
const { QUERY_DOCS_TOP, REQUEST_SUCCESS_STATUS } = require('./consts')
|
|
12
|
+
const { translateRecords } = require('./translate');
|
|
12
13
|
const _ = require('lodash')
|
|
13
14
|
const { getObject } = require('@steedos/objectql');
|
|
14
15
|
|
|
@@ -53,6 +54,8 @@ module.exports = {
|
|
|
53
54
|
* @apiGroup @steedos/service-rest
|
|
54
55
|
* @apiParam {String} objectName 对象API Name,如:contracts
|
|
55
56
|
* @apiQuery {String} [fields] 字段名,如:'["name", "description"]'
|
|
57
|
+
* @apiQuery {String} [uiFields] 字段名,如:'["owner", "date"]',此参数中的字段要求在参数fields中存在
|
|
58
|
+
* @apiQuery {String} [expandFields] 字段名,如:'{owner: {fields: ["name"], uiFields: ["owner"], expandFields: ...}}'
|
|
56
59
|
* @apiQuery {String} [filters] 过滤条件,如:'[["name", "=", "test"],["amount", ">", 100]]'
|
|
57
60
|
* @apiQuery {String} [top] 获取条数,如:'10',最多5000
|
|
58
61
|
* @apiQuery {String} [skip] 跳过条数,如:'10'
|
|
@@ -90,22 +93,51 @@ module.exports = {
|
|
|
90
93
|
params: {
|
|
91
94
|
objectName: { type: "string" },
|
|
92
95
|
fields: { type: 'string', optional: true },
|
|
96
|
+
uiFields: { type: 'string', optional: true },
|
|
97
|
+
expandFields: { type: 'string', optional: true },
|
|
93
98
|
filters: { type: 'string', optional: true },
|
|
94
99
|
top: { type: 'string', optional: true, default: QUERY_DOCS_TOP },
|
|
95
100
|
skip: { type: 'string', optional: true },
|
|
96
101
|
sort: { type: 'string', optional: true }
|
|
97
102
|
},
|
|
98
103
|
async handler(ctx) {
|
|
104
|
+
if (process.env.STEEDOS_DEBUG) {
|
|
105
|
+
console.time('open api find total time');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (process.env.STEEDOS_DEBUG) {
|
|
109
|
+
console.time('open api find before find');
|
|
110
|
+
}
|
|
99
111
|
const params = ctx.params
|
|
100
|
-
const { objectName,
|
|
112
|
+
const { objectName, filters, top, skip, sort } = params
|
|
101
113
|
const userSession = ctx.meta.user;
|
|
102
114
|
|
|
115
|
+
let fields = [];
|
|
116
|
+
if(params.fields){
|
|
117
|
+
fields = JSON.parse(params.fields)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let uiFields = [];
|
|
121
|
+
if(params.uiFields){
|
|
122
|
+
uiFields = JSON.parse(params.uiFields)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let expandFields;
|
|
126
|
+
if(params.expandFields){
|
|
127
|
+
expandFields = JSON.parse(params.expandFields)
|
|
128
|
+
}
|
|
129
|
+
|
|
103
130
|
const query = {}
|
|
104
131
|
if (filters) {
|
|
105
132
|
query.filters = JSON.parse(filters)
|
|
106
133
|
}
|
|
107
134
|
if (fields) {
|
|
108
|
-
|
|
135
|
+
let queryFields = fields;
|
|
136
|
+
if(expandFields){
|
|
137
|
+
// 跟GraphQL第一层一样,从库里查的字段要补上expandFields中的字段,即uiFields中依赖的字段可以在fields中定义,也可以在expandFields中字义
|
|
138
|
+
queryFields = _.union(queryFields, _.keys(expandFields));
|
|
139
|
+
}
|
|
140
|
+
query.fields = queryFields;
|
|
109
141
|
}
|
|
110
142
|
if (top) {
|
|
111
143
|
query.top = Number(top)
|
|
@@ -126,22 +158,128 @@ module.exports = {
|
|
|
126
158
|
}
|
|
127
159
|
}
|
|
128
160
|
|
|
129
|
-
|
|
161
|
+
if (process.env.STEEDOS_DEBUG) {
|
|
162
|
+
console.timeEnd('open api find before find');
|
|
163
|
+
}
|
|
164
|
+
|
|
130
165
|
const countQuery = {
|
|
131
166
|
filters: query.filters
|
|
132
167
|
}
|
|
133
|
-
|
|
168
|
+
|
|
169
|
+
if (process.env.STEEDOS_DEBUG) {
|
|
170
|
+
console.time('open api find find record and count');
|
|
171
|
+
}
|
|
172
|
+
const [records, totalCount] = await Promise.all([
|
|
173
|
+
await this.find(objectName, query, userSession),
|
|
174
|
+
await this.count(objectName, countQuery, userSession)
|
|
175
|
+
])
|
|
176
|
+
if (process.env.STEEDOS_DEBUG) {
|
|
177
|
+
console.timeEnd('open api find find record and count');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
if (process.env.STEEDOS_DEBUG) {
|
|
182
|
+
console.time('open api find translateRecords');
|
|
183
|
+
}
|
|
184
|
+
const translatedRecords = await translateRecords(records, objectName, fields, uiFields, expandFields, userSession);
|
|
185
|
+
if (process.env.STEEDOS_DEBUG) {
|
|
186
|
+
console.timeEnd('open api find translateRecords');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (process.env.STEEDOS_DEBUG) {
|
|
190
|
+
console.timeEnd('open api find total time');
|
|
191
|
+
}
|
|
134
192
|
|
|
135
193
|
return {
|
|
136
194
|
"status": REQUEST_SUCCESS_STATUS,
|
|
137
195
|
"msg": "",
|
|
138
196
|
"data": {
|
|
139
|
-
"items":
|
|
197
|
+
"items": translatedRecords,
|
|
140
198
|
"total": totalCount
|
|
141
199
|
}
|
|
142
200
|
}
|
|
143
201
|
}
|
|
144
202
|
},
|
|
203
|
+
/**
|
|
204
|
+
* @api {GET} /api/v1/:objectName/count 获取记录个数
|
|
205
|
+
* @apiVersion 0.0.0
|
|
206
|
+
* @apiName count
|
|
207
|
+
* @apiGroup @steedos/service-rest
|
|
208
|
+
* @apiParam {String} objectName 对象API Name,如:contracts
|
|
209
|
+
* @apiQuery {String} [filters] 过滤条件,如:'[["name", "=", "test"],["amount", ">", 100]]'
|
|
210
|
+
* @apiSuccess {Object[]} count 记录个数
|
|
211
|
+
* @apiSuccessExample {json} Success-Response:
|
|
212
|
+
* HTTP/1.1 200 OK
|
|
213
|
+
* {
|
|
214
|
+
* "status": 0, // 返回 0,表示当前接口正确返回,否则按错误请求处理
|
|
215
|
+
* "msg": "", // 返回接口处理信息
|
|
216
|
+
* "data": {
|
|
217
|
+
* "count": 200
|
|
218
|
+
* }
|
|
219
|
+
* }
|
|
220
|
+
* @apiErrorExample {json} Error-Response:
|
|
221
|
+
* HTTP/1.1 500 Error
|
|
222
|
+
* {
|
|
223
|
+
* "status": -1,
|
|
224
|
+
* "msg": "",
|
|
225
|
+
* "data": {}
|
|
226
|
+
* }
|
|
227
|
+
*/
|
|
228
|
+
count: {
|
|
229
|
+
rest: {
|
|
230
|
+
method: "GET",
|
|
231
|
+
path: "/:objectName/count"
|
|
232
|
+
},
|
|
233
|
+
params: {
|
|
234
|
+
objectName: { type: "string" },
|
|
235
|
+
filters: { type: 'string', optional: true },
|
|
236
|
+
},
|
|
237
|
+
async handler(ctx) {
|
|
238
|
+
if (process.env.STEEDOS_DEBUG) {
|
|
239
|
+
console.time('open api count total time');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (process.env.STEEDOS_DEBUG) {
|
|
243
|
+
console.time('open api count before find');
|
|
244
|
+
}
|
|
245
|
+
const params = ctx.params
|
|
246
|
+
const { objectName, filters } = params
|
|
247
|
+
const userSession = ctx.meta.user;
|
|
248
|
+
|
|
249
|
+
const query = {}
|
|
250
|
+
if (filters) {
|
|
251
|
+
query.filters = JSON.parse(filters)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (process.env.STEEDOS_DEBUG) {
|
|
255
|
+
console.timeEnd('open api count before find');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const countQuery = {
|
|
259
|
+
filters: query.filters
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (process.env.STEEDOS_DEBUG) {
|
|
263
|
+
console.time('open api count find count');
|
|
264
|
+
}
|
|
265
|
+
const count = await this.count(objectName, countQuery, userSession);
|
|
266
|
+
if (process.env.STEEDOS_DEBUG) {
|
|
267
|
+
console.timeEnd('open api count find count');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (process.env.STEEDOS_DEBUG) {
|
|
271
|
+
console.timeEnd('open api count total time');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
"status": REQUEST_SUCCESS_STATUS,
|
|
276
|
+
"msg": "",
|
|
277
|
+
"data": {
|
|
278
|
+
"count": count
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
},
|
|
145
283
|
/**
|
|
146
284
|
* @api {POST} /api/v1/:objectName/search 查询列表记录
|
|
147
285
|
* @apiVersion 0.0.0
|
package/translate.js
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: baozhoutao@steedos.com
|
|
3
|
+
* @Date: 2024-02-26 13:29:53
|
|
4
|
+
* @LastEditors: 殷亮辉 yinlianghui@hotoa.com
|
|
5
|
+
* @LastEditTime: 2024-03-12 17:32:17
|
|
6
|
+
* @Description:
|
|
7
|
+
*/
|
|
8
|
+
const _ = require('lodash')
|
|
9
|
+
|
|
10
|
+
const { getSteedosSchema, getUserLocale, absoluteUrl } = require("@steedos/objectql");
|
|
11
|
+
const { translationObject } = require("@steedos/i18n");
|
|
12
|
+
const { formatBasicFieldValue } = require("./utils");
|
|
13
|
+
|
|
14
|
+
const EXPAND_SUFFIX = '__expand';
|
|
15
|
+
const UI_PREFIX = '_ui';
|
|
16
|
+
|
|
17
|
+
function getTranslatedFieldConfig(translatedObject, name) {
|
|
18
|
+
return translatedObject.fields[name.replace(/__label$/, "")];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function translateRecordToUI(record, objectName, selectorFieldNames, userSession){
|
|
22
|
+
|
|
23
|
+
const lng = getUserLocale(userSession);
|
|
24
|
+
let steedosSchema = getSteedosSchema();
|
|
25
|
+
let object = steedosSchema.getObject(objectName);
|
|
26
|
+
let objConfig = await object.toConfig();
|
|
27
|
+
let fields = objConfig.fields;
|
|
28
|
+
// let _object = clone(objConfig);
|
|
29
|
+
translationObject(lng, objConfig.name, objConfig, true);
|
|
30
|
+
|
|
31
|
+
async function _translateToUI(record, selectorFieldNames, parentRecord) {
|
|
32
|
+
let displayObj = {};
|
|
33
|
+
for (const name of selectorFieldNames) {
|
|
34
|
+
if (Object.prototype.hasOwnProperty.call(fields, name)) {
|
|
35
|
+
const field = fields[name];
|
|
36
|
+
try {
|
|
37
|
+
if (_.has(record, name)) {
|
|
38
|
+
const fType = field.type;
|
|
39
|
+
const dataType = field.data_type;
|
|
40
|
+
if (fType == "select") {
|
|
41
|
+
let label = "";
|
|
42
|
+
let map = {};
|
|
43
|
+
let value = record[name];
|
|
44
|
+
let translatedField = getTranslatedFieldConfig(objConfig, name);
|
|
45
|
+
let translatedFieldOptions =
|
|
46
|
+
translatedField && translatedField.options;
|
|
47
|
+
_.forEach(translatedFieldOptions, function (o) {
|
|
48
|
+
map[o.value] = o.label;
|
|
49
|
+
});
|
|
50
|
+
if (field.multiple) {
|
|
51
|
+
let labels = [];
|
|
52
|
+
_.forEach(value, function (v) {
|
|
53
|
+
labels.push(map[v]);
|
|
54
|
+
});
|
|
55
|
+
label = labels.join(",");
|
|
56
|
+
} else {
|
|
57
|
+
label = map[value];
|
|
58
|
+
}
|
|
59
|
+
displayObj[name] = label;
|
|
60
|
+
} else if (fType == "lookup" && (_.isString(field.reference_to) || (!_.has(field, 'reference_to') && !_.has(field, '_reference_to')))) {
|
|
61
|
+
if (_.isString(field.reference_to)) {
|
|
62
|
+
let refTo = field.reference_to;
|
|
63
|
+
|
|
64
|
+
let refField = field.reference_to_field || '_id';
|
|
65
|
+
|
|
66
|
+
if (refTo === 'users') {
|
|
67
|
+
refTo = 'space_users';
|
|
68
|
+
refField = 'user'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let refValue = record[name];
|
|
72
|
+
if (!refValue) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
let refObj = steedosSchema.getObject(refTo);
|
|
76
|
+
let nameFieldKey = await refObj.getNameFieldKey();
|
|
77
|
+
let refFilters = null;
|
|
78
|
+
|
|
79
|
+
if (field.multiple) {
|
|
80
|
+
refFilters = [refField, "in", refValue]
|
|
81
|
+
} else {
|
|
82
|
+
refFilters = [refField, "=", refValue]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 判断如果是 reference_to = object_fields && reference_to_field = name, 则额外添加查询条件 object 查询条件;
|
|
86
|
+
if (refTo === 'object_fields' && refField == 'name') {
|
|
87
|
+
if(objConfig.name === 'objects'){
|
|
88
|
+
refFilters = [['object', '=', parentRecord['name']], refFilters]
|
|
89
|
+
}else{
|
|
90
|
+
const refToObjectsField = _.find(fields, (_field) => {
|
|
91
|
+
return _field.reference_to === 'objects'
|
|
92
|
+
})
|
|
93
|
+
if (refToObjectsField) {
|
|
94
|
+
refFilters = [['object', '=', parentRecord[refToObjectsField.name]], refFilters]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 判断如果是 reference_to = object_actions && reference_to_field = name, 则额外添加查询条件 object 查询条件;
|
|
101
|
+
if (refTo === 'object_actions' && refField == 'name') {
|
|
102
|
+
const refToObjectsField = _.find(fields, (_field) => {
|
|
103
|
+
return _field.reference_to === 'objects'
|
|
104
|
+
})
|
|
105
|
+
if (refToObjectsField) {
|
|
106
|
+
refFilters = [['object', '=', parentRecord[refToObjectsField.name]], refFilters]
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (field.multiple) {
|
|
111
|
+
let refRecords = await refObj.find({
|
|
112
|
+
filters: refFilters,
|
|
113
|
+
fields: [nameFieldKey],
|
|
114
|
+
});
|
|
115
|
+
displayObj[name] = _.map(refRecords, (item) => {
|
|
116
|
+
return {
|
|
117
|
+
objectName: refTo,
|
|
118
|
+
value: item ? item._id : refValue,
|
|
119
|
+
label: item ? item[nameFieldKey] : refValue
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
} else {
|
|
123
|
+
let refRecord = (
|
|
124
|
+
await refObj.find({
|
|
125
|
+
filters: refFilters,
|
|
126
|
+
fields: [nameFieldKey],
|
|
127
|
+
})
|
|
128
|
+
)[0];
|
|
129
|
+
if (refRecord) {
|
|
130
|
+
displayObj[name] = {
|
|
131
|
+
objectName: refTo,
|
|
132
|
+
value: refRecord._id,
|
|
133
|
+
label: refRecord[nameFieldKey]
|
|
134
|
+
};
|
|
135
|
+
} else {
|
|
136
|
+
displayObj[name] = {
|
|
137
|
+
objectName: refTo,
|
|
138
|
+
value: refValue,
|
|
139
|
+
label: refValue
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
let refValue = record[name];
|
|
145
|
+
if (!refValue) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (field.multiple && _.isArray(refValue)) {
|
|
149
|
+
_.each(refValue, (item) => {
|
|
150
|
+
displayObj[name] = {
|
|
151
|
+
value: item,
|
|
152
|
+
label: item
|
|
153
|
+
};
|
|
154
|
+
})
|
|
155
|
+
} else {
|
|
156
|
+
displayObj[name] = {
|
|
157
|
+
value: refValue,
|
|
158
|
+
label: refValue
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
} else if (fType == "master_detail" && _.isString(field.reference_to)) {
|
|
164
|
+
let refTo = field.reference_to;
|
|
165
|
+
let refField = field.reference_to_field || '_id';
|
|
166
|
+
|
|
167
|
+
if (refTo === 'users') {
|
|
168
|
+
refTo = 'space_users';
|
|
169
|
+
refField = 'user'
|
|
170
|
+
}
|
|
171
|
+
let refValue = record[name];
|
|
172
|
+
if (!refValue) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
let refObj = steedosSchema.getObject(refTo);
|
|
176
|
+
let nameFieldKey = await refObj.getNameFieldKey();
|
|
177
|
+
|
|
178
|
+
if (field.multiple) {
|
|
179
|
+
let refRecords = await refObj.find({
|
|
180
|
+
filters: [refField, "in", refValue],
|
|
181
|
+
fields: [nameFieldKey],
|
|
182
|
+
});
|
|
183
|
+
displayObj[name] = _.map(refRecords, (item) => {
|
|
184
|
+
return {
|
|
185
|
+
objectName: refTo,
|
|
186
|
+
value: item._id,
|
|
187
|
+
label: item[nameFieldKey]
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
} else {
|
|
191
|
+
let refRecord = (
|
|
192
|
+
await refObj.find({
|
|
193
|
+
filters: [refField, "=", refValue],
|
|
194
|
+
fields: [nameFieldKey],
|
|
195
|
+
})
|
|
196
|
+
)[0];
|
|
197
|
+
if (refRecord) {
|
|
198
|
+
displayObj[name] = {
|
|
199
|
+
objectName: refTo,
|
|
200
|
+
value: refRecord._id,
|
|
201
|
+
label: refRecord[nameFieldKey]
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
} else if ((fType == "master_detail" || fType == "lookup") && ((field.reference_to && !_.isString(field.reference_to)) || _.isString(field._reference_to))) {
|
|
206
|
+
let refValue = record[name];
|
|
207
|
+
if (!refValue) {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
let refTo = refValue.o;
|
|
211
|
+
let refValues = refValue.ids;
|
|
212
|
+
if (!refTo) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
let refObj = steedosSchema.getObject(refTo);
|
|
216
|
+
let nameFieldKey = await refObj.getNameFieldKey();
|
|
217
|
+
let refRecords = await refObj.find({
|
|
218
|
+
filters: [`_id`, "in", refValues],
|
|
219
|
+
fields: [nameFieldKey]
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
displayObj[name] = _.map(refRecords, (item) => {
|
|
223
|
+
return {
|
|
224
|
+
objectName: refTo,
|
|
225
|
+
value: item._id,
|
|
226
|
+
label: item[nameFieldKey]
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
} else if (fType == "formula") {
|
|
230
|
+
displayObj[name] = formatBasicFieldValue(field.data_type, field, record[name], objConfig, userSession);
|
|
231
|
+
} else if (fType == "summary") {
|
|
232
|
+
displayObj[name] = formatBasicFieldValue(dataType, field, record[name], objConfig, userSession);
|
|
233
|
+
} else if (fType == "image" || fType == "file" || fType === 'avatar') {
|
|
234
|
+
const optionsStr = fType == "file" ? '?download=1' : ''
|
|
235
|
+
let fileValue = null;
|
|
236
|
+
let value = record[name];
|
|
237
|
+
if (!value) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
// TODO: cfs_images_filerecord对象不存在,需要额外处理
|
|
241
|
+
let storageName = getFileStorageName(fType)
|
|
242
|
+
let fileObjectName = `cfs_${storageName}_filerecord`;
|
|
243
|
+
let fileObject = steedosSchema.getObject(fileObjectName);
|
|
244
|
+
const fileNameFieldKey = "original.name";
|
|
245
|
+
if (field.multiple) {
|
|
246
|
+
let fileRecords = await fileObject.find({
|
|
247
|
+
filters: [`_id`, "in", value],
|
|
248
|
+
fields: ['_id', fileNameFieldKey, 'original.size', 'original.type'],
|
|
249
|
+
});
|
|
250
|
+
fileValue = _.map(fileRecords, (fileRecord) => {
|
|
251
|
+
return {
|
|
252
|
+
name: fileRecord.original?.name,
|
|
253
|
+
url: absoluteUrl(`/api/files/${storageName}/${fileRecord._id}${optionsStr}`),
|
|
254
|
+
size: fileRecord.original?.size,
|
|
255
|
+
type: fileRecord.original?.type,
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
} else {
|
|
259
|
+
let fileRecord = (
|
|
260
|
+
await fileObject.find({
|
|
261
|
+
filters: [`_id`, "=", value],
|
|
262
|
+
fields: ['_id', fileNameFieldKey, 'original.size', 'original.type'],
|
|
263
|
+
})
|
|
264
|
+
)[0];
|
|
265
|
+
if (fileRecord) {
|
|
266
|
+
fileValue = {
|
|
267
|
+
name: fileRecord["original"]["name"],
|
|
268
|
+
url: absoluteUrl(`/api/files/${storageName}/${value}${optionsStr}`),
|
|
269
|
+
size: fileRecord.original?.size,
|
|
270
|
+
type: fileRecord.original?.type
|
|
271
|
+
};
|
|
272
|
+
} else {
|
|
273
|
+
fileValue = {
|
|
274
|
+
url: value
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
displayObj[name] = fileValue;
|
|
279
|
+
} else if (fType == "filesize") {
|
|
280
|
+
displayObj[name] = formatFileSize(record[name]);
|
|
281
|
+
}
|
|
282
|
+
else if (fType === 'object') {
|
|
283
|
+
if (record[name] && _.isObject(record[name])) {
|
|
284
|
+
const _doc = {}
|
|
285
|
+
_.each(record[name], function (v, k) {
|
|
286
|
+
const newKey = `${name}.${k}`
|
|
287
|
+
_doc[newKey] = v
|
|
288
|
+
})
|
|
289
|
+
const objectFieldDoc = await _translateToUI(_doc, Object.keys(_doc), record)
|
|
290
|
+
const objectDoc = {}
|
|
291
|
+
_.each(objectFieldDoc, function (v, k) {
|
|
292
|
+
const newKey = k.replace(`${name}.`, '')
|
|
293
|
+
objectDoc[newKey] = v
|
|
294
|
+
})
|
|
295
|
+
displayObj[name] = objectDoc
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
else if (fType === 'grid' || 'table' === fType) {
|
|
299
|
+
if (record[name] && _.isArray(record[name])) {
|
|
300
|
+
const gridDocs = []
|
|
301
|
+
for (const gridDoc of record[name]) {
|
|
302
|
+
const _doc = {}
|
|
303
|
+
_.each(gridDoc, function (v, k) {
|
|
304
|
+
const newKey = `${name}.$.${k}`
|
|
305
|
+
_doc[newKey] = v
|
|
306
|
+
})
|
|
307
|
+
const objectFieldDoc = await _translateToUI(_doc, Object.keys(_doc), record)
|
|
308
|
+
const objectDoc = {}
|
|
309
|
+
_.each(objectFieldDoc, function (v, k) {
|
|
310
|
+
const newKey = k.replace(`${name}.$.`, '')
|
|
311
|
+
objectDoc[newKey] = v
|
|
312
|
+
})
|
|
313
|
+
gridDocs.push(objectDoc)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
displayObj[name] = gridDocs
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
displayObj[name] = formatBasicFieldValue(fType, field, record[name], objConfig, userSession);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
displayObj[name] = ""; // 如果值为空,均返回空字符串
|
|
325
|
+
}
|
|
326
|
+
} catch (error) {
|
|
327
|
+
displayObj[name] = record[name];
|
|
328
|
+
// console.warn(error)
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return displayObj
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
let uiDoc = await _translateToUI(record, selectorFieldNames, record)
|
|
336
|
+
return uiDoc;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async function translateRecordToExpand(record, objectName, expandFields, userSession) {
|
|
340
|
+
let steedosSchema = getSteedosSchema();
|
|
341
|
+
let object = steedosSchema.getObject(objectName);
|
|
342
|
+
let objConfig = await object.toConfig();
|
|
343
|
+
let fields = objConfig.fields;
|
|
344
|
+
|
|
345
|
+
async function _translateToExpand(record, expandFields) {
|
|
346
|
+
let expandObj = {};
|
|
347
|
+
for (const fieldName in expandFields) {
|
|
348
|
+
const expandField = expandFields[fieldName];
|
|
349
|
+
let expandFieldFields = expandField.fields || [];
|
|
350
|
+
const expandFieldUiFields = expandField.uiFields;
|
|
351
|
+
const expandFieldExpandFields = expandField.expandFields;
|
|
352
|
+
if(expandFieldFields.length === 0 && _.isEmpty(expandFieldExpandFields)){
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (Object.prototype.hasOwnProperty.call(fields, fieldName) && expandFieldFields.length > 0) {
|
|
357
|
+
const field = fields[fieldName];
|
|
358
|
+
try {
|
|
359
|
+
if (field && _.has(record, fieldName)) {
|
|
360
|
+
let isLookup = (field.type == "lookup" || field.type == "master_detail") &&
|
|
361
|
+
field.reference_to && _.isString(field.reference_to);
|
|
362
|
+
let isFile = field.type == "image" || field.type == "file";
|
|
363
|
+
if (isLookup || isFile){
|
|
364
|
+
let refTo = field.reference_to;
|
|
365
|
+
let refField = field.reference_to_field || '_id';
|
|
366
|
+
if (isFile) {
|
|
367
|
+
// TODO: cfs_images_filerecord对象不存在,需要额外处理
|
|
368
|
+
refTo = field.type == "image" ? "cfs_images_filerecord" : "cfs_files_filerecord";
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
let refValue = record[fieldName];
|
|
372
|
+
if (!refValue) {
|
|
373
|
+
// 如果值为空,跟GraphQL一样,返回null值
|
|
374
|
+
expandObj[fieldName + EXPAND_SUFFIX] = null;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
let refObj = steedosSchema.getObject(refTo);
|
|
378
|
+
let refFilters = null;
|
|
379
|
+
|
|
380
|
+
if (field.multiple) {
|
|
381
|
+
refFilters = [refField, "in", refValue]
|
|
382
|
+
} else {
|
|
383
|
+
refFilters = [refField, "=", refValue]
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
let queryFields = expandFieldFields;
|
|
387
|
+
if(expandFieldExpandFields){
|
|
388
|
+
// 跟GraphQL第一层一样,从库里查的字段要补上expandFields中的字段,即uiFields中依赖的字段可以在fields中定义,也可以在expandFields中字义
|
|
389
|
+
queryFields = _.union(queryFields, _.keys(expandFieldExpandFields));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const queryFilters = [refFilters];
|
|
393
|
+
const spaceId = userSession.spaceId;
|
|
394
|
+
if (refField && refField != '_id' && refTo != 'users' && refTo != 'spaces' && spaceId) {
|
|
395
|
+
queryFilters.push(["space", "=", spaceId])
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
let refRecords = await refObj.find({
|
|
399
|
+
filters: queryFilters,
|
|
400
|
+
fields: queryFields,
|
|
401
|
+
});
|
|
402
|
+
let translatedRecords = await translateRecords(refRecords, refTo, expandFieldFields, expandFieldUiFields, expandFieldExpandFields, userSession);
|
|
403
|
+
if(field.multiple){
|
|
404
|
+
expandObj[fieldName + EXPAND_SUFFIX] = translatedRecords;
|
|
405
|
+
}
|
|
406
|
+
else{
|
|
407
|
+
expandObj[fieldName + EXPAND_SUFFIX] = translatedRecords[0];
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
// 如果值为空,跟GraphQL一样,返回null值
|
|
413
|
+
expandObj[fieldName + EXPAND_SUFFIX] = null;
|
|
414
|
+
}
|
|
415
|
+
} catch (error) {
|
|
416
|
+
// 如果报错,跟GraphQL一样,返回null值
|
|
417
|
+
expandObj[fieldName + EXPAND_SUFFIX] = null;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
return expandObj
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
let expandProps = await _translateToExpand(record, expandFields)
|
|
426
|
+
return expandProps;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async function translateRecords(records, objectName, fields, uiFields, expandFields, userSession) {
|
|
430
|
+
var hasUiFields = !_.isEmpty(uiFields);
|
|
431
|
+
var hasExpandFields = !_.isEmpty(expandFields);
|
|
432
|
+
var emptyRecord = {};
|
|
433
|
+
_.each(fields || [], (field) => {
|
|
434
|
+
// GraphQL那边空值规则是返回null,openApi跟它一样
|
|
435
|
+
emptyRecord[field] = null;
|
|
436
|
+
});
|
|
437
|
+
// 跟GraphQl一样,找出在expandFields中字义过,但是没在fields中定义的字段,需要排除掉这些字段,即只返回fields中指定的字段,record中因为expandFields额外多查出的字段不返回
|
|
438
|
+
let omitFields = _.difference(_.keys(expandFields), fields);
|
|
439
|
+
let resRecords = [];
|
|
440
|
+
for (let record of records) {
|
|
441
|
+
if (hasUiFields || hasExpandFields) {
|
|
442
|
+
record = Object.assign({}, emptyRecord, record); // 跟GraphQl一样,只返回fields中指定的字段,record中多查出的字段不返回
|
|
443
|
+
if (hasUiFields) {
|
|
444
|
+
record[UI_PREFIX] = await translateRecordToUI(record, objectName, uiFields, userSession);
|
|
445
|
+
}
|
|
446
|
+
if (hasExpandFields) {
|
|
447
|
+
let expandProps = await translateRecordToExpand(record, objectName, expandFields, userSession);
|
|
448
|
+
Object.assign(record, expandProps);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
record = _.omit(record, omitFields);
|
|
452
|
+
resRecords.push(record);
|
|
453
|
+
}
|
|
454
|
+
return resRecords;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
module.exports = {
|
|
458
|
+
translateRecords
|
|
459
|
+
}
|
package/utils.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: sunhaolin@hotoa.com
|
|
3
|
+
* @Date: 2023-02-06 16:44:55
|
|
4
|
+
* @LastEditors: 殷亮辉 yinlianghui@hotoa.com
|
|
5
|
+
* @LastEditTime: 2024-03-12 16:52:36
|
|
6
|
+
* @Description:
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const moment = require('moment');
|
|
10
|
+
const _ = require("underscore");
|
|
11
|
+
const { getSteedosSchema } = require('@steedos/objectql');
|
|
12
|
+
|
|
13
|
+
function formatFileSize(fileSize) {
|
|
14
|
+
var rev, unit;
|
|
15
|
+
rev = fileSize / 1024.00;
|
|
16
|
+
unit = 'KB';
|
|
17
|
+
if (rev > 1024.00) {
|
|
18
|
+
rev = rev / 1024.00;
|
|
19
|
+
unit = 'MB';
|
|
20
|
+
}
|
|
21
|
+
if (rev > 1024.00) {
|
|
22
|
+
rev = rev / 1024.00;
|
|
23
|
+
unit = 'GB';
|
|
24
|
+
}
|
|
25
|
+
return rev.toFixed(2) + unit;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function getFileStorageName(type) {
|
|
29
|
+
switch (type) {
|
|
30
|
+
case 'avatar':
|
|
31
|
+
return 'avatars'
|
|
32
|
+
case 'image':
|
|
33
|
+
return 'images'
|
|
34
|
+
case 'file':
|
|
35
|
+
return 'files'
|
|
36
|
+
default:
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function formatBasicFieldValue(valueType, field, value, objectConfig, userSession) {
|
|
42
|
+
switch (valueType) {
|
|
43
|
+
case 'text':
|
|
44
|
+
case 'textarea':
|
|
45
|
+
case 'html_text':
|
|
46
|
+
case 'color':
|
|
47
|
+
case 'autonumber':
|
|
48
|
+
case 'url':
|
|
49
|
+
case 'email':
|
|
50
|
+
case 'html':
|
|
51
|
+
case 'markdown':
|
|
52
|
+
case 'code':
|
|
53
|
+
return value || "";
|
|
54
|
+
case 'boolean':
|
|
55
|
+
return value ? "√" : ""
|
|
56
|
+
case 'date':
|
|
57
|
+
return value ? moment.utc(value).format("YYYY-MM-DD") : '';
|
|
58
|
+
case 'datetime':
|
|
59
|
+
return value ? moment(value).utcOffset(userSession.utcOffset || 8).format("YYYY-MM-DD HH:mm") : '';
|
|
60
|
+
case 'time':
|
|
61
|
+
return value ? moment.utc(value).format("HH:mm") : '';
|
|
62
|
+
case 'number':
|
|
63
|
+
case 'currency':
|
|
64
|
+
return numberToString(value, field.scale, field.enable_thousands === false);
|
|
65
|
+
case 'percent':
|
|
66
|
+
const str = numberToString(value * 100, field.scale, field.enable_thousands === false);
|
|
67
|
+
return str ? `${str}%` : ''
|
|
68
|
+
case 'password':
|
|
69
|
+
return _.isString(value) ? "******" : ""
|
|
70
|
+
default:
|
|
71
|
+
// console.log(field)
|
|
72
|
+
console.error(
|
|
73
|
+
`Graphql Display: need to handle new field type ${field.type} for ${objectConfig.name}.`
|
|
74
|
+
);
|
|
75
|
+
return value || "";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function numberToString(number, scale, notThousands = false) {
|
|
80
|
+
if (typeof number === "number") {
|
|
81
|
+
number = number.toString();
|
|
82
|
+
}
|
|
83
|
+
if (!number) {
|
|
84
|
+
return '';
|
|
85
|
+
}
|
|
86
|
+
if (number !== "NaN") {
|
|
87
|
+
if (scale || scale === 0) {
|
|
88
|
+
number = Number(number).toFixed(scale);
|
|
89
|
+
}
|
|
90
|
+
if (!notThousands) {
|
|
91
|
+
if (!(scale || scale === 0)) {
|
|
92
|
+
// 没定义scale时,根据小数点位置算出scale值
|
|
93
|
+
let regDots = number.match(/\.(\d+)/);
|
|
94
|
+
scale = regDots && regDots[1] && regDots[1].length
|
|
95
|
+
if (!scale) {
|
|
96
|
+
scale = 0;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
let reg = /(\d)(?=(\d{3})+\.)/g;
|
|
100
|
+
if (scale === 0) {
|
|
101
|
+
reg = /(\d)(?=(\d{3})+\b)/g;
|
|
102
|
+
}
|
|
103
|
+
number = number.replace(reg, '$1,');
|
|
104
|
+
}
|
|
105
|
+
return number;
|
|
106
|
+
} else {
|
|
107
|
+
return "";
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function callObjectServiceAction(actionName, userSession, data) {
|
|
112
|
+
const broker = getSteedosSchema().broker;
|
|
113
|
+
return broker.call(actionName, data, { meta: { user: userSession } })
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 获取object元数据
|
|
117
|
+
function getLocalService(objectApiName) {
|
|
118
|
+
let steedosSchema = getSteedosSchema();
|
|
119
|
+
return steedosSchema.broker.getLocalService(getObjectServiceName(objectApiName));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function correctName(name) {
|
|
123
|
+
return name.replace(/\./g, "_").replace(/\$/g, "_");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function _getRelatedType(relatedFieldName, relatedObjName) {
|
|
127
|
+
return `${relatedFieldName}(fields: [String], filters: JSON, top: Int, skip: Int, sort: String): [${relatedObjName}] `;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function getObjectServiceName(objectApiName){
|
|
131
|
+
return `@${objectApiName}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function getGraphqlServiceName(){
|
|
135
|
+
return 'graphql';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
module.exports = {
|
|
140
|
+
formatFileSize,
|
|
141
|
+
getFileStorageName,
|
|
142
|
+
formatBasicFieldValue,
|
|
143
|
+
numberToString,
|
|
144
|
+
callObjectServiceAction,
|
|
145
|
+
getLocalService,
|
|
146
|
+
correctName,
|
|
147
|
+
_getRelatedType,
|
|
148
|
+
getObjectServiceName,
|
|
149
|
+
getGraphqlServiceName
|
|
150
|
+
}
|