@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.
- package/dist/base-plugin/fileviews.d.ts +3 -3
- package/dist/base-plugin/fileviews.js +17 -6
- 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/viewtemplates/edit.d.ts +2 -2
- package/dist/base-plugin/viewtemplates/edit.d.ts.map +1 -1
- package/dist/base-plugin/viewtemplates/edit.js +14 -12
- package/dist/base-plugin/viewtemplates/edit.js.map +1 -1
- package/dist/models/config.js +7 -8
- package/dist/models/config.js.map +1 -1
- package/dist/models/field.js +1 -1
- 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 +358 -43
- package/dist/models/file.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 +505 -0
- package/dist/models/internal/s3_helpers.js.map +1 -0
- 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/package.json +8 -8
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,9 @@ class File {
|
|
|
73
77
|
* @returns {Promise<*>}
|
|
74
78
|
*/
|
|
75
79
|
static async find(where, selectopts = {}) {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
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 =
|
|
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
|
|
311
|
-
|
|
479
|
+
const relative = File.relativePathFromFieldValue(where);
|
|
480
|
+
const lookup = relative === null ? where : relative;
|
|
481
|
+
return await File.findOneS3(lookup);
|
|
312
482
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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 ?
|
|
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
|
|
349
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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
|
/**
|