spice-js 2.6.66 → 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.
@@ -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
- function makeDirectory(dir) {
70
- if (!fs.existsSync(dir)) {
71
- fs.mkdirSync(dir);
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
- try {
76
- var download_type = ctx.request.query.format || "csv";
77
- var include_id = ctx.request.query.include_id;
78
- var content;
90
+ return typeof val === "string" ? val.trim() : val;
91
+ };
79
92
 
80
- if (download_type == "csv") {
81
- var {
82
- flatten
83
- } = yield Promise.resolve().then(() => _interopRequireWildcard(require("flat")));
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
- var items = _lodash.default.map(ctx.data, item => {
86
- if (!include_id || include_id == "false") {
87
- delete item.id;
88
- }
101
+ if (obj && typeof obj === "object") {
102
+ var copy = _extends({}, obj);
89
103
 
90
- delete item._permissions_;
91
- return flatten(item);
92
- });
104
+ if (removeId) delete copy.id;
105
+ delete copy._permissions_;
106
+ return copy;
107
+ }
93
108
 
94
- var fields = _lodash.default.union(_lodash.default.keys(_lodash.default.first(items)), _lodash.default.keys(_lodash.default.last(items)), _lodash.default.keys(_lodash.default.nth(items.length / 2)));
109
+ return obj;
110
+ };
95
111
 
96
- var opts = {
97
- fields
98
- };
99
- content = parse(items, opts);
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
- makeDirectory("./storage/exports");
107
- makeDirectory("./storage/exports/" + download_type + "/");
108
- var file = path.resolve("./storage/exports/" + download_type + "/" + RestHelper.makeid(9) + "." + download_type);
109
- yield fs.writeFile(file, content, function (err) {
110
- if (err) throw err;
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
- ctx.set("content-disposition", "attachment");
113
- ctx.response.attachment(file);
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(file);
216
+ ctx.body = fs.createReadStream(filePath);
116
217
  } catch (e) {
117
- console.log(e.stack);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spice-js",
3
- "version": "2.6.66",
3
+ "version": "2.6.67",
4
4
  "description": "spice",
5
5
  "main": "build/index.js",
6
6
  "repository": {
@@ -41,57 +41,112 @@ export default class RestHelper {
41
41
  }
42
42
 
43
43
  static async send_download(ctx, next) {
44
- function makeDirectory(dir) {
45
- if (!fs.existsSync(dir)) {
46
- fs.mkdirSync(dir);
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
- let download_type = ctx.request.query.format || "csv";
51
- let include_id = ctx.request.query.include_id;
52
- let content;
53
- if (download_type == "csv") {
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
- let items = _.map(ctx.data, (item) => {
57
- if (!include_id || include_id == "false") {
58
- delete item.id;
59
- }
60
- delete item._permissions_;
61
- return flatten(item);
62
- });
63
- let fields = _.union(
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
- const opts = {
69
- fields,
70
- };
71
- content = parse(items, opts);
72
- ctx.set("content-type", "text/csv");
73
- } else {
74
- content = JSON.stringify(ctx.data);
75
- ctx.set("content-type", "application/json");
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
- makeDirectory(`./storage/exports`);
79
- makeDirectory(`./storage/exports/${download_type}/`);
80
- let file = path.resolve(
81
- `./storage/exports/${download_type}/${RestHelper.makeid(
82
- 9
83
- )}.${download_type}`
84
- );
85
- await fs.writeFile(file, content, function (err) {
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(file);
146
+ ctx.body = fs.createReadStream(filePath);
147
+
93
148
  } catch (e) {
94
- console.log(e.stack);
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
+