spice-js 2.6.65 → 2.6.67
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/build/models/SpiceModel.js +159 -89
- package/build/utility/RestHelper.js +138 -37
- package/package.json +1 -1
- package/src/utility/RestHelper.js +99 -43
|
@@ -916,25 +916,76 @@ class SpiceModel {
|
|
|
916
916
|
JSON.stringify(this);
|
|
917
917
|
}
|
|
918
918
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
919
|
+
buildArrayProjection(alias, field) {
|
|
920
|
+
var safeAlias = String(alias).replace(/`/g, "");
|
|
921
|
+
var safeField = String(field).replace(/`/g, "");
|
|
922
|
+
return "ARRAY rc." + safeField + " FOR rc IN IFMISSINGORNULL(" + safeAlias + ", []) END AS `" + safeAlias + "_" + safeField + "`";
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
prepColumns(columns, protectedAliases, arrayAliases) {
|
|
926
|
+
if (protectedAliases === void 0) {
|
|
927
|
+
protectedAliases = [];
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if (arrayAliases === void 0) {
|
|
931
|
+
arrayAliases = [];
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
if (!columns || columns === "") return columns;
|
|
935
|
+
var protectedSet = new Set(protectedAliases);
|
|
936
|
+
var arraySet = new Set(arrayAliases);
|
|
937
|
+
var tokens = columns.split(",");
|
|
938
|
+
var out = tokens.map(raw => {
|
|
939
|
+
var col = (raw || "").trim();
|
|
940
|
+
if (col === "" || col === "meta().id") return undefined;
|
|
941
|
+
if (/^\s*ARRAY\s+/i.test(col) || /\w+\s*\(/.test(col)) return col;
|
|
942
|
+
var m = col.match(/^\s*`?(\w+)`?\.`?(\w+)`?(?:\s+AS\s+`?([\w]+)`?)?\s*$/i);
|
|
943
|
+
|
|
944
|
+
if (m) {
|
|
945
|
+
var alias = m[1];
|
|
946
|
+
var field = m[2];
|
|
947
|
+
var explicitAs = m[3];
|
|
948
|
+
|
|
949
|
+
if (arraySet.has(alias)) {
|
|
950
|
+
var proj = this.buildArrayProjection(alias, field);
|
|
951
|
+
|
|
952
|
+
if (explicitAs && explicitAs !== alias + "_" + field) {
|
|
953
|
+
proj = proj.replace(/AS\s+`[^`]+`$/i, "AS `" + explicitAs + "`");
|
|
954
|
+
}
|
|
924
955
|
|
|
925
|
-
|
|
926
|
-
column = "`" + column.trim() + "`";
|
|
956
|
+
return proj;
|
|
927
957
|
}
|
|
928
958
|
|
|
929
|
-
if (
|
|
930
|
-
|
|
959
|
+
if (protectedSet.has(alias)) {
|
|
960
|
+
var aliased = "`" + alias + "`." + (field.startsWith("`") ? field : "`" + field + "`");
|
|
961
|
+
return explicitAs ? aliased + " AS `" + explicitAs + "`" : aliased;
|
|
931
962
|
}
|
|
932
963
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
964
|
+
var bare = col.replace(/`/g, "");
|
|
965
|
+
return "`" + this.type + "`." + bare;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
var looksProtected = [...protectedSet].some(a => col === a || col === "`" + a + "`" || col.startsWith(a + ".") || col.startsWith("`" + a + "`."));
|
|
936
969
|
|
|
937
|
-
|
|
970
|
+
if (!looksProtected) {
|
|
971
|
+
if (!col.startsWith("`") && !col.endsWith("`") && !col.includes("(")) {
|
|
972
|
+
col = "`" + col + "`";
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
if (col && !col.includes(".") && !col.startsWith(this.type)) {
|
|
976
|
+
col = "`" + this.type + "`." + col.replace(/`/g, "");
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
return col;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
if (!col.includes(".") && !col.startsWith("`")) {
|
|
983
|
+
return "`" + col + "`";
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
return col;
|
|
987
|
+
});
|
|
988
|
+
return _.join(_.compact(out), ",");
|
|
938
989
|
}
|
|
939
990
|
|
|
940
991
|
filterResultsByColumns(data, columns) {
|
|
@@ -989,12 +1040,20 @@ class SpiceModel {
|
|
|
989
1040
|
return [...new Set(returnVal)];
|
|
990
1041
|
}
|
|
991
1042
|
|
|
992
|
-
createJoinSection(
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1043
|
+
createJoinSection(mappedNestings) {
|
|
1044
|
+
if (!mappedNestings || mappedNestings.length === 0) return "";
|
|
1045
|
+
return mappedNestings.map((_ref2) => {
|
|
1046
|
+
var {
|
|
1047
|
+
alias,
|
|
1048
|
+
reference,
|
|
1049
|
+
is_array
|
|
1050
|
+
} = _ref2;
|
|
1051
|
+
var keyspace = (0, _fix.fixCollection)(reference);
|
|
1052
|
+
|
|
1053
|
+
if (is_array === true) {
|
|
1054
|
+
return "LEFT NEST `" + keyspace + "` AS `" + alias + "` ON KEYS `" + this.type + "`.`" + alias + "`";
|
|
996
1055
|
} else {
|
|
997
|
-
return "LEFT JOIN `" +
|
|
1056
|
+
return "LEFT JOIN `" + keyspace + "` AS `" + alias + "` ON KEYS `" + this.type + "`.`" + alias + "`";
|
|
998
1057
|
}
|
|
999
1058
|
}).join(" ");
|
|
1000
1059
|
}
|
|
@@ -1009,67 +1068,63 @@ class SpiceModel {
|
|
|
1009
1068
|
} */
|
|
1010
1069
|
|
|
1011
1070
|
|
|
1012
|
-
fixColumnName(columns) {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1071
|
+
fixColumnName(columns, protectedAliases) {
|
|
1072
|
+
if (protectedAliases === void 0) {
|
|
1073
|
+
protectedAliases = [];
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
if (!columns || typeof columns !== "string") return columns;
|
|
1077
|
+
var protectedSet = new Set(protectedAliases);
|
|
1078
|
+
var tokens = columns.split(",").map(s => s.trim());
|
|
1079
|
+
var aliasTracker = {};
|
|
1080
|
+
var out = tokens.map(col => {
|
|
1081
|
+
if (!col) return undefined; // Do not rewrite ARRAY projections
|
|
1017
1082
|
|
|
1083
|
+
if (/^\s*ARRAY\s+/i.test(col)) return col; // If token is literally this.type.alias → compress to `alias`
|
|
1084
|
+
|
|
1085
|
+
for (var a of protectedSet) {
|
|
1086
|
+
var re = new RegExp("^`?" + _.escapeRegExp(this.type) + "`?\\.`?" + _.escapeRegExp(a) + "`?$");
|
|
1087
|
+
|
|
1088
|
+
if (re.test(col)) {
|
|
1089
|
+
return "`" + a + "`";
|
|
1090
|
+
}
|
|
1091
|
+
} // Extract explicit AS
|
|
1018
1092
|
|
|
1019
|
-
var columnList = columns.split(",").map(col => col.trim()); // Object to keep track of alias usage to avoid duplicates.
|
|
1020
1093
|
|
|
1021
|
-
var aliasTracker = {};
|
|
1022
|
-
var fixedColumns = columnList.map(col => {
|
|
1023
|
-
// Check if an explicit alias is already provided.
|
|
1024
1094
|
var aliasRegex = /\s+AS\s+`?([\w]+)`?$/i;
|
|
1025
1095
|
var aliasMatch = col.match(aliasRegex);
|
|
1026
|
-
var explicitAlias = aliasMatch ? aliasMatch[1] : null;
|
|
1096
|
+
var explicitAlias = aliasMatch ? aliasMatch[1] : null;
|
|
1097
|
+
var colWithoutAlias = explicitAlias ? col.replace(aliasRegex, "").trim() : col; // `table`.`col` or table.col
|
|
1027
1098
|
|
|
1028
|
-
var colWithoutAlias = explicitAlias ? col.replace(aliasRegex, "").trim() : col; // Use regex to extract the table and column names.
|
|
1029
|
-
// This regex matches optional backticks around each identifier.
|
|
1030
|
-
|
|
1031
|
-
var tableName = this.type;
|
|
1032
|
-
var columnName = "";
|
|
1033
1099
|
var columnRegex = /^`?([\w]+)`?\.`?([\w]+)`?$/;
|
|
1034
1100
|
var match = colWithoutAlias.match(columnRegex);
|
|
1101
|
+
var tableName = this.type;
|
|
1102
|
+
var columnName = colWithoutAlias.replace(/`/g, "");
|
|
1035
1103
|
|
|
1036
1104
|
if (match) {
|
|
1037
1105
|
tableName = match[1];
|
|
1038
1106
|
columnName = match[2];
|
|
1039
|
-
}
|
|
1040
|
-
// If no table prefix is found, use the column name only.
|
|
1041
|
-
columnName = colWithoutAlias.replace(/`/g, "");
|
|
1042
|
-
} // Generate the alias.
|
|
1043
|
-
// If the column comes from a table different from this.type, then the alias
|
|
1044
|
-
// becomes tableName_columnName. Otherwise, just use the column name.
|
|
1045
|
-
|
|
1107
|
+
} // For joined table columns, add default alias <table_col> if none
|
|
1046
1108
|
|
|
1047
|
-
var newAlias = columnName;
|
|
1048
1109
|
|
|
1049
1110
|
if (tableName && tableName !== this.type) {
|
|
1050
|
-
newAlias = tableName + "_" + columnName;
|
|
1051
|
-
|
|
1052
|
-
if (explicitAlias) {
|
|
1053
|
-
newAlias = explicitAlias;
|
|
1054
|
-
} // Avoid duplicates by appending a count if needed.
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
if (aliasTracker.hasOwnProperty(newAlias)) {
|
|
1058
|
-
aliasTracker[newAlias]++;
|
|
1059
|
-
newAlias = newAlias + "_" + aliasTracker[newAlias];
|
|
1060
|
-
} else {
|
|
1061
|
-
aliasTracker[newAlias] = 0;
|
|
1062
|
-
} // If there's no explicit alias, add an alias clause.
|
|
1063
|
-
|
|
1111
|
+
var newAlias = explicitAlias || tableName + "_" + columnName;
|
|
1064
1112
|
|
|
1065
1113
|
if (!explicitAlias) {
|
|
1114
|
+
if (aliasTracker.hasOwnProperty(newAlias)) {
|
|
1115
|
+
aliasTracker[newAlias]++;
|
|
1116
|
+
newAlias = newAlias + "_" + aliasTracker[newAlias];
|
|
1117
|
+
} else {
|
|
1118
|
+
aliasTracker[newAlias] = 0;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1066
1121
|
return colWithoutAlias + " AS `" + newAlias + "`";
|
|
1067
1122
|
}
|
|
1068
1123
|
}
|
|
1069
1124
|
|
|
1070
1125
|
return col;
|
|
1071
1126
|
});
|
|
1072
|
-
return
|
|
1127
|
+
return _.join(_.compact(out), ", ");
|
|
1073
1128
|
}
|
|
1074
1129
|
|
|
1075
1130
|
list(args) {
|
|
@@ -1083,38 +1138,55 @@ class SpiceModel {
|
|
|
1083
1138
|
try {
|
|
1084
1139
|
var _args6, _args7, _args8;
|
|
1085
1140
|
|
|
1086
|
-
args.
|
|
1087
|
-
if (args.mapping_dept) _this12[_mapping_dept] = args.mapping_dept;
|
|
1088
|
-
var nestings = [..._this12.extractNestings((_args6 = args) == null ? void 0 : _args6.query, _this12.type), ..._this12.extractNestings((_args7 = args) == null ? void 0 : _args7.columns, _this12.type), ..._this12.extractNestings((_args8 = args) == null ? void 0 : _args8.sort, _this12.type)];
|
|
1089
|
-
args.columns = _this12.fixColumnName(args.columns);
|
|
1141
|
+
if (args.mapping_dept) _this12[_mapping_dept] = args.mapping_dept; // Find alias tokens from query/columns/sort
|
|
1090
1142
|
|
|
1091
|
-
var
|
|
1092
|
-
var _prop$map;
|
|
1143
|
+
var nestings = [..._this12.extractNestings(((_args6 = args) == null ? void 0 : _args6.query) || "", _this12.type), ..._this12.extractNestings(((_args7 = args) == null ? void 0 : _args7.columns) || "", _this12.type), ..._this12.extractNestings(((_args8 = args) == null ? void 0 : _args8.sort) || "", _this12.type)]; // Decide which aliases we can join: only when map.type===MODEL AND reference is a STRING keyspace.
|
|
1093
1144
|
|
|
1094
|
-
|
|
1145
|
+
var mappedNestings = _.compact(_.uniq(nestings).map(alias => {
|
|
1146
|
+
var prop = _this12.props[alias];
|
|
1147
|
+
if (!(prop == null ? void 0 : prop.map) || prop.map.type !== _2.MapType.MODEL) return null;
|
|
1148
|
+
var ref = prop.map.reference;
|
|
1095
1149
|
|
|
1096
|
-
if (
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
reference: prop.map.reference.toLowerCase(),
|
|
1100
|
-
type: prop.type,
|
|
1101
|
-
value_field: prop.map.value_field
|
|
1102
|
-
};
|
|
1150
|
+
if (typeof ref !== "string") {
|
|
1151
|
+
// reference is a class/function/array-of-classes → no SQL join; serializer will handle it
|
|
1152
|
+
return null;
|
|
1103
1153
|
}
|
|
1154
|
+
|
|
1155
|
+
var is_array = prop.type === "array" || prop.type === Array || prop.type === _2.DataType.ARRAY;
|
|
1156
|
+
return {
|
|
1157
|
+
alias,
|
|
1158
|
+
reference: ref.toLowerCase(),
|
|
1159
|
+
// keyspace to join
|
|
1160
|
+
is_array,
|
|
1161
|
+
type: prop.type,
|
|
1162
|
+
value_field: prop.map.value_field,
|
|
1163
|
+
destination: prop.map.destination || alias
|
|
1164
|
+
};
|
|
1104
1165
|
}));
|
|
1105
1166
|
|
|
1106
|
-
|
|
1167
|
+
var protectedAliases = mappedNestings.map(m => m.alias);
|
|
1168
|
+
var arrayAliases = mappedNestings.filter(m => m.is_array).map(m => m.alias); // Columns: first prepare (prefix base table, rewrite array alias.field → ARRAY proj),
|
|
1169
|
+
// then normalize names and add default aliases
|
|
1170
|
+
|
|
1171
|
+
args.columns = _this12.prepColumns(args.columns, protectedAliases, arrayAliases);
|
|
1172
|
+
args.columns = _this12.fixColumnName(args.columns, protectedAliases); // Build JOIN/NEST from the mapped keyspaces
|
|
1173
|
+
|
|
1174
|
+
args._join = _this12.createJoinSection(mappedNestings); // WHERE
|
|
1175
|
+
|
|
1107
1176
|
var query = "";
|
|
1177
|
+
var deletedCondition = "(`" + _this12.type + "`.deleted = false OR `" + _this12.type + "`.deleted IS MISSING)";
|
|
1108
1178
|
|
|
1109
1179
|
if (args.is_full_text === "true" || args.is_custom_query === "true") {
|
|
1110
|
-
query = args.query;
|
|
1180
|
+
query = args.query || "";
|
|
1181
|
+
} else if (args.filters) {
|
|
1182
|
+
query = _this12.makeQueryFromFilter(args.filters);
|
|
1183
|
+
} else if (args.query) {
|
|
1184
|
+
query = args.query + " AND " + deletedCondition;
|
|
1111
1185
|
} else {
|
|
1112
|
-
query =
|
|
1186
|
+
query = deletedCondition;
|
|
1113
1187
|
}
|
|
1114
1188
|
|
|
1115
|
-
if ((0, _Security.hasSQLInjection)(query))
|
|
1116
|
-
return [];
|
|
1117
|
-
}
|
|
1189
|
+
if ((0, _Security.hasSQLInjection)(query)) return []; // LIMIT/OFFSET/SORT
|
|
1118
1190
|
|
|
1119
1191
|
args.limit = Number(args.limit) || undefined;
|
|
1120
1192
|
args.offset = Number(args.offset) || 0;
|
|
@@ -1128,15 +1200,12 @@ class SpiceModel {
|
|
|
1128
1200
|
var results;
|
|
1129
1201
|
|
|
1130
1202
|
if (_this12.shouldUseCache(_this12.type)) {
|
|
1131
|
-
var
|
|
1132
|
-
results =
|
|
1203
|
+
var cached = yield _this12.getCacheProviderObject(_this12.type).get(cacheKey);
|
|
1204
|
+
results = cached == null ? void 0 : cached.value;
|
|
1205
|
+
var isEmpty = (cached == null ? void 0 : cached.value) === undefined;
|
|
1206
|
+
var refresh = yield _this12.shouldForceRefresh(cached);
|
|
1133
1207
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
var shouldRefresh = yield _this12.shouldForceRefresh(cachedResults); // Always force a refresh for "workflow" type
|
|
1137
|
-
|
|
1138
|
-
if (isCacheEmpty || shouldRefresh) {
|
|
1139
|
-
// Force a database read and cache update
|
|
1208
|
+
if (isEmpty || refresh) {
|
|
1140
1209
|
results = yield _this12.fetchResults(args, query);
|
|
1141
1210
|
|
|
1142
1211
|
_this12.getCacheProviderObject(_this12.type).set(cacheKey, {
|
|
@@ -1146,7 +1215,8 @@ class SpiceModel {
|
|
|
1146
1215
|
}
|
|
1147
1216
|
} else {
|
|
1148
1217
|
results = yield _this12.fetchResults(args, query);
|
|
1149
|
-
}
|
|
1218
|
+
} // Serializer still handles class-based refs and value_field
|
|
1219
|
+
|
|
1150
1220
|
|
|
1151
1221
|
if (args.skip_read_serialize !== true && args.skip_serialize !== true) {
|
|
1152
1222
|
results.data = yield _this12.do_serialize(results.data, "read", {}, args, (yield _this12.propsToBeRemoved(results.data)));
|
|
@@ -1180,12 +1250,12 @@ class SpiceModel {
|
|
|
1180
1250
|
})();
|
|
1181
1251
|
}
|
|
1182
1252
|
|
|
1183
|
-
addHook(
|
|
1253
|
+
addHook(_ref3) {
|
|
1184
1254
|
var {
|
|
1185
1255
|
operation,
|
|
1186
1256
|
when,
|
|
1187
1257
|
execute
|
|
1188
|
-
} =
|
|
1258
|
+
} = _ref3;
|
|
1189
1259
|
|
|
1190
1260
|
this[_hooks][operation][when].push(execute);
|
|
1191
1261
|
}
|
|
@@ -1362,11 +1432,11 @@ class SpiceModel {
|
|
|
1362
1432
|
})();
|
|
1363
1433
|
}
|
|
1364
1434
|
|
|
1365
|
-
addModifier(
|
|
1435
|
+
addModifier(_ref4) {
|
|
1366
1436
|
var {
|
|
1367
1437
|
when,
|
|
1368
1438
|
execute
|
|
1369
|
-
} =
|
|
1439
|
+
} = _ref4;
|
|
1370
1440
|
|
|
1371
1441
|
if (this[_serializers][when]) {
|
|
1372
1442
|
this[_serializers][when]["modifiers"].push(execute);
|
|
@@ -11,6 +11,8 @@ function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return
|
|
|
11
11
|
|
|
12
12
|
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
13
13
|
|
|
14
|
+
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
15
|
+
|
|
14
16
|
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
|
|
15
17
|
|
|
16
18
|
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
|
|
@@ -66,55 +68,154 @@ class RestHelper {
|
|
|
66
68
|
|
|
67
69
|
static send_download(ctx, next) {
|
|
68
70
|
return _asyncToGenerator(function* () {
|
|
69
|
-
|
|
70
|
-
if (!fs.existsSync(dir)) {
|
|
71
|
-
|
|
71
|
+
var makeDirectory = dir => {
|
|
72
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, {
|
|
73
|
+
recursive: true
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
var deepTrimStrings = val => {
|
|
78
|
+
if (Array.isArray(val)) return val.map(deepTrimStrings);
|
|
79
|
+
|
|
80
|
+
if (val && typeof val === "object") {
|
|
81
|
+
var out = {};
|
|
82
|
+
|
|
83
|
+
for (var [k, v] of Object.entries(val)) {
|
|
84
|
+
out[k] = deepTrimStrings(v);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return out;
|
|
72
88
|
}
|
|
73
|
-
}
|
|
74
89
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
var include_id = ctx.request.query.include_id;
|
|
78
|
-
var content;
|
|
90
|
+
return typeof val === "string" ? val.trim() : val;
|
|
91
|
+
};
|
|
79
92
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
93
|
+
var stripTopLevel = (obj, _ref) => {
|
|
94
|
+
var {
|
|
95
|
+
removeId
|
|
96
|
+
} = _ref;
|
|
97
|
+
if (Array.isArray(obj)) return obj.map(i => stripTopLevel(i, {
|
|
98
|
+
removeId
|
|
99
|
+
}));
|
|
84
100
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
delete item.id;
|
|
88
|
-
}
|
|
101
|
+
if (obj && typeof obj === "object") {
|
|
102
|
+
var copy = _extends({}, obj);
|
|
89
103
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
104
|
+
if (removeId) delete copy.id;
|
|
105
|
+
delete copy._permissions_;
|
|
106
|
+
return copy;
|
|
107
|
+
}
|
|
93
108
|
|
|
94
|
-
|
|
109
|
+
return obj;
|
|
110
|
+
};
|
|
95
111
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
ctx.set("content-type", "text/csv");
|
|
101
|
-
} else {
|
|
102
|
-
content = JSON.stringify(ctx.data);
|
|
103
|
-
ctx.set("content-type", "application/json");
|
|
112
|
+
var normalizeEmptyArraysForCsv = val => {
|
|
113
|
+
if (Array.isArray(val)) {
|
|
114
|
+
if (val.length === 0) return undefined;
|
|
115
|
+
return val.map(v => normalizeEmptyArraysForCsv(v));
|
|
104
116
|
}
|
|
105
117
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
118
|
+
if (val && typeof val === "object") {
|
|
119
|
+
var out = {};
|
|
120
|
+
|
|
121
|
+
for (var [k, v] of Object.entries(val)) {
|
|
122
|
+
var nv = normalizeEmptyArraysForCsv(v);
|
|
123
|
+
if (nv !== undefined) out[k] = nv;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return val;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
var safeJSONStringify = function safeJSONStringify(value, space) {
|
|
133
|
+
if (space === void 0) {
|
|
134
|
+
space = 2;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
var seen = new WeakSet();
|
|
138
|
+
|
|
139
|
+
var replacer = (_k, v) => {
|
|
140
|
+
if (typeof v === "bigint") return v.toString();
|
|
141
|
+
if (v instanceof Date) return v.toISOString();
|
|
142
|
+
|
|
143
|
+
if (v && typeof v === "object") {
|
|
144
|
+
if (seen.has(v)) return "[Circular]";
|
|
145
|
+
seen.add(v);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return v;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return JSON.stringify(value, replacer, space);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
var download_type = (ctx.request.query.format || "csv").toLowerCase();
|
|
156
|
+
var include_id = ctx.request.query.include_id;
|
|
157
|
+
|
|
158
|
+
var original = _lodash.default.cloneDeep(ctx.data);
|
|
159
|
+
|
|
160
|
+
var trimmed = deepTrimStrings(original);
|
|
161
|
+
var cleaned = stripTopLevel(trimmed, {
|
|
162
|
+
removeId: !include_id || include_id === "false"
|
|
111
163
|
});
|
|
112
|
-
|
|
113
|
-
|
|
164
|
+
var filename, filePath;
|
|
165
|
+
|
|
166
|
+
if (download_type === "csv") {
|
|
167
|
+
var _ret = yield* function* () {
|
|
168
|
+
var {
|
|
169
|
+
flatten
|
|
170
|
+
} = yield Promise.resolve().then(() => _interopRequireWildcard(require("flat")));
|
|
171
|
+
var rows = Array.isArray(cleaned) ? cleaned : [cleaned];
|
|
172
|
+
var csvReady = rows.map(normalizeEmptyArraysForCsv);
|
|
173
|
+
var flatRows = csvReady.map(row => flatten(row, {
|
|
174
|
+
safe: false,
|
|
175
|
+
delimiter: "."
|
|
176
|
+
}));
|
|
177
|
+
var fieldSet = new Set();
|
|
178
|
+
|
|
179
|
+
for (var r of flatRows) {
|
|
180
|
+
Object.keys(r).forEach(k => fieldSet.add(k));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
var fields = Array.from(fieldSet).sort((a, b) => a.localeCompare(b, undefined, {
|
|
184
|
+
numeric: true,
|
|
185
|
+
sensitivity: "base"
|
|
186
|
+
}));
|
|
187
|
+
var csv = parse(flatRows, {
|
|
188
|
+
fields,
|
|
189
|
+
defaultValue: "",
|
|
190
|
+
excelStrings: true
|
|
191
|
+
});
|
|
192
|
+
makeDirectory("./storage/exports/csv");
|
|
193
|
+
filename = RestHelper.makeid(9) + ".csv";
|
|
194
|
+
filePath = path.resolve("./storage/exports/csv/" + filename);
|
|
195
|
+
yield fs.promises.writeFile(filePath, csv, "utf8");
|
|
196
|
+
ctx.set("Content-Disposition", "attachment; filename=\"" + filename + "\"");
|
|
197
|
+
ctx.type = "text/csv; charset=utf-8";
|
|
198
|
+
ctx.status = 200;
|
|
199
|
+
ctx.body = fs.createReadStream(filePath);
|
|
200
|
+
return {
|
|
201
|
+
v: void 0
|
|
202
|
+
};
|
|
203
|
+
}();
|
|
204
|
+
|
|
205
|
+
if (typeof _ret === "object") return _ret.v;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
var jsonText = safeJSONStringify(cleaned, 2);
|
|
209
|
+
makeDirectory("./storage/exports/json");
|
|
210
|
+
filename = RestHelper.makeid(9) + ".json";
|
|
211
|
+
filePath = path.resolve("./storage/exports/json/" + filename);
|
|
212
|
+
yield fs.promises.writeFile(filePath, jsonText, "utf8");
|
|
213
|
+
ctx.set("Content-Disposition", "attachment; filename=\"" + filename + "\"");
|
|
214
|
+
ctx.type = "application/json; charset=utf-8";
|
|
114
215
|
ctx.status = 200;
|
|
115
|
-
ctx.body = fs.createReadStream(
|
|
216
|
+
ctx.body = fs.createReadStream(filePath);
|
|
116
217
|
} catch (e) {
|
|
117
|
-
console.
|
|
218
|
+
console.error(e.stack);
|
|
118
219
|
ctx.status = 400;
|
|
119
220
|
ctx.body = RestHelper.prepare_response(RestHelper.FAILURE, e);
|
|
120
221
|
}
|
package/package.json
CHANGED
|
@@ -41,57 +41,112 @@ export default class RestHelper {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
static async send_download(ctx, next) {
|
|
44
|
-
|
|
45
|
-
if (!fs.existsSync(dir)) {
|
|
46
|
-
|
|
44
|
+
const makeDirectory = (dir) => {
|
|
45
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const deepTrimStrings = (val) => {
|
|
49
|
+
if (Array.isArray(val)) return val.map(deepTrimStrings);
|
|
50
|
+
if (val && typeof val === "object") {
|
|
51
|
+
const out = {};
|
|
52
|
+
for (const [k, v] of Object.entries(val)) out[k] = deepTrimStrings(v);
|
|
53
|
+
return out;
|
|
47
54
|
}
|
|
48
|
-
|
|
55
|
+
return typeof val === "string" ? val.trim() : val;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const stripTopLevel = (obj, { removeId }) => {
|
|
59
|
+
if (Array.isArray(obj)) return obj.map((i) => stripTopLevel(i, { removeId }));
|
|
60
|
+
if (obj && typeof obj === "object") {
|
|
61
|
+
const copy = { ...obj };
|
|
62
|
+
if (removeId) delete copy.id;
|
|
63
|
+
delete copy._permissions_;
|
|
64
|
+
return copy;
|
|
65
|
+
}
|
|
66
|
+
return obj;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const normalizeEmptyArraysForCsv = (val) => {
|
|
70
|
+
if (Array.isArray(val)) {
|
|
71
|
+
if (val.length === 0) return undefined;
|
|
72
|
+
return val.map((v) => normalizeEmptyArraysForCsv(v));
|
|
73
|
+
}
|
|
74
|
+
if (val && typeof val === "object") {
|
|
75
|
+
const out = {};
|
|
76
|
+
for (const [k, v] of Object.entries(val)) {
|
|
77
|
+
const nv = normalizeEmptyArraysForCsv(v);
|
|
78
|
+
if (nv !== undefined) out[k] = nv;
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
return val;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const safeJSONStringify = (value, space = 2) => {
|
|
86
|
+
const seen = new WeakSet();
|
|
87
|
+
const replacer = (_k, v) => {
|
|
88
|
+
if (typeof v === "bigint") return v.toString();
|
|
89
|
+
if (v instanceof Date) return v.toISOString();
|
|
90
|
+
if (v && typeof v === "object") {
|
|
91
|
+
if (seen.has(v)) return "[Circular]";
|
|
92
|
+
seen.add(v);
|
|
93
|
+
}
|
|
94
|
+
return v;
|
|
95
|
+
};
|
|
96
|
+
return JSON.stringify(value, replacer, space);
|
|
97
|
+
};
|
|
98
|
+
|
|
49
99
|
try {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
100
|
+
const download_type = (ctx.request.query.format || "csv").toLowerCase();
|
|
101
|
+
const include_id = ctx.request.query.include_id;
|
|
102
|
+
|
|
103
|
+
const original = _.cloneDeep(ctx.data);
|
|
104
|
+
const trimmed = deepTrimStrings(original);
|
|
105
|
+
const cleaned = stripTopLevel(trimmed, { removeId: !include_id || include_id === "false" });
|
|
106
|
+
|
|
107
|
+
let filename, filePath;
|
|
108
|
+
|
|
109
|
+
if (download_type === "csv") {
|
|
54
110
|
const { flatten } = await import("flat");
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
_.keys(_.first(items)),
|
|
65
|
-
_.keys(_.last(items)),
|
|
66
|
-
_.keys(_.nth(items.length / 2))
|
|
111
|
+
const rows = Array.isArray(cleaned) ? cleaned : [cleaned];
|
|
112
|
+
|
|
113
|
+
const csvReady = rows.map(normalizeEmptyArraysForCsv);
|
|
114
|
+
const flatRows = csvReady.map((row) => flatten(row, { safe: false, delimiter: "." }));
|
|
115
|
+
|
|
116
|
+
const fieldSet = new Set();
|
|
117
|
+
for (const r of flatRows) Object.keys(r).forEach((k) => fieldSet.add(k));
|
|
118
|
+
const fields = Array.from(fieldSet).sort((a, b) =>
|
|
119
|
+
a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" })
|
|
67
120
|
);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
121
|
+
|
|
122
|
+
const csv = parse(flatRows, { fields, defaultValue: "", excelStrings: true });
|
|
123
|
+
|
|
124
|
+
makeDirectory(`./storage/exports/csv`);
|
|
125
|
+
filename = `${RestHelper.makeid(9)}.csv`;
|
|
126
|
+
filePath = path.resolve(`./storage/exports/csv/${filename}`);
|
|
127
|
+
await fs.promises.writeFile(filePath, csv, "utf8");
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
ctx.set("Content-Disposition", `attachment; filename="${filename}"`);
|
|
131
|
+
ctx.type = "text/csv; charset=utf-8";
|
|
132
|
+
ctx.status = 200;
|
|
133
|
+
ctx.body = fs.createReadStream(filePath);
|
|
134
|
+
return;
|
|
76
135
|
}
|
|
77
136
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
if (err) throw err;
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
ctx.set("content-disposition", "attachment");
|
|
90
|
-
ctx.response.attachment(file);
|
|
137
|
+
const jsonText = safeJSONStringify(cleaned, 2);
|
|
138
|
+
|
|
139
|
+
makeDirectory(`./storage/exports/json`);
|
|
140
|
+
filename = `${RestHelper.makeid(9)}.json`;
|
|
141
|
+
filePath = path.resolve(`./storage/exports/json/${filename}`);
|
|
142
|
+
await fs.promises.writeFile(filePath, jsonText, "utf8");
|
|
143
|
+
ctx.set("Content-Disposition", `attachment; filename="${filename}"`);
|
|
144
|
+
ctx.type = "application/json; charset=utf-8";
|
|
91
145
|
ctx.status = 200;
|
|
92
|
-
ctx.body = fs.createReadStream(
|
|
146
|
+
ctx.body = fs.createReadStream(filePath);
|
|
147
|
+
|
|
93
148
|
} catch (e) {
|
|
94
|
-
console.
|
|
149
|
+
console.error(e.stack);
|
|
95
150
|
ctx.status = 400;
|
|
96
151
|
ctx.body = RestHelper.prepare_response(RestHelper.FAILURE, e);
|
|
97
152
|
}
|
|
@@ -203,3 +258,4 @@ export default class RestHelper {
|
|
|
203
258
|
return false;
|
|
204
259
|
}
|
|
205
260
|
}
|
|
261
|
+
|