@galadrim/galadmin 0.1.226 → 0.1.227
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 +25 -0
- package/dist/api.js +354 -170
- package/dist/api.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/config.d.ts +6 -0
- package/package.json +2 -1
- package/src/api.ts +1227 -916
- package/src/front/yarn.lock +10795 -0
- package/src/types/config.ts +6 -0
package/src/api.ts
CHANGED
|
@@ -6,1027 +6,1338 @@ import * as crypto from "crypto";
|
|
|
6
6
|
import * as mime from "mime";
|
|
7
7
|
|
|
8
8
|
import { S3, Endpoint } from "aws-sdk";
|
|
9
|
-
import
|
|
9
|
+
import { Storage } from "@google-cloud/storage";
|
|
10
|
+
import sizeOf from "image-size";
|
|
10
11
|
import * as serializeJs from "serialize-javascript";
|
|
11
|
-
import * as SftpClient from
|
|
12
|
+
import * as SftpClient from "ssh2-sftp-client";
|
|
12
13
|
|
|
13
14
|
import * as rollup from "rollup";
|
|
14
|
-
import babel from
|
|
15
|
-
import resolve from
|
|
15
|
+
import babel from "@rollup/plugin-babel";
|
|
16
|
+
import resolve from "@rollup/plugin-node-resolve";
|
|
16
17
|
import axios from "axios";
|
|
17
|
-
import { v4 as uuidv4 } from
|
|
18
|
+
import { v4 as uuidv4 } from "uuid";
|
|
18
19
|
|
|
19
20
|
function generateUniqueIdForObject(obj) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const str = JSON.stringify(obj);
|
|
22
|
+
const hash = crypto.createHash("sha256").update(str).digest("hex");
|
|
23
|
+
return hash;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export default (db, config, user) => {
|
|
26
|
-
|
|
27
|
+
const app = express();
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
app.use(async (req, res, next) => {
|
|
30
|
+
if (!config.enriched) {
|
|
31
|
+
await enrichConfig(config, db);
|
|
32
|
+
}
|
|
33
|
+
next();
|
|
34
|
+
});
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
36
|
+
async function getRecords(params, computeCount = false) {
|
|
37
|
+
let queryString = "";
|
|
38
|
+
if (params.filters) {
|
|
39
|
+
queryString = " WHERE (" + params.filters + ")";
|
|
40
|
+
}
|
|
41
|
+
if (params.query) {
|
|
42
|
+
const columns = params.columns;
|
|
43
|
+
let isNewCondition = true;
|
|
44
|
+
for (let i in columns) {
|
|
45
|
+
if (
|
|
46
|
+
columns[i].searchable !== false &&
|
|
47
|
+
(columns[i].searchableName || columns[i].name)
|
|
48
|
+
) {
|
|
49
|
+
if (queryString.length == 0) {
|
|
50
|
+
queryString += " WHERE (";
|
|
51
|
+
} else if (isNewCondition) {
|
|
52
|
+
queryString += " AND (";
|
|
53
|
+
} else {
|
|
54
|
+
queryString += " OR ";
|
|
55
|
+
}
|
|
56
|
+
if (columns[i].searchableName) {
|
|
57
|
+
// on ne doit pas ajouter de backticks sur le searchableName car celui-ci peut être une fonction
|
|
58
|
+
// exemple : searchableName = "CONCAT_WS(' ', firstname, lastname)"
|
|
59
|
+
queryString += columns[i].searchableName;
|
|
60
|
+
} else if (columns[i].name) {
|
|
61
|
+
queryString += columns[i].name
|
|
62
|
+
.split(".")
|
|
63
|
+
.map((x) => (db.dbType === "POSTGRESQL" ? x : "`" + x + "`"))
|
|
64
|
+
.join(".");
|
|
65
|
+
}
|
|
66
|
+
queryString += " LIKE '%" + params.query.replace(/'/g, "\\'") + "%'";
|
|
67
|
+
isNewCondition = false;
|
|
64
68
|
}
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
}
|
|
70
|
+
queryString += ")";
|
|
71
|
+
}
|
|
72
|
+
const limit = params.allRecords ? 999999 : parseInt(params.limit);
|
|
73
|
+
const query =
|
|
74
|
+
params.customQuery ||
|
|
75
|
+
`SELECT
|
|
67
76
|
${params.selectExpression || "*"}
|
|
68
77
|
FROM ${params.table}
|
|
69
78
|
${params.join || ""}
|
|
70
79
|
${queryString}
|
|
71
80
|
${params.groupBy ? "GROUP BY " + params.groupBy : ""}
|
|
72
81
|
${params.having ? " HAVING " + params.having : ""}
|
|
73
|
-
${params.orderBy || params.primaryId ? " ORDER BY " + (params.orderBy || `${params.table}.${params.primaryId}`) : ""}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (computeCount) {
|
|
83
|
-
const { err: countErr, rows: countRows } = await db.query(`SELECT COUNT(*) AS count FROM (${query}) s`)
|
|
84
|
-
if (countErr) {
|
|
85
|
-
const { rows: allRows } = await db.query(query)
|
|
86
|
-
count = allRows.length
|
|
87
|
-
} else {
|
|
88
|
-
count = countRows?.[0]?.count || 0
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
rows,
|
|
94
|
-
queryString,
|
|
95
|
-
input: {
|
|
96
|
-
query,
|
|
97
|
-
params
|
|
98
|
-
},
|
|
99
|
-
count
|
|
100
|
-
}
|
|
82
|
+
${params.orderBy || params.primaryId ? " ORDER BY " + (params.orderBy || `${params.table}.${params.primaryId}`) : ""}`;
|
|
83
|
+
const { err, rows } = await db.query(
|
|
84
|
+
`${query} LIMIT ${limit} OFFSET ${Math.max((params.page - 1) * limit, 0)}`
|
|
85
|
+
);
|
|
86
|
+
if (err) {
|
|
87
|
+
console.error(err);
|
|
88
|
+
return {
|
|
89
|
+
error: err,
|
|
90
|
+
};
|
|
101
91
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
92
|
+
let count = undefined;
|
|
93
|
+
if (computeCount) {
|
|
94
|
+
const { err: countErr, rows: countRows } = await db.query(
|
|
95
|
+
`SELECT COUNT(*) AS count FROM (${query}) s`
|
|
96
|
+
);
|
|
97
|
+
if (countErr) {
|
|
98
|
+
const { rows: allRows } = await db.query(query);
|
|
99
|
+
count = allRows.length;
|
|
100
|
+
} else {
|
|
101
|
+
count = countRows?.[0]?.count || 0;
|
|
102
|
+
}
|
|
108
103
|
}
|
|
109
104
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
})
|
|
105
|
+
return {
|
|
106
|
+
rows,
|
|
107
|
+
queryString,
|
|
108
|
+
input: {
|
|
109
|
+
query,
|
|
110
|
+
params,
|
|
111
|
+
},
|
|
112
|
+
count,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
121
115
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
babel({
|
|
131
|
-
configFile: path.join(__dirname, "../babel.config"),
|
|
132
|
-
babelHelpers: "bundled"
|
|
133
|
-
})
|
|
134
|
-
],
|
|
135
|
-
external
|
|
136
|
-
});
|
|
137
|
-
const { output } = await bundle.generate({ format: 'cjs', exports: 'auto' });
|
|
138
|
-
res.send(output[0].code)
|
|
139
|
-
} catch (err) {
|
|
140
|
-
console.error(err)
|
|
141
|
-
}
|
|
142
|
-
})
|
|
116
|
+
async function findRecordByPosition(params) {
|
|
117
|
+
return await getRecords(
|
|
118
|
+
Object.assign(params, {
|
|
119
|
+
page: parseInt(params.position) + parseInt(params.offset),
|
|
120
|
+
limit: 1,
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
}
|
|
143
124
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
125
|
+
app.get("/config", async (req, res) => {
|
|
126
|
+
try {
|
|
127
|
+
const { mysql, auth, sessionSecret, ...safeConfig } = config;
|
|
128
|
+
res.send({
|
|
129
|
+
config: serializeJs(safeConfig),
|
|
130
|
+
});
|
|
131
|
+
} catch (e) {
|
|
132
|
+
console.error(e);
|
|
133
|
+
res.sendStatus(500);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
154
136
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
137
|
+
app.get("/customComponent", async (req, res) => {
|
|
138
|
+
try {
|
|
139
|
+
const pkg = JSON.parse(
|
|
140
|
+
fs.readFileSync(
|
|
141
|
+
path.join(__dirname, "../src/front/package.json"),
|
|
142
|
+
"utf-8"
|
|
143
|
+
)
|
|
144
|
+
);
|
|
145
|
+
const external = Object.keys(pkg.dependencies || {});
|
|
146
|
+
const bundle = await rollup.rollup({
|
|
147
|
+
input: path.join(
|
|
148
|
+
__dirname,
|
|
149
|
+
"../../",
|
|
150
|
+
req.query.componentPath as string
|
|
151
|
+
),
|
|
152
|
+
plugins: [
|
|
153
|
+
resolve(),
|
|
154
|
+
babel({
|
|
155
|
+
configFile: path.join(__dirname, "../babel.config"),
|
|
156
|
+
babelHelpers: "bundled",
|
|
157
|
+
}),
|
|
158
|
+
],
|
|
159
|
+
external,
|
|
160
|
+
});
|
|
161
|
+
const { output } = await bundle.generate({
|
|
162
|
+
format: "cjs",
|
|
163
|
+
exports: "auto",
|
|
164
|
+
});
|
|
165
|
+
res.send(output[0].code);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
console.error(err);
|
|
174
168
|
}
|
|
169
|
+
});
|
|
175
170
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
filters: sqlAnd([
|
|
187
|
-
column.filters,
|
|
188
|
-
req.body.query ? `${column.searchableName} LIKE '${column.wholeSearch ? '%' : ''}${queryEscaped}%'` : null,
|
|
189
|
-
column.filtersFromUser ? column.filtersFromUser(user.userId, user.role) : null
|
|
190
|
-
]),
|
|
191
|
-
join: column.join,
|
|
192
|
-
selectExpression: column.selectExpression,
|
|
193
|
-
orderBy: column.orderBy,
|
|
194
|
-
limit: column.limit || 1000
|
|
195
|
-
})
|
|
196
|
-
if (error) {
|
|
197
|
-
return res.status(500).send(error)
|
|
198
|
-
}
|
|
199
|
-
const options = await Promise.all(records.map(async (record) => {
|
|
200
|
-
let label;
|
|
201
|
-
if (column.formatForeignValue) {
|
|
202
|
-
label = await column.formatForeignValue(record);
|
|
203
|
-
} else if (column.formatValue) {
|
|
204
|
-
label = await column.formatValue(record);
|
|
205
|
-
} else if (column.formattedValue) {
|
|
206
|
-
label = evalExpression(column.formattedValue, record)
|
|
207
|
-
} else {
|
|
208
|
-
label = record[column.name]
|
|
209
|
-
}
|
|
210
|
-
const value = record[column.foreignName] || record[column.name]
|
|
211
|
-
return ({
|
|
212
|
-
value,
|
|
213
|
-
label
|
|
214
|
-
})
|
|
215
|
-
}))
|
|
216
|
-
res.send(options)
|
|
217
|
-
})
|
|
171
|
+
app.post("/adjacentRecordId", async function (req, res, next) {
|
|
172
|
+
const { rows, error } = await findRecordByPosition(req.body);
|
|
173
|
+
if (error) {
|
|
174
|
+
return res.status(500).send(error);
|
|
175
|
+
}
|
|
176
|
+
const recordId = rows[0] ? rows[0][req.body.primaryId] : null;
|
|
177
|
+
res.send({
|
|
178
|
+
recordId: recordId,
|
|
179
|
+
});
|
|
180
|
+
});
|
|
218
181
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
having: subview?.having,
|
|
234
|
-
customQuery: view.customQuery,
|
|
235
|
-
join: view.join,
|
|
236
|
-
selectExpression: view.selectExpression,
|
|
237
|
-
orderBy: [orderBy ? `${orderBy} ${isDesc ? "DESC" : "ASC"}` : null, view.primaryId].filter(x => x).join(", "),
|
|
238
|
-
primaryId: view.primaryId,
|
|
239
|
-
query: req.body.query,
|
|
240
|
-
groupBy: view.groupBy,
|
|
241
|
-
columns: view.columns,
|
|
242
|
-
limit: view.limit || 10,
|
|
243
|
-
}, true)
|
|
244
|
-
if (error) {
|
|
245
|
-
return res.status(500).send(error)
|
|
182
|
+
const sqlAnd = (conditions) => {
|
|
183
|
+
const existingConditions = [];
|
|
184
|
+
for (const condition of conditions) {
|
|
185
|
+
if (condition && condition.length > 0) {
|
|
186
|
+
existingConditions.push(condition);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (existingConditions.length <= 1) {
|
|
190
|
+
return existingConditions[0] || null;
|
|
191
|
+
} else {
|
|
192
|
+
let result = "";
|
|
193
|
+
for (let i = 0; i < existingConditions.length; i++) {
|
|
194
|
+
if (i > 0) {
|
|
195
|
+
result += " AND ";
|
|
246
196
|
}
|
|
197
|
+
result += "(" + existingConditions[i] + ")";
|
|
198
|
+
}
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
247
202
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
203
|
+
app.post("/foreignSelectRecords", async (req, res, next) => {
|
|
204
|
+
const view = config.views[req.body.viewIndex];
|
|
205
|
+
const column = [
|
|
206
|
+
...view.columns,
|
|
207
|
+
...(view.recordViewPanels
|
|
208
|
+
?.map((x) => x?.createConfig?.columns)
|
|
209
|
+
?.filter((x) => x)
|
|
210
|
+
?.flat() || []),
|
|
211
|
+
].find((x) => x.__id__ === req.body.columnId);
|
|
212
|
+
const queryEscaped =
|
|
213
|
+
db.dbType === "POSTGRESQL"
|
|
214
|
+
? req.body.query?.replace(/'/g, "''")
|
|
215
|
+
: req.body.query?.replace(/'/g, "\\'");
|
|
216
|
+
const { rows: records, error } = await getRecords({
|
|
217
|
+
table: column.table,
|
|
218
|
+
page: 1,
|
|
219
|
+
filters: sqlAnd([
|
|
220
|
+
column.filters,
|
|
221
|
+
req.body.query
|
|
222
|
+
? `${column.searchableName} LIKE '${column.wholeSearch ? "%" : ""}${queryEscaped}%'`
|
|
223
|
+
: null,
|
|
224
|
+
column.filtersFromUser
|
|
225
|
+
? column.filtersFromUser(user.userId, user.role)
|
|
226
|
+
: null,
|
|
227
|
+
]),
|
|
228
|
+
join: column.join,
|
|
229
|
+
selectExpression: column.selectExpression,
|
|
230
|
+
orderBy: column.orderBy,
|
|
231
|
+
limit: column.limit || 1000,
|
|
232
|
+
});
|
|
233
|
+
if (error) {
|
|
234
|
+
return res.status(500).send(error);
|
|
235
|
+
}
|
|
236
|
+
const options = await Promise.all(
|
|
237
|
+
records.map(async (record) => {
|
|
238
|
+
let label;
|
|
239
|
+
if (column.formatForeignValue) {
|
|
240
|
+
label = await column.formatForeignValue(record);
|
|
241
|
+
} else if (column.formatValue) {
|
|
242
|
+
label = await column.formatValue(record);
|
|
243
|
+
} else if (column.formattedValue) {
|
|
244
|
+
label = evalExpression(column.formattedValue, record);
|
|
245
|
+
} else {
|
|
246
|
+
label = record[column.name];
|
|
254
247
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
async function getFormattedValue(column, record) {
|
|
265
|
-
let value;
|
|
266
|
-
try {
|
|
248
|
+
const value = record[column.foreignName] || record[column.name];
|
|
249
|
+
return {
|
|
250
|
+
value,
|
|
251
|
+
label,
|
|
252
|
+
};
|
|
253
|
+
})
|
|
254
|
+
);
|
|
255
|
+
res.send(options);
|
|
256
|
+
});
|
|
267
257
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
258
|
+
app.post("/records", async function (req, res, next) {
|
|
259
|
+
const view = config.views[req.body.viewIndex];
|
|
260
|
+
const isDesc = req.body.isDesc === null ? view.isDesc : req.body.isDesc;
|
|
261
|
+
const subview =
|
|
262
|
+
view.subviews && req.body.subviewIndex !== undefined
|
|
263
|
+
? view.subviews[req.body.subviewIndex]
|
|
264
|
+
: null;
|
|
265
|
+
const orderBy = req.body.orderBy || subview?.orderBy || view.orderBy;
|
|
266
|
+
const {
|
|
267
|
+
rows: records,
|
|
268
|
+
input,
|
|
269
|
+
error,
|
|
270
|
+
count,
|
|
271
|
+
} = await getRecords(
|
|
272
|
+
{
|
|
273
|
+
table: view.tableName,
|
|
274
|
+
page: req.body.page,
|
|
275
|
+
filters: sqlAnd([
|
|
276
|
+
view.filters,
|
|
277
|
+
subview?.filters,
|
|
278
|
+
...req.body.filters,
|
|
279
|
+
view.filtersFromUser
|
|
280
|
+
? view.filtersFromUser(user.userId, user.role)
|
|
281
|
+
: null,
|
|
282
|
+
]),
|
|
283
|
+
having: subview?.having,
|
|
284
|
+
customQuery: view.customQuery,
|
|
285
|
+
join: view.join,
|
|
286
|
+
selectExpression: view.selectExpression,
|
|
287
|
+
orderBy: [
|
|
288
|
+
orderBy ? `${orderBy} ${isDesc ? "DESC" : "ASC"}` : null,
|
|
289
|
+
view.primaryId,
|
|
290
|
+
]
|
|
291
|
+
.filter((x) => x)
|
|
292
|
+
.join(", "),
|
|
293
|
+
primaryId: view.primaryId,
|
|
294
|
+
query: req.body.query,
|
|
295
|
+
groupBy: view.groupBy,
|
|
296
|
+
columns: view.columns,
|
|
297
|
+
limit: view.limit || 10,
|
|
298
|
+
},
|
|
299
|
+
true
|
|
300
|
+
);
|
|
301
|
+
if (error) {
|
|
302
|
+
return res.status(500).send(error);
|
|
284
303
|
}
|
|
285
304
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
for (let key in data) {
|
|
292
|
-
statement = "var " + key + " = " + (typeof data[key] === "string" ? "'" + data[key].replace(/'/g, "\\'").replace(/\n/g, "").replace(/\r/g, "") + "'" : (typeof data[key] === "object" ? JSON.stringify(data[key]) : data[key])) + ";"
|
|
293
|
-
varString += statement
|
|
294
|
-
}
|
|
295
|
-
const result = eval(varString + expression);
|
|
296
|
-
return result;
|
|
297
|
-
} catch (e) {
|
|
298
|
-
console.log("Problème d'évaluation de l'expression", statement)
|
|
299
|
-
console.error(e);
|
|
300
|
-
return "Error"
|
|
305
|
+
for (const column of view.columns) {
|
|
306
|
+
if (column.formatValue) {
|
|
307
|
+
for (const record of records) {
|
|
308
|
+
record[`__formatValue-${column.__id__}__`] =
|
|
309
|
+
await column.formatValue(record);
|
|
301
310
|
}
|
|
311
|
+
}
|
|
302
312
|
}
|
|
313
|
+
res.send({
|
|
314
|
+
records,
|
|
315
|
+
input,
|
|
316
|
+
count,
|
|
317
|
+
});
|
|
318
|
+
});
|
|
303
319
|
|
|
304
|
-
|
|
305
|
-
if (value !== 0 && !value) {
|
|
306
|
-
return "NULL"
|
|
307
|
-
}
|
|
308
|
-
if (value instanceof Date) {
|
|
309
|
-
return value.toISOString()
|
|
310
|
-
}
|
|
311
|
-
if (typeof value === "string") {
|
|
312
|
-
if (dbType === "POSTGRESQL") {
|
|
313
|
-
return "'" + value.replace(/'/g, "''") + "'";
|
|
314
|
-
} else {
|
|
315
|
-
return "'" + value.replace(/'/g, "\\'") + "'"
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
if (typeof value === "object") {
|
|
319
|
-
return "'" + JSON.stringify(value).replace(/'/g, "\\'") + "'"
|
|
320
|
-
}
|
|
321
|
-
if (typeof value === "boolean") {
|
|
322
|
-
return value ? "TRUE" : "FALSE"
|
|
323
|
-
}
|
|
324
|
-
if (typeof value === "number") {
|
|
325
|
-
return value
|
|
326
|
-
}
|
|
320
|
+
const formattedValueToFunction = {};
|
|
327
321
|
|
|
328
|
-
|
|
322
|
+
async function getFormattedValue(column, record) {
|
|
323
|
+
let value;
|
|
324
|
+
try {
|
|
325
|
+
record.options = column.options;
|
|
326
|
+
if (column.formatValueForExport) {
|
|
327
|
+
value = await column.formatValueForExport(record);
|
|
328
|
+
} else if (column.formattedValue) {
|
|
329
|
+
value = evalExpression(column.formattedValue, record);
|
|
330
|
+
} else if (column.formatValue) {
|
|
331
|
+
value = await column.formatValue(record);
|
|
332
|
+
} else if (column.dataType == "tinyint") {
|
|
333
|
+
value = record[column.name] == 1 ? "Oui" : "Non";
|
|
334
|
+
} else {
|
|
335
|
+
value = record[column.name];
|
|
336
|
+
}
|
|
337
|
+
return value === null || typeof value == "undefined" ? "" : value;
|
|
338
|
+
} catch (err) {
|
|
339
|
+
return value || "";
|
|
329
340
|
}
|
|
341
|
+
}
|
|
330
342
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const isDesc = req.body.isDesc === null ? view.isDesc : req.body.isDesc;
|
|
335
|
-
const subview = view.subviews && req.body.subviewIndex !== undefined ? view.subviews[req.body.subviewIndex] : null
|
|
336
|
-
const { rows: records, error } = await getRecords({
|
|
337
|
-
table: view.tableName,
|
|
338
|
-
page: 1,
|
|
339
|
-
allRecords: true,
|
|
340
|
-
filters: sqlAnd([view.filters, subview?.filters, req.body.filters]),
|
|
341
|
-
having: subview?.having,
|
|
342
|
-
customQuery: view.customQuery,
|
|
343
|
-
join: view.join,
|
|
344
|
-
selectExpression: view.selectExpression,
|
|
345
|
-
orderBy: orderBy ? `${orderBy} ${isDesc ? "DESC" : "ASC"}` : null,
|
|
346
|
-
primaryId: view.primaryId,
|
|
347
|
-
query: req.body.query,
|
|
348
|
-
groupBy: view.groupBy,
|
|
349
|
-
columns: view.columns,
|
|
350
|
-
limit: view.limit || 10,
|
|
351
|
-
});
|
|
352
|
-
if (error) {
|
|
353
|
-
return res.status(500).send(error)
|
|
354
|
-
}
|
|
355
|
-
let output = "";
|
|
356
|
-
const columns = view.columns
|
|
357
|
-
for (let j = 0; j < columns.length; j++) {
|
|
358
|
-
if (!columns[j].hideInTable || columns[j].includeInExport) {
|
|
359
|
-
if (j > 0) output += ";"
|
|
360
|
-
output += columns[j].label;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
for (let i in records) {
|
|
364
|
-
const el = records[i];
|
|
365
|
-
output += "\r\n"
|
|
366
|
-
for (let j = 0; j < columns.length; j++) {
|
|
367
|
-
if (!columns[j].hideInTable || columns[j].includeInExport) {
|
|
368
|
-
if (j > 0) output += ";"
|
|
369
|
-
if (columns[j].dataType == 'textarea') {
|
|
370
|
-
output += '"'
|
|
371
|
-
}
|
|
372
|
-
output += await getFormattedValue(columns[j], el)
|
|
373
|
-
if (columns[j].dataType == 'textarea') {
|
|
374
|
-
output += '"'
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
res.send({
|
|
380
|
-
content: output
|
|
381
|
-
})
|
|
382
|
-
})
|
|
383
|
-
|
|
384
|
-
app.post('/recordsImport', async function (req, res) {
|
|
385
|
-
const form = new multiparty.Form();
|
|
386
|
-
form.parse(req, async function (err, fields, files) {
|
|
387
|
-
if (err) {
|
|
388
|
-
return res.status(500).send(err);
|
|
389
|
-
}
|
|
390
|
-
// Get viewIndex in fields
|
|
391
|
-
const view = config.views[fields.viewIndex[0]];
|
|
392
|
-
if (typeof view.parseImportFile !== "function") {
|
|
393
|
-
return res.status(500).send("\"parseImportFile\" doit être défini dans la configuration de la vue.");
|
|
394
|
-
}
|
|
343
|
+
function evalExpression(expression, data) {
|
|
344
|
+
let varString = "";
|
|
345
|
+
let statement = "";
|
|
395
346
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
347
|
+
try {
|
|
348
|
+
for (let key in data) {
|
|
349
|
+
statement =
|
|
350
|
+
"var " +
|
|
351
|
+
key +
|
|
352
|
+
" = " +
|
|
353
|
+
(typeof data[key] === "string"
|
|
354
|
+
? "'" +
|
|
355
|
+
data[key]
|
|
356
|
+
.replace(/'/g, "\\'")
|
|
357
|
+
.replace(/\n/g, "")
|
|
358
|
+
.replace(/\r/g, "") +
|
|
359
|
+
"'"
|
|
360
|
+
: typeof data[key] === "object"
|
|
361
|
+
? JSON.stringify(data[key])
|
|
362
|
+
: data[key]) +
|
|
363
|
+
";";
|
|
364
|
+
varString += statement;
|
|
365
|
+
}
|
|
366
|
+
const result = eval(varString + expression);
|
|
367
|
+
return result;
|
|
368
|
+
} catch (e) {
|
|
369
|
+
console.log("Problème d'évaluation de l'expression", statement);
|
|
370
|
+
console.error(e);
|
|
371
|
+
return "Error";
|
|
372
|
+
}
|
|
373
|
+
}
|
|
412
374
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
375
|
+
function formatValueForInsertion(value: any, dbType: string) {
|
|
376
|
+
if (value !== 0 && !value) {
|
|
377
|
+
return "NULL";
|
|
378
|
+
}
|
|
379
|
+
if (value instanceof Date) {
|
|
380
|
+
return value.toISOString();
|
|
381
|
+
}
|
|
382
|
+
if (typeof value === "string") {
|
|
383
|
+
if (dbType === "POSTGRESQL") {
|
|
384
|
+
return "'" + value.replace(/'/g, "''") + "'";
|
|
385
|
+
} else {
|
|
386
|
+
return "'" + value.replace(/'/g, "\\'") + "'";
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (typeof value === "object") {
|
|
390
|
+
return "'" + JSON.stringify(value).replace(/'/g, "\\'") + "'";
|
|
391
|
+
}
|
|
392
|
+
if (typeof value === "boolean") {
|
|
393
|
+
return value ? "TRUE" : "FALSE";
|
|
394
|
+
}
|
|
395
|
+
if (typeof value === "number") {
|
|
396
|
+
return value;
|
|
397
|
+
}
|
|
416
398
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
queryString += ` (${valuesList}),`;
|
|
420
|
-
}
|
|
421
|
-
queryString = queryString.slice(0, -1) + ` ON CONFLICT (${view.importFileUniqueField}) DO UPDATE SET `;
|
|
399
|
+
return value;
|
|
400
|
+
}
|
|
422
401
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
402
|
+
app.post("/recordsExport", async function (req, res) {
|
|
403
|
+
const view = config.views[req.body.viewIndex];
|
|
404
|
+
const orderBy = req.body.orderBy || view.orderBy;
|
|
405
|
+
const isDesc = req.body.isDesc === null ? view.isDesc : req.body.isDesc;
|
|
406
|
+
const subview =
|
|
407
|
+
view.subviews && req.body.subviewIndex !== undefined
|
|
408
|
+
? view.subviews[req.body.subviewIndex]
|
|
409
|
+
: null;
|
|
410
|
+
const { rows: records, error } = await getRecords({
|
|
411
|
+
table: view.tableName,
|
|
412
|
+
page: 1,
|
|
413
|
+
allRecords: true,
|
|
414
|
+
filters: sqlAnd([view.filters, subview?.filters, req.body.filters]),
|
|
415
|
+
having: subview?.having,
|
|
416
|
+
customQuery: view.customQuery,
|
|
417
|
+
join: view.join,
|
|
418
|
+
selectExpression: view.selectExpression,
|
|
419
|
+
orderBy: orderBy ? `${orderBy} ${isDesc ? "DESC" : "ASC"}` : null,
|
|
420
|
+
primaryId: view.primaryId,
|
|
421
|
+
query: req.body.query,
|
|
422
|
+
groupBy: view.groupBy,
|
|
423
|
+
columns: view.columns,
|
|
424
|
+
limit: view.limit || 10,
|
|
425
|
+
});
|
|
426
|
+
if (error) {
|
|
427
|
+
return res.status(500).send(error);
|
|
428
|
+
}
|
|
429
|
+
let output = "";
|
|
430
|
+
const columns = view.columns;
|
|
431
|
+
for (let j = 0; j < columns.length; j++) {
|
|
432
|
+
if (!columns[j].hideInTable || columns[j].includeInExport) {
|
|
433
|
+
if (j > 0) output += ";";
|
|
434
|
+
output += columns[j].label;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
for (let i in records) {
|
|
438
|
+
const el = records[i];
|
|
439
|
+
output += "\r\n";
|
|
440
|
+
for (let j = 0; j < columns.length; j++) {
|
|
441
|
+
if (!columns[j].hideInTable || columns[j].includeInExport) {
|
|
442
|
+
if (j > 0) output += ";";
|
|
443
|
+
if (columns[j].dataType == "textarea") {
|
|
444
|
+
output += '"';
|
|
445
|
+
}
|
|
446
|
+
output += await getFormattedValue(columns[j], el);
|
|
447
|
+
if (columns[j].dataType == "textarea") {
|
|
448
|
+
output += '"';
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
res.send({
|
|
454
|
+
content: output,
|
|
455
|
+
});
|
|
456
|
+
});
|
|
437
457
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
message: "Une erreur est survenue."
|
|
454
|
-
})
|
|
455
|
-
}
|
|
456
|
-
})
|
|
457
|
-
})
|
|
458
|
+
app.post("/recordsImport", async function (req, res) {
|
|
459
|
+
const form = new multiparty.Form();
|
|
460
|
+
form.parse(req, async function (err, fields, files) {
|
|
461
|
+
if (err) {
|
|
462
|
+
return res.status(500).send(err);
|
|
463
|
+
}
|
|
464
|
+
// Get viewIndex in fields
|
|
465
|
+
const view = config.views[fields.viewIndex[0]];
|
|
466
|
+
if (typeof view.parseImportFile !== "function") {
|
|
467
|
+
return res
|
|
468
|
+
.status(500)
|
|
469
|
+
.send(
|
|
470
|
+
'"parseImportFile" doit être défini dans la configuration de la vue.'
|
|
471
|
+
);
|
|
472
|
+
}
|
|
458
473
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
const view = config.views[fields.viewIndex[0]];
|
|
467
|
-
const panel = view.recordViewPanels[fields.panelIndex[0]];
|
|
468
|
-
if (typeof panel.parseImportFile !== "function") {
|
|
469
|
-
return res.status(500).send("\"parseImportFile\" doit être défini dans la configuration du panneau.");
|
|
470
|
-
}
|
|
471
|
-
if (!Boolean(panel.createConfig)) {
|
|
472
|
-
return res.status(500).send("\"createConfig\" doit être défini dans la configuration du panneau.");
|
|
473
|
-
}
|
|
474
|
+
const records = await view.parseImportFile(files.data[0]);
|
|
475
|
+
const columns = [];
|
|
476
|
+
if (records.length === 0) {
|
|
477
|
+
return res.send({
|
|
478
|
+
affectedRows: 0,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
474
481
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
})
|
|
482
|
+
try {
|
|
483
|
+
for (let record of records) {
|
|
484
|
+
for (let key in record) {
|
|
485
|
+
if (!columns.includes(key)) {
|
|
486
|
+
columns.push(key);
|
|
481
487
|
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
482
490
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
if (!columns.includes(key)) {
|
|
487
|
-
columns.push(key)
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
+
let queryString;
|
|
492
|
+
if (db.dbType === "POSTGRESQL") {
|
|
493
|
+
queryString = `INSERT INTO ?? (${columns.map((x) => `"${x}"`).join(", ")}) VALUES`;
|
|
491
494
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
if (err) {
|
|
502
|
-
console.error(err)
|
|
503
|
-
return res.status(500).send({
|
|
504
|
-
err,
|
|
505
|
-
message: "Une erreur est survenue."
|
|
506
|
-
})
|
|
507
|
-
}
|
|
508
|
-
res.send({
|
|
509
|
-
affectedRows: r.affectedRows
|
|
510
|
-
})
|
|
511
|
-
} catch (err) {
|
|
512
|
-
console.error(err)
|
|
513
|
-
res.status(500).send({
|
|
514
|
-
err,
|
|
515
|
-
message: "Une erreur est survenue."
|
|
516
|
-
})
|
|
517
|
-
}
|
|
518
|
-
})
|
|
519
|
-
})
|
|
495
|
+
for (let record of records) {
|
|
496
|
+
const valuesList = columns
|
|
497
|
+
.map((x) => formatValueForInsertion(record[x], db.dbType))
|
|
498
|
+
.join(", ");
|
|
499
|
+
queryString += ` (${valuesList}),`;
|
|
500
|
+
}
|
|
501
|
+
queryString =
|
|
502
|
+
queryString.slice(0, -1) +
|
|
503
|
+
` ON CONFLICT (${view.importFileUniqueField}) DO UPDATE SET `;
|
|
520
504
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
if (err) {
|
|
525
|
-
return res.status(500).send(err);
|
|
526
|
-
}
|
|
527
|
-
if (rows.length > 0) {
|
|
528
|
-
const record = rows[0]
|
|
529
|
-
for (const column of view.columns) {
|
|
530
|
-
if (column.formatValue) {
|
|
531
|
-
record[`__formatValue-${column.__id__}__`] = await column.formatValue(record)
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
res.send(record)
|
|
505
|
+
for (let column of columns) {
|
|
506
|
+
queryString += `"${column}" = EXCLUDED."${column}",`;
|
|
507
|
+
}
|
|
535
508
|
} else {
|
|
536
|
-
|
|
509
|
+
queryString =
|
|
510
|
+
"INSERT INTO ?? (" +
|
|
511
|
+
columns.map((x) => "`" + x + "`").join(", ") +
|
|
512
|
+
") VALUES";
|
|
513
|
+
for (let record of records) {
|
|
514
|
+
queryString +=
|
|
515
|
+
" (" +
|
|
516
|
+
columns
|
|
517
|
+
.map((x) => formatValueForInsertion(record[x], db.dbType))
|
|
518
|
+
.join(", ") +
|
|
519
|
+
"),";
|
|
520
|
+
}
|
|
521
|
+
queryString = queryString.slice(0, -1) + " ON DUPLICATE KEY UPDATE";
|
|
522
|
+
for (let column of columns) {
|
|
523
|
+
queryString += " `" + column + "` = VALUES(`" + column + "`),";
|
|
524
|
+
}
|
|
537
525
|
}
|
|
538
|
-
|
|
526
|
+
queryString = queryString.slice(0, -1); // Remove the trailing comma
|
|
539
527
|
|
|
540
|
-
|
|
541
|
-
const view = config.views[req.body.viewIndex];
|
|
542
|
-
for (let key in req.body.data) {
|
|
543
|
-
if (req.body.data[key] == "NULL" || req.body.data[key] === "" || req.body.data[key] === "Invalid date") {
|
|
544
|
-
req.body.data[key] = null
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
if (Object.keys(req.body.data).length === 0) {
|
|
548
|
-
return res.sendStatus(200)
|
|
549
|
-
}
|
|
550
|
-
const queryExpression = Object.keys(req.body.data).map(x => `${x} = ?`).join(", ");
|
|
551
|
-
const queryParameters = Object.keys(req.body.data).map(x => req.body.data[x]);
|
|
552
|
-
const { err } = await db.query(`UPDATE ?? SET ${queryExpression} WHERE ?? = ?`, [view.tableName, ...queryParameters, view.primaryId, req.body.primaryValue])
|
|
528
|
+
const { err, rows: r } = await db.query(queryString, [view.tableName]);
|
|
553
529
|
if (err) {
|
|
554
|
-
|
|
530
|
+
console.error(err);
|
|
531
|
+
return res.status(500).send({
|
|
532
|
+
err,
|
|
533
|
+
message: "Une erreur est survenue.",
|
|
534
|
+
});
|
|
555
535
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
data: req.body.data
|
|
569
|
-
})
|
|
570
|
-
}
|
|
571
|
-
res.sendStatus(200)
|
|
572
|
-
})
|
|
536
|
+
res.send({
|
|
537
|
+
affectedRows: r.affectedRows,
|
|
538
|
+
});
|
|
539
|
+
} catch (err) {
|
|
540
|
+
console.error(err);
|
|
541
|
+
res.status(500).send({
|
|
542
|
+
err,
|
|
543
|
+
message: "Une erreur est survenue.",
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
});
|
|
573
548
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
549
|
+
app.post("/subrecordsImport", async function (req, res) {
|
|
550
|
+
const form = new multiparty.Form();
|
|
551
|
+
form.parse(req, async function (err, fields, files) {
|
|
552
|
+
if (err) {
|
|
553
|
+
return res.status(500).send(err);
|
|
554
|
+
}
|
|
555
|
+
// Get viewIndex and panelIndex in fields
|
|
556
|
+
const view = config.views[fields.viewIndex[0]];
|
|
557
|
+
const panel = view.recordViewPanels[fields.panelIndex[0]];
|
|
558
|
+
if (typeof panel.parseImportFile !== "function") {
|
|
559
|
+
return res
|
|
560
|
+
.status(500)
|
|
561
|
+
.send(
|
|
562
|
+
'"parseImportFile" doit être défini dans la configuration du panneau.'
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
if (!Boolean(panel.createConfig)) {
|
|
566
|
+
return res
|
|
567
|
+
.status(500)
|
|
568
|
+
.send(
|
|
569
|
+
'"createConfig" doit être défini dans la configuration du panneau.'
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const records = await panel.parseImportFile(
|
|
574
|
+
files.data[0],
|
|
575
|
+
fields.recordId[0]
|
|
576
|
+
);
|
|
577
|
+
const columns = [];
|
|
578
|
+
if (records.length === 0) {
|
|
579
|
+
return res.send({
|
|
580
|
+
affectedRows: 0,
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
for (let record of records) {
|
|
586
|
+
for (let key in record) {
|
|
587
|
+
if (!columns.includes(key)) {
|
|
588
|
+
columns.push(key);
|
|
594
589
|
}
|
|
590
|
+
}
|
|
595
591
|
}
|
|
596
|
-
res.send({
|
|
597
|
-
success: true,
|
|
598
|
-
message: action.successMessage
|
|
599
|
-
})
|
|
600
|
-
})
|
|
601
592
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
593
|
+
let queryString =
|
|
594
|
+
"INSERT IGNORE INTO ?? (`" +
|
|
595
|
+
panel.createConfig.recordIdKey +
|
|
596
|
+
"`, " +
|
|
597
|
+
columns.map((x) => "`" + x + "`").join(", ") +
|
|
598
|
+
") VALUES";
|
|
599
|
+
for (let record of records) {
|
|
600
|
+
queryString +=
|
|
601
|
+
" (" +
|
|
602
|
+
fields.recordId[0] +
|
|
603
|
+
", " +
|
|
604
|
+
columns
|
|
605
|
+
.map((x) => formatValueForInsertion(record[x], db.dbType))
|
|
606
|
+
.join(", ") +
|
|
607
|
+
"),";
|
|
607
608
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
609
|
+
queryString = queryString.slice(0, -1) + " ON DUPLICATE KEY UPDATE";
|
|
610
|
+
for (let column of columns) {
|
|
611
|
+
queryString += " `" + column + "` = VALUES(`" + column + "`),";
|
|
611
612
|
}
|
|
612
|
-
const { err } = await db.query(
|
|
613
|
+
const { err, rows: r } = await db.query(queryString.slice(0, -1), [
|
|
614
|
+
panel.tableName,
|
|
615
|
+
]);
|
|
613
616
|
if (err) {
|
|
614
|
-
|
|
617
|
+
console.error(err);
|
|
618
|
+
return res.status(500).send({
|
|
619
|
+
err,
|
|
620
|
+
message: "Une erreur est survenue.",
|
|
621
|
+
});
|
|
615
622
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
res.sendStatus(200)
|
|
630
|
-
})
|
|
623
|
+
res.send({
|
|
624
|
+
affectedRows: r.affectedRows,
|
|
625
|
+
});
|
|
626
|
+
} catch (err) {
|
|
627
|
+
console.error(err);
|
|
628
|
+
res.status(500).send({
|
|
629
|
+
err,
|
|
630
|
+
message: "Une erreur est survenue.",
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
});
|
|
631
635
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
})
|
|
636
|
+
app.get("/record", async function (req, res) {
|
|
637
|
+
const view = config.views[req.query.viewIndex as string];
|
|
638
|
+
const { err, rows } = await db.query(
|
|
639
|
+
"SELECT " +
|
|
640
|
+
(view.selectExpression || "*") +
|
|
641
|
+
" FROM ?? " +
|
|
642
|
+
(view.join || "") +
|
|
643
|
+
" WHERE ?? = ?",
|
|
644
|
+
[
|
|
645
|
+
view.tableName,
|
|
646
|
+
view.tableName + "." + view.primaryId,
|
|
647
|
+
req.query.recordId,
|
|
648
|
+
]
|
|
649
|
+
);
|
|
650
|
+
if (err) {
|
|
651
|
+
return res.status(500).send(err);
|
|
652
|
+
}
|
|
653
|
+
if (rows.length > 0) {
|
|
654
|
+
const record = rows[0];
|
|
655
|
+
for (const column of view.columns) {
|
|
656
|
+
if (column.formatValue) {
|
|
657
|
+
record[`__formatValue-${column.__id__}__`] =
|
|
658
|
+
await column.formatValue(record);
|
|
656
659
|
}
|
|
657
|
-
|
|
658
|
-
|
|
660
|
+
}
|
|
661
|
+
res.send(record);
|
|
662
|
+
} else {
|
|
663
|
+
res.sendStatus(404);
|
|
664
|
+
}
|
|
665
|
+
});
|
|
659
666
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
667
|
+
app.post("/updateRecord", async function (req, res, next) {
|
|
668
|
+
const view = config.views[req.body.viewIndex];
|
|
669
|
+
for (let key in req.body.data) {
|
|
670
|
+
if (
|
|
671
|
+
req.body.data[key] == "NULL" ||
|
|
672
|
+
req.body.data[key] === "" ||
|
|
673
|
+
req.body.data[key] === "Invalid date"
|
|
674
|
+
) {
|
|
675
|
+
req.body.data[key] = null;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
if (Object.keys(req.body.data).length === 0) {
|
|
679
|
+
return res.sendStatus(200);
|
|
680
|
+
}
|
|
681
|
+
const queryExpression = Object.keys(req.body.data)
|
|
682
|
+
.map((x) => `${x} = ?`)
|
|
683
|
+
.join(", ");
|
|
684
|
+
const queryParameters = Object.keys(req.body.data).map(
|
|
685
|
+
(x) => req.body.data[x]
|
|
686
|
+
);
|
|
687
|
+
const { err } = await db.query(
|
|
688
|
+
`UPDATE ?? SET ${queryExpression} WHERE ?? = ?`,
|
|
689
|
+
[
|
|
690
|
+
view.tableName,
|
|
691
|
+
...queryParameters,
|
|
692
|
+
view.primaryId,
|
|
693
|
+
req.body.primaryValue,
|
|
694
|
+
]
|
|
695
|
+
);
|
|
696
|
+
if (err) {
|
|
697
|
+
return res.status(500).send(err);
|
|
698
|
+
}
|
|
699
|
+
if (view.editCallback) {
|
|
700
|
+
try {
|
|
701
|
+
await axios.post(view.editCallback, {
|
|
702
|
+
id: req.body.primaryValue,
|
|
703
|
+
data: req.body.data,
|
|
704
|
+
previousData: req.body.previousData,
|
|
705
|
+
});
|
|
706
|
+
} catch (e) {}
|
|
707
|
+
}
|
|
708
|
+
if (view.editCallbackFunction) {
|
|
709
|
+
await view.editCallbackFunction({
|
|
710
|
+
id: req.body.primaryValue,
|
|
711
|
+
data: req.body.data,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
res.sendStatus(200);
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
app.post("/customAction", async function (req, res, next) {
|
|
718
|
+
const view = config.views[req.body.viewIndex];
|
|
719
|
+
const action = view.customActions[req.body.actionIndex];
|
|
720
|
+
for (const recordId of req.body.selectedRecordIds) {
|
|
721
|
+
try {
|
|
722
|
+
const { rows } = await db.query(
|
|
723
|
+
"SELECT " +
|
|
724
|
+
(view.selectExpression || "*") +
|
|
725
|
+
" FROM ?? " +
|
|
726
|
+
(view.join || "") +
|
|
727
|
+
" WHERE ?? = ?",
|
|
728
|
+
[view.tableName, view.tableName + "." + view.primaryId, recordId]
|
|
729
|
+
);
|
|
730
|
+
const record = rows[0];
|
|
731
|
+
const actionResult = await action.action(record, user.userId);
|
|
732
|
+
if (actionResult) {
|
|
733
|
+
return res.send({
|
|
734
|
+
success: true,
|
|
735
|
+
message: actionResult,
|
|
736
|
+
});
|
|
665
737
|
}
|
|
666
|
-
|
|
667
|
-
|
|
738
|
+
} catch (err) {
|
|
739
|
+
console.error(err);
|
|
740
|
+
return res.send({
|
|
741
|
+
success: false,
|
|
742
|
+
error: err,
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
res.send({
|
|
747
|
+
success: true,
|
|
748
|
+
message: action.successMessage,
|
|
749
|
+
});
|
|
750
|
+
});
|
|
668
751
|
|
|
669
|
-
|
|
670
|
-
|
|
752
|
+
app.post("/deleteRecord", async function (req, res) {
|
|
753
|
+
const view = config.views[req.body.viewIndex];
|
|
754
|
+
const { rows, err: err2 } = await db.query(
|
|
755
|
+
"SELECT " +
|
|
756
|
+
(view.selectExpression || "*") +
|
|
757
|
+
" FROM ?? " +
|
|
758
|
+
(view.join || "") +
|
|
759
|
+
" WHERE ?? = ?",
|
|
760
|
+
[
|
|
761
|
+
view.tableName,
|
|
762
|
+
view.tableName + "." + view.primaryId,
|
|
763
|
+
req.body.primaryValue,
|
|
764
|
+
]
|
|
765
|
+
);
|
|
766
|
+
if (err2) {
|
|
767
|
+
return res.status(500).send(err2);
|
|
768
|
+
}
|
|
769
|
+
const deletedRecord = rows[0];
|
|
770
|
+
if (!deletedRecord) {
|
|
771
|
+
return res.status(404).send("Record not found");
|
|
772
|
+
}
|
|
773
|
+
const { err } = await db.query("DELETE FROM ?? WHERE ?? = ?", [
|
|
774
|
+
view.tableName,
|
|
775
|
+
view.primaryId,
|
|
776
|
+
req.body.primaryValue,
|
|
777
|
+
]);
|
|
778
|
+
if (err) {
|
|
779
|
+
return res.status(500).send(err);
|
|
780
|
+
}
|
|
781
|
+
if (view.deleteCallback) {
|
|
782
|
+
try {
|
|
783
|
+
await axios.post(view.deleteCallback, {
|
|
784
|
+
id: req.body.primaryValue,
|
|
785
|
+
});
|
|
786
|
+
} catch (e) {}
|
|
787
|
+
}
|
|
788
|
+
if (view.deleteCallbackFunction) {
|
|
789
|
+
await view.deleteCallbackFunction({
|
|
790
|
+
id: req.body.primaryValue,
|
|
791
|
+
deletedRecord,
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
res.sendStatus(200);
|
|
795
|
+
});
|
|
671
796
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
797
|
+
app.post("/deleteRecordFromPanel", async function (req, res) {
|
|
798
|
+
const view = config.views[req.body.viewIndex];
|
|
799
|
+
const panel = view.recordViewPanels.find(
|
|
800
|
+
(x) => x.__id__ === req.body.panelId
|
|
801
|
+
);
|
|
802
|
+
const { rows, err: err2 } = await db.query(
|
|
803
|
+
"SELECT * FROM ?? WHERE ?? = ?",
|
|
804
|
+
[panel.tableName, panel.primaryId, req.body.primaryValue]
|
|
805
|
+
);
|
|
806
|
+
if (err2) {
|
|
807
|
+
return res.status(500).send(err2);
|
|
808
|
+
}
|
|
809
|
+
const deletedRecord = rows[0];
|
|
810
|
+
const { err } = await db.query("DELETE FROM ?? WHERE ?? = ?", [
|
|
811
|
+
panel.tableName,
|
|
812
|
+
panel.primaryId,
|
|
813
|
+
req.body.primaryValue,
|
|
814
|
+
]);
|
|
815
|
+
if (err) {
|
|
816
|
+
return res.status(500).send(err);
|
|
817
|
+
}
|
|
818
|
+
if (panel.deleteCallback) {
|
|
819
|
+
try {
|
|
820
|
+
await axios.post(panel.deleteCallback, {
|
|
821
|
+
id: req.body.primaryValue,
|
|
822
|
+
});
|
|
823
|
+
} catch (e) {}
|
|
824
|
+
}
|
|
825
|
+
if (panel.deleteCallbackFunction) {
|
|
826
|
+
await panel.deleteCallbackFunction({
|
|
827
|
+
id: req.body.primaryValue,
|
|
828
|
+
deletedRecord,
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
res.sendStatus(200);
|
|
832
|
+
});
|
|
685
833
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
834
|
+
app.post("/deleteRecords", async function (req, res, next) {
|
|
835
|
+
const view = config.views[req.body.viewIndex];
|
|
836
|
+
const { err } = await db.query(
|
|
837
|
+
`DELETE FROM ?? WHERE ?? IN(${req.body.primaryValues.map((x) => "'" + x + "'").join(",")})`,
|
|
838
|
+
[view.tableName, view.primaryId]
|
|
839
|
+
);
|
|
840
|
+
if (err) {
|
|
841
|
+
return res.status(500).send(err);
|
|
842
|
+
}
|
|
843
|
+
res.sendStatus(200);
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
app.get("/notificationBadges", async function (req, res) {
|
|
847
|
+
const notificationBadges = {};
|
|
696
848
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
const { err, rows } = await db.query(
|
|
849
|
+
async function checkAndGetNotificationBadge(obj) {
|
|
850
|
+
if (obj.notificationBadge) {
|
|
851
|
+
const { err, rows } = await db.query(obj.notificationBadge.query);
|
|
700
852
|
if (err) {
|
|
701
|
-
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
853
|
+
console.error(err);
|
|
854
|
+
} else if (
|
|
855
|
+
rows.length > 0 &&
|
|
856
|
+
rows[0][obj.notificationBadge.attribute]
|
|
857
|
+
) {
|
|
858
|
+
const notificationBadge = rows[0][obj.notificationBadge.attribute];
|
|
859
|
+
if (
|
|
860
|
+
!obj.notificationBadge.displayCondition ||
|
|
861
|
+
obj.notificationBadge.displayCondition(notificationBadge)
|
|
862
|
+
) {
|
|
863
|
+
notificationBadges[obj.__id__] = notificationBadge;
|
|
864
|
+
}
|
|
707
865
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
for (const view of config.views) {
|
|
870
|
+
await checkAndGetNotificationBadge(view);
|
|
871
|
+
if (view.subviews) {
|
|
872
|
+
for (const subview of view.subviews) {
|
|
873
|
+
await checkAndGetNotificationBadge(subview);
|
|
711
874
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
res.send(notificationBadges);
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
app.post("/duplicateRecord", async function (req, res, next) {
|
|
881
|
+
const primaryId = req.body.duplicateConfig.primaryId;
|
|
882
|
+
const { err, rows } = await db.query("SELECT * FROM ?? WHERE ?? = ?", [
|
|
883
|
+
req.body.table,
|
|
884
|
+
primaryId,
|
|
885
|
+
req.body.primaryValue,
|
|
886
|
+
]);
|
|
887
|
+
if (err) {
|
|
888
|
+
return res.status(500).send(err);
|
|
889
|
+
}
|
|
890
|
+
const record = rows[0];
|
|
891
|
+
const data = {};
|
|
892
|
+
for (let field of req.body.duplicateConfig.fields) {
|
|
893
|
+
data[field] = record[field];
|
|
894
|
+
}
|
|
895
|
+
const { err: err2, rows: r } = await db.query(
|
|
896
|
+
"INSERT IGNORE INTO ?? SET ?",
|
|
897
|
+
[req.body.table, data]
|
|
898
|
+
);
|
|
899
|
+
if (err2) {
|
|
900
|
+
return res.status(500).send(err);
|
|
901
|
+
}
|
|
902
|
+
const newRecordId = r.insertId;
|
|
903
|
+
for (let foreignTable of req.body.duplicateConfig.foreignTables) {
|
|
904
|
+
const { err, rows } = await db.query("SELECT * FROM ?? WHERE ?? = ?", [
|
|
905
|
+
foreignTable.table,
|
|
906
|
+
primaryId,
|
|
907
|
+
req.body.primaryValue,
|
|
908
|
+
]);
|
|
909
|
+
if (err) console.log(err);
|
|
910
|
+
for (let record of rows) {
|
|
911
|
+
const data = {};
|
|
912
|
+
data[primaryId] = newRecordId;
|
|
913
|
+
for (let field of foreignTable.fields) {
|
|
914
|
+
data[field] = record[field];
|
|
724
915
|
}
|
|
725
|
-
|
|
726
|
-
|
|
916
|
+
await db.query("INSERT IGNORE INTO ?? SET ?", [
|
|
917
|
+
foreignTable.table,
|
|
918
|
+
data,
|
|
919
|
+
]);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
res.sendStatus(200);
|
|
923
|
+
});
|
|
727
924
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
925
|
+
app.post("/createRecord", async function (req, res, next) {
|
|
926
|
+
const view = config.views[req.body.viewIndex];
|
|
927
|
+
const panel = req.body.panelIndex
|
|
928
|
+
? view.recordViewPanels[req.body.panelIndex]
|
|
929
|
+
: null;
|
|
930
|
+
try {
|
|
931
|
+
for (let key in req.body.data) {
|
|
932
|
+
if (
|
|
933
|
+
req.body.data[key] == "NULL" ||
|
|
934
|
+
req.body.data[key] === "Invalid date"
|
|
935
|
+
) {
|
|
936
|
+
req.body.data[key] = null;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
if (panel) {
|
|
940
|
+
req.body.data[panel.createConfig.recordIdKey] = req.body.recordId;
|
|
941
|
+
}
|
|
942
|
+
const primaryColumn = (view || panel)?.columns?.find(
|
|
943
|
+
(x) => x.name === (view || panel).primaryId
|
|
944
|
+
);
|
|
945
|
+
if (primaryColumn?.dataType == "uuid") {
|
|
946
|
+
req.body.data[primaryColumn.name] = uuidv4();
|
|
947
|
+
}
|
|
948
|
+
const createPayload = JSON.parse(JSON.stringify(req.body.data));
|
|
949
|
+
const columns = panel ? panel.createConfig.columns : view.columns;
|
|
950
|
+
for (let key in createPayload) {
|
|
951
|
+
if (
|
|
952
|
+
columns?.find((x) => x.name === key)?.includedInCreatePayload ===
|
|
953
|
+
false
|
|
954
|
+
) {
|
|
955
|
+
delete createPayload[key];
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
const queryExpression = Object.keys(createPayload).join(", ");
|
|
959
|
+
const queryPlaceholders = Object.keys(createPayload)
|
|
960
|
+
.map((x) => "?")
|
|
961
|
+
.join(", ");
|
|
962
|
+
const queryParameters = Object.keys(createPayload).map(
|
|
963
|
+
(x) => createPayload[x]
|
|
964
|
+
);
|
|
965
|
+
const { err, rows: r } = await db.query(
|
|
966
|
+
`INSERT INTO ?? (${queryExpression}) VALUES(${queryPlaceholders})`,
|
|
967
|
+
[panel ? panel.tableName : view.tableName, ...queryParameters]
|
|
968
|
+
);
|
|
969
|
+
if (err) {
|
|
970
|
+
console.error(err);
|
|
971
|
+
return res.status(500).send({
|
|
972
|
+
err,
|
|
973
|
+
message:
|
|
974
|
+
err.code === "ER_DUP_ENTRY"
|
|
975
|
+
? "Cet élément existe déjà."
|
|
976
|
+
: "Une erreur est survenue.",
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
if (panel ? panel.createCallback : view.createCallback) {
|
|
731
980
|
try {
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
id: r.insertId
|
|
778
|
-
})
|
|
779
|
-
} catch (err) {
|
|
780
|
-
console.error(err)
|
|
781
|
-
res.status(500).send({
|
|
782
|
-
err,
|
|
783
|
-
message: "Une erreur est survenue."
|
|
784
|
-
})
|
|
981
|
+
await axios.post(panel ? panel.createCallback : view.createCallback, {
|
|
982
|
+
id: r.insertId,
|
|
983
|
+
data: req.body.data,
|
|
984
|
+
});
|
|
985
|
+
} catch (e) {}
|
|
986
|
+
}
|
|
987
|
+
if (panel ? panel.createCallbackFunction : view.createCallbackFunction) {
|
|
988
|
+
await (
|
|
989
|
+
panel ? panel.createCallbackFunction : view.createCallbackFunction
|
|
990
|
+
)({
|
|
991
|
+
id: r.insertId,
|
|
992
|
+
data: req.body.data,
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
res.send({
|
|
996
|
+
id: r.insertId,
|
|
997
|
+
});
|
|
998
|
+
} catch (err) {
|
|
999
|
+
console.error(err);
|
|
1000
|
+
res.status(500).send({
|
|
1001
|
+
err,
|
|
1002
|
+
message: "Une erreur est survenue.",
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
app.get("/customQuery", async function (req, res, next) {
|
|
1008
|
+
const query = (req.query.query as string).replace(
|
|
1009
|
+
/\?/g,
|
|
1010
|
+
req.query.primaryValue as string
|
|
1011
|
+
);
|
|
1012
|
+
const { err, rows } = await db.query(query);
|
|
1013
|
+
if (err) {
|
|
1014
|
+
return res.status(500).send(err);
|
|
1015
|
+
}
|
|
1016
|
+
if (req.query.viewIndex && req.query.panelIndex) {
|
|
1017
|
+
const panel =
|
|
1018
|
+
config.views[req.query.viewIndex as string].recordViewPanels[
|
|
1019
|
+
req.query.panelIndex as string
|
|
1020
|
+
];
|
|
1021
|
+
if (panel.formatValue) {
|
|
1022
|
+
for (const row of rows) {
|
|
1023
|
+
for (const key in panel.formatValue) {
|
|
1024
|
+
row[key] = await panel.formatValue[key](row);
|
|
1025
|
+
}
|
|
785
1026
|
}
|
|
786
|
-
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
res.send(rows);
|
|
1030
|
+
});
|
|
787
1031
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
1032
|
+
function uploadFile(column, fileParamName, req, res) {
|
|
1033
|
+
if (column.s3Config || column.gcpConfig) {
|
|
1034
|
+
const form = new multiparty.Form();
|
|
1035
|
+
form.parse(req, function (err, fields, files) {
|
|
791
1036
|
if (err) {
|
|
792
|
-
|
|
793
|
-
}
|
|
794
|
-
if (req.query.viewIndex && req.query.panelIndex) {
|
|
795
|
-
const panel = config.views[req.query.viewIndex as string].recordViewPanels[req.query.panelIndex as string];
|
|
796
|
-
if (panel.formatValue) {
|
|
797
|
-
for (const row of rows) {
|
|
798
|
-
for (const key in panel.formatValue) {
|
|
799
|
-
row[key] = await panel.formatValue[key](row);
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
}
|
|
1037
|
+
return res.status(500).send(err);
|
|
803
1038
|
}
|
|
804
|
-
|
|
805
|
-
|
|
1039
|
+
const originalFilename = files[fileParamName][0].originalFilename;
|
|
1040
|
+
const contentType =
|
|
1041
|
+
mime.getType(originalFilename) || "application/octet-stream";
|
|
806
1042
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
});
|
|
818
|
-
form.parse(req, function (err, fields, files) {
|
|
819
|
-
if (err) {
|
|
820
|
-
return res.status(500).send(err);
|
|
821
|
-
}
|
|
822
|
-
const originalFilename = files[fileParamName][0].originalFilename;
|
|
823
|
-
const contentType = mime.getType(originalFilename) || 'application/octet-stream';
|
|
1043
|
+
const extension = originalFilename.match(/(?:\.([^.]+))?$/)[1];
|
|
1044
|
+
let aspectRatio = null;
|
|
1045
|
+
if (["png", "jpeg", "jpg", "gif"].includes(extension)) {
|
|
1046
|
+
const dimensions = sizeOf(files[fileParamName][0].path);
|
|
1047
|
+
aspectRatio = dimensions.height / dimensions.width;
|
|
1048
|
+
}
|
|
1049
|
+
const originalFilenameWithoutExtension = originalFilename.replace(
|
|
1050
|
+
"." + extension,
|
|
1051
|
+
""
|
|
1052
|
+
);
|
|
824
1053
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1054
|
+
if (column.gcpConfig) {
|
|
1055
|
+
const gcpConfig = column.gcpConfig;
|
|
1056
|
+
const path =
|
|
1057
|
+
(gcpConfig.subfolder ? gcpConfig.subfolder + "/" : "") +
|
|
1058
|
+
originalFilenameWithoutExtension +
|
|
1059
|
+
"-" +
|
|
1060
|
+
Math.random().toString().substr(2) +
|
|
1061
|
+
"." +
|
|
1062
|
+
extension;
|
|
1063
|
+
const storage = new Storage();
|
|
833
1064
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
Expires: 150,
|
|
840
|
-
});
|
|
841
|
-
axios.put(signedUrl, fs.readFileSync(files[fileParamName][0].path), {
|
|
842
|
-
maxContentLength: 100000000000,
|
|
843
|
-
maxBodyLength: 100000000000,
|
|
844
|
-
headers: {
|
|
845
|
-
"Content-Type": contentType
|
|
846
|
-
}
|
|
847
|
-
})
|
|
848
|
-
.then(() => {
|
|
849
|
-
res.status(200).send({
|
|
850
|
-
path: s3Config.pathTransform ? s3Config.pathTransform(path) : path,
|
|
851
|
-
aspectRatio,
|
|
852
|
-
originalFilename,
|
|
853
|
-
});
|
|
854
|
-
})
|
|
855
|
-
.catch(e => {
|
|
856
|
-
res.status(500).send(e)
|
|
857
|
-
})
|
|
858
|
-
} else {
|
|
859
|
-
s3.putObject({
|
|
860
|
-
Body: fs.readFileSync(files[fileParamName][0].path),
|
|
861
|
-
Bucket: s3Config.bucket,
|
|
862
|
-
ContentType: contentType,
|
|
863
|
-
ACL: 'public-read',
|
|
864
|
-
Key: path
|
|
865
|
-
}, function (err, data) {
|
|
866
|
-
if (err) return res.status(500).send(err);
|
|
867
|
-
res.send({
|
|
868
|
-
url: `https://${s3Config.host}/${s3Config.bucket}/${path}`,
|
|
869
|
-
path: s3Config.pathTransform ? s3Config.pathTransform(path) : path,
|
|
870
|
-
aspectRatio,
|
|
871
|
-
originalFilename
|
|
872
|
-
})
|
|
873
|
-
});
|
|
874
|
-
}
|
|
1065
|
+
const bucketName = gcpConfig.bucket;
|
|
1066
|
+
storage
|
|
1067
|
+
.bucket(bucketName)
|
|
1068
|
+
.upload(files[fileParamName][0].path, {
|
|
1069
|
+
destination: path,
|
|
875
1070
|
})
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
}
|
|
885
|
-
const pathSplitted = files[fileParamName][0].path.split(/[\/\\]+/);
|
|
886
|
-
const filename = pathSplitted[pathSplitted.length - 1];
|
|
887
|
-
(async () => {
|
|
888
|
-
const sftp = new SftpClient();
|
|
889
|
-
await sftp.connect({
|
|
890
|
-
host: column.sftpConfig.host,
|
|
891
|
-
port: 22,
|
|
892
|
-
username: column.sftpConfig.user,
|
|
893
|
-
password: column.sftpConfig.password
|
|
894
|
-
});
|
|
895
|
-
await sftp.fastPut(files[fileParamName][0].path, column.publicPath + (column.subdirectory ? "/" + column.subdirectory : "") + "/" + filename);
|
|
896
|
-
await sftp.end();
|
|
897
|
-
res.send({
|
|
898
|
-
path: filename,
|
|
899
|
-
originalFilename: files[fileParamName][0].originalFilename
|
|
900
|
-
})
|
|
901
|
-
fs.unlinkSync(files[fileParamName][0].path)
|
|
902
|
-
})();
|
|
1071
|
+
.then(() => {
|
|
1072
|
+
res.status(200).send({
|
|
1073
|
+
path: gcpConfig.pathTransform
|
|
1074
|
+
? gcpConfig.pathTransform(path)
|
|
1075
|
+
: path,
|
|
1076
|
+
aspectRatio,
|
|
1077
|
+
originalFilename,
|
|
1078
|
+
});
|
|
903
1079
|
})
|
|
1080
|
+
.catch((e) => {
|
|
1081
|
+
res.status(500).send(e);
|
|
1082
|
+
});
|
|
904
1083
|
} else {
|
|
905
|
-
|
|
906
|
-
|
|
1084
|
+
const s3Config = column.s3Config;
|
|
1085
|
+
const path =
|
|
1086
|
+
(s3Config.subfolder ? s3Config.subfolder + "/" : "") +
|
|
1087
|
+
originalFilenameWithoutExtension +
|
|
1088
|
+
"-" +
|
|
1089
|
+
Math.random().toString().substr(2) +
|
|
1090
|
+
"." +
|
|
1091
|
+
extension;
|
|
1092
|
+
const s3 = new S3({
|
|
1093
|
+
endpoint: new Endpoint(s3Config.host),
|
|
1094
|
+
accessKeyId: s3Config.accessKeyId,
|
|
1095
|
+
secretAccessKey: s3Config.secretAccessKey,
|
|
1096
|
+
region: s3Config.region,
|
|
1097
|
+
signatureVersion: "v4",
|
|
1098
|
+
});
|
|
1099
|
+
if (s3Config.signedUrlNeeded) {
|
|
1100
|
+
const signedUrl = s3.getSignedUrl("putObject", {
|
|
1101
|
+
Bucket: s3Config.bucket,
|
|
1102
|
+
Key: path,
|
|
1103
|
+
ContentType: contentType,
|
|
1104
|
+
Expires: 150,
|
|
907
1105
|
});
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1106
|
+
axios
|
|
1107
|
+
.put(signedUrl, fs.readFileSync(files[fileParamName][0].path), {
|
|
1108
|
+
maxContentLength: 100000000000,
|
|
1109
|
+
maxBodyLength: 100000000000,
|
|
1110
|
+
headers: {
|
|
1111
|
+
"Content-Type": contentType,
|
|
1112
|
+
},
|
|
1113
|
+
})
|
|
1114
|
+
.then(() => {
|
|
1115
|
+
res.status(200).send({
|
|
1116
|
+
path: s3Config.pathTransform
|
|
1117
|
+
? s3Config.pathTransform(path)
|
|
1118
|
+
: path,
|
|
1119
|
+
aspectRatio,
|
|
1120
|
+
originalFilename,
|
|
1121
|
+
});
|
|
1122
|
+
})
|
|
1123
|
+
.catch((e) => {
|
|
1124
|
+
res.status(500).send(e);
|
|
1125
|
+
});
|
|
1126
|
+
} else {
|
|
1127
|
+
s3.putObject(
|
|
1128
|
+
{
|
|
1129
|
+
Body: fs.readFileSync(files[fileParamName][0].path),
|
|
1130
|
+
Bucket: s3Config.bucket,
|
|
1131
|
+
ContentType: contentType,
|
|
1132
|
+
ACL: "public-read",
|
|
1133
|
+
Key: path,
|
|
1134
|
+
},
|
|
1135
|
+
function (err, data) {
|
|
1136
|
+
if (err) return res.status(500).send(err);
|
|
912
1137
|
res.send({
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1138
|
+
url: `https://${s3Config.host}/${s3Config.bucket}/${path}`,
|
|
1139
|
+
path: s3Config.pathTransform
|
|
1140
|
+
? s3Config.pathTransform(path)
|
|
1141
|
+
: path,
|
|
1142
|
+
aspectRatio,
|
|
1143
|
+
originalFilename,
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
} else if (column.sftpConfig) {
|
|
1151
|
+
if (!column.publicPath) {
|
|
1152
|
+
return res
|
|
1153
|
+
.status(500)
|
|
1154
|
+
.send("You need to pass publicPath parameter when using sftpConfig");
|
|
1155
|
+
}
|
|
1156
|
+
const form = new multiparty.Form();
|
|
1157
|
+
form.parse(req, function (err, fields, files) {
|
|
1158
|
+
if (err) {
|
|
1159
|
+
return res.status(500).send(err);
|
|
917
1160
|
}
|
|
1161
|
+
const pathSplitted = files[fileParamName][0].path.split(/[\/\\]+/);
|
|
1162
|
+
const filename = pathSplitted[pathSplitted.length - 1];
|
|
1163
|
+
(async () => {
|
|
1164
|
+
const sftp = new SftpClient();
|
|
1165
|
+
await sftp.connect({
|
|
1166
|
+
host: column.sftpConfig.host,
|
|
1167
|
+
port: 22,
|
|
1168
|
+
username: column.sftpConfig.user,
|
|
1169
|
+
password: column.sftpConfig.password,
|
|
1170
|
+
});
|
|
1171
|
+
await sftp.fastPut(
|
|
1172
|
+
files[fileParamName][0].path,
|
|
1173
|
+
column.publicPath +
|
|
1174
|
+
(column.subdirectory ? "/" + column.subdirectory : "") +
|
|
1175
|
+
"/" +
|
|
1176
|
+
filename
|
|
1177
|
+
);
|
|
1178
|
+
await sftp.end();
|
|
1179
|
+
res.send({
|
|
1180
|
+
path: filename,
|
|
1181
|
+
originalFilename: files[fileParamName][0].originalFilename,
|
|
1182
|
+
});
|
|
1183
|
+
fs.unlinkSync(files[fileParamName][0].path);
|
|
1184
|
+
})();
|
|
1185
|
+
});
|
|
1186
|
+
} else {
|
|
1187
|
+
const form = new multiparty.Form({
|
|
1188
|
+
uploadDir:
|
|
1189
|
+
(column.publicPath as string) + (column.subdirectory as string),
|
|
1190
|
+
});
|
|
1191
|
+
form.parse(req, function (err, fields, files) {
|
|
1192
|
+
if (err) {
|
|
1193
|
+
return res.status(500).send(err);
|
|
1194
|
+
}
|
|
1195
|
+
res.send({
|
|
1196
|
+
path: files[fileParamName][0].path.replace(column.publicPath, ""),
|
|
1197
|
+
originalFilename: files[fileParamName][0].originalFilename,
|
|
1198
|
+
});
|
|
1199
|
+
});
|
|
918
1200
|
}
|
|
1201
|
+
}
|
|
919
1202
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
1203
|
+
app.post(
|
|
1204
|
+
"/uploadFileFromBetterRichTextInput",
|
|
1205
|
+
async function (req, res, next) {
|
|
1206
|
+
const fileUploadProperties = JSON.parse(
|
|
1207
|
+
req.headers["file-upload-properties"] as string
|
|
1208
|
+
);
|
|
1209
|
+
uploadFile(fileUploadProperties, "upload", req, res);
|
|
1210
|
+
}
|
|
1211
|
+
);
|
|
924
1212
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1213
|
+
app.post("/uploadFile", async function (req, res, next) {
|
|
1214
|
+
const view = config.views[req.query.viewIndex as string];
|
|
1215
|
+
const column = [
|
|
1216
|
+
...view.columns,
|
|
1217
|
+
...(view.recordViewPanels
|
|
1218
|
+
?.map((x) => x?.createConfig?.columns)
|
|
1219
|
+
?.filter((x) => x)
|
|
1220
|
+
?.flat() || []),
|
|
1221
|
+
].find((x) => x.__id__ == req.query.columnId);
|
|
1222
|
+
uploadFile(column, "file", req, res);
|
|
1223
|
+
});
|
|
933
1224
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
.filter((etablissement) => etablissement.etatadministratifetablissement === "Actif")
|
|
945
|
-
.map((etablissement) => ({
|
|
946
|
-
id: Math.random().toString().slice(2),
|
|
947
|
-
companyName: etablissement.denominationunitelegale,
|
|
948
|
-
siret: etablissement.siret,
|
|
949
|
-
siren: etablissement.siren,
|
|
950
|
-
address: `${etablissement.numerovoieetablissement || ""} ${etablissement.typevoieetablissement || ""} ${etablissement.libellevoieetablissement || ""}`,
|
|
951
|
-
city: etablissement.libellecommuneetablissement,
|
|
952
|
-
zipCode: etablissement.codepostaletablissement,
|
|
953
|
-
activityNumber: etablissement.activiteprincipaleunitelegale,
|
|
954
|
-
}))
|
|
955
|
-
.slice(0, 30)
|
|
956
|
-
res.send(results)
|
|
957
|
-
} catch (e) {
|
|
958
|
-
res.send([])
|
|
1225
|
+
app.get("/searchCompanies", async (req, res) => {
|
|
1226
|
+
const processedQuery = (req.query.query as any).replaceAll(" ", "-");
|
|
1227
|
+
try {
|
|
1228
|
+
const response = await axios.get(
|
|
1229
|
+
`https://public.opendatasoft.com/api/explore/v2.1/catalog/datasets/economicref-france-sirene-v3/records`,
|
|
1230
|
+
{
|
|
1231
|
+
params: {
|
|
1232
|
+
where: `denominationunitelegale LIKE '${processedQuery}*' OR siret LIKE '${processedQuery}*'`,
|
|
1233
|
+
limit: 100,
|
|
1234
|
+
},
|
|
959
1235
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1236
|
+
);
|
|
1237
|
+
const results = response.data.results
|
|
1238
|
+
.filter(
|
|
1239
|
+
(etablissement) =>
|
|
1240
|
+
etablissement.etatadministratifetablissement === "Actif"
|
|
1241
|
+
)
|
|
1242
|
+
.map((etablissement) => ({
|
|
1243
|
+
id: Math.random().toString().slice(2),
|
|
1244
|
+
companyName: etablissement.denominationunitelegale,
|
|
1245
|
+
siret: etablissement.siret,
|
|
1246
|
+
siren: etablissement.siren,
|
|
1247
|
+
address: `${etablissement.numerovoieetablissement || ""} ${etablissement.typevoieetablissement || ""} ${etablissement.libellevoieetablissement || ""}`,
|
|
1248
|
+
city: etablissement.libellecommuneetablissement,
|
|
1249
|
+
zipCode: etablissement.codepostaletablissement,
|
|
1250
|
+
activityNumber: etablissement.activiteprincipaleunitelegale,
|
|
1251
|
+
}))
|
|
1252
|
+
.slice(0, 30);
|
|
1253
|
+
res.send(results);
|
|
1254
|
+
} catch (e) {
|
|
1255
|
+
res.send([]);
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
965
1258
|
|
|
1259
|
+
return app;
|
|
1260
|
+
};
|
|
966
1261
|
|
|
967
1262
|
async function enrichConfig(config, db) {
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
enrichColumn(column, tableName, info)
|
|
983
|
-
}
|
|
984
|
-
if (view.recordViewPanels) {
|
|
985
|
-
for (const panel of view.recordViewPanels) {
|
|
986
|
-
if (panel.createConfig) {
|
|
987
|
-
for (const column of panel.createConfig.columns) {
|
|
988
|
-
enrichColumn(column, tableName, info)
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
if (!panel.__id__) {
|
|
992
|
-
panel.__id__ = generateUniqueIdForObject(panel)
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
}
|
|
1263
|
+
const { rows: info, err } = await db.query(
|
|
1264
|
+
"SELECT * FROM information_schema.columns WHERE table_schema = ?",
|
|
1265
|
+
[config.mysql?.database || config.postgresql?.database]
|
|
1266
|
+
);
|
|
1267
|
+
if (err) {
|
|
1268
|
+
console.error(err);
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
for (const view of config.views) {
|
|
1272
|
+
if (view.type == "table") {
|
|
1273
|
+
const tableName = view.tableName;
|
|
1274
|
+
for (const infoEl of info) {
|
|
1275
|
+
if (infoEl.TABLE_NAME == tableName && infoEl.COLUMN_KEY == "PRI") {
|
|
1276
|
+
view.primaryId = infoEl.COLUMN_NAME;
|
|
996
1277
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1278
|
+
}
|
|
1279
|
+
for (const column of view.columns) {
|
|
1280
|
+
enrichColumn(column, tableName, info);
|
|
1281
|
+
}
|
|
1282
|
+
if (view.recordViewPanels) {
|
|
1283
|
+
for (const panel of view.recordViewPanels) {
|
|
1284
|
+
if (panel.createConfig) {
|
|
1285
|
+
for (const column of panel.createConfig.columns) {
|
|
1286
|
+
enrichColumn(column, tableName, info);
|
|
1002
1287
|
}
|
|
1288
|
+
}
|
|
1289
|
+
if (!panel.__id__) {
|
|
1290
|
+
panel.__id__ = generateUniqueIdForObject(panel);
|
|
1291
|
+
}
|
|
1003
1292
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
if (view.subviews) {
|
|
1296
|
+
for (const subview of view.subviews) {
|
|
1297
|
+
if (!subview.__id__) {
|
|
1298
|
+
subview.__id__ = generateUniqueIdForObject(subview);
|
|
1006
1299
|
}
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
if (!view.__id__) {
|
|
1303
|
+
view.__id__ = generateUniqueIdForObject(view);
|
|
1007
1304
|
}
|
|
1008
|
-
|
|
1305
|
+
}
|
|
1306
|
+
config.enriched = true;
|
|
1009
1307
|
}
|
|
1010
1308
|
|
|
1011
1309
|
function enrichColumn(column, tableName, info) {
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1310
|
+
if (!column.__id__) {
|
|
1311
|
+
column.__id__ = generateUniqueIdForObject(column);
|
|
1312
|
+
}
|
|
1313
|
+
const infoEl = info.find(
|
|
1314
|
+
(x) => x.TABLE_NAME == tableName && x.COLUMN_NAME == column.name
|
|
1315
|
+
);
|
|
1316
|
+
if (
|
|
1317
|
+
infoEl?.IS_NULLABLE == "NO" &&
|
|
1318
|
+
infoEl?.DATA_TYPE != "text" &&
|
|
1319
|
+
column.required !== true &&
|
|
1320
|
+
column.required !== false
|
|
1321
|
+
) {
|
|
1322
|
+
column.required = true;
|
|
1323
|
+
}
|
|
1324
|
+
if (!column.dataType && infoEl) {
|
|
1325
|
+
column.dataType = infoEl.DATA_TYPE;
|
|
1326
|
+
if (column.dataType == "enum") {
|
|
1327
|
+
column.dataType = "select";
|
|
1328
|
+
column.options = JSON.parse(
|
|
1329
|
+
infoEl.COLUMN_TYPE.replace("enum(", "[")
|
|
1330
|
+
.replace(")", "]")
|
|
1331
|
+
.replace(/'/g, '"')
|
|
1332
|
+
);
|
|
1031
1333
|
}
|
|
1334
|
+
}
|
|
1335
|
+
if (column.dataType == "richText") {
|
|
1336
|
+
column.name = column.contentField;
|
|
1337
|
+
}
|
|
1338
|
+
if (column.dataType == "file" && (column.s3Config || column.gcpConfig)) {
|
|
1339
|
+
const host = column.s3Config?.host || column.gcpConfig?.host;
|
|
1340
|
+
const bucket = column.s3Config?.bucket || column.gcpConfig?.bucket;
|
|
1341
|
+
column.baseUrl = `https://${host}/${bucket}/`;
|
|
1342
|
+
}
|
|
1032
1343
|
}
|