@saltcorn/data 1.5.0-beta.8 → 1.5.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base-plugin/actions.d.ts +221 -145
- package/dist/base-plugin/actions.d.ts.map +1 -1
- package/dist/base-plugin/actions.js +80 -12
- package/dist/base-plugin/actions.js.map +1 -1
- package/dist/base-plugin/fileviews.d.ts +3 -3
- package/dist/base-plugin/fileviews.js +20 -9
- package/dist/base-plugin/fileviews.js.map +1 -1
- package/dist/base-plugin/index.d.ts +2 -2
- package/dist/base-plugin/index.d.ts.map +1 -1
- package/dist/base-plugin/types.d.ts.map +1 -1
- package/dist/base-plugin/types.js +8 -5
- package/dist/base-plugin/types.js.map +1 -1
- package/dist/base-plugin/viewtemplates/edit.d.ts +6 -2
- package/dist/base-plugin/viewtemplates/edit.d.ts.map +1 -1
- package/dist/base-plugin/viewtemplates/edit.js +17 -341
- package/dist/base-plugin/viewtemplates/edit.js.map +1 -1
- package/dist/base-plugin/viewtemplates/filter.d.ts.map +1 -1
- package/dist/base-plugin/viewtemplates/filter.js +9 -3
- package/dist/base-plugin/viewtemplates/filter.js.map +1 -1
- package/dist/base-plugin/viewtemplates/show.d.ts.map +1 -1
- package/dist/base-plugin/viewtemplates/show.js +1 -0
- package/dist/base-plugin/viewtemplates/show.js.map +1 -1
- package/dist/base-plugin/viewtemplates/viewable_fields.d.ts +16 -0
- package/dist/base-plugin/viewtemplates/viewable_fields.d.ts.map +1 -1
- package/dist/base-plugin/viewtemplates/viewable_fields.js +340 -3
- package/dist/base-plugin/viewtemplates/viewable_fields.js.map +1 -1
- package/dist/base-plugin/viewtemplates/workflow-room.d.ts.map +1 -1
- package/dist/base-plugin/viewtemplates/workflow-room.js +1 -1
- package/dist/base-plugin/viewtemplates/workflow-room.js.map +1 -1
- package/dist/db/connect.d.ts.map +1 -1
- package/dist/db/connect.js +4 -1
- package/dist/db/connect.js.map +1 -1
- package/dist/db/state.d.ts +11 -7
- package/dist/db/state.d.ts.map +1 -1
- package/dist/db/state.js +78 -12
- package/dist/db/state.js.map +1 -1
- package/dist/mobile-mocks/npm/dockerode.d.ts +1 -0
- package/dist/mobile-mocks/npm/dockerode.d.ts.map +1 -0
- package/dist/mobile-mocks/npm/dockerode.js +2 -0
- package/dist/mobile-mocks/npm/dockerode.js.map +1 -0
- package/dist/models/config.d.ts.map +1 -1
- package/dist/models/config.js +55 -12
- package/dist/models/config.js.map +1 -1
- package/dist/models/email.js.map +1 -1
- package/dist/models/expression.d.ts.map +1 -1
- package/dist/models/expression.js +29 -0
- package/dist/models/expression.js.map +1 -1
- package/dist/models/field.d.ts +5 -3
- package/dist/models/field.d.ts.map +1 -1
- package/dist/models/field.js +8 -2
- package/dist/models/field.js.map +1 -1
- package/dist/models/file.d.ts +18 -1
- package/dist/models/file.d.ts.map +1 -1
- package/dist/models/file.js +361 -43
- package/dist/models/file.js.map +1 -1
- package/dist/models/form.d.ts.map +1 -1
- package/dist/models/form.js.map +1 -1
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/internal/push_message_helper.d.ts +42 -12
- package/dist/models/internal/push_message_helper.d.ts.map +1 -1
- package/dist/models/internal/push_message_helper.js +117 -50
- package/dist/models/internal/push_message_helper.js.map +1 -1
- package/dist/models/internal/s3_helpers.d.ts +54 -0
- package/dist/models/internal/s3_helpers.d.ts.map +1 -0
- package/dist/models/internal/s3_helpers.js +561 -0
- package/dist/models/internal/s3_helpers.js.map +1 -0
- package/dist/models/model.d.ts +1 -1
- package/dist/models/model.d.ts.map +1 -1
- package/dist/models/model.js.map +1 -1
- package/dist/models/notification.d.ts.map +1 -1
- package/dist/models/notification.js +2 -5
- package/dist/models/notification.js.map +1 -1
- package/dist/models/page.d.ts +1 -0
- package/dist/models/page.d.ts.map +1 -1
- package/dist/models/page.js +27 -24
- package/dist/models/page.js.map +1 -1
- package/dist/models/plugin.d.ts +11 -3
- package/dist/models/plugin.d.ts.map +1 -1
- package/dist/models/plugin.js +51 -12
- package/dist/models/plugin.js.map +1 -1
- package/dist/models/s3_helpers.d.ts +54 -0
- package/dist/models/s3_helpers.d.ts.map +1 -0
- package/dist/models/s3_helpers.js +505 -0
- package/dist/models/s3_helpers.js.map +1 -0
- package/dist/models/scheduler.d.ts.map +1 -1
- package/dist/models/scheduler.js +4 -1
- package/dist/models/scheduler.js.map +1 -1
- package/dist/models/table.d.ts +8 -3
- package/dist/models/table.d.ts.map +1 -1
- package/dist/models/table.js +121 -32
- package/dist/models/table.js.map +1 -1
- package/dist/models/user.d.ts +4 -4
- package/dist/models/user.d.ts.map +1 -1
- package/dist/models/user.js.map +1 -1
- package/dist/models/view.js +1 -1
- package/dist/models/view.js.map +1 -1
- package/dist/models/workflow_run.d.ts +1 -1
- package/dist/models/workflow_run.d.ts.map +1 -1
- package/dist/models/workflow_run.js +8 -2
- package/dist/models/workflow_run.js.map +1 -1
- package/dist/models/workflow_step.js +1 -1
- package/dist/models/workflow_step.js.map +1 -1
- package/dist/plugin-helper.d.ts.map +1 -1
- package/dist/plugin-helper.js +139 -15
- package/dist/plugin-helper.js.map +1 -1
- package/dist/standard-menu.js +2 -2
- package/dist/standard-menu.js.map +1 -1
- package/dist/test-utils/mocks.d.ts +203 -0
- package/dist/test-utils/mocks.d.ts.map +1 -0
- package/dist/test-utils/mocks.js +329 -0
- package/dist/test-utils/mocks.js.map +1 -0
- package/dist/tests/assertions.d.ts.map +1 -1
- package/dist/tests/assertions.js +10 -9
- package/dist/tests/assertions.js.map +1 -1
- package/dist/tests/auth.test.js +0 -68
- package/dist/tests/auth.test.js.map +1 -1
- package/dist/tests/calc.test.js +3 -62
- package/dist/tests/calc.test.js.map +1 -1
- package/dist/tests/exact_views.test.js +14 -14
- package/dist/tests/exact_views.test.js.map +1 -1
- package/dist/tests/show.test.js +1 -1
- package/dist/tests/show.test.js.map +1 -1
- package/dist/tests/table.test.js +0 -37
- package/dist/tests/table.test.js.map +1 -1
- package/dist/tests/table_sync_info.test.d.ts +2 -0
- package/dist/tests/table_sync_info.test.d.ts.map +1 -0
- package/dist/tests/table_sync_info.test.js +62 -0
- package/dist/tests/table_sync_info.test.js.map +1 -0
- package/dist/tests/user.test.js +4 -29
- package/dist/tests/user.test.js.map +1 -1
- package/dist/tests/view.test.js +0 -3
- package/dist/tests/view.test.js.map +1 -1
- package/dist/utils.d.ts +11 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +33 -3
- package/dist/utils.js.map +1 -1
- package/dist/viewable_fields.d.ts +172 -0
- package/dist/viewable_fields.d.ts.map +1 -0
- package/dist/viewable_fields.js +1562 -0
- package/dist/viewable_fields.js.map +1 -0
- package/dist/web-mobile-commons.d.ts +1 -1
- package/dist/web-mobile-commons.d.ts.map +1 -1
- package/dist/web-mobile-commons.js +19 -3
- package/dist/web-mobile-commons.js.map +1 -1
- package/package.json +14 -9
- package/webpack.config.js +3 -0
package/dist/models/file.js
CHANGED
|
@@ -11,15 +11,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
11
11
|
const db_1 = __importDefault(require("../db"));
|
|
12
12
|
const uuid_1 = require("uuid");
|
|
13
13
|
const path_1 = require("path");
|
|
14
|
+
const s3_helpers_1 = require("./internal/s3_helpers");
|
|
14
15
|
const { asyncMap } = require("../utils");
|
|
15
16
|
const promises_1 = require("fs/promises");
|
|
16
17
|
const axios_1 = __importDefault(require("axios"));
|
|
17
18
|
const form_data_1 = __importDefault(require("form-data"));
|
|
18
19
|
const mime_types_1 = require("mime-types");
|
|
19
20
|
const path = require("path");
|
|
21
|
+
const posix = path.posix;
|
|
20
22
|
const fsp = require("fs").promises;
|
|
21
23
|
const fs = require("fs");
|
|
22
24
|
const fsx = require("fs-extended-attributes");
|
|
25
|
+
const ABSOLUTE_URL_RE = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//;
|
|
23
26
|
function xattr_set(fp, attrName, value) {
|
|
24
27
|
return new Promise((resolve) => fsx.set(fp, attrName, value, resolve));
|
|
25
28
|
}
|
|
@@ -33,6 +36,7 @@ function xattr_get(fp, attrName) {
|
|
|
33
36
|
}
|
|
34
37
|
const dirCache = {};
|
|
35
38
|
const enableDirCache = {};
|
|
39
|
+
const DEFAULT_MIN_ROLE_READ = 100;
|
|
36
40
|
/**
|
|
37
41
|
* File Descriptor class
|
|
38
42
|
*
|
|
@@ -73,14 +77,12 @@ class File {
|
|
|
73
77
|
* @returns {Promise<*>}
|
|
74
78
|
*/
|
|
75
79
|
static async find(where, selectopts = {}) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const db_flds = await db_1.default.select("_sc_files", where, selectopts);
|
|
83
|
-
return db_flds.map((dbf) => new File(dbf));
|
|
80
|
+
if (where?.inDB) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
const useS3 = (0, s3_helpers_1.isS3Enabled)();
|
|
84
|
+
if (useS3) {
|
|
85
|
+
return await File.findInS3(where, selectopts);
|
|
84
86
|
}
|
|
85
87
|
else {
|
|
86
88
|
const relativeSearchFolder = where?.folder || "/";
|
|
@@ -144,11 +146,81 @@ class File {
|
|
|
144
146
|
else
|
|
145
147
|
return null;
|
|
146
148
|
}
|
|
149
|
+
static isAbsoluteURL(value) {
|
|
150
|
+
return typeof value === "string" && ABSOLUTE_URL_RE.test(value);
|
|
151
|
+
}
|
|
152
|
+
static fieldValueFromRelative(relPath) {
|
|
153
|
+
if (!relPath)
|
|
154
|
+
return relPath || "";
|
|
155
|
+
const cleaned = relPath.replace(/^[\/]+/, "").replace(/\\/g, "/");
|
|
156
|
+
return cleaned;
|
|
157
|
+
}
|
|
158
|
+
static relativePathFromFieldValue(value) {
|
|
159
|
+
if (typeof value !== "string")
|
|
160
|
+
return null;
|
|
161
|
+
const trimmed = value.trim();
|
|
162
|
+
if (!trimmed)
|
|
163
|
+
return "";
|
|
164
|
+
if (File.isAbsoluteURL(trimmed)) {
|
|
165
|
+
if ((0, s3_helpers_1.isS3Enabled)()) {
|
|
166
|
+
const rel = (0, s3_helpers_1.publicUrlToRelativePath)(trimmed);
|
|
167
|
+
if (rel)
|
|
168
|
+
return rel;
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const normalised = File.normalise(trimmed).replace(/\\/g, "/");
|
|
173
|
+
return normalised.startsWith("/") ? normalised.slice(1) : normalised;
|
|
174
|
+
}
|
|
175
|
+
static normalizeFieldValueInput(value) {
|
|
176
|
+
if (typeof value !== "string")
|
|
177
|
+
return value || "";
|
|
178
|
+
const relative = File.relativePathFromFieldValue(value);
|
|
179
|
+
if (relative === null)
|
|
180
|
+
return value.trim();
|
|
181
|
+
return File.fieldValueFromRelative(relative);
|
|
182
|
+
}
|
|
183
|
+
static pathToServeUrl(value, opts = {}) {
|
|
184
|
+
if (!value)
|
|
185
|
+
return "";
|
|
186
|
+
const trimmed = value.trim();
|
|
187
|
+
if (File.isAbsoluteURL(trimmed))
|
|
188
|
+
return trimmed;
|
|
189
|
+
const relative = File.relativePathFromFieldValue(trimmed);
|
|
190
|
+
if (relative === null)
|
|
191
|
+
return trimmed;
|
|
192
|
+
if ((0, s3_helpers_1.isS3Enabled)()) {
|
|
193
|
+
try {
|
|
194
|
+
const { getState } = require("../db/state");
|
|
195
|
+
const direct = !!getState()?.getConfig("files_direct_s3_links");
|
|
196
|
+
if (direct && opts.preferDirect !== false) {
|
|
197
|
+
return (0, s3_helpers_1.getPublicFileUrl)(relative, {
|
|
198
|
+
download: opts.download,
|
|
199
|
+
filename: opts.filename || posix.basename(relative),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch (e) {
|
|
204
|
+
// ignore and fall back to proxied serving
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const cleaned = File.fieldValueFromRelative(relative);
|
|
208
|
+
const safePath = cleaned.replace(/^[\/]+/, "");
|
|
209
|
+
const route = opts.download ? "download" : "serve";
|
|
210
|
+
const prefix = opts.targetPrefix || "";
|
|
211
|
+
return `${prefix}/files/${route}/${safePath}`;
|
|
212
|
+
}
|
|
147
213
|
static async rootFolder() {
|
|
214
|
+
if ((0, s3_helpers_1.isS3Enabled)())
|
|
215
|
+
return File.directoryFromS3Path("/");
|
|
148
216
|
const tenant = db_1.default.getTenantSchema();
|
|
149
217
|
return await File.from_file_on_disk("/", path.join(db_1.default.connectObj.file_store, tenant));
|
|
150
218
|
}
|
|
151
219
|
static absPathToServePath(absPath) {
|
|
220
|
+
if ((0, s3_helpers_1.isS3Enabled)()) {
|
|
221
|
+
const rel = File.normalise(`${absPath}`);
|
|
222
|
+
return rel.startsWith("/") ? rel.slice(1) : rel;
|
|
223
|
+
}
|
|
152
224
|
if (typeof absPath === "number")
|
|
153
225
|
return `${absPath}`;
|
|
154
226
|
const tenant = db_1.default.getTenantSchema();
|
|
@@ -166,6 +238,21 @@ class File {
|
|
|
166
238
|
* @returns
|
|
167
239
|
*/
|
|
168
240
|
static async allDirectories(ignoreCache) {
|
|
241
|
+
if ((0, s3_helpers_1.isS3Enabled)()) {
|
|
242
|
+
const { files } = await (0, s3_helpers_1.listS3Folder)("/", { recursive: true });
|
|
243
|
+
const dirs = new Set(["/"]);
|
|
244
|
+
for (const obj of files) {
|
|
245
|
+
const relPath = obj.relativePath || "";
|
|
246
|
+
const dirName = relPath.includes("/") ? posix.dirname(relPath) : "";
|
|
247
|
+
if (!dirName || dirName === ".")
|
|
248
|
+
continue;
|
|
249
|
+
const parts = dirName.split("/").filter(Boolean);
|
|
250
|
+
for (let i = 1; i <= parts.length; i++) {
|
|
251
|
+
dirs.add(parts.slice(0, i).join("/"));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return Array.from(dirs).map((dir) => File.directoryFromS3Path(dir === "" ? "/" : dir));
|
|
255
|
+
}
|
|
169
256
|
const tenant = db_1.default.getTenantSchema();
|
|
170
257
|
if (!ignoreCache) {
|
|
171
258
|
const cache = File.getDirCache();
|
|
@@ -233,6 +320,91 @@ class File {
|
|
|
233
320
|
enableDirCache[db_1.default.getTenantSchema()] = false;
|
|
234
321
|
dirCache[db_1.default.getTenantSchema()] = null;
|
|
235
322
|
}
|
|
323
|
+
static async findInS3(where, selectopts = {}) {
|
|
324
|
+
const folder = typeof where?.folder === "string" ? where.folder : "/";
|
|
325
|
+
const recursive = !!selectopts.recursive || !!where?.search;
|
|
326
|
+
const { files, directories } = await (0, s3_helpers_1.listS3Folder)(folder, { recursive });
|
|
327
|
+
const results = [];
|
|
328
|
+
for (const dir of directories) {
|
|
329
|
+
const dirFile = File.directoryFromS3Path(dir || "/");
|
|
330
|
+
if (dirFile.filename?.startsWith(".") && !where?.hiddenFiles)
|
|
331
|
+
continue;
|
|
332
|
+
results.push(dirFile);
|
|
333
|
+
}
|
|
334
|
+
const headResults = await Promise.all(files.map((obj) => (0, s3_helpers_1.headObject)(obj.relativePath)));
|
|
335
|
+
for (const head of headResults) {
|
|
336
|
+
if (!head)
|
|
337
|
+
continue;
|
|
338
|
+
const file = File.fileFromS3Head(head);
|
|
339
|
+
if (file.filename?.startsWith(".") && !where?.hiddenFiles)
|
|
340
|
+
continue;
|
|
341
|
+
results.push(file);
|
|
342
|
+
}
|
|
343
|
+
let filtered = results;
|
|
344
|
+
if (where?.filename) {
|
|
345
|
+
filtered = filtered.filter((f) => f.filename === where.filename);
|
|
346
|
+
}
|
|
347
|
+
if (where?.mime_super) {
|
|
348
|
+
filtered = filtered.filter((f) => f.mime_super === where.mime_super);
|
|
349
|
+
}
|
|
350
|
+
if (where?.mime_sub) {
|
|
351
|
+
filtered = filtered.filter((f) => f.mime_sub === where.mime_sub);
|
|
352
|
+
}
|
|
353
|
+
if (where?.isDirectory !== undefined) {
|
|
354
|
+
filtered = filtered.filter((f) => !!f.isDirectory === !!where.isDirectory);
|
|
355
|
+
}
|
|
356
|
+
if (where?.search) {
|
|
357
|
+
filtered = filtered.filter((f) => f.filename.includes(where.search));
|
|
358
|
+
}
|
|
359
|
+
return filtered.sort((a, b) => a.filename.localeCompare(b.filename));
|
|
360
|
+
}
|
|
361
|
+
static async findOneS3(where) {
|
|
362
|
+
if (typeof where === "string") {
|
|
363
|
+
const safePath = File.normalise(where);
|
|
364
|
+
const head = await (0, s3_helpers_1.headObject)(safePath);
|
|
365
|
+
return head ? File.fileFromS3Head(head) : null;
|
|
366
|
+
}
|
|
367
|
+
const results = await File.findInS3(where);
|
|
368
|
+
return results[0] || null;
|
|
369
|
+
}
|
|
370
|
+
static fileFromS3Head(info) {
|
|
371
|
+
const metadata = info.metadata || {};
|
|
372
|
+
const mime = metadata.mime_super && metadata.mime_sub
|
|
373
|
+
? `${metadata.mime_super}/${metadata.mime_sub}`
|
|
374
|
+
: File.nameToMimeType(metadata.filename || info.relativePath) || "";
|
|
375
|
+
const [mime_super, mime_sub] = mime ? mime.split("/") : ["", ""];
|
|
376
|
+
return new File({
|
|
377
|
+
filename: metadata.filename || posix.basename(info.relativePath),
|
|
378
|
+
location: info.relativePath,
|
|
379
|
+
uploaded_at: info.lastModified || new Date(),
|
|
380
|
+
size_kb: Math.max(1, Math.round((info.size || 0) / 1024)),
|
|
381
|
+
mime_super,
|
|
382
|
+
mime_sub,
|
|
383
|
+
min_role_read: metadata.min_role_read || DEFAULT_MIN_ROLE_READ,
|
|
384
|
+
user_id: metadata.user_id,
|
|
385
|
+
s3_store: true,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
static directoryFromS3Path(dirPath) {
|
|
389
|
+
const normalised = File.normalise(dirPath) || "/";
|
|
390
|
+
const filename = normalised === "/" ? "/" : posix.basename(normalised);
|
|
391
|
+
return new File({
|
|
392
|
+
filename,
|
|
393
|
+
location: normalised,
|
|
394
|
+
uploaded_at: new Date(),
|
|
395
|
+
size_kb: 0,
|
|
396
|
+
mime_super: "",
|
|
397
|
+
mime_sub: "",
|
|
398
|
+
min_role_read: DEFAULT_MIN_ROLE_READ,
|
|
399
|
+
s3_store: true,
|
|
400
|
+
isDirectory: true,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
static relativeS3Path(key) {
|
|
404
|
+
const normalised = File.normalise(key);
|
|
405
|
+
const rel = (0, s3_helpers_1.relativeKeyToPath)(normalised);
|
|
406
|
+
return rel.startsWith("/") ? rel.slice(1) : rel;
|
|
407
|
+
}
|
|
236
408
|
async is_symlink() {
|
|
237
409
|
try {
|
|
238
410
|
let stat = await fsp.lstat(this.location);
|
|
@@ -296,7 +468,7 @@ class File {
|
|
|
296
468
|
if (typeof where === "string") {
|
|
297
469
|
const { getState } = require("../db/state");
|
|
298
470
|
const state = getState();
|
|
299
|
-
const useS3 =
|
|
471
|
+
const useS3 = (0, s3_helpers_1.isS3Enabled)();
|
|
300
472
|
//legacy serving ids
|
|
301
473
|
if (/^\d+$/.test(where)) {
|
|
302
474
|
const legacy_file_id_locations = state?.getConfig("legacy_file_id_locations");
|
|
@@ -307,26 +479,25 @@ class File {
|
|
|
307
479
|
}
|
|
308
480
|
}
|
|
309
481
|
if (useS3) {
|
|
310
|
-
const
|
|
311
|
-
|
|
482
|
+
const relative = File.relativePathFromFieldValue(where);
|
|
483
|
+
const lookup = relative === null ? where : relative;
|
|
484
|
+
return await File.findOneS3(lookup);
|
|
312
485
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
return null;
|
|
325
|
-
}
|
|
486
|
+
const tenant = db_1.default.getTenantSchema();
|
|
487
|
+
const safeDir = path.normalize(where).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
488
|
+
const absoluteFolder = path.join(db_1.default.connectObj.file_store, tenant, safeDir);
|
|
489
|
+
const name = path.basename(absoluteFolder);
|
|
490
|
+
const dir = path.dirname(absoluteFolder);
|
|
491
|
+
try {
|
|
492
|
+
return await File.from_file_on_disk(name, dir);
|
|
493
|
+
}
|
|
494
|
+
catch (e) {
|
|
495
|
+
state?.log(2, e?.toString ? e.toString() : e);
|
|
496
|
+
return null;
|
|
326
497
|
}
|
|
327
498
|
}
|
|
328
499
|
const files = await File.find(where);
|
|
329
|
-
return files.length > 0 ?
|
|
500
|
+
return files.length > 0 ? files[0] : null;
|
|
330
501
|
}
|
|
331
502
|
/**
|
|
332
503
|
* Create new folder
|
|
@@ -334,6 +505,23 @@ class File {
|
|
|
334
505
|
* @param inFolder
|
|
335
506
|
*/
|
|
336
507
|
static async new_folder(name, inFolder = "") {
|
|
508
|
+
if ((0, s3_helpers_1.isS3Enabled)()) {
|
|
509
|
+
const folderName = File.normalise(name).replace(/\\/g, "/");
|
|
510
|
+
const parent = File.normalise(inFolder).replace(/\\/g, "/");
|
|
511
|
+
const target = [parent, folderName]
|
|
512
|
+
.filter((s) => s && s !== ".")
|
|
513
|
+
.join("/");
|
|
514
|
+
const placeholder = target
|
|
515
|
+
? `${target.replace(/\/$/, "")}/.keep`
|
|
516
|
+
: ".keep";
|
|
517
|
+
await (0, s3_helpers_1.uploadBuffer)(placeholder, Buffer.alloc(0), "application/octet-stream", {
|
|
518
|
+
min_role_read: DEFAULT_MIN_ROLE_READ,
|
|
519
|
+
filename: ".keep",
|
|
520
|
+
mime_super: "application",
|
|
521
|
+
mime_sub: "octet-stream",
|
|
522
|
+
});
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
337
525
|
const tenant = db_1.default.getTenantSchema();
|
|
338
526
|
const safeDir = path.normalize(name).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
339
527
|
const safeInDir = path.normalize(inFolder).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
@@ -345,12 +533,19 @@ class File {
|
|
|
345
533
|
* Get path to serve
|
|
346
534
|
*/
|
|
347
535
|
get path_to_serve() {
|
|
348
|
-
if (this.s3_store
|
|
349
|
-
|
|
536
|
+
if (this.s3_store) {
|
|
537
|
+
const rel = File.normalise(this.location);
|
|
538
|
+
return rel.startsWith("/") ? rel.slice(1) : rel;
|
|
539
|
+
}
|
|
350
540
|
const tenant = db_1.default.getTenantSchema();
|
|
351
541
|
const s = this.location.replace(path.join(db_1.default.connectObj.file_store, tenant), "");
|
|
352
542
|
return s[0] === "/" ? s.substring(1) : s;
|
|
353
543
|
}
|
|
544
|
+
get field_value() {
|
|
545
|
+
if (this.isDirectory)
|
|
546
|
+
return this.path_to_serve;
|
|
547
|
+
return File.fieldValueFromRelative(this.path_to_serve);
|
|
548
|
+
}
|
|
354
549
|
/**
|
|
355
550
|
* Get current folder
|
|
356
551
|
*/
|
|
@@ -372,26 +567,45 @@ class File {
|
|
|
372
567
|
* @param min_role_read target Role for file to read
|
|
373
568
|
*/
|
|
374
569
|
async set_role(min_role_read) {
|
|
570
|
+
this.min_role_read = min_role_read;
|
|
571
|
+
if (this.s3_store) {
|
|
572
|
+
await this.persistS3Metadata();
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
375
575
|
if (this.id) {
|
|
376
576
|
await File.update(this.id, { min_role_read });
|
|
377
577
|
}
|
|
378
578
|
else {
|
|
379
579
|
await xattr_set(this.location, "user.saltcorn.min_role_read", `${min_role_read}`);
|
|
380
580
|
}
|
|
381
|
-
this.min_role_read = min_role_read;
|
|
382
581
|
}
|
|
383
582
|
/**
|
|
384
583
|
* Set user for file
|
|
385
584
|
* @param user_id target user_id
|
|
386
585
|
*/
|
|
387
586
|
async set_user(user_id) {
|
|
587
|
+
this.user_id = user_id;
|
|
588
|
+
if (this.s3_store) {
|
|
589
|
+
await this.persistS3Metadata();
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
388
592
|
if (this.id) {
|
|
389
593
|
await File.update(this.id, { user_id });
|
|
390
594
|
}
|
|
391
595
|
else {
|
|
392
596
|
await xattr_set(this.location, "user.saltcorn.user_id", `${user_id}`);
|
|
393
597
|
}
|
|
394
|
-
|
|
598
|
+
}
|
|
599
|
+
async persistS3Metadata() {
|
|
600
|
+
if (!this.s3_store)
|
|
601
|
+
return;
|
|
602
|
+
await (0, s3_helpers_1.setObjectMetadata)(this.location, {
|
|
603
|
+
min_role_read: this.min_role_read,
|
|
604
|
+
user_id: this.user_id,
|
|
605
|
+
mime_super: this.mime_super,
|
|
606
|
+
mime_sub: this.mime_sub,
|
|
607
|
+
filename: this.filename,
|
|
608
|
+
});
|
|
395
609
|
}
|
|
396
610
|
/**
|
|
397
611
|
* Rename file
|
|
@@ -399,6 +613,28 @@ class File {
|
|
|
399
613
|
*/
|
|
400
614
|
async rename(filenameIn) {
|
|
401
615
|
const filename = File.normalise(filenameIn);
|
|
616
|
+
if (this.s3_store) {
|
|
617
|
+
const currentDir = this.location.includes("/")
|
|
618
|
+
? posix.dirname(this.location)
|
|
619
|
+
: "";
|
|
620
|
+
const newRel = currentDir
|
|
621
|
+
? `${currentDir}/${filename}`.replace(/\\/g, "/")
|
|
622
|
+
: filename;
|
|
623
|
+
await (0, s3_helpers_1.copyObject)(this.location, newRel, {
|
|
624
|
+
metadata: {
|
|
625
|
+
min_role_read: this.min_role_read,
|
|
626
|
+
user_id: this.user_id,
|
|
627
|
+
mime_super: this.mime_super,
|
|
628
|
+
mime_sub: this.mime_sub,
|
|
629
|
+
filename,
|
|
630
|
+
},
|
|
631
|
+
});
|
|
632
|
+
await (0, s3_helpers_1.deleteObject)(this.location);
|
|
633
|
+
await File.update_table_references(this.path_to_serve, newRel);
|
|
634
|
+
this.location = newRel;
|
|
635
|
+
this.filename = filename;
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
402
638
|
if (this.id) {
|
|
403
639
|
await File.update(this.id, { filename });
|
|
404
640
|
}
|
|
@@ -420,11 +656,18 @@ class File {
|
|
|
420
656
|
const Table = require("./table");
|
|
421
657
|
const fileFields = await Field.find({ type: "File" }, { cached: true });
|
|
422
658
|
const schema = db_1.default.getTenantSchemaPrefix();
|
|
659
|
+
const targetValue = File.fieldValueFromRelative(to);
|
|
660
|
+
const fromValues = new Set([from]);
|
|
661
|
+
if ((0, s3_helpers_1.isS3Enabled)()) {
|
|
662
|
+
fromValues.add(File.fieldValueFromRelative(from));
|
|
663
|
+
}
|
|
423
664
|
for (const field of fileFields) {
|
|
424
665
|
const table = Table.findOne({ id: field.table_id });
|
|
425
666
|
if (table.external || table.provider_name)
|
|
426
667
|
continue;
|
|
427
|
-
|
|
668
|
+
for (const fromValue of fromValues) {
|
|
669
|
+
await db_1.default.query(`update ${schema}"${db_1.default.sqlsanitize(table.name)}" set "${field.name}" = $1 where "${field.name}" = $2`, [targetValue, fromValue]);
|
|
670
|
+
}
|
|
428
671
|
}
|
|
429
672
|
}
|
|
430
673
|
/**
|
|
@@ -433,6 +676,26 @@ class File {
|
|
|
433
676
|
*/
|
|
434
677
|
async move_to_dir(newFolder) {
|
|
435
678
|
const newFolderNormd = File.normalise(newFolder);
|
|
679
|
+
if (this.s3_store) {
|
|
680
|
+
const relative = newFolderNormd
|
|
681
|
+
.replace(/\\/g, "/")
|
|
682
|
+
.replace(/^(\/)+/, "")
|
|
683
|
+
.replace(/\/$/, "");
|
|
684
|
+
const newRel = relative ? `${relative}/${this.filename}` : this.filename;
|
|
685
|
+
await (0, s3_helpers_1.copyObject)(this.location, newRel, {
|
|
686
|
+
metadata: {
|
|
687
|
+
min_role_read: this.min_role_read,
|
|
688
|
+
user_id: this.user_id,
|
|
689
|
+
mime_super: this.mime_super,
|
|
690
|
+
mime_sub: this.mime_sub,
|
|
691
|
+
filename: this.filename,
|
|
692
|
+
},
|
|
693
|
+
});
|
|
694
|
+
await (0, s3_helpers_1.deleteObject)(this.location);
|
|
695
|
+
await File.update_table_references(this.path_to_serve, newRel);
|
|
696
|
+
this.location = newRel;
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
436
699
|
const tenant = db_1.default.getTenantSchema();
|
|
437
700
|
const file_store = db_1.default.connectObj.file_store;
|
|
438
701
|
const newPath = path.join(file_store, tenant, newFolderNormd, this.filename);
|
|
@@ -448,12 +711,13 @@ class File {
|
|
|
448
711
|
* @returns {string} - path to file
|
|
449
712
|
*/
|
|
450
713
|
static get_new_path(suggest, renameIfExisting) {
|
|
451
|
-
const
|
|
452
|
-
const state = getState();
|
|
453
|
-
// Check if it uses S3, then use a default "saltcorn" folder
|
|
454
|
-
const useS3 = state?.getConfig("storage_s3_enabled");
|
|
714
|
+
const useS3 = (0, s3_helpers_1.isS3Enabled)();
|
|
455
715
|
const tenant = db_1.default.getTenantSchema();
|
|
456
|
-
|
|
716
|
+
if (useS3) {
|
|
717
|
+
const relative = (suggest || (0, uuid_1.v4)()).replace(/\\/g, "/");
|
|
718
|
+
return (0, s3_helpers_1.buildKeyFromRelative)(relative);
|
|
719
|
+
}
|
|
720
|
+
const file_store = db_1.default.connectObj.file_store;
|
|
457
721
|
const newFnm = suggest || (0, uuid_1.v4)();
|
|
458
722
|
let newPath = (0, path_1.join)(file_store, tenant, newFnm);
|
|
459
723
|
if (renameIfExisting && fs.existsSync(newPath)) {
|
|
@@ -529,6 +793,12 @@ class File {
|
|
|
529
793
|
}
|
|
530
794
|
}
|
|
531
795
|
async get_contents(encoding) {
|
|
796
|
+
if (this.s3_store) {
|
|
797
|
+
const buffer = await (0, s3_helpers_1.downloadBuffer)(this.location);
|
|
798
|
+
return encoding
|
|
799
|
+
? buffer.toString(encoding)
|
|
800
|
+
: buffer;
|
|
801
|
+
}
|
|
532
802
|
return await fsp.readFile(this.location, encoding);
|
|
533
803
|
}
|
|
534
804
|
/**
|
|
@@ -548,6 +818,27 @@ class File {
|
|
|
548
818
|
const [mime_super, mime_sub] = mimetype.split("/");
|
|
549
819
|
// move file in file system to newPath
|
|
550
820
|
const contents1 = contents instanceof ArrayBuffer ? Buffer.from(contents) : contents;
|
|
821
|
+
if ((0, s3_helpers_1.isS3Enabled)()) {
|
|
822
|
+
const relativePath = File.relativeS3Path(newPath);
|
|
823
|
+
await (0, s3_helpers_1.uploadBuffer)(relativePath, Buffer.from(contents1), mimetype, {
|
|
824
|
+
min_role_read,
|
|
825
|
+
user_id,
|
|
826
|
+
mime_super,
|
|
827
|
+
mime_sub,
|
|
828
|
+
filename: path.basename(name),
|
|
829
|
+
});
|
|
830
|
+
return await File.create({
|
|
831
|
+
filename: path.basename(name),
|
|
832
|
+
location: relativePath,
|
|
833
|
+
uploaded_at: new Date(),
|
|
834
|
+
size_kb: Math.round(contents1.length / 1024),
|
|
835
|
+
user_id,
|
|
836
|
+
mime_super,
|
|
837
|
+
mime_sub,
|
|
838
|
+
min_role_read,
|
|
839
|
+
s3_store: true,
|
|
840
|
+
});
|
|
841
|
+
}
|
|
551
842
|
await fsp.writeFile(newPath, contents1);
|
|
552
843
|
// create file
|
|
553
844
|
const file = await File.create({
|
|
@@ -564,7 +855,18 @@ class File {
|
|
|
564
855
|
return file;
|
|
565
856
|
}
|
|
566
857
|
async overwrite_contents(contents) {
|
|
567
|
-
|
|
858
|
+
const data = contents instanceof ArrayBuffer ? Buffer.from(contents) : contents;
|
|
859
|
+
if (this.s3_store) {
|
|
860
|
+
await (0, s3_helpers_1.uploadBuffer)(this.location, Buffer.from(data), this.mimetype, {
|
|
861
|
+
min_role_read: this.min_role_read,
|
|
862
|
+
user_id: this.user_id,
|
|
863
|
+
mime_super: this.mime_super,
|
|
864
|
+
mime_sub: this.mime_sub,
|
|
865
|
+
filename: this.filename,
|
|
866
|
+
});
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
await fsp.writeFile(this.location, data);
|
|
568
870
|
}
|
|
569
871
|
/**
|
|
570
872
|
* Delete file
|
|
@@ -576,6 +878,20 @@ class File {
|
|
|
576
878
|
if (this.id)
|
|
577
879
|
await db_1.default.deleteWhere("_sc_files", { id: this.id });
|
|
578
880
|
// delete name and possible file from file system
|
|
881
|
+
if (this.s3_store) {
|
|
882
|
+
if (this.isDirectory) {
|
|
883
|
+
const { files } = await (0, s3_helpers_1.listS3Folder)(this.location, {
|
|
884
|
+
recursive: true,
|
|
885
|
+
});
|
|
886
|
+
for (const obj of files) {
|
|
887
|
+
await (0, s3_helpers_1.deleteObject)(obj.relativePath);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
else {
|
|
891
|
+
await (0, s3_helpers_1.deleteObject)(this.location);
|
|
892
|
+
}
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
579
895
|
if (unlinker)
|
|
580
896
|
await unlinker(this);
|
|
581
897
|
else if (this.isDirectory) {
|
|
@@ -615,13 +931,15 @@ class File {
|
|
|
615
931
|
*/
|
|
616
932
|
static async create(f) {
|
|
617
933
|
const file = new File(f);
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
934
|
+
if (file.s3_store) {
|
|
935
|
+
file.location = File.relativeS3Path(file.location);
|
|
936
|
+
await file.persistS3Metadata();
|
|
937
|
+
}
|
|
938
|
+
else {
|
|
939
|
+
await file.set_role(file.min_role_read);
|
|
940
|
+
if (file.user_id)
|
|
941
|
+
await file.set_user(file.user_id);
|
|
942
|
+
}
|
|
625
943
|
return file;
|
|
626
944
|
}
|
|
627
945
|
/**
|