@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.
Files changed (146) hide show
  1. package/dist/base-plugin/actions.d.ts +221 -145
  2. package/dist/base-plugin/actions.d.ts.map +1 -1
  3. package/dist/base-plugin/actions.js +80 -12
  4. package/dist/base-plugin/actions.js.map +1 -1
  5. package/dist/base-plugin/fileviews.d.ts +3 -3
  6. package/dist/base-plugin/fileviews.js +20 -9
  7. package/dist/base-plugin/fileviews.js.map +1 -1
  8. package/dist/base-plugin/index.d.ts +2 -2
  9. package/dist/base-plugin/index.d.ts.map +1 -1
  10. package/dist/base-plugin/types.d.ts.map +1 -1
  11. package/dist/base-plugin/types.js +8 -5
  12. package/dist/base-plugin/types.js.map +1 -1
  13. package/dist/base-plugin/viewtemplates/edit.d.ts +6 -2
  14. package/dist/base-plugin/viewtemplates/edit.d.ts.map +1 -1
  15. package/dist/base-plugin/viewtemplates/edit.js +17 -341
  16. package/dist/base-plugin/viewtemplates/edit.js.map +1 -1
  17. package/dist/base-plugin/viewtemplates/filter.d.ts.map +1 -1
  18. package/dist/base-plugin/viewtemplates/filter.js +9 -3
  19. package/dist/base-plugin/viewtemplates/filter.js.map +1 -1
  20. package/dist/base-plugin/viewtemplates/show.d.ts.map +1 -1
  21. package/dist/base-plugin/viewtemplates/show.js +1 -0
  22. package/dist/base-plugin/viewtemplates/show.js.map +1 -1
  23. package/dist/base-plugin/viewtemplates/viewable_fields.d.ts +16 -0
  24. package/dist/base-plugin/viewtemplates/viewable_fields.d.ts.map +1 -1
  25. package/dist/base-plugin/viewtemplates/viewable_fields.js +340 -3
  26. package/dist/base-plugin/viewtemplates/viewable_fields.js.map +1 -1
  27. package/dist/base-plugin/viewtemplates/workflow-room.d.ts.map +1 -1
  28. package/dist/base-plugin/viewtemplates/workflow-room.js +1 -1
  29. package/dist/base-plugin/viewtemplates/workflow-room.js.map +1 -1
  30. package/dist/db/connect.d.ts.map +1 -1
  31. package/dist/db/connect.js +4 -1
  32. package/dist/db/connect.js.map +1 -1
  33. package/dist/db/state.d.ts +11 -7
  34. package/dist/db/state.d.ts.map +1 -1
  35. package/dist/db/state.js +78 -12
  36. package/dist/db/state.js.map +1 -1
  37. package/dist/mobile-mocks/npm/dockerode.d.ts +1 -0
  38. package/dist/mobile-mocks/npm/dockerode.d.ts.map +1 -0
  39. package/dist/mobile-mocks/npm/dockerode.js +2 -0
  40. package/dist/mobile-mocks/npm/dockerode.js.map +1 -0
  41. package/dist/models/config.d.ts.map +1 -1
  42. package/dist/models/config.js +55 -12
  43. package/dist/models/config.js.map +1 -1
  44. package/dist/models/email.js.map +1 -1
  45. package/dist/models/expression.d.ts.map +1 -1
  46. package/dist/models/expression.js +29 -0
  47. package/dist/models/expression.js.map +1 -1
  48. package/dist/models/field.d.ts +5 -3
  49. package/dist/models/field.d.ts.map +1 -1
  50. package/dist/models/field.js +8 -2
  51. package/dist/models/field.js.map +1 -1
  52. package/dist/models/file.d.ts +18 -1
  53. package/dist/models/file.d.ts.map +1 -1
  54. package/dist/models/file.js +361 -43
  55. package/dist/models/file.js.map +1 -1
  56. package/dist/models/form.d.ts.map +1 -1
  57. package/dist/models/form.js.map +1 -1
  58. package/dist/models/index.d.ts.map +1 -1
  59. package/dist/models/internal/push_message_helper.d.ts +42 -12
  60. package/dist/models/internal/push_message_helper.d.ts.map +1 -1
  61. package/dist/models/internal/push_message_helper.js +117 -50
  62. package/dist/models/internal/push_message_helper.js.map +1 -1
  63. package/dist/models/internal/s3_helpers.d.ts +54 -0
  64. package/dist/models/internal/s3_helpers.d.ts.map +1 -0
  65. package/dist/models/internal/s3_helpers.js +561 -0
  66. package/dist/models/internal/s3_helpers.js.map +1 -0
  67. package/dist/models/model.d.ts +1 -1
  68. package/dist/models/model.d.ts.map +1 -1
  69. package/dist/models/model.js.map +1 -1
  70. package/dist/models/notification.d.ts.map +1 -1
  71. package/dist/models/notification.js +2 -5
  72. package/dist/models/notification.js.map +1 -1
  73. package/dist/models/page.d.ts +1 -0
  74. package/dist/models/page.d.ts.map +1 -1
  75. package/dist/models/page.js +27 -24
  76. package/dist/models/page.js.map +1 -1
  77. package/dist/models/plugin.d.ts +11 -3
  78. package/dist/models/plugin.d.ts.map +1 -1
  79. package/dist/models/plugin.js +51 -12
  80. package/dist/models/plugin.js.map +1 -1
  81. package/dist/models/s3_helpers.d.ts +54 -0
  82. package/dist/models/s3_helpers.d.ts.map +1 -0
  83. package/dist/models/s3_helpers.js +505 -0
  84. package/dist/models/s3_helpers.js.map +1 -0
  85. package/dist/models/scheduler.d.ts.map +1 -1
  86. package/dist/models/scheduler.js +4 -1
  87. package/dist/models/scheduler.js.map +1 -1
  88. package/dist/models/table.d.ts +8 -3
  89. package/dist/models/table.d.ts.map +1 -1
  90. package/dist/models/table.js +121 -32
  91. package/dist/models/table.js.map +1 -1
  92. package/dist/models/user.d.ts +4 -4
  93. package/dist/models/user.d.ts.map +1 -1
  94. package/dist/models/user.js.map +1 -1
  95. package/dist/models/view.js +1 -1
  96. package/dist/models/view.js.map +1 -1
  97. package/dist/models/workflow_run.d.ts +1 -1
  98. package/dist/models/workflow_run.d.ts.map +1 -1
  99. package/dist/models/workflow_run.js +8 -2
  100. package/dist/models/workflow_run.js.map +1 -1
  101. package/dist/models/workflow_step.js +1 -1
  102. package/dist/models/workflow_step.js.map +1 -1
  103. package/dist/plugin-helper.d.ts.map +1 -1
  104. package/dist/plugin-helper.js +139 -15
  105. package/dist/plugin-helper.js.map +1 -1
  106. package/dist/standard-menu.js +2 -2
  107. package/dist/standard-menu.js.map +1 -1
  108. package/dist/test-utils/mocks.d.ts +203 -0
  109. package/dist/test-utils/mocks.d.ts.map +1 -0
  110. package/dist/test-utils/mocks.js +329 -0
  111. package/dist/test-utils/mocks.js.map +1 -0
  112. package/dist/tests/assertions.d.ts.map +1 -1
  113. package/dist/tests/assertions.js +10 -9
  114. package/dist/tests/assertions.js.map +1 -1
  115. package/dist/tests/auth.test.js +0 -68
  116. package/dist/tests/auth.test.js.map +1 -1
  117. package/dist/tests/calc.test.js +3 -62
  118. package/dist/tests/calc.test.js.map +1 -1
  119. package/dist/tests/exact_views.test.js +14 -14
  120. package/dist/tests/exact_views.test.js.map +1 -1
  121. package/dist/tests/show.test.js +1 -1
  122. package/dist/tests/show.test.js.map +1 -1
  123. package/dist/tests/table.test.js +0 -37
  124. package/dist/tests/table.test.js.map +1 -1
  125. package/dist/tests/table_sync_info.test.d.ts +2 -0
  126. package/dist/tests/table_sync_info.test.d.ts.map +1 -0
  127. package/dist/tests/table_sync_info.test.js +62 -0
  128. package/dist/tests/table_sync_info.test.js.map +1 -0
  129. package/dist/tests/user.test.js +4 -29
  130. package/dist/tests/user.test.js.map +1 -1
  131. package/dist/tests/view.test.js +0 -3
  132. package/dist/tests/view.test.js.map +1 -1
  133. package/dist/utils.d.ts +11 -1
  134. package/dist/utils.d.ts.map +1 -1
  135. package/dist/utils.js +33 -3
  136. package/dist/utils.js.map +1 -1
  137. package/dist/viewable_fields.d.ts +172 -0
  138. package/dist/viewable_fields.d.ts.map +1 -0
  139. package/dist/viewable_fields.js +1562 -0
  140. package/dist/viewable_fields.js.map +1 -0
  141. package/dist/web-mobile-commons.d.ts +1 -1
  142. package/dist/web-mobile-commons.d.ts.map +1 -1
  143. package/dist/web-mobile-commons.js +19 -3
  144. package/dist/web-mobile-commons.js.map +1 -1
  145. package/package.json +14 -9
  146. package/webpack.config.js +3 -0
@@ -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
- const { getState } = require("../db/state");
77
- const state = getState();
78
- const useS3 = state?.getConfig("storage_s3_enabled");
79
- if (useS3 || where?.inDB) {
80
- if (where?.inDB)
81
- delete where.inDB;
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 = state?.getConfig("storage_s3_enabled");
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 files = await File.find({ id: +where });
311
- return files[0];
482
+ const relative = File.relativePathFromFieldValue(where);
483
+ const lookup = relative === null ? where : relative;
484
+ return await File.findOneS3(lookup);
312
485
  }
313
- else {
314
- const tenant = db_1.default.getTenantSchema();
315
- const safeDir = path.normalize(where).replace(/^(\.\.(\/|\\|$))+/, "");
316
- const absoluteFolder = path.join(db_1.default.connectObj.file_store, tenant, safeDir);
317
- const name = path.basename(absoluteFolder);
318
- const dir = path.dirname(absoluteFolder);
319
- try {
320
- return await File.from_file_on_disk(name, dir);
321
- }
322
- catch (e) {
323
- state?.log(2, e?.toString ? e.toString() : e);
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 ? new File(files[0]) : null;
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 && this.id)
349
- return this.id;
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
- this.user_id = user_id;
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
- await db_1.default.query(`update ${schema}"${db_1.default.sqlsanitize(table.name)}" set "${field.name}" = $1 where "${field.name}" = $2`, [to, from]);
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 { getState } = require("../db/state");
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
- const file_store = !useS3 ? db_1.default.connectObj.file_store : "saltcorn/";
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
- await fsp.writeFile(this.location, contents);
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
- //const { id, ...rest } = file;
619
- // insert file descriptor row to database
620
- //file.id = await db.insert("_sc_files", rest);
621
- // refresh file list cache
622
- await file.set_role(file.min_role_read);
623
- if (file.user_id)
624
- await file.set_user(file.user_id);
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
  /**