@saltcorn/data 1.5.0-beta.12 → 1.5.0-beta.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,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,9 @@ 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
+ const useS3 = (0, s3_helpers_1.isS3Enabled)();
81
+ if (useS3) {
82
+ return await File.findInS3(where, selectopts);
84
83
  }
85
84
  else {
86
85
  const relativeSearchFolder = where?.folder || "/";
@@ -144,11 +143,81 @@ class File {
144
143
  else
145
144
  return null;
146
145
  }
146
+ static isAbsoluteURL(value) {
147
+ return typeof value === "string" && ABSOLUTE_URL_RE.test(value);
148
+ }
149
+ static fieldValueFromRelative(relPath) {
150
+ if (!relPath)
151
+ return relPath || "";
152
+ const cleaned = relPath.replace(/^[\/]+/, "").replace(/\\/g, "/");
153
+ return cleaned;
154
+ }
155
+ static relativePathFromFieldValue(value) {
156
+ if (typeof value !== "string")
157
+ return null;
158
+ const trimmed = value.trim();
159
+ if (!trimmed)
160
+ return "";
161
+ if (File.isAbsoluteURL(trimmed)) {
162
+ if ((0, s3_helpers_1.isS3Enabled)()) {
163
+ const rel = (0, s3_helpers_1.publicUrlToRelativePath)(trimmed);
164
+ if (rel)
165
+ return rel;
166
+ }
167
+ return null;
168
+ }
169
+ const normalised = File.normalise(trimmed).replace(/\\/g, "/");
170
+ return normalised.startsWith("/") ? normalised.slice(1) : normalised;
171
+ }
172
+ static normalizeFieldValueInput(value) {
173
+ if (typeof value !== "string")
174
+ return value || "";
175
+ const relative = File.relativePathFromFieldValue(value);
176
+ if (relative === null)
177
+ return value.trim();
178
+ return File.fieldValueFromRelative(relative);
179
+ }
180
+ static pathToServeUrl(value, opts = {}) {
181
+ if (!value)
182
+ return "";
183
+ const trimmed = value.trim();
184
+ if (File.isAbsoluteURL(trimmed))
185
+ return trimmed;
186
+ const relative = File.relativePathFromFieldValue(trimmed);
187
+ if (relative === null)
188
+ return trimmed;
189
+ if ((0, s3_helpers_1.isS3Enabled)()) {
190
+ try {
191
+ const { getState } = require("../db/state");
192
+ const direct = !!getState()?.getConfig("files_direct_s3_links");
193
+ if (direct && opts.preferDirect !== false) {
194
+ return (0, s3_helpers_1.getPublicFileUrl)(relative, {
195
+ download: opts.download,
196
+ filename: opts.filename || posix.basename(relative),
197
+ });
198
+ }
199
+ }
200
+ catch (e) {
201
+ // ignore and fall back to proxied serving
202
+ }
203
+ }
204
+ const cleaned = File.fieldValueFromRelative(relative);
205
+ const safePath = cleaned.replace(/^[\/]+/, "");
206
+ const route = opts.download ? "download" : "serve";
207
+ const prefix = opts.targetPrefix || "";
208
+ return `${prefix}/files/${route}/${safePath}`;
209
+ }
147
210
  static async rootFolder() {
211
+ if ((0, s3_helpers_1.isS3Enabled)())
212
+ return File.directoryFromS3Path("/");
148
213
  const tenant = db_1.default.getTenantSchema();
149
214
  return await File.from_file_on_disk("/", path.join(db_1.default.connectObj.file_store, tenant));
150
215
  }
151
216
  static absPathToServePath(absPath) {
217
+ if ((0, s3_helpers_1.isS3Enabled)()) {
218
+ const rel = File.normalise(`${absPath}`);
219
+ return rel.startsWith("/") ? rel.slice(1) : rel;
220
+ }
152
221
  if (typeof absPath === "number")
153
222
  return `${absPath}`;
154
223
  const tenant = db_1.default.getTenantSchema();
@@ -166,6 +235,21 @@ class File {
166
235
  * @returns
167
236
  */
168
237
  static async allDirectories(ignoreCache) {
238
+ if ((0, s3_helpers_1.isS3Enabled)()) {
239
+ const { files } = await (0, s3_helpers_1.listS3Folder)("/", { recursive: true });
240
+ const dirs = new Set(["/"]);
241
+ for (const obj of files) {
242
+ const relPath = obj.relativePath || "";
243
+ const dirName = relPath.includes("/") ? posix.dirname(relPath) : "";
244
+ if (!dirName || dirName === ".")
245
+ continue;
246
+ const parts = dirName.split("/").filter(Boolean);
247
+ for (let i = 1; i <= parts.length; i++) {
248
+ dirs.add(parts.slice(0, i).join("/"));
249
+ }
250
+ }
251
+ return Array.from(dirs).map((dir) => File.directoryFromS3Path(dir === "" ? "/" : dir));
252
+ }
169
253
  const tenant = db_1.default.getTenantSchema();
170
254
  if (!ignoreCache) {
171
255
  const cache = File.getDirCache();
@@ -233,6 +317,91 @@ class File {
233
317
  enableDirCache[db_1.default.getTenantSchema()] = false;
234
318
  dirCache[db_1.default.getTenantSchema()] = null;
235
319
  }
320
+ static async findInS3(where, selectopts = {}) {
321
+ const folder = typeof where?.folder === "string" ? where.folder : "/";
322
+ const recursive = !!selectopts.recursive || !!where?.search;
323
+ const { files, directories } = await (0, s3_helpers_1.listS3Folder)(folder, { recursive });
324
+ const results = [];
325
+ for (const dir of directories) {
326
+ const dirFile = File.directoryFromS3Path(dir || "/");
327
+ if (dirFile.filename?.startsWith(".") && !where?.hiddenFiles)
328
+ continue;
329
+ results.push(dirFile);
330
+ }
331
+ const headResults = await Promise.all(files.map((obj) => (0, s3_helpers_1.headObject)(obj.relativePath)));
332
+ for (const head of headResults) {
333
+ if (!head)
334
+ continue;
335
+ const file = File.fileFromS3Head(head);
336
+ if (file.filename?.startsWith(".") && !where?.hiddenFiles)
337
+ continue;
338
+ results.push(file);
339
+ }
340
+ let filtered = results;
341
+ if (where?.filename) {
342
+ filtered = filtered.filter((f) => f.filename === where.filename);
343
+ }
344
+ if (where?.mime_super) {
345
+ filtered = filtered.filter((f) => f.mime_super === where.mime_super);
346
+ }
347
+ if (where?.mime_sub) {
348
+ filtered = filtered.filter((f) => f.mime_sub === where.mime_sub);
349
+ }
350
+ if (where?.isDirectory !== undefined) {
351
+ filtered = filtered.filter((f) => !!f.isDirectory === !!where.isDirectory);
352
+ }
353
+ if (where?.search) {
354
+ filtered = filtered.filter((f) => f.filename.includes(where.search));
355
+ }
356
+ return filtered.sort((a, b) => a.filename.localeCompare(b.filename));
357
+ }
358
+ static async findOneS3(where) {
359
+ if (typeof where === "string") {
360
+ const safePath = File.normalise(where);
361
+ const head = await (0, s3_helpers_1.headObject)(safePath);
362
+ return head ? File.fileFromS3Head(head) : null;
363
+ }
364
+ const results = await File.findInS3(where);
365
+ return results[0] || null;
366
+ }
367
+ static fileFromS3Head(info) {
368
+ const metadata = info.metadata || {};
369
+ const mime = metadata.mime_super && metadata.mime_sub
370
+ ? `${metadata.mime_super}/${metadata.mime_sub}`
371
+ : File.nameToMimeType(metadata.filename || info.relativePath) || "";
372
+ const [mime_super, mime_sub] = mime ? mime.split("/") : ["", ""];
373
+ return new File({
374
+ filename: metadata.filename || posix.basename(info.relativePath),
375
+ location: info.relativePath,
376
+ uploaded_at: info.lastModified || new Date(),
377
+ size_kb: Math.max(1, Math.round((info.size || 0) / 1024)),
378
+ mime_super,
379
+ mime_sub,
380
+ min_role_read: metadata.min_role_read || DEFAULT_MIN_ROLE_READ,
381
+ user_id: metadata.user_id,
382
+ s3_store: true,
383
+ });
384
+ }
385
+ static directoryFromS3Path(dirPath) {
386
+ const normalised = File.normalise(dirPath) || "/";
387
+ const filename = normalised === "/" ? "/" : posix.basename(normalised);
388
+ return new File({
389
+ filename,
390
+ location: normalised,
391
+ uploaded_at: new Date(),
392
+ size_kb: 0,
393
+ mime_super: "",
394
+ mime_sub: "",
395
+ min_role_read: DEFAULT_MIN_ROLE_READ,
396
+ s3_store: true,
397
+ isDirectory: true,
398
+ });
399
+ }
400
+ static relativeS3Path(key) {
401
+ const normalised = File.normalise(key);
402
+ const rel = (0, s3_helpers_1.relativeKeyToPath)(normalised);
403
+ return rel.startsWith("/") ? rel.slice(1) : rel;
404
+ }
236
405
  async is_symlink() {
237
406
  try {
238
407
  let stat = await fsp.lstat(this.location);
@@ -296,7 +465,7 @@ class File {
296
465
  if (typeof where === "string") {
297
466
  const { getState } = require("../db/state");
298
467
  const state = getState();
299
- const useS3 = state?.getConfig("storage_s3_enabled");
468
+ const useS3 = (0, s3_helpers_1.isS3Enabled)();
300
469
  //legacy serving ids
301
470
  if (/^\d+$/.test(where)) {
302
471
  const legacy_file_id_locations = state?.getConfig("legacy_file_id_locations");
@@ -307,26 +476,25 @@ class File {
307
476
  }
308
477
  }
309
478
  if (useS3) {
310
- const files = await File.find({ id: +where });
311
- return files[0];
479
+ const relative = File.relativePathFromFieldValue(where);
480
+ const lookup = relative === null ? where : relative;
481
+ return await File.findOneS3(lookup);
312
482
  }
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
- }
483
+ const tenant = db_1.default.getTenantSchema();
484
+ const safeDir = path.normalize(where).replace(/^(\.\.(\/|\\|$))+/, "");
485
+ const absoluteFolder = path.join(db_1.default.connectObj.file_store, tenant, safeDir);
486
+ const name = path.basename(absoluteFolder);
487
+ const dir = path.dirname(absoluteFolder);
488
+ try {
489
+ return await File.from_file_on_disk(name, dir);
490
+ }
491
+ catch (e) {
492
+ state?.log(2, e?.toString ? e.toString() : e);
493
+ return null;
326
494
  }
327
495
  }
328
496
  const files = await File.find(where);
329
- return files.length > 0 ? new File(files[0]) : null;
497
+ return files.length > 0 ? files[0] : null;
330
498
  }
331
499
  /**
332
500
  * Create new folder
@@ -334,6 +502,23 @@ class File {
334
502
  * @param inFolder
335
503
  */
336
504
  static async new_folder(name, inFolder = "") {
505
+ if ((0, s3_helpers_1.isS3Enabled)()) {
506
+ const folderName = File.normalise(name).replace(/\\/g, "/");
507
+ const parent = File.normalise(inFolder).replace(/\\/g, "/");
508
+ const target = [parent, folderName]
509
+ .filter((s) => s && s !== ".")
510
+ .join("/");
511
+ const placeholder = target
512
+ ? `${target.replace(/\/$/, "")}/.keep`
513
+ : ".keep";
514
+ await (0, s3_helpers_1.uploadBuffer)(placeholder, Buffer.alloc(0), "application/octet-stream", {
515
+ min_role_read: DEFAULT_MIN_ROLE_READ,
516
+ filename: ".keep",
517
+ mime_super: "application",
518
+ mime_sub: "octet-stream",
519
+ });
520
+ return;
521
+ }
337
522
  const tenant = db_1.default.getTenantSchema();
338
523
  const safeDir = path.normalize(name).replace(/^(\.\.(\/|\\|$))+/, "");
339
524
  const safeInDir = path.normalize(inFolder).replace(/^(\.\.(\/|\\|$))+/, "");
@@ -345,12 +530,19 @@ class File {
345
530
  * Get path to serve
346
531
  */
347
532
  get path_to_serve() {
348
- if (this.s3_store && this.id)
349
- return this.id;
533
+ if (this.s3_store) {
534
+ const rel = File.normalise(this.location);
535
+ return rel.startsWith("/") ? rel.slice(1) : rel;
536
+ }
350
537
  const tenant = db_1.default.getTenantSchema();
351
538
  const s = this.location.replace(path.join(db_1.default.connectObj.file_store, tenant), "");
352
539
  return s[0] === "/" ? s.substring(1) : s;
353
540
  }
541
+ get field_value() {
542
+ if (this.isDirectory)
543
+ return this.path_to_serve;
544
+ return File.fieldValueFromRelative(this.path_to_serve);
545
+ }
354
546
  /**
355
547
  * Get current folder
356
548
  */
@@ -372,26 +564,45 @@ class File {
372
564
  * @param min_role_read target Role for file to read
373
565
  */
374
566
  async set_role(min_role_read) {
567
+ this.min_role_read = min_role_read;
568
+ if (this.s3_store) {
569
+ await this.persistS3Metadata();
570
+ return;
571
+ }
375
572
  if (this.id) {
376
573
  await File.update(this.id, { min_role_read });
377
574
  }
378
575
  else {
379
576
  await xattr_set(this.location, "user.saltcorn.min_role_read", `${min_role_read}`);
380
577
  }
381
- this.min_role_read = min_role_read;
382
578
  }
383
579
  /**
384
580
  * Set user for file
385
581
  * @param user_id target user_id
386
582
  */
387
583
  async set_user(user_id) {
584
+ this.user_id = user_id;
585
+ if (this.s3_store) {
586
+ await this.persistS3Metadata();
587
+ return;
588
+ }
388
589
  if (this.id) {
389
590
  await File.update(this.id, { user_id });
390
591
  }
391
592
  else {
392
593
  await xattr_set(this.location, "user.saltcorn.user_id", `${user_id}`);
393
594
  }
394
- this.user_id = user_id;
595
+ }
596
+ async persistS3Metadata() {
597
+ if (!this.s3_store)
598
+ return;
599
+ await (0, s3_helpers_1.setObjectMetadata)(this.location, {
600
+ min_role_read: this.min_role_read,
601
+ user_id: this.user_id,
602
+ mime_super: this.mime_super,
603
+ mime_sub: this.mime_sub,
604
+ filename: this.filename,
605
+ });
395
606
  }
396
607
  /**
397
608
  * Rename file
@@ -399,6 +610,28 @@ class File {
399
610
  */
400
611
  async rename(filenameIn) {
401
612
  const filename = File.normalise(filenameIn);
613
+ if (this.s3_store) {
614
+ const currentDir = this.location.includes("/")
615
+ ? posix.dirname(this.location)
616
+ : "";
617
+ const newRel = currentDir
618
+ ? `${currentDir}/${filename}`.replace(/\\/g, "/")
619
+ : filename;
620
+ await (0, s3_helpers_1.copyObject)(this.location, newRel, {
621
+ metadata: {
622
+ min_role_read: this.min_role_read,
623
+ user_id: this.user_id,
624
+ mime_super: this.mime_super,
625
+ mime_sub: this.mime_sub,
626
+ filename,
627
+ },
628
+ });
629
+ await (0, s3_helpers_1.deleteObject)(this.location);
630
+ await File.update_table_references(this.path_to_serve, newRel);
631
+ this.location = newRel;
632
+ this.filename = filename;
633
+ return;
634
+ }
402
635
  if (this.id) {
403
636
  await File.update(this.id, { filename });
404
637
  }
@@ -420,11 +653,18 @@ class File {
420
653
  const Table = require("./table");
421
654
  const fileFields = await Field.find({ type: "File" }, { cached: true });
422
655
  const schema = db_1.default.getTenantSchemaPrefix();
656
+ const targetValue = File.fieldValueFromRelative(to);
657
+ const fromValues = new Set([from]);
658
+ if ((0, s3_helpers_1.isS3Enabled)()) {
659
+ fromValues.add(File.fieldValueFromRelative(from));
660
+ }
423
661
  for (const field of fileFields) {
424
662
  const table = Table.findOne({ id: field.table_id });
425
663
  if (table.external || table.provider_name)
426
664
  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]);
665
+ for (const fromValue of fromValues) {
666
+ await db_1.default.query(`update ${schema}"${db_1.default.sqlsanitize(table.name)}" set "${field.name}" = $1 where "${field.name}" = $2`, [targetValue, fromValue]);
667
+ }
428
668
  }
429
669
  }
430
670
  /**
@@ -433,6 +673,26 @@ class File {
433
673
  */
434
674
  async move_to_dir(newFolder) {
435
675
  const newFolderNormd = File.normalise(newFolder);
676
+ if (this.s3_store) {
677
+ const relative = newFolderNormd
678
+ .replace(/\\/g, "/")
679
+ .replace(/^(\/)+/, "")
680
+ .replace(/\/$/, "");
681
+ const newRel = relative ? `${relative}/${this.filename}` : this.filename;
682
+ await (0, s3_helpers_1.copyObject)(this.location, newRel, {
683
+ metadata: {
684
+ min_role_read: this.min_role_read,
685
+ user_id: this.user_id,
686
+ mime_super: this.mime_super,
687
+ mime_sub: this.mime_sub,
688
+ filename: this.filename,
689
+ },
690
+ });
691
+ await (0, s3_helpers_1.deleteObject)(this.location);
692
+ await File.update_table_references(this.path_to_serve, newRel);
693
+ this.location = newRel;
694
+ return;
695
+ }
436
696
  const tenant = db_1.default.getTenantSchema();
437
697
  const file_store = db_1.default.connectObj.file_store;
438
698
  const newPath = path.join(file_store, tenant, newFolderNormd, this.filename);
@@ -448,12 +708,13 @@ class File {
448
708
  * @returns {string} - path to file
449
709
  */
450
710
  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");
711
+ const useS3 = (0, s3_helpers_1.isS3Enabled)();
455
712
  const tenant = db_1.default.getTenantSchema();
456
- const file_store = !useS3 ? db_1.default.connectObj.file_store : "saltcorn/";
713
+ if (useS3) {
714
+ const relative = (suggest || (0, uuid_1.v4)()).replace(/\\/g, "/");
715
+ return (0, s3_helpers_1.buildKeyFromRelative)(relative);
716
+ }
717
+ const file_store = db_1.default.connectObj.file_store;
457
718
  const newFnm = suggest || (0, uuid_1.v4)();
458
719
  let newPath = (0, path_1.join)(file_store, tenant, newFnm);
459
720
  if (renameIfExisting && fs.existsSync(newPath)) {
@@ -529,6 +790,12 @@ class File {
529
790
  }
530
791
  }
531
792
  async get_contents(encoding) {
793
+ if (this.s3_store) {
794
+ const buffer = await (0, s3_helpers_1.downloadBuffer)(this.location);
795
+ return encoding
796
+ ? buffer.toString(encoding)
797
+ : buffer;
798
+ }
532
799
  return await fsp.readFile(this.location, encoding);
533
800
  }
534
801
  /**
@@ -548,6 +815,27 @@ class File {
548
815
  const [mime_super, mime_sub] = mimetype.split("/");
549
816
  // move file in file system to newPath
550
817
  const contents1 = contents instanceof ArrayBuffer ? Buffer.from(contents) : contents;
818
+ if ((0, s3_helpers_1.isS3Enabled)()) {
819
+ const relativePath = File.relativeS3Path(newPath);
820
+ await (0, s3_helpers_1.uploadBuffer)(relativePath, Buffer.from(contents1), mimetype, {
821
+ min_role_read,
822
+ user_id,
823
+ mime_super,
824
+ mime_sub,
825
+ filename: path.basename(name),
826
+ });
827
+ return await File.create({
828
+ filename: path.basename(name),
829
+ location: relativePath,
830
+ uploaded_at: new Date(),
831
+ size_kb: Math.round(contents1.length / 1024),
832
+ user_id,
833
+ mime_super,
834
+ mime_sub,
835
+ min_role_read,
836
+ s3_store: true,
837
+ });
838
+ }
551
839
  await fsp.writeFile(newPath, contents1);
552
840
  // create file
553
841
  const file = await File.create({
@@ -564,7 +852,18 @@ class File {
564
852
  return file;
565
853
  }
566
854
  async overwrite_contents(contents) {
567
- await fsp.writeFile(this.location, contents);
855
+ const data = contents instanceof ArrayBuffer ? Buffer.from(contents) : contents;
856
+ if (this.s3_store) {
857
+ await (0, s3_helpers_1.uploadBuffer)(this.location, Buffer.from(data), this.mimetype, {
858
+ min_role_read: this.min_role_read,
859
+ user_id: this.user_id,
860
+ mime_super: this.mime_super,
861
+ mime_sub: this.mime_sub,
862
+ filename: this.filename,
863
+ });
864
+ return;
865
+ }
866
+ await fsp.writeFile(this.location, data);
568
867
  }
569
868
  /**
570
869
  * Delete file
@@ -576,6 +875,20 @@ class File {
576
875
  if (this.id)
577
876
  await db_1.default.deleteWhere("_sc_files", { id: this.id });
578
877
  // delete name and possible file from file system
878
+ if (this.s3_store) {
879
+ if (this.isDirectory) {
880
+ const { files } = await (0, s3_helpers_1.listS3Folder)(this.location, {
881
+ recursive: true,
882
+ });
883
+ for (const obj of files) {
884
+ await (0, s3_helpers_1.deleteObject)(obj.relativePath);
885
+ }
886
+ }
887
+ else {
888
+ await (0, s3_helpers_1.deleteObject)(this.location);
889
+ }
890
+ return;
891
+ }
579
892
  if (unlinker)
580
893
  await unlinker(this);
581
894
  else if (this.isDirectory) {
@@ -615,13 +928,15 @@ class File {
615
928
  */
616
929
  static async create(f) {
617
930
  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);
931
+ if (file.s3_store) {
932
+ file.location = File.relativeS3Path(file.location);
933
+ await file.persistS3Metadata();
934
+ }
935
+ else {
936
+ await file.set_role(file.min_role_read);
937
+ if (file.user_id)
938
+ await file.set_user(file.user_id);
939
+ }
625
940
  return file;
626
941
  }
627
942
  /**